From 429bd126a73094a6e54f78a3c3817b3fab704be3 Mon Sep 17 00:00:00 2001 From: Anton <14254374+0xmad@users.noreply.github.com> Date: Fri, 24 May 2024 10:08:48 -0500 Subject: [PATCH] test: fuzz testing for circuits - [x] Add fast-check for circuits packages - [x] Add fuzz testing test templates - [x] Code style fixes for circuits --- circuits/circom/utils/calculateTotal.circom | 2 +- circuits/circom/utils/privToPubKey.circom | 8 + circuits/package.json | 30 +- circuits/ts/__tests__/CalculateTotal.test.ts | 49 ++- circuits/ts/__tests__/Hasher.test.ts | 365 +++++++++++-------- circuits/ts/__tests__/PrivToPubKey.test.ts | 88 ++++- circuits/ts/__tests__/utils/constants.ts | 2 + pnpm-lock.yaml | 13 +- 8 files changed, 371 insertions(+), 186 deletions(-) diff --git a/circuits/circom/utils/calculateTotal.circom b/circuits/circom/utils/calculateTotal.circom index d3bf034a..af4c5eb7 100644 --- a/circuits/circom/utils/calculateTotal.circom +++ b/circuits/circom/utils/calculateTotal.circom @@ -14,7 +14,7 @@ template CalculateTotal(n) { signal sums[n]; sums[0] <== nums[0]; - for (var i=1; i < n; i++) { + for (var i = 1; i < n; i++) { sums[i] <== sums[i - 1] + nums[i]; } diff --git a/circuits/circom/utils/privToPubKey.circom b/circuits/circom/utils/privToPubKey.circom index 1a748ee7..7342acab 100644 --- a/circuits/circom/utils/privToPubKey.circom +++ b/circuits/circom/utils/privToPubKey.circom @@ -2,6 +2,7 @@ pragma circom 2.0.0; // circomlib imports include "./bitify.circom"; +include "./comparators.circom"; include "./escalarmulfix.circom"; /** @@ -15,9 +16,16 @@ template PrivToPubKey() { 16950150798460657717958625567821834550301663161624707787222815936182638968203 ]; + // Prime subgroup order 'l'. + var l = 2736030358979909402780800718157159386076813972158567259200215660948447373041; + signal input privKey; signal output pubKey[2]; + // Check if private key is in the prime subgroup order 'l' + var isLessThan = LessThan(251)([privKey, l]); + isLessThan === 1; + // Convert the private key to bits. var computedPrivBits[253] = Num2Bits(253)(privKey); diff --git a/circuits/package.json b/circuits/package.json index 412a56a4..5d83b720 100644 --- a/circuits/package.json +++ b/circuits/package.json @@ -20,19 +20,21 @@ "circom:build": "NODE_OPTIONS=--max-old-space-size=4096 circomkit compile", "circom:setup": "NODE_OPTIONS=--max-old-space-size=4096 circomkit setup", "types": "tsc -p tsconfig.json --noEmit", - "test": "ts-mocha --exit ts/__tests__/*.test.ts", - "test:hasher": "ts-mocha --exit ts/__tests__/Hasher.test.ts", - "test:slAndBallotTransformer": "ts-mocha --exit ts/__tests__/StateLeafAndBallotTransformer.test.ts", - "test:messageToCommand": "ts-mocha --exit ts/__tests__/MessageToCommand.test.ts", - "test:messageValidator": "ts-mocha --exit ts/__tests__/MessageValidator.test.ts", - "test:verifySignature": "ts-mocha --exit ts/__tests__/VerifySignature.test.ts", - "test:splicer": "ts-mocha --exit ts/__tests__/Splicer.test.ts", - "test:privToPubKey": "ts-mocha --exit ts/__tests__/PrivToPubKey.test.ts", - "test:calculateTotal": "ts-mocha --exit ts/__tests__/CalculateTotal.test.ts", - "test:processMessages": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/ProcessMessages.test.ts", - "test:tallyVotes": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit ts/__tests__/TallyVotes.test.ts", - "test:ceremonyParams": "ts-mocha --exit ts/__tests__/CeremonyParams.test.ts", - "test:incrementalQuinaryTree": "ts-mocha --exit ts/__tests__/IncrementalQuinaryTree.test.ts" + "mocha-test": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit -g '^(?!.*\\[fuzz\\]).*$'", + "test": "pnpm run mocha-test ts/__tests__/*.test.ts", + "test:fuzz": "NODE_OPTIONS=--max-old-space-size=4096 ts-mocha --exit -g '\\[fuzz\\]' ./ts/__tests__/*.test.ts", + "test:hasher": "pnpm run mocha-test ts/__tests__/Hasher.test.ts", + "test:slAndBallotTransformer": "pnpm run mocha-test ts/__tests__/StateLeafAndBallotTransformer.test.ts", + "test:messageToCommand": "pnpm run mocha-test ts/__tests__/MessageToCommand.test.ts", + "test:messageValidator": "pnpm run mocha-test ts/__tests__/MessageValidator.test.ts", + "test:verifySignature": "pnpm run mocha-test ts/__tests__/VerifySignature.test.ts", + "test:splicer": "pnpm run mocha-test ts/__tests__/Splicer.test.ts", + "test:privToPubKey": "pnpm run mocha-test ts/__tests__/PrivToPubKey.test.ts", + "test:calculateTotal": "pnpm run mocha-test ts/__tests__/CalculateTotal.test.ts", + "test:processMessages": "pnpm run mocha-test ts/__tests__/ProcessMessages.test.ts", + "test:tallyVotes": "pnpm run mocha-test ts/__tests__/TallyVotes.test.ts", + "test:ceremonyParams": "pnpm run mocha-test ts/__tests__/CeremonyParams.test.ts", + "test:incrementalQuinaryTree": "pnpm run mocha-test ts/__tests__/IncrementalQuinaryTree.test.ts" }, "dependencies": { "@zk-kit/circuits": "^0.4.0", @@ -48,8 +50,10 @@ "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.6", "@types/node": "^20.12.12", + "@zk-kit/baby-jubjub": "^1.0.1", "chai": "^4.3.10", "chai-as-promised": "^7.1.2", + "fast-check": "^3.18.0", "mocha": "^10.4.0", "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", diff --git a/circuits/ts/__tests__/CalculateTotal.test.ts b/circuits/ts/__tests__/CalculateTotal.test.ts index 432c39e5..54c1bce2 100644 --- a/circuits/ts/__tests__/CalculateTotal.test.ts +++ b/circuits/ts/__tests__/CalculateTotal.test.ts @@ -1,8 +1,12 @@ +import { r } from "@zk-kit/baby-jubjub"; import { type WitnessTester } from "circomkit"; +import fc from "fast-check"; -import { circomkitInstance } from "./utils/utils"; +import { circomkitInstance, getSignal } from "./utils/utils"; + +describe("CalculateTotal circuit", function test() { + this.timeout(900000); -describe("CalculateTotal circuit", () => { let circuit: WitnessTester<["nums"], ["sum"]>; before(async () => { @@ -15,6 +19,7 @@ describe("CalculateTotal circuit", () => { it("should correctly sum a list of values", async () => { const nums: number[] = []; + for (let i = 0; i < 6; i += 1) { nums.push(Math.floor(Math.random() * 100)); } @@ -27,4 +32,44 @@ describe("CalculateTotal circuit", () => { await circuit.expectPass(circuitInputs, { sum }); }); + + it("should sum max value and loop back", async () => { + const nums: bigint[] = [r, r, r, r, r, r]; + + await circuit.expectPass({ nums }, { sum: 0n }); + }); + + it("should sum max negative value and loop back", async () => { + const nums: bigint[] = [-r, -r, -r, -r, -r, -r]; + + await circuit.expectPass({ nums }, { sum: 0n }); + }); + + it("should sum max positive and negative values without looping", async () => { + const nums: bigint[] = [-r, r, -r, r, 1n, 2n]; + + await circuit.expectPass({ nums }, { sum: 3n }); + }); + + it("should correctly sum a list of values [fuzz]", async () => { + await fc.assert( + fc.asyncProperty(fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: 1 }), async (nums: bigint[]) => { + const sum = nums.reduce((a, b) => a + b, 0n); + fc.pre(sum <= r - 1n); + + const testCircuit = await circomkitInstance.WitnessTester("calculateTotal", { + file: "./utils/calculateTotal", + template: "CalculateTotal", + params: [nums.length], + }); + + const witness = await testCircuit.calculateWitness({ nums }); + await testCircuit.expectConstraintPass(witness); + const total = await getSignal(testCircuit, witness, "sum"); + + return total === sum; + }), + { numRuns: 10_000 }, + ); + }); }); diff --git a/circuits/ts/__tests__/Hasher.test.ts b/circuits/ts/__tests__/Hasher.test.ts index 8ff2c3c8..100c5634 100644 --- a/circuits/ts/__tests__/Hasher.test.ts +++ b/circuits/ts/__tests__/Hasher.test.ts @@ -1,12 +1,14 @@ +import { r } from "@zk-kit/baby-jubjub"; import { expect } from "chai"; import { type WitnessTester } from "circomkit"; +import fc from "fast-check"; import { genRandomSalt, sha256Hash, hash5, hash4, hash3, hash2 } from "maci-crypto"; import { PCommand, Keypair } from "maci-domainobjs"; import { getSignal, circomkitInstance } from "./utils/utils"; describe("Poseidon hash circuits", function test() { - this.timeout(30000); + this.timeout(900000); describe("SHA256", () => { describe("Sha256Hasher", () => { @@ -21,22 +23,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + in: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = sha256Hash(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 3 random values", async () => { @@ -48,22 +49,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + in: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = sha256Hash(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 4 random values", async () => { @@ -75,22 +75,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + in: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = sha256Hash(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 5 random values", async () => { @@ -102,22 +101,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + in: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = sha256Hash(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 6 random values", async () => { @@ -129,22 +127,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + in: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = sha256Hash(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 10 random values", async () => { @@ -156,22 +153,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - in: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "hash"); - - const outputJS = sha256Hash(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + in: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = sha256Hash(preImages); + + return output === outputJS; + }, + ), + ); }); }); }); @@ -189,22 +185,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - inputs: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "out"); - - const outputJS = hash2(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + inputs: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = hash2(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 3 random values", async () => { @@ -216,22 +211,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - inputs: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "out"); - - const outputJS = hash3(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + inputs: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = hash3(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 4 random values", async () => { @@ -243,22 +237,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - inputs: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "out"); - - const outputJS = hash4(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + inputs: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = hash4(preImages); + + return output === outputJS; + }, + ), + ); }); it("correctly hashes 5 random values", async () => { @@ -270,22 +263,21 @@ describe("Poseidon hash circuits", function test() { params: [n], }); - const preImages: bigint[] = []; - for (let i = 0; i < n; i += 1) { - preImages.push(genRandomSalt()); - } - - const circuitInputs = { - inputs: preImages, - }; - - const witness = await circuit.calculateWitness(circuitInputs); - await circuit.expectConstraintPass(witness); - const output = await getSignal(circuit, witness, "out"); - - const outputJS = hash5(preImages); - - expect(output.toString()).to.be.eq(outputJS.toString()); + await fc.assert( + fc.asyncProperty( + fc.array(fc.bigInt({ min: 0n, max: r - 1n }), { minLength: n, maxLength: n }), + async (preImages: bigint[]) => { + const witness = await circuit.calculateWitness({ + inputs: preImages, + }); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + const outputJS = hash5(preImages); + + return output === outputJS; + }, + ), + ); }); }); }); @@ -330,5 +322,56 @@ describe("Poseidon hash circuits", function test() { const output = await getSignal(circuit, witness, "hash"); expect(output.toString()).to.be.eq(messageHash.toString()); }); + + it("should correctly hash a message [fuzz]", async () => { + const random50bitBigInt = (salt: bigint): bigint => + // eslint-disable-next-line no-bitwise + ((BigInt(1) << BigInt(50)) - BigInt(1)) & salt; + + await fc.assert( + fc.asyncProperty( + fc.bigInt({ min: 0n }), + fc.bigInt({ min: 0n }), + fc.bigInt({ min: 0n }), + fc.bigInt({ min: 0n }), + fc.bigInt({ min: 0n }), + fc.bigInt({ min: 0n }), + async ( + stateIndex: bigint, + voteOptionIndex: bigint, + newVoteWeight: bigint, + nonce: bigint, + pollId: bigint, + salt: bigint, + ) => { + const { pubKey, privKey } = new Keypair(); + + const command: PCommand = new PCommand( + random50bitBigInt(stateIndex), + pubKey, + random50bitBigInt(voteOptionIndex), + random50bitBigInt(newVoteWeight), + random50bitBigInt(nonce), + random50bitBigInt(pollId), + salt, + ); + + const ecdhSharedKey = Keypair.genEcdhSharedKey(privKey, pubKey); + const signature = command.sign(privKey); + const message = command.encrypt(signature, ecdhSharedKey); + const messageHash = message.hash(pubKey); + const circuitInputs = { + in: message.asCircuitInputs(), + encPubKey: pubKey.asCircuitInputs() as unknown as [bigint, bigint], + }; + const witness = await circuit.calculateWitness(circuitInputs); + await circuit.expectConstraintPass(witness); + const output = await getSignal(circuit, witness, "hash"); + + return output === messageHash; + }, + ), + ); + }); }); }); diff --git a/circuits/ts/__tests__/PrivToPubKey.test.ts b/circuits/ts/__tests__/PrivToPubKey.test.ts index f62aafea..45e876d4 100644 --- a/circuits/ts/__tests__/PrivToPubKey.test.ts +++ b/circuits/ts/__tests__/PrivToPubKey.test.ts @@ -1,12 +1,14 @@ +import { Base8, inCurve, mulPointEscalar, r } from "@zk-kit/baby-jubjub"; import { expect } from "chai"; import { type WitnessTester } from "circomkit"; -import { SNARK_FIELD_SIZE } from "maci-crypto"; -import { Keypair } from "maci-domainobjs"; +import fc from "fast-check"; +import { Keypair, PrivKey, PubKey } from "maci-domainobjs"; +import { L } from "./utils/constants"; import { circomkitInstance, getSignal } from "./utils/utils"; -describe("Public key derivation circuit", function test() { - this.timeout(90000); +describe.only("Public key derivation circuit", function test() { + this.timeout(900000); let circuit: WitnessTester<["privKey"], ["pubKey"]>; @@ -45,7 +47,81 @@ describe("Public key derivation circuit", function test() { const derivedPubkey0 = await getSignal(circuit, witness, "pubKey[0]"); const derivedPubkey1 = await getSignal(circuit, witness, "pubKey[1]"); - expect(derivedPubkey0 < SNARK_FIELD_SIZE).to.eq(true); - expect(derivedPubkey1 < SNARK_FIELD_SIZE).to.eq(true); + expect(inCurve([derivedPubkey0, derivedPubkey1])).to.eq(true); + }); + + it("should throw error if private key is not in the prime subgroup l", async () => { + await fc.assert( + fc.asyncProperty(fc.bigInt({ min: L, max: r }), async (privKey: bigint) => { + const error = await circuit.expectFail({ privKey }); + + return error.includes("Assert Failed"); + }), + ); + }); + + it("should correctly produce different public keys for the different private keys [fuzz]", async () => { + await fc.assert( + fc.asyncProperty(fc.bigInt({ min: 1n, max: L - 1n }), async (x: bigint) => { + const publicKeys = new Map(); + const privateKeys: PrivKey[] = []; + + let i = 0n; + + while (x + i * r <= 2n ** 253n) { + privateKeys.push(new PrivKey(x + i * r)); + i += 1n; + } + + // eslint-disable-next-line no-restricted-syntax + for (const privateKey of privateKeys) { + const publicKey = mulPointEscalar(Base8, BigInt(privateKey.rawPrivKey)); + + // eslint-disable-next-line no-await-in-loop + const witness = await circuit.calculateWitness({ + privKey: BigInt(privateKey.rawPrivKey), + }); + // eslint-disable-next-line no-await-in-loop + await circuit.expectConstraintPass(witness); + // eslint-disable-next-line no-await-in-loop + const derivedPubkey0 = await getSignal(circuit, witness, "pubKey[0]"); + // eslint-disable-next-line no-await-in-loop + const derivedPubkey1 = await getSignal(circuit, witness, "pubKey[1]"); + + expect(publicKey[0]).to.eq(derivedPubkey0); + expect(publicKey[1]).to.eq(derivedPubkey1); + + publicKeys.set(privateKey.serialize(), new PubKey([derivedPubkey0, derivedPubkey1])); + } + + const uniquePublicKeys = [...publicKeys.values()].filter( + (value, index, array) => array.findIndex((publicKey) => publicKey.equals(value)) === index, + ); + + return uniquePublicKeys.length === privateKeys.length && uniquePublicKeys.length === publicKeys.size; + }), + { numRuns: 10_000 }, + ); + }); + + it("should correctly compute a public key [fuzz]", async () => { + await fc.assert( + fc.asyncProperty(fc.bigInt(), async (salt: bigint) => { + const { pubKey, privKey } = new Keypair(new PrivKey(salt)); + + const witness = await circuit.calculateWitness({ privKey: BigInt(privKey.asCircuitInputs()) }); + await circuit.expectConstraintPass(witness); + + const derivedPubkey0 = await getSignal(circuit, witness, "pubKey[0]"); + const derivedPubkey1 = await getSignal(circuit, witness, "pubKey[1]"); + + return ( + derivedPubkey0 === pubKey.rawPubKey[0] && + derivedPubkey1 === pubKey.rawPubKey[1] && + inCurve([derivedPubkey0, derivedPubkey1]) + ); + }), + { numRuns: 10_000 }, + ); }); }); diff --git a/circuits/ts/__tests__/utils/constants.ts b/circuits/ts/__tests__/utils/constants.ts index 6b5aa6ff..590e66b9 100644 --- a/circuits/ts/__tests__/utils/constants.ts +++ b/circuits/ts/__tests__/utils/constants.ts @@ -13,3 +13,5 @@ export const treeDepths = { voteOptionTreeDepth: 2, }; export const messageBatchSize = 5; + +export const L = 2736030358979909402780800718157159386076813972158567259200215660948447373041n; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd1f4449..6413e63a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,12 +126,18 @@ importers: '@types/node': specifier: ^20.12.12 version: 20.12.12 + '@zk-kit/baby-jubjub': + specifier: ^1.0.1 + version: 1.0.1 chai: specifier: ^4.3.10 version: 4.4.1 chai-as-promised: specifier: ^7.1.2 version: 7.1.2(chai@4.4.1) + fast-check: + specifier: ^3.18.0 + version: 3.19.0 mocha: specifier: ^10.4.0 version: 10.4.0 @@ -6459,6 +6465,7 @@ packages: /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + requiresBuild: true dev: true /@types/katex@0.16.7: @@ -6933,7 +6940,6 @@ packages: resolution: {integrity: sha512-ADQd4nI71uZLd9mMLHm5u97QS9zwUBOIxK8776Og1gHHrIdsku8q8X1Xi54QAsbidvRBC5FDIQnP7QMUWJK/hQ==} dependencies: '@zk-kit/utils': 1.0.0 - dev: false /@zk-kit/circuits@0.4.0: resolution: {integrity: sha512-Di7mokhwBS3qxVeCfHxGeNIpDg1kTnr1JXmsWiQMZLkRTn3Hugh6Tl07J394rWD0pIWRwPQsinaMVL2sB4F8yQ==} @@ -6966,7 +6972,6 @@ packages: resolution: {integrity: sha512-v5UjrZiaRNAN2UJmTFHvlMktaA2Efc2qN1Mwd4060ExX12yRhY8ZhzdlDODhnuHkvW5zPukuBHgQhHMScNP3Pg==} dependencies: buffer: 6.0.3 - dev: false /@zkochan/js-yaml@0.0.6: resolution: {integrity: sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg==} @@ -7917,7 +7922,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false /builtins@1.0.3: resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==} @@ -13785,6 +13789,7 @@ packages: /json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + requiresBuild: true dependencies: minimist: 1.2.8 dev: true @@ -19254,6 +19259,7 @@ packages: /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + requiresBuild: true dev: true /strip-bom@4.0.0: @@ -19842,6 +19848,7 @@ packages: /tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + requiresBuild: true dependencies: '@types/json5': 0.0.29 json5: 1.0.2