diff --git a/examples/CRISP/circuits/src/main.nr b/examples/CRISP/circuits/src/main.nr index 4371f59745..55e84dec3f 100644 --- a/examples/CRISP/circuits/src/main.nr +++ b/examples/CRISP/circuits/src/main.nr @@ -33,6 +33,7 @@ fn main( signature: [u8; 64], hashed_message: [u8; 32], balance: Field, + merkle_root: pub Field, merkle_proof_length: u32, merkle_proof_indices: [u1; 20], merkle_proof_siblings: [Field; 20], @@ -44,7 +45,7 @@ fn main( let address = address_to_field(derive_address(public_key_x, public_key_y)); // Then get Merkle root. - let merkle_root = get_merkle_root( + let merkle_root_calculated = get_merkle_root( address, balance, merkle_proof_length, @@ -52,6 +53,13 @@ fn main( merkle_proof_siblings, ); + // compare the merkle root with the param to confirm whether it's the voter + // or a masker + let mut is_voter = false; + if (merkle_root_calculated == merkle_root) { + is_voter = true; + } + // then verify that the vote was encrypted correctly let circuit: Greco<2048, 1, 54, 54, 5, 5, 20, 20, 54, 16, 54> = Greco::new( params, diff --git a/examples/CRISP/circuits/src/merkle_tree.nr b/examples/CRISP/circuits/src/merkle_tree.nr index 1db90d50f9..bad53ad9af 100644 --- a/examples/CRISP/circuits/src/merkle_tree.nr +++ b/examples/CRISP/circuits/src/merkle_tree.nr @@ -42,3 +42,38 @@ fn test_get_merkle_root() { assert(merkle_root == expected); } + +#[test] +fn test_get_merkle_root_ts_sdk() { + let address = 0x1234567890123456789012345678901234567890; + let balance = 1000; + + let depth = 4; + let indices: [u1; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let siblings: [Field; 20] = [ + 3427403932201889290042771220465494458496044723860771244846412090114641612933, + 142173982782527023041821996079117549632814652929360547003021612347642943458, + 5984039385283196232308319035002972848802042601279200765710389913462508845879, + 13230920196039626330844247773673397239708271370016925050843639396057862269188, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + + let merkle_root = get_merkle_root(address, balance, depth, indices, siblings); + let expected = 1459018155640011149834119814743866717977769794914747043488779523926892000355; + assert(merkle_root == expected); +} diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index 6e9e2e547a..1008cad09c 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -60,6 +60,8 @@ export interface IMerkleProof { leaf: bigint index: number proof: LeanIMTMerkleProof + length: number + indices: number[] } /** @@ -163,6 +165,7 @@ export interface CRISPCircuitInputs { signature: string[] hashed_message: string[] balance: string + merkle_root: string merkle_proof_length: string merkle_proof_indices: string[] merkle_proof_siblings: string[] diff --git a/examples/CRISP/packages/crisp-sdk/src/utils.ts b/examples/CRISP/packages/crisp-sdk/src/utils.ts index f05bddef2b..d3928eca4b 100644 --- a/examples/CRISP/packages/crisp-sdk/src/utils.ts +++ b/examples/CRISP/packages/crisp-sdk/src/utils.ts @@ -34,8 +34,15 @@ export const generateMerkleTree = (leaves: bigint[]): LeanIMT => { * @param balance The voter's balance * @param address The voter's address * @param leaves The leaves of the Merkle tree + * @param maxDepth The maximum depth of the Merkle tree */ -export const generateMerkleProof = (threshold: number, balance: number, address: string, leaves: bigint[]): IMerkleProof => { +export const generateMerkleProof = ( + threshold: bigint, + balance: bigint, + address: string, + leaves: bigint[], + maxDepth: number, +): IMerkleProof => { if (balance < threshold) { throw new Error('Balance is below the threshold') } @@ -52,10 +59,23 @@ export const generateMerkleProof = (threshold: number, balance: number, address: const proof = tree.generateProof(index) + // Pad siblings with zeros + const paddedSiblings = [...proof.siblings, ...Array(maxDepth - proof.siblings.length).fill(0n)] + + // Pad indices with zeros + const indices = proof.siblings.map((_, i) => Number((BigInt(index) >> BigInt(i)) & 1n)) + const paddedIndices = [...indices, ...Array(maxDepth - indices.length).fill(0)] + return { leaf, index, - proof, + proof: { + ...proof, + siblings: paddedSiblings, + }, + // Original length before padding + length: proof.siblings.length, + indices: paddedIndices, } } diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 18135b59d2..2fdd810c9d 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs' -import { BFVParams, type CRISPCircuitInputs, type IVote, VotingMode } from './types' +import { BFVParams, type CRISPCircuitInputs, type IMerkleProof, type IVote, VotingMode } from './types' import { toBinary } from './utils' import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS } from './constants' import { extractSignature } from './signature' @@ -170,12 +170,12 @@ export const encryptVoteAndGenerateCRISPInputs = async ( // the rest of the public and private inputs will need to be generated before calling the circuit to generate the CRISP proof return { ...crispInputs, - // @todo fill the rest of the inputs needed for CRISP public_key_x: [], public_key_y: [], signature: [], hashed_message: [], balance: '0', + merkle_root: '0', merkle_proof_length: '0', merkle_proof_indices: [], merkle_proof_siblings: [], @@ -184,16 +184,19 @@ export const encryptVoteAndGenerateCRISPInputs = async ( /** * Generate the CRISP circuit inputs by extracting signature components and adding them to the partial inputs - * @todo Add the merkle tree inputs too * @param partialInputs The partial CRISP circuit inputs * @param signature The voter's signature * @param message The signed message + * @param merkleData The voter's Merkle proof data + * @param balance The voter's balance * @returns The complete CRISP circuit inputs */ export const generateCRISPInputs = async ( partialInputs: CRISPCircuitInputs, signature: `0x${string}`, message: string, + merkleData: IMerkleProof, + balance: bigint, ): Promise => { const { hashed_message, pub_key_x, pub_key_y, signature: extractedSignature } = await extractSignature(message, signature) @@ -203,5 +206,10 @@ export const generateCRISPInputs = async ( public_key_x: Array.from(pub_key_x).map((b) => b.toString()), public_key_y: Array.from(pub_key_y).map((b) => b.toString()), signature: Array.from(extractedSignature).map((b) => b.toString()), + merkle_proof_length: merkleData.length.toString(), + merkle_proof_indices: merkleData.indices.map((i) => i.toString()), + merkle_proof_siblings: merkleData.proof.siblings.map((s) => s.toString()), + merkle_root: merkleData.proof.root.toString(), + balance: balance.toString(), } } diff --git a/examples/CRISP/packages/crisp-sdk/tests/constants.ts b/examples/CRISP/packages/crisp-sdk/tests/constants.ts index 2261a03a66..f1cd2e7340 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/constants.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/constants.ts @@ -10,3 +10,18 @@ export const MESSAGE = 'Vote for round 0' export const SIGNATURE = '0x1641431d0ed3fd86814f026da62e11434b53c6a85162fea7f99218bf3c307dec7f361c235f07b658780afd91a5c9d68d6a4b14d5eb0511f6688d3e91140eec121b' export const VOTE = { yes: 10n, no: 0n } + +export const LEAVES = [ + 5744770974032406598001112375731623179326875761382288642755141437508907349272n, + 3427403932201889290042771220465494458496044723860771244846412090114641612933n, + 2345497045557010425836789889102383730351985634595867684180656101146305513188n, + 16333345332467701633145728130049893741621620444233378624735176290889202182510n, + 7181270461626747418571270128374653017252322331096300145108059576488978730010n, + 10652491433271864584861721641100216833021493709204436430564590071100900917428n, + 20897323534773557936302566533365152086278248537724825381610164527249213072770n, + 4720511075913887710172192848636076523165432993226978491435561065722130431597n, + 14131255645332550266535358189863475289290770471998199141522479556687499890181n, + 4935126455042678253283865781346660214959064333962120811317994370162001200675n, +] + +export const MAX_DEPTH = 20 diff --git a/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts b/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts index 182b7bd67b..5f3bcc6744 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts @@ -4,18 +4,11 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { expect, describe, it, beforeAll } from 'vitest' +import { expect, describe, it } from 'vitest' import { generateMerkleProof, generateMerkleTree, hashLeaf } from '../src/utils' -import { getTreeData } from '../src' -import { CRISP_SERVER_URL } from './constants' +import { LEAVES, MAX_DEPTH } from './constants' describe('Utils', () => { - let leaves: bigint[] - - beforeAll(async () => { - leaves = await getTreeData(CRISP_SERVER_URL, 0) - }) - describe('hashLeaf', () => { it('should return a bigint hash of the two values', () => { const leaf = hashLeaf('0x1234567890123456789012345678901234567890', '1000') @@ -25,21 +18,33 @@ describe('Utils', () => { describe('generateMerkleTree', () => { it('should generate a merkle tree', () => { - const tree = generateMerkleTree(leaves) + const tree = generateMerkleTree(LEAVES) expect(tree.root).toBeDefined() }) }) describe('generateMerkleProof', () => { const address = '0x1234567890123456789012345678901234567890' - const balance = 1000 - it('should generate a merkle proof for a leaf', () => { - const proof = generateMerkleProof(0, balance, address, leaves) + const balance = 1000n + it('should generate a valid merkle proof for a leaf', () => { + const tree = generateMerkleTree(LEAVES); + + const proof = generateMerkleProof(0n, balance, address, LEAVES, MAX_DEPTH) expect(proof.leaf).toBe(hashLeaf(address, balance.toString())) + + expect(proof.length).toBe(4) + expect(proof.indices.length).toBe(MAX_DEPTH) + // Unpad the proof for verification + const unpaddedProof = { + ...proof.proof, + siblings: proof.proof.siblings.slice(0, proof.length) + }; + + expect(tree.verifyProof(unpaddedProof)).toBe(true); }) it('should throw if the leaf does not exist in the tree', () => { - expect(() => generateMerkleProof(0, balance, address, [])).toThrow('Leaf not found in the tree') - expect(() => generateMerkleProof(0, 999, address, leaves)).toThrow('Leaf not found in the tree') + expect(() => generateMerkleProof(0n, balance, address, [], MAX_DEPTH)).toThrow('Leaf not found in the tree') + expect(() => generateMerkleProof(0n, 999n, address, LEAVES, MAX_DEPTH)).toThrow('Leaf not found in the tree') }) }) }) diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index 11e3c3998d..3be7ccbe6b 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -16,9 +16,9 @@ import { validateVote, } from '../src/vote' import { BFVParams, VotingMode } from '../src/types' -import { DEFAULT_BFV_PARAMS, MAXIMUM_VOTE_VALUE } from '../src' +import { DEFAULT_BFV_PARAMS, generateMerkleProof, MAXIMUM_VOTE_VALUE } from '../src' -import { MESSAGE, SIGNATURE, VOTE } from './constants' +import { LEAVES, MAX_DEPTH, MESSAGE, SIGNATURE, VOTE } from './constants' describe('Vote', () => { const votingPower = 10n @@ -143,10 +143,12 @@ describe('Vote', () => { }) describe('generateCRISPInputs', () => { + const votingPowerLeaf = 1000n + const merkleProof = generateMerkleProof(0n, votingPowerLeaf, '0x1234567890123456789012345678901234567890', LEAVES, MAX_DEPTH) it('should add the remaining inputs to the CRISP inputs object', async () => { const encodedVote = encodeVote(VOTE, VotingMode.GOVERNANCE, votingPower) const partialInputs = await encryptVoteAndGenerateCRISPInputs(encodedVote, publicKey, previousCiphertext) - const crispInputs = await generateCRISPInputs(partialInputs, SIGNATURE, MESSAGE) + const crispInputs = await generateCRISPInputs(partialInputs, SIGNATURE, MESSAGE, merkleProof, votingPowerLeaf) expect(crispInputs.ct_add).toBeInstanceOf(Object) expect(crispInputs.params).toBeInstanceOf(Object) @@ -161,6 +163,11 @@ describe('Vote', () => { expect(crispInputs.public_key_x).toBeInstanceOf(Array) expect(crispInputs.public_key_y).toBeInstanceOf(Array) expect(crispInputs.signature).toBeInstanceOf(Array) + expect(crispInputs.merkle_proof_indices).toBeDefined() + expect(crispInputs.merkle_proof_siblings).toBeDefined() + expect(crispInputs.merkle_proof_length).toBeDefined() + expect(crispInputs.merkle_root).toBeDefined() + expect(crispInputs.balance).toBe(votingPowerLeaf.toString()) }) }) })