tornado-nova/circuits/transaction.circom

134 lines
4.5 KiB
Plaintext
Raw Normal View History

2020-04-08 11:41:12 +02:00
include "./merkleTree.circom"
include "./treeUpdater.circom"
include "./utils.circom"
/*
Utxo structure:
{
amount,
blinding, // random number
pubkey,
}
commitment = hash(amount, blinding, pubKey)
nullifier = hash(commitment, privKey, merklePath)
*/
// Universal JoinSplit transaction with nIns inputs and 2 outputs
2021-06-09 12:58:57 +02:00
template Transaction(levels, nIns, nOuts, zeroLeaf) {
2020-04-08 11:41:12 +02:00
signal input root;
signal input newRoot;
// extAmount = external amount used for deposits and withdrawals
2020-04-08 11:41:12 +02:00
// correct extAmount range is enforced on the smart contract
2021-08-19 09:58:29 +02:00
// publicAmount = extAmount - fee
signal input publicAmount;
2021-06-07 12:12:15 +02:00
signal input extDataHash;
2020-04-08 11:41:12 +02:00
2021-06-09 12:58:57 +02:00
// data for transaction inputs
2021-06-16 13:01:29 +02:00
signal input inputNullifier[nIns];
2021-06-09 12:58:57 +02:00
signal private input inAmount[nIns];
signal private input inBlinding[nIns];
signal private input inPrivateKey[nIns];
signal private input inPathIndices[nIns];
signal private input inPathElements[nIns][levels];
// data for transaction outputs
2021-06-16 13:01:29 +02:00
signal input outputCommitment[nOuts];
2021-06-09 12:58:57 +02:00
signal private input outAmount[nOuts];
signal private input outBlinding[nOuts];
signal private input outPubkey[nOuts];
2021-06-16 13:01:29 +02:00
signal input outPathIndices;
2020-04-08 11:41:12 +02:00
signal private input outPathElements[levels - 1];
2021-06-09 12:58:57 +02:00
component inKeypair[nIns];
component inUtxoHasher[nIns];
component nullifierHasher[nIns];
component inAmountCheck[nIns];
component tree[nIns];
component checkRoot[nIns];
var sumIns = 0;
2020-04-09 11:04:06 +02:00
2020-04-08 11:41:12 +02:00
// verify correctness of transaction inputs
2021-06-09 12:58:57 +02:00
for (var tx = 0; tx < nIns; tx++) {
inKeypair[tx] = Keypair();
inKeypair[tx].privateKey <== inPrivateKey[tx];
2020-04-08 11:41:12 +02:00
inUtxoHasher[tx] = TransactionHasher();
inUtxoHasher[tx].amount <== inAmount[tx];
inUtxoHasher[tx].blinding <== inBlinding[tx];
inUtxoHasher[tx].publicKey <== inKeypair[tx].publicKey;
2020-04-08 11:41:12 +02:00
nullifierHasher[tx] = NullifierHasher();
nullifierHasher[tx].commitment <== inUtxoHasher[tx].commitment;
nullifierHasher[tx].merklePath <== inPathIndices[tx];
nullifierHasher[tx].privateKey <== inPrivateKey[tx];
2020-04-08 11:41:12 +02:00
nullifierHasher[tx].nullifier === inputNullifier[tx];
tree[tx] = MerkleTree(levels);
tree[tx].leaf <== inUtxoHasher[tx].commitment;
tree[tx].pathIndices <== inPathIndices[tx];
for (var i = 0; i < levels; i++) {
tree[tx].pathElements[i] <== inPathElements[tx][i];
}
2020-04-09 11:04:06 +02:00
2020-04-08 11:41:12 +02:00
// check merkle proof only if amount is non-zero
checkRoot[tx] = ForceEqualIfEnabled();
checkRoot[tx].in[0] <== root;
checkRoot[tx].in[1] <== tree[tx].root;
checkRoot[tx].enabled <== inAmount[tx];
// Check that amount fits into 248 bits to prevent overflow
inAmountCheck[tx] = Num2Bits(248);
inAmountCheck[tx].in <== inAmount[tx];
2021-06-09 12:58:57 +02:00
sumIns += inAmount[tx];
2020-04-08 11:41:12 +02:00
}
2021-06-09 12:58:57 +02:00
component outUtxoHasher[nOuts];
component outAmountCheck[nOuts];
var sumOuts = 0;
2020-04-08 11:41:12 +02:00
// verify correctness of transaction outputs
2021-06-09 12:58:57 +02:00
for (var tx = 0; tx < nOuts; tx++) {
2020-04-08 11:41:12 +02:00
outUtxoHasher[tx] = TransactionHasher();
outUtxoHasher[tx].amount <== outAmount[tx];
outUtxoHasher[tx].blinding <== outBlinding[tx];
2020-04-09 20:38:10 +02:00
outUtxoHasher[tx].publicKey <== outPubkey[tx];
2020-04-08 11:41:12 +02:00
outUtxoHasher[tx].commitment === outputCommitment[tx];
// Check that amount fits into 248 bits to prevent overflow
outAmountCheck[tx] = Num2Bits(248);
outAmountCheck[tx].in <== outAmount[tx];
2021-06-09 12:58:57 +02:00
sumOuts += outAmount[tx];
2020-04-08 11:41:12 +02:00
}
// check that there are no same nullifiers among all inputs
component sameNullifiers[nIns * (nIns - 1) / 2];
var index = 0;
for (var i = 0; i < nIns - 1; i++) {
for (var j = i + 1; j < nIns; j++) {
sameNullifiers[index] = IsEqual();
sameNullifiers[index].in[0] <== inputNullifier[i];
sameNullifiers[index].in[1] <== inputNullifier[j];
sameNullifiers[index].out === 0;
index++;
}
}
2020-04-08 11:41:12 +02:00
// verify amount invariant
2021-08-16 21:17:07 +02:00
sumIns + publicAmount === sumOuts;
2020-04-08 11:41:12 +02:00
// Check merkle tree update with inserted transaction outputs
2021-08-13 17:43:47 +02:00
component treeUpdater = TreeUpdater(levels, 1 /* log2(nOuts) */, zeroLeaf);
2020-04-08 11:41:12 +02:00
treeUpdater.oldRoot <== root;
treeUpdater.newRoot <== newRoot;
for (var i = 0; i < nOuts; i++) {
treeUpdater.leaf[i] <== outputCommitment[i];
}
2020-04-08 11:41:12 +02:00
treeUpdater.pathIndices <== outPathIndices;
for (var i = 0; i < levels - 1; i++) {
treeUpdater.pathElements[i] <== outPathElements[i];
}
}