From f4bc6e0793f84f2ea2ebc7fc36a747dfa1fff3d8 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 9 Feb 2026 18:01:16 +0100 Subject: [PATCH 01/14] refactor: add share_decryption circuit --- .../threshold/share_decryption/src/main.nr | 18 +- .../lib/src/configs/insecure/threshold.nr | 17 +- circuits/lib/src/configs/secure/threshold.nr | 17 +- .../src/core/threshold/share_decryption.nr | 48 +- crates/zk-helpers/src/bin/zk_cli.rs | 12 + .../zk-helpers/src/circuits/threshold/mod.rs | 1 + .../threshold/share_decryption/circuit.rs | 29 ++ .../threshold/share_decryption/codegen.rs | 194 ++++++++ .../threshold/share_decryption/computation.rs | 447 ++++++++++++++++++ .../threshold/share_decryption/mod.rs | 19 + .../threshold/share_decryption/sample.rs | 319 +++++++++++++ 11 files changed, 1068 insertions(+), 53 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs create mode 100644 crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs diff --git a/circuits/bin/threshold/share_decryption/src/main.nr b/circuits/bin/threshold/share_decryption/src/main.nr index c2285bed5c..eaddad3878 100644 --- a/circuits/bin/threshold/share_decryption/src/main.nr +++ b/circuits/bin/threshold/share_decryption/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use lib::configs::default::threshold::{ +use lib::configs::secure::threshold::{ L, N, SHARE_DECRYPTION_BIT_CT, SHARE_DECRYPTION_BIT_D, SHARE_DECRYPTION_BIT_E_SM, SHARE_DECRYPTION_BIT_R1, SHARE_DECRYPTION_BIT_R2, SHARE_DECRYPTION_BIT_SK, SHARE_DECRYPTION_CONFIGS, @@ -15,24 +15,24 @@ use lib::math::polynomial::Polynomial; fn main( expected_sk_commitment: pub Field, expected_e_sm_commitment: pub Field, - c_0: pub [Polynomial; L], - c_1: pub [Polynomial; L], + ct0: pub [Polynomial; L], + ct1: pub [Polynomial; L], sk: [Polynomial; L], e_sm: [Polynomial; L], - r_1: [Polynomial<(2 * N) - 1>; L], - r_2: [Polynomial; L], + r1: [Polynomial<(2 * N) - 1>; L], + r2: [Polynomial; L], d: [Polynomial; L], ) { let share_decryption: ShareDecryption = ShareDecryption::new( SHARE_DECRYPTION_CONFIGS, expected_sk_commitment, expected_e_sm_commitment, - c_0, - c_1, + ct0, + ct1, sk, e_sm, - r_1, - r_2, + r1, + r2, d, ); share_decryption.execute() diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index cc21b0c727..4b6b92c4a5 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -108,23 +108,20 @@ pub global USER_DATA_ENCRYPTION_CONFIGS: UserDataEncryptionConfigs = UserD /************************************ ------------------------------------- -share_decryption (GRECO) +share_decryption ------------------------------------- ************************************/ -// share_decryption - bit parameters -pub global SHARE_DECRYPTION_BIT_CT: u32 = 36; -pub global SHARE_DECRYPTION_BIT_SK: u32 = 36; -pub global SHARE_DECRYPTION_BIT_E_SM: u32 = 36; -pub global SHARE_DECRYPTION_BIT_R1: u32 = 44; -pub global SHARE_DECRYPTION_BIT_R2: u32 = 36; -pub global SHARE_DECRYPTION_BIT_D: u32 = 36; +pub global SHARE_DECRYPTION_BIT_CT: u32 = 35; +pub global SHARE_DECRYPTION_BIT_SK: u32 = 35; +pub global SHARE_DECRYPTION_BIT_E_SM: u32 = 35; +pub global SHARE_DECRYPTION_BIT_R1: u32 = 43; +pub global SHARE_DECRYPTION_BIT_R2: u32 = 35; +pub global SHARE_DECRYPTION_BIT_D: u32 = 35; -// share_decryption - bounds pub global SHARE_DECRYPTION_R1_BOUNDS: [Field; L] = [8796083584897, 8796061564801]; pub global SHARE_DECRYPTION_R2_BOUNDS: [Field; L] = [34359701504, 34359615488]; -// share_decryption - configs pub global SHARE_DECRYPTION_CONFIGS: ShareDecryptionConfigs = ShareDecryptionConfigs::new(QIS, SHARE_DECRYPTION_R1_BOUNDS, SHARE_DECRYPTION_R2_BOUNDS); diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index cf42116d5e..6c9b499e14 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -118,25 +118,22 @@ pub global USER_DATA_ENCRYPTION_CONFIGS: UserDataEncryptionConfigs = UserD /************************************ ------------------------------------- -share_decryption (CIRCUIT 6) +share_decryption ------------------------------------- ************************************/ -// share_decryption - bit parameters -pub global SHARE_DECRYPTION_BIT_CT: u32 = 53; -pub global SHARE_DECRYPTION_BIT_SK: u32 = 53; -pub global SHARE_DECRYPTION_BIT_E_SM: u32 = 53; -pub global SHARE_DECRYPTION_BIT_R1: u32 = 65; -pub global SHARE_DECRYPTION_BIT_R2: u32 = 53; -pub global SHARE_DECRYPTION_BIT_D: u32 = 53; +pub global SHARE_DECRYPTION_BIT_CT: u32 = 52; +pub global SHARE_DECRYPTION_BIT_SK: u32 = 52; +pub global SHARE_DECRYPTION_BIT_E_SM: u32 = 52; +pub global SHARE_DECRYPTION_BIT_R1: u32 = 64; +pub global SHARE_DECRYPTION_BIT_R2: u32 = 52; +pub global SHARE_DECRYPTION_BIT_D: u32 = 52; -// share_decryption - bounds pub global SHARE_DECRYPTION_R1_BOUNDS: [Field; L] = [4611686035875690497, 9223372037660080129, 9223372045176272897, 9223372051618723841]; pub global SHARE_DECRYPTION_R2_BOUNDS: [Field; L] = [1125899911102464, 2251799813881856, 2251799815716864, 2251799817289728]; -// share_decryption - configs pub global SHARE_DECRYPTION_CONFIGS: ShareDecryptionConfigs = ShareDecryptionConfigs::new(QIS, SHARE_DECRYPTION_R1_BOUNDS, SHARE_DECRYPTION_R2_BOUNDS); diff --git a/circuits/lib/src/core/threshold/share_decryption.nr b/circuits/lib/src/core/threshold/share_decryption.nr index 02255ca9c6..2f838f0ebc 100644 --- a/circuits/lib/src/core/threshold/share_decryption.nr +++ b/circuits/lib/src/core/threshold/share_decryption.nr @@ -45,10 +45,10 @@ pub struct ShareDecryption; L], - /// c_1 components for each CRT basis (degree N-1 polynomials with N coefficients) - c_1: [Polynomial; L], + /// ct0 components for each CRT basis (degree N-1 polynomials with N coefficients) + ct0: [Polynomial; L], + /// ct1 components for each CRT basis (degree N-1 polynomials with N coefficients) + ct1: [Polynomial; L], /// Aggregated sum of sk shares (secret witness) sk: [Polynomial; L], @@ -58,8 +58,8 @@ pub struct ShareDecryption; L], /// Quotient polynomials for lifting to Z (secret witnesses) - r_1: [Polynomial<2 * N - 1>; L], - r_2: [Polynomial; L], + r1: [Polynomial<2 * N - 1>; L], + r2: [Polynomial; L], /// Party's computed decryption share /// (public witnesses) @@ -71,24 +71,24 @@ impl, expected_sk_commitment: Field, expected_e_sm_commitment: Field, - c_0: [Polynomial; L], - c_1: [Polynomial; L], + ct0: [Polynomial; L], + ct1: [Polynomial; L], sk: [Polynomial; L], e_sm: [Polynomial; L], - r_1: [Polynomial<2 * N - 1>; L], - r_2: [Polynomial; L], + r1: [Polynomial<2 * N - 1>; L], + r2: [Polynomial; L], d: [Polynomial; L], ) -> Self { ShareDecryption { configs, expected_sk_commitment, expected_e_sm_commitment, - c_0, - c_1, + ct0, + ct1, sk, e_sm, - r_1, - r_2, + r1, + r2, d, } } @@ -141,12 +141,12 @@ impl(inputs, self.c_0); - inputs = flatten::<_, _, BIT_CT>(inputs, self.c_1); + inputs = flatten::<_, _, BIT_CT>(inputs, self.ct0); + inputs = flatten::<_, _, BIT_CT>(inputs, self.ct1); // Flatten quotient polynomials (secret witnesses) - inputs = flatten::<_, _, BIT_R1>(inputs, self.r_1); - inputs = flatten::<_, _, BIT_R2>(inputs, self.r_2); + inputs = flatten::<_, _, BIT_R1>(inputs, self.r1); + inputs = flatten::<_, _, BIT_R2>(inputs, self.r2); // Flatten decryption shares (public outputs) inputs = flatten::<_, _, BIT_D>(inputs, self.d); @@ -205,12 +205,12 @@ impl( + self.r1[basis_idx].range_check_2bounds::( self.configs.r1_bounds[basis_idx], self.configs.r1_bounds[basis_idx], ); // r_2 quotients (cyclotomic quotients) - self.r_2[basis_idx].range_check_2bounds::( + self.r2[basis_idx].range_check_2bounds::( self.configs.r2_bounds[basis_idx], self.configs.r2_bounds[basis_idx], ); @@ -261,16 +261,16 @@ impl Result<()> { registry.register(Arc::new(ShareEncryptionCircuit)); registry.register(Arc::new(ShareDecryptionCircuit)); registry.register(Arc::new(PkAggregationCircuit)); + registry.register(Arc::new(ShareDecryptionCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -313,6 +317,14 @@ fn main() -> Result<()> { let circuit = PkAggregationCircuit; circuit.codegen(preset, &sample)? } + name if name == ::NAME => { + let sample = ShareDecryptionCircuitInput::generate_sample( + preset, + CiphernodesCommitteeSize::Small.values(), + )?; + let circuit = ShareDecryptionCircuit; + circuit.codegen(preset, &sample)? + } name => return Err(anyhow!("circuit {} not yet implemented", name)), }; diff --git a/crates/zk-helpers/src/circuits/threshold/mod.rs b/crates/zk-helpers/src/circuits/threshold/mod.rs index cdd8d3a606..0ebb26a830 100644 --- a/crates/zk-helpers/src/circuits/threshold/mod.rs +++ b/crates/zk-helpers/src/circuits/threshold/mod.rs @@ -6,4 +6,5 @@ pub mod pk_aggregation; pub mod pk_generation; +pub mod share_decryption; pub mod user_data_encryption; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs new file mode 100644 index 0000000000..2b37265ec9 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs @@ -0,0 +1,29 @@ +// 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 e3_fhe_params::ParameterType; +use e3_polynomial::CrtPolynomial; +use fhe::bfv::{Ciphertext, PublicKey}; + +#[derive(Debug)] +pub struct ShareDecryptionCircuit; + +impl Circuit for ShareDecryptionCircuit { + const NAME: &'static str = "share-decryption"; + const PREFIX: &'static str = "SHARE_DECRYPTION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + const DKG_INPUT_TYPE: Option = None; +} + +pub struct ShareDecryptionCircuitInput { + pub ciphertext: Ciphertext, + pub public_key: PublicKey, + pub s: CrtPolynomial, + pub e: CrtPolynomial, + pub d_share: CrtPolynomial, +} diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs new file mode 100644 index 0000000000..bbe21ef1d7 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs @@ -0,0 +1,194 @@ +// 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 public-key BFV circuit: Prover.toml and configs.nr. + +use crate::circuits::computation::Computation; +use crate::threshold::share_decryption::computation::Witness; +use crate::threshold::share_decryption::{ + Configs, ShareDecryptionCircuit, ShareDecryptionCircuitInput, +}; +use crate::utils::join_display; +use crate::Circuit; +use crate::CircuitCodegen; +use crate::CircuitsErrors; +use crate::{Artifacts, CodegenConfigs, CodegenToml}; + +use e3_fhe_params::BfvPreset; + +/// Implementation of [`CircuitCodegen`] for [`ShareDecryptionCircuit`]. +impl CircuitCodegen for ShareDecryptionCircuit { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + 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 = generate_configs(preset, &configs); + + Ok(Artifacts { toml, configs }) + } +} + +pub fn generate_toml(witness: Witness) -> Result { + let json = witness + .to_json() + .map_err(|e| CircuitsErrors::SerdeJson(e))?; + + Ok(toml::to_string(&json)?) +} + +pub fn generate_configs(_preset: BfvPreset, configs: &Configs) -> CodegenConfigs { + let prefix = ::PREFIX; + + let qis_str = join_display(&configs.moduli, ", "); + let r1_bounds_str = join_display(&configs.bounds.r1_bounds, ", "); + let r2_bounds_str = join_display(&configs.bounds.r2_bounds, ", "); + + format!( + r#"use crate::core::threshold::share_decryption::Configs as ShareDecryptionConfigs; + +// Global configs for Share Decryption circuit +pub global N: u32 = {}; +pub global L: u32 = {}; +pub global QIS: [Field; L] = [{}]; + +/************************************ +------------------------------------- +share_decryption +------------------------------------- +************************************/ + +pub global {}_BIT_CT: u32 = {}; +pub global {}_BIT_SK: u32 = {}; +pub global {}_BIT_E_SM: u32 = {}; +pub global {}_BIT_R1: u32 = {}; +pub global {}_BIT_R2: u32 = {}; +pub global {}_BIT_D: u32 = {}; + +pub global {}_R1_BOUNDS: [Field; L] = [{}]; +pub global {}_R2_BOUNDS: [Field; L] = [{}]; + +pub global {}_CONFIGS: ShareDecryptionConfigs = ShareDecryptionConfigs::new( + QIS, + {}_R1_BOUNDS, + {}_R2_BOUNDS, +); +"#, + configs.n, + configs.l, + qis_str, + prefix, + configs.bits.ct_bit, + prefix, + configs.bits.sk_bit, + prefix, + configs.bits.e_sm_bit, + prefix, + configs.bits.r1_bit, + prefix, + configs.bits.r2_bit, + prefix, + configs.bits.d_bit, + prefix, + r1_bounds_str, + prefix, + r2_bounds_str, + prefix, + prefix, + prefix, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::circuits::computation::Computation; + use crate::codegen::write_artifacts; + use crate::threshold::share_decryption::computation::{Bits, Bounds}; + use crate::threshold::share_decryption::ShareDecryptionCircuitInput; + use crate::CiphernodesCommitteeSize; + + use e3_fhe_params::BfvPreset; + use tempfile::TempDir; + + #[test] + fn test_toml_generation_and_structure() { + let committee = CiphernodesCommitteeSize::Small.values(); + + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + committee, + ) + .unwrap(); + let artifacts = ShareDecryptionCircuit + .codegen(BfvPreset::InsecureThreshold512, &sample) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + let ct0 = parsed + .get("ct0") + .and_then(|value| value.as_array()) + .unwrap(); + let ct1 = parsed + .get("ct1") + .and_then(|value| value.as_array()) + .unwrap(); + assert!(!ct0.is_empty()); + assert!(!ct1.is_empty()); + + let temp_dir = TempDir::new().unwrap(); + write_artifacts( + Some(&artifacts.toml), + &artifacts.configs, + Some(temp_dir.path()), + ) + .unwrap(); + + let output_path = temp_dir.path().join("Prover.toml"); + assert!(output_path.exists()); + + let content = std::fs::read_to_string(&output_path).unwrap(); + assert!(content.contains("ct0")); + assert!(content.contains("ct1")); + + assert!(artifacts.toml.contains("[[ct0]]")); + assert!(artifacts.toml.contains("[[ct1]]")); + + let configs_path = temp_dir.path().join("configs.nr"); + assert!(configs_path.exists()); + + let configs_content = std::fs::read_to_string(&configs_path).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &()).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); + + assert!(configs_content.contains( + format!( + "N: u32 = {}", + BfvPreset::InsecureThreshold512.metadata().degree + ) + .as_str() + )); + assert!(configs_content.contains( + format!( + "L: u32 = {}", + BfvPreset::InsecureThreshold512.metadata().num_moduli + ) + .as_str() + )); + assert!(configs_content.contains( + format!( + "{}_BIT_CT: u32 = {}", + ::PREFIX, + bits.ct_bit + ) + .as_str() + )); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs new file mode 100644 index 0000000000..b48600c005 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -0,0 +1,447 @@ +// 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. + +//! Computation types for the public key generation circuit: constants, bounds, bit widths, and witness. +//! +//! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) a public key. They implement [`Computation`] and are used by codegen. + +use crate::calculate_bit_width; +use crate::circuits::commitments::compute_aggregated_shares_commitment; +use crate::compute_pk_bit; +use crate::crt_polynomial_to_toml_json; +use crate::get_zkp_modulus; +use crate::threshold::share_decryption::circuit::ShareDecryptionCircuit; +use crate::threshold::share_decryption::circuit::ShareDecryptionCircuitInput; +use crate::CircuitsErrors; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_polynomial::CrtPolynomial; +use e3_polynomial::Polynomial; +use itertools::izip; +use num_bigint::BigInt; +use num_bigint::BigUint; +use num_traits::ToPrimitive; +use rayon::iter::ParallelBridge; +use rayon::iter::ParallelIterator; +use serde::{Deserialize, Serialize}; + +/// Output of [`CircuitComputation::compute`] for [`PkGenerationCircuit`]: bounds, bit widths, and witness. +#[derive(Debug)] +pub struct ShareDecryptionComputationOutput { + pub bounds: Bounds, + pub bits: Bits, + pub witness: Witness, +} + +/// Implementation of [`CircuitComputation`] for [`PkGenerationCircuit`]. +impl CircuitComputation for ShareDecryptionCircuit { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Output = ShareDecryptionComputationOutput; + 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(ShareDecryptionComputationOutput { + bounds, + bits, + witness, + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Configs { + pub n: usize, + pub l: usize, + pub moduli: Vec, + pub bits: Bits, + pub bounds: Bounds, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + pub ct_bit: u32, + pub sk_bit: u32, + pub e_sm_bit: u32, + pub r1_bit: u32, + pub r2_bit: u32, + pub d_bit: u32, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds { + pub r1_bounds: Vec, + pub r2_bounds: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + pub ct0: CrtPolynomial, + pub ct1: CrtPolynomial, + pub sk: CrtPolynomial, + pub e_sm: CrtPolynomial, + pub r1: CrtPolynomial, + pub r2: CrtPolynomial, + pub d: CrtPolynomial, + pub expected_sk_commitment: BigInt, + pub expected_e_sm_commitment: BigInt, +} + +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 bounds = Bounds::compute(preset, &())?; + let bits = Bits::compute(preset, &bounds)?; + + Ok(Configs { + n: threshold_params.degree(), + l: moduli.len(), + moduli, + bits, + bounds, + }) + } +} + +impl Computation for Bits { + type Preset = BfvPreset; + type Input = Bounds; + type Error = CircuitsErrors; + + fn compute(_: Self::Preset, input: &Self::Input) -> Result { + // For r1, use the maximum of all low and up bounds + let mut r1_bit = 0; + for bound in input.r1_bounds.iter() { + r1_bit = r1_bit.max(calculate_bit_width(BigInt::from(bound.clone()))); + } + + // For r2, use the maximum of all bounds + let mut r2_bit = 0; + for bound in &input.r2_bounds { + r2_bit = r2_bit.max(calculate_bit_width(BigInt::from(bound.clone()))); + } + + Ok(Bits { + ct_bit: r2_bit, + sk_bit: r2_bit, + e_sm_bit: r2_bit, + r1_bit, + r2_bit, + d_bit: r2_bit, + }) + } +} + +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 n = BigInt::from(threshold_params.degree()); + // Get cyclotomic degree and context at provided level + let ctx = threshold_params.ctx_at_level(0)?; + + // Calculate bounds for each CRT basis + let mut r1_bounds: Vec = Vec::new(); + let mut r2_bounds: Vec = Vec::new(); + let mut moduli: Vec = Vec::new(); + + for qi in ctx.moduli_operators() { + let qi_bigint = BigInt::from(qi.modulus()); + let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); + + moduli.push(qi.modulus()); + + // r_2j bounds: [- (q_j-1)/2 , (q_j-1)/2] (cyclotomic quotients) + r2_bounds.push(qi_bound.clone()); + + // r_1j bounds: based on the formula from the notes + // r_1j: [(-(q_j-1)/2 * (BS.N+3) - Be) / q_j , ((q_j-1)/2 * (BS.N+3) + Be) / q_j] + // Where BS = s_bound, Be = e_bound, N = n (degree) + r1_bounds.push( + (&qi_bound * (&qi_bound.clone() * &n + BigInt::from(3)) - &qi_bound.clone()) + / &qi_bigint, + ); + } + + let bounds = Bounds { + r1_bounds: r1_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + r2_bounds: r2_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + }; + + Ok(bounds) + } +} + +impl Computation for Witness { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; + + let moduli: Vec = threshold_params + .moduli() + .iter() + .copied() + .map(BigInt::from) + .collect(); + let n = threshold_params.degree() as u64; + + // Extract and convert ciphertext polynomials + let ct0 = CrtPolynomial::from_fhe_polynomial(&input.ciphertext.c[0]); + let ct1 = CrtPolynomial::from_fhe_polynomial(&input.ciphertext.c[1]); + + // Create cyclotomic polynomial x^N + 1 + let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; + cyclo[0] = BigInt::from(1u64); // x^N term + cyclo[n as usize] = BigInt::from(1u64); // x^0 term + + // Perform the main computation logic + let mut results: Vec<( + usize, + Polynomial, + Polynomial, + Polynomial, + Polynomial, + Polynomial, + Polynomial, + Polynomial, + )> = izip!( + moduli.clone(), + ct0.limbs.clone(), + ct1.limbs.clone(), + input.s.limbs.clone(), + input.e.limbs.clone(), + input.d_share.limbs.clone(), + ) + .enumerate() + .par_bridge() + .map(|(i, (qi, mut ct0, mut ct1, mut s, mut e, mut d_share))| { + ct0.reverse(); + ct0.reduce(&qi); + ct0.center(&qi); + + ct1.reverse(); + ct1.reduce(&qi); + ct1.center(&qi); + + s.reverse(); + s.reduce(&qi); + s.center(&qi); + + e.reverse(); + e.reduce(&qi); + e.center(&qi); + + d_share.reverse(); + d_share.reduce(&qi); + d_share.center(&qi); + + // Compute d_share_hat = ct0 + ct1 * s + e + // This is the expected value before lifting to Z + let d_share_hat = { + // ct1 * s (degree 2*(n-1)) + let ct1_s_times = ct1.mul(&s); + assert_eq!((ct1_s_times.coefficients().len() as u64) - 1, 2 * (n - 1)); + + // ct0 + ct1 * s + e + ct0.add(&ct1_s_times).add(&e) + }; + assert_eq!((d_share_hat.coefficients().len() as u64) - 1, 2 * (n - 1)); + + // Check whether d_share_hat mod R_qi (the ring) is equal to d_share + let mut d_share_hat_mod_rqi = d_share_hat.reduce_by_cyclotomic(&cyclo).unwrap(); + + d_share_hat_mod_rqi.reduce(&qi); + d_share_hat_mod_rqi.center(&qi); + + assert_eq!(&d_share, &d_share_hat_mod_rqi); + + // Compute r2_numerator = d_share - d_share_hat (in Z) + // This should be divisible by (X^N + 1) and q_i + let r2_numerator = d_share.sub(&d_share_hat); + + let mut r2_numerator_centered = r2_numerator.clone(); + r2_numerator_centered.reduce(&qi); + r2_numerator_centered.center(&qi); + + // First, compute r2 = (d_share - d_share_hat) / (X^N + 1) mod Z_qi + let cyclo_polynomial = Polynomial::new(cyclo.clone()); + let (r2, r2_rem) = r2_numerator_centered.div(&cyclo_polynomial).unwrap(); + + assert!(r2_rem.is_zero()); + assert_eq!((r2.coefficients().len() as u64) - 1, n - 2); // Order(r2) = N - 2 + + // Assert that (d_share - d_share_hat) = (r2 * cyclo) mod Z_qi + let r2_cyclo_times = r2.mul(&cyclo_polynomial); + + let mut r2_cyclo_times_centered = r2_cyclo_times.clone(); + r2_cyclo_times_centered.reduce(&qi); + r2_cyclo_times_centered.center(&qi); + + assert_eq!(&r2_numerator_centered, &r2_cyclo_times_centered); + assert_eq!( + (r2_cyclo_times.coefficients().len() as u64) - 1, + 2 * (n - 1) + ); + + // Now compute r1 = (d_share - d_share_hat - r2 * cyclo) / q_i mod Z_p + let r1_numerator = r2_numerator.sub(&r2_cyclo_times); + + assert_eq!((r1_numerator.coefficients().len() as u64) - 1, 2 * (n - 1)); + + let qi_polynomial = Polynomial::new(vec![qi.clone()]); + let (r1, r1_rem) = r1_numerator.div(&qi_polynomial).unwrap(); + + assert!(r1_rem.is_zero()); + assert_eq!((r1.coefficients().len() as u64) - 1, 2 * (n - 1)); // Order(r1) = 2*(N-1) + + assert_eq!(&r1_numerator, &r1.mul(&qi_polynomial)); + + // Assert that d_share = ct0 + ct1 * s + e + r2 * cyclo + r1 * q_i mod Z_p + + let r1_qi_times = r1.scalar_mul(&qi); + let d_share_calculated = d_share_hat.add(&r1_qi_times).add(&r2_cyclo_times); + + assert_eq!(&d_share, &d_share_calculated.trim_leading_zeros()); + + (i, ct0, ct1, s, e, d_share, r2, r1) + }) + .collect(); + + results.sort_by_key(|(i, _, _, _, _, _, _, _)| *i); + + let mut ct0 = CrtPolynomial::new(vec![]); + let mut ct1 = CrtPolynomial::new(vec![]); + let mut sk = CrtPolynomial::new(vec![]); + let mut e_sm = CrtPolynomial::new(vec![]); + let mut r1 = CrtPolynomial::new(vec![]); + let mut r2 = CrtPolynomial::new(vec![]); + let mut d = CrtPolynomial::new(vec![]); + + for (_i, ct0i, ct1i, si, ei, d_sharei, r2i, r1i) in results { + ct0.add_limb(ct0i); + ct1.add_limb(ct1i); + sk.add_limb(si); + e_sm.add_limb(ei); + r1.add_limb(r1i); + r2.add_limb(r2i); + d.add_limb(d_sharei); + } + + let zkp_modulus = &get_zkp_modulus(); + + ct0.reduce_uniform(zkp_modulus); + ct1.reduce_uniform(zkp_modulus); + sk.reduce_uniform(zkp_modulus); + e_sm.reduce_uniform(zkp_modulus); + r1.reduce_uniform(zkp_modulus); + r2.reduce_uniform(zkp_modulus); + d.reduce_uniform(zkp_modulus); + + // Compute commitments to s and e (matches circuit's commitment functions) + let pk_bit = compute_pk_bit(&threshold_params); + let expected_sk_commitment = compute_aggregated_shares_commitment(&sk, pk_bit); + let expected_e_sm_commitment = compute_aggregated_shares_commitment(&e_sm, pk_bit); + + Ok(Witness { + ct0, + ct1, + sk, + e_sm, + r1, + r2, + d, + expected_sk_commitment, + expected_e_sm_commitment, + }) + } + + fn to_json(&self) -> serde_json::Result { + let ct0 = crt_polynomial_to_toml_json(&self.ct0); + let ct1 = crt_polynomial_to_toml_json(&self.ct1); + let sk = crt_polynomial_to_toml_json(&self.sk); + let e_sm = crt_polynomial_to_toml_json(&self.e_sm); + let r1 = crt_polynomial_to_toml_json(&self.r1); + let r2 = crt_polynomial_to_toml_json(&self.r2); + let d = crt_polynomial_to_toml_json(&self.d); + let expected_sk_commitment = self.expected_sk_commitment.to_string(); + let expected_e_sm_commitment = self.expected_e_sm_commitment.to_string(); + + let json = serde_json::json!({ + "ct0": ct0, + "ct1": ct1, + "sk": sk, + "e_sm": e_sm, + "r1": r1, + "r2": r2, + "d": d, + "expected_sk_commitment": expected_sk_commitment, + "expected_e_sm_commitment": expected_e_sm_commitment, + }); + + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use e3_fhe_params::DEFAULT_BFV_PRESET; + + #[test] + fn test_bound_and_bits_computation_consistency() { + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &()).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + + let expected_bit = + calculate_bit_width(BigInt::from(bounds.r2_bounds.iter().max().unwrap().clone())); + + assert_eq!(bits.d_bit, expected_bit); + } + + #[test] + fn test_constants_json_roundtrip() { + let constants = Configs::compute(DEFAULT_BFV_PRESET, &()).unwrap(); + + let json = constants.to_json().unwrap(); + let decoded: Configs = serde_json::from_value(json).unwrap(); + + assert_eq!(decoded.n, constants.n); + assert_eq!(decoded.l, constants.l); + assert_eq!(decoded.moduli, constants.moduli); + assert_eq!(decoded.bits, constants.bits); + assert_eq!(decoded.bounds, constants.bounds); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs new file mode 100644 index 0000000000..d2a27d04f1 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs @@ -0,0 +1,19 @@ +// 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. + +//! User data encryption circuit. +//! +//! This circuit proves data encryption with a BFV public key (pk0, pk1) and produces +//! Prover.toml and configs.nr for the Noir prover. See [`UserDataEncryptionCircuit`] and +//! [`UserDataEncryptionCircuitInput`]. + +pub mod circuit; +pub mod codegen; +pub mod computation; +pub mod sample; +pub use circuit::*; +pub use codegen::*; +pub use computation::*; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs new file mode 100644 index 0000000000..6d76889783 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs @@ -0,0 +1,319 @@ +// 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 user data encryption circuit. +//! +//! [`Sample`] produces a random BFV key pair and plaintext; the public key and plaintext are used as input +//! for codegen and tests. + +use std::sync::Arc; + +use crate::{ + threshold::share_decryption::ShareDecryptionCircuitInput, CiphernodesCommittee, CircuitsErrors, +}; +use e3_fhe_params::{build_pair_for_preset, BfvPreset}; +use e3_polynomial::CrtPolynomial; +use fhe::{ + bfv::{Encoding, Plaintext, PublicKey}, + mbfv::{AggregateIter, CommonRandomPoly, PublicKeyShare}, + trbfv::{ShareManager, TRBFV}, +}; +use fhe_traits::{FheEncoder, FheEncrypter}; +use ndarray::ArrayView; +use rand::{rngs::OsRng, thread_rng}; + +impl ShareDecryptionCircuitInput { + /// Generates a random secret key, public key, and plaintext for the given BFV parameters. + pub fn generate_sample( + preset: BfvPreset, + committee: CiphernodesCommittee, + ) -> Result { + let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to build pair for preset: {:?}", e)) + })?; + + let mut rng = OsRng; + let mut thread_rng = thread_rng(); + + let num_parties = committee.n; + let threshold = committee.threshold; + let num_ciphertexts = 10; + let lambda = preset.metadata().lambda; + + // Create TRBFV instance for share generation + let trbfv = TRBFV::new(num_parties, threshold, threshold_params.clone()) + .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; + + // Generate a random secret key and create public key shares + let crp = CommonRandomPoly::new(&threshold_params, &mut thread_rng) + .map_err(|e| CircuitsErrors::Sample(format!("Failed to create CRP: {:?}", e)))?; + + // Generate secret keys for each party (each party has their own secret key) + // Each party splits their secret key into shares and sends them to others + let mut party_secret_keys = Vec::new(); + let mut pk_shares = Vec::new(); + + for _ in 0..num_parties { + let sk = fhe::bfv::SecretKey::random(&threshold_params, &mut rng); + let pk_share = PublicKeyShare::new(&sk, crp.clone(), &mut thread_rng).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create public key share: {:?}", e)) + })?; + party_secret_keys.push(sk); + pk_shares.push(pk_share); + } + + // Aggregate public key shares to get the full public key + let public_key: PublicKey = pk_shares.iter().cloned().aggregate().map_err(|e| { + CircuitsErrors::Sample(format!("Failed to aggregate public key: {:?}", e)) + })?; + + // Encrypt a sample message (e.g., 1) to create a ciphertext + let message = 1u64; + let pt = Plaintext::try_encode(&[message], Encoding::poly(), &threshold_params) + .map_err(|e| CircuitsErrors::Sample(format!("Failed to encode plaintext: {:?}", e)))?; + let ciphertext = public_key.try_encrypt(&pt, &mut thread_rng)?; + + // Simulate party 0's perspective: + // - Each party has their own secret key + // - Each party splits their secret key into shares and sends them to others + // - Party 0 collects shares from other parties (including themselves) + // - When party 0 computes a decryption share, they aggregate all collected shares + + let mut share_manager = ShareManager::new(num_parties, threshold, threshold_params.clone()); + + // Generate shares for each party's secret key + // In reality, each party would do this independently + let mut all_party_sk_shares = Vec::new(); // [party][modulus][receiver][coefficient] + let mut all_party_esi_shares = Vec::new(); // [party][modulus][receiver][coefficient] + + for party_sk in party_secret_keys.iter().take(num_parties) { + let sk = &party_sk; + let sk_poly = share_manager + .coeffs_to_poly_level0(sk.coeffs.clone().as_ref()) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to convert SK coeffs to poly: {:?}", e)) + })?; + + let temp_trbfv = trbfv.clone(); + let sk_sss = temp_trbfv + .generate_secret_shares_from_poly(sk_poly, rng) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to generate SK shares: {:?}", e)) + })?; + + all_party_sk_shares.push(sk_sss); + + let esi_coeffs = trbfv + .generate_smudging_error(num_ciphertexts, lambda, &mut rng) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to generate smudging error: {:?}", e)) + })?; + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to convert error to poly: {:?}", e)) + })?; + let esi_sss = share_manager + .generate_secret_shares_from_poly(esi_poly, rng) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to generate error shares: {:?}", e)) + })?; + all_party_esi_shares.push(esi_sss); + } + + // Simulate party 0 collecting shares from other parties + let honest_parties = threshold + 1; + let mut sk_sss_collected = Vec::new(); + let mut es_sss_collected = Vec::new(); + + for modulus_idx in 0..threshold_params.moduli().len() { + let mut sk_collected = ndarray::Array2::::zeros((0, threshold_params.degree())); + let mut es_collected = ndarray::Array2::::zeros((0, threshold_params.degree())); + + // Party 0 collects shares from honest parties + // For each party i, party 0 collects the share that party i sent to party 0 + // This is all_party_sk_shares[i][modulus_idx].row(0) (share for party 0) + for party_idx in 0..honest_parties { + // Check bounds before accessing + if modulus_idx >= all_party_sk_shares[party_idx].len() { + return Err(CircuitsErrors::Sample(format!( + "Modulus index {} out of bounds for party {} (has {} moduli)", + modulus_idx, + party_idx, + all_party_sk_shares[party_idx].len() + ))); + } + if modulus_idx >= all_party_esi_shares[party_idx].len() { + return Err(CircuitsErrors::Sample(format!( + "Modulus index {} out of bounds for party {} error shares (has {} moduli)", + modulus_idx, + party_idx, + all_party_esi_shares[party_idx].len() + ))); + } + + // Collect the share that party_idx sent to party 0 + let sk_share_row = all_party_sk_shares[party_idx][modulus_idx].row(0); + let sk_share_vec = sk_share_row.to_vec(); + sk_collected + .push_row(ArrayView::from(&sk_share_vec)) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to push SK share row: {:?}", e)) + })?; + + let es_share_row = all_party_esi_shares[party_idx][modulus_idx].row(0); + let es_share_vec = es_share_row.to_vec(); + es_collected + .push_row(ArrayView::from(&es_share_vec)) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to push ES share row: {:?}", e)) + })?; + } + + sk_sss_collected.push(sk_collected); + es_sss_collected.push(es_collected); + } + + // Aggregate collected shares to get s and e polynomials + // First, sum across parties for each modulus, then restructure to match + // the format expected by aggregate_collected_shares: one Array2 of shape [num_moduli, degree] + let ctx = threshold_params.ctx_at_level(0)?; + let num_moduli = sk_sss_collected.len(); + + // Sum across parties for each modulus to create [num_moduli, degree] matrices + let mut sk_sum_matrix = + ndarray::Array2::::zeros((num_moduli, threshold_params.degree())); + let mut es_sum_matrix = + ndarray::Array2::::zeros((num_moduli, threshold_params.degree())); + + for modulus_idx in 0..num_moduli { + // Sum across parties (rows) for this modulus + for party_idx in 0..sk_sss_collected[modulus_idx].nrows() { + for coeff_idx in 0..threshold_params.degree() { + sk_sum_matrix[[modulus_idx, coeff_idx]] = (sk_sum_matrix + [[modulus_idx, coeff_idx]] + + sk_sss_collected[modulus_idx][[party_idx, coeff_idx]]) + % ctx.moduli()[modulus_idx]; + es_sum_matrix[[modulus_idx, coeff_idx]] = (es_sum_matrix + [[modulus_idx, coeff_idx]] + + es_sss_collected[modulus_idx][[party_idx, coeff_idx]]) + % ctx.moduli()[modulus_idx]; + } + } + } + + // Use aggregate_collected_shares with the correctly formatted data + // It expects a slice with one Array2 of shape [num_moduli, degree] + let sk_poly_sum = trbfv + .aggregate_collected_shares(&[sk_sum_matrix]) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to aggregate SK shares: {:?}", e)) + })?; + + let es_poly_sum = trbfv + .aggregate_collected_shares(&[es_sum_matrix]) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to aggregate ES shares: {:?}", e)) + })?; + + // Compute the decryption share using TRBFV + let d_share_rns = trbfv + .clone() + .decryption_share( + Arc::new(ciphertext.clone()), + sk_poly_sum.clone(), + es_poly_sum.clone(), + ) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to compute decryption share: {:?}", e)) + })?; + + Ok(Self { + ciphertext, + public_key, + s: CrtPolynomial::from_fhe_polynomial(&sk_poly_sum), + e: CrtPolynomial::from_fhe_polynomial(&es_poly_sum), + d_share: CrtPolynomial::from_fhe_polynomial(&d_share_rns), + }) + } +} + +#[cfg(test)] +mod tests { + use crate::CiphernodesCommitteeSize; + + use super::*; + use e3_fhe_params::BfvPreset; + + const PRESET: BfvPreset = BfvPreset::InsecureThreshold512; + + #[test] + fn test_generate_template_succeeds_and_has_correct_structure() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = ShareDecryptionCircuitInput::generate_sample(PRESET, committee).unwrap(); + + let degree = PRESET.metadata().degree; + let num_moduli = PRESET.metadata().num_moduli; + + assert_eq!( + sample.public_key.c.c.len(), + 2, + "BFV public key has two components" + ); + assert_eq!( + sample.ciphertext.c.len(), + 2, + "BFV ciphertext has two components" + ); + + assert_eq!( + sample.s.limbs.len(), + num_moduli, + "s polynomial has one limb per modulus" + ); + assert_eq!( + sample.e.limbs.len(), + num_moduli, + "e polynomial has one limb per modulus" + ); + assert_eq!( + sample.d_share.limbs.len(), + num_moduli, + "d_share polynomial has one limb per modulus" + ); + + assert_eq!( + sample.s.limb(0).coefficients().len(), + degree, + "each limb has degree coefficients" + ); + } + + #[test] + fn test_generate_template_polynomials_consistent() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = ShareDecryptionCircuitInput::generate_sample(PRESET, committee).unwrap(); + + let n = sample.s.limbs.len(); + assert_eq!(sample.e.limbs.len(), n, "e must have same limb count as s"); + assert_eq!( + sample.d_share.limbs.len(), + n, + "d_share must have same limb count as s" + ); + } + + #[test] + fn test_generate_template_repeatable() { + let committee = CiphernodesCommitteeSize::Small.values(); + + let a = ShareDecryptionCircuitInput::generate_sample(PRESET, committee.clone()).unwrap(); + let b = ShareDecryptionCircuitInput::generate_sample(PRESET, committee).unwrap(); + + assert_eq!(a.public_key.c.c.len(), b.public_key.c.c.len()); + assert_eq!(a.s.limbs.len(), b.s.limbs.len()); + assert_eq!(a.e.limbs.len(), b.e.limbs.len()); + assert_eq!(a.d_share.limbs.len(), b.d_share.limbs.len()); + } +} From 244e5e8918e92405be6e78392432a18c0707c240 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 9 Feb 2026 18:24:56 +0100 Subject: [PATCH 02/14] docs: update share_decryption circuit documentation --- .../threshold/share_decryption/circuit.rs | 16 ++++++++++++++++ .../threshold/share_decryption/codegen.rs | 2 +- .../threshold/share_decryption/computation.rs | 9 +++++---- .../circuits/threshold/share_decryption/mod.rs | 10 ++++++---- .../threshold/share_decryption/sample.rs | 6 +++--- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs index 2b37265ec9..5386637484 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs @@ -4,15 +4,31 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +//! Circuit type and input for threshold share decryption. + use crate::computation::DkgInputType; use crate::registry::Circuit; use e3_fhe_params::ParameterType; use e3_polynomial::CrtPolynomial; use fhe::bfv::{Ciphertext, PublicKey}; +/// Threshold share decryption circuit (PVSS #6). +/// +/// Verifies correct computation of a party's decryption share with respect to +/// committed aggregated secret and smudging-error shares. #[derive(Debug)] pub struct ShareDecryptionCircuit; +/// Input to the share decryption circuit: ciphertext, public key, and the party's +/// aggregated secret share (s), smudging error (e), and computed decryption share (d_share). +pub struct ShareDecryptionCircuitInput { + pub ciphertext: Ciphertext, + pub public_key: PublicKey, + pub s: CrtPolynomial, + pub e: CrtPolynomial, + pub d_share: CrtPolynomial, +} + impl Circuit for ShareDecryptionCircuit { const NAME: &'static str = "share-decryption"; const PREFIX: &'static str = "SHARE_DECRYPTION"; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs index bbe21ef1d7..723f24bbf3 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/codegen.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. +//! Code generation for the threshold share decryption circuit: Prover.toml and configs.nr. use crate::circuits::computation::Computation; use crate::threshold::share_decryption::computation::Witness; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs index b48600c005..3b4358719f 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -4,10 +4,11 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Computation types for the public key generation circuit: constants, bounds, bit widths, and witness. +//! Computation types for the threshold share decryption circuit: constants, bounds, bit widths, and witness. //! //! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters -//! and (for witness) a public key. They implement [`Computation`] and are used by codegen. +//! and (for witness) ciphertext plus aggregated shares (s, e, d_share). They implement +//! [`Computation`] and are used by codegen. use crate::calculate_bit_width; use crate::circuits::commitments::compute_aggregated_shares_commitment; @@ -30,7 +31,7 @@ use rayon::iter::ParallelBridge; use rayon::iter::ParallelIterator; use serde::{Deserialize, Serialize}; -/// Output of [`CircuitComputation::compute`] for [`PkGenerationCircuit`]: bounds, bit widths, and witness. +/// Output of [`CircuitComputation::compute`] for [`ShareDecryptionCircuit`]: bounds, bit widths, and witness. #[derive(Debug)] pub struct ShareDecryptionComputationOutput { pub bounds: Bounds, @@ -38,7 +39,7 @@ pub struct ShareDecryptionComputationOutput { pub witness: Witness, } -/// Implementation of [`CircuitComputation`] for [`PkGenerationCircuit`]. +/// Implementation of [`CircuitComputation`] for [`ShareDecryptionCircuit`]. impl CircuitComputation for ShareDecryptionCircuit { type Preset = BfvPreset; type Input = ShareDecryptionCircuitInput; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs index d2a27d04f1..ddca09c8ac 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/mod.rs @@ -4,11 +4,13 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! User data encryption circuit. +//! Threshold share decryption circuit. //! -//! This circuit proves data encryption with a BFV public key (pk0, pk1) and produces -//! Prover.toml and configs.nr for the Noir prover. See [`UserDataEncryptionCircuit`] and -//! [`UserDataEncryptionCircuitInput`]. +//! Proves correct computation of a BFV decryption share: the prover shows that their +//! share `d` satisfies the lifted relation +//! `d = c_0 + c_1 * s + e + r_2 * (X^N + 1) + r_1 * q_i` with committed `s` and `e`, +//! and produces Prover.toml and configs.nr for the Noir prover. See [`ShareDecryptionCircuit`] +//! and [`ShareDecryptionCircuitInput`]. pub mod circuit; pub mod codegen; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs index 6d76889783..2db24afc88 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs @@ -4,10 +4,10 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Sample data generation for user data encryption circuit. +//! Sample data generation for the threshold share decryption circuit. //! -//! [`Sample`] produces a random BFV key pair and plaintext; the public key and plaintext are used as input -//! for codegen and tests. +//! Produces a random BFV ciphertext, aggregated secret and smudging-error shares (s, e), +//! and the corresponding decryption share (d_share) for use in codegen and tests. use std::sync::Arc; From e621ec8a091ade142ea58b65dc5c7e474780a9dc Mon Sep 17 00:00:00 2001 From: Cedoor Date: Mon, 9 Feb 2026 18:29:05 +0100 Subject: [PATCH 03/14] fix: remove duplicate struct --- .../src/circuits/threshold/share_decryption/circuit.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs index 5386637484..9aea8e3dd0 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs @@ -35,11 +35,3 @@ impl Circuit for ShareDecryptionCircuit { const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; const DKG_INPUT_TYPE: Option = None; } - -pub struct ShareDecryptionCircuitInput { - pub ciphertext: Ciphertext, - pub public_key: PublicKey, - pub s: CrtPolynomial, - pub e: CrtPolynomial, - pub d_share: CrtPolynomial, -} From 8a3a8aeab99fe08e499165eb114481d5f9b800be Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 09:12:05 +0100 Subject: [PATCH 04/14] fix(zk-helpers): correct cyclotomic polynomial term comments - cyclo[0] is constant (x^0) term, not x^N - cyclo[n] is x^N term, not x^0 Co-authored-by: Cursor --- .../src/circuits/threshold/share_decryption/computation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs index 3b4358719f..3e90af24cb 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -224,8 +224,8 @@ impl Computation for Witness { // Create cyclotomic polynomial x^N + 1 let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; - cyclo[0] = BigInt::from(1u64); // x^N term - cyclo[n as usize] = BigInt::from(1u64); // x^0 term + cyclo[0] = BigInt::from(1u64); // constant (x^0) term + cyclo[n as usize] = BigInt::from(1u64); // x^N term // Perform the main computation logic let mut results: Vec<( From ee1172249338fa99158c2afebb380c63145f21bf Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 09:13:29 +0100 Subject: [PATCH 05/14] docs(share-decryption): align r1_bounds comment with implementation - Update comment to reflect subtraction formula (qi_bound * (qi_bound * n + 3) - qi_bound) / qi_bigint - Document qi_bound, qi_bigint, n variables - Remove outdated BS/Be reference that expected addition Co-authored-by: Cursor --- .../src/circuits/threshold/share_decryption/computation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs index 3e90af24cb..19ddd105f5 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -177,9 +177,9 @@ impl Computation for Bounds { // r_2j bounds: [- (q_j-1)/2 , (q_j-1)/2] (cyclotomic quotients) r2_bounds.push(qi_bound.clone()); - // r_1j bounds: based on the formula from the notes - // r_1j: [(-(q_j-1)/2 * (BS.N+3) - Be) / q_j , ((q_j-1)/2 * (BS.N+3) + Be) / q_j] - // Where BS = s_bound, Be = e_bound, N = n (degree) + // r_1j upper bound: (qi_bound * (qi_bound * n + 3) - qi_bound) / qi_bigint + // Symmetric lower bound used by range_check_2bounds. Variables: qi_bound = (q_j-1)/2, + // qi_bigint = q_j, n = degree. r1_bounds.push( (&qi_bound * (&qi_bound.clone() * &n + BigInt::from(3)) - &qi_bound.clone()) / &qi_bigint, From e02a3206822af4c534431f6f34823e8d619650a4 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:17:19 +0100 Subject: [PATCH 06/14] fix: solve remaining conflicts --- crates/zk-helpers/src/bin/zk_cli.rs | 30 +++++---- .../circuits/dkg/share_decryption/codegen.rs | 6 +- .../dkg/share_decryption/computation.rs | 9 ++- .../circuits/dkg/share_decryption/sample.rs | 66 ++++++++++++++----- 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 9914f1cf20..d929a5d980 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -20,14 +20,18 @@ use e3_zk_helpers::circuits::dkg::share_computation::circuit::{ }; use e3_zk_helpers::codegen::{write_artifacts, CircuitCodegen}; use e3_zk_helpers::computation::DkgInputType; -use e3_zk_helpers::dkg::share_decryption::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; +use e3_zk_helpers::dkg::share_decryption::{ + ShareDecryptionCircuit as DkgShareDecryptionCircuit, + ShareDecryptionCircuitInput as DkgShareDecryptionCircuitInput, +}; use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; 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}; use e3_zk_helpers::threshold::share_decryption::{ - ShareDecryptionCircuit, ShareDecryptionCircuitInput, + ShareDecryptionCircuit as ThresholdShareDecryptionCircuit, + ShareDecryptionCircuitInput as ThresholdShareDecryptionCircuitInput, }; use e3_zk_helpers::threshold::user_data_encryption::{ UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, @@ -162,9 +166,9 @@ fn main() -> Result<()> { registry.register(Arc::new(UserDataEncryptionCircuit)); registry.register(Arc::new(PkGenerationCircuit)); registry.register(Arc::new(ShareEncryptionCircuit)); - registry.register(Arc::new(ShareDecryptionCircuit)); + registry.register(Arc::new(DkgShareDecryptionCircuit)); registry.register(Arc::new(PkAggregationCircuit)); - registry.register(Arc::new(ShareDecryptionCircuit)); + registry.register(Arc::new(ThresholdShareDecryptionCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -215,7 +219,8 @@ fn main() -> Result<()> { // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME || circuit_meta.name() == ShareEncryptionCircuit::NAME - || circuit_meta.name() == ShareDecryptionCircuit::NAME; + || circuit_meta.name() == DkgShareDecryptionCircuit::NAME + || circuit_meta.name() == ThresholdShareDecryptionCircuit::NAME; let dkg_input_type = if has_witness_type { // Share-computation: require --witness when generating Prover.toml; default secret-key for configs-only. @@ -298,14 +303,14 @@ fn main() -> Result<()> { let circuit = PkGenerationCircuit; circuit.codegen(preset, &sample)? } - name if name == ::NAME => { - let sample = ShareDecryptionCircuitInput::generate_sample( + name if name == ::NAME => { + let sample = DkgShareDecryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, - ); + )?; - let circuit = ShareDecryptionCircuit; + let circuit = DkgShareDecryptionCircuit; circuit.codegen(preset, &sample)? } name if name == ::NAME => { @@ -317,12 +322,13 @@ fn main() -> Result<()> { let circuit = PkAggregationCircuit; circuit.codegen(preset, &sample)? } - name if name == ::NAME => { - let sample = ShareDecryptionCircuitInput::generate_sample( + name if name == ::NAME => { + let sample = ThresholdShareDecryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small.values(), )?; - let circuit = ShareDecryptionCircuit; + + let circuit = ThresholdShareDecryptionCircuit; circuit.codegen(preset, &sample)? } name => return Err(anyhow!("circuit {} not yet implemented", name)), diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs index 990c1dbd83..4590e05ff9 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs @@ -87,7 +87,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, - ); + ) + .unwrap(); let artifacts = ShareDecryptionCircuit .codegen(BfvPreset::InsecureThreshold512, &sample) @@ -104,7 +105,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, - ); + ) + .unwrap(); let artifacts = ShareDecryptionCircuit .codegen(BfvPreset::InsecureThreshold512, &sample) diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs index b446530d98..14d9c2a098 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs @@ -233,7 +233,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, - ); + ) + .unwrap(); let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); @@ -248,7 +249,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, - ); + ) + .unwrap(); let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let json = constants.to_json().unwrap(); @@ -267,7 +269,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, - ); + ) + .unwrap(); let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); // Witness should have one row per honest party diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs index 3030019e0c..025a517b4d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -27,9 +27,13 @@ impl ShareDecryptionCircuitInput { preset: BfvPreset, committee: CiphernodesCommitteeSize, dkg_input_type: DkgInputType, - ) -> Self { - let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap(); - let sd = preset.search_defaults().unwrap(); + ) -> Result { + let (threshold_params, dkg_params) = build_pair_for_preset(preset).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to build pair for preset: {:?}", e)) + })?; + let sd = preset + .search_defaults() + .ok_or_else(|| CircuitsErrors::Sample("Preset has no search defaults".into()))?; let mut rng = thread_rng(); let committee = committee.values(); @@ -38,7 +42,7 @@ impl ShareDecryptionCircuitInput { let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) - .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); + .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; let mut share_manager = ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); @@ -53,11 +57,21 @@ impl ShareDecryptionCircuitInput { let sk_poly = share_manager .coeffs_to_poly_level0(threshold_secret_key.coeffs.clone().as_ref()) - .unwrap(); + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to convert secret key to polynomial: {:?}", + e + )) + })?; let sk_sss_u64 = share_manager .generate_secret_shares_from_poly(sk_poly.clone(), &mut rng) - .unwrap(); + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate secret shares: {:?}", + e + )) + })?; sk_sss_u64[0].row(0).to_vec() } @@ -70,8 +84,18 @@ impl ShareDecryptionCircuitInput { e )) }) - .unwrap(); - let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).unwrap(); + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate smudging error: {:?}", + e + )) + })?; + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to convert error to poly: {:?}", + e + )) + })?; let esi_sss_u64 = share_manager .generate_secret_shares_from_poly(esi_poly.clone(), &mut rng.clone()) .map_err(|e| { @@ -80,24 +104,34 @@ impl ShareDecryptionCircuitInput { e )) }) - .unwrap(); + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate error shares: {:?}", + e + )) + })?; esi_sss_u64[0].row(0).to_vec() } }; - let pt = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params).unwrap(); + let pt = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params).map_err( + |e| CircuitsErrors::Sample(format!("Failed to encode plaintext: {:?}", e)), + )?; + + let ct = dkg_public_key.try_encrypt(&pt, &mut rng).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to encrypt plaintext: {:?}", e)) + })?; - let ct = dkg_public_key.try_encrypt(&pt, &mut rng).unwrap(); party_cts.push(ct); } honest_ciphertexts.push(party_cts); } - ShareDecryptionCircuitInput { + Ok(ShareDecryptionCircuitInput { honest_ciphertexts, secret_key: dkg_secret_key, - } + }) } } @@ -115,7 +149,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, - ); + ) + .unwrap(); assert_eq!(sample.honest_ciphertexts.len(), committee.n); assert_eq!( @@ -131,7 +166,8 @@ mod tests { BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SmudgingNoise, - ); + ) + .unwrap(); assert_eq!(sample.honest_ciphertexts.len(), committee.n); assert_eq!( From d827c88837d5c93ec6808aa14eae762356ddbdd5 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:25:56 +0100 Subject: [PATCH 07/14] refactor: align committee type in samples --- crates/zk-helpers/src/bin/zk_cli.rs | 23 +++++++------------ .../circuits/dkg/share_computation/sample.rs | 21 ++++++++--------- .../circuits/dkg/share_decryption/sample.rs | 9 ++++---- .../circuits/dkg/share_encryption/codegen.rs | 9 ++++---- .../circuits/dkg/share_encryption/sample.rs | 14 +++++------ 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index d929a5d980..aba39c9ec0 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -256,6 +256,7 @@ fn main() -> Result<()> { run_with_spinner(|| { let circuit_name = circuit_meta.name(); + let committee = CiphernodesCommitteeSize::Small.values(); let artifacts = match circuit_name { name if name == ::NAME => { let sample = PkCircuitInput::generate_sample(preset); @@ -266,7 +267,7 @@ fn main() -> Result<()> { name if name == ::NAME => { let sample = ShareComputationCircuitInput::generate_sample( preset, - CiphernodesCommitteeSize::Small, + committee, dkg_input_type, ); @@ -279,7 +280,7 @@ fn main() -> Result<()> { })?; let sample = ShareEncryptionCircuitInput::generate_sample( preset, - CiphernodesCommitteeSize::Small, + committee, dkg_input_type, sd.z, sd.lambda, @@ -295,10 +296,7 @@ fn main() -> Result<()> { circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sample = PkGenerationCircuitInput::generate_sample( - preset, - CiphernodesCommitteeSize::Small.values(), - )?; + let sample = PkGenerationCircuitInput::generate_sample(preset, committee)?; let circuit = PkGenerationCircuit; circuit.codegen(preset, &sample)? @@ -306,7 +304,7 @@ fn main() -> Result<()> { name if name == ::NAME => { let sample = DkgShareDecryptionCircuitInput::generate_sample( preset, - CiphernodesCommitteeSize::Small, + committee, dkg_input_type, )?; @@ -314,19 +312,14 @@ fn main() -> Result<()> { circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sample = PkAggregationCircuitInput::generate_sample( - preset, - CiphernodesCommitteeSize::Small.values(), - )?; + let sample = PkAggregationCircuitInput::generate_sample(preset, committee)?; let circuit = PkAggregationCircuit; circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sample = ThresholdShareDecryptionCircuitInput::generate_sample( - preset, - CiphernodesCommitteeSize::Small.values(), - )?; + let sample = + ThresholdShareDecryptionCircuitInput::generate_sample(preset, committee)?; let circuit = ThresholdShareDecryptionCircuit; circuit.codegen(preset, &sample)? diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs index ad5fc4732e..582f554143 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -7,10 +7,10 @@ //! Sample data generation for the share-computation circuit: committee, DKG public key, //! secret (SK or smudging noise) in CRT form, Shamir shares, and parity matrices. -use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::dkg::share_computation::utils::compute_parity_matrix; use crate::computation::DkgInputType; use crate::dkg::share_computation::ShareComputationCircuitInput; +use crate::CiphernodesCommittee; use crate::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; @@ -26,13 +26,12 @@ impl ShareComputationCircuitInput { /// Generates sample data for the share-computation circuit. pub fn generate_sample( preset: BfvPreset, - committee_size: CiphernodesCommitteeSize, + committee: CiphernodesCommittee, dkg_input_type: DkgInputType, ) -> Self { let (threshold_params, _) = build_pair_for_preset(preset).unwrap(); let sd = preset.search_defaults().unwrap(); let mut rng = thread_rng(); - let committee = committee_size.values(); let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); @@ -121,14 +120,14 @@ mod tests { #[test] fn test_generate_secret_key_sample() { - let committee_size = CiphernodesCommitteeSize::Small; + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - committee_size, + committee.clone(), DkgInputType::SecretKey, ); - assert_eq!(sample.n_parties, committee_size.values().n as u32); - assert_eq!(sample.threshold, committee_size.values().threshold as u32); + assert_eq!(sample.n_parties, committee.n as u32); + assert_eq!(sample.threshold, committee.threshold as u32); assert_eq!(sample.dkg_input_type, DkgInputType::SecretKey); assert_eq!(sample.secret_sss.len(), 2); assert_eq!(sample.secret.limbs.len(), 2); @@ -136,14 +135,14 @@ mod tests { #[test] fn test_generate_smudging_noise_sample() { - let committee_size = CiphernodesCommitteeSize::Small; + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - committee_size, + committee.clone(), DkgInputType::SmudgingNoise, ); - assert_eq!(sample.n_parties, committee_size.values().n as u32); - assert_eq!(sample.threshold, committee_size.values().threshold as u32); + assert_eq!(sample.n_parties, committee.n as u32); + assert_eq!(sample.threshold, committee.threshold as u32); assert_eq!(sample.dkg_input_type, DkgInputType::SmudgingNoise); assert_eq!(sample.secret_sss.len(), 2); assert_eq!(sample.secret.limbs.len(), 2); diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs index 025a517b4d..cc1220ea4c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -6,9 +6,9 @@ //! Sample data generation for the share-decryption circuit: honest ciphertexts, sum ciphertexts, secret key, and message. -use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::dkg::share_decryption::circuit::ShareDecryptionCircuitInput; use crate::computation::DkgInputType; +use crate::CiphernodesCommittee; use crate::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; @@ -25,7 +25,7 @@ impl ShareDecryptionCircuitInput { /// Generates sample data for the share-decryption circuit (decrypts a sum of honest ciphertexts under DKG secret key). pub fn generate_sample( preset: BfvPreset, - committee: CiphernodesCommitteeSize, + committee: CiphernodesCommittee, dkg_input_type: DkgInputType, ) -> Result { let (threshold_params, dkg_params) = build_pair_for_preset(preset).map_err(|e| { @@ -36,7 +36,6 @@ impl ShareDecryptionCircuitInput { .ok_or_else(|| CircuitsErrors::Sample("Preset has no search defaults".into()))?; let mut rng = thread_rng(); - let committee = committee.values(); let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); @@ -147,7 +146,7 @@ mod tests { let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee.clone(), DkgInputType::SecretKey, ) .unwrap(); @@ -164,7 +163,7 @@ mod tests { let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee.clone(), DkgInputType::SmudgingNoise, ) .unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs index 06c000c8b3..d8b4130f87 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs @@ -187,19 +187,19 @@ pub global {}_CONFIGS: ShareEncryptionConfigs = ShareEncryptionConfigs::new( mod tests { use super::*; - use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::dkg::share_encryption::{Bounds, ShareEncryptionCircuitInput}; use crate::computation::Computation; use crate::computation::DkgInputType; - use crate::Circuit; + use crate::{CiphernodesCommitteeSize, Circuit}; use e3_fhe_params::BfvPreset; #[test] fn test_toml_generation_and_structure() { + let committee = CiphernodesCommitteeSize::Small.values(); let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee.clone(), DkgInputType::SecretKey, sd.z, sd.lambda, @@ -217,10 +217,11 @@ mod tests { #[test] fn test_configs_generation_contains_expected() { + let committee = CiphernodesCommitteeSize::Small.values(); let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee.clone(), DkgInputType::SecretKey, sd.z, sd.lambda, 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 3cbc963dbe..a83883629f 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -7,9 +7,9 @@ //! Sample data generation for the share-encryption circuit: DKG public key, plaintext, //! ciphertext, and encryption randomness (u_rns, e0_rns, e1_rns) for testing and codegen. -use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::dkg::share_encryption::circuit::ShareEncryptionCircuitInput; use crate::computation::DkgInputType; +use crate::CiphernodesCommittee; use crate::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; @@ -24,7 +24,7 @@ impl ShareEncryptionCircuitInput { /// Generates sample data for the share-encryption circuit (encrypts a share row under DKG pk). pub fn generate_sample( preset: BfvPreset, - committee_size: CiphernodesCommitteeSize, + committee: CiphernodesCommittee, dkg_input_type: DkgInputType, num_ciphertexts: u128, // z in the search defaults lambda: u32, @@ -32,7 +32,6 @@ impl ShareEncryptionCircuitInput { let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap(); let mut rng = thread_rng(); - let committee = committee_size.values(); let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); @@ -97,16 +96,16 @@ impl ShareEncryptionCircuitInput { #[cfg(test)] mod tests { use super::*; - use crate::ciphernodes_committee::CiphernodesCommitteeSize; - use crate::computation::DkgInputType; + use crate::{computation::DkgInputType, CiphernodesCommitteeSize}; use e3_fhe_params::BfvPreset; #[test] fn test_generate_secret_key_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee.clone(), DkgInputType::SecretKey, sd.z, sd.lambda, @@ -134,10 +133,11 @@ mod tests { #[test] fn test_generate_smudging_noise_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee.clone(), DkgInputType::SecretKey, sd.z, sd.lambda, From b542cb63e4b421bbb75a84f60941165698c416d5 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:29:00 +0100 Subject: [PATCH 08/14] refactor: align committee type in computations --- .../src/circuits/dkg/share_computation/codegen.rs | 3 ++- .../src/circuits/dkg/share_computation/computation.rs | 9 ++++++--- .../src/circuits/dkg/share_decryption/codegen.rs | 6 ++++-- .../src/circuits/dkg/share_decryption/computation.rs | 9 ++++++--- .../src/circuits/dkg/share_encryption/computation.rs | 9 ++++++--- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index f1f3344de0..4eaf55f418 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -153,9 +153,10 @@ mod tests { #[test] fn test_toml_generation_and_structure() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index d60d10212a..dd8408b866 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -271,9 +271,10 @@ mod tests { #[test] fn test_bound_and_bits_computation_consistency() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ); let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); @@ -285,9 +286,10 @@ mod tests { #[test] fn test_witness_smudging_noise_secret_consistency() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SmudgingNoise, ); let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); @@ -308,9 +310,10 @@ mod tests { #[test] fn test_constants_json_roundtrip() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ); diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs index 4590e05ff9..ca3c6a26b2 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs @@ -83,9 +83,10 @@ mod tests { #[test] fn test_toml_generation_and_structure() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ) .unwrap(); @@ -101,9 +102,10 @@ mod tests { #[test] fn test_configs_generation_contains_expected() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ) .unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs index 14d9c2a098..c8af802e69 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs @@ -229,9 +229,10 @@ mod tests { #[test] fn test_bound_and_bits_computation_consistency() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ) .unwrap(); @@ -245,9 +246,10 @@ mod tests { #[test] fn test_constants_json_roundtrip() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ) .unwrap(); @@ -265,9 +267,10 @@ mod tests { #[test] fn test_witness_decryption_consistency() { + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, ) .unwrap(); 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 07ad48542d..bed29ecb4e 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -754,9 +754,10 @@ mod tests { #[test] fn test_bound_and_bits_computation_consistency() { let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, sd.z, sd.lambda, @@ -775,9 +776,10 @@ mod tests { #[test] fn test_constants_json_roundtrip() { let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, sd.z, sd.lambda, @@ -798,9 +800,10 @@ mod tests { #[test] fn test_witness_message_consistency() { let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let committee = CiphernodesCommitteeSize::Small.values(); let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee, DkgInputType::SecretKey, sd.z, sd.lambda, From 3b97abd222205b437862bacd413a7f40a9b88c59 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:33:39 +0100 Subject: [PATCH 09/14] refactor: update share-decryption circuit names --- .../threshold/share_decryption/src/main.nr | 2 +- crates/zk-helpers/src/bin/zk_cli.rs | 3 +- .../circuits/dkg/share_encryption/circuit.rs | 4 +- .../threshold/share_decryption/circuit.rs | 4 +- .../threshold/share_decryption/computation.rs | 57 +------------------ 5 files changed, 8 insertions(+), 62 deletions(-) diff --git a/circuits/bin/threshold/share_decryption/src/main.nr b/circuits/bin/threshold/share_decryption/src/main.nr index eaddad3878..32d3ec4e57 100644 --- a/circuits/bin/threshold/share_decryption/src/main.nr +++ b/circuits/bin/threshold/share_decryption/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use lib::configs::secure::threshold::{ +use lib::configs::default::threshold::{ L, N, SHARE_DECRYPTION_BIT_CT, SHARE_DECRYPTION_BIT_D, SHARE_DECRYPTION_BIT_E_SM, SHARE_DECRYPTION_BIT_R1, SHARE_DECRYPTION_BIT_R2, SHARE_DECRYPTION_BIT_SK, SHARE_DECRYPTION_CONFIGS, diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index aba39c9ec0..f425af26ce 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -219,8 +219,7 @@ fn main() -> Result<()> { // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME || circuit_meta.name() == ShareEncryptionCircuit::NAME - || circuit_meta.name() == DkgShareDecryptionCircuit::NAME - || circuit_meta.name() == ThresholdShareDecryptionCircuit::NAME; + || circuit_meta.name() == DkgShareDecryptionCircuit::NAME; let dkg_input_type = if has_witness_type { // Share-computation: require --witness when generating Prover.toml; default secret-key for configs-only. diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs index ec3bcd3bf5..4f4a13340c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs @@ -20,8 +20,8 @@ use fhe_math::rq::Poly; pub struct ShareEncryptionCircuit; impl Circuit for ShareEncryptionCircuit { - const NAME: &'static str = "share-encryption"; - const PREFIX: &'static str = "SHARE_ENCRYPTION"; + const NAME: &'static str = "dkg-share-encryption"; + const PREFIX: &'static str = "DKG_SHARE_ENCRYPTION"; const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; /// None: circuit accepts runtime-varying input type (SecretKey or SmudgingNoise). const DKG_INPUT_TYPE: Option = None; diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs index 9aea8e3dd0..b07c9fc12d 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/circuit.rs @@ -30,8 +30,8 @@ pub struct ShareDecryptionCircuitInput { } impl Circuit for ShareDecryptionCircuit { - const NAME: &'static str = "share-decryption"; - const PREFIX: &'static str = "SHARE_DECRYPTION"; + const NAME: &'static str = "threshold-share-decryption"; + const PREFIX: &'static str = "THRESHOLD_SHARE_DECRYPTION"; const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; const DKG_INPUT_TYPE: Option = None; } diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs index 19ddd105f5..dc06e93af8 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -14,6 +14,7 @@ use crate::calculate_bit_width; use crate::circuits::commitments::compute_aggregated_shares_commitment; use crate::compute_pk_bit; use crate::crt_polynomial_to_toml_json; +use crate::decompose_residue; use crate::get_zkp_modulus; use crate::threshold::share_decryption::circuit::ShareDecryptionCircuit; use crate::threshold::share_decryption::circuit::ShareDecryptionCircuitInput; @@ -280,61 +281,7 @@ impl Computation for Witness { }; assert_eq!((d_share_hat.coefficients().len() as u64) - 1, 2 * (n - 1)); - // Check whether d_share_hat mod R_qi (the ring) is equal to d_share - let mut d_share_hat_mod_rqi = d_share_hat.reduce_by_cyclotomic(&cyclo).unwrap(); - - d_share_hat_mod_rqi.reduce(&qi); - d_share_hat_mod_rqi.center(&qi); - - assert_eq!(&d_share, &d_share_hat_mod_rqi); - - // Compute r2_numerator = d_share - d_share_hat (in Z) - // This should be divisible by (X^N + 1) and q_i - let r2_numerator = d_share.sub(&d_share_hat); - - let mut r2_numerator_centered = r2_numerator.clone(); - r2_numerator_centered.reduce(&qi); - r2_numerator_centered.center(&qi); - - // First, compute r2 = (d_share - d_share_hat) / (X^N + 1) mod Z_qi - let cyclo_polynomial = Polynomial::new(cyclo.clone()); - let (r2, r2_rem) = r2_numerator_centered.div(&cyclo_polynomial).unwrap(); - - assert!(r2_rem.is_zero()); - assert_eq!((r2.coefficients().len() as u64) - 1, n - 2); // Order(r2) = N - 2 - - // Assert that (d_share - d_share_hat) = (r2 * cyclo) mod Z_qi - let r2_cyclo_times = r2.mul(&cyclo_polynomial); - - let mut r2_cyclo_times_centered = r2_cyclo_times.clone(); - r2_cyclo_times_centered.reduce(&qi); - r2_cyclo_times_centered.center(&qi); - - assert_eq!(&r2_numerator_centered, &r2_cyclo_times_centered); - assert_eq!( - (r2_cyclo_times.coefficients().len() as u64) - 1, - 2 * (n - 1) - ); - - // Now compute r1 = (d_share - d_share_hat - r2 * cyclo) / q_i mod Z_p - let r1_numerator = r2_numerator.sub(&r2_cyclo_times); - - assert_eq!((r1_numerator.coefficients().len() as u64) - 1, 2 * (n - 1)); - - let qi_polynomial = Polynomial::new(vec![qi.clone()]); - let (r1, r1_rem) = r1_numerator.div(&qi_polynomial).unwrap(); - - assert!(r1_rem.is_zero()); - assert_eq!((r1.coefficients().len() as u64) - 1, 2 * (n - 1)); // Order(r1) = 2*(N-1) - - assert_eq!(&r1_numerator, &r1.mul(&qi_polynomial)); - - // Assert that d_share = ct0 + ct1 * s + e + r2 * cyclo + r1 * q_i mod Z_p - - let r1_qi_times = r1.scalar_mul(&qi); - let d_share_calculated = d_share_hat.add(&r1_qi_times).add(&r2_cyclo_times); - - assert_eq!(&d_share, &d_share_calculated.trim_leading_zeros()); + let (r1, r2) = decompose_residue(&d_share, &d_share_hat, &qi, &cyclo, n); (i, ct0, ct1, s, e, d_share, r2, r1) }) From d663f2241f43e8fca6265d61d0778218ac35271c Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:43:50 +0100 Subject: [PATCH 10/14] refactor: update local e2e tests in zk-prover --- crates/zk-prover/tests/local_e2e_tests.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/zk-prover/tests/local_e2e_tests.rs b/crates/zk-prover/tests/local_e2e_tests.rs index 7dea5d0896..8eebf6391f 100644 --- a/crates/zk-prover/tests/local_e2e_tests.rs +++ b/crates/zk-prover/tests/local_e2e_tests.rs @@ -11,11 +11,9 @@ mod common; use common::fixtures_dir; use e3_fhe_params::BfvPreset; -use e3_zk_helpers::ciphernodes_committee::CiphernodesCommitteeSize; +use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuit; use e3_zk_helpers::circuits::dkg::pk::circuit::PkCircuitInput; -use e3_zk_helpers::circuits::dkg::pk::prepare_pk_sample_for_test; use e3_zk_helpers::circuits::{commitments::compute_dkg_pk_commitment, CircuitComputation}; -use e3_zk_helpers::PkCircuit; use e3_zk_prover::{Provable, ZkBackend, ZkConfig, ZkProver}; use std::path::PathBuf; use tempfile::tempdir; @@ -96,14 +94,14 @@ async fn test_pk_bfv_proof_generation() { .unwrap(); let preset = BfvPreset::InsecureThreshold512; - let sample = prepare_pk_sample_for_test(preset, CiphernodesCommitteeSize::Small); + let sample = PkCircuitInput::generate_sample(preset); let prover = ZkProver::new(&backend); let circuit = PkCircuit; let e3_id = "test-pk-bfv-001"; let proof = circuit - .prove(&prover, &preset, &sample.dkg_public_key, e3_id) + .prove(&prover, &preset, &sample.public_key, e3_id) .expect("proof generation should succeed"); assert!(!proof.data.is_empty(), "proof data should not be empty"); @@ -138,14 +136,14 @@ async fn test_pk_bfv_proof_verification() { .unwrap(); let preset = BfvPreset::InsecureThreshold512; - let sample = prepare_pk_sample_for_test(preset, CiphernodesCommitteeSize::Small); + let sample = PkCircuitInput::generate_sample(preset); let prover = ZkProver::new(&backend); let circuit = PkCircuit; let e3_id = "test-verify-001"; let proof = circuit - .prove(&prover, &preset, &sample.dkg_public_key, e3_id) + .prove(&prover, &preset, &sample.public_key, e3_id) .expect("proof generation should succeed"); let party_id = 1; @@ -182,14 +180,14 @@ async fn test_pk_bfv_commitment_consistency() { .unwrap(); let preset = BfvPreset::InsecureThreshold512; - let sample = prepare_pk_sample_for_test(preset, CiphernodesCommitteeSize::Small); + let sample = PkCircuitInput::generate_sample(preset); let prover = ZkProver::new(&backend); let circuit = PkCircuit; let e3_id = "test-commitment-001"; let proof = circuit - .prove(&prover, &preset, &sample.dkg_public_key, e3_id) + .prove(&prover, &preset, &sample.public_key, e3_id) .expect("proof generation should succeed"); // Verify the commitment from the proof is a valid field element @@ -202,7 +200,7 @@ async fn test_pk_bfv_commitment_consistency() { // Compute the commitment independently to ensure consistency let circuit_input = PkCircuitInput { - public_key: sample.dkg_public_key.clone(), + public_key: sample.public_key.clone(), }; let computation_output = PkCircuit::compute(preset, &circuit_input).expect("computation should succeed"); From 3304e0d450bbd1b100e3bbc8abc710dcc33882fc Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:48:41 +0100 Subject: [PATCH 11/14] refactor(zk-helpers): use preset-derived z for share decryption smudging - Replace hardcoded num_ciphertexts=10 with preset.search_defaults().z - Add search_defaults() resolution with error handling for presets without defaults - Smudging error generation now uses configured parameter per preset Co-authored-by: Cursor --- .../src/circuits/threshold/share_decryption/sample.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs index 2db24afc88..81a13728fd 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/sample.rs @@ -35,12 +35,16 @@ impl ShareDecryptionCircuitInput { CircuitsErrors::Sample(format!("Failed to build pair for preset: {:?}", e)) })?; + let sd = preset + .search_defaults() + .ok_or_else(|| CircuitsErrors::Sample("Preset has no search defaults".into()))?; + let mut rng = OsRng; let mut thread_rng = thread_rng(); let num_parties = committee.n; let threshold = committee.threshold; - let num_ciphertexts = 10; + let num_ciphertexts = sd.z as usize; let lambda = preset.metadata().lambda; // Create TRBFV instance for share generation From 877fdde6a360ad5a34e348abe728026a6da7f300 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 10:54:07 +0100 Subject: [PATCH 12/14] refactor: rename compute_pk_bit to compute_modulus_bit --- crates/zk-helpers/src/circuits/dkg/pk/codegen.rs | 4 ++-- crates/zk-helpers/src/circuits/dkg/pk/computation.rs | 6 +++--- .../src/circuits/dkg/share_encryption/computation.rs | 4 ++-- .../src/circuits/threshold/pk_aggregation/computation.rs | 8 ++++---- .../circuits/threshold/share_decryption/computation.rs | 8 ++++---- .../threshold/user_data_encryption/computation.rs | 4 ++-- .../src/circuits/threshold/user_data_encryption/utils.rs | 6 +++--- crates/zk-helpers/src/utils.rs | 6 +++--- .../CRISP/crates/zk-inputs/src/ciphertext_addition.rs | 4 ++-- examples/CRISP/crates/zk-inputs/src/lib.rs | 4 ++-- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 8eb9a8f426..0ba7b3c5ef 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -73,7 +73,7 @@ mod tests { use super::*; use crate::codegen::write_artifacts; use crate::dkg::pk::PkCircuitInput; - use crate::utils::compute_pk_bit; + use crate::utils::compute_modulus_bit; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use tempfile::TempDir; @@ -121,7 +121,7 @@ mod tests { assert!(configs_path.exists()); let configs_content = std::fs::read_to_string(&configs_path).unwrap(); - let pk_bit = compute_pk_bit(&dkg_params); + let pk_bit = compute_modulus_bit(&dkg_params); assert!(configs_content.contains( format!( diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 55bf3d0658..bff8be486e 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -13,7 +13,7 @@ use crate::circuits::dkg::pk::circuit::PkCircuit; use crate::circuits::dkg::pk::circuit::PkCircuitInput; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; -use crate::utils::compute_pk_bit; +use crate::utils::compute_modulus_bit; use crate::CircuitsErrors; use crate::{CircuitComputation, Computation}; use e3_fhe_params::build_pair_for_preset; @@ -113,7 +113,7 @@ impl Computation for Bits { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; Ok(Bits { - pk_bit: compute_pk_bit(&dkg_params), + pk_bit: compute_modulus_bit(&dkg_params), }) } } @@ -190,7 +190,7 @@ mod tests { let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &()).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &()).unwrap(); - let expected_bits = compute_pk_bit(&dkg_params); + let expected_bits = compute_modulus_bit(&dkg_params); assert_eq!(bounds.pk_bound, BigUint::from(1125899906777088u128)); assert_eq!(bits.pk_bit, expected_bits); 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 bed29ecb4e..027a974c0d 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::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_msg_bit, compute_pk_bit}; +use crate::utils::{compute_msg_bit, compute_modulus_bit}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; use crate::{CircuitComputation, Computation}; @@ -353,7 +353,7 @@ impl Computation for Witness { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let ctx = dkg_params.ctx_at_level(input.plaintext.level())?; - let pk_bit = compute_pk_bit(&dkg_params); + let pk_bit = compute_modulus_bit(&dkg_params); let msg_bit = compute_msg_bit(&dkg_params); let pk = input.public_key.clone(); diff --git a/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs index 6b61a553fd..c54610c623 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs @@ -11,7 +11,7 @@ use crate::bigint_1d_to_json_values; use crate::compute_pk_aggregation_commitment; -use crate::compute_pk_bit; +use crate::compute_modulus_bit; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::threshold::pk_aggregation::circuit::PkAggregationCircuit; @@ -114,7 +114,7 @@ impl Computation for Bits { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; - let pk_bit = compute_pk_bit(&threshold_params); + let pk_bit = compute_modulus_bit(&threshold_params); Ok(Bits { pk_bit }) } @@ -156,7 +156,7 @@ impl Computation for Witness { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Other(e.to_string()))?; - let bit_pk = compute_pk_bit(&threshold_params); + let bit_pk = compute_modulus_bit(&threshold_params); let moduli = threshold_params.moduli(); let zkp_modulus = &get_zkp_modulus(); @@ -249,7 +249,7 @@ mod tests { let bounds = Bounds::compute(preset, &()).unwrap(); let bits = Bits::compute(preset, &()).unwrap(); - let expected_bits = compute_pk_bit(&threshold_params); + let expected_bits = compute_modulus_bit(&threshold_params); assert_eq!(bounds.pk_bound, BigUint::from(34359701504u128)); assert_eq!(bits.pk_bit, expected_bits); diff --git a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs index dc06e93af8..5bae8c0b22 100644 --- a/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/share_decryption/computation.rs @@ -12,7 +12,7 @@ use crate::calculate_bit_width; use crate::circuits::commitments::compute_aggregated_shares_commitment; -use crate::compute_pk_bit; +use crate::compute_modulus_bit; use crate::crt_polynomial_to_toml_json; use crate::decompose_residue; use crate::get_zkp_modulus; @@ -318,9 +318,9 @@ impl Computation for Witness { d.reduce_uniform(zkp_modulus); // Compute commitments to s and e (matches circuit's commitment functions) - let pk_bit = compute_pk_bit(&threshold_params); - let expected_sk_commitment = compute_aggregated_shares_commitment(&sk, pk_bit); - let expected_e_sm_commitment = compute_aggregated_shares_commitment(&e_sm, pk_bit); + let modulus_bit = compute_modulus_bit(&threshold_params); + let expected_sk_commitment = compute_aggregated_shares_commitment(&sk, modulus_bit); + let expected_e_sm_commitment = compute_aggregated_shares_commitment(&e_sm, modulus_bit); Ok(Witness { ct0, 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 2210269b18..cd65fb574e 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 @@ -19,7 +19,7 @@ use crate::polynomial_to_toml_json; use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; -use crate::utils::compute_pk_bit; +use crate::utils::compute_modulus_bit; use crate::CircuitsErrors; use crate::{CircuitComputation, Computation}; use e3_fhe_params::build_pair_for_preset; @@ -360,7 +360,7 @@ impl Computation for Witness { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - let pk_bit = compute_pk_bit(&threshold_params); + let pk_bit = compute_modulus_bit(&threshold_params); let pk = input.public_key.clone(); let pt = input.plaintext.clone(); 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 9ec418fef4..78d8c861fa 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::crt::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}; @@ -94,7 +94,7 @@ pub fn compute_public_key_commitment( )) })?; - let pk_bit = compute_pk_bit(params); + let pk_bit = compute_modulus_bit(params); let commitment = compute_pk_aggregation_commitment(&pk0is, &pk1is, pk_bit); let bytes = commitment.to_bytes_be().1; @@ -139,7 +139,7 @@ pub fn compute_ciphertext_commitment( )) })?; - let pk_bit = compute_pk_bit(params); + let pk_bit = compute_modulus_bit(params); let commitment = compute_ciphertext_commitment(&ct0is, &ct1is, pk_bit); let bytes = commitment.to_bytes_be().1; diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index dd8a4bd11d..c1b7865245 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -151,14 +151,14 @@ pub fn calculate_bit_width(bound: BigInt) -> u32 { bound.bits() as u32 } -/// Computes the bit width of the public key. +/// Computes the bit width of ring elements (coefficients bounded by the coefficient modulus). /// /// # Arguments /// * `params` - BFV parameters /// /// # Returns -/// The bit width of the public key -pub fn compute_pk_bit(params: &BfvParameters) -> u32 { +/// The bit width of ring elements (coefficients bounded by the coefficient modulus) +pub fn compute_modulus_bit(params: &BfvParameters) -> u32 { let moduli = params.moduli(); let modulus = BigInt::from(moduli.iter().copied().max().unwrap()); let bound = (modulus - BigInt::from(1)) / BigInt::from(2); diff --git a/examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs b/examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs index 869a4b68bf..27b6cd1f72 100644 --- a/examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs +++ b/examples/CRISP/crates/zk-inputs/src/ciphertext_addition.rs @@ -7,7 +7,7 @@ use e3_polynomial::{CrtPolynomial, Polynomial}; use e3_zk_helpers::commitments::compute_ciphertext_commitment; use e3_zk_helpers::crt_polynomial_to_toml_json; -use e3_zk_helpers::utils::compute_pk_bit; +use e3_zk_helpers::utils::compute_modulus_bit; use e3_zk_helpers::utils::get_zkp_modulus; use eyre::{Context, Result}; use fhe::bfv::BfvParameters; @@ -97,7 +97,7 @@ impl CiphertextAdditionWitness { r0.reduce_uniform(zkp_modulus); r1.reduce_uniform(zkp_modulus); - let pk_bit = compute_pk_bit(params); + let pk_bit = compute_modulus_bit(params); let prev_ct_commitment = compute_ciphertext_commitment(&prev_ct0, &prev_ct1, pk_bit); Ok(CiphertextAdditionWitness { diff --git a/examples/CRISP/crates/zk-inputs/src/lib.rs b/examples/CRISP/crates/zk-inputs/src/lib.rs index 0eef17cf86..16cb1ba7cd 100644 --- a/examples/CRISP/crates/zk-inputs/src/lib.rs +++ b/examples/CRISP/crates/zk-inputs/src/lib.rs @@ -17,7 +17,7 @@ use e3_polynomial::CrtPolynomial; use e3_zk_helpers::circuits::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; use e3_zk_helpers::circuits::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_zk_helpers::commitments::compute_ciphertext_commitment; -use e3_zk_helpers::utils::compute_pk_bit; +use e3_zk_helpers::utils::compute_modulus_bit; use e3_zk_helpers::CircuitComputation; use e3_zk_helpers::Computation; use eyre::{Context, Result}; @@ -253,7 +253,7 @@ impl ZKInputsGenerator { ct0: &CrtPolynomial, ct1: &CrtPolynomial, ) -> BigInt { - let pk_bit = compute_pk_bit(&self.bfv_params); + let pk_bit = compute_modulus_bit(&self.bfv_params); compute_ciphertext_commitment(ct0, ct1, pk_bit) } From ed63dfb8f4a50580bd5e11eaf26b5adc72c4dcd7 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 11:10:51 +0100 Subject: [PATCH 13/14] style: format rs file --- .../zk-helpers/src/circuits/dkg/share_encryption/computation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 027a974c0d..eb9d353403 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::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_msg_bit, compute_modulus_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}; From f4cbb7af5358459986c67c83fa8fb5993f614a91 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Tue, 10 Feb 2026 11:11:21 +0100 Subject: [PATCH 14/14] style: format rs file --- .../src/circuits/threshold/pk_aggregation/computation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs index c54610c623..f2cd4584dc 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_aggregation/computation.rs @@ -10,8 +10,8 @@ //! and (for witness) public key shares and aggregated public key. They implement [`Computation`] and are used by codegen. use crate::bigint_1d_to_json_values; -use crate::compute_pk_aggregation_commitment; use crate::compute_modulus_bit; +use crate::compute_pk_aggregation_commitment; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::threshold::pk_aggregation::circuit::PkAggregationCircuit;