Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
28d8369
docs(polynomial): explain descending coefficient order for circuit Ho…
cedoor Jan 29, 2026
75dbb59
refactor: add CRT polynomial
cedoor Jan 29, 2026
2ec0484
chore: update crisp lockfile
cedoor Jan 30, 2026
2e164a5
fix(polynomial): enable serde "rc" for Arc<CrtContext>
cedoor Jan 30, 2026
f1d805b
refactor: update ciphertext addition logic with new poly struct
cedoor Jan 30, 2026
240825a
chore: update lockfile
cedoor Jan 30, 2026
c8a75eb
chore: fix lint issues
cedoor Jan 30, 2026
82e7be8
chore: add rand feature to num-bigint
cedoor Jan 30, 2026
bc273f6
refactor(polynomial): add reduce functions
cedoor Jan 30, 2026
38074b4
refactor: remove unused function
cedoor Feb 2, 2026
2d54241
fix(polynomial): enable num-bigint rand feature for fhe-math
cedoor Feb 2, 2026
71b00a2
chore: remove unused rand feature from global num-bigint
cedoor Feb 2, 2026
7521dbe
docs(zk-inputs): clarify first-in-slot prev_ct_commitment comment
cedoor Feb 2, 2026
1241491
fix(zk-inputs): validate exact division remainder in ciphertext addition
cedoor Feb 2, 2026
a9686ac
refactor(polynomial): remove from_ascending_coefficients and to_ascen…
cedoor Feb 2, 2026
ab22b31
refactor: remove redundant reduce and center functions
cedoor Feb 2, 2026
b88ffaa
chore: remove .git from fhe-math dependency
cedoor Feb 2, 2026
0dac129
chore: update scripts to lint circuits
cedoor Feb 2, 2026
36bf62c
docs: remove polynomial representation from README
cedoor Feb 2, 2026
522eb82
fix: update param set name in ciphertext addition
cedoor Feb 2, 2026
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ duct = "=1.0.0"
eyre = { version = "=0.6.12" }
fhe = { git = "https://github.com/gnosisguild/fhe.rs" }
fhe-traits = { git = "https://github.com/gnosisguild/fhe.rs" }
fhe-math = { git = "https://github.com/gnosisguild/fhe.rs.git" }
fhe-math = { git = "https://github.com/gnosisguild/fhe.rs" }
fhe-util = { git = "https://github.com/gnosisguild/fhe.rs" }
figment = { version = "=0.10.19", features = ["yaml", "test"] }
futures = "=0.3.31"
Expand All @@ -154,7 +154,7 @@ greco = { package = "e3-greco-generator", git = "https://github.com/gnosisguild/
hex = "=0.4.3"
lazy_static = "=1.5.0"
num = "=0.4.3"
num-bigint = "=0.4.6"
num-bigint = { version = "=0.4.6" }
num-traits = "=0.2.19"
ndarray = { version = "=0.15.6", features = ["serde"] }
once_cell = "=1.21.3"
Expand Down
3 changes: 2 additions & 1 deletion circuits/bin/threshold/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ members = [
"pk_aggregation",
"user_data_encryption",
"share_decryption",
"decrypted_shares_aggregation"
"decrypted_shares_aggregation_bn",
"decrypted_shares_aggregation_mod"
]
1 change: 1 addition & 0 deletions crates/bfv-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ fhe-traits.workspace = true
greco = { package = "zkfhe-greco", git = "https://github.com/gnosisguild/zkfhe-generator" }
rand.workspace = true
thiserror = { workspace = true }
e3-polynomial = { workspace = true }
e3-zk-helpers = { workspace = true }
12 changes: 10 additions & 2 deletions crates/bfv-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use anyhow::{anyhow, Result};
use e3_fhe_params::build_bfv_params_arc;
use e3_greco_helpers::{bfv_ciphertext_to_greco, bfv_public_key_to_greco};
use e3_polynomial::CrtPolynomial;
use e3_zk_helpers::commitments::{
compute_ciphertext_commitment, compute_pk_aggregation_commitment,
};
Expand Down Expand Up @@ -149,7 +150,11 @@ pub fn compute_pk_commitment(
let bit_pk = calculate_bit_width(&bounds.pk_bounds[0].to_string())?;

let (pk0is, pk1is) = bfv_public_key_to_greco(&public_key, &params);
let commitment_bigint = compute_pk_aggregation_commitment(&pk0is, &pk1is, bit_pk);

let pk0 = CrtPolynomial::from_bigint_vectors(pk0is);
let pk1 = CrtPolynomial::from_bigint_vectors(pk1is);

let commitment_bigint = compute_pk_aggregation_commitment(&pk0, &pk1, bit_pk);

let bytes = commitment_bigint.to_bytes_be().1;

Expand Down Expand Up @@ -187,7 +192,10 @@ pub fn compute_ct_commitment(
let (_, bounds) = GrecoBounds::compute(&params, 0)?;
let bit_ct = calculate_bit_width(&bounds.pk_bounds[0].to_string())?;

let commitment_bigint = compute_ciphertext_commitment(&ct0is, &ct1is, bit_ct);
let ct0 = CrtPolynomial::from_bigint_vectors(ct0is);
let ct1 = CrtPolynomial::from_bigint_vectors(ct1is);

let commitment_bigint = compute_ciphertext_commitment(&ct0, &ct1, bit_ct);

let bytes = commitment_bigint.to_bytes_be().1;

Expand Down
6 changes: 4 additions & 2 deletions crates/polynomial/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ description = "A polynomial library with big integer coefficients for cryptograp
repository = "https://github.com/gnosisguild/enclave/crates/polynomial"

[dependencies]
num-bigint = { workspace = true, features = ["serde"] }
num-bigint = { workspace = true, features = ["rand", "serde"] }
num-traits = { workspace = true }
serde = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["rc"] }
bincode = { workspace = true, optional = true }
thiserror = { workspace = true }
fhe-math = { workspace = true }
ndarray = { workspace = true }
Comment thread
cedoor marked this conversation as resolved.

[dev-dependencies]
criterion = "0.5"
Expand Down
12 changes: 0 additions & 12 deletions crates/polynomial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,6 @@ reduction operations commonly used in:
- **Homomorphic encryption**: BFV, BGV, and CKKS schemes
- **Zero-knowledge proofs**: Polynomial commitment schemes

### Polynomial Representation

Polynomials are represented as:

```
a_n * x^n + a_{n-1} * x^{n-1} + ... + a_1 * x + a_0
```

Where coefficients are stored in descending order (highest degree first) of degree using `BigInt`
for arbitrary precision. You can rely on some methods to transform to ascending order (lowest degree
first) and viceversa.

### Performance

The library is optimized for cryptographic workloads with:
Expand Down
36 changes: 7 additions & 29 deletions crates/polynomial/benches/polynomial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ fn benchmark_modular_reduction(c: &mut Criterion) {
let modulus = BigInt::from(1000000007); // Large prime

group.bench_function(&format!("degree_{}", degree), |b| {
b.iter(|| black_box(poly1.reduce_and_center(&modulus)))
b.iter(|| {
let mut p = poly1.clone();
p.reduce(&modulus);
p.center(&modulus);
black_box(p)
})
});
}

Expand Down Expand Up @@ -146,32 +151,6 @@ fn benchmark_utility_functions(c: &mut Criterion) {
group.finish();
}

fn benchmark_coefficient_conversion(c: &mut Criterion) {
let mut group = c.benchmark_group("coefficient_conversion");

for degree in [10, 50, 100, 500, 1000] {
let (poly, _) = create_test_polynomials(degree);

// Benchmark conversion from ascending to descending order
let ascending_coeffs: Vec<BigInt> = (0..=degree).map(|i| BigInt::from(i)).collect();

group.bench_function(&format!("from_ascending_degree_{}", degree), |b| {
b.iter(|| {
black_box(Polynomial::from_ascending_coefficients(
ascending_coeffs.clone(),
))
})
});

// Benchmark conversion from descending to ascending order
group.bench_function(&format!("to_ascending_degree_{}", degree), |b| {
b.iter(|| black_box(poly.to_ascending_coefficients()))
});
}

group.finish();
}

criterion_group!(
benches,
benchmark_polynomial_addition,
Expand All @@ -180,7 +159,6 @@ criterion_group!(
benchmark_polynomial_evaluation,
benchmark_modular_reduction,
benchmark_cyclotomic_reduction,
benchmark_utility_functions,
benchmark_coefficient_conversion
benchmark_utility_functions
);
criterion_main!(benches);
169 changes: 169 additions & 0 deletions crates/polynomial/src/crt_polynomial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: LGPL-3.0-only
//
// This file is provided WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

//! CRT (Chinese Remainder Theorem) polynomial representation.

use crate::polynomial::Polynomial;
use fhe_math::rq::{Poly, Representation};
use num_bigint::BigInt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Errors that can occur during CRT polynomial operations.
#[derive(Debug, Error)]
pub enum CrtPolynomialError {
/// Moduli slice length does not match number of limbs.
#[error("moduli length ({moduli_len}) must match limbs length ({limbs_len})")]
ModuliLengthMismatch { limbs_len: usize, moduli_len: usize },
}

/// A polynomial in CRT form: one limb polynomial per modulus.
///
/// Each limb is a `Polynomial` whose coefficients are expected to be reduced/centered
/// modulo the corresponding `ctx.moduli[i]` as required by the caller.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CrtPolynomial {
pub limbs: Vec<Polynomial>,
}

impl CrtPolynomial {
/// Builds a `CrtPolynomial` from a vector of polynomials.
///
/// # Arguments
///
/// * `limbs` - Vector of polynomials.
pub fn new(limbs: Vec<Polynomial>) -> Self {
Self { limbs }
}

/// Builds a `CrtPolynomial` from coefficient vectors (one `Vec<BigInt>` per modulus).
///
/// # Arguments
///
/// * `limbs` - Vector of coefficient vectors.
pub fn from_bigint_vectors(limbs: Vec<Vec<BigInt>>) -> Self {
let limbs = limbs.into_iter().map(Polynomial::new).collect::<Vec<_>>();

Self { limbs }
}

/// Builds a `CrtPolynomial` from an fhe-math `Poly` in PowerBasis representation.
///
/// Used to prepare inputs for ZK circuits by converting FHE BFV ciphertext polynomials
/// into CRT limb format. If `p` is in NTT form, it is converted to PowerBasis first.
///
/// # Arguments
///
/// * `p` - An fhe-math polynomial (PowerBasis or Ntt).
pub fn from_fhe_polynomial(p: &Poly) -> Self {
let mut p = p.clone();

if *p.representation() == Representation::Ntt {
p.change_representation(Representation::PowerBasis);
}

let limbs = p
.coefficients()
.outer_iter()
.map(|row| Polynomial::from_u64_vector(row.to_vec()))
.collect();

Self { limbs }
}
Comment thread
cedoor marked this conversation as resolved.

/// Reverses the coefficient order of every limb in-place.
///
/// For each limb, converts between descending degree (a_n, …, a_0) and ascending
/// degree (a_0, …, a_n). Calling this twice restores the original order.
pub fn reverse(&mut self) {
for limb in &mut self.limbs {
limb.reverse();
}
}

/// Centers each limb's coefficients (already in [0, q_i)) into (-q_i/2, q_i/2] in-place.
///
/// # Arguments
///
/// * `moduli` - One modulus per limb; `moduli[i]` is used for `self.limbs[i]`.
///
/// # Errors
///
/// Returns [`CrtPolynomialError::ModuliLengthMismatch`] if `moduli.len() != self.limbs.len()`.
pub fn center(&mut self, moduli: &[u64]) -> Result<(), CrtPolynomialError> {
if self.limbs.len() != moduli.len() {
return Err(CrtPolynomialError::ModuliLengthMismatch {
limbs_len: self.limbs.len(),
moduli_len: moduli.len(),
});
}

for (limb, qi) in self.limbs.iter_mut().zip(moduli.iter()) {
limb.center(&BigInt::from(*qi));
}

Ok(())
}

/// Reduces each limb's coefficients modulo the corresponding modulus in-place (range [0, qi)).
///
/// # Arguments
///
/// * `moduli` - One modulus per limb; `moduli[i]` is used for `self.limbs[i]`.
///
/// # Errors
///
/// Returns [`CrtPolynomialError::ModuliLengthMismatch`] if `moduli.len() != self.limbs.len()`.
pub fn reduce(&mut self, moduli: &[u64]) -> Result<(), CrtPolynomialError> {
if self.limbs.len() != moduli.len() {
return Err(CrtPolynomialError::ModuliLengthMismatch {
limbs_len: self.limbs.len(),
moduli_len: moduli.len(),
});
}

for (limb, qi) in self.limbs.iter_mut().zip(moduli.iter()) {
limb.reduce(&BigInt::from(*qi));
}

Ok(())
}

/// Reduces each limb's coefficients modulo the same modulus in-place.
///
/// Every limb uses the same `modulus`; coefficients are reduced into the range `[0, modulus)`.
/// Use this when all limbs should be reduced by one common modulus (e.g. a single prime)
/// instead of per-limb moduli as in [`reduce`](Self::reduce).
///
/// # Arguments
///
/// * `modulus` - The modulus applied to every limb.
pub fn reduce_uniform(&mut self, modulus: &BigInt) {
for limb in &mut self.limbs {
limb.reduce(&modulus);
}
}

/// Returns a reference to the limb polynomial at the given index.
///
/// # Arguments
///
/// * `i` - Limb index; must be in range `0..self.limbs.len()`.
///
/// # Returns
///
/// A reference to the polynomial for modulus `i`. Coefficients are expected to be
/// reduced/centered modulo the corresponding modulus as required by the caller.
///
/// # Panics
///
/// Panics if `i >= self.limbs.len()`.
pub fn limb(&self, i: usize) -> &Polynomial {
&self.limbs[i]
}
}
2 changes: 2 additions & 0 deletions crates/polynomial/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
//! - Homomorphic encryption: BFV, BGV, and CKKS schemes.
//! - Zero-knowledge proofs: Polynomial commitment schemes.

pub mod crt_polynomial;
pub mod polynomial;
pub mod utils;

pub use crt_polynomial::{CrtPolynomial, CrtPolynomialError};
pub use polynomial::{Polynomial, PolynomialError};
pub use utils::*;
Loading
Loading