diff --git a/src/index.js b/src/index.js index 2f1d32e..d99f991 100644 --- a/src/index.js +++ b/src/index.js @@ -150,4 +150,4 @@ async function registerAndTransact({ tornadoPool, account, ...rest }) { await receipt.wait() } -module.exports = { transaction, registerAndTransact, prepareTransaction } +module.exports = { transaction, registerAndTransact, prepareTransaction, buildMerkleTree } diff --git a/test/full.test.js b/test/full.test.js index 9578a9c..2b4af3c 100644 --- a/test/full.test.js +++ b/test/full.test.js @@ -5,7 +5,8 @@ const { expect } = require('chai') const { utils } = ethers const Utxo = require('../src/utxo') -const { transaction, registerAndTransact, prepareTransaction } = require('../src/index') +const { transaction, registerAndTransact, prepareTransaction, buildMerkleTree } = require('../src/index') +const { toFixedHex, poseidonHash } = require('../src/utils') const { Keypair } = require('../src/keypair') const { encodeDataForBridge } = require('./utils') @@ -336,4 +337,66 @@ describe('TornadoPool', function () { outputs: [aliceDepositUtxo], }) }) + + it('should be compliant', async function () { + // basically verifier should check if a commitment and a nullifier hash are on chain + const { tornadoPool } = await loadFixture(fixture) + const aliceDepositAmount = utils.parseEther('0.07') + const aliceDepositUtxo = new Utxo({ amount: aliceDepositAmount }) + const [sender] = await ethers.getSigners() + + const { args, extData } = await prepareTransaction({ + tornadoPool, + outputs: [aliceDepositUtxo], + }) + const receipt = await tornadoPool.transact(args, extData, { + gasLimit: 2e6, + }) + await receipt.wait() + + // withdrawal + await transaction({ + tornadoPool, + inputs: [aliceDepositUtxo], + outputs: [], + recipient: sender.address, + }) + + const tree = await buildMerkleTree({ tornadoPool }) + const commitment = aliceDepositUtxo.getCommitment() + const index = tree.indexOf(toFixedHex(commitment)) // it's the same as merklePath and merklePathIndexes and index in the tree + aliceDepositUtxo.index = index + const nullifier = aliceDepositUtxo.getNullifier() + + // commitment = hash(amount, pubKey, blinding) + // nullifier = hash(commitment, merklePath, sign(merklePath, privKey)) + const dataForVerifier = { + commitment: { + amount: aliceDepositUtxo.amount, + pubkey: aliceDepositUtxo.keypair.pubkey, + blinding: aliceDepositUtxo.blinding, + }, + nullifier: { + merklePath: index, + signature: aliceDepositUtxo.keypair.sign(index), + }, + } + + // generateReport(dataForVerifier) -> compliance report + // on the verifier side we compute commitment and nullifier and then check them onchain + const commitmentV = poseidonHash([...Object.values(dataForVerifier.commitment)]) + const nullifierV = poseidonHash([ + commitmentV, + dataForVerifier.nullifier.merklePath, + dataForVerifier.nullifier.signature, + ]) + + expect(commitmentV).to.be.equal(commitment) + expect(nullifierV).to.be.equal(nullifier) + expect(await tornadoPool.nullifierHashes(nullifierV)).to.be.equal(true) + // expect commitmentV present onchain (it will be in NewCommitment events) + + // in report we can see the tx with NewCommitment event (this is how alice got money) + // and the tx with NewNullifier event is where alice spent the UTXO + }) })