From d671d3076a9abbfa82bc170140f662d7dd4be4a7 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Wed, 27 Nov 2024 08:38:22 -0500 Subject: [PATCH] Increase the size of the deposit output to 2 for better tracking-resistance Signed-off-by: Jim Zhang --- solidity/contracts/lib/zeto_fungible.sol | 7 +- solidity/contracts/zeto_anon.sol | 8 +- solidity/contracts/zeto_anon_enc.sol | 8 +- .../contracts/zeto_anon_enc_nullifier.sol | 8 +- .../contracts/zeto_anon_enc_nullifier_kyc.sol | 8 +- ...eto_anon_enc_nullifier_non_repudiation.sol | 8 +- solidity/contracts/zeto_anon_nullifier.sol | 8 +- .../contracts/zeto_anon_nullifier_kyc.sol | 8 +- solidity/test/utils.ts | 24 ++- solidity/test/zeto_anon.ts | 5 +- solidity/test/zeto_anon_enc.ts | 5 +- solidity/test/zeto_anon_enc_nullifier.ts | 7 +- solidity/test/zeto_anon_enc_nullifier_kyc.ts | 18 ++- ...zeto_anon_enc_nullifier_non_repudiation.ts | 8 +- solidity/test/zeto_anon_nullifier.ts | 7 +- solidity/test/zeto_anon_nullifier_kyc.ts | 18 ++- zkp/circuits/check_hashes_value.circom | 2 +- zkp/js/integration-test/check_hashes_value.js | 17 ++- zkp/js/test/check_hashes_value.js | 137 +++++++++--------- 19 files changed, 170 insertions(+), 141 deletions(-) diff --git a/solidity/contracts/lib/zeto_fungible.sol b/solidity/contracts/lib/zeto_fungible.sol index 2b1be2d..562876f 100644 --- a/solidity/contracts/lib/zeto_fungible.sol +++ b/solidity/contracts/lib/zeto_fungible.sol @@ -45,15 +45,16 @@ abstract contract ZetoFungible is OwnableUpgradeable { function _deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof ) public virtual { // verifies that the output UTXOs match the claimed value // to be deposited // construct the public inputs - uint256[2] memory publicInputs; + uint256[3] memory publicInputs; publicInputs[0] = amount; - publicInputs[1] = utxo; + publicInputs[1] = outputs[0]; + publicInputs[2] = outputs[1]; // Check the proof require( diff --git a/solidity/contracts/zeto_anon.sol b/solidity/contracts/zeto_anon.sol index d08da9c..e490d1c 100644 --- a/solidity/contracts/zeto_anon.sol +++ b/solidity/contracts/zeto_anon.sol @@ -170,14 +170,12 @@ contract Zeto_Anon is IZeto, ZetoBase, ZetoFungibleWithdraw, UUPSUpgradeable { function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/contracts/zeto_anon_enc.sol b/solidity/contracts/zeto_anon_enc.sol index 3e805cf..e620826 100644 --- a/solidity/contracts/zeto_anon_enc.sol +++ b/solidity/contracts/zeto_anon_enc.sol @@ -208,14 +208,12 @@ contract Zeto_AnonEnc is function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/contracts/zeto_anon_enc_nullifier.sol b/solidity/contracts/zeto_anon_enc_nullifier.sol index 3fc9fbf..8477560 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier.sol @@ -223,14 +223,12 @@ contract Zeto_AnonEncNullifier is function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol index cd20920..ee7ecad 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_kyc.sol @@ -238,14 +238,12 @@ contract Zeto_AnonEncNullifierKyc is // Therefore, token circulation from & to parties that are not in the KYC list is prevented function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol index 4a92501..7079abe 100644 --- a/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol +++ b/solidity/contracts/zeto_anon_enc_nullifier_non_repudiation.sol @@ -274,14 +274,12 @@ contract Zeto_AnonEncNullifierNonRepudiation is function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/contracts/zeto_anon_nullifier.sol b/solidity/contracts/zeto_anon_nullifier.sol index ada1d70..ea2a75b 100644 --- a/solidity/contracts/zeto_anon_nullifier.sol +++ b/solidity/contracts/zeto_anon_nullifier.sol @@ -192,14 +192,12 @@ contract Zeto_AnonNullifier is function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/contracts/zeto_anon_nullifier_kyc.sol b/solidity/contracts/zeto_anon_nullifier_kyc.sol index 8dcaf43..58f8147 100644 --- a/solidity/contracts/zeto_anon_nullifier_kyc.sol +++ b/solidity/contracts/zeto_anon_nullifier_kyc.sol @@ -202,14 +202,12 @@ contract Zeto_AnonNullifierKyc is function deposit( uint256 amount, - uint256 utxo, + uint256[] memory outputs, Commonlib.Proof calldata proof, bytes calldata data ) public { - _deposit(amount, utxo, proof); - uint256[] memory utxos = new uint256[](1); - utxos[0] = utxo; - _mint(utxos, data); + _deposit(amount, outputs, proof); + _mint(outputs, data); } function withdraw( diff --git a/solidity/test/utils.ts b/solidity/test/utils.ts index 0000ce5..b81d715 100644 --- a/solidity/test/utils.ts +++ b/solidity/test/utils.ts @@ -42,17 +42,27 @@ export function loadProvingKeys(type: string) { }; } -export async function prepareDepositProof(signer: User, output: UTXO) { - const outputCommitments: [BigNumberish] = [output.hash] as [BigNumberish]; - const outputValues = [BigInt(output.value || 0n)]; - const outputOwnerPublicKeys: [[BigNumberish, BigNumberish]] = [ - signer.babyJubPublicKey, - ] as [[BigNumberish, BigNumberish]]; +export async function prepareDepositProof(signer: User, outputs: [UTXO, UTXO]) { + const outputCommitments: [BigNumberish, BigNumberish] = [ + outputs[0].hash, + outputs[1].hash, + ] as [BigNumberish, BigNumberish]; + const outputValues = [ + BigInt(outputs[0].value || 0n), + BigInt(outputs[1].value || 0n), + ]; + const outputOwnerPublicKeys: [ + [BigNumberish, BigNumberish], + [BigNumberish, BigNumberish], + ] = [signer.babyJubPublicKey, signer.babyJubPublicKey] as [ + [BigNumberish, BigNumberish], + [BigNumberish, BigNumberish], + ]; const inputObj = { outputCommitments, outputValues, - outputSalts: [output.salt], + outputSalts: [outputs[0].salt, outputs[1].salt], outputOwnerPublicKeys, }; diff --git a/solidity/test/zeto_anon.ts b/solidity/test/zeto_anon.ts index f05a666..1ba4573 100644 --- a/solidity/test/zeto_anon.ts +++ b/solidity/test/zeto_anon.ts @@ -170,13 +170,14 @@ describe("Zeto based fungible token with anonymity without encryption or nullifi await tx1.wait(); utxo100 = newUTXO(100, Alice); + const utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo100, utxo0], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); }); diff --git a/solidity/test/zeto_anon_enc.ts b/solidity/test/zeto_anon_enc.ts index 3379923..1e9c638 100644 --- a/solidity/test/zeto_anon_enc.ts +++ b/solidity/test/zeto_anon_enc.ts @@ -191,13 +191,14 @@ describe("Zeto based fungible token with anonymity and encryption", function () await tx1.wait(); utxo100 = newUTXO(100, Alice); + const utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo100, utxo0], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); }); diff --git a/solidity/test/zeto_anon_enc_nullifier.ts b/solidity/test/zeto_anon_enc_nullifier.ts index 5e431cc..261e361 100644 --- a/solidity/test/zeto_anon_enc_nullifier.ts +++ b/solidity/test/zeto_anon_enc_nullifier.ts @@ -275,17 +275,20 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti await tx1.wait(); utxo100 = newUTXO(100, Alice); + const utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo100, utxo0], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); await smtAlice.add(utxo100.hash, utxo100.hash); + await smtAlice.add(utxo0.hash, utxo0.hash); await smtBob.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo0.hash, utxo0.hash); }); it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { diff --git a/solidity/test/zeto_anon_enc_nullifier_kyc.ts b/solidity/test/zeto_anon_enc_nullifier_kyc.ts index 74e7afa..cb46e80 100644 --- a/solidity/test/zeto_anon_enc_nullifier_kyc.ts +++ b/solidity/test/zeto_anon_enc_nullifier_kyc.ts @@ -61,6 +61,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti let erc20: any; let zeto: any; let utxo100: UTXO; + let utxo0: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -336,17 +337,20 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti await tx1.wait(); utxo100 = newUTXO(100, Alice); + utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo100, utxo0], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); await smtAlice.add(utxo100.hash, utxo100.hash); + await smtAlice.add(utxo0.hash, utxo0.hash); await smtBob.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo0.hash, utxo0.hash); }); it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { @@ -576,6 +580,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti describe("unregistered user cases", function () { let unregisteredUtxo100: UTXO; + let unregisteredUtxo0: UTXO; it("deposit by an unregistered user should succeed", async function () { const tx = await erc20 @@ -588,24 +593,28 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti await tx1.wait(); unregisteredUtxo100 = newUTXO(100, unregistered); + unregisteredUtxo0 = newUTXO(0, unregistered); const { outputCommitments, encodedProof } = await prepareDepositProof( unregistered, - unregisteredUtxo100, + [unregisteredUtxo100, unregisteredUtxo0], ); const tx2 = await zeto .connect(unregistered.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); // Alice tracks the UTXO inside the SMT await smtAlice.add(unregisteredUtxo100.hash, unregisteredUtxo100.hash); + await smtAlice.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash); // Bob also locally tracks the UTXOs inside the SMT await smtBob.add(unregisteredUtxo100.hash, unregisteredUtxo100.hash); + await smtBob.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash); }); it("transfer from an unregistered user should fail", async function () { // catch up the local SMT for the unregistered user await smtUnregistered.add(utxo100.hash, utxo100.hash); + await smtUnregistered.add(utxo0.hash, utxo0.hash); await smtUnregistered.add(utxo1.hash, utxo1.hash); await smtUnregistered.add(utxo2.hash, utxo2.hash); await smtUnregistered.add(_utxo3.hash, _utxo3.hash); @@ -620,6 +629,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti unregisteredUtxo100.hash, unregisteredUtxo100.hash, ); + await smtUnregistered.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash); const utxosRoot = await smtUnregistered.root(); const nullifier = newNullifier(unregisteredUtxo100, unregistered); diff --git a/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts b/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts index 6fd6e2d..86a4976 100644 --- a/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts +++ b/solidity/test/zeto_anon_enc_nullifier_non_repudiation.ts @@ -59,6 +59,7 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti let erc20: any; let zeto: any; let utxo100: UTXO; + let utxo0: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -330,17 +331,20 @@ describe("Zeto based fungible token with anonymity using nullifiers and encrypti await tx1.wait(); utxo100 = newUTXO(100, Alice); + utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo100, utxo0], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); await smtAlice.add(utxo100.hash, utxo100.hash); + await smtAlice.add(utxo0.hash, utxo0.hash); await smtBob.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo0.hash, utxo0.hash); }); it("mint to Alice and transfer UTXOs honestly to Bob should succeed and verifiable by the regulator", async function () { diff --git a/solidity/test/zeto_anon_nullifier.ts b/solidity/test/zeto_anon_nullifier.ts index cd1fc4a..7bdd963 100644 --- a/solidity/test/zeto_anon_nullifier.ts +++ b/solidity/test/zeto_anon_nullifier.ts @@ -247,17 +247,20 @@ describe("Zeto based fungible token with anonymity using nullifiers without encr await tx1.wait(); utxo100 = newUTXO(100, Alice); + const utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo0, utxo100], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); await smtAlice.add(utxo100.hash, utxo100.hash); + await smtAlice.add(utxo0.hash, utxo0.hash); await smtBob.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo0.hash, utxo0.hash); }); it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { diff --git a/solidity/test/zeto_anon_nullifier_kyc.ts b/solidity/test/zeto_anon_nullifier_kyc.ts index e5e1e2a..2a6c622 100644 --- a/solidity/test/zeto_anon_nullifier_kyc.ts +++ b/solidity/test/zeto_anon_nullifier_kyc.ts @@ -47,6 +47,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou let erc20: any; let zeto: any; let utxo100: UTXO; + let utxo0: UTXO; let utxo1: UTXO; let utxo2: UTXO; let utxo3: UTXO; @@ -308,17 +309,20 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou await tx1.wait(); utxo100 = newUTXO(100, Alice); + utxo0 = newUTXO(0, Alice); const { outputCommitments, encodedProof } = await prepareDepositProof( Alice, - utxo100, + [utxo100, utxo0], ); const tx2 = await zeto .connect(Alice.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); await smtAlice.add(utxo100.hash, utxo100.hash); + await smtAlice.add(utxo0.hash, utxo0.hash); await smtBob.add(utxo100.hash, utxo100.hash); + await smtBob.add(utxo0.hash, utxo0.hash); }); it("mint to Alice and transfer UTXOs honestly to Bob should succeed", async function () { @@ -547,6 +551,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou describe("unregistered user flows", function () { let unregisteredUtxo100: UTXO; + let unregisteredUtxo0: UTXO; it("deposit by an unregistered user should succeed", async function () { const tx = await erc20 @@ -559,24 +564,28 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou await tx1.wait(); unregisteredUtxo100 = newUTXO(100, unregistered); + unregisteredUtxo0 = newUTXO(0, unregistered); const { outputCommitments, encodedProof } = await prepareDepositProof( unregistered, - unregisteredUtxo100, + [unregisteredUtxo100, unregisteredUtxo0], ); const tx2 = await zeto .connect(unregistered.signer) - .deposit(100, outputCommitments[0], encodedProof, "0x"); + .deposit(100, outputCommitments, encodedProof, "0x"); await tx2.wait(); // Alice tracks the UTXO inside the SMT await smtAlice.add(unregisteredUtxo100.hash, unregisteredUtxo100.hash); + await smtAlice.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash); // Bob also locally tracks the UTXOs inside the SMT await smtBob.add(unregisteredUtxo100.hash, unregisteredUtxo100.hash); + await smtBob.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash); }); it("transfer from an unregistered user should fail", async function () { // catch up the local SMT for the unregistered user await smtUnregistered.add(utxo100.hash, utxo100.hash); + await smtUnregistered.add(utxo0.hash, utxo0.hash); await smtUnregistered.add(utxo1.hash, utxo1.hash); await smtUnregistered.add(utxo2.hash, utxo2.hash); await smtUnregistered.add(_utxo3.hash, _utxo3.hash); @@ -591,6 +600,7 @@ describe("Zeto based fungible token with anonymity, KYC, using nullifiers withou unregisteredUtxo100.hash, unregisteredUtxo100.hash, ); + await smtUnregistered.add(unregisteredUtxo0.hash, unregisteredUtxo0.hash); const utxosRoot = await smtUnregistered.root(); const nullifier = newNullifier(unregisteredUtxo100, unregistered); diff --git a/zkp/circuits/check_hashes_value.circom b/zkp/circuits/check_hashes_value.circom index aacef14..5f1dc73 100644 --- a/zkp/circuits/check_hashes_value.circom +++ b/zkp/circuits/check_hashes_value.circom @@ -42,4 +42,4 @@ template Zeto(nOutputs) { out <== sumOutputs; } -component main {public [ outputCommitments ]} = Zeto(1); +component main {public [ outputCommitments ]} = Zeto(2); diff --git a/zkp/js/integration-test/check_hashes_value.js b/zkp/js/integration-test/check_hashes_value.js index cfb69b4..f16a3f0 100644 --- a/zkp/js/integration-test/check_hashes_value.js +++ b/zkp/js/integration-test/check_hashes_value.js @@ -33,7 +33,7 @@ describe("check-hashes-value circuit tests", () => { }); it("should return true for valid witness and false when public signals are tampered", async () => { - const outputValues = [200]; + const outputValues = [200, 0]; // create the output UTXO const salt1 = newSalt(); @@ -42,14 +42,19 @@ describe("check-hashes-value circuit tests", () => { salt1, ...sender.pubKey, ]); - const outputCommitments = [output1]; + const output2 = poseidonHash([ + BigInt(outputValues[1]), + salt1, + ...sender.pubKey, + ]); + const outputCommitments = [output1, output2]; let witness = await circuit.calculateWitness( { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt1], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, true, ); @@ -60,8 +65,8 @@ describe("check-hashes-value circuit tests", () => { { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt1], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, true, ); diff --git a/zkp/js/test/check_hashes_value.js b/zkp/js/test/check_hashes_value.js index 4a33896..95cb134 100644 --- a/zkp/js/test/check_hashes_value.js +++ b/zkp/js/test/check_hashes_value.js @@ -14,77 +14,83 @@ // See the License for the specific language governing permissions and // limitations under the License. -const { expect } = require("chai"); -const { join } = require("path"); -const { wasm: wasm_tester } = require("circom_tester"); -const { genKeypair } = require("maci-crypto"); -const { Poseidon, newSalt } = require("../index.js"); +const { expect } = require('chai'); +const { join } = require('path'); +const { wasm: wasm_tester } = require('circom_tester'); +const { genKeypair } = require('maci-crypto'); +const { Poseidon, newSalt } = require('../index.js'); const MAX_VALUE = 2n ** 100n - 1n; const poseidonHash = Poseidon.poseidon4; -describe("check_hashes_value circuit tests", () => { +describe('check_hashes_value circuit tests', () => { let circuit; const sender = {}; before(async function () { this.timeout(60000); - circuit = await wasm_tester( - join(__dirname, "../../circuits/check_hashes_value.circom"), - ); + circuit = await wasm_tester(join(__dirname, '../../circuits/check_hashes_value.circom')); let keypair = genKeypair(); sender.privKey = keypair.privKey; sender.pubKey = keypair.pubKey; }); - it("should return true for valid witness", async () => { - const outputValues = [200]; + it('should return true for valid witness', async () => { + const outputValues = [100, 200]; // create the output UTXO const salt1 = newSalt(); - const output1 = poseidonHash([ - BigInt(outputValues[0]), - salt1, - ...sender.pubKey, - ]); - const outputCommitments = [output1]; + const salt2 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const output2 = poseidonHash([BigInt(outputValues[1]), salt2, ...sender.pubKey]); + const outputCommitments = [output1, output2]; let witness = await circuit.calculateWitness( { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt2], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, - true, + true ); - expect(witness[1]).to.equal(BigInt(200)); // index 1 is the output, for the calculated value + expect(witness[1]).to.equal(BigInt(300)); // index 1 is the output, for the calculated value + }); + + it('should return true for valid witness (using 0 value for one of the outputs)', async () => { + const outputValues = [300, 0]; + + // create the output UTXO + const salt1 = newSalt(); + const salt2 = newSalt(); + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const output2 = poseidonHash([BigInt(outputValues[1]), salt2, ...sender.pubKey]); + const outputCommitments = [output1, output2]; - witness = await circuit.calculateWitness( + let witness = await circuit.calculateWitness( { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt2], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, - true, + true ); + + expect(witness[1]).to.equal(BigInt(300)); // index 1 is the output, for the calculated value }); - it("should fail to generate a witness because of invalid output commitments", async () => { - const outputValues = [200]; + it('should fail to generate a witness because of invalid output commitments', async () => { + const outputValues = [100, 200]; // create the output UTXO const salt1 = newSalt(); - const output1 = poseidonHash([ - BigInt(outputValues[0] + 100), - salt1, - ...sender.pubKey, - ]); - const outputCommitments = [output1]; + const output1 = poseidonHash([BigInt(outputValues[0] + 100), salt1, ...sender.pubKey]); + const output2 = poseidonHash([BigInt(outputValues[1]), salt1, ...sender.pubKey]); + const outputCommitments = [output1, output2]; let error; try { @@ -92,10 +98,10 @@ describe("check_hashes_value circuit tests", () => { { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt1], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, - true, + true ); } catch (e) { error = e; @@ -104,19 +110,16 @@ describe("check_hashes_value circuit tests", () => { expect(error).to.match(/Error in template CheckHashes_80 line: 53/); // hash check failed }); - it("should fail to generate a witness because of negative values in output commitments", async () => { + it('should fail to generate a witness because of negative values in output commitments', async () => { // in the finite field used in the Poseidion hash implementation, -100n is equivalent to // 21888242871839275222246405745257275088548364400416034343698204186575808495517n - const outputValues = [-100]; + const outputValues = [-100, 200]; // create the output UTXO const salt1 = newSalt(); - const output1 = poseidonHash([ - BigInt(outputValues[0]), - salt1, - ...sender.pubKey, - ]); - const outputCommitments = [output1]; + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const output2 = poseidonHash([BigInt(outputValues[1]), salt1, ...sender.pubKey]); + const outputCommitments = [output1, output2]; let error; try { @@ -124,10 +127,10 @@ describe("check_hashes_value circuit tests", () => { { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt1], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, - true, + true ); } catch (e) { error = e; @@ -136,22 +139,17 @@ describe("check_hashes_value circuit tests", () => { expect(error).to.match(/Error in template CheckPositive_3 line: 37/); // positive range check failed }); - it("should fail to generate a witness because of using the inverse of a negative value in output commitments", async () => { + it('should fail to generate a witness because of using the inverse of a negative value in output commitments', async () => { // in the finite field used in the Poseidion hash implementation, -100n is equivalent to // 21888242871839275222246405745257275088548364400416034343698204186575808495517n. This number // is considered negative by the circuit, because we allow the range of 0 to (2**40 - 1) - const outputValues = [ - 21888242871839275222246405745257275088548364400416034343698204186575808495518n, - ]; + const outputValues = [21888242871839275222246405745257275088548364400416034343698204186575808495518n, 100]; // create the output UTXO const salt1 = newSalt(); - const output1 = poseidonHash([ - BigInt(outputValues[0]), - salt1, - ...sender.pubKey, - ]); - const outputCommitments = [output1]; + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const output2 = poseidonHash([BigInt(outputValues[1]), salt1, ...sender.pubKey]); + const outputCommitments = [output1, output2]; let error; try { @@ -159,10 +157,10 @@ describe("check_hashes_value circuit tests", () => { { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt1], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, - true, + true ); } catch (e) { error = e; @@ -171,17 +169,14 @@ describe("check_hashes_value circuit tests", () => { expect(error).to.match(/Error in template CheckPositive_3 line: 37/); // positive range check failed }); - it("should fail to generate a witness because a larger than MAX_VALUE is used in output", async () => { - const outputValues = [MAX_VALUE + 1n]; + it('should fail to generate a witness because a larger than MAX_VALUE is used in output', async () => { + const outputValues = [MAX_VALUE + 1n, 0]; // create the output UTXO const salt1 = newSalt(); - const output1 = poseidonHash([ - BigInt(outputValues[0]), - salt1, - ...sender.pubKey, - ]); - const outputCommitments = [output1]; + const output1 = poseidonHash([BigInt(outputValues[0]), salt1, ...sender.pubKey]); + const output2 = poseidonHash([BigInt(outputValues[1]), salt1, ...sender.pubKey]); + const outputCommitments = [output1, output2]; let error; try { @@ -189,10 +184,10 @@ describe("check_hashes_value circuit tests", () => { { outputCommitments, outputValues, - outputSalts: [salt1], - outputOwnerPublicKeys: [sender.pubKey], + outputSalts: [salt1, salt1], + outputOwnerPublicKeys: [sender.pubKey, sender.pubKey], }, - true, + true ); } catch (e) { error = e;