diff --git a/examples/CRISP/circuits/src/ecdsa.nr b/examples/CRISP/circuits/src/ecdsa.nr index 7da7b37dd6..c7e5523670 100644 --- a/examples/CRISP/circuits/src/ecdsa.nr +++ b/examples/CRISP/circuits/src/ecdsa.nr @@ -12,10 +12,8 @@ pub fn verify_signature( pub_key_x: [u8; 32], pub_key_y: [u8; 32], signature: [u8; 64], -) { - let valid_signature = - std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); - assert(valid_signature); +) -> bool { + std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message) } /// Given a public key, derive the Ethereum address @@ -89,7 +87,7 @@ fn test_verify_signature() { 124, 118, 143, 228, 126, 216, 173, 160, 231, 62, 52, 188, 154, 110, 230, 183, 71, 36, 161, 171, 163, 213, 62, 223, 152, ]; - verify_signature(hashed_message, pub_key_x, pub_key_y, signature); + assert(verify_signature(hashed_message, pub_key_x, pub_key_y, signature) == true); } #[test] @@ -114,7 +112,7 @@ fn test_verify_signature_sdk_input() { 236, 18, ]; - verify_signature(hashed_message, pub_key_x, pub_key_y, signature); + assert(verify_signature(hashed_message, pub_key_x, pub_key_y, signature) == true); } #[test] @@ -137,9 +135,8 @@ fn test_fail_verify_signature() { 124, 118, 143, 228, 126, 216, 173, 160, 231, 62, 52, 188, 154, 110, 230, 183, 71, 36, 161, 171, 163, 213, 62, 223, 151, ]; - let valid_signature = - std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, hashed_message); - assert(!valid_signature); + + assert(verify_signature(hashed_message, pub_key_x, pub_key_y, signature) == false); } #[test] diff --git a/examples/CRISP/circuits/src/main.nr b/examples/CRISP/circuits/src/main.nr index 55e84dec3f..4a19b8949d 100644 --- a/examples/CRISP/circuits/src/main.nr +++ b/examples/CRISP/circuits/src/main.nr @@ -39,7 +39,8 @@ fn main( merkle_proof_siblings: [Field; 20], ) { // First verify the signature. - verify_signature(hashed_message, public_key_x, public_key_y, signature); + let is_signature_valid = + verify_signature(hashed_message, public_key_x, public_key_y, signature); // Then derive the address. let address = address_to_field(derive_address(public_key_x, public_key_y)); @@ -56,7 +57,7 @@ fn main( // 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) { + if ((merkle_root_calculated == merkle_root) & (is_signature_valid == true)) { is_voter = true; } diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index 1008cad09c..9644830991 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -201,3 +201,17 @@ export interface NoirSignatureInputs { */ hashed_message: Uint8Array } + +/** + * Parameters for encryptVoteAndGenerateCRISPInputs function + */ +export interface EncryptVoteAndGenerateCRISPInputsParams { + encodedVote: string[] + publicKey: Uint8Array + previousCiphertext: Uint8Array + signature: `0x${string}` + message: string + merkleData: IMerkleProof + balance: bigint + bfvParams?: BFVParams +} diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 2fdd810c9d..a2a8c6ceb4 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 IMerkleProof, type IVote, VotingMode } from './types' +import { BFVParams, type CRISPCircuitInputs, type EncryptVoteAndGenerateCRISPInputsParams, type IVote, VotingMode } from './types' import { toBinary } from './utils' import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS } from './constants' import { extractSignature } from './signature' @@ -151,12 +151,16 @@ export const validateVote = (votingMode: VotingMode, vote: IVote, votingPower: b * @param bfvParams The BFV parameters to use for encryption * @returns The CRISP circuit inputs */ -export const encryptVoteAndGenerateCRISPInputs = async ( - encodedVote: string[], - publicKey: Uint8Array, - previousCiphertext: Uint8Array, - bfvParams: BFVParams = DEFAULT_BFV_PARAMS, -): Promise => { +export const encryptVoteAndGenerateCRISPInputs = async ({ + encodedVote, + publicKey, + previousCiphertext, + bfvParams = DEFAULT_BFV_PARAMS, + merkleData, + message, + signature, + balance, +}: EncryptVoteAndGenerateCRISPInputsParams): Promise => { if (encodedVote.length !== bfvParams.degree) { throw new RangeError(`encodedVote length ${encodedVote.length} does not match BFV degree ${bfvParams.degree}`) } @@ -167,41 +171,10 @@ export const encryptVoteAndGenerateCRISPInputs = async ( const crispInputs = (await zkInputsGenerator.generateInputs(previousCiphertext, publicKey, vote)) as CRISPCircuitInputs - // the rest of the public and private inputs will need to be generated before calling the circuit to generate the CRISP proof - return { - ...crispInputs, - public_key_x: [], - public_key_y: [], - signature: [], - hashed_message: [], - balance: '0', - merkle_root: '0', - merkle_proof_length: '0', - merkle_proof_indices: [], - merkle_proof_siblings: [], - } -} - -/** - * Generate the CRISP circuit inputs by extracting signature components and adding them to the partial inputs - * @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) return { - ...partialInputs, + ...crispInputs, hashed_message: Array.from(hashed_message).map((b) => b.toString()), public_key_x: Array.from(pub_key_x).map((b) => b.toString()), public_key_y: Array.from(pub_key_y).map((b) => b.toString()), @@ -213,3 +186,43 @@ export const generateCRISPInputs = async ( balance: balance.toString(), } } + +/** + * A function to generate the data required to mask a vote + * @param voter The voter's address + * @param publicKey The voter's public key + * @param previousCiphertext The previous ciphertext + * @returns The CRISP circuit inputs for a mask vote + */ +export const generateMaskVote = async ( + publicKey: Uint8Array, + previousCiphertext: Uint8Array, + bfvParams = DEFAULT_BFV_PARAMS, + merkleRoot: bigint, +): Promise => { + const plaintextVote: IVote = { + yes: 0n, + no: 0n, + } + + const encodedVote = encodeVote(plaintextVote, VotingMode.GOVERNANCE, 0n, bfvParams) + + const zkInputsGenerator: ZKInputsGenerator = new ZKInputsGenerator(bfvParams.degree, bfvParams.plaintextModulus, bfvParams.moduli) + + const vote = BigInt64Array.from(encodedVote.map(BigInt)) + + const crispInputs = (await zkInputsGenerator.generateInputs(previousCiphertext, publicKey, vote)) as CRISPCircuitInputs + + return { + ...crispInputs, + public_key_x: Array.from({ length: 32 }, () => '0'), + public_key_y: Array.from({ length: 32 }, () => '0'), + signature: Array.from({ length: 64 }, () => '0'), + hashed_message: Array.from({ length: 32 }, () => '0'), + merkle_proof_indices: Array.from({ length: 4 }, () => '0'), + merkle_proof_siblings: Array.from({ length: 4 }, () => '0'), + merkle_proof_length: '1', + merkle_root: merkleRoot.toString(), + balance: '0', + } +} diff --git a/examples/CRISP/packages/crisp-sdk/tests/constants.ts b/examples/CRISP/packages/crisp-sdk/tests/constants.ts index f1cd2e7340..40c03e35fd 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/constants.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/constants.ts @@ -4,6 +4,8 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +import { generateMerkleProof } from '../src' + export const CRISP_SERVER_URL = 'http://localhost:4000' export const MESSAGE = 'Vote for round 0' @@ -25,3 +27,6 @@ export const LEAVES = [ ] export const MAX_DEPTH = 20 + +export const votingPowerLeaf = 1000n +export const merkleProof = generateMerkleProof(0n, votingPowerLeaf, '0x1234567890123456789012345678901234567890', LEAVES, MAX_DEPTH) diff --git a/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts b/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts index 5f3bcc6744..7e76573ca5 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/utils.test.ts @@ -27,7 +27,7 @@ describe('Utils', () => { const address = '0x1234567890123456789012345678901234567890' const balance = 1000n it('should generate a valid merkle proof for a leaf', () => { - const tree = generateMerkleTree(LEAVES); + const tree = generateMerkleTree(LEAVES) const proof = generateMerkleProof(0n, balance, address, LEAVES, MAX_DEPTH) expect(proof.leaf).toBe(hashLeaf(address, balance.toString())) @@ -37,10 +37,10 @@ describe('Utils', () => { // Unpad the proof for verification const unpaddedProof = { ...proof.proof, - siblings: proof.proof.siblings.slice(0, proof.length) - }; + siblings: proof.proof.siblings.slice(0, proof.length), + } - expect(tree.verifyProof(unpaddedProof)).toBe(true); + expect(tree.verifyProof(unpaddedProof)).toBe(true) }) it('should throw if the leaf does not exist in the tree', () => { expect(() => generateMerkleProof(0n, balance, address, [], 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 3be7ccbe6b..2aac7f4bc2 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -12,13 +12,13 @@ import { decodeTally, encodeVote, encryptVoteAndGenerateCRISPInputs, - generateCRISPInputs, + generateMaskVote, validateVote, } from '../src/vote' import { BFVParams, VotingMode } from '../src/types' -import { DEFAULT_BFV_PARAMS, generateMerkleProof, MAXIMUM_VOTE_VALUE } from '../src' +import { DEFAULT_BFV_PARAMS, MAXIMUM_VOTE_VALUE } from '../src' -import { LEAVES, MAX_DEPTH, MESSAGE, SIGNATURE, VOTE } from './constants' +import { merkleProof, MESSAGE, SIGNATURE, VOTE, votingPowerLeaf } from './constants' describe('Vote', () => { const votingPower = 10n @@ -128,7 +128,15 @@ describe('Vote', () => { describe('encryptVoteAndGenerateCRISPInputs', () => { it('should encrypt a vote and generate the circuit inputs', async () => { const encodedVote = encodeVote(VOTE, VotingMode.GOVERNANCE, votingPower) - const crispInputs = await encryptVoteAndGenerateCRISPInputs(encodedVote, publicKey, previousCiphertext) + const crispInputs = await encryptVoteAndGenerateCRISPInputs({ + encodedVote, + publicKey, + previousCiphertext, + signature: SIGNATURE, + message: MESSAGE, + merkleData: merkleProof, + balance: votingPowerLeaf, + }) expect(crispInputs.ct_add).toBeInstanceOf(Object) expect(crispInputs.params).toBeInstanceOf(Object) @@ -139,16 +147,21 @@ describe('Vote', () => { expect(crispInputs.r1is).toBeInstanceOf(Array) expect(crispInputs.r2is).toBeInstanceOf(Array) expect(crispInputs.p1is).toBeInstanceOf(Array) + expect(crispInputs.hashed_message).toBeInstanceOf(Array) + 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()) }) }) - 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, merkleProof, votingPowerLeaf) + describe('generateMaskVote', () => { + it('should generate a mask vote and its inputs', async () => { + const crispInputs = await generateMaskVote(publicKey, previousCiphertext, DEFAULT_BFV_PARAMS, merkleProof.proof.root) expect(crispInputs.ct_add).toBeInstanceOf(Object) expect(crispInputs.params).toBeInstanceOf(Object) @@ -167,7 +180,7 @@ describe('Vote', () => { expect(crispInputs.merkle_proof_siblings).toBeDefined() expect(crispInputs.merkle_proof_length).toBeDefined() expect(crispInputs.merkle_root).toBeDefined() - expect(crispInputs.balance).toBe(votingPowerLeaf.toString()) + expect(crispInputs.balance).toBeDefined() }) }) })