From c35e080a94455c10bcbe9e2b97ddc846ee8e758e Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 5 Feb 2026 16:19:04 +0100 Subject: [PATCH 01/10] add share-encryption circuit gen --- crates/zk-helpers/src/bin/zk_cli.rs | 32 ++++++++++++++----- .../src/circuits/dkg/share_encryption/mod.rs | 2 +- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index f425af26ce..7767cc5009 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -264,29 +264,45 @@ fn main() -> Result<()> { circuit.codegen(preset, &sample)? } name if name == ::NAME => { + let sd = preset + .search_defaults() + .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; let sample = ShareComputationCircuitInput::generate_sample( preset, committee, dkg_input_type, + sd.z, + sd.lambda, ); - let circuit = ShareComputationCircuit; + circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset.search_defaults().ok_or_else(|| { - anyhow!("preset does not define search defaults for {}", name) - })?; - let sample = ShareEncryptionCircuitInput::generate_sample( + let sd = preset + .search_defaults() + .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; + let sample = ShareEncryptionSample::generate( preset, - committee, + CiphernodesCommitteeSize::Small, dkg_input_type, sd.z, sd.lambda, ); - let circuit = ShareEncryptionCircuit; - circuit.codegen(preset, &sample)? + + circuit.codegen( + preset, + &ShareEncryptionCircuitInput { + plaintext: sample.plaintext, + ciphertext: sample.ciphertext, + public_key: sample.public_key, + secret_key: sample.secret_key, + u_rns: sample.u_rns, + e0_rns: sample.e0_rns, + e1_rns: sample.e1_rns, + }, + )? } name if name == ::NAME => { let sample = UserDataEncryptionCircuitInput::generate_sample(preset); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs index a52d988af2..f18c24fbe0 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs @@ -11,4 +11,4 @@ pub mod codegen; pub mod computation; pub mod sample; pub use circuit::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; -pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; +pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; \ No newline at end of file From 5ad8c2c12d2ac8c0927540d8b0d5f610f8677169 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 5 Feb 2026 17:22:38 +0100 Subject: [PATCH 02/10] port of c4 --- .../src/circuits/dkg/share_decryption/mod.rs | 2 +- crates/zk-helpers/src/utils.rs | 38 +++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs index 5313a42a56..b8f977a81f 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs @@ -11,4 +11,4 @@ pub mod codegen; pub mod computation; pub mod sample; pub use circuit::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; -pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; +pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; \ No newline at end of file diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index c1b7865245..3d38add9cf 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -216,7 +216,25 @@ pub fn crt_polynomial_to_toml_json(crt_polynomial: &CrtPolynomial) -> Vec Vec { + bigint_1d + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect() +} + +/// Convert a 2D vector of BigInt to a vector of vectors of JSON values. +/// +/// # Arguments +/// * `bigint_2d` - 2D vector of BigInt values +/// +/// # Returns +/// A vector of vectors of JSON values pub fn bigint_2d_to_json_values(y: &[Vec]) -> Vec> { y.iter() .map(|coeff| { @@ -228,15 +246,21 @@ pub fn bigint_2d_to_json_values(y: &[Vec]) -> Vec .collect() } +/// Nested BigInt structure to JSON: map each value to `Value::String(s)`. +/// /// # Arguments -/// * `bigint_1d` - 1D vector of BigInt values +/// * `y` - 3D vector of BigInt values /// /// # Returns -/// A vector of JSON values -pub fn bigint_1d_to_json_values(bigint_1d: &[BigInt]) -> Vec { - bigint_1d - .iter() - .map(|v| serde_json::Value::String(v.to_string())) +/// A vector of vectors of vectors of JSON values +pub fn bigint_3d_to_json_values(y: &[Vec>]) -> Vec>> { + y.iter() + .map(|coeff| { + coeff + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect() + }) .collect() } From 0280291273b2239faafa73f92a00fb57556fc8e0 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 18:06:34 +0100 Subject: [PATCH 03/10] generate_sample using the same input in user_data_enc --- crates/zk-helpers/src/bin/zk_cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 7767cc5009..7ba80b862b 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -306,8 +306,8 @@ fn main() -> Result<()> { } name if name == ::NAME => { let sample = UserDataEncryptionCircuitInput::generate_sample(preset); - let circuit = UserDataEncryptionCircuit; + circuit.codegen(preset, &sample)? } name if name == ::NAME => { From 925a6f253db30c8ad6eeee4cfeb88c73831c1991 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 18:26:23 +0100 Subject: [PATCH 04/10] simplify and uniform the samples --- crates/zk-helpers/src/bin/zk_cli.rs | 29 ++++--------------- .../src/circuits/dkg/share_decryption/mod.rs | 2 +- .../src/circuits/dkg/share_encryption/mod.rs | 2 +- 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 7ba80b862b..e33e4daa17 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -264,50 +264,31 @@ fn main() -> Result<()> { circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; let sample = ShareComputationCircuitInput::generate_sample( preset, committee, dkg_input_type, - sd.z, - sd.lambda, ); let circuit = ShareComputationCircuit; - circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = ShareEncryptionSample::generate( + let sd = preset.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, sd.z, sd.lambda, ); - let circuit = ShareEncryptionCircuit; - circuit.codegen( - preset, - &ShareEncryptionCircuitInput { - plaintext: sample.plaintext, - ciphertext: sample.ciphertext, - public_key: sample.public_key, - secret_key: sample.secret_key, - u_rns: sample.u_rns, - e0_rns: sample.e0_rns, - e1_rns: sample.e1_rns, - }, - )? + let circuit = ShareEncryptionCircuit; + circuit.codegen(preset, &sample)? } name if name == ::NAME => { let sample = UserDataEncryptionCircuitInput::generate_sample(preset); - let circuit = UserDataEncryptionCircuit; + let circuit = UserDataEncryptionCircuit; circuit.codegen(preset, &sample)? } name if name == ::NAME => { diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs index b8f977a81f..5313a42a56 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs @@ -11,4 +11,4 @@ pub mod codegen; pub mod computation; pub mod sample; pub use circuit::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; -pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; \ No newline at end of file +pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs index f18c24fbe0..a52d988af2 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs @@ -11,4 +11,4 @@ pub mod codegen; pub mod computation; pub mod sample; pub use circuit::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; -pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; \ No newline at end of file +pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; From 3d91af4f87f7c5ede3a1a9db0a2bf7a46197c803 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 21:31:19 +0100 Subject: [PATCH 05/10] port c7 --- Cargo.lock | 1 + Cargo.toml | 1 + .../Nargo.toml | 2 +- .../Nargo.toml | 2 +- .../lib/src/configs/insecure/threshold.nr | 4 +- circuits/lib/src/configs/secure/threshold.nr | 2 +- crates/zk-helpers/Cargo.toml | 1 + crates/zk-helpers/src/bin/zk_cli.rs | 13 + .../src/circuits/dkg/pk/computation.rs | 4 +- .../dkg/share_encryption/computation.rs | 15 +- .../decrypted_shares_aggregation/circuit.rs | 35 ++ .../decrypted_shares_aggregation/codegen.rs | 128 ++++++ .../computation.rs | 408 ++++++++++++++++++ .../decrypted_shares_aggregation/mod.rs | 22 + .../decrypted_shares_aggregation/sample.rs | 267 ++++++++++++ .../decrypted_shares_aggregation/utils.rs | 122 ++++++ .../zk-helpers/src/circuits/threshold/mod.rs | 1 + .../threshold/pk_generation/computation.rs | 2 +- .../user_data_encryption/computation.rs | 13 +- .../threshold/user_data_encryption/utils.rs | 4 +- crates/zk-helpers/src/crt.rs | 48 --- crates/zk-helpers/src/lib.rs | 6 +- crates/zk-helpers/src/math.rs | 210 +++++++++ crates/zk-helpers/src/ring.rs | 88 ---- examples/CRISP/Cargo.lock | 1 + templates/default/Cargo.lock | 1 + 26 files changed, 1238 insertions(+), 163 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/circuit.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/mod.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs delete mode 100644 crates/zk-helpers/src/crt.rs create mode 100644 crates/zk-helpers/src/math.rs delete mode 100644 crates/zk-helpers/src/ring.rs diff --git a/Cargo.lock b/Cargo.lock index 01d6e25262..c7e628c25c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3799,6 +3799,7 @@ dependencies = [ "itertools 0.14.0", "ndarray", "num-bigint", + "num-integer", "num-traits", "rand 0.8.5", "rayon", diff --git a/Cargo.toml b/Cargo.toml index a7854cec23..38ad27f0cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,6 +153,7 @@ lazy_static = "=1.5.0" num = "=0.4.3" num-bigint = { version = "=0.4.6" } num-traits = "=0.2.19" +num-integer = "0.1" ndarray = { version = "=0.15.6", features = ["serde"] } once_cell = "=1.21.3" opentelemetry = "=0.29.0" diff --git a/circuits/bin/threshold/decrypted_shares_aggregation_bn/Nargo.toml b/circuits/bin/threshold/decrypted_shares_aggregation_bn/Nargo.toml index 55b7c2948d..7531bee3e1 100644 --- a/circuits/bin/threshold/decrypted_shares_aggregation_bn/Nargo.toml +++ b/circuits/bin/threshold/decrypted_shares_aggregation_bn/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "decrypted_shares_aggregation" +name = "decrypted_shares_aggregation_bn" type = "bin" authors = ["Gnosis Guild / Enclave"] version = "1.0.0-beta.15" diff --git a/circuits/bin/threshold/decrypted_shares_aggregation_mod/Nargo.toml b/circuits/bin/threshold/decrypted_shares_aggregation_mod/Nargo.toml index 55b7c2948d..c9dbf1f0ad 100644 --- a/circuits/bin/threshold/decrypted_shares_aggregation_mod/Nargo.toml +++ b/circuits/bin/threshold/decrypted_shares_aggregation_mod/Nargo.toml @@ -1,5 +1,5 @@ [package] -name = "decrypted_shares_aggregation" +name = "decrypted_shares_aggregation_mod" type = "bin" authors = ["Gnosis Guild / Enclave"] version = "1.0.0-beta.15" diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index 4b6b92c4a5..531c406ae5 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -14,9 +14,9 @@ use crate::core::threshold::user_data_encryption::Configs as UserDataEncryptionC pub global N: u32 = 512; pub global L: u32 = 2; pub global PLAINTEXT_MODULUS: Field = 100; +pub global Q_INVERSE_MOD_T: Field = 57; pub global QIS: [Field; L] = [68719403009, 68719230977]; pub global Q_MOD_T: Field = 3; -pub global Q_INVERSE_MOD_T: Field = 7; pub global T_INV_MOD_Q: Field = 1416703358393105942938; /************************************ @@ -132,7 +132,7 @@ decrypted_shares_aggregation (CIRCUIT 7) ************************************/ // decrypted_shares_aggregation - bit parameters -pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 69; +pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 65; // decrypted_shares_aggregation - configs pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs = diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index 6c9b499e14..b0bc41df5d 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -144,7 +144,7 @@ decrypted_shares_aggregation (CIRCUIT 7) ************************************/ // decrypted_shares_aggregation - bit parameters -pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 201; +pub global DECRYPTED_SHARES_AGGREGATION_BIT_NOISE: u32 = 200; // decrypted_shares_aggregation - configs pub global DECRYPTED_SHARES_AGGREGATION_CONFIGS: DecryptedSharesAggregationConfigs = diff --git a/crates/zk-helpers/Cargo.toml b/crates/zk-helpers/Cargo.toml index 741732d526..6c8a75feed 100644 --- a/crates/zk-helpers/Cargo.toml +++ b/crates/zk-helpers/Cargo.toml @@ -18,6 +18,7 @@ fhe = { workspace = true } fhe-math = { workspace = true } fhe-traits = { workspace = true } num-bigint = { workspace = true } +num-integer = { workspace = true } num-traits = { workspace = true } thiserror = { workspace = true } rand = { workspace = true } diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index e33e4daa17..d0097adf36 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -26,6 +26,9 @@ use e3_zk_helpers::dkg::share_decryption::{ }; use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; +use e3_zk_helpers::threshold::decrypted_shares_aggregation::{ + DecryptedSharesAggregationCircuit, DecryptedSharesAggregationCircuitInput, +}; use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuit; use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuitInput; use e3_zk_helpers::threshold::pk_generation::{PkGenerationCircuit, PkGenerationCircuitInput}; @@ -169,6 +172,7 @@ fn main() -> Result<()> { registry.register(Arc::new(DkgShareDecryptionCircuit)); registry.register(Arc::new(PkAggregationCircuit)); registry.register(Arc::new(ThresholdShareDecryptionCircuit)); + registry.register(Arc::new(DecryptedSharesAggregationCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -320,6 +324,15 @@ fn main() -> Result<()> { let circuit = ThresholdShareDecryptionCircuit; circuit.codegen(preset, &sample)? } + name if name == ::NAME => { + let sample = DecryptedSharesAggregationCircuitInput::generate_sample( + preset, + CiphernodesCommitteeSize::Small.values(), + ); + + let circuit = DecryptedSharesAggregationCircuit; + circuit.codegen(preset, &sample)? + } name => return Err(anyhow!("circuit {} not yet implemented", name)), }; diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index bff8be486e..eb51262a84 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -153,8 +153,8 @@ impl Computation for Witness { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let moduli = dkg_params.moduli(); - let mut pk0is = crate::crt::fhe_poly_to_crt_centered(&input.public_key.c.c[0], moduli)?; - let mut pk1is = crate::crt::fhe_poly_to_crt_centered(&input.public_key.c.c[1], moduli)?; + let mut pk0is = crate::math::fhe_poly_to_crt_centered(&input.public_key.c.c[0], moduli)?; + let mut pk1is = crate::math::fhe_poly_to_crt_centered(&input.public_key.c.c[1], moduli)?; let zkp_modulus = &get_zkp_modulus(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index eb9d353403..b3a803c98d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -15,13 +15,13 @@ use crate::circuits::commitments::{ }; use std::ops::Deref; -use crate::crt::compute_k0is; +use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::dkg::share_encryption::ShareEncryptionCircuit; use crate::dkg::share_encryption::ShareEncryptionCircuitInput; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; -use crate::ring::{cyclotomic_polynomial, decompose_residue}; -use crate::utils::{compute_modulus_bit, compute_msg_bit}; +use crate::math::{cyclotomic_polynomial, decompose_residue}; +use crate::utils::{compute_msg_bit, compute_pk_bit}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; use crate::{CircuitComputation, Computation}; @@ -152,12 +152,13 @@ impl Computation for Configs { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let moduli = dkg_params.moduli().to_vec(); - let ctx = dkg_params.ctx_at_level(0)?; - let modulus = BigInt::from(ctx.modulus().clone()); - let t = BigInt::from(dkg_params.plaintext()); + let plaintext = dkg_params.plaintext(); + let q = compute_q_product(&moduli); + let q_mod_t_uint = compute_q_mod_t(&q, plaintext); + let t = BigInt::from(plaintext); let p = get_zkp_modulus(); - let q_mod_t = center(&reduce(&modulus, &t), &t); + let q_mod_t = center(&BigInt::from(q_mod_t_uint), &t); let q_mod_t_mod_p = reduce(&q_mod_t, &p); let k0is = compute_k0is(&moduli, dkg_params.plaintext())?; diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/circuit.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/circuit.rs new file mode 100644 index 0000000000..5e78ab057d --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/circuit.rs @@ -0,0 +1,35 @@ +// 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. + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use crate::CiphernodesCommittee; +use e3_fhe_params::ParameterType; +use fhe_math::rq::Poly; + +/// Circuit identifier for threshold decrypted-shares aggregation (Noir circuit 7). +#[derive(Debug)] +pub struct DecryptedSharesAggregationCircuit; + +impl Circuit for DecryptedSharesAggregationCircuit { + const NAME: &'static str = "decrypted-shares-aggregation"; + const PREFIX: &'static str = "DECRYPTED_SHARES_AGGREGATION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + const DKG_INPUT_TYPE: Option = None; +} + +/// Raw input for witness computation: decryption share polynomials from T+1 parties, +/// party IDs (1-based), and decoded message. Witness::compute runs Lagrange + CRT. +#[derive(Debug, Clone)] +pub struct DecryptedSharesAggregationCircuitInput { + pub committee: CiphernodesCommittee, + /// Decryption shares from T+1 parties (Poly in RNS form). + pub d_share_polys: Vec, + /// Party IDs (1-based: 1, 2, ..., T+1) for the reconstructing parties. + pub reconstructing_parties: Vec, + /// Decoded message polynomial coefficients. + pub message_vec: Vec, +} diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs new file mode 100644 index 0000000000..c176ea6ecb --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/codegen.rs @@ -0,0 +1,128 @@ +// 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. + +//! Code generation for the Decrypted Shares Aggregation circuit: Prover.toml and configs.nr. + +use e3_fhe_params::BfvPreset; + +use crate::circuits::computation::Computation; +use crate::threshold::decrypted_shares_aggregation::circuit::DecryptedSharesAggregationCircuit; +use crate::threshold::decrypted_shares_aggregation::computation::{Configs, Witness}; +use crate::threshold::decrypted_shares_aggregation::DecryptedSharesAggregationCircuitInput; +use crate::Circuit; +use crate::CircuitCodegen; +use crate::CircuitsErrors; +use crate::{Artifacts, CodegenConfigs, CodegenToml}; + +/// Implementation of [`CircuitCodegen`] for [`DecryptedSharesAggregationCircuit`]. +impl CircuitCodegen for DecryptedSharesAggregationCircuit { + type Preset = BfvPreset; + type Input = DecryptedSharesAggregationCircuitInput; + type Error = CircuitsErrors; + + fn codegen(&self, preset: Self::Preset, input: &Self::Input) -> Result { + let witness = Witness::compute(preset, input)?; + let configs = Configs::compute(preset, &())?; + + let toml = generate_toml(witness)?; + let configs_str = generate_configs(preset, &configs); + + Ok(Artifacts { + toml, + configs: configs_str, + }) + } +} + +pub fn generate_toml(witness: Witness) -> Result { + let json = witness.to_json().map_err(CircuitsErrors::SerdeJson)?; + + Ok(toml::to_string(&json)?) +} + +/// Generates the decrypted_shares_aggregation config fragment for threshold.nr. +/// Emits L, QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T so the circuit uses the same +/// crypto params as the witness (avoids "Cannot satisfy constraint" from config mismatch). +pub fn generate_configs(_preset: BfvPreset, configs: &Configs) -> CodegenConfigs { + let prefix = ::PREFIX; + let qis_str = configs + .moduli + .iter() + .map(|q| q.to_string()) + .collect::>() + .join(", "); + + format!( + r#"use crate::core::threshold::decrypted_shares_aggregation::Configs as DecryptedSharesAggregationConfigs; + +/************************************ +------------------------------------- +decrypted_shares_aggregation (CIRCUIT 7) +------------------------------------- +************************************/ + +pub global L: u32 = {}; +pub global QIS: [Field; L] = [{}]; +pub global PLAINTEXT_MODULUS: Field = {}; +pub global Q_INVERSE_MOD_T: Field = {}; + +pub global {}_BIT_NOISE: u32 = {}; + +pub global {}_CONFIGS: DecryptedSharesAggregationConfigs = + DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T); +"#, + configs.l, + qis_str, + configs.plaintext_modulus, + configs.q_inverse_mod_t, + prefix, + configs.bits.noise_bit, + prefix, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::CiphernodesCommitteeSize; + + #[test] + fn test_configs_generation() { + let preset = BfvPreset::InsecureThreshold512; + let configs = Configs::compute(preset, &()).unwrap(); + let prefix: &str = ::PREFIX; + + let codegen_configs = generate_configs(preset, &configs); + + assert!(codegen_configs.contains("decrypted_shares_aggregation")); + assert!(codegen_configs.contains(&format!( + "{}_BIT_NOISE: u32 = {}", + prefix, configs.bits.noise_bit + ))); + assert!(codegen_configs.contains(&format!("{}_CONFIGS:", prefix))); + assert!(codegen_configs.contains( + "DecryptedSharesAggregationConfigs::new(QIS, PLAINTEXT_MODULUS, Q_INVERSE_MOD_T)" + )); + } + + #[test] + fn test_codegen_with_sample() { + let preset = BfvPreset::InsecureThreshold512; + let committee = CiphernodesCommitteeSize::Small.values(); + let input = DecryptedSharesAggregationCircuitInput::generate_sample(preset, committee); + let circuit = DecryptedSharesAggregationCircuit; + + let artifacts = circuit.codegen(preset, &input).unwrap(); + + assert!(!artifacts.toml.is_empty()); + assert!(artifacts + .configs + .contains("DECRYPTED_SHARES_AGGREGATION_BIT_NOISE")); + assert!(artifacts + .configs + .contains("DECRYPTED_SHARES_AGGREGATION_CONFIGS")); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs new file mode 100644 index 0000000000..c164c3ff48 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -0,0 +1,408 @@ +// 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. + +//! Bounds, configs, bits, and witness computation for the Decryption Share Aggregation TRBFV circuit. +//! +//! Uses [`crate::threshold::decrypted_shares_aggregation::utils`] for Q/delta, modular inverses, +//! Lagrange-at-zero recovery, and scalar CRT reconstruction. Witness coefficients are normalized +//! with [`e3_polynomial::reduce`] in [`Witness::standard_form`], consistent with other circuits. + +use crate::calculate_bit_width; +use crate::get_zkp_modulus; +use crate::threshold::decrypted_shares_aggregation::circuit::DecryptedSharesAggregationCircuit; +use crate::threshold::decrypted_shares_aggregation::circuit::DecryptedSharesAggregationCircuitInput; +use crate::threshold::decrypted_shares_aggregation::utils; +use crate::CircuitsErrors; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_polynomial::reduce; +use e3_fhe_params::BfvPreset; +use fhe_math::rq::Representation; +use num_bigint::{BigInt, BigUint}; +use num_traits::Zero; +use serde::{Deserialize, Serialize}; + +/// Output of [`CircuitComputation::compute`] for [`DecryptedSharesAggregationCircuit`]. +#[derive(Debug)] +pub struct DecryptedSharesAggregationComputationOutput { + pub bounds: Bounds, + pub bits: Bits, + pub witness: Witness, +} + +impl CircuitComputation for DecryptedSharesAggregationCircuit { + type Preset = BfvPreset; + type Input = DecryptedSharesAggregationCircuitInput; + type Output = DecryptedSharesAggregationComputationOutput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let bounds = Bounds::compute(preset, &())?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; + + Ok(DecryptedSharesAggregationComputationOutput { + bounds, + bits, + witness, + }) + } +} + +/// Bounds for noise and scaling: delta = floor(Q/t), delta_half = floor(delta/2). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds { + pub delta: BigUint, + pub delta_half: BigUint, +} + +/// Bit widths used by the circuit (e.g. noise bit for range checks). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + pub noise_bit: u32, +} + +/// Circuit config: moduli count, plaintext modulus, q_inverse_mod_t, bits, bounds, and message polynomial length. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Configs { + pub l: usize, + pub threshold: usize, + pub moduli: Vec, + pub plaintext_modulus: u64, + pub q_inverse_mod_t: u64, + pub bits: Bits, + pub bounds: Bounds, + /// Max number of non-zero coefficients in the message polynomial (matches Noir's MAX_MSG_NON_ZERO_COEFFS). + pub max_msg_non_zero_coeffs: usize, +} + +/// Witness for decrypted shares aggregation (same shape as old DecSharesAggTrBfvVectors). +/// All coefficients reduced to [0, zkp_modulus) in standard_form. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + /// [party][modulus][coeff] + pub decryption_shares: Vec>>, + /// Party IDs (1-based: 1, 2, ..., T+1) + pub party_ids: Vec, + /// Message polynomial coefficients + pub message: Vec, + /// u_global polynomial (CRT reconstruction) + pub u_global: Vec, + /// [modulus][coeff] + pub crt_quotients: Vec>, +} + +impl Computation for Bounds { + type Preset = BfvPreset; + type Input = (); + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, _: &Self::Input) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; + let moduli = threshold_params.moduli(); + let t = threshold_params.plaintext(); + let q = utils::compute_q_product(moduli); + let delta = utils::compute_delta(&q, t); + let delta_half = utils::compute_delta_half(&delta); + Ok(Bounds { delta, delta_half }) + } +} + +impl Computation for Bits { + type Preset = BfvPreset; + type Input = Bounds; + type Error = CircuitsErrors; + + fn compute(_: Self::Preset, bounds: &Self::Input) -> Result { + let noise_bit = calculate_bit_width(BigInt::from(bounds.delta_half.clone())); + Ok(Bits { noise_bit }) + } +} + +impl Computation for Configs { + type Preset = BfvPreset; + type Input = (); + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, _: &Self::Input) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; + let moduli = threshold_params.moduli().to_vec(); + let t = threshold_params.plaintext(); + let q = utils::compute_q_product(&moduli); + let q_inverse_mod_t = utils::compute_q_inverse_mod_t(&q, t)?; + let bounds = Bounds::compute(preset, &())?; + let bits = Bits::compute(preset, &bounds)?; + Ok(Configs { + threshold: 0, // Not derived from preset; set by caller if needed. + l: moduli.len(), + moduli, + plaintext_modulus: t, + q_inverse_mod_t, + bits, + bounds, + // TODO: make this configurable based on the application (e.g., CRISP = 80, + // since there's just CRISP for now we can hardcode it). + max_msg_non_zero_coeffs: 80, // Default; matches Noir's MAX_MSG_NON_ZERO_COEFFS. + }) + } +} + +impl Computation for Witness { + type Preset = BfvPreset; + type Input = DecryptedSharesAggregationCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let configs = Configs::compute(preset, &())?; + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; + let ctx = threshold_params + .ctx_at_level(0) + .map_err(|e| CircuitsErrors::Other(format!("ctx_at_level: {:?}", e)))?; + let num_moduli = ctx.moduli().len(); + let degree = ctx.degree; + let threshold = input.committee.threshold; + let max_msg_non_zero_coeffs = configs.max_msg_non_zero_coeffs; + + // Copy to PowerBasis for coefficient extraction + let d_share_polys: Vec<_> = input + .d_share_polys + .iter() + .map(|p| { + let mut copy = p.clone(); + copy.change_representation(Representation::PowerBasis); + copy + }) + .collect(); + + // 1. Extract decryption shares per modulus per party [party][modulus][coeff] + let mut decryption_shares = Vec::with_capacity(d_share_polys.len()); + for d_share in &d_share_polys { + let coeffs = d_share.coefficients(); + let mut party_shares = Vec::with_capacity(num_moduli); + for m in 0..num_moduli { + let modulus_row = coeffs.row(m); + let qi_bigint = BigInt::from(ctx.moduli()[m]); + let coeff_vec: Vec = modulus_row + .iter() + .map(|&x| { + let mut coeff = BigInt::from(x); + coeff %= &qi_bigint; + if coeff < BigInt::zero() { + coeff += &qi_bigint; + } + coeff + }) + .collect(); + party_shares.push(coeff_vec); + } + decryption_shares.push(party_shares); + } + + // 2. Party IDs (1-based) + let party_ids: Vec = input + .reconstructing_parties + .iter() + .map(|&x| BigInt::from(x)) + .collect(); + + // 3. Message (pad to degree for computation, then truncate to MAX_MSG_NON_ZERO_COEFFS for witness) + let mut message: Vec = input.message_vec.iter().map(|&x| BigInt::from(x)).collect(); + message.resize(degree, BigInt::zero()); + + // 4. u^{(l)} via Lagrange per modulus + let reconstructing_parties = &input.reconstructing_parties; + let mut u_per_modulus: Vec> = Vec::new(); + for m in 0..num_moduli { + let modulus = ctx.moduli()[m]; + let mut u_modulus_coeffs = Vec::with_capacity(degree); + for coeff_idx in 0..degree { + let shares: Vec = (0..=threshold) + .map(|party_idx| { + let coeffs = d_share_polys[party_idx].coefficients(); + let row = coeffs.row(m); + BigInt::from(row[coeff_idx]) + }) + .collect(); + let u_coeff_u64 = + utils::lagrange_recover_at_zero(reconstructing_parties, &shares, modulus)?; + u_modulus_coeffs.push(u_coeff_u64); + } + u_per_modulus.push(u_modulus_coeffs); + } + + // 5. u_global via CRT reconstruction + let mut u_global: Vec = Vec::with_capacity(degree); + for coeff_idx in 0..degree { + let rests: Vec = (0..num_moduli) + .map(|m| u_per_modulus[m][coeff_idx]) + .collect(); + let u_global_coeff = utils::crt_reconstruct(&rests, ctx.moduli())?; + u_global.push(BigInt::from(u_global_coeff)); + } + + // 6. CRT quotients: r^{(m)} = (u_global - u^{(m)}) / q_m + let mut crt_quotients: Vec> = Vec::new(); + for (m, u_modulus) in u_per_modulus.iter().enumerate().take(num_moduli) { + let q_m = ctx.moduli()[m]; + let q_m_bigint = BigInt::from(q_m); + let mut r_m_coeffs = Vec::with_capacity(degree); + for (coeff_idx, u_global_val) in u_global.iter().enumerate().take(degree) { + let u_m = BigInt::from(u_modulus[coeff_idx]); + let diff = u_global_val - &u_m; + let remainder = &diff % &q_m_bigint; + if !remainder.is_zero() { + return Err(CircuitsErrors::Other(format!( + "CRT quotient not exact at m={} coeff={}", + m, coeff_idx + ))); + } + r_m_coeffs.push(&diff / &q_m_bigint); + } + crt_quotients.push(r_m_coeffs); + } + + // Truncate to max_msg_non_zero_coeffs. Do NOT reverse: match old impl (dec_shares_agg_trbfv + // vectors.rs) and circuit—index 0 = constant term (ascending order). + let truncate = |v: &[BigInt]| -> Vec { + v.iter().take(max_msg_non_zero_coeffs).cloned().collect() + }; + let decryption_shares: Vec>> = decryption_shares + .into_iter() + .map(|party| party.into_iter().map(|row| truncate(&row)).collect()) + .collect(); + let message = truncate(&message); + let u_global = truncate(&u_global); + let crt_quotients: Vec> = crt_quotients + .into_iter() + .map(|row| truncate(&row)) + .collect(); + + let witness = Witness { + decryption_shares, + party_ids, + message, + u_global, + crt_quotients, + }; + Ok(witness.standard_form()) + } +} + +impl Witness { + /// Reduce all coefficients to [0, zkp_modulus). Uses `e3_polynomial::reduce` like other circuits. + pub fn standard_form(&self) -> Self { + let zkp_modulus = get_zkp_modulus(); + Witness { + decryption_shares: self + .decryption_shares + .iter() + .map(|party| { + party + .iter() + .map(|row| row.iter().map(|c| reduce(c, &zkp_modulus)).collect()) + .collect() + }) + .collect(), + party_ids: self + .party_ids + .iter() + .map(|c| reduce(c, &zkp_modulus)) + .collect(), + message: self.message.iter().map(|c| reduce(c, &zkp_modulus)).collect(), + u_global: self.u_global.iter().map(|c| reduce(c, &zkp_modulus)).collect(), + crt_quotients: self + .crt_quotients + .iter() + .map(|row| row.iter().map(|c| reduce(c, &zkp_modulus)).collect()) + .collect(), + } + } + + /// Serializes the witness to JSON for Prover.toml. Each polynomial is emitted as + /// `{ "coefficients": [string, ...] }` to match Noir's `Polynomial` struct. + pub fn to_json(&self) -> serde_json::Result { + use crate::bigint_1d_to_json_values; + use crate::poly_coefficients_to_toml_json; + + let decryption_shares_json: Vec> = self + .decryption_shares + .iter() + .map(|party| { + party + .iter() + .map(|modulus_row| poly_coefficients_to_toml_json(modulus_row)) + .collect() + }) + .collect(); + let party_ids_json = bigint_1d_to_json_values(&self.party_ids); + let message_json = poly_coefficients_to_toml_json(&self.message); + let u_global_json = poly_coefficients_to_toml_json(&self.u_global); + let crt_quotients_json: Vec = self + .crt_quotients + .iter() + .map(|row| poly_coefficients_to_toml_json(row)) + .collect(); + + let json = serde_json::json!({ + "decryption_shares": decryption_shares_json, + "party_ids": party_ids_json, + "message": message_json, + "u_global": u_global_json, + "crt_quotients": crt_quotients_json, + }); + + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::threshold::decrypted_shares_aggregation::DecryptedSharesAggregationCircuitInput; + use crate::CiphernodesCommitteeSize; + + #[test] + fn test_bounds_and_bits_consistency() { + let preset = BfvPreset::InsecureThreshold512; + let bounds = Bounds::compute(preset, &()).unwrap(); + let bits = Bits::compute(preset, &bounds).unwrap(); + + assert!(!bounds.delta.is_zero()); + assert!(!bounds.delta_half.is_zero()); + assert!(bounds.delta_half < bounds.delta); + assert!(bits.noise_bit > 0); + } + + #[test] + fn test_configs_compute() { + let preset = BfvPreset::InsecureThreshold512; + let configs = Configs::compute(preset, &()).unwrap(); + + assert_eq!(configs.moduli.len(), configs.l); + assert!(configs.q_inverse_mod_t > 0); + } + + #[test] + fn test_full_computation_with_sample() { + let preset = BfvPreset::InsecureThreshold512; + let committee = CiphernodesCommitteeSize::Small.values(); + let input = + DecryptedSharesAggregationCircuitInput::generate_sample(preset, committee.clone()); + + let out = DecryptedSharesAggregationCircuit::compute(preset, &input).unwrap(); + + let configs = Configs::compute(preset, &()).unwrap(); + assert_eq!(out.witness.decryption_shares.len(), committee.threshold + 1); + assert_eq!(out.witness.party_ids.len(), committee.threshold + 1); + assert_eq!(out.witness.message.len(), configs.max_msg_non_zero_coeffs); + assert_eq!(out.witness.u_global.len(), configs.max_msg_non_zero_coeffs); + assert_eq!(out.bounds.delta, out.bounds.delta); // sanity + assert!(out.bits.noise_bit > 0); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/mod.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/mod.rs new file mode 100644 index 0000000000..0ffa414de5 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/mod.rs @@ -0,0 +1,22 @@ +// 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. + +//! Decrypted shares aggregation circuit for threshold BFV. +//! +//! Proves correct aggregation of T+1 decryption shares (Lagrange interpolation at 0 per modulus, +//! CRT reconstruction to u_global, and CRT quotients). Input: decryption share polynomials, +//! 1-based party IDs, and the decoded message. Output: witness (decryption_shares, party_ids, +//! message, u_global, crt_quotients) in standard form for the Noir circuit. + +pub mod circuit; +pub mod codegen; +pub mod computation; +pub mod sample; +pub mod utils; +pub use circuit::*; +pub use codegen::*; +pub use computation::*; +pub use utils::*; diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs new file mode 100644 index 0000000000..1e1c469adb --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/sample.rs @@ -0,0 +1,267 @@ +// 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. + +//! Sample data generation for decrypted shares aggregation circuit. +//! +//! Produces TRBFV parties with secret/public key shares, collects and aggregates shares, +//! encrypts a message, computes T+1 decryption shares, and decrypts to obtain the message. +//! The result is used as input for witness computation and codegen. + +use crate::circuits::computation::Computation; +use crate::threshold::decrypted_shares_aggregation::computation::Configs; +use crate::{ + threshold::decrypted_shares_aggregation::DecryptedSharesAggregationCircuitInput, + CiphernodesCommittee, +}; +use e3_fhe_params::{build_pair_for_preset, BfvPreset}; +use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; +use fhe::mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}; +use fhe::trbfv::{ShareManager, TRBFV}; +use fhe_math::rq::{Poly, Representation}; +use fhe_traits::FheDecoder; +use fhe_traits::{FheEncoder, FheEncrypter}; +use ndarray::Array2; +use rand::rngs::OsRng; +use std::sync::Arc; + +struct Party { + pk_share: PublicKeyShare, + sk_sss: Vec>, + esi_sss: Vec>, + sk_sss_collected: Vec>, + es_sss_collected: Vec>, + sk_poly_sum: Poly, + es_poly_sum: Poly, +} + +impl DecryptedSharesAggregationCircuitInput { + /// Generates sample data for the decrypted shares aggregation circuit: + /// TRBFV setup, parties with sk/pk shares and smudging error shares, share collection + /// and aggregation, encryption of a message, T+1 decryption shares, and threshold decrypt. + pub fn generate_sample(preset: BfvPreset, committee: CiphernodesCommittee) -> Self { + let (threshold_params, _) = build_pair_for_preset(preset).unwrap(); + + let sd = preset.search_defaults().unwrap(); + + let num_parties = committee.n; + let threshold = committee.threshold; + let degree = threshold_params.degree(); + let num_moduli = threshold_params.moduli().len(); + + let trbfv = TRBFV::new(num_parties, threshold, threshold_params.clone()).unwrap(); + let mut rng = OsRng; + let mut thread_rng = rand::thread_rng(); + + let crp = CommonRandomPoly::new(&threshold_params, &mut rng).unwrap(); + + let ctx = threshold_params.ctx_at_level(0).unwrap(); + + let mut parties: Vec = (0..num_parties) + .map(|_| { + let sk_share = SecretKey::random(&threshold_params, &mut rng); + let pk_share = + PublicKeyShare::new(&sk_share, crp.clone(), &mut thread_rng).unwrap(); + + let mut share_manager = + ShareManager::new(num_parties, threshold, threshold_params.clone()); + let sk_poly = share_manager + .coeffs_to_poly_level0(sk_share.coeffs.as_ref()) + .unwrap(); + + let sk_sss = share_manager + .generate_secret_shares_from_poly(sk_poly, &mut rng) + .unwrap(); + + let esi_coeffs = trbfv + .generate_smudging_error(sd.z as usize, sd.lambda as usize, &mut rng) + .unwrap(); + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).unwrap(); + let esi_sss = share_manager + .generate_secret_shares_from_poly(esi_poly, &mut rng) + .unwrap(); + + let sk_sss_collected = Vec::with_capacity(num_parties); + let es_sss_collected = Vec::with_capacity(num_parties); + let sk_poly_sum = Poly::zero(&ctx, Representation::PowerBasis); + let es_poly_sum = Poly::zero(&ctx, Representation::PowerBasis); + + Party { + pk_share, + sk_sss, + esi_sss, + sk_sss_collected, + es_sss_collected, + sk_poly_sum, + es_poly_sum, + } + }) + .collect(); + + // Collect shares: for each party i, sk_sss_collected is one Array2 per sender j + // (same as Vec). Each Array2 has shape (num_moduli, degree): row m = share from j for modulus m. + for i in 0..num_parties { + parties[i].sk_sss_collected = (0..num_parties) + .map(|j| { + let data: Vec = (0..num_moduli) + .flat_map(|m| { + parties[j].sk_sss[m] + .row(i) + .iter() + .copied() + .collect::>() + }) + .collect(); + Array2::from_shape_vec((num_moduli, degree), data) + .map_err(|e| format!("sk_sss_collected shape: {:?}", e)) + }) + .collect::, _>>() + .unwrap(); + parties[i].es_sss_collected = (0..num_parties) + .map(|j| { + let data: Vec = (0..num_moduli) + .flat_map(|m| { + parties[j].esi_sss[m] + .row(i) + .iter() + .copied() + .collect::>() + }) + .collect(); + Array2::from_shape_vec((num_moduli, degree), data) + .map_err(|e| format!("es_sss_collected shape: {:?}", e)) + }) + .collect::, _>>() + .unwrap(); + } + + // Aggregate collected shares to get sk_poly_sum and es_poly_sum per party + for party in parties.iter_mut() { + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + party.sk_poly_sum = share_manager + .aggregate_collected_shares(&party.sk_sss_collected) + .unwrap(); + party.es_poly_sum = share_manager + .aggregate_collected_shares(&party.es_sss_collected) + .unwrap(); + } + + // Aggregate public key + let public_key: PublicKey = parties + .iter() + .map(|p| p.pk_share.clone()) + .collect::>() + .iter() + .cloned() + .aggregate() + .unwrap(); + + // Build message: max_msg_non_zero_coeffs from config, tiled from CRISP-style pattern, pad to degree + let configs = Configs::compute(preset, &()).unwrap(); + let n = configs.max_msg_non_zero_coeffs; + let pattern: Vec = vec![ + 2, 1, 5, 2, 1, 2, 3, 2, 4, 3, 3, 3, 2, 3, 3, 1, 2, 3, 4, 6, 1, 5, 1, 1, 2, 1, 2, + ]; + let mut message: Vec = (0..n).map(|i| pattern[i % pattern.len()]).collect(); + message.resize(degree, 0); + + let pt = Plaintext::try_encode(&message, Encoding::poly(), &threshold_params).unwrap(); + let ciphertext = public_key.try_encrypt(&pt, &mut thread_rng).unwrap(); + + let ciphertext = Arc::new(ciphertext); + + // Decryption shares from T+1 parties (1-based party IDs) + let honest_parties = threshold + 1; + let mut d_share_polys: Vec = Vec::with_capacity(honest_parties); + + for party in parties.iter().take(honest_parties) { + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + // For a single ciphertext, es_poly_sum is one Poly per party + let d_share = share_manager + .decryption_share( + Arc::clone(&ciphertext), + party.sk_poly_sum.clone(), + party.es_poly_sum.clone(), + ) + .unwrap(); + d_share_polys.push(d_share); + } + + let reconstructing_parties: Vec = (1..=honest_parties).collect(); + + // Threshold decrypt to obtain message (verify) + let share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + let plaintext = share_manager + .decrypt_from_shares( + d_share_polys.clone(), + reconstructing_parties.clone(), + Arc::clone(&ciphertext), + ) + .unwrap(); + + let message_vec = Vec::::try_decode(&plaintext, Encoding::poly()).unwrap(); + + DecryptedSharesAggregationCircuitInput { + committee, + d_share_polys, + reconstructing_parties, + message_vec, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + computation::Computation, + threshold::decrypted_shares_aggregation::{ + DecryptedSharesAggregationCircuitInput, Witness, + }, + CiphernodesCommitteeSize, + }; + use e3_fhe_params::BfvPreset; + use num_bigint::BigInt; + + /// Sample generation and witness computation: output shapes match circuit expectations. + #[test] + fn test_generate_sample() { + let preset = BfvPreset::InsecureThreshold512; + let committee = CiphernodesCommitteeSize::Small.values(); + + let sample = DecryptedSharesAggregationCircuitInput::generate_sample(preset, committee); + let witness = Witness::compute(preset, &sample).unwrap(); + + assert_eq!( + witness.decryption_shares.len(), + sample.committee.threshold + 1 + ); + assert_eq!(witness.party_ids.len(), sample.reconstructing_parties.len()); + let configs = + crate::threshold::decrypted_shares_aggregation::computation::Configs::compute( + preset, + &(), + ) + .unwrap(); + assert_eq!(witness.message.len(), configs.max_msg_non_zero_coeffs); + } + + /// Witness message matches sample (ascending order: index 0 = constant term). + #[test] + fn test_witness_message_matches_sample() { + use crate::threshold::decrypted_shares_aggregation::computation::Configs; + let preset = BfvPreset::InsecureThreshold512; + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = DecryptedSharesAggregationCircuitInput::generate_sample(preset, committee); + let witness = Witness::compute(preset, &sample).unwrap(); + let configs = Configs::compute(preset, &()).unwrap(); + let n = configs.max_msg_non_zero_coeffs; + for i in 0..n { + let expected = sample.message_vec.get(i).copied().unwrap_or(0); + let w = &witness.message[i]; + let exp = BigInt::from(expected); + assert_eq!(w, &exp, "message coeff {} mismatch", i); + } + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs new file mode 100644 index 0000000000..12dfee99e6 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs @@ -0,0 +1,122 @@ +// 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. + +//! Utilities for Decryption Share Aggregation TRBFV circuit. +//! +//! **Generic BFV math** lives in [`crate::math`] and is re-exported here for convenience. +//! **This module** adds only Shamir + scalar CRT helpers: [`lagrange_recover_at_zero`] and +//! [`crt_reconstruct`]. Coefficient reduction uses [`e3_polynomial::reduce`] in +//! [`super::computation::Witness::standard_form`]. + +use crate::math; +use crate::CircuitsErrors; +use num_bigint::{BigInt, BigUint}; +use num_traits::{ToPrimitive, Zero}; + +// Re-export so callers can use decrypted_shares_aggregation::utils for one-stop. +pub use math::{ + compute_delta, compute_delta_half, compute_q_inverse_mod_t, compute_q_mod_t, compute_q_product, + compute_t_inv_mod_q, mod_inverse_bigint, +}; + +/// Lagrange interpolation at 0: given shares (party_id, value) mod modulus, returns the recovered secret. +/// Party IDs are 1-based (1, 2, ..., T+1). Formula: f(0) = sum_i y_i * L_i(0) with +/// L_i(0) = prod_{j != i} (0 - x_j) / (x_i - x_j) mod modulus. Used only by this circuit (Shamir recovery). +pub fn lagrange_recover_at_zero( + party_ids: &[usize], + shares: &[BigInt], + modulus: u64, +) -> Result { + let m = BigInt::from(modulus); + let mut secret = BigInt::zero(); + for (i, &x_i) in party_ids.iter().enumerate() { + let y_i = &shares[i] % &m; + let y_i_pos = if y_i < BigInt::zero() { + &y_i + &m + } else { + y_i.clone() + }; + let mut lambda_i = BigInt::from(1); + for (j, &x_j) in party_ids.iter().enumerate() { + if i != j { + let x_i_b = BigInt::from(x_i); + let x_j_b = BigInt::from(x_j); + let num = BigInt::from(0) - &x_j_b; + let den = &x_i_b - &x_j_b; + let den_inv = crate::math::mod_inverse_bigint(&den, &m) + .ok_or_else(|| CircuitsErrors::Other("lagrange: den not invertible".into()))?; + lambda_i = (&lambda_i * &num % &m * &den_inv % &m + &m) % &m; + } + } + secret = (&secret + &y_i_pos * &lambda_i % &m + &m) % &m; + } + let secret_pos = if secret < BigInt::zero() { + &secret + &m + } else { + secret + }; + secret_pos + .to_u64() + .ok_or_else(|| CircuitsErrors::Other("lagrange_recover: result too large for u64".into())) +} + +/// CRT reconstruction: given residues[i] in [0, moduli[i]), returns the unique value in [0, Q). +/// Used here for per-coefficient u_global from u_per_modulus; reusable for any scalar CRT. +pub fn crt_reconstruct(residues: &[u64], moduli: &[u64]) -> Result { + if residues.len() != moduli.len() { + return Err(CircuitsErrors::Other(format!( + "crt_reconstruct: residues.len() {} != moduli.len() {}", + residues.len(), + moduli.len() + ))); + } + let q: BigUint = crate::math::compute_q_product(moduli); + let mut result = BigUint::zero(); + for (i, &r_i) in residues.iter().enumerate() { + let m_i = BigUint::from(moduli[i]); + let m_i_bigint = BigInt::from(m_i.clone()); + let q_i = &q / &m_i; + let q_i_bigint = BigInt::from(q_i.clone()); + let inv = crate::math::mod_inverse_bigint(&q_i_bigint, &m_i_bigint).ok_or_else(|| { + CircuitsErrors::Other("crt_reconstruct: q_i not invertible mod m_i".into()) + })?; + let c_i = (BigInt::from(r_i) * &inv) % &m_i_bigint; + let c_i_pos = if c_i < BigInt::zero() { + &c_i + &m_i_bigint + } else { + c_i + }; + let c_i_u = c_i_pos + .to_biguint() + .ok_or_else(|| CircuitsErrors::Other("crt_reconstruct: c_i_pos negative".into()))?; + result = (result + c_i_u * q_i) % &q; + } + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_crt_reconstruct_small() { + let moduli = [3u64, 5, 7]; + let residues = [2u64, 4, 3]; // x ≡ 2 mod 3, ≡ 4 mod 5, ≡ 3 mod 7 + let x = crt_reconstruct(&residues, &moduli).unwrap(); + assert_eq!(x, BigUint::from(59u64)); // 59 mod 3=2, mod 5=4, mod 7=3 + } + + #[test] + fn test_lagrange_recover_at_zero_two_points() { + let party_ids = [1usize, 2]; + let shares = [BigInt::from(10i64), BigInt::from(20i64)]; + let modulus = 7u64; + let secret = lagrange_recover_at_zero(&party_ids, &shares, modulus).unwrap(); + // f(0) = f(1)*L_1(0) + f(2)*L_2(0); L_1(0) = (0-2)/(1-2) = 2, L_2(0) = (0-1)/(2-1) = -1 mod 7 = 6 + // f(0) = 10*2 + 20*6 = 20 + 120 = 140 ≡ 0 mod 7 (for linear f(x)=10x: f(1)=10, f(2)=20 -> f(0)=0) + assert_eq!(secret, 0); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/mod.rs b/crates/zk-helpers/src/circuits/threshold/mod.rs index 0ebb26a830..ec39cf6915 100644 --- a/crates/zk-helpers/src/circuits/threshold/mod.rs +++ b/crates/zk-helpers/src/circuits/threshold/mod.rs @@ -4,6 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +pub mod decrypted_shares_aggregation; pub mod pk_aggregation; pub mod pk_generation; pub mod share_decryption; diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index 81608583cd..0cd4703f8b 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -13,7 +13,7 @@ use crate::calculate_bit_width; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; -use crate::ring::{cyclotomic_polynomial, decompose_residue}; +use crate::math::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::pk_generation::circuit::PkGenerationCircuit; use crate::threshold::pk_generation::circuit::PkGenerationCircuitInput; use crate::CiphernodesCommittee; diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs index cd65fb574e..827e6ac0d2 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs @@ -12,11 +12,11 @@ use crate::calculate_bit_width; use crate::commitments::compute_pk_aggregation_commitment; use crate::compute_ciphertext_commitment; -use crate::crt::compute_k0is; +use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; -use crate::ring::{cyclotomic_polynomial, decompose_residue}; +use crate::math::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use crate::utils::compute_modulus_bit; @@ -144,12 +144,13 @@ impl Computation for Configs { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let moduli = threshold_params.moduli().to_vec(); - let ctx = threshold_params.ctx_at_level(0)?; - let modulus = BigInt::from(ctx.modulus().clone()); - let t = BigInt::from(threshold_params.plaintext()); + let plaintext = threshold_params.plaintext(); + let q = compute_q_product(&moduli); + let q_mod_t_uint = compute_q_mod_t(&q, plaintext); + let t = BigInt::from(plaintext); let p = get_zkp_modulus(); - let q_mod_t = center(&reduce(&modulus, &t), &t); + let q_mod_t = center(&BigInt::from(q_mod_t_uint), &t); let q_mod_t_mod_p = reduce(&q_mod_t, &p); let k0is = compute_k0is(threshold_params.moduli(), threshold_params.plaintext())?; diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs index 78d8c861fa..c634898c9b 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs @@ -4,8 +4,8 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use crate::crt::fhe_poly_to_crt_centered; -use crate::utils::{compute_modulus_bit, get_zkp_modulus, ZkHelpersUtilsError}; +use crate::math::fhe_poly_to_crt_centered; +use crate::utils::{compute_pk_bit, get_zkp_modulus, ZkHelpersUtilsError}; use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; use fhe::bfv::{BfvParameters, Ciphertext, PublicKey}; diff --git a/crates/zk-helpers/src/crt.rs b/crates/zk-helpers/src/crt.rs deleted file mode 100644 index c9e264d772..0000000000 --- a/crates/zk-helpers/src/crt.rs +++ /dev/null @@ -1,48 +0,0 @@ -// 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) operations for ZK circuit computations. -//! -//! Used by share_encryption, user_data_encryption, pk, and other circuits that work with -//! polynomials in CRT form or need to convert FHE types to CRT limbs. - -use crate::CircuitsErrors; -use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; -use fhe_math::rq::Poly; -use fhe_math::zq::Modulus; - -/// Computes k0_i = (-t)^{-1} mod q_i for each modulus (used in Configs and bounds). -/// -/// Same logic as share_encryption and user_data_encryption when building k0is. -pub fn compute_k0is(moduli: &[u64], plaintext_modulus: u64) -> Result, CircuitsErrors> { - let mut k0is = Vec::with_capacity(moduli.len()); - for &qi in moduli { - let m = Modulus::new(qi).map_err(|e| { - CircuitsErrors::Sample(format!("Failed to create modulus for k0is: {:?}", e)) - })?; - let k0qi = m.inv(m.neg(plaintext_modulus)).ok_or_else(|| { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - })?; - k0is.push(k0qi); - } - Ok(k0is) -} - -/// Converts an FHE polynomial to CRT form with reverse + center (no ZKP reduce). -/// -/// Same pattern used by share_encryption, user_data_encryption, and pk circuits -/// when building circuit input from FHE types. -pub fn fhe_poly_to_crt_centered( - poly: &Poly, - moduli: &[u64], -) -> Result { - let mut crt = CrtPolynomial::from_fhe_polynomial(poly); - crt.reverse(); - crt.center(moduli)?; - Ok(crt) -} diff --git a/crates/zk-helpers/src/lib.rs b/crates/zk-helpers/src/lib.rs index dd1d9e7a3c..d66dd1fc5c 100644 --- a/crates/zk-helpers/src/lib.rs +++ b/crates/zk-helpers/src/lib.rs @@ -6,16 +6,14 @@ pub mod ciphernodes_committee; pub mod circuits; -pub mod crt; +pub mod math; pub mod packing; pub mod registry; -pub mod ring; pub mod utils; pub use ciphernodes_committee::*; pub use circuits::*; -pub use crt::*; +pub use math::*; pub use packing::*; pub use registry::*; -pub use ring::*; pub use utils::*; diff --git a/crates/zk-helpers/src/math.rs b/crates/zk-helpers/src/math.rs new file mode 100644 index 0000000000..31aa0f2471 --- /dev/null +++ b/crates/zk-helpers/src/math.rs @@ -0,0 +1,210 @@ +// 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. + +//! Unified math helpers for ZK circuit computations: BFV/TRBFV parameters (Q, delta, inverses), +//! CRT operations (k0is, FHE poly to CRT), and polynomial ring (cyclotomic, residue decomposition). + +use crate::CircuitsErrors; +use e3_polynomial::{CrtPolynomial, CrtPolynomialError, Polynomial}; +use fhe_math::rq::Poly; +use fhe_math::zq::Modulus; +use num_bigint::{BigInt, BigUint}; +use num_integer::Integer; +use num_traits::{ToPrimitive, Zero}; + +// ---------- BFV / TRBFV parameter math ---------- + +/// Product Q = q_0 * q_1 * ... * q_{L-1} of CRT moduli. +pub fn compute_q_product(moduli: &[u64]) -> BigUint { + let mut q = BigUint::from(1u64); + for &m in moduli { + q *= BigUint::from(m); + } + q +} + +/// Delta = floor(Q / t) for plaintext modulus t. +pub fn compute_delta(q: &BigUint, t: u64) -> BigUint { + q / BigUint::from(t) +} + +/// Delta_half = floor(delta / 2). +pub fn compute_delta_half(delta: &BigUint) -> BigUint { + delta / BigUint::from(2u64) +} + +/// Q^{-1} mod t (for BFV decoding). Fails if gcd(Q, t) != 1. +pub fn compute_q_inverse_mod_t(q: &BigUint, t: u64) -> Result { + let q_bigint = BigInt::from(q.clone()); + let t_bigint = BigInt::from(t); + let gcd_result = q_bigint.extended_gcd(&t_bigint); + if gcd_result.gcd != BigInt::from(1) { + return Err(CircuitsErrors::Other(format!( + "Q and t are not coprime, gcd = {}", + gcd_result.gcd + ))); + } + let inv = gcd_result.x % &t_bigint; + let inv_positive = if inv < BigInt::from(0) { + inv + &t_bigint + } else { + inv + }; + inv_positive.to_u64().ok_or_else(|| { + CircuitsErrors::Other(format!( + "q_inverse_mod_t too large to fit in u64: {}", + inv_positive + )) + }) +} + +/// Q mod t. +pub fn compute_q_mod_t(q: &BigUint, t: u64) -> BigUint { + q % BigUint::from(t) +} + +/// t^{-1} mod Q (for CRT / scaling). Fails if gcd(Q, t) != 1. +pub fn compute_t_inv_mod_q(q: &BigUint, t: u64) -> Result { + let q_bigint = BigInt::from(q.clone()); + let t_bigint = BigInt::from(t); + let gcd_result = q_bigint.extended_gcd(&t_bigint); + if gcd_result.gcd != BigInt::from(1) { + return Err(CircuitsErrors::Other(format!( + "Q and t are not coprime (t_inv_mod_q), gcd = {}", + gcd_result.gcd + ))); + } + let y = gcd_result.y; + let t_inv_bigint = if y < BigInt::from(0) { + y + &q_bigint + } else { + y + }; + t_inv_bigint + .to_biguint() + .ok_or_else(|| CircuitsErrors::Other("Failed to convert t_inv_mod_q to BigUint".into())) +} + +/// Modular inverse a^{-1} mod m; None if gcd(a, m) != 1. +pub fn mod_inverse_bigint(a: &BigInt, m: &BigInt) -> Option { + let g = a.extended_gcd(m); + if g.gcd != BigInt::from(1) { + return None; + } + let inv = g.x % m; + Some(if inv < BigInt::zero() { inv + m } else { inv }) +} + +// ---------- CRT (k0is, FHE poly to CRT) ---------- + +/// Computes k0_i = (-t)^{-1} mod q_i for each modulus (used in Configs and bounds). +pub fn compute_k0is(moduli: &[u64], plaintext_modulus: u64) -> Result, CircuitsErrors> { + let mut k0is = Vec::with_capacity(moduli.len()); + for &qi in moduli { + let m = Modulus::new(qi).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create modulus for k0is: {:?}", e)) + })?; + let k0qi = m.inv(m.neg(plaintext_modulus)).ok_or_else(|| { + CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( + "Failed to calculate modulus inverse for k0qi".into(), + ))) + })?; + k0is.push(k0qi); + } + Ok(k0is) +} + +/// Converts an FHE polynomial to CRT form with reverse + center (no ZKP reduce). +pub fn fhe_poly_to_crt_centered( + poly: &Poly, + moduli: &[u64], +) -> Result { + let mut crt = CrtPolynomial::from_fhe_polynomial(poly); + crt.reverse(); + crt.center(moduli)?; + Ok(crt) +} + +// ---------- Polynomial ring (cyclotomic, residue decomposition) ---------- + +/// Returns the coefficient vector for the cyclotomic polynomial x^N + 1 (degree N). +#[must_use] +pub fn cyclotomic_polynomial(n: u64) -> Vec { + let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; + cyclo[0] = BigInt::from(1u64); + cyclo[n as usize] = BigInt::from(1u64); + cyclo +} + +/// Decomposes the residue `xi - xi_hat` into `r1 * qi + r2 * cyclo` mod R_qi. +pub fn decompose_residue( + xi: &Polynomial, + xi_hat: &Polynomial, + qi_bigint: &BigInt, + cyclo: &[BigInt], + n: u64, +) -> (Polynomial, Polynomial) { + let cyclo_poly = Polynomial::new(cyclo.to_vec()); + let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); + + let mut xi_hat_mod_rqi = xi_hat.clone(); + xi_hat_mod_rqi = xi_hat_mod_rqi.reduce_by_cyclotomic(cyclo).unwrap(); + xi_hat_mod_rqi.reduce(qi_bigint); + xi_hat_mod_rqi.center(qi_bigint); + assert_eq!(xi, &xi_hat_mod_rqi); + + let num_coeffs = xi.sub(xi_hat).coefficients().to_vec(); + assert_eq!((num_coeffs.len() as u64) - 1, 2 * (n - 1)); + + let mut num_mod_zqi = Polynomial::new(num_coeffs.clone()); + num_mod_zqi.reduce(qi_bigint); + num_mod_zqi.center(qi_bigint); + + let (r2_poly, r2_rem_poly) = num_mod_zqi.clone().div(&cyclo_poly).unwrap(); + assert!(r2_rem_poly.coefficients().iter().all(|c| c.is_zero())); + assert_eq!((r2_poly.coefficients().len() as u64) - 1, n - 2); + + let r2_times_cyclo = r2_poly.mul(&cyclo_poly); + let mut r2_times_cyclo_mod = r2_times_cyclo.clone(); + r2_times_cyclo_mod.reduce(qi_bigint); + r2_times_cyclo_mod.center(qi_bigint); + assert_eq!(&num_mod_zqi, &r2_times_cyclo_mod); + assert_eq!( + (r2_times_cyclo.coefficients().len() as u64) - 1, + 2 * (n - 1) + ); + + let num_poly = Polynomial::new(num_coeffs); + let r1_num = num_poly.sub(&r2_times_cyclo); + assert_eq!((r1_num.coefficients().len() as u64) - 1, 2 * (n - 1)); + + let (r1_poly, r1_rem_poly) = r1_num.div(&qi_poly).unwrap(); + assert!(r1_rem_poly.coefficients().iter().all(|c| c.is_zero())); + assert_eq!((r1_poly.coefficients().len() as u64) - 1, 2 * (n - 1)); + assert_eq!(&r1_num, &r1_poly.mul(&qi_poly)); + + let r1_times_qi = r1_poly.clone().scalar_mul(qi_bigint); + let xi_calculated = xi_hat + .clone() + .add(&r1_times_qi) + .add(&r2_times_cyclo) + .trim_leading_zeros(); + assert_eq!(xi, &xi_calculated); + + (r1_poly, r2_poly) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compute_q_product() { + let moduli = [3u64, 5, 7]; + let q = compute_q_product(&moduli); + assert_eq!(q, BigUint::from(105u64)); + } +} diff --git a/crates/zk-helpers/src/ring.rs b/crates/zk-helpers/src/ring.rs deleted file mode 100644 index 396461f0b6..0000000000 --- a/crates/zk-helpers/src/ring.rs +++ /dev/null @@ -1,88 +0,0 @@ -// 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. - -//! Polynomial ring helpers for ZK circuit computations (R_qi = Z_q_i[x] / (x^N + 1)). -//! -//! Cyclotomic polynomial and residue decomposition used by pk_generation, share_encryption, -//! and user_data_encryption when reducing by R_qi and decomposing residues. - -use e3_polynomial::Polynomial; -use num_bigint::BigInt; -use num_traits::Zero; - -/// Returns the coefficient vector for the cyclotomic polynomial x^N + 1 (degree N). -/// -/// Used by share_encryption, user_data_encryption, and pk_generation when reducing by R_qi. -#[must_use] -pub fn cyclotomic_polynomial(n: u64) -> Vec { - let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; - cyclo[0] = BigInt::from(1u64); - cyclo[n as usize] = BigInt::from(1u64); - cyclo -} - -/// Decomposes the residue `xi - xi_hat` into `r1 * qi + r2 * cyclo` mod R_qi. -/// -/// Verifies that `xi == xi_hat (mod R_qi)`, then computes quotient polynomials -/// such that `xi - xi_hat = r1 * qi + r2 * cyclo` (with cyclo = x^N + 1). -/// Returns `(r1, r2)` as polynomials. Panics on assertion failures (exact division, -/// degree checks, reconstruction). -pub fn decompose_residue( - xi: &Polynomial, - xi_hat: &Polynomial, - qi_bigint: &BigInt, - cyclo: &[BigInt], - n: u64, -) -> (Polynomial, Polynomial) { - let cyclo_poly = Polynomial::new(cyclo.to_vec()); - let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); - - let mut xi_hat_mod_rqi = xi_hat.clone(); - xi_hat_mod_rqi = xi_hat_mod_rqi.reduce_by_cyclotomic(cyclo).unwrap(); - xi_hat_mod_rqi.reduce(qi_bigint); - xi_hat_mod_rqi.center(qi_bigint); - assert_eq!(xi, &xi_hat_mod_rqi); - - let num_coeffs = xi.sub(xi_hat).coefficients().to_vec(); - assert_eq!((num_coeffs.len() as u64) - 1, 2 * (n - 1)); - - let mut num_mod_zqi = Polynomial::new(num_coeffs.clone()); - num_mod_zqi.reduce(qi_bigint); - num_mod_zqi.center(qi_bigint); - - let (r2_poly, r2_rem_poly) = num_mod_zqi.clone().div(&cyclo_poly).unwrap(); - assert!(r2_rem_poly.coefficients().iter().all(|c| c.is_zero())); - assert_eq!((r2_poly.coefficients().len() as u64) - 1, n - 2); - - let r2_times_cyclo = r2_poly.mul(&cyclo_poly); - let mut r2_times_cyclo_mod = r2_times_cyclo.clone(); - r2_times_cyclo_mod.reduce(qi_bigint); - r2_times_cyclo_mod.center(qi_bigint); - assert_eq!(&num_mod_zqi, &r2_times_cyclo_mod); - assert_eq!( - (r2_times_cyclo.coefficients().len() as u64) - 1, - 2 * (n - 1) - ); - - let num_poly = Polynomial::new(num_coeffs); - let r1_num = num_poly.sub(&r2_times_cyclo); - assert_eq!((r1_num.coefficients().len() as u64) - 1, 2 * (n - 1)); - - let (r1_poly, r1_rem_poly) = r1_num.div(&qi_poly).unwrap(); - assert!(r1_rem_poly.coefficients().iter().all(|c| c.is_zero())); - assert_eq!((r1_poly.coefficients().len() as u64) - 1, 2 * (n - 1)); - assert_eq!(&r1_num, &r1_poly.mul(&qi_poly)); - - let r1_times_qi = r1_poly.clone().scalar_mul(qi_bigint); - let xi_calculated = xi_hat - .clone() - .add(&r1_times_qi) - .add(&r2_times_cyclo) - .trim_leading_zeros(); - assert_eq!(xi, &xi_calculated); - - (r1_poly, r2_poly) -} diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 368788038b..8cc30f6f61 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2534,6 +2534,7 @@ dependencies = [ "itertools 0.14.0", "ndarray", "num-bigint", + "num-integer", "num-traits", "rand 0.8.5", "rayon", diff --git a/templates/default/Cargo.lock b/templates/default/Cargo.lock index 480bc9a824..f8d5e13518 100644 --- a/templates/default/Cargo.lock +++ b/templates/default/Cargo.lock @@ -1347,6 +1347,7 @@ dependencies = [ "itertools 0.14.0", "ndarray", "num-bigint", + "num-integer", "num-traits", "rand 0.8.5", "rayon", From 3c42d7e403899b73570f53b14d51b0dd0faab81f Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 21:31:43 +0100 Subject: [PATCH 06/10] format --- .../circuits/dkg/share_encryption/computation.rs | 4 ++-- .../decrypted_shares_aggregation/computation.rs | 14 +++++++++++--- .../threshold/pk_generation/computation.rs | 2 +- .../threshold/user_data_encryption/computation.rs | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index b3a803c98d..84a540b4b2 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -15,12 +15,12 @@ use crate::circuits::commitments::{ }; use std::ops::Deref; -use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::dkg::share_encryption::ShareEncryptionCircuit; use crate::dkg::share_encryption::ShareEncryptionCircuitInput; use crate::get_zkp_modulus; -use crate::polynomial_to_toml_json; +use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::math::{cyclotomic_polynomial, decompose_residue}; +use crate::polynomial_to_toml_json; use crate::utils::{compute_msg_bit, compute_pk_bit}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs index c164c3ff48..107741cd4e 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -18,8 +18,8 @@ use crate::threshold::decrypted_shares_aggregation::utils; use crate::CircuitsErrors; use crate::{CircuitComputation, Computation}; use e3_fhe_params::build_pair_for_preset; -use e3_polynomial::reduce; use e3_fhe_params::BfvPreset; +use e3_polynomial::reduce; use fhe_math::rq::Representation; use num_bigint::{BigInt, BigUint}; use num_traits::Zero; @@ -314,8 +314,16 @@ impl Witness { .iter() .map(|c| reduce(c, &zkp_modulus)) .collect(), - message: self.message.iter().map(|c| reduce(c, &zkp_modulus)).collect(), - u_global: self.u_global.iter().map(|c| reduce(c, &zkp_modulus)).collect(), + message: self + .message + .iter() + .map(|c| reduce(c, &zkp_modulus)) + .collect(), + u_global: self + .u_global + .iter() + .map(|c| reduce(c, &zkp_modulus)) + .collect(), crt_quotients: self .crt_quotients .iter() diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index 0cd4703f8b..ed5356cdbf 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -12,8 +12,8 @@ use crate::calculate_bit_width; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; -use crate::polynomial_to_toml_json; use crate::math::{cyclotomic_polynomial, decompose_residue}; +use crate::polynomial_to_toml_json; use crate::threshold::pk_generation::circuit::PkGenerationCircuit; use crate::threshold::pk_generation::circuit::PkGenerationCircuitInput; use crate::CiphernodesCommittee; diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs index 827e6ac0d2..8c22e94d1e 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs @@ -12,11 +12,11 @@ use crate::calculate_bit_width; use crate::commitments::compute_pk_aggregation_commitment; use crate::compute_ciphertext_commitment; -use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; -use crate::polynomial_to_toml_json; +use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::math::{cyclotomic_polynomial, decompose_residue}; +use crate::polynomial_to_toml_json; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use crate::utils::compute_modulus_bit; From 4c28403ac6b00698174ef2090add42395f50ff3f Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Feb 2026 11:24:59 +0100 Subject: [PATCH 07/10] minor coderabbit fixes --- Cargo.toml | 2 +- crates/zk-helpers/src/bin/zk_cli.rs | 2 +- .../src/circuits/dkg/share_encryption/sample.rs | 4 ++-- .../decrypted_shares_aggregation/computation.rs | 1 - .../decrypted_shares_aggregation/utils.rs | 7 +++++++ crates/zk-helpers/src/utils.rs | 17 +++++++++++++++++ 6 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38ad27f0cb..cc63263516 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,7 +153,7 @@ lazy_static = "=1.5.0" num = "=0.4.3" num-bigint = { version = "=0.4.6" } num-traits = "=0.2.19" -num-integer = "0.1" +num-integer = "0.1.46" ndarray = { version = "=0.15.6", features = ["serde"] } once_cell = "=1.21.3" opentelemetry = "=0.29.0" diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index d0097adf36..d9bd221cff 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -220,7 +220,7 @@ fn main() -> Result<()> { } let write_prover_toml = args.toml; - // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. + // DKG circuits have a witness-type choice (secret-key vs smudging-noise) excluding `pk` or C0 circuit. let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME || circuit_meta.name() == ShareEncryptionCircuit::NAME || circuit_meta.name() == DkgShareDecryptionCircuit::NAME; diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs index a83883629f..fd30696b9b 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -137,8 +137,8 @@ mod tests { let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - committee.clone(), - DkgInputType::SecretKey, + CiphernodesCommitteeSize::Small, + DkgInputType::SmudgingNoise, sd.z, sd.lambda, ); diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs index 107741cd4e..8357f1a23d 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -410,7 +410,6 @@ mod tests { assert_eq!(out.witness.party_ids.len(), committee.threshold + 1); assert_eq!(out.witness.message.len(), configs.max_msg_non_zero_coeffs); assert_eq!(out.witness.u_global.len(), configs.max_msg_non_zero_coeffs); - assert_eq!(out.bounds.delta, out.bounds.delta); // sanity assert!(out.bits.noise_bit > 0); } } diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs index 12dfee99e6..0242fd4df7 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/utils.rs @@ -30,6 +30,13 @@ pub fn lagrange_recover_at_zero( shares: &[BigInt], modulus: u64, ) -> Result { + if party_ids.len() != shares.len() { + return Err(CircuitsErrors::Other(format!( + "lagrange_recover_at_zero: party_ids.len() {} != shares.len() {}", + party_ids.len(), + shares.len() + ))); + } let m = BigInt::from(modulus); let mut secret = BigInt::zero(); for (i, &x_i) in party_ids.iter().enumerate() { diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index 3d38add9cf..7a16408bbd 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -264,6 +264,23 @@ pub fn bigint_3d_to_json_values(y: &[Vec>]) -> Vec]) -> Vec> { + y.iter() + .map(|coeff| { + coeff + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect() + }) + .collect() +} /// Convert a 3D vector of BigInt to a vector of vectors of vectors of JSON values. /// /// # Arguments From f9b1bc0d16722cce261c89920c58f885fa186904 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Feb 2026 11:42:25 +0100 Subject: [PATCH 08/10] remove duplicated utils --- crates/zk-helpers/src/utils.rs | 44 ++++------------------------------ 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index 7a16408bbd..89d430a626 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -258,45 +258,11 @@ pub fn bigint_3d_to_json_values(y: &[Vec>]) -> Vec]) -> Vec> { - y.iter() - .map(|coeff| { - coeff - .iter() - .map(|v| serde_json::Value::String(v.to_string())) - .collect() - }) - .collect() -} -/// Convert a 3D vector of BigInt to a vector of vectors of vectors of JSON values. -/// -/// # Arguments -/// * `bigint_3d` - 3D vector of BigInt values -/// -/// # Returns -/// A vector of vectors of vectors of JSON values -pub fn bigint_3d_to_json_values( - bigint_3d: &[Vec>], -) -> Vec>> { - bigint_3d - .iter() - .map(|coeff| { - coeff - .iter() - .map(|modulus| bigint_1d_to_json_values(modulus)) + .map(|v| { + v.iter() + .map(|x| serde_json::Value::String(x.to_string())) + .collect() + }) .collect() }) .collect() From 46c9b1d6bb4620949d9d90887d976f45becf5f15 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Feb 2026 11:45:51 +0100 Subject: [PATCH 09/10] add missing guard on witness computation --- .../decrypted_shares_aggregation/computation.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs index 8357f1a23d..d5f68eb87b 100644 --- a/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/decrypted_shares_aggregation/computation.rs @@ -180,6 +180,15 @@ impl Computation for Witness { }) .collect(); + if d_share_polys.len() < threshold + 1 { + return Err(CircuitsErrors::Other(format!( + "d_share_polys.len() {} < threshold + 1 ({}); need at least {} polynomials", + d_share_polys.len(), + threshold + 1, + threshold + 1 + ))); + } + // 1. Extract decryption shares per modulus per party [party][modulus][coeff] let mut decryption_shares = Vec::with_capacity(d_share_polys.len()); for d_share in &d_share_polys { From 530c944f65ed0a3721c4a01a35d90d33328f4182 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 10 Feb 2026 12:52:31 +0100 Subject: [PATCH 10/10] conflicts --- crates/zk-helpers/src/bin/zk_cli.rs | 2 +- .../zk-helpers/src/circuits/dkg/share_encryption/computation.rs | 2 +- crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs | 2 +- .../src/circuits/threshold/user_data_encryption/utils.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index d9bd221cff..774dacc15c 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -280,7 +280,7 @@ fn main() -> Result<()> { let sd = preset.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( preset, - CiphernodesCommitteeSize::Small, + committee, dkg_input_type, sd.z, sd.lambda, diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index 84a540b4b2..074d4c70e8 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -21,7 +21,7 @@ use crate::get_zkp_modulus; use crate::math::{compute_k0is, compute_q_mod_t, compute_q_product}; use crate::math::{cyclotomic_polynomial, decompose_residue}; use crate::polynomial_to_toml_json; -use crate::utils::{compute_msg_bit, compute_pk_bit}; +use crate::utils::{compute_modulus_bit, compute_msg_bit}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; use crate::{CircuitComputation, Computation}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs index fd30696b9b..d150c0515d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -137,7 +137,7 @@ mod tests { let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SmudgingNoise, sd.z, sd.lambda, diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs index c634898c9b..d289074aad 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs @@ -5,7 +5,7 @@ // or FITNESS FOR A PARTICULAR PURPOSE. use crate::math::fhe_poly_to_crt_centered; -use crate::utils::{compute_pk_bit, get_zkp_modulus, ZkHelpersUtilsError}; +use crate::utils::{compute_modulus_bit, get_zkp_modulus, ZkHelpersUtilsError}; use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; use fhe::bfv::{BfvParameters, Ciphertext, PublicKey};