From 78155a813a833992d0563565aefe7adb13b80df3 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 2 Feb 2026 17:38:08 +0100 Subject: [PATCH 1/4] refactor: simplify polynomial utility functions --- crates/polynomial/benches/polynomial.rs | 18 +- crates/polynomial/src/utils.rs | 359 +----------------------- 2 files changed, 12 insertions(+), 365 deletions(-) diff --git a/crates/polynomial/benches/polynomial.rs b/crates/polynomial/benches/polynomial.rs index 2e3beb019b..8c29c27d83 100644 --- a/crates/polynomial/benches/polynomial.rs +++ b/crates/polynomial/benches/polynomial.rs @@ -121,19 +121,17 @@ fn benchmark_cyclotomic_reduction(c: &mut Criterion) { fn benchmark_utility_functions(c: &mut Criterion) { let mut group = c.benchmark_group("utility_functions"); - // Benchmark reduce_and_center let x = BigInt::from(123456789); let modulus = BigInt::from(1000000007); - let half_modulus = &modulus / 2; - group.bench_function("reduce_and_center", |b| { - b.iter(|| { - black_box(e3_polynomial::utils::reduce_and_center( - &x, - &modulus, - &half_modulus, - )) - }) + // Benchmark reduce + group.bench_function("reduce", |b| { + b.iter(|| black_box(e3_polynomial::utils::reduce(&x, &modulus))) + }); + + // Benchmark center + group.bench_function("center", |b| { + b.iter(|| black_box(e3_polynomial::utils::center(&x, &modulus))) }); // Benchmark range checking diff --git a/crates/polynomial/src/utils.rs b/crates/polynomial/src/utils.rs index 9b611c94c6..bdb1d41318 100644 --- a/crates/polynomial/src/utils.rs +++ b/crates/polynomial/src/utils.rs @@ -6,8 +6,6 @@ //! Utility functions for polynomial operations. -use crate::polynomial::PolynomialError; -use crate::Polynomial; use num_bigint::BigInt; use num_traits::Zero; @@ -36,36 +34,6 @@ pub fn center(x: &BigInt, modulus: &BigInt) -> BigInt { r } -/// Reduces a number modulo a prime modulus and centers it. -/// -/// This function takes an arbitrary number and reduces it modulo the specified prime modulus. -/// After reduction, the number is adjusted to be within the symmetric range -/// [−(modulus−1)/2, (modulus−1)/2]. If the number is already within this range, it remains unchanged. -/// -/// # Arguments -/// -/// * `x` - A reference to a `BigInt` representing the number to be reduced and centered. -/// * `modulus` - A reference to the prime modulus `BigInt` used for reduction. -/// * `half_modulus` - A reference to a `BigInt` representing half of the modulus used to center the coefficient. -/// -/// # Returns -/// -/// A `BigInt` representing the reduced and centered number. -pub fn reduce_and_center(x: &BigInt, modulus: &BigInt, half_modulus: &BigInt) -> BigInt { - let mut r = reduce(x, modulus); - - // Adjust the remainder if it is greater than half_modulus. - if (modulus % BigInt::from(2)) == BigInt::from(1) { - if r > *half_modulus { - r -= modulus; - } - } else if r >= *half_modulus { - r -= modulus; - } - - r -} - /// Reduces a number modulo a modulus. /// /// # Arguments @@ -84,122 +52,6 @@ pub fn reduce(x: &BigInt, modulus: &BigInt) -> BigInt { r } -/// Centers polynomial coefficients that are already in [0, modulus) into (-modulus/2, modulus/2]. -/// -/// # Arguments -/// -/// * `coefficients` - Coefficients in [0, modulus); mutated in place. -/// * `modulus` - The modulus. -pub fn center_coefficients_mut(coefficients: &mut [BigInt], modulus: &BigInt) { - coefficients - .iter_mut() - .for_each(|x| *x = center(x, modulus)); -} - -/// Reduces a polynomial's coefficients within a polynomial ring defined by a cyclotomic polynomial and a modulus. -/// -/// This function performs two reductions on the polynomial represented by `coefficients`: -/// 1. **Cyclotomic Reduction**: Reduces the polynomial by the cyclotomic polynomial, replacing -/// the original coefficients with the remainder after polynomial division. -/// 2. **Modulus Reduction**: Reduces the coefficients of the polynomial modulo a given modulus, -/// centering the coefficients within the range [-modulus/2, modulus/2). -/// -/// # Arguments -/// -/// * `coefficients` - A mutable reference to a `Vec` representing the coefficients of the polynomial -/// to be reduced. The coefficients should be in descending order of degree. -/// * `cyclo` - A slice of `BigInt` representing the coefficients of the cyclotomic polynomial (typically x^N + 1). -/// * `modulus` - A reference to a `BigInt` representing the modulus for the coefficient reduction. The coefficients -/// will be reduced and centered modulo this value. -/// -/// # Returns -/// -/// Returns `Ok(())` on success, or a `PolynomialError` if the cyclotomic reduction fails. -pub fn reduce_in_ring( - coefficients: &mut Vec, - cyclo: &[BigInt], - modulus: &BigInt, -) -> Result<(), PolynomialError> { - let coeffs = coefficients.clone(); - let poly = Polynomial::new(coeffs); - let reduced = poly.reduce_by_cyclotomic(cyclo)?; - *coefficients = reduced.coefficients; - reduce_coefficients_mut(coefficients, modulus); - center_coefficients_mut(coefficients, modulus); - Ok(()) -} - -/// Reduces each element in the given slice of `BigInt` by the modulus `p`. -/// -/// This function takes a slice of `BigInt` coefficients and applies the modulus operation -/// on each element. It ensures the result is within the range `[0, p-1]` by computing -/// `r = coeff % p` and adding `p` if `r` is negative. The result is collected into a new `Vec`. -/// -/// # Arguments -/// -/// * `coefficients` - A slice of `BigInt` representing the coefficients to be reduced. -/// * `p` - A reference to a `BigInt` that represents the modulus value. -/// -/// # Returns -/// -/// A `Vec` where each element is reduced modulo `p`. -pub fn reduce_coefficients(coefficients: &[BigInt], p: &BigInt) -> Vec { - coefficients.iter().map(|coeff| reduce(coeff, p)).collect() -} - -/// Reduces coefficients in a 2D matrix. -/// -/// # Arguments -/// -/// * `coefficient_matrix` - A 2D matrix of coefficients to reduce. -/// * `p` - The modulus to reduce by. -/// -/// # Returns -/// -/// A new 2D matrix with reduced coefficients. -pub fn reduce_coefficients_2d(coefficient_matrix: &[Vec], p: &BigInt) -> Vec> { - coefficient_matrix - .iter() - .map(|coeffs| reduce_coefficients(coeffs, p)) - .collect() -} - -/// Reduces coefficients in a 3D matrix. -/// -/// # Arguments -/// -/// * `coefficient_matrix` - A 3D matrix of coefficients to reduce. -/// * `p` - The modulus to reduce by. -/// -/// # Returns -/// -/// A new 3D matrix with reduced coefficients. -pub fn reduce_coefficients_3d( - coefficient_matrix: &[Vec>], - p: &BigInt, -) -> Vec>> { - coefficient_matrix - .iter() - .map(|coeffs| reduce_coefficients_2d(coeffs, p)) - .collect() -} - -/// Mutably reduces each element in the given slice of `BigInt` by the modulus `p`. -/// -/// This function modifies the given mutable slice of `BigInt` coefficients in place. It computes -/// `r = coeff % p` for each element, then adds `p` if `r` is negative, ensuring the results are -/// within the range `[0, p-1]`. -/// -/// # Arguments -/// -/// * `coefficients` - A mutable slice of `BigInt` representing the coefficients to be reduced. -/// * `p` - A reference to a `BigInt` that represents the modulus value. -pub fn reduce_coefficients_mut(coefficients: &mut [BigInt], p: &BigInt) { - for coeff in coefficients.iter_mut() { - *coeff = reduce(coeff, p); - } -} - /// Checks if all coefficients in a vector are within a centered range. /// /// This function verifies that every coefficient in the input vector falls within @@ -294,93 +146,23 @@ mod tests { use num_bigint::BigInt; #[test] - fn test_reduce_and_center() { + fn test_reduce_then_center() { let modulus = BigInt::from(7); - let half_modulus = &modulus / 2; - // Test positive number assert_eq!( - reduce_and_center(&BigInt::from(10), &modulus, &half_modulus), + center(&reduce(&BigInt::from(10), &modulus), &modulus), BigInt::from(3) ); - - // Test negative number assert_eq!( - reduce_and_center(&BigInt::from(-3), &modulus, &half_modulus), + center(&reduce(&BigInt::from(-3), &modulus), &modulus), BigInt::from(-3) ); - - // Test number greater than half modulus assert_eq!( - reduce_and_center(&BigInt::from(6), &modulus, &half_modulus), + center(&reduce(&BigInt::from(6), &modulus), &modulus), BigInt::from(-1) ); } - #[test] - fn test_reduce_coefficients() { - let coeffs = vec![BigInt::from(10), BigInt::from(-3), BigInt::from(15)]; - let modulus = BigInt::from(7); - let result = reduce_coefficients(&coeffs, &modulus); - assert_eq!( - result, - vec![BigInt::from(3), BigInt::from(4), BigInt::from(1)] - ); - } - - #[test] - fn test_reduce_coefficients_less_than_neg_modulus() { - let modulus = BigInt::from(7); - - // Test with values < -p (the bug fix case) - let coeffs = vec![ - BigInt::from(-10), // -10 % 7 = -3, -3 + 7 = 4 - BigInt::from(-14), // -14 % 7 = 0 - BigInt::from(-15), // -15 % 7 = -1, -1 + 7 = 6 - BigInt::from(-21), // -21 % 7 = 0 - ]; - let result = reduce_coefficients(&coeffs, &modulus); - assert_eq!( - result, - vec![ - BigInt::from(4), - BigInt::from(0), - BigInt::from(6), - BigInt::from(0) - ] - ); - - // Test mixed positive and negative values - let coeffs2 = vec![ - BigInt::from(-50), - BigInt::from(-7), - BigInt::from(-1), - BigInt::from(0), - BigInt::from(1), - BigInt::from(7), - BigInt::from(50), - ]; - let result2 = reduce_coefficients(&coeffs2, &modulus); - assert_eq!( - result2, - vec![ - BigInt::from(6), // -50 % 7 = -1, -1 + 7 = 6 - BigInt::from(0), // -7 % 7 = 0 - BigInt::from(6), // -1 % 7 = -1, -1 + 7 = 6 - BigInt::from(0), - BigInt::from(1), - BigInt::from(0), // 7 % 7 = 0 - BigInt::from(1), // 50 % 7 = 1 - ] - ); - - // Verify all results are in [0, modulus) - for r in &result2 { - assert!(*r >= BigInt::from(0), "Result {} should be >= 0", r); - assert!(*r < modulus, "Result {} should be < {}", r, modulus); - } - } - #[test] fn test_range_check_centered() { let vec = vec![BigInt::from(-2), BigInt::from(0), BigInt::from(2)]; @@ -400,14 +182,6 @@ mod tests { assert!(range_check_standard(&vec, &bound, &modulus)); } - #[test] - fn test_reduce() { - let x = BigInt::from(-3); - let modulus = BigInt::from(7); - let result = reduce(&x, &modulus); - assert_eq!(result, BigInt::from(4)); - } - #[test] fn test_reduce_less_than_neg_modulus() { let modulus = BigInt::from(7); @@ -458,129 +232,4 @@ mod tests { ); } } - - #[test] - fn test_reduce_in_ring() { - // Test successful reduction - // cyclo = [1, 0, 1] represents x^2 + 1, so n = cyclo.len() - 1 = 2 - let cyclo = vec![BigInt::from(1), BigInt::from(0), BigInt::from(1)]; - let modulus = BigInt::from(7); - - // Create coefficients: [1, 2, 3] represents x^2 + 2x + 3 - let mut coeffs = vec![BigInt::from(1), BigInt::from(2), BigInt::from(3)]; - - // Reduce in ring: first reduce by cyclotomic, then reduce coefficients modulo - let result = reduce_in_ring(&mut coeffs, &cyclo, &modulus); - assert!(result.is_ok()); - - // Verify coefficients were modified in place - // The result should be the remainder after dividing by x^2 + 1, then reduced mod 7 - // x^2 + 2x + 3 divided by x^2 + 1 gives remainder 2x + 2 - // After right-aligning to n=2 (cyclo.len()-1 = 2): [2, 2] - // After mod 7 and centering: [2, 2] - assert_eq!(coeffs.len(), 2); - // The remainder 2x + 2 = [2, 2], when right-aligned to length 2, gives [2, 2] - assert_eq!(coeffs[0], BigInt::from(2)); - assert_eq!(coeffs[1], BigInt::from(2)); - } - - #[test] - fn test_reduce_in_ring_error_cases() { - // Test with zero cyclotomic polynomial - let cyclo_zero = vec![BigInt::from(0), BigInt::from(0)]; - let modulus = BigInt::from(7); - let mut coeffs = vec![BigInt::from(1), BigInt::from(2)]; - - let result = reduce_in_ring(&mut coeffs, &cyclo_zero, &modulus); - assert!(matches!(result, Err(PolynomialError::DivisionByZero))); - - // Test with invalid cyclotomic (zero leading coefficient) - let cyclo_invalid = vec![BigInt::from(0), BigInt::from(1)]; - let mut coeffs2 = vec![BigInt::from(1), BigInt::from(2)]; - - let result2 = reduce_in_ring(&mut coeffs2, &cyclo_invalid, &modulus); - assert!(matches!( - result2, - Err(PolynomialError::InvalidPolynomial { .. }) - )); - } - - #[test] - fn test_reduce_in_ring_modulus_reduction() { - // Test that coefficients are properly reduced and centered modulo - let cyclo = vec![BigInt::from(1), BigInt::from(0), BigInt::from(1)]; - let modulus = BigInt::from(7); - - // Create coefficients with large values - let mut coeffs = vec![BigInt::from(10), BigInt::from(15), BigInt::from(20)]; - - let result = reduce_in_ring(&mut coeffs, &cyclo, &modulus); - assert!(result.is_ok()); - - // Verify coefficients are reduced and centered (within [-3, 3] for modulus 7) - for coeff in &coeffs { - assert!(*coeff >= BigInt::from(-3)); - assert!(*coeff <= BigInt::from(3)); - } - } - - #[test] - fn test_reduce_coefficients_mut() { - let modulus = BigInt::from(7); - - // Test with values < -p (the bug fix case) - let mut coeffs = vec![ - BigInt::from(-10), // -10 % 7 = -3, -3 + 7 = 4 - BigInt::from(-14), // -14 % 7 = 0 - BigInt::from(-15), // -15 % 7 = -1, -1 + 7 = 6 - BigInt::from(-21), // -21 % 7 = 0 - ]; - reduce_coefficients_mut(&mut coeffs, &modulus); - assert_eq!( - coeffs, - vec![ - BigInt::from(4), - BigInt::from(0), - BigInt::from(6), - BigInt::from(0) - ] - ); - - // Test mixed positive and negative values - let mut coeffs2 = vec![ - BigInt::from(-50), - BigInt::from(-7), - BigInt::from(-1), - BigInt::from(0), - BigInt::from(1), - BigInt::from(7), - BigInt::from(50), - ]; - reduce_coefficients_mut(&mut coeffs2, &modulus); - assert_eq!( - coeffs2, - vec![ - BigInt::from(6), // -50 % 7 = -1, -1 + 7 = 6 - BigInt::from(0), // -7 % 7 = 0 - BigInt::from(6), // -1 % 7 = -1, -1 + 7 = 6 - BigInt::from(0), - BigInt::from(1), - BigInt::from(0), // 7 % 7 = 0 - BigInt::from(1), // 50 % 7 = 1 - ] - ); - - // Verify all results are in [0, modulus) - for r in &coeffs2 { - assert!(*r >= BigInt::from(0), "Result {} should be >= 0", r); - assert!(*r < modulus, "Result {} should be < {}", r, modulus); - } - - // Test that it modifies in place - let mut coeffs3 = vec![BigInt::from(-3)]; - let original_ptr = coeffs3.as_ptr(); - reduce_coefficients_mut(&mut coeffs3, &modulus); - assert_eq!(coeffs3[0], BigInt::from(4)); - assert_eq!(coeffs3.as_ptr(), original_ptr); // Same memory location - } } From f227e4f7e9808ae56ee0fa2ad455eb238565606a Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 2 Feb 2026 17:38:16 +0100 Subject: [PATCH 2/4] refactor: update pk_bfv computation logic --- crates/zk-helpers/src/circuits/errors.rs | 2 +- .../zk-helpers/src/circuits/pk_bfv/codegen.rs | 18 +--- .../src/circuits/pk_bfv/computation.rs | 98 ++++++------------- 3 files changed, 36 insertions(+), 82 deletions(-) diff --git a/crates/zk-helpers/src/circuits/errors.rs b/crates/zk-helpers/src/circuits/errors.rs index da45852c57..561f09b57e 100644 --- a/crates/zk-helpers/src/circuits/errors.rs +++ b/crates/zk-helpers/src/circuits/errors.rs @@ -19,7 +19,7 @@ pub enum CircuitsErrors { #[error("BFV error: {0}")] Fhe(#[from] fhe::Error), #[error("ZK helper error: {0}")] - ZkHelpers(#[from] ZkHelpersUtilsError), + ZkHelpers(#[from] e3_zk_helpers::utils::ZkHelpersUtilsError), #[error("Unexpected error: {0}")] Other(String), } diff --git a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs b/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs index d6402e3501..7b4534e98d 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs +++ b/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs @@ -8,16 +8,8 @@ use crate::circuits::pk_bfv::circuit::PkBfvCircuitInput; use crate::circuits::pk_bfv::computation::{Bits, Bounds, Witness}; -use crate::circuits::PkBfvCircuit; -use crate::codegen::Artifacts; -use crate::codegen::CircuitCodegen; -use crate::computation::Computation; -use crate::computation::Configs; -use crate::computation::ReduceToZkpModulus; -use crate::computation::Toml; -use crate::errors::CircuitsErrors; -use crate::registry::Circuit; -use crate::utils::map_witness_2d_vector_to_json; +use crate::{Artifacts, CircuitCodegen, CircuitsErrors, Configs, PkBfvCircuit}; + use fhe::bfv::BfvParameters; use serde::{Deserialize, Serialize}; use serde_json; @@ -57,9 +49,9 @@ pub struct TomlJson { } /// Builds the Prover TOML string from the pk-bfv witness (pk0is, pk1is). -pub fn generate_toml(witness: Witness) -> Result { - let pk0is = map_witness_2d_vector_to_json(&witness.pk0is); - let pk1is = map_witness_2d_vector_to_json(&witness.pk1is); +pub fn generate_toml(witness: Witness) -> Result { + let pk0is = crt_polynomial_to_toml_json(&witness.pk0is); + let pk1is = crt_polynomial_to_toml_json(&witness.pk1is); let toml_json = TomlJson { pk0is, pk1is }; Ok(toml::to_string(&toml_json)?) diff --git a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs b/crates/zk-helpers/src/circuits/pk_bfv/computation.rs index 5dffbfec2c..d9e305ef49 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs +++ b/crates/zk-helpers/src/circuits/pk_bfv/computation.rs @@ -9,23 +9,14 @@ //! [`Constants`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters //! and (for witness) a public key. They implement [`Computation`] and are used by codegen. -use crate::circuits::pk_bfv::circuit::PkBfvCircuitInput; -use crate::computation::CircuitComputation; -use crate::computation::Computation; -use crate::computation::ConvertToJson; -use crate::computation::ReduceToZkpModulus; -use crate::errors::CircuitsErrors; -use crate::utils::calculate_bit_width; -use crate::utils::get_zkp_modulus; -use crate::PkBfvCircuit; -use e3_polynomial::center_coefficients_mut; -use e3_polynomial::reduce_coefficients_2d; +use crate::traits::Computation; +use crate::traits::ConvertToJson; +use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; +use e3_zk_helpers::get_zkp_modulus; +use e3_zk_helpers::utils::calculate_bit_width; use fhe::bfv::BfvParameters; -use fhe_math::rq::Representation; -use itertools::izip; -use num_bigint::BigInt; +use fhe::bfv::PublicKey; use num_bigint::BigUint; -use rayon::prelude::*; use serde::{Deserialize, Serialize}; /// Output of [`CircuitComputation::compute`] for [`PkBfvCircuit`]: bounds, bit widths, and witness. @@ -95,10 +86,9 @@ pub struct Bounds { /// Witness data for the pk-bfv circuit: public key polynomials in CRT form for the prover. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Witness { - /// First component of the public key per modulus (coefficients as BigInt). - pub pk0is: Vec>, - /// Second component of the public key per modulus (coefficients as BigInt). - pub pk1is: Vec>, + /// Public key polynomials (pk0, pk1) for each CRT basis. + pub pk0is: CrtPolynomial, + pub pk1is: CrtPolynomial, } impl Computation for Configs { @@ -158,42 +148,24 @@ impl Computation for Bounds { impl Computation for Witness { type Params = BfvParameters; type Input = PkBfvCircuitInput; - type Error = fhe::Error; + type Error = CrtPolynomialError; fn compute(params: &Self::Params, input: &Self::Input) -> Result { let moduli = params.moduli(); - // Extract public key components (pk0, pk1) from the ciphertext structure - // and change representation to Power Basis. - let mut pk0 = input.public_key.c.c[0].clone(); - let mut pk1 = input.public_key.c.c[1].clone(); - pk0.change_representation(Representation::PowerBasis); - pk1.change_representation(Representation::PowerBasis); - - let pk0_coeffs = pk0.coefficients(); - let pk1_coeffs = pk1.coefficients(); - let pk0_rows = pk0_coeffs.rows(); - let pk1_rows = pk1_coeffs.rows(); - - // Extract and convert public key polynomials per modulus - // Collect into Vec first to preserve moduli ordering with par_iter - let zipped: Vec<_> = izip!(moduli, pk0_rows, pk1_rows).collect(); - let results: Vec<(Vec, Vec)> = zipped - .par_iter() - .map(|(qi, pk0_coeffs, pk1_coeffs)| { - let mut pk0i: Vec = - pk0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); - let mut pk1i: Vec = - pk1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); - - center_coefficients_mut(&mut pk0i, &BigInt::from(**qi)); - center_coefficients_mut(&mut pk1i, &BigInt::from(**qi)); - - (pk0i, pk1i) - }) - .collect(); - - let (pk0is, pk1is): (Vec<_>, Vec<_>) = results.into_iter().unzip(); + let mut pk0is = CrtPolynomial::from_fhe_polynomial(&input.public_key.c.c[0]); + let mut pk1is = CrtPolynomial::from_fhe_polynomial(&input.public_key.c.c[1]); + + pk0is.reverse(); + pk1is.reverse(); + + pk0is.center(&moduli)?; + pk1is.center(&moduli)?; + + let zkp_modulus = &get_zkp_modulus(); + + pk0is.reduce_uniform(zkp_modulus); + pk1is.reduce_uniform(zkp_modulus); Ok(Witness { pk0is, pk1is }) } @@ -217,21 +189,12 @@ impl ConvertToJson for Witness { } } -impl ReduceToZkpModulus for Witness { - fn reduce_to_zkp_modulus(&self) -> Witness { - Witness { - pk0is: reduce_coefficients_2d(&self.pk0is, &get_zkp_modulus()), - pk1is: reduce_coefficients_2d(&self.pk1is, &get_zkp_modulus()), - } - } -} - #[cfg(test)] mod tests { use super::*; - use crate::computation::ConvertToJson; - use crate::computation::ReduceToZkpModulus; - use crate::sample::Sample; + + use crate::sample::generate_sample; + use crate::traits::ConvertToJson; use e3_fhe_params::BfvParamSet; use e3_fhe_params::DEFAULT_BFV_PRESET; @@ -249,7 +212,7 @@ mod tests { #[test] fn test_witness_reduction_and_json_roundtrip() { let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let encryption_data = Sample::generate(¶ms); + let encryption_data = generate_sample(¶ms); let witness = Witness::compute( ¶ms, &PkBfvCircuitInput { @@ -257,12 +220,11 @@ mod tests { }, ) .unwrap(); - let zkp_reduced = witness.reduce_to_zkp_modulus(); - let json = zkp_reduced.convert_to_json().unwrap(); + let json = witness.convert_to_json().unwrap(); let decoded: Witness = serde_json::from_value(json.clone()).unwrap(); - assert_eq!(decoded.pk0is, zkp_reduced.pk0is); - assert_eq!(decoded.pk1is, zkp_reduced.pk1is); + assert_eq!(decoded.pk0is, witness.pk0is); + assert_eq!(decoded.pk1is, witness.pk1is); } #[test] From 2d4547f215205fa81941029f5af0f5cf9e16a68f Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 3 Feb 2026 13:55:50 +0100 Subject: [PATCH 3/4] refactor: pk-bfv computation and codegen --- crates/zk-helpers/Cargo.toml | 2 +- crates/zk-helpers/src/circuits/computation.rs | 6 +-- crates/zk-helpers/src/circuits/errors.rs | 5 ++- .../zk-helpers/src/circuits/pk_bfv/circuit.rs | 1 - .../zk-helpers/src/circuits/pk_bfv/codegen.rs | 23 ++++++----- .../src/circuits/pk_bfv/computation.rs | 38 +++++++------------ crates/zk-helpers/src/utils.rs | 14 ++++--- 7 files changed, 40 insertions(+), 49 deletions(-) diff --git a/crates/zk-helpers/Cargo.toml b/crates/zk-helpers/Cargo.toml index bedddc62c6..72e4f7c847 100644 --- a/crates/zk-helpers/Cargo.toml +++ b/crates/zk-helpers/Cargo.toml @@ -11,7 +11,7 @@ anyhow = { workspace = true } ark-bn254 = { workspace = true } clap = { workspace = true } ark-ff = { workspace = true } -e3-polynomial = { workspace = true } +e3-polynomial = { workspace = true, features = ["serde"] } e3-safe = { workspace = true } e3-fhe-params = { workspace = true } fhe = { workspace = true } diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index 524892bac8..b43ce9cebc 100644 --- a/crates/zk-helpers/src/circuits/computation.rs +++ b/crates/zk-helpers/src/circuits/computation.rs @@ -42,11 +42,7 @@ pub trait CircuitComputation: crate::registry::Circuit { type Error; /// Computes circuit-specific data (bounds, bits, witness) from parameters and input. - fn compute( - &self, - params: &Self::Params, - input: &Self::Input, - ) -> Result; + fn compute(params: &Self::Params, input: &Self::Input) -> Result; } /// Converts a value to a JSON [`serde_json::Value`] for serialization. diff --git a/crates/zk-helpers/src/circuits/errors.rs b/crates/zk-helpers/src/circuits/errors.rs index 561f09b57e..ea0b95e3ce 100644 --- a/crates/zk-helpers/src/circuits/errors.rs +++ b/crates/zk-helpers/src/circuits/errors.rs @@ -7,6 +7,7 @@ //! Error types for circuit and codegen operations. use crate::utils::ZkHelpersUtilsError; +use e3_polynomial::CrtPolynomialError; use thiserror::Error; /// Errors that can occur during circuit codegen or artifact I/O. @@ -18,8 +19,10 @@ pub enum CircuitsErrors { Toml(#[from] toml::ser::Error), #[error("BFV error: {0}")] Fhe(#[from] fhe::Error), + #[error("CRT polynomial error: {0}")] + CrtPolynomial(#[from] CrtPolynomialError), #[error("ZK helper error: {0}")] - ZkHelpers(#[from] e3_zk_helpers::utils::ZkHelpersUtilsError), + ZkHelpers(#[from] ZkHelpersUtilsError), #[error("Unexpected error: {0}")] Other(String), } diff --git a/crates/zk-helpers/src/circuits/pk_bfv/circuit.rs b/crates/zk-helpers/src/circuits/pk_bfv/circuit.rs index 7cd37300fa..35d9474884 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/circuit.rs +++ b/crates/zk-helpers/src/circuits/pk_bfv/circuit.rs @@ -20,6 +20,5 @@ impl Circuit for PkBfvCircuit { } pub struct PkBfvCircuitInput { - /// BFV public key. pub public_key: PublicKey, } diff --git a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs b/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs index 7b4534e98d..1b17b0e335 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs +++ b/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs @@ -6,9 +6,11 @@ //! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. -use crate::circuits::pk_bfv::circuit::PkBfvCircuitInput; -use crate::circuits::pk_bfv::computation::{Bits, Bounds, Witness}; -use crate::{Artifacts, CircuitCodegen, CircuitsErrors, Configs, PkBfvCircuit}; +use crate::circuits::pk_bfv::circuit::{PkBfvCircuit, PkBfvCircuitInput}; +use crate::{ + crt_polynomial_to_toml_json, Artifacts, Bits, Circuit, CircuitCodegen, CircuitComputation, + CircuitsErrors, Configs, PkBfvComputationOutput, Toml, Witness, +}; use fhe::bfv::BfvParameters; use serde::{Deserialize, Serialize}; @@ -26,13 +28,9 @@ impl CircuitCodegen for PkBfvCircuit { params: &Self::Params, input: &Self::Input, ) -> Result { - // Compute. - let bounds = Bounds::compute(¶ms, &())?; - let bits = Bits::compute(¶ms, &bounds)?; - let witness = Witness::compute(¶ms, input)?; - let zkp_witness = witness.reduce_to_zkp_modulus(); + let PkBfvComputationOutput { witness, bits, .. } = PkBfvCircuit::compute(params, input)?; - let toml = generate_toml(zkp_witness)?; + let toml = generate_toml(witness)?; let configs = generate_configs(¶ms, &bits); Ok(Artifacts { toml, configs }) @@ -49,11 +47,12 @@ pub struct TomlJson { } /// Builds the Prover TOML string from the pk-bfv witness (pk0is, pk1is). -pub fn generate_toml(witness: Witness) -> Result { +pub fn generate_toml(witness: Witness) -> Result { let pk0is = crt_polynomial_to_toml_json(&witness.pk0is); let pk1is = crt_polynomial_to_toml_json(&witness.pk1is); let toml_json = TomlJson { pk0is, pk1is }; + Ok(toml::to_string(&toml_json)?) } @@ -83,8 +82,11 @@ pub global {}_BIT_PK: u32 = {}; #[cfg(test)] mod tests { use super::*; + use crate::circuits::computation::Computation; use crate::codegen::write_artifacts; use crate::sample::Sample; + use crate::Bounds; + use e3_fhe_params::BfvParamSet; use e3_fhe_params::DEFAULT_BFV_PRESET; use tempfile::TempDir; @@ -138,6 +140,7 @@ mod tests { let configs_content = std::fs::read_to_string(&configs_path).unwrap(); let bounds = Bounds::compute(¶ms, &()).unwrap(); let bits = Bits::compute(¶ms, &bounds).unwrap(); + assert!(configs_content.contains(format!("N: u32 = {}", params.degree()).as_str())); assert!(configs_content.contains(format!("L: u32 = {}", params.moduli().len()).as_str())); assert!(configs_content.contains(format!("PK_BFV_BIT_PK: u32 = {}", bits.pk_bit).as_str())); diff --git a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs b/crates/zk-helpers/src/circuits/pk_bfv/computation.rs index d9e305ef49..71ac9d1604 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs +++ b/crates/zk-helpers/src/circuits/pk_bfv/computation.rs @@ -9,24 +9,23 @@ //! [`Constants`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters //! and (for witness) a public key. They implement [`Computation`] and are used by codegen. -use crate::traits::Computation; -use crate::traits::ConvertToJson; -use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; -use e3_zk_helpers::get_zkp_modulus; -use e3_zk_helpers::utils::calculate_bit_width; +use crate::calculate_bit_width; +use crate::get_zkp_modulus; +use crate::pk_bfv::PkBfvCircuitInput; +use crate::CircuitsErrors; +use crate::ConvertToJson; +use crate::PkBfvCircuit; +use crate::{CircuitComputation, Computation}; +use e3_polynomial::CrtPolynomial; use fhe::bfv::BfvParameters; -use fhe::bfv::PublicKey; use num_bigint::BigUint; use serde::{Deserialize, Serialize}; /// Output of [`CircuitComputation::compute`] for [`PkBfvCircuit`]: bounds, bit widths, and witness. #[derive(Debug)] pub struct PkBfvComputationOutput { - /// Coefficient bounds for public key polynomials. pub bounds: Bounds, - /// Bit widths for the prover (e.g. pk_bit). pub bits: Bits, - /// Witness data (pk0is, pk1is) for the Noir prover. pub witness: Witness, } @@ -37,11 +36,7 @@ impl CircuitComputation for PkBfvCircuit { type Output = PkBfvComputationOutput; type Error = CircuitsErrors; - fn compute( - &self, - params: &Self::Params, - input: &Self::Input, - ) -> Result { + fn compute(params: &Self::Params, input: &Self::Input) -> Result { let bounds = Bounds::compute(params, &())?; let bits = Bits::compute(params, &bounds)?; let witness = Witness::compute(params, input)?; @@ -57,29 +52,22 @@ impl CircuitComputation for PkBfvCircuit { /// BFV parameters extracted for the circuit: degree, number of moduli, and modulus values. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Configs { - /// Polynomial degree (N). pub n: usize, - /// Number of CRT moduli (L). pub l: usize, - /// CRT moduli q_i. pub moduli: Vec, - /// Bits. pub bits: Bits, - /// Bounds. pub bounds: Bounds, } /// Bit widths used by the Noir prover (e.g. for packing coefficients). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Bits { - /// Bit width for public key coefficients. pub pk_bit: u32, } /// Coefficient bounds for public key polynomials (used to derive bit widths). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Bounds { - /// Bound for public key polynomials (pk0, pk1). pub pk_bound: BigUint, } @@ -148,7 +136,7 @@ impl Computation for Bounds { impl Computation for Witness { type Params = BfvParameters; type Input = PkBfvCircuitInput; - type Error = CrtPolynomialError; + type Error = CircuitsErrors; fn compute(params: &Self::Params, input: &Self::Input) -> Result { let moduli = params.moduli(); @@ -193,8 +181,8 @@ impl ConvertToJson for Witness { mod tests { use super::*; - use crate::sample::generate_sample; - use crate::traits::ConvertToJson; + use crate::ConvertToJson; + use crate::Sample; use e3_fhe_params::BfvParamSet; use e3_fhe_params::DEFAULT_BFV_PRESET; @@ -212,7 +200,7 @@ mod tests { #[test] fn test_witness_reduction_and_json_roundtrip() { let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let encryption_data = generate_sample(¶ms); + let encryption_data = Sample::generate(¶ms); let witness = Witness::compute( ¶ms, &PkBfvCircuitInput { diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index 84eca15131..34661eac9d 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -16,6 +16,7 @@ use ark_bn254::Fr as Field; use ark_bn254::Fr as FieldElement; use ark_ff::PrimeField; +use e3_polynomial::CrtPolynomial; use e3_safe::SafeSponge; use num_bigint::BigInt; use num_traits::Zero; @@ -150,19 +151,20 @@ pub fn get_zkp_modulus() -> BigInt { .expect("Invalid ZKP modulus") } -/// Map a 2D vector of BigInt to a vector of JSON values. +/// Map a CRT polynomial to a vector of JSON values. /// /// # Arguments -/// * `values` - 2D vector of BigInt values +/// * `crt_polynomial` - CRT polynomial to convert to TOML JSON /// /// # Returns /// A vector of JSON values -pub fn map_witness_2d_vector_to_json(values: &Vec>) -> Vec { - values +pub fn crt_polynomial_to_toml_json(crt_polynomial: &CrtPolynomial) -> Vec { + crt_polynomial + .limbs .iter() - .map(|value| { + .map(|limb| { serde_json::json!({ - "coefficients": to_string_1d_vec(value) + "coefficients": to_string_1d_vec(limb.coefficients()) }) }) .collect() From 016e1ebc27e30ef11b80e037b01ef7954604b4cf Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 3 Feb 2026 13:59:39 +0100 Subject: [PATCH 4/4] chore: update template Cargo lockfile --- templates/default/Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/default/Cargo.lock b/templates/default/Cargo.lock index b444b796f6..a48d246b18 100644 --- a/templates/default/Cargo.lock +++ b/templates/default/Cargo.lock @@ -1326,6 +1326,7 @@ dependencies = [ "ndarray", "num-bigint", "num-traits", + "serde", "thiserror", ]