diff --git a/nightfall-deployer/circuits/burn.circom b/nightfall-deployer/circuits/burn.circom index 7064879d7..42ca01aee 100644 --- a/nightfall-deployer/circuits/burn.circom +++ b/nightfall-deployer/circuits/burn.circom @@ -3,6 +3,7 @@ pragma circom 2.1.2; include "./common/utils/calculate_keys.circom"; include "./common/utils/array_uint32_to_bits.circom"; include "./common/verifiers/verify_duplicates.circom"; +include "./common/verifiers/commitments/verify_commitments.circom"; include "./common/verifiers/commitments/verify_commitments_optional.circom"; include "./common/verifiers/nullifiers/verify_nullifiers.circom"; include "./common/verifiers/nullifiers/verify_nullifiers_optional.circom"; @@ -94,18 +95,18 @@ template Burn(N,C) { n1 === 0; // Convert the nullifiers values to numbers and calculate its sum - var nullifiersSum = 0; + // var nullifiersSum = 0; for(var i = 0; i < N; i++) { - nullifiersSum += nullifiersValues[i]; + // nullifiersSum += nullifiersValues[i]; var nullifierValueBits[254] = Num2Bits(254)(nullifiersValues[i]); nullifierValueBits[253] === 0; nullifierValueBits[252] === 0; } // Convert the commitment values to numbers and calculate its sum - var commitmentsSum = 0; + // var commitmentsSum = 0; for(var i = 0; i < C; i++) { - commitmentsSum += commitmentsValues[i]; + // commitmentsSum += commitmentsValues[i]; var commitmentValueBits[254] = Num2Bits(254)(commitmentsValues[i]); commitmentValueBits[253] === 0; commitmentValueBits[252] === 0; @@ -117,7 +118,7 @@ template Burn(N,C) { valuePrivateBits[252] === 0; // Check that the value holds - nullifiersSum === commitmentsSum + fee + valuePrivate; + //nullifiersSum === commitmentsSum + fee + valuePrivate; // Calculate the nullifierKeys and the zkpPublicKeys from the root key @@ -150,13 +151,32 @@ template Burn(N,C) { } checkFeeNullifiers.valid === 1; - // Check that the first commitment is valid either with packedErcAddress or if value is zero - var checkCommitment = VerifyCommitmentsOptional(1)(packedErcAddressPrivate, idRemainderPrivate, [commitments[0]],[commitmentsValues[0]], [commitmentsSalts[0]], [recipientPublicKey[0]]); + // Check that the first commitment is valid with packedErcAddress + var checkCommitment = VerifyCommitments(1)(packedErcAddressPrivate, idRemainderPrivate, [commitments[0]],[commitmentsValues[0]], [commitmentsSalts[0]], [recipientPublicKey[0]]); checkCommitment === 1; // Check that the second commitment is valid either with feeAddress or if value is zero - var checkCommitmentFee = VerifyCommitmentsOptional(1)(feeAddress, 0, [commitments[1]],[commitmentsValues[1]], [commitmentsSalts[1]], [recipientPublicKey[1]]); - checkCommitmentFee === 1; + // var checkCommitmentFee = VerifyCommitmentsOptional(1)(feeAddress, 0, [commitments[1]],[commitmentsValues[1]], [commitmentsSalts[1]], [recipientPublicKey[1]]); + // checkCommitmentFee === 1; + + // Check that the other commitments are valid either using the feeAddress or if value is zero + // note that this means we only support burning a single token for simplicity all other commitments are expected to be fees + component checkOptionalCommitments = VerifyCommitmentsOptional(C -1); + checkOptionalCommitments.packedErcAddress <== feeAddress; + checkOptionalCommitments.idRemainder <== 0; + for(var i = 1; i < C; i++) { + checkOptionalCommitments.commitmentsHashes[i-1] <== commitments[i]; + checkOptionalCommitments.newCommitmentsValues[i-1] <== commitmentsValues[i]; + checkOptionalCommitments.newCommitmentsSalts[i-1] <== commitmentsSalts[i]; + checkOptionalCommitments.recipientPublicKey[i-1][0] <== recipientPublicKey[i][0]; + checkOptionalCommitments.recipientPublicKey[i-1][1] <== recipientPublicKey[i][1]; + } + checkOptionalCommitments.valid === 1; + + // constrain the fees + checkFeeNullifiers.total === checkOptionalCommitments.total + fee; + // constrain the L2 value + nullifiersValues[0] === commitmentsValues[0] + value; // assert(commitmentsValues[0] == 0 || ( // zkpPublicKeys[0] == recipientPublicKey[0][0] && zkpPublicKeys[1] == recipientPublicKey[0][1])); diff --git a/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_generic.circom b/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_generic.circom index 29bbadc7f..bd2135a25 100644 --- a/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_generic.circom +++ b/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_generic.circom @@ -29,9 +29,15 @@ template VerifyCommitmentsGeneric(C) { signal input feeAddress; signal output valid; + signal output feeTotal; + signal output nonFeeTotal; signal r[C]; + signal f[C]; + signal s[C]; + var feeSum = 0; + var nonFeeSum = 0; for(var i=0; i < C; i++) { // Check if the commitment value is zero var isCommitmentValueZero = IsZero()(newCommitmentsValues[i]); @@ -52,7 +58,15 @@ template VerifyCommitmentsGeneric(C) { // assert(commitment == commitmentsHashes[i] || commitmentFee == commitmentsHashes[i]); r[i] <== OR()(IsEqual()([commitment, commitmentsHashes[i]]), IsEqual()([commitmentFee, commitmentsHashes[i]])); r[i] === 1; - } + // add up the fees and the commitment values. These can be different coins and so can't always be validly added together + // in the case where the address is the same, the two values will end up equal + var IsEqualCommitment = IsEqual()([commitment, commitmentsHashes[i]]); + var IsEqualCommitmentFee = IsEqual()([commitmentFee, commitmentsHashes[i]]); + feeSum += IsEqualCommitmentFee * newCommitmentsValues[i]; + nonFeeSum += IsEqualCommitment * newCommitmentsValues[i]; + } valid <== 1; + feeTotal <-- feeSum; + nonFeeTotal <-- nonFeeSum; } diff --git a/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_optional.circom b/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_optional.circom index 7a6bec2ee..f1dd0ba23 100644 --- a/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_optional.circom +++ b/nightfall-deployer/circuits/common/verifiers/commitments/verify_commitments_optional.circom @@ -24,8 +24,9 @@ template VerifyCommitmentsOptional(C) { signal input recipientPublicKey[C][2]; signal output valid; + signal output total; - + var sum = 0; for(var i=0; i < C; i++) { // Check if the commitment value is zero @@ -39,7 +40,9 @@ template VerifyCommitmentsOptional(C) { // Check that the reconstructed commitment hash is equal to the public transaction commitment hash commitment === commitmentsHashes[i]; + sum += newCommitmentsValues[i]; } valid <== 1; + total <-- sum; } diff --git a/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_generic.circom b/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_generic.circom index 7ebdd83c1..18cb0e9a8 100644 --- a/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_generic.circom +++ b/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_generic.circom @@ -37,9 +37,15 @@ template VerifyNullifiersGeneric(N) { signal input feeAddress; signal output valid; + signal output feeTotal; + signal output nonFeeTotal; + signal r[N]; + var nonFeeSum = 0; + var feeSum = 0; + for(var i=0; i < N; i++) { // Check if the commitment value is zero var isNullifierValueZero = IsZero()(oldCommitmentValues[i]); @@ -85,7 +91,13 @@ template VerifyNullifiersGeneric(N) { // Check that the roots are equal. If nullifierValue is zero it will directly be considered valid var isValidRoot = Mux1()([IsEqualRoots, 1], isNullifierValueZero); isValidRoot === 1; - } + // add up the fees and the commitment values. These can be different coins and so can't be validly added together + // In the casde where they have the same address, these values will end up equal + feeSum += IsEqualNullifierFee * oldCommitmentValues[i]; + nonFeeSum += IsEqualNullifier * oldCommitmentValues[i]; + } valid <== 1; + feeTotal <-- feeSum; + nonFeeTotal <-- nonFeeSum; } \ No newline at end of file diff --git a/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_optional.circom b/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_optional.circom index 7eccfdf9e..048746087 100644 --- a/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_optional.circom +++ b/nightfall-deployer/circuits/common/verifiers/nullifiers/verify_nullifiers_optional.circom @@ -34,7 +34,9 @@ template VerifyNullifiersOptional(N) { signal input orders[N]; signal output valid; + signal output total; + var sum = 0; for(var i=0; i < N; i++) { // Check if the commitment value is zero var isNullifierValueZero = IsZero()(oldCommitmentValues[i]); @@ -60,7 +62,9 @@ template VerifyNullifiersOptional(N) { // Check that the root is valid var isValidRoot = Mux1()([IsEqualRoots, 1], isNullifierValueZero); isValidRoot === 1; + sum += oldCommitmentValues[i]; } valid <== 1; + total <-- sum; } \ No newline at end of file diff --git a/nightfall-deployer/circuits/depositfee.circom b/nightfall-deployer/circuits/depositfee.circom index fc28baf8e..df3ba71bf 100644 --- a/nightfall-deployer/circuits/depositfee.circom +++ b/nightfall-deployer/circuits/depositfee.circom @@ -131,8 +131,17 @@ template DepositFee(N,C) { var checkCommitments = VerifyCommitments(1)(packedErcAddress, idRemainder.out, [commitments[0]], [commitmentsValues[0]], [commitmentsSalts[0]], [recipientPublicKey[0]]); checkCommitments === 1; - var checkCommitmentsFee = VerifyCommitmentsOptional(1)(feeAddress, 0, [commitments[1]], [commitmentsValues[1]], [commitmentsSalts[1]], [recipientPublicKey[1]]); - checkCommitmentsFee === 1; + component checkCommitmentsFee = VerifyCommitmentsOptional(C-1); + checkCommitmentsFee.packedErcAddress <== feeAddress; + checkCommitmentsFee.idRemainder <== 0; + for(var i = 1; i < C; i++) { + checkCommitmentsFee.commitmentsHashes[i-1] <== commitments[i]; + checkCommitmentsFee.newCommitmentsValues[i-1] <== commitmentsValues[i]; + checkCommitmentsFee.newCommitmentsSalts[i-1] <== commitmentsSalts[i]; + checkCommitmentsFee.recipientPublicKey[i-1][0] <== recipientPublicKey[i][0]; + checkCommitmentsFee.recipientPublicKey[i-1][1] <== recipientPublicKey[i][1]; + } + checkCommitmentsFee.valid === 1; // Calculate the nullifierKeys and the zkpPublicKeys from the root key var nullifierKeys, zkpPublicKeys[2]; @@ -140,9 +149,9 @@ template DepositFee(N,C) { // Check that the nullifiers are valid either using the feeAddress or if value is zero - var checkNullifier = VerifyNullifiersOptional(N)(feeAddress, 0, nullifierKeys, zkpPublicKeys, nullifiers, roots, + component checkNullifier = VerifyNullifiersOptional(N)(feeAddress, 0, nullifierKeys, zkpPublicKeys, nullifiers, roots, nullifiersValues, nullifiersSalts, paths, orders); - checkNullifier === 1; + checkNullifier.valid === 1; // Verify the fee change // assert(commitmentsValues[1] == 0 || ( diff --git a/nightfall-deployer/circuits/transfer.circom b/nightfall-deployer/circuits/transfer.circom index f229477f3..fd4de7ab2 100644 --- a/nightfall-deployer/circuits/transfer.circom +++ b/nightfall-deployer/circuits/transfer.circom @@ -103,23 +103,23 @@ template Transfer(N,C) { // Convert the nullifiers values to numbers and calculate its sum var nullifiersSum = 0; for(var i = 0; i < N; i++) { - nullifiersSum += nullifiersValues[i]; + // nullifiersSum += nullifiersValues[i]; var nullifierValueBits[254] = Num2Bits(254)(nullifiersValues[i]); nullifierValueBits[253] === 0; nullifierValueBits[252] === 0; } // Convert the commitment values to numbers and calculate its sum - var commitmentsSum = 0; + // var commitmentsSum = 0; for(var i = 0; i < C; i++) { - commitmentsSum += commitmentsValues[i]; + // commitmentsSum += commitmentsValues[i]; var commitmentValueBits[254] = Num2Bits(254)(commitmentsValues[i]); commitmentValueBits[253] === 0; commitmentValueBits[252] === 0; } // Check that the value holds - nullifiersSum === commitmentsSum + fee; + // nullifiersSum === commitmentsSum + fee; // Calculate the nullifierKeys and the zkpPublicKeys from the root key var nullifierKeys, zkpPublicKeys[2]; @@ -166,6 +166,14 @@ template Transfer(N,C) { } checkGenericCommitments.valid === 1; + // constrain input and output values to be equal for both the fee and transaction values. + // We have to count differently if the feeAddress and the packedERCAddress are the same and the idRemainder is zero because + // then we have no way to differentiate fee and non-fee commitments - in this case we just sum all the values, noting that the fee and non-fee + // values are the same becuase they both contain a copy of everything. In this 'degenerate' case, the two constraints become identical. + var isDegenerate = AND()(IsEqual()([packedErcAddressPrivate, feeAddress]), IsZero()(idRemainderPrivate)); + checkGenericNullifiers.nonFeeTotal + nullifiersValues[0] === checkGenericCommitments.nonFeeTotal + commitmentsValues[0] + fee * isDegenerate; + checkGenericNullifiers.feeTotal + (nullifiersValues[0] - commitmentsValues[0]) * isDegenerate === checkGenericCommitments.feeTotal + fee; + // Verify the withdraw change //assert(commitmentsValues[C - 2] == 0 || ( // zkpPublicKeys[0] == recipientPublicKey[C - 2][0] && zkpPublicKeys[1] == recipientPublicKey[C - 2][1])); diff --git a/nightfall-deployer/circuits/transform.circom b/nightfall-deployer/circuits/transform.circom index 3478cac32..972395349 100644 --- a/nightfall-deployer/circuits/transform.circom +++ b/nightfall-deployer/circuits/transform.circom @@ -116,18 +116,20 @@ template Transform(N,C) { // check the fee nullifiers and fee commitments by looking at the input addresses var feeNullifiersSum = 0; for (var i = 0; i < N; i++) { - var isFee = inputPackedAddressesPrivate[i] == feeAddress; + var isFee = IsEqual()([inputPackedAddressesPrivate[i],feeAddress]); feeNullifiersSum += nullifiersValues[i] * isFee; } var feeCommitmentSum = 0; for (var i = 0; i < C; i++) { - var isFee = outputPackedAddressesPrivate[i] == feeAddress; + var isFee = IsEqual()([outputPackedAddressesPrivate[i], feeAddress]); feeCommitmentSum += commitmentsValues[i] * isFee; } + signal in <-- feeNullifiersSum; + signal out <-- feeCommitmentSum; - // Check that the value holds - var feeIsCovered = feeNullifiersSum - feeCommitmentSum - fee; + // Constrain the fees so that new 'fees' can't be added from an L2 commitment + in === fee + out; // Calculate the nullifierKeys and the zkpPublicKeys from the root key var nullifierKeys, zkpPublicKeys[2]; diff --git a/nightfall-deployer/circuits/withdraw.circom b/nightfall-deployer/circuits/withdraw.circom index ec64c3d16..89162e6bb 100644 --- a/nightfall-deployer/circuits/withdraw.circom +++ b/nightfall-deployer/circuits/withdraw.circom @@ -120,7 +120,7 @@ template Withdraw(N,C) { // Check that the value holds - nullifiersSum === commitmentsSum + fee + value; + // nullifiersSum === commitmentsSum + fee + value; // Calculate the token Id remainder without the 4 top bytes component idRemainder = Bits2Num(224); @@ -160,8 +160,26 @@ template Withdraw(N,C) { checkGenericNullifiers.valid === 1; // Check that the commimtents are valid either using the ercAddress, the feeAddress or if value is zero - var checkCommitments = VerifyCommitmentsGeneric(C)(packedErcAddress, idRemainder.out, commitments, commitmentsValues, commitmentsSalts, recipientPublicKey, feeAddress); - checkCommitments === 1; + component checkGenericCommitments = VerifyCommitmentsGeneric(C); + checkGenericCommitments.packedErcAddress <== packedErcAddress; + checkGenericCommitments.idRemainder <== idRemainder.out; + checkGenericCommitments.feeAddress <== feeAddress; + for(var i = 0; i < C; i++) { + checkGenericCommitments.commitmentsHashes[i] <== commitments[i]; + checkGenericCommitments.newCommitmentsValues[i] <== commitmentsValues[i]; + checkGenericCommitments.newCommitmentsSalts[i] <== commitmentsSalts[i]; + checkGenericCommitments.recipientPublicKey[i][0] <== recipientPublicKey[i][0]; + checkGenericCommitments.recipientPublicKey[i][1] <== recipientPublicKey[i][1]; + } + checkGenericCommitments.valid === 1; + + // constrain input and output values to be equal for both the fee and transaction values. + // We have to count differently if the feeAddress and the packedERCAddress are the same and the idRemainder is zero because + // then we have no way to differentiate fee and non-fee commitments - in this case we just sum all the values, noting that the fee and non-fee + // values are the same becuase they both contain a copy of everything. In this 'degenerate' case, the two constraints become identical. + var isDegenerate = AND()(IsEqual()([packedErcAddress, feeAddress]), IsZero()(idRemainder.out)); + checkGenericNullifiers.nonFeeTotal + nullifiersValues[0] === checkGenericCommitments.nonFeeTotal + fee * isDegenerate + value; + checkGenericNullifiers.feeTotal + (nullifiersValues[0] - value) * isDegenerate === checkGenericCommitments.feeTotal + fee; // Verify the withdraw change // assert(commitmentsValues[C - 2] == 0 || @@ -172,7 +190,6 @@ template Withdraw(N,C) { signal t <== OR()(f1, AND()(f2, f3)); t === 1; - // Verify the fee change // assert(commitmentsValues[C - 1] == 0 || // (zkpPublicKeys[0] == recipientPublicKey[C - 1][0] && zkpPublicKeys[1] == recipientPublicKey[C - 1][1]));