This commit is contained in:
Alexey 2021-02-02 22:46:51 +03:00
parent 6a52accbd6
commit 5fe2429b89
4 changed files with 115 additions and 49 deletions

@ -74,9 +74,9 @@ contract TornadoTrees is ITornadoTrees, EnsResolve {
emit DepositData(_instance, _commitment, blockNumber(), deposits.length - 1); emit DepositData(_instance, _commitment, blockNumber(), deposits.length - 1);
} }
function registerWithdrawal(address _instance, bytes32 _nullifier) external override onlyTornadoProxy { function registerWithdrawal(address _instance, bytes32 _nullifierHash) external override onlyTornadoProxy {
withdrawals.push(keccak256(abi.encode(_instance, _nullifier, blockNumber()))); withdrawals.push(keccak256(abi.encode(_instance, _nullifierHash, blockNumber())));
emit WithdrawalData(_instance, _nullifier, blockNumber(), withdrawals.length - 1); emit WithdrawalData(_instance, _nullifierHash, blockNumber(), withdrawals.length - 1);
} }
// todo !!! ensure that during migration the tree is filled evenly // todo !!! ensure that during migration the tree is filled evenly
@ -92,7 +92,6 @@ contract TornadoTrees is ITornadoTrees, EnsResolve {
require(_newRoot != previousDepositRoot, "Outdated deposit root"); require(_newRoot != previousDepositRoot, "Outdated deposit root");
require(_currentRoot == depositRoot, "Proposed deposit root is invalid"); require(_currentRoot == depositRoot, "Proposed deposit root is invalid");
require(_pathIndices == offset >> CHUNK_TREE_HEIGHT, "Incorrect insert index"); require(_pathIndices == offset >> CHUNK_TREE_HEIGHT, "Incorrect insert index");
require(uint256(_newRoot) < SNARK_FIELD, "Proposed root is out of range"); // optional
bytes memory data = new bytes(BYTES_SIZE); bytes memory data = new bytes(BYTES_SIZE);
assembly { assembly {
@ -101,12 +100,11 @@ contract TornadoTrees is ITornadoTrees, EnsResolve {
mstore(add(data, 0x20), _currentRoot) mstore(add(data, 0x20), _currentRoot)
} }
for (uint256 i = 0; i < CHUNK_SIZE; i++) { for (uint256 i = 0; i < CHUNK_SIZE; i++) {
(bytes32 hash, address instance, uint32 depositBlock) = (_events[i].hash, _events[i].instance, _events[i].block); (bytes32 hash, address instance, uint32 blockNumber) = (_events[i].hash, _events[i].instance, _events[i].block);
bytes32 leafHash = keccak256(abi.encode(instance, hash, depositBlock)); bytes32 leafHash = keccak256(abi.encode(instance, hash, blockNumber));
require(leafHash == deposits[offset + i], "Incorrect deposit"); require(leafHash == deposits[offset + i], "Incorrect deposit");
require(uint256(hash) < SNARK_FIELD, "Hash out of range"); // optional
assembly { assembly {
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x7c), depositBlock) mstore(add(add(data, mul(ITEM_SIZE, i)), 0x7c), blockNumber)
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x78), instance) mstore(add(add(data, mul(ITEM_SIZE, i)), 0x78), instance)
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x64), hash) mstore(add(add(data, mul(ITEM_SIZE, i)), 0x64), hash)
} }
@ -143,12 +141,12 @@ contract TornadoTrees is ITornadoTrees, EnsResolve {
mstore(add(data, 0x20), _currentRoot) mstore(add(data, 0x20), _currentRoot)
} }
for (uint256 i = 0; i < CHUNK_SIZE; i++) { for (uint256 i = 0; i < CHUNK_SIZE; i++) {
(bytes32 hash, address instance, uint32 withdrawalBlock) = (_events[i].hash, _events[i].instance, _events[i].block); (bytes32 hash, address instance, uint32 blockNumber) = (_events[i].hash, _events[i].instance, _events[i].block);
bytes32 leafHash = keccak256(abi.encode(instance, hash, withdrawalBlock)); bytes32 leafHash = keccak256(abi.encode(instance, hash, blockNumber));
require(leafHash == withdrawals[offset + i], "Incorrect withdrawal"); require(leafHash == withdrawals[offset + i], "Incorrect withdrawal");
require(uint256(hash) < SNARK_FIELD, "Hash out of range"); require(uint256(hash) < SNARK_FIELD, "Hash out of range");
assembly { assembly {
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x7c), withdrawalBlock) mstore(add(add(data, mul(ITEM_SIZE, i)), 0x7c), blockNumber)
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x78), instance) mstore(add(add(data, mul(ITEM_SIZE, i)), 0x78), instance)
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x64), hash) mstore(add(add(data, mul(ITEM_SIZE, i)), 0x64), hash)
} }

@ -1,7 +1,7 @@
const ethers = require('ethers') const ethers = require('ethers')
const BigNumber = ethers.BigNumber const BigNumber = ethers.BigNumber
const { bitsToNumber, toBuffer, poseidonHash } = require('./utils') const { bitsToNumber, toBuffer, toFixedHex, poseidonHash } = require('./utils')
const jsSHA = require('jssha') const jsSHA = require('jssha')
@ -79,7 +79,19 @@ function batchTreeUpdate(tree, events) {
} }
input.argsHash = hashInputs(input) input.argsHash = hashInputs(input)
return input
const args = [
toFixedHex(input.argsHash),
toFixedHex(input.oldRoot),
toFixedHex(input.newRoot),
toFixedHex(input.pathIndices, 4),
events.map((e) => ({
hash: toFixedHex(e.hash),
instance: toFixedHex(e.instance, 20),
block: toFixedHex(e.block, 4),
})),
]
return { input, args }
// const proofData = await websnarkUtils.genWitnessAndProve( // const proofData = await websnarkUtils.genWitnessAndProve(
// this.groth16, // this.groth16,
// input, // input,

@ -16,8 +16,8 @@ describe('Snark', () => {
block: randomBN(4).toString(), block: randomBN(4).toString(),
}) })
} }
const data = await batchTreeUpdate(tree, events) const { input } = batchTreeUpdate(tree, events)
const proof = await prove(data, './artifacts/circuits/BatchTreeUpdate') const proof = await prove(input, './artifacts/circuits/BatchTreeUpdate')
expect(proof.length).to.be.gt(0) expect(proof.length).to.be.gt(0)
}) })

@ -37,7 +37,8 @@ describe('TornadoTrees', function () {
let verifier let verifier
let tornadoTrees let tornadoTrees
let notes let notes
let events const depositEvents = []
const withdrawalEvents = []
beforeEach(async function () { beforeEach(async function () {
tree = new MerkleTree(levels, [], { hashFunction: poseidonHash2 }) tree = new MerkleTree(levels, [], { hashFunction: poseidonHash2 })
@ -65,44 +66,99 @@ describe('TornadoTrees', function () {
nullifierHash: randomBN(), nullifierHash: randomBN(),
} }
await register(notes[i], tornadoTrees, tornadoProxy) await register(notes[i], tornadoTrees, tornadoProxy)
depositEvents[i] = {
hash: toFixedHex(notes[i].commitment),
instance: toFixedHex(notes[i].instance, 20),
block: toFixedHex(notes[i].depositBlock, 4),
}
withdrawalEvents[i] = {
hash: toFixedHex(notes[i].nullifierHash),
instance: toFixedHex(notes[i].instance, 20),
block: toFixedHex(notes[i].withdrawalBlock, 4),
}
} }
events = notes.map((note) => ({
hash: toFixedHex(note.commitment),
instance: toFixedHex(note.instance, 20),
block: toFixedHex(note.depositBlock, 4),
}))
}) })
it('Should calculate hash', async function () { describe('#updateDepositTree', () => {
const data = await controller.batchTreeUpdate(tree, events) it('should check hash', async () => {
const solHash = await tornadoTrees.updateDepositTreeMock( const { args } = controller.batchTreeUpdate(tree, depositEvents)
toFixedHex(data.oldRoot), const solHash = await tornadoTrees.updateDepositTreeMock(...args.slice(1))
toFixedHex(data.newRoot), expect(solHash).to.be.equal(args[0])
toFixedHex(data.pathIndices, 4), })
events,
) it('should prove snark', async () => {
expect(solHash).to.be.equal(data.argsHash) const { input, args } = controller.batchTreeUpdate(tree, depositEvents)
const proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
const updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
})
it('should work for non-empty tree', async () => {
let { input, args } = controller.batchTreeUpdate(tree, depositEvents)
let proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
let updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
//
for (let i = 0; i < notes.length; i++) {
await register(notes[i], tornadoTrees, tornadoProxy)
}
;({ input, args } = controller.batchTreeUpdate(tree, depositEvents))
proof = await controller.prove(input, './artifacts/circuits/BatchTreeUpdate')
await tornadoTrees.updateDepositTree(proof, ...args)
updatedRoot = await tornadoTrees.depositRoot()
expect(updatedRoot).to.be.equal(tree.root())
})
it('should reject for partially filled tree')
it('should reject for outdated deposit root')
it('should reject for incorrect insert index')
it('should reject for overflows of newRoot')
it('should reject for invalid sha256 args')
}) })
it('Should calculate hash', async function () { describe('#getRegisteredDeposits', () => {
const data = await controller.batchTreeUpdate(tree, events) it('should work', async () => {
const proof = await controller.prove(data, './artifacts/circuits/BatchTreeUpdate') const abi = new ethers.utils.AbiCoder()
await tornadoTrees.updateDepositTree( let { count, _deposits } = await tornadoTrees.getRegisteredDeposits()
proof, expect(count).to.be.equal(notes.length)
toFixedHex(data.argsHash), _deposits.forEach((hash, i) => {
toFixedHex(data.oldRoot), const encodedData = abi.encode(
toFixedHex(data.newRoot), ['address', 'bytes32', 'uint256'],
toFixedHex(data.pathIndices, 4), [notes[i].instance, toFixedHex(notes[i].commitment), notes[i].depositBlock],
events, )
) const leaf = ethers.utils.keccak256(encodedData)
expect(await tornadoTrees.depositRoot()).to.be.equal(tree.root())
expect(leaf).to.be.equal(hash)
})
// res.length.should.be.equal(1)
// res[0].should.be.true
// await tornadoTrees.updateRoots([note1DepositLeaf], [])
// res = await tornadoTrees.getRegisteredDeposits()
// res.length.should.be.equal(0)
// await registerDeposit(note2, tornadoTrees)
// res = await tornadoTrees.getRegisteredDeposits()
// // res[0].should.be.true
})
}) })
it('should work for non-empty tree') describe('#getRegisteredWithdrawals', () => {
it('should reject for partially filled tree') it('should work', async () => {
it('should reject for outdated deposit root') const abi = new ethers.utils.AbiCoder()
it('should reject for incorrect insert index') let { count, _withdrawals } = await tornadoTrees.getRegisteredWithdrawals()
it('should reject for overflows of newRoot') expect(count).to.be.equal(notes.length)
it('should reject for invalid sha256 args') _withdrawals.forEach((hash, i) => {
const encodedData = abi.encode(
['address', 'bytes32', 'uint256'],
[notes[i].instance, toFixedHex(notes[i].nullifierHash), notes[i].withdrawalBlock],
)
const leaf = ethers.utils.keccak256(encodedData)
expect(leaf).to.be.equal(hash)
})
})
})
}) })