Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 63 additions & 15 deletions examples/CRISP/crates/zk-inputs-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>)
#[wasm_bindgen(constructor)]
pub fn new(
degree: usize,
plaintext_modulus: u64,
moduli: Vec<u64>,
plaintext_modulus: i64,
moduli: Vec<i64>,
) -> Result<ZKInputsGenerator, JsValue> {
let generator = CoreZKInputsGenerator::new(degree, plaintext_modulus, &moduli)
let plaintext_modulus_u64 = plaintext_modulus as u64;
let moduli_vec: Vec<u64> = 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 })
Comment thread
cedoor marked this conversation as resolved.
}
Expand All @@ -51,11 +54,13 @@ impl ZKInputsGenerator {
&self,
prev_ciphertext: &[u8],
public_key: &[u8],
vote: u8,
vote: Vec<i64>,
) -> Result<JsValue, JsValue> {
let vote_vec: Vec<u64> = 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.
Expand All @@ -79,13 +84,48 @@ impl ZKInputsGenerator {

/// Encrypt a vote from JavaScript.
#[wasm_bindgen(js_name = "encryptVote")]
pub fn encrypt_vote(&self, public_key: &[u8], vote: u8) -> Result<Vec<u8>, JsValue> {
match self.generator.encrypt_vote(public_key, vote) {
pub fn encrypt_vote(&self, public_key: &[u8], vote: Vec<i64>) -> Result<Vec<u8>, JsValue> {
let vote_vec: Vec<u64> = 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())),
}
}
Comment thread
cedoor marked this conversation as resolved.

/// Get the BFV parameters used by the generator.
#[wasm_bindgen(js_name = "getBFVParams")]
pub fn get_bfv_params(&self) -> Result<JsValue, JsValue> {
let bfv_params = self.generator.get_bfv_params();
let params_json = js_sys::Object::new();

// Set degree
js_sys::Reflect::set(
&params_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(
&params_json,
&"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() {
let modulus_bigint = js_sys::BigInt::new(&JsValue::from_str(&modulus.to_string()))?;
moduli_array.push(&modulus_bigint.into());
}
js_sys::Reflect::set(&params_json, &"moduli".into(), &moduli_array.into())?;

Ok(JsValue::from(params_json))
}
Comment thread
cedoor marked this conversation as resolved.

/// Get the version of the library.
#[wasm_bindgen]
pub fn version() -> String {
Expand All @@ -97,16 +137,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<i64> {
(0..DEFAULT_DEGREE).map(|i| (i % 2) as i64).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);

assert!(result.is_ok());

Expand All @@ -124,14 +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 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);

assert!(result.is_ok());

Expand Down
83 changes: 46 additions & 37 deletions examples/CRISP/crates/zk-inputs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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> {
Self::new(DEFAULT_DEGREE, DEFAULT_PLAINTEXT_MODULUS, &DEFAULT_MODULI)
}
Expand All @@ -57,28 +63,22 @@ 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
pub fn generate_inputs(
&self,
prev_ciphertext: &[u8],
public_key: &[u8],
vote: u8,
vote: Vec<u64>,
) -> Result<String> {
// 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")?;
Comment thread
cedoor marked this conversation as resolved.

// Encrypt using the provided public key to ensure ciphertext matches the key.
Expand Down Expand Up @@ -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<Vec<u8>> {
pub fn encrypt_vote(&self, public_key: &[u8], vote: Vec<u64>) -> Result<Vec<u8>> {
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")?;

Comment thread
cedoor marked this conversation as resolved.
let (ct, _u_rns, _e0_rns, _e1_rns) = pk
Expand Down Expand Up @@ -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<u64> {
(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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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"));
Expand All @@ -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());
}

Expand All @@ -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());
}

Expand All @@ -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();
Expand All @@ -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, create_vote_vector(DEFAULT_DEGREE))
.expect("Failed to encrypt vote 0 again");
assert_ne!(ct0, ct0_2);

Expand Down
1 change: 0 additions & 1 deletion examples/CRISP/packages/crisp-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions examples/CRISP/packages/crisp-sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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
Loading
Loading