diff --git a/contracts/TornadoPool.sol b/contracts/TornadoPool.sol index 6cd3a24..22d5ba9 100644 --- a/contracts/TornadoPool.sol +++ b/contracts/TornadoPool.sol @@ -57,7 +57,7 @@ contract TornadoPool is ReentrancyGuard { bytes calldata _proof, bytes32 _root, bytes32 _newRoot, - bytes32[2] calldata _inputNullifiers, + bytes32[] calldata _inputNullifiers, bytes32[2] calldata _outputCommitments, uint256 _extAmount, uint256 _fee, @@ -67,24 +67,56 @@ contract TornadoPool is ReentrancyGuard { external payable nonReentrant { require(currentRoot == _root, "Invalid merkle root"); - require(!isSpent(_inputNullifiers[0]), "Input 0 is already spent"); - require(!isSpent(_inputNullifiers[1]), "Input 1 is already spent"); + for(uint256 i = 0; i < _inputNullifiers.length; i++) { + require(!isSpent(_inputNullifiers[i]), "Input is already spent"); + } require(uint256(_extDataHash) == uint256(keccak256(abi.encode(_extData))) % FIELD_SIZE, "Incorrect external data hash"); - require(verifier2.verifyProof(_proof, [ - uint256(_root), - uint256(_newRoot), - uint256(_inputNullifiers[0]), - uint256(_inputNullifiers[1]), - uint256(_outputCommitments[0]), - uint256(_outputCommitments[1]), - _extAmount, - _fee, - uint256(_extDataHash) - ]), "Invalid transaction proof"); + if (_inputNullifiers.length == 2) { + require(verifier2.verifyProof(_proof, [ + uint256(_root), + uint256(_newRoot), + uint256(_inputNullifiers[0]), + uint256(_inputNullifiers[1]), + uint256(_outputCommitments[0]), + uint256(_outputCommitments[1]), + _extAmount, + _fee, + uint256(_extDataHash) + ]), "Invalid transaction proof"); + } else if (_inputNullifiers.length == 16) { + require(verifier16.verifyProof(_proof, [ + uint256(_root), + uint256(_newRoot), + uint256(_inputNullifiers[0]), + uint256(_inputNullifiers[1]), + uint256(_inputNullifiers[2]), + uint256(_inputNullifiers[3]), + uint256(_inputNullifiers[4]), + uint256(_inputNullifiers[5]), + uint256(_inputNullifiers[6]), + uint256(_inputNullifiers[7]), + uint256(_inputNullifiers[8]), + uint256(_inputNullifiers[9]), + uint256(_inputNullifiers[10]), + uint256(_inputNullifiers[11]), + uint256(_inputNullifiers[12]), + uint256(_inputNullifiers[13]), + uint256(_inputNullifiers[14]), + uint256(_inputNullifiers[15]), + uint256(_outputCommitments[0]), + uint256(_outputCommitments[1]), + _extAmount, + _fee, + uint256(_extDataHash) + ]), "Invalid transaction proof"); + } else { + revert("unsupported input count"); + } currentRoot = _newRoot; - nullifierHashes[_inputNullifiers[0]] = true; - nullifierHashes[_inputNullifiers[1]] = true; + for(uint256 i = 0; i < _inputNullifiers.length; i++) { + nullifierHashes[_inputNullifiers[i]] = true; + } int256 extAmount = calculateExternalAmount(_extAmount); if (extAmount > 0) { @@ -101,9 +133,9 @@ contract TornadoPool is ReentrancyGuard { // todo enforce currentCommitmentIndex value in snark emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++, _extData.encryptedOutput1); emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++, _extData.encryptedOutput2); - emit NewNullifier(_inputNullifiers[0]); - emit NewNullifier(_inputNullifiers[1]); - // emit Transaction(); + for(uint256 i = 0; i < _inputNullifiers.length; i++) { + emit NewNullifier(_inputNullifiers[i]); + } } function calculateExternalAmount(uint256 _extAmount) public pure returns(int256) { diff --git a/src/index.js b/src/index.js index 3a02403..80cd60d 100644 --- a/src/index.js +++ b/src/index.js @@ -126,6 +126,30 @@ async function deposit({ tornadoPool }) { return outputs[0] } +async function merge({ tornadoPool }) { + const amount = 1e6 + const inputs = new Array(16).fill(0).map(_ => new Utxo()) + const outputs = [new Utxo({ amount }), new Utxo()] + + const { proof, args } = await getProof({ + inputs, + outputs, + tree: await buildMerkleTree({ tornadoPool }), + extAmount: amount, + fee: 0, + recipient: 0, + relayer: 0, + }) + + console.log('Sending merge transaction...', proof, args) + const receipt = await tornadoPool.transaction(proof, ...args, { + value: amount, + gasLimit: 1e6, + }) + console.log(`Receipt ${receipt.hash}`) + return outputs[0] +} + async function transact({ tornadoPool, utxo }) { const inputs = [utxo, new Utxo()] const outputs = [ @@ -168,4 +192,4 @@ async function withdraw({ tornadoPool, utxo, recipient }) { console.log(`Receipt ${receipt.hash}`) } -module.exports = { deposit, withdraw, transact } +module.exports = { deposit, withdraw, transact, merge } diff --git a/test/full.test.js b/test/full.test.js index 90616b7..7776d06 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -7,7 +7,7 @@ const { poseidonHash2, toFixedHex, takeSnapshot, revertSnapshot } = require('../ const MERKLE_TREE_HEIGHT = 5 const MerkleTree = require('fixed-merkle-tree') -const { deposit, transact, withdraw } = require('../src/index') +const { deposit, transact, withdraw, merge } = require('../src/index') describe('TornadoPool', () => { let snapshotId, tornadoPool @@ -42,6 +42,10 @@ describe('TornadoPool', () => { expect(bal).to.be.gt(0) }) + it('should work with 16 inputs', async function () { + const utxo1 = await merge({tornadoPool}) + }) + afterEach(async () => { await revertSnapshot(snapshotId) snapshotId = await takeSnapshot()