tornado-anonymity-mining/circuits/Reward.circom

151 lines
5.6 KiB
Plaintext

include "../node_modules/circomlib/circuits/poseidon.circom";
include "../node_modules/circomlib/circuits/bitify.circom";
include "../node_modules/circomlib/circuits/comparators.circom";
include "./Utils.circom";
include "./MerkleTree.circom";
include "./MerkleTreeUpdater.circom";
template Reward(levels, zeroLeaf) {
signal input rate;
signal input fee;
signal input instance;
signal input rewardNullifier;
signal input extDataHash;
signal private input noteSecret;
signal private input noteNullifier;
signal private input inputAmount;
signal private input inputSecret;
signal private input inputNullifier;
signal input inputRoot;
signal private input inputPathElements[levels];
signal private input inputPathIndices;
signal input inputNullifierHash;
signal private input outputAmount;
signal private input outputSecret;
signal private input outputNullifier;
signal input outputRoot;
signal input outputPathIndices;
signal private input outputPathElements[levels];
signal input outputCommitment;
signal private input depositBlock;
signal input depositRoot;
signal private input depositPathIndices;
signal private input depositPathElements[levels];
signal private input withdrawalBlock;
signal input withdrawalRoot;
signal private input withdrawalPathIndices;
signal private input withdrawalPathElements[levels];
// Check amount invariant
inputAmount + rate * (withdrawalBlock - depositBlock) === outputAmount + fee;
// === check input and output accounts and block range ===
// Check that amounts fit into 248 bits to prevent overflow
// Fee range is checked by the smart contract
// Technically block range check could be skipped because it can't be large enough
// negative number that `outputAmount` fits into 248 bits
component inputAmountCheck = Num2Bits(248);
component outputAmountCheck = Num2Bits(248);
component blockRangeCheck = Num2Bits(32);
inputAmountCheck.in <== inputAmount;
outputAmountCheck.in <== outputAmount;
blockRangeCheck.in <== withdrawalBlock - depositBlock;
// Compute input commitment
component inputHasher = Poseidon(3);
inputHasher.inputs[0] <== inputAmount;
inputHasher.inputs[1] <== inputSecret;
inputHasher.inputs[2] <== inputNullifier;
// Verify that input commitment exists in the tree
component inputTree = MerkleTree(levels);
inputTree.leaf <== inputHasher.out;
inputTree.pathIndices <== inputPathIndices;
for (var i = 0; i < levels; i++) {
inputTree.pathElements[i] <== inputPathElements[i];
}
// Check merkle proof only if amount is non-zero
component checkRoot = ForceEqualIfEnabled();
checkRoot.in[0] <== inputRoot;
checkRoot.in[1] <== inputTree.root;
checkRoot.enabled <== inputAmount;
// Verify input nullifier hash
component inputNullifierHasher = Poseidon(1);
inputNullifierHasher.inputs[0] <== inputNullifier;
inputNullifierHasher.out === inputNullifierHash;
// Compute and verify output commitment
component outputHasher = Poseidon(3);
outputHasher.inputs[0] <== outputAmount;
outputHasher.inputs[1] <== outputSecret;
outputHasher.inputs[2] <== outputNullifier;
outputHasher.out === outputCommitment;
// Update accounts tree with output account commitment
component accountTreeUpdater = MerkleTreeUpdater(levels, zeroLeaf);
accountTreeUpdater.oldRoot <== inputRoot;
accountTreeUpdater.newRoot <== outputRoot;
accountTreeUpdater.leaf <== outputCommitment;
accountTreeUpdater.pathIndices <== outputPathIndices;
for (var i = 0; i < levels; i++) {
accountTreeUpdater.pathElements[i] <== outputPathElements[i];
}
// === check deposit and withdrawal ===
// Compute tornado.cash commitment and nullifier
component noteHasher = TornadoCommitmentHasher();
noteHasher.nullifier <== noteNullifier;
noteHasher.secret <== noteSecret;
// Compute deposit commitment
component depositHasher = Poseidon(3);
depositHasher.inputs[0] <== instance;
depositHasher.inputs[1] <== noteHasher.commitment;
depositHasher.inputs[2] <== depositBlock;
// Verify that deposit commitment exists in the tree
component depositTree = MerkleTree(levels);
depositTree.leaf <== depositHasher.out;
depositTree.pathIndices <== depositPathIndices;
for (var i = 0; i < levels; i++) {
depositTree.pathElements[i] <== depositPathElements[i];
}
depositTree.root === depositRoot;
// Compute withdrawal commitment
component withdrawalHasher = Poseidon(3);
withdrawalHasher.inputs[0] <== instance;
withdrawalHasher.inputs[1] <== noteHasher.nullifierHash;
withdrawalHasher.inputs[2] <== withdrawalBlock;
// Verify that withdrawal commitment exists in the tree
component withdrawalTree = MerkleTree(levels);
withdrawalTree.leaf <== withdrawalHasher.out;
withdrawalTree.pathIndices <== withdrawalPathIndices;
for (var i = 0; i < levels; i++) {
withdrawalTree.pathElements[i] <== withdrawalPathElements[i];
}
withdrawalTree.root === withdrawalRoot;
// Compute reward nullifier
component rewardNullifierHasher = Poseidon(1);
rewardNullifierHasher.inputs[0] <== noteNullifier;
rewardNullifierHasher.out === rewardNullifier;
// Add hidden signals to make sure that tampering with recipient or fee will invalidate the snark proof
// Most likely it is not required, but it's better to stay on the safe side and it only takes 2 constraints
// Squares are used to prevent optimizer from removing those constraints
signal extDataHashSquare;
extDataHashSquare <== extDataHash * extDataHash;
}
// zeroLeaf = keccak256("tornado") % FIELD_SIZE
component main = Reward(20, 21663839004416932945382355908790599225266501822907911457504978515578255421292);