From b3d5138a90d6ad3ee7b40f29f5c2e430d54269a6 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 27 Oct 2025 14:31:46 +0000 Subject: [PATCH 1/5] refactor: replace enclave-sdk with crisp-sdk --- .../CRISP/crates/zk-inputs-wasm/src/lib.rs | 43 +++++++-- examples/CRISP/crates/zk-inputs/src/lib.rs | 83 +++++++++-------- .../CRISP/packages/crisp-sdk/package.json | 1 - .../CRISP/packages/crisp-sdk/src/constants.ts | 9 ++ .../CRISP/packages/crisp-sdk/src/index.ts | 10 +-- .../CRISP/packages/crisp-sdk/src/types.ts | 88 +++++++++++++++---- examples/CRISP/packages/crisp-sdk/src/vote.ts | 66 ++++++-------- .../packages/crisp-sdk/tests/vote.test.ts | 39 ++++---- pnpm-lock.yaml | 3 - 9 files changed, 214 insertions(+), 128 deletions(-) diff --git a/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs b/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs index 88e6e0bc6a..e5271594bd 100644 --- a/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs +++ b/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs @@ -51,7 +51,7 @@ impl ZKInputsGenerator { &self, prev_ciphertext: &[u8], public_key: &[u8], - vote: u8, + vote: Vec, ) -> Result { match self .generator @@ -79,13 +79,36 @@ impl ZKInputsGenerator { /// Encrypt a vote from JavaScript. #[wasm_bindgen(js_name = "encryptVote")] - pub fn encrypt_vote(&self, public_key: &[u8], vote: u8) -> Result, JsValue> { + pub fn encrypt_vote(&self, public_key: &[u8], vote: Vec) -> Result, JsValue> { match self.generator.encrypt_vote(public_key, vote) { Ok(ciphertext_bytes) => Ok(ciphertext_bytes), Err(e) => Err(JsValue::from_str(&e.to_string())), } } + /// Get the BFV parameters used by the generator. + #[wasm_bindgen(js_name = "getBFVParams")] + pub fn get_bfv_params(&self) -> Result { + let bfv_params = self.generator.get_bfv_params(); + let params_json = js_sys::Object::new(); + js_sys::Reflect::set( + ¶ms_json, + &"degree".into(), + &JsValue::from(bfv_params.degree() as u32), + )?; + js_sys::Reflect::set( + ¶ms_json, + &"plaintext_modulus".into(), + &JsValue::from(bfv_params.plaintext() as f64), + )?; + let moduli_array = js_sys::Array::new(); + for modulus in bfv_params.moduli() { + moduli_array.push(&JsValue::from(*modulus as f64)); + } + js_sys::Reflect::set(¶ms_json, &"moduli".into(), &moduli_array.into())?; + Ok(JsValue::from(params_json)) + } + /// Get the version of the library. #[wasm_bindgen] pub fn version() -> String { @@ -97,16 +120,23 @@ impl ZKInputsGenerator { mod tests { use super::*; use wasm_bindgen_test::*; + use zk_inputs::DEFAULT_DEGREE; wasm_bindgen_test_configure!(run_in_browser); + /// Helper function to create a vote vector with alternating 0s and 1s (deterministic). + fn create_vote_vector() -> Vec { + (0..DEFAULT_DEGREE).map(|i| (i % 2) as u64).collect() + } + #[wasm_bindgen_test] fn test_js_inputs_generation_with_defaults() { // Create generator with default parameters. let generator = ZKInputsGenerator::with_defaults().unwrap(); let public_key = generator.generate_public_key().unwrap(); - let old_ciphertext = generator.encrypt_vote(&public_key, 1).unwrap(); - let result = generator.generate_inputs(&old_ciphertext, &public_key, 1); + let vote = create_vote_vector(); + let old_ciphertext = generator.encrypt_vote(&public_key, vote.clone()).unwrap(); + let result = generator.generate_inputs(&old_ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); @@ -130,8 +160,9 @@ mod tests { // Create generator with custom parameters. let generator = ZKInputsGenerator::new(degree, plaintext_modulus, moduli).unwrap(); let public_key = generator.generate_public_key().unwrap(); - let old_ciphertext = generator.encrypt_vote(&public_key, 1).unwrap(); - let result = generator.generate_inputs(&old_ciphertext, &public_key, 1); + let vote = create_vote_vector(); + let old_ciphertext = generator.encrypt_vote(&public_key, vote.clone()).unwrap(); + let result = generator.generate_inputs(&old_ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); diff --git a/examples/CRISP/crates/zk-inputs/src/lib.rs b/examples/CRISP/crates/zk-inputs/src/lib.rs index 804b239f5f..94ed9601b8 100644 --- a/examples/CRISP/crates/zk-inputs/src/lib.rs +++ b/examples/CRISP/crates/zk-inputs/src/lib.rs @@ -26,7 +26,7 @@ use crate::ciphertext_addition::CiphertextAdditionInputs; mod serialization; use serialization::{construct_inputs, serialize_inputs_to_json}; -// Default BFV parameters constants +// Default BFV parameters constants for testing purposes. pub const DEFAULT_DEGREE: usize = 2048; pub const DEFAULT_PLAINTEXT_MODULUS: u64 = 1032193; pub const DEFAULT_MODULI: [u64; 1] = [0x3FFFFFFF000001]; @@ -47,7 +47,13 @@ impl ZKInputsGenerator { Ok(Self { bfv_params }) } - /// Creates a generator with default BFV parameters. + /// Creates a generator with default BFV parameters for testing purposes. + /// + /// # Notes + /// - This is for testing purposes only. + /// - The default parameters are not suitable for production. + /// # Returns + /// A new ZKInputsGenerator instance with default BFV parameters pub fn with_defaults() -> Result { Self::new(DEFAULT_DEGREE, DEFAULT_PLAINTEXT_MODULUS, &DEFAULT_MODULI) } @@ -57,7 +63,7 @@ impl ZKInputsGenerator { /// # Arguments /// * `prev_ciphertext` - Previous ciphertext bytes to add to /// * `public_key` - Public key bytes for encryption - /// * `vote` - Vote value (0 or 1) + /// * `vote` - Vote value as a vector of coefficients /// /// # Returns /// JSON string containing the CRISP ZK inputs @@ -65,20 +71,14 @@ impl ZKInputsGenerator { &self, prev_ciphertext: &[u8], public_key: &[u8], - vote: u8, + vote: Vec, ) -> Result { // Deserialize the provided public key. let pk = PublicKey::from_bytes(public_key, &self.bfv_params) .with_context(|| "Failed to deserialize public key")?; - // Create a sample plaintext consistent with the GRECO sample generator. - // All coefficients are 3, and the first coefficient represents the vote. - let mut message_data = vec![3u64; self.bfv_params.degree()]; - // Set vote value (0 or 1) in the first coefficient. - message_data[0] = if vote == 1 { 1 } else { 0 }; - // Encode the plaintext into a polynomial. - let pt = Plaintext::try_encode(&message_data, Encoding::poly(), &self.bfv_params) + let pt = Plaintext::try_encode(&vote, Encoding::poly(), &self.bfv_params) .with_context(|| "Failed to encode plaintext")?; // Encrypt using the provided public key to ensure ciphertext matches the key. @@ -122,19 +122,15 @@ impl ZKInputsGenerator { /// /// # Arguments /// * `public_key` - Public key bytes for encryption - /// * `vote` - Vote value (0 or 1) + /// * `vote` - Vote data as a vector of coefficients /// /// # Returns /// Ciphertext bytes - pub fn encrypt_vote(&self, public_key: &[u8], vote: u8) -> Result> { + pub fn encrypt_vote(&self, public_key: &[u8], vote: Vec) -> Result> { let pk = PublicKey::from_bytes(public_key, &self.bfv_params) .with_context(|| "Failed to deserialize public key")?; - let mut message_data = vec![3u64; self.bfv_params.degree()]; - // Set vote value (0 or 1) in the first coefficient. - message_data[0] = if vote == 1 { 1 } else { 0 }; - - let pt = Plaintext::try_encode(&message_data, Encoding::poly(), &self.bfv_params) + let pt = Plaintext::try_encode(&vote, Encoding::poly(), &self.bfv_params) .with_context(|| "Failed to encode plaintext")?; let (ct, _u_rns, _e0_rns, _e1_rns) = pk @@ -167,16 +163,22 @@ impl ZKInputsGenerator { mod tests { use super::*; + /// Helper function to create a vote vector with alternating 0s and 1s (deterministic) + fn create_vote_vector() -> Vec { + (0..DEFAULT_DEGREE).map(|i| (i % 2) as u64).collect() + } + #[test] fn test_inputs_generation_with_defaults() { let generator = ZKInputsGenerator::with_defaults().expect("failed to create generator"); let public_key = generator .generate_public_key() .expect("failed to generate public key"); + let vote = create_vote_vector(); let prev_ciphertext = generator - .encrypt_vote(&public_key, 1) + .encrypt_vote(&public_key, vote.clone()) .expect("failed to generate previous ciphertext"); - let result = generator.generate_inputs(&prev_ciphertext, &public_key, 0); + let result = generator.generate_inputs(&prev_ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); let json_output = result.unwrap(); @@ -193,10 +195,11 @@ mod tests { let public_key = generator .generate_public_key() .expect("failed to generate public key"); + let vote = create_vote_vector(); let prev_ciphertext = generator - .encrypt_vote(&public_key, 0) + .encrypt_vote(&public_key, vote.clone()) .expect("failed to generate previous ciphertext"); - let result = generator.generate_inputs(&prev_ciphertext, &public_key, 1); + let result = generator.generate_inputs(&prev_ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); let json_output = result.unwrap(); @@ -212,10 +215,11 @@ mod tests { let public_key = generator .generate_public_key() .expect("failed to generate public key"); + let vote = create_vote_vector(); let prev_ciphertext = generator - .encrypt_vote(&public_key, 0) + .encrypt_vote(&public_key, vote.clone()) .expect("failed to generate previous ciphertext"); - let result = generator.generate_inputs(&prev_ciphertext, &public_key, 1); + let result = generator.generate_inputs(&prev_ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); let json_output = result.unwrap(); @@ -244,13 +248,14 @@ mod tests { .generate_public_key() .expect("failed to generate public key"); assert!(!public_key.is_empty()); + let vote = create_vote_vector(); let ciphertext = generator - .encrypt_vote(&public_key, 1) + .encrypt_vote(&public_key, vote.clone()) .expect("failed to encrypt vote"); assert!(!ciphertext.is_empty()); - let result = generator.generate_inputs(&ciphertext, &public_key, 0); + let result = generator.generate_inputs(&ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); let json_output = result.unwrap(); assert!(json_output.contains("params")); @@ -262,17 +267,18 @@ mod tests { #[test] fn test_invalid_inputs() { let generator = ZKInputsGenerator::with_defaults().expect("failed to create generator"); + let vote = create_vote_vector(); // Test invalid byte inputs. - let result = generator.generate_inputs(&[1, 2, 3], &[4, 5, 6], 0); + let result = generator.generate_inputs(&[1, 2, 3], &[4, 5, 6], vote.clone()); assert!(result.is_err()); // Test empty slices. - let result = generator.generate_inputs(&[], &[], 0); + let result = generator.generate_inputs(&[], &[], vote.clone()); assert!(result.is_err()); // Test invalid public key for encryption. - let result = generator.encrypt_vote(&[1, 2, 3], 0); + let result = generator.encrypt_vote(&[1, 2, 3], vote.clone()); assert!(result.is_err()); } @@ -283,16 +289,17 @@ mod tests { let public_key = generator .generate_public_key() .expect("failed to generate public key"); + let vote = create_vote_vector(); let prev_ciphertext = generator - .encrypt_vote(&public_key, 0) + .encrypt_vote(&public_key, vote.clone()) .expect("failed to encrypt vote"); // Test vote = 0. - let result_0 = generator.generate_inputs(&prev_ciphertext, &public_key, 0); + let result_0 = generator.generate_inputs(&prev_ciphertext, &public_key, vote.clone()); assert!(result_0.is_ok()); // Test vote = 1. - let result_1 = generator.generate_inputs(&prev_ciphertext, &public_key, 1); + let result_1 = generator.generate_inputs(&prev_ciphertext, &public_key, vote.clone()); assert!(result_1.is_ok()); } @@ -313,10 +320,11 @@ mod tests { let public_key = generator .generate_public_key() .expect("failed to generate public key"); + let vote = create_vote_vector(); let prev_ciphertext = generator - .encrypt_vote(&public_key, 0) + .encrypt_vote(&public_key, vote.clone()) .expect("failed to encrypt vote"); - let result = generator.generate_inputs(&prev_ciphertext, &public_key, 1); + let result = generator.generate_inputs(&prev_ciphertext, &public_key, vote.clone()); assert!(result.is_ok()); let json_output = result.unwrap(); @@ -340,20 +348,21 @@ mod tests { let public_key = generator .generate_public_key() .expect("Failed to generate public key"); + let vote = create_vote_vector(); // Test that different votes produce different ciphertexts. let ct0 = generator - .encrypt_vote(&public_key, 0) + .encrypt_vote(&public_key, vote.clone()) .expect("Failed to encrypt vote 0"); let ct1 = generator - .encrypt_vote(&public_key, 1) + .encrypt_vote(&public_key, vote.clone()) .expect("Failed to encrypt vote 1"); assert_ne!(ct0, ct1); // Test that same vote produces different ciphertexts (due to randomness). let ct0_2 = generator - .encrypt_vote(&public_key, 0) + .encrypt_vote(&public_key, vote.clone()) .expect("Failed to encrypt vote 0 again"); assert_ne!(ct0, ct0_2); diff --git a/examples/CRISP/packages/crisp-sdk/package.json b/examples/CRISP/packages/crisp-sdk/package.json index e6ac3e61da..122dac62f9 100644 --- a/examples/CRISP/packages/crisp-sdk/package.json +++ b/examples/CRISP/packages/crisp-sdk/package.json @@ -41,7 +41,6 @@ }, "dependencies": { "@enclave/crisp-zk-inputs": "workspace:*", - "@enclave-e3/sdk": "workspace:*", "@zk-kit/lean-imt": "^2.2.4", "poseidon-lite": "^0.3.0", "viem": "2.30.6" diff --git a/examples/CRISP/packages/crisp-sdk/src/constants.ts b/examples/CRISP/packages/crisp-sdk/src/constants.ts index cd1cb403da..2b701e0251 100644 --- a/examples/CRISP/packages/crisp-sdk/src/constants.ts +++ b/examples/CRISP/packages/crisp-sdk/src/constants.ts @@ -4,6 +4,9 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs' +import { BFVParams } from './types' + export const CRISP_SERVER_TOKEN_TREE_ENDPOINT = 'state/token-holders' export const CRISP_SERVER_STATE_LITE_ENDPOINT = 'state/lite' @@ -13,3 +16,9 @@ export const CRISP_SERVER_STATE_LITE_ENDPOINT = 'state/lite' * If you change this value, make sure to update the circuit too. */ export const MAXIMUM_VOTE_VALUE = 268435456n + +/** + * Default BFV parameters for the CRISP ZK inputs generator. + * These are the parameters used for the default testing purposes only. + */ +export const DEFAULT_BFV_PARAMS = ZKInputsGenerator.withDefaults().getBFVParams() as BFVParams diff --git a/examples/CRISP/packages/crisp-sdk/src/index.ts b/examples/CRISP/packages/crisp-sdk/src/index.ts index 60d338a3ed..b6d537375b 100644 --- a/examples/CRISP/packages/crisp-sdk/src/index.ts +++ b/examples/CRISP/packages/crisp-sdk/src/index.ts @@ -10,12 +10,4 @@ export * from './constants' export * from './utils' export { VotingMode } from './types' -export type { - IRoundDetails, - IRoundDetailsResponse, - ITokenDetails, - IMerkleProof, - IVote, - CRISPCircuitInputs, - CRISPVoteAndInputs, -} from './types' +export type { IRoundDetails, IRoundDetailsResponse, ITokenDetails, IMerkleProof, IVote, CRISPCircuitInputs } from './types' diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index 9a2f0b83fc..d5913779be 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -87,22 +87,77 @@ export interface IVote { no: bigint } +/** + * Interface representing a vector with coefficients + */ +export interface Polynomial { + coefficients: string[] +} + +/** + * Interface representing cryptographic parameters + */ +export interface GrecoCryptographicParams { + q_mod_t: string + qis: string[] + k0is: string[] +} + +/** + * Interface representing Greco bounds + */ +export interface GrecoBoundParams { + e_bound: string + u_bound: string + k1_low_bound: string + k1_up_bound: string + p1_bounds: string[] + p2_bounds: string[] + pk_bounds: string[] + r1_low_bounds: string[] + r1_up_bounds: string[] + r2_bounds: string[] +} + +/** + * Interface representing ciphertext addition inputs + */ +export interface CiphertextAdditionInputs { + prev_ct0is: Polynomial[] + prev_ct1is: Polynomial[] + sum_ct0is: Polynomial[] + sum_ct1is: Polynomial[] + r0is: Polynomial[] + r1is: Polynomial[] + r_bound: number +} + +/** + * Interface representing Greco parameters + */ +export interface GrecoParams { + crypto: GrecoCryptographicParams + bounds: GrecoBoundParams +} + /** * The inputs required for the CRISP circuit */ export interface CRISPCircuitInputs { - pk0is: string[][] - pk1is: string[][] - ct0is: string[][] - ct1is: string[][] - u: string[] - e0: string[] - e1: string[] - k1: string[] - r1is: string[][] - r2is: string[][] - p1is: string[][] - p2is: string[][] + ct_add: CiphertextAdditionInputs + params: GrecoParams + ct0is: Polynomial[] + ct1is: Polynomial[] + pk0is: Polynomial[] + pk1is: Polynomial[] + r1is: Polynomial[] + r2is: Polynomial[] + p1is: Polynomial[] + p2is: Polynomial[] + u: Polynomial + e0: Polynomial + e1: Polynomial + k1: Polynomial public_key_x: string[] public_key_y: string[] signature: string[] @@ -114,9 +169,10 @@ export interface CRISPCircuitInputs { } /** - * The result of encrypting a vote and generating CRISP circuit inputs + * Interface representing the BFV parameters */ -export interface CRISPVoteAndInputs { - encryptedVote: Uint8Array - circuitInputs: CRISPCircuitInputs +export interface BFVParams { + degree: number + plaintextModulus: bigint + moduli: bigint[] } diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 16963c1e98..1caa710de6 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -4,10 +4,10 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import { ProtocolParams, EnclaveSDK, FheProtocol } from '@enclave-e3/sdk' -import { type CRISPVoteAndInputs, type IVote, VotingMode } from './types' +import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs' +import { BFVParams, type CRISPCircuitInputs, type IVote, VotingMode } from './types' import { toBinary } from './utils' -import { MAXIMUM_VOTE_VALUE } from './constants' +import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS } from './constants' /** * This utility function calculates the first valid index for vote options @@ -52,17 +52,17 @@ export const calculateValidIndicesForPlaintext = (totalVotingPower: bigint, degr * Encode a vote based on the voting mode * @param vote The vote to encode * @param votingMode The voting mode to use for encoding - * @param bfvConfig The BFV protocol parameters to use for encoding + * @param bfvDegree The degree of the BFV polynomial to use for encoding * @param votingPower The voting power of the voter * @returns The encoded vote as a string */ -export const encodeVote = (vote: IVote, votingMode: VotingMode, bfvConfig: ProtocolParams, votingPower: bigint): string[] => { +export const encodeVote = (vote: IVote, votingMode: VotingMode, votingPower: bigint, bfvParams?: BFVParams): string[] => { validateVote(votingMode, vote, votingPower) switch (votingMode) { case VotingMode.GOVERNANCE: const voteArray = [] - const length = bfvConfig.degree + const length = bfvParams?.degree || DEFAULT_BFV_PARAMS.degree const halfLength = length / 2 const yesBinary = toBinary(vote.yes).split('') const noBinary = toBinary(vote.no).split('') @@ -88,7 +88,6 @@ export const encodeVote = (vote: IVote, votingMode: VotingMode, bfvConfig: Proto * Given an encoded tally, decode it into its decimal representation * @param tally The encoded tally to decode * @param votingMode The voting mode - * @param bfvConfig The BFV protocol parameters used for encryption */ export const decodeTally = (tally: string[], votingMode: VotingMode): IVote => { switch (votingMode) { @@ -147,41 +146,30 @@ export const validateVote = (votingMode: VotingMode, vote: IVote, votingPower: b * input values which generic Greco do not need. * @param encodedVote The encoded vote as string array * @param publicKey The public key to use for encryption + * @param previousCiphertext The previous ciphertext to use for addition operation */ -export const encryptVoteAndGenerateCRISPInputs = async (encodedVote: string[], publicKey: Uint8Array): Promise => { - // @todo The SDK need refactoring - const enclaveSDK = EnclaveSDK.create({ - protocol: FheProtocol.BFV, - chainId: 31337, - contracts: { - enclave: '0xc6e7DF5E7b4f2A278906862b61205850344D4e7d', - ciphernodeRegistry: '0xc6e7DF5E7b4f2A278906862b61205850344D4e7d', - }, - // local node - rpcUrl: 'http://localhost:8545', - // default Anvil private key - privateKey: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', - }) - - // Convert string[] to BigUint64Array - const bigUint64Array = new BigUint64Array(encodedVote.map((str) => BigInt(str))) - - const encryptedData = await enclaveSDK.encryptVectorAndGenInputs(bigUint64Array, publicKey) +export const encryptVoteAndGenerateCRISPInputs = async ( + encodedVote: string[], + publicKey: Uint8Array, + previousCiphertext: Uint8Array, +): Promise => { + const zkInputsGenerator = ZKInputsGenerator.withDefaults() + + const vote = BigUint64Array.from(encodedVote.map(BigInt)) + + 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 { - encryptedVote: encryptedData.encryptedData, - circuitInputs: { - ...encryptedData.publicInputs, - // @todo fill the rest of the inputs needed for CRISP - public_key_x: [], - public_key_y: [], - signature: [], - hashed_message: [], - balance: '0', - merkle_proof_length: '0', - merkle_proof_indices: [], - merkle_proof_siblings: [], - }, + ...crispInputs, + // @todo fill the rest of the inputs needed for CRISP + public_key_x: [], + public_key_y: [], + signature: [], + hashed_message: [], + balance: '0', + merkle_proof_length: '0', + merkle_proof_indices: [], + merkle_proof_siblings: [], } } diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index a89cf0c2a4..7ec98243a3 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -4,32 +4,29 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -import fs from 'fs/promises' -import path from 'path' -import { describe, it, expect, beforeAll } from 'vitest' -import { BfvProtocolParams, type ProtocolParams } from '@enclave-e3/sdk' +import { describe, it, expect } from 'vitest' import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs' import { calculateValidIndicesForPlaintext, decodeTally, encodeVote, encryptVoteAndGenerateCRISPInputs, validateVote } from '../src/vote' -import { VotingMode } from '../src/types' -import { MAXIMUM_VOTE_VALUE } from '../src' +import { BFVParams, VotingMode } from '../src/types' +import { DEFAULT_BFV_PARAMS, MAXIMUM_VOTE_VALUE } from '../src' describe('Vote', () => { const votingPower = 10n describe('encodeVote', () => { const vote = { yes: 10n, no: 0n } it('should work for valid votes', () => { - const encoded = encodeVote(vote, VotingMode.GOVERNANCE, BfvProtocolParams.BFV_NORMAL, votingPower) - expect(encoded.length).toBe(BfvProtocolParams.BFV_NORMAL.degree) + const encoded = encodeVote(vote, VotingMode.GOVERNANCE, votingPower) + expect(encoded.length).toBe(DEFAULT_BFV_PARAMS.degree) }) it('should work with small moduli', () => { - const params: ProtocolParams = { + const params: BFVParams = { degree: 10, - // irrelevant + // Irrelevant for this test. plaintextModulus: 0n, - moduli: 0n, + moduli: [0n], } - const encoded = encodeVote(vote, VotingMode.GOVERNANCE, params, votingPower) + const encoded = encodeVote(vote, VotingMode.GOVERNANCE, votingPower, params) expect(encoded.length).toBe(params.degree) // 01010 = 10 @@ -121,13 +118,21 @@ describe('Vote', () => { let zkInputsGenerator = ZKInputsGenerator.withDefaults() let publicKey = zkInputsGenerator.generatePublicKey() + const previousCiphertext = zkInputsGenerator.encryptVote(publicKey, 0) it('should encrypt a vote and generate the circuit inputs', async () => { - const encodedVote = encodeVote(vote, VotingMode.GOVERNANCE, BfvProtocolParams.BFV_NORMAL, votingPower) - const encryptedData = await encryptVoteAndGenerateCRISPInputs(encodedVote, publicKey) - - expect(encryptedData.encryptedVote).toBeInstanceOf(Uint8Array) - expect(encryptedData.circuitInputs).toBeInstanceOf(Object) + const encodedVote = encodeVote(vote, VotingMode.GOVERNANCE, votingPower) + const crispInputs = await encryptVoteAndGenerateCRISPInputs(encodedVote, publicKey, previousCiphertext) + + expect(crispInputs.ct_add).toBeInstanceOf(Object) + expect(crispInputs.params).toBeInstanceOf(Object) + expect(crispInputs.ct0is).toBeInstanceOf(Array) + expect(crispInputs.ct1is).toBeInstanceOf(Array) + expect(crispInputs.pk0is).toBeInstanceOf(Array) + expect(crispInputs.pk1is).toBeInstanceOf(Array) + expect(crispInputs.r1is).toBeInstanceOf(Array) + expect(crispInputs.r2is).toBeInstanceOf(Array) + expect(crispInputs.p1is).toBeInstanceOf(Array) }) }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08c2c8a83e..c6bb4cd110 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -339,9 +339,6 @@ importers: examples/CRISP/packages/crisp-sdk: dependencies: - '@enclave-e3/sdk': - specifier: workspace:* - version: link:../../../../packages/enclave-sdk '@enclave/crisp-zk-inputs': specifier: workspace:* version: link:../crisp-zk-inputs From 44b4495af18b22f0648aa12d8653651aaae51569 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 27 Oct 2025 14:38:29 +0000 Subject: [PATCH 2/5] refactor: add bfv params to vote function --- examples/CRISP/packages/crisp-sdk/src/types.ts | 2 +- examples/CRISP/packages/crisp-sdk/src/vote.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index d5913779be..1c238446db 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -174,5 +174,5 @@ export interface CRISPCircuitInputs { export interface BFVParams { degree: number plaintextModulus: bigint - moduli: bigint[] + moduli: BigUint64Array } diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 1caa710de6..5a2944cfe0 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -152,8 +152,15 @@ export const encryptVoteAndGenerateCRISPInputs = async ( encodedVote: string[], publicKey: Uint8Array, previousCiphertext: Uint8Array, + bfvParams?: BFVParams, ): Promise => { - const zkInputsGenerator = ZKInputsGenerator.withDefaults() + let zkInputsGenerator: ZKInputsGenerator + + if (bfvParams) { + zkInputsGenerator = new ZKInputsGenerator(bfvParams.degree, bfvParams.plaintextModulus, bfvParams.moduli) + } else { + zkInputsGenerator = ZKInputsGenerator.withDefaults() + } const vote = BigUint64Array.from(encodedVote.map(BigInt)) From 0563f5af33f4be9354edc67b78acaf652805e0ed Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 27 Oct 2025 15:32:17 +0000 Subject: [PATCH 3/5] refactor: update vote test --- examples/CRISP/packages/crisp-sdk/tests/vote.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index 7ec98243a3..188e662a7c 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -118,7 +118,7 @@ describe('Vote', () => { let zkInputsGenerator = ZKInputsGenerator.withDefaults() let publicKey = zkInputsGenerator.generatePublicKey() - const previousCiphertext = zkInputsGenerator.encryptVote(publicKey, 0) + const previousCiphertext = zkInputsGenerator.encryptVote(publicKey, new BigUint64Array([0n])) it('should encrypt a vote and generate the circuit inputs', async () => { const encodedVote = encodeVote(vote, VotingMode.GOVERNANCE, votingPower) From 4cdd1afbd000b3b5913a5e825f1adebb79242256 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 27 Oct 2025 16:17:01 +0000 Subject: [PATCH 4/5] refactor: update vote test and types --- .../CRISP/crates/zk-inputs-wasm/src/lib.rs | 53 ++++++++++++------- examples/CRISP/crates/zk-inputs/src/lib.rs | 2 +- .../CRISP/packages/crisp-sdk/src/types.ts | 2 +- examples/CRISP/packages/crisp-sdk/src/vote.ts | 14 +++-- .../packages/crisp-sdk/tests/vote.test.ts | 2 +- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs b/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs index e5271594bd..09d945dd57 100644 --- a/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs +++ b/examples/CRISP/crates/zk-inputs-wasm/src/lib.rs @@ -24,15 +24,18 @@ impl ZKInputsGenerator { /// /// # Arguments /// - `degree`: Polynomial degree - /// - `plaintext_modulus`: Plaintext modulus - /// - `moduli`: Array of moduli + /// - `plaintext_modulus`: Plaintext modulus (will be converted to u64) + /// - `moduli`: Array of moduli (will be converted to Vec) #[wasm_bindgen(constructor)] pub fn new( degree: usize, - plaintext_modulus: u64, - moduli: Vec, + plaintext_modulus: i64, + moduli: Vec, ) -> Result { - let generator = CoreZKInputsGenerator::new(degree, plaintext_modulus, &moduli) + let plaintext_modulus_u64 = plaintext_modulus as u64; + let moduli_vec: Vec = moduli.into_iter().map(|m| m as u64).collect(); + + let generator = CoreZKInputsGenerator::new(degree, plaintext_modulus_u64, &moduli_vec) .map_err(|e| JsValue::from_str(&e.to_string()))?; Ok(ZKInputsGenerator { generator }) } @@ -51,11 +54,13 @@ impl ZKInputsGenerator { &self, prev_ciphertext: &[u8], public_key: &[u8], - vote: Vec, + vote: Vec, ) -> Result { + let vote_vec: Vec = vote.into_iter().map(|v| v as u64).collect(); + match self .generator - .generate_inputs(prev_ciphertext, public_key, vote) + .generate_inputs(prev_ciphertext, public_key, vote_vec) { Ok(inputs_json) => { // Parse the JSON string and return as JsValue. @@ -79,8 +84,10 @@ impl ZKInputsGenerator { /// Encrypt a vote from JavaScript. #[wasm_bindgen(js_name = "encryptVote")] - pub fn encrypt_vote(&self, public_key: &[u8], vote: Vec) -> Result, JsValue> { - match self.generator.encrypt_vote(public_key, vote) { + pub fn encrypt_vote(&self, public_key: &[u8], vote: Vec) -> Result, JsValue> { + let vote_vec: Vec = vote.into_iter().map(|v| v as u64).collect(); + + match self.generator.encrypt_vote(public_key, vote_vec) { Ok(ciphertext_bytes) => Ok(ciphertext_bytes), Err(e) => Err(JsValue::from_str(&e.to_string())), } @@ -91,21 +98,31 @@ impl ZKInputsGenerator { pub fn get_bfv_params(&self) -> Result { let bfv_params = self.generator.get_bfv_params(); let params_json = js_sys::Object::new(); + + // Set degree js_sys::Reflect::set( ¶ms_json, &"degree".into(), &JsValue::from(bfv_params.degree() as u32), )?; + + // Set plaintext_modulus as BigInt to preserve precision for large values. + let plaintext_modulus_bigint = + js_sys::BigInt::new(&JsValue::from_str(&bfv_params.plaintext().to_string()))?; js_sys::Reflect::set( ¶ms_json, - &"plaintext_modulus".into(), - &JsValue::from(bfv_params.plaintext() as f64), + &"plaintextModulus".into(), + &plaintext_modulus_bigint.into(), )?; + + // Return moduli as array of BigInts to preserve precision let moduli_array = js_sys::Array::new(); for modulus in bfv_params.moduli() { - moduli_array.push(&JsValue::from(*modulus as f64)); + let modulus_bigint = js_sys::BigInt::new(&JsValue::from_str(&modulus.to_string()))?; + moduli_array.push(&modulus_bigint.into()); } js_sys::Reflect::set(¶ms_json, &"moduli".into(), &moduli_array.into())?; + Ok(JsValue::from(params_json)) } @@ -125,8 +142,8 @@ mod tests { wasm_bindgen_test_configure!(run_in_browser); /// Helper function to create a vote vector with alternating 0s and 1s (deterministic). - fn create_vote_vector() -> Vec { - (0..DEFAULT_DEGREE).map(|i| (i % 2) as u64).collect() + fn create_vote_vector() -> Vec { + (0..DEFAULT_DEGREE).map(|i| (i % 2) as i64).collect() } #[wasm_bindgen_test] @@ -136,7 +153,7 @@ mod tests { let public_key = generator.generate_public_key().unwrap(); let vote = create_vote_vector(); let old_ciphertext = generator.encrypt_vote(&public_key, vote.clone()).unwrap(); - let result = generator.generate_inputs(&old_ciphertext, &public_key, vote.clone()); + let result = generator.generate_inputs(&old_ciphertext, &public_key, vote); assert!(result.is_ok()); @@ -154,15 +171,15 @@ mod tests { #[wasm_bindgen_test] fn test_js_with_custom_params() { let degree = 2048; - let plaintext_modulus = 1032193; - let moduli = vec![0x3FFFFFFF000001]; + let plaintext_modulus = 1032193i64; + let moduli = vec![0x3FFFFFFF000001i64]; // Create generator with custom parameters. let generator = ZKInputsGenerator::new(degree, plaintext_modulus, moduli).unwrap(); let public_key = generator.generate_public_key().unwrap(); let vote = create_vote_vector(); let old_ciphertext = generator.encrypt_vote(&public_key, vote.clone()).unwrap(); - let result = generator.generate_inputs(&old_ciphertext, &public_key, vote.clone()); + let result = generator.generate_inputs(&old_ciphertext, &public_key, vote); assert!(result.is_ok()); diff --git a/examples/CRISP/crates/zk-inputs/src/lib.rs b/examples/CRISP/crates/zk-inputs/src/lib.rs index 94ed9601b8..136732f2f2 100644 --- a/examples/CRISP/crates/zk-inputs/src/lib.rs +++ b/examples/CRISP/crates/zk-inputs/src/lib.rs @@ -362,7 +362,7 @@ mod tests { // Test that same vote produces different ciphertexts (due to randomness). let ct0_2 = generator - .encrypt_vote(&public_key, vote.clone()) + .encrypt_vote(&public_key, create_vote_vector(DEFAULT_DEGREE)) .expect("Failed to encrypt vote 0 again"); assert_ne!(ct0, ct0_2); diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index 1c238446db..e57f5f31ee 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -174,5 +174,5 @@ export interface CRISPCircuitInputs { export interface BFVParams { degree: number plaintextModulus: bigint - moduli: BigUint64Array + moduli: BigInt64Array } diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 5a2944cfe0..043086ad74 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -152,17 +152,15 @@ export const encryptVoteAndGenerateCRISPInputs = async ( encodedVote: string[], publicKey: Uint8Array, previousCiphertext: Uint8Array, - bfvParams?: BFVParams, + bfvParams: BFVParams = DEFAULT_BFV_PARAMS, ): Promise => { - let zkInputsGenerator: ZKInputsGenerator - - if (bfvParams) { - zkInputsGenerator = new ZKInputsGenerator(bfvParams.degree, bfvParams.plaintextModulus, bfvParams.moduli) - } else { - zkInputsGenerator = ZKInputsGenerator.withDefaults() + if (encodedVote.length !== bfvParams.degree) { + throw new RangeError(`encodedVote length ${encodedVote.length} does not match BFV degree ${bfvParams.degree}`) } - const vote = BigUint64Array.from(encodedVote.map(BigInt)) + 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 diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index 188e662a7c..133ef74629 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -118,7 +118,7 @@ describe('Vote', () => { let zkInputsGenerator = ZKInputsGenerator.withDefaults() let publicKey = zkInputsGenerator.generatePublicKey() - const previousCiphertext = zkInputsGenerator.encryptVote(publicKey, new BigUint64Array([0n])) + const previousCiphertext = zkInputsGenerator.encryptVote(publicKey, new BigInt64Array([0n])) it('should encrypt a vote and generate the circuit inputs', async () => { const encodedVote = encodeVote(vote, VotingMode.GOVERNANCE, votingPower) From e16eb286031bb1893674da133e00a586310bdd83 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 27 Oct 2025 17:12:38 +0000 Subject: [PATCH 5/5] docs: update vote.ts documentation --- examples/CRISP/packages/crisp-sdk/src/vote.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 043086ad74..c1e1e56e70 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -52,8 +52,8 @@ export const calculateValidIndicesForPlaintext = (totalVotingPower: bigint, degr * Encode a vote based on the voting mode * @param vote The vote to encode * @param votingMode The voting mode to use for encoding - * @param bfvDegree The degree of the BFV polynomial to use for encoding * @param votingPower The voting power of the voter + * @param bfvParams The BFV parameters to use for encoding * @returns The encoded vote as a string */ export const encodeVote = (vote: IVote, votingMode: VotingMode, votingPower: bigint, bfvParams?: BFVParams): string[] => { @@ -147,6 +147,8 @@ export const validateVote = (votingMode: VotingMode, vote: IVote, votingPower: b * @param encodedVote The encoded vote as string array * @param publicKey The public key to use for encryption * @param previousCiphertext The previous ciphertext to use for addition operation + * @param bfvParams The BFV parameters to use for encryption + * @returns The CRISP circuit inputs */ export const encryptVoteAndGenerateCRISPInputs = async ( encodedVote: string[],