From 84c2cedcc12835c1f2b0f81525cef0cd2d581022 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 5 Feb 2026 16:19:04 +0100 Subject: [PATCH 01/10] add share-encryption circuit gen --- circuits/lib/src/configs/insecure/dkg.nr | 25 +- circuits/lib/src/configs/secure/dkg.nr | 27 +- crates/zk-helpers/src/bin/zk_cli.rs | 33 +- crates/zk-helpers/src/circuits/dkg/mod.rs | 1 + .../circuits/dkg/share_encryption/circuit.rs | 46 + .../circuits/dkg/share_encryption/codegen.rs | 297 ++++++ .../dkg/share_encryption/computation.rs | 996 ++++++++++++++++++ .../src/circuits/dkg/share_encryption/mod.rs | 15 + .../circuits/dkg/share_encryption/sample.rs | 186 ++++ crates/zk-helpers/src/utils.rs | 13 + 10 files changed, 1609 insertions(+), 30 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 3797eb9a2b..06f8218579 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -74,19 +74,17 @@ share_encryption_sk (CIRCUIT 3a) ------------------------------------- ************************************/ -// share_encryption_sk - bit parameters -pub global SHARE_ENCRYPTION_BIT_PK: u32 = 51; -pub global SHARE_ENCRYPTION_BIT_CT: u32 = 51; -pub global SHARE_ENCRYPTION_BIT_U: u32 = 2; -pub global SHARE_ENCRYPTION_BIT_E0: u32 = 4; -pub global SHARE_ENCRYPTION_BIT_E1: u32 = 4; -pub global SHARE_ENCRYPTION_BIT_MSG: u32 = 37; -pub global SHARE_ENCRYPTION_BIT_R1: u32 = 36; -pub global SHARE_ENCRYPTION_BIT_R2: u32 = 51; -pub global SHARE_ENCRYPTION_BIT_P1: u32 = 10; -pub global SHARE_ENCRYPTION_BIT_P2: u32 = 51; - -// share_encryption_sk - bounds +pub global SHARE_ENCRYPTION_BIT_PK: u32 = 50; +pub global SHARE_ENCRYPTION_BIT_CT: u32 = 50; +pub global SHARE_ENCRYPTION_BIT_U: u32 = 1; +pub global SHARE_ENCRYPTION_BIT_E0: u32 = 3; +pub global SHARE_ENCRYPTION_BIT_E1: u32 = 3; +pub global SHARE_ENCRYPTION_BIT_MSG: u32 = 36; +pub global SHARE_ENCRYPTION_BIT_R1: u32 = 35; +pub global SHARE_ENCRYPTION_BIT_R2: u32 = 50; +pub global SHARE_ENCRYPTION_BIT_P1: u32 = 9; +pub global SHARE_ENCRYPTION_BIT_P2: u32 = 50; + pub global SHARE_ENCRYPTION_T: Field = 68719403009; pub global SHARE_ENCRYPTION_Q_MOD_T: Field = 2415755265; pub global SHARE_ENCRYPTION_K0IS: [Field; L] = [1284838520228573]; @@ -101,7 +99,6 @@ pub global SHARE_ENCRYPTION_P1_BOUNDS: [Field; L] = [256]; pub global SHARE_ENCRYPTION_P2_BOUNDS: [Field; L] = [1125899906777088]; pub global SHARE_ENCRYPTION_MSG_BOUND: Field = 68719403008; -// share_encryption_sk - configs pub global SHARE_ENCRYPTION_CONFIGS_SK: ShareEncryptionConfigs = ShareEncryptionConfigs::new( SHARE_ENCRYPTION_T, SHARE_ENCRYPTION_Q_MOD_T, diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index 27e544b637..2a8f97f23f 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -86,19 +86,17 @@ share_encryption_sk (CIRCUIT 3a) ------------------------------------- ************************************/ -// share_encryption_sk - bit parameters -pub global SHARE_ENCRYPTION_BIT_PK: u32 = 57; -pub global SHARE_ENCRYPTION_BIT_CT: u32 = 57; -pub global SHARE_ENCRYPTION_BIT_U: u32 = 2; -pub global SHARE_ENCRYPTION_BIT_E0: u32 = 6; -pub global SHARE_ENCRYPTION_BIT_E1: u32 = 6; -pub global SHARE_ENCRYPTION_BIT_MSG: u32 = 55; -pub global SHARE_ENCRYPTION_BIT_R1: u32 = 54; -pub global SHARE_ENCRYPTION_BIT_R2: u32 = 57; -pub global SHARE_ENCRYPTION_BIT_P1: u32 = 14; -pub global SHARE_ENCRYPTION_BIT_P2: u32 = 57; - -// share_encryption_sk - bounds +pub global SHARE_ENCRYPTION_BIT_PK: u32 = 56; +pub global SHARE_ENCRYPTION_BIT_CT: u32 = 56; +pub global SHARE_ENCRYPTION_BIT_U: u32 = 1; +pub global SHARE_ENCRYPTION_BIT_E0: u32 = 5; +pub global SHARE_ENCRYPTION_BIT_E1: u32 = 5; +pub global SHARE_ENCRYPTION_BIT_MSG: u32 = 54; +pub global SHARE_ENCRYPTION_BIT_R1: u32 = 53; +pub global SHARE_ENCRYPTION_BIT_R2: u32 = 56; +pub global SHARE_ENCRYPTION_BIT_P1: u32 = 13; +pub global SHARE_ENCRYPTION_BIT_P2: u32 = 56; + pub global SHARE_ENCRYPTION_T: Field = 18014398509481984; pub global SHARE_ENCRYPTION_Q_MOD_T: Field = 1082658244788225; pub global SHARE_ENCRYPTION_K0IS: [Field; L] = [70854796903366627, 47439047573780733]; @@ -113,7 +111,6 @@ pub global SHARE_ENCRYPTION_P1_BOUNDS: [Field; L] = [4096, 4096]; pub global SHARE_ENCRYPTION_P2_BOUNDS: [Field; L] = [36028797041049600, 36028797031219200]; pub global SHARE_ENCRYPTION_MSG_BOUND: Field = 18014398509481983; -// share_encryption_sk - configs pub global SHARE_ENCRYPTION_CONFIGS_SK: ShareEncryptionConfigs = ShareEncryptionConfigs::new( SHARE_ENCRYPTION_T, SHARE_ENCRYPTION_Q_MOD_T, @@ -137,7 +134,7 @@ share_encryption_e_sm (CIRCUIT 3b) ------------------------------------- ************************************/ -// share_encryption_e_sm uses the same bit parameters and bounds as SK +// share_encryption_e_sm uses the same bit parameters and bounds as share_encryption_sk pub global SHARE_ENCRYPTION_CONFIGS_E_SM: ShareEncryptionConfigs = ShareEncryptionConfigs::new( SHARE_ENCRYPTION_T, SHARE_ENCRYPTION_Q_MOD_T, diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index d29475d8bb..947de61b98 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -20,6 +20,9 @@ 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_encryption::{ + ShareEncryptionCircuit, ShareEncryptionCircuitInput, ShareEncryptionSample, +}; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; use e3_zk_helpers::threshold::{ UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, UserDataEncryptionSample, @@ -153,6 +156,7 @@ fn main() -> Result<()> { registry.register(Arc::new(PkCircuit)); registry.register(Arc::new(ShareComputationCircuit)); registry.register(Arc::new(UserDataEncryptionCircuit)); + registry.register(Arc::new(ShareEncryptionCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -201,7 +205,8 @@ fn main() -> Result<()> { let write_prover_toml = args.toml; // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. - let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME; + let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME + || circuit_meta.name() == ShareEncryptionCircuit::NAME; let dkg_input_type = if has_witness_type { // Share-computation: require --witness when generating Prover.toml; default secret-key for configs-only. @@ -273,6 +278,32 @@ fn main() -> Result<()> { }, )? } + name if name == ::NAME => { + let sd = preset + .search_defaults() + .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; + let sample = ShareEncryptionSample::generate( + preset, + CiphernodesCommitteeSize::Small, + dkg_input_type, + sd.z, + sd.lambda, + ); + let circuit = ShareEncryptionCircuit; + + circuit.codegen( + preset, + &ShareEncryptionCircuitInput { + plaintext: sample.plaintext, + ciphertext: sample.ciphertext, + public_key: sample.public_key, + secret_key: sample.secret_key, + u_rns: sample.u_rns, + e0_rns: sample.e0_rns, + e1_rns: sample.e1_rns, + }, + )? + } name if name == ::NAME => { let sample = UserDataEncryptionSample::generate(preset); let circuit = UserDataEncryptionCircuit; diff --git a/crates/zk-helpers/src/circuits/dkg/mod.rs b/crates/zk-helpers/src/circuits/dkg/mod.rs index 905b80d19e..deb387206b 100644 --- a/crates/zk-helpers/src/circuits/dkg/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/mod.rs @@ -6,3 +6,4 @@ pub mod pk; pub mod share_computation; +pub mod share_encryption; diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs new file mode 100644 index 0000000000..93bc22c389 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs @@ -0,0 +1,46 @@ +// 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. + +//! Circuit definition and input type for the share-encryption ZK circuit (CIRCUIT 3a/3b). + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use e3_fhe_params::ParameterType; +use fhe::bfv::Ciphertext; +use fhe::bfv::Plaintext; +use fhe::bfv::PublicKey; +use fhe::bfv::SecretKey; +use fhe_math::rq::Poly; + +/// Share-encryption circuit: proves correct encryption of a (secret or smudging) share under the DKG public key. +#[derive(Debug)] +pub struct ShareEncryptionCircuit; + +impl Circuit for ShareEncryptionCircuit { + const NAME: &'static str = "share-encryption"; + const PREFIX: &'static str = "SHARE_ENCRYPTION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + /// None: circuit accepts runtime-varying input type (SecretKey or SmudgingNoise). + const DKG_INPUT_TYPE: Option = None; +} + +/// Input to the share-encryption circuit: plaintext, ciphertext, keys, and encryption randomness. +pub struct ShareEncryptionCircuitInput { + /// Plaintext (encoded share row). + pub plaintext: Plaintext, + /// Ciphertext (encryption under public_key). + pub ciphertext: Ciphertext, + /// DKG public key used to encrypt. + pub public_key: PublicKey, + /// Secret key (for witness; not revealed in proof). + pub secret_key: SecretKey, + /// Encryption randomness u in RNS form (from try_encrypt_extended). + pub u_rns: Poly, + /// Encryption error e0 in RNS form. + pub e0_rns: Poly, + /// Encryption error e1 in RNS form. + pub e1_rns: Poly, +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs new file mode 100644 index 0000000000..3b1127bc67 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs @@ -0,0 +1,297 @@ +// 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 share-encryption BFV circuit: Prover.toml and configs.nr. + +use crate::circuits::computation::CircuitComputation; +use crate::circuits::dkg::share_encryption::Configs; +use crate::circuits::dkg::share_encryption::ShareEncryptionCircuit; +use crate::circuits::dkg::share_encryption::ShareEncryptionCircuitInput; +use crate::circuits::dkg::share_encryption::ShareEncryptionOutput; +use crate::circuits::dkg::share_encryption::Witness; +use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; +use crate::codegen::CodegenConfigs; +use crate::computation::Computation; +use crate::registry::Circuit; +use crate::utils::join_display; +use e3_fhe_params::BfvPreset; + +/// Implementation of [`CircuitCodegen`] for [`ShareEncryptionCircuit`]. +impl CircuitCodegen for ShareEncryptionCircuit { + type Preset = BfvPreset; + type Input = ShareEncryptionCircuitInput; + type Error = CircuitsErrors; + + fn codegen(&self, preset: Self::Preset, input: &Self::Input) -> Result { + let ShareEncryptionOutput { witness, .. } = ShareEncryptionCircuit::compute(preset, input)?; + + let toml = generate_toml(&witness)?; + let configs = Configs::compute(preset, input)?; + let configs_str = generate_configs(preset, &configs); + + Ok(Artifacts { + toml, + configs: configs_str, + }) + } +} + +/// Serializes the witness to TOML string for the Noir prover (Prover.toml). +pub fn generate_toml(witness: &Witness) -> Result { + let json = witness + .to_json() + .map_err(|e| CircuitsErrors::SerdeJson(e))?; + + Ok(toml::to_string(&json)?) +} + +/// Builds the configs.nr string (N, L, bit parameters, bounds, and ShareEncryptionConfigs) for the Noir prover. +pub fn generate_configs(preset: BfvPreset, configs: &Configs) -> CodegenConfigs { + let prefix = ::PREFIX; + + let qis_str = join_display(&configs.moduli, ", "); + let k0is_str = join_display(&configs.k0is, ", "); + let pk_bounds_str = join_display(&configs.bounds.pk_bounds, ", "); + let r1_low_bounds_str = join_display(&configs.bounds.r1_low_bounds, ", "); + let r1_up_bounds_str = join_display(&configs.bounds.r1_up_bounds, ", "); + let r2_bounds_str = join_display(&configs.bounds.r2_bounds, ", "); + let p1_bounds_str = join_display(&configs.bounds.p1_bounds, ", "); + let p2_bounds_str = join_display(&configs.bounds.p2_bounds, ", "); + + format!( + r#"use crate::core::dkg::share_encryption::Configs as ShareEncryptionConfigs; + +pub global N: u32 = {}; +pub global L: u32 = {}; +pub global QIS: [Field; L] = [{}]; + +/************************************ +------------------------------------- +share_encryption_sk (CIRCUIT 3a) +------------------------------------- +************************************/ + +pub global {}_BIT_PK: u32 = {}; +pub global {}_BIT_CT: u32 = {}; +pub global {}_BIT_U: u32 = {}; +pub global {}_BIT_E0: u32 = {}; +pub global {}_BIT_E1: u32 = {}; +pub global {}_BIT_MSG: u32 = {}; +pub global {}_BIT_R1: u32 = {}; +pub global {}_BIT_R2: u32 = {}; +pub global {}_BIT_P1: u32 = {}; +pub global {}_BIT_P2: u32 = {}; + +pub global {}_T: Field = {}; +pub global {}_Q_MOD_T: Field = {}; +pub global {}_K0IS: [Field; L] = [{}]; +pub global {}_PK_BOUNDS: [Field; L] = [{}]; +pub global {}_E0_BOUND: Field = {}; +pub global {}_E1_BOUND: Field = {}; +pub global {}_U_BOUND: Field = {}; +pub global {}_R1_LOW_BOUNDS: [Field; L] = [{}]; +pub global {}_R1_UP_BOUNDS: [Field; L] = [{}]; +pub global {}_R2_BOUNDS: [Field; L] = [{}]; +pub global {}_P1_BOUNDS: [Field; L] = [{}]; +pub global {}_P2_BOUNDS: [Field; L] = [{}]; +pub global {}_MSG_BOUND: Field = {}; + +pub global {}_CONFIGS_SK: ShareEncryptionConfigs = ShareEncryptionConfigs::new( + {}_T, + {}_Q_MOD_T, + QIS, + {}_K0IS, + {}_PK_BOUNDS, + {}_E0_BOUND, + {}_E1_BOUND, + {}_U_BOUND, + {}_R1_LOW_BOUNDS, + {}_R1_UP_BOUNDS, + {}_R2_BOUNDS, + {}_P1_BOUNDS, + {}_P2_BOUNDS, + {}_MSG_BOUND, +); + +/************************************ +------------------------------------- +share_encryption_e_sm (CIRCUIT 3b) +------------------------------------- +************************************/ + +// share_encryption_e_sm uses the same bit parameters and bounds as share_encryption_sk +pub global SHARE_ENCRYPTION_CONFIGS_E_SM: ShareEncryptionConfigs = ShareEncryptionConfigs::new( + {}_T, + {}_Q_MOD_T, + QIS, + {}_K0IS, + {}_PK_BOUNDS, + {}_E0_BOUND, + {}_E1_BOUND, + {}_U_BOUND, + {}_R1_LOW_BOUNDS, + {}_R1_UP_BOUNDS, + {}_R2_BOUNDS, + {}_P1_BOUNDS, + {}_P2_BOUNDS, + {}_MSG_BOUND,); +"#, + preset.dkg_counterpart().unwrap().metadata().degree, + preset.dkg_counterpart().unwrap().metadata().num_moduli, + qis_str, + prefix, + configs.bits.pk_bit, + prefix, + configs.bits.ct_bit, + prefix, + configs.bits.u_bit, + prefix, + configs.bits.e0_bit, + prefix, + configs.bits.e1_bit, + prefix, + configs.bits.msg_bit, + prefix, + configs.bits.r1_bit, + prefix, + configs.bits.r2_bit, + prefix, + configs.bits.p1_bit, + prefix, + configs.bits.p2_bit, + prefix, + configs.t, + prefix, + configs.q_mod_t, + prefix, + k0is_str, + prefix, + pk_bounds_str, + prefix, + configs.bounds.e0_bound, + prefix, + configs.bounds.e1_bound, + prefix, + configs.bounds.u_bound, + prefix, + r1_low_bounds_str, + prefix, + r1_up_bounds_str, + prefix, + r2_bounds_str, + prefix, + p1_bounds_str, + prefix, + p2_bounds_str, + prefix, + configs.bounds.msg_bound, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + ) +} + +#[cfg(test)] +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::dkg::share_encryption::sample::prepare_share_encryption_sample_for_test; + use crate::dkg::share_encryption::ShareEncryptionSample; + use crate::Circuit; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + + fn share_encryption_input_from_sample( + sample: &ShareEncryptionSample, + ) -> ShareEncryptionCircuitInput { + ShareEncryptionCircuitInput { + plaintext: sample.plaintext.clone(), + ciphertext: sample.ciphertext.clone(), + public_key: sample.public_key.clone(), + secret_key: sample.secret_key.clone(), + u_rns: sample.u_rns.clone(), + e0_rns: sample.e0_rns.clone(), + e1_rns: sample.e1_rns.clone(), + } + } + + #[test] + fn test_toml_generation_and_structure() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_encryption_input_from_sample(&sample); + + let artifacts = ShareEncryptionCircuit + .codegen(DEFAULT_BFV_PRESET, &input) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + assert!(parsed.get("message").is_some()); + assert!(parsed.get("pk0is").is_some()); + assert!(parsed.get("expected_pk_commitment").is_some()); + assert!(parsed.get("expected_message_commitment").is_some()); + } + + #[test] + fn test_configs_generation_contains_expected() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_encryption_input_from_sample(&sample); + + let artifacts = ShareEncryptionCircuit + .codegen(DEFAULT_BFV_PRESET, &input) + .unwrap(); + + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let bits = + crate::circuits::dkg::share_encryption::Bits::compute(DEFAULT_BFV_PRESET, &bounds) + .unwrap(); + let prefix = ::PREFIX; + + assert!(artifacts.configs.contains("ShareEncryptionConfigs")); + assert!(artifacts + .configs + .contains(format!("{}_BIT_PK: u32 = {}", prefix, bits.pk_bit).as_str())); + assert!(artifacts + .configs + .contains(format!("{}_BIT_MSG: u32 = {}", prefix, bits.msg_bit).as_str())); + assert!(artifacts.configs.contains("SHARE_ENCRYPTION_CONFIGS_E_SM")); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs new file mode 100644 index 0000000000..802a209409 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -0,0 +1,996 @@ +// 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 share-encryption circuit: configs, bounds, bit widths, and witness. +//! +//! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) plaintext, ciphertext, and encryption randomness. Witness values are +//! normalized for the ZKP field so the Noir circuit's range checks and commitment checks succeed. + +use crate::circuits::commitments::{ + compute_dkg_pk_commitment, compute_share_encryption_commitment_from_message, +}; +use std::ops::Deref; + +use crate::dkg::share_encryption::ShareEncryptionCircuit; +use crate::dkg::share_encryption::ShareEncryptionCircuitInput; +use crate::get_zkp_modulus; +use crate::polynomial_to_toml_json; +use crate::utils::{compute_msg_bit, compute_pk_bit}; +use crate::CircuitsErrors; +use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_polynomial::Polynomial; +use e3_polynomial::{center, reduce, CrtPolynomial}; +use fhe::bfv::SecretKey; +use fhe_math::rq::Poly; +use fhe_math::rq::Representation; +use fhe_math::zq::Modulus; +use itertools::izip; +use num_bigint::ToBigInt; +use num_bigint::{BigInt, BigUint}; +use num_traits::Zero; +use num_traits::{Signed, ToPrimitive}; +use rayon::iter::ParallelIterator; +use rayon::prelude::ParallelBridge; +use serde::{Deserialize, Serialize}; + +/// Output of [`CircuitComputation::compute`] for [`ShareEncryptionCircuit`]: bounds, bit widths, and witness. +#[derive(Debug)] +pub struct ShareEncryptionOutput { + /// Coefficient bounds used to derive bit widths. + pub bounds: Bounds, + /// Bit widths used by the Noir prover for packing. + pub bits: Bits, + /// Witness data for the share-encryption circuit. + pub witness: Witness, +} + +/// Implementation of [`CircuitComputation`] for [`ShareEncryptionCircuit`]. +impl CircuitComputation for ShareEncryptionCircuit { + type Preset = BfvPreset; + type Input = ShareEncryptionCircuitInput; + type Output = ShareEncryptionOutput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; + + Ok(ShareEncryptionOutput { + bounds, + bits, + witness, + }) + } +} + +/// Global configs for the share-encryption circuit: plaintext modulus, [q]_t, moduli, k0is, bits, and bounds. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Configs { + /// Plaintext modulus (as usize). + pub t: usize, + /// [q]_t reduced to ZKP field modulus. + pub q_mod_t: BigInt, + /// CRT moduli (one per limb). + pub moduli: Vec, + /// k0_i = [1/q_i]_t per modulus, for scaling in the circuit. + pub k0is: Vec, + pub bits: Bits, + pub bounds: Bounds, +} + +/// Bit widths used by the Noir prover (e.g. for packing coefficients). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + pub pk_bit: u32, + pub ct_bit: u32, + pub u_bit: u32, + pub e0_bit: u32, + pub e1_bit: u32, + pub msg_bit: u32, + pub r1_bit: u32, + pub r2_bit: u32, + pub p1_bit: u32, + pub p2_bit: u32, +} + +/// Coefficient bounds for polynomials (used to derive bit widths). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds { + pub u_bound: BigUint, + pub e0_bound: BigUint, + pub e1_bound: BigUint, + pub msg_bound: BigUint, + pub pk_bounds: Vec, + pub r1_low_bounds: Vec, + pub r1_up_bounds: Vec, + pub r2_bounds: Vec, + pub p1_bounds: Vec, + pub p2_bounds: Vec, +} + +/// Witness data for the share-encryption circuit: CRT limbs for pk, ct, randomness, and message. +/// +/// Coefficients are reduced to the ZKP field modulus for serialization. The circuit verifies +/// that the ciphertext and commitments match the public inputs. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + /// Public key and ciphertext polynomials in CRT form (per modulus). + pub pk0is: CrtPolynomial, + pub pk1is: CrtPolynomial, + pub ct0is: CrtPolynomial, + pub ct1is: CrtPolynomial, + pub r1is: CrtPolynomial, + pub r2is: CrtPolynomial, + pub p1is: CrtPolynomial, + pub p2is: CrtPolynomial, + pub e0is: CrtPolynomial, + pub e0_quotients: CrtPolynomial, + pub e0: Polynomial, + pub e1: Polynomial, + pub u: Polynomial, + pub message: Polynomial, + pub pk_commitment: BigInt, + pub msg_commitment: BigInt, +} + +impl Computation for Configs { + type Preset = BfvPreset; + type Input = ShareEncryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (_, dkg_params) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + + let moduli = dkg_params.moduli().to_vec(); + let ctx = dkg_params.ctx_at_level(0)?; + let modulus = BigInt::from(ctx.modulus().clone()); + let t = BigInt::from(dkg_params.plaintext()); + let p = get_zkp_modulus(); + + let q_mod_t = center(&reduce(&modulus, &t), &t); + let q_mod_t_mod_p = reduce(&q_mod_t, &p); + + let mut k0is: Vec = Vec::new(); + + for qi in ctx.moduli_operators() { + let k0qi = BigInt::from(qi.inv(qi.neg(dkg_params.plaintext())).ok_or_else(|| { + CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( + "Failed to calculate modulus inverse for k0qi".into(), + ))) + })?); + + k0is.push(k0qi.to_u64().unwrap_or(0)); + } + + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + + Ok(Configs { + t: dkg_params.plaintext() as usize, + q_mod_t: q_mod_t_mod_p, + moduli, + k0is, + bits, + bounds, + }) + } +} + +impl Computation for Bits { + type Preset = BfvPreset; + type Input = Bounds; + type Error = crate::utils::ZkHelpersUtilsError; + + fn compute(_: Self::Preset, input: &Self::Input) -> Result { + let max_pk_bound = input.pk_bounds.iter().max().unwrap(); + let max_r2_bound = input.r2_bounds.iter().max().unwrap(); + let max_p1_bound = input.p1_bounds.iter().max().unwrap(); + let max_p2_bound = input.p2_bounds.iter().max().unwrap(); + + let pk_bit = calculate_bit_width(BigInt::from(max_pk_bound.clone())); + let ct_bit = calculate_bit_width(BigInt::from(max_pk_bound.clone())); + let u_bit = calculate_bit_width(BigInt::from(input.u_bound.clone())); + let e0_bit = calculate_bit_width(BigInt::from(input.e0_bound.clone())); + let e1_bit = calculate_bit_width(BigInt::from(input.e1_bound.clone())); + let msg_bit = calculate_bit_width(BigInt::from(input.msg_bound.clone())); + let r1_bit = calculate_bit_width(BigInt::from( + input + .r1_low_bounds + .iter() + .chain(input.r1_up_bounds.iter()) + .max() + .unwrap() + .clone(), + )); + let r2_bit = calculate_bit_width(BigInt::from(max_r2_bound.clone())); + let p1_bit = calculate_bit_width(BigInt::from(max_p1_bound.clone())); + let p2_bit = calculate_bit_width(BigInt::from(max_p2_bound.clone())); + + Ok(Bits { + pk_bit, + ct_bit, + u_bit, + e0_bit, + e1_bit, + msg_bit, + r1_bit, + r2_bit, + p1_bit, + p2_bit, + }) + } +} + +impl Computation for Bounds { + type Preset = BfvPreset; + type Input = ShareEncryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, _: &Self::Input) -> Result { + let (_, dkg_params) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + + let n = BigInt::from(dkg_params.degree()); + let ctx = dkg_params.ctx_at_level(0)?; + + let t = BigInt::from(dkg_params.plaintext()); + + // CBD bound + let cbd_bound = (dkg_params.variance() * 2) as u64; + // Uniform bound + let uniform_bound = (dkg_params.get_error1_variance() * BigUint::from(3u32)) + .sqrt() + .to_bigint() + .ok_or_else(|| { + CircuitsErrors::Other("Failed to convert uniform bound to BigInt".into()) + })?; + + let u_bound = SecretKey::sk_bound() as u128; // u_bound is the same as sk_bound + + // e0 = e1 in the fhe.rs + let e0_bound: u128 = if dkg_params.get_error1_variance() <= &BigUint::from(16u32) { + cbd_bound as u128 + } else { + uniform_bound.to_u128().unwrap() + }; + let e1_bound = cbd_bound; // e1 = e2 in the fhe.rs + + // Message bound: message is in [0, t), so bound is t - 1 + let msg_bound = t.clone() - BigInt::from(1); + + let ptxt_up_bound = (t.clone() - BigInt::from(1)) / BigInt::from(2); + let ptxt_low_bound: BigInt = if (t.clone() % BigInt::from(2)) == BigInt::from(1) { + -1 * ptxt_up_bound.clone() + } else { + -1 * ptxt_up_bound.clone() - BigInt::from(1) + }; + + // Calculate bounds for each CRT basis + let _num_moduli = ctx.moduli().len(); + let mut pk_bounds: Vec = Vec::new(); + let mut r1_low_bounds: Vec = Vec::new(); + let mut r1_up_bounds: Vec = Vec::new(); + let mut r2_bounds: Vec = Vec::new(); + let mut p1_bounds: Vec = Vec::new(); + let mut p2_bounds: Vec = Vec::new(); + let mut moduli: Vec = Vec::new(); + let mut k0is: 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()); + + // Calculate k0qi for bounds + let k0qi = BigInt::from(qi.inv(qi.neg(dkg_params.plaintext())).ok_or_else(|| { + CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( + "Failed to calculate modulus inverse for k0qi".into(), + ))) + })?); + k0is.push(k0qi.to_u64().unwrap_or(0)); + + // PK and R2 bounds (same as qi_bound) + pk_bounds.push(qi_bound.clone()); + r2_bounds.push(qi_bound.clone()); + + let e0_bound_i = e0_bound % qi_bigint.clone(); + + // R1 bounds (more complex calculation) + let r1_low: BigInt = (&ptxt_low_bound * k0qi.abs() + - &((&n * u_bound + BigInt::from(2)) * &qi_bound + e0_bound_i.clone())) + / &qi_bigint; + let r1_up: BigInt = (&ptxt_up_bound * k0qi.abs() + + ((&n * u_bound + BigInt::from(2)) * &qi_bound + e0_bound_i.clone())) + / &qi_bigint; + + r1_low_bounds.push(BigInt::from(-1) * r1_low.clone()); + r1_up_bounds.push(r1_up.clone()); + + // P1 and P2 bounds + let p1_bound: BigInt = + ((&n * u_bound + BigInt::from(2)) * &qi_bound + e1_bound) / &qi_bigint; + p1_bounds.push(p1_bound.clone()); + p2_bounds.push(qi_bound.clone()); + } + + Ok(Bounds { + pk_bounds: pk_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + u_bound: BigUint::from(u_bound as u64), + e0_bound: BigUint::from(e0_bound), + e1_bound: BigUint::from(e1_bound), + msg_bound: BigUint::from(msg_bound.to_u128().unwrap()), + r1_low_bounds: r1_low_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + r1_up_bounds: r1_up_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(), + p1_bounds: p1_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + p2_bounds: p2_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + }) + } +} + +impl Computation for Witness { + type Preset = BfvPreset; + type Input = ShareEncryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (_, dkg_params) = + 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 msg_bit = compute_msg_bit(&dkg_params); + + let pk = input.public_key.clone(); + let pt = input.plaintext.clone(); + + // Reconstruct e0 in mod Q so that e0_poly row i matches e0_rns row i (same ctx). + let mut e0_power = input.e0_rns.clone(); + e0_power.change_representation(Representation::PowerBasis); + let e0_mod_q: Vec = Vec::::from(&e0_power); + let e0_bigints: Vec = e0_mod_q.iter().map(|c| c.to_bigint().unwrap()).collect(); + let e0 = (*Poly::from_bigints(&e0_bigints, &ctx) + .map_err(|e| CircuitsErrors::Other(e.to_string()))?) + .clone(); + + let t = Modulus::new(dkg_params.plaintext()) + .map_err(|e| CircuitsErrors::Fhe(fhe::Error::from(e)))?; + let n: u64 = ctx.degree as u64; + + let mut message = Polynomial::from_u64_vector(pt.value.deref().to_vec()); + message.reverse(); + + // k1[i] = (q_mod_t * message[i]) mod t, centered to [-t/2, t/2) + let q_mod_t = (ctx.modulus() % t.modulus()).to_u64().unwrap(); + let mut k1_u64: Vec = message + .coefficients() + .iter() + .map(|c| c.to_u64().unwrap()) + .collect(); + t.scalar_mul_vec(&mut k1_u64, q_mod_t); + + let mut k1 = Polynomial::from_u64_vector(k1_u64); + k1.center(&BigInt::from(t.modulus())); + + let mut u_rns_copy = input.u_rns.clone(); + let mut e0_rns_copy = input.e0_rns.clone(); + let mut e0_poly_copy = e0.clone(); + let mut e1_rns_copy = input.e1_rns.clone(); + + u_rns_copy.change_representation(Representation::PowerBasis); + e0_rns_copy.change_representation(Representation::PowerBasis); + e0_poly_copy.change_representation(Representation::PowerBasis); + e1_rns_copy.change_representation(Representation::PowerBasis); + + // Extract coefficients using the current API + let u: Vec = + unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(u_rns_copy.coefficients().row(0).as_slice().ok_or_else( + || CircuitsErrors::Other("Cannot center coefficients.".into()), + )?) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + let mut e0_vec = Polynomial::new(e0_bigints.clone()); + e0_vec.reverse(); + + // Center the coefficients mod Q + let q_bigint = BigInt::from(ctx.modulus().clone()); + + e0_vec.center(&q_bigint); + + let e1: Vec = + unsafe { + ctx.moduli_operators()[0] + .center_vec_vt(e1_rns_copy.coefficients().row(0).as_slice().ok_or_else( + || CircuitsErrors::Other("Cannot center coefficients.".into()), + )?) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + // Extract and convert ciphertext and public key polynomials + let mut ct0 = input.ciphertext.c[0].clone(); // ct0 + let mut ct1 = input.ciphertext.c[1].clone(); // ct1 + ct0.change_representation(Representation::PowerBasis); + ct1.change_representation(Representation::PowerBasis); + + let mut pk0: Poly = pk.c.c[0].clone(); + let mut pk1: Poly = pk.c.c[1].clone(); + pk0.change_representation(Representation::PowerBasis); + pk1.change_representation(Representation::PowerBasis); + + // 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 + + let ct0_coeffs = ct0.coefficients(); + let ct1_coeffs = ct1.coefficients(); + let pk0_coeffs = pk0.coefficients(); + let pk1_coeffs = pk1.coefficients(); + let e0_coeffs = e0_rns_copy.coefficients(); + let e0_poly_coeffs = e0_poly_copy.coefficients(); + + let ct0_coeffs_rows = ct0_coeffs.rows(); + let ct1_coeffs_rows = ct1_coeffs.rows(); + let pk0_coeffs_rows = pk0_coeffs.rows(); + let pk1_coeffs_rows = pk1_coeffs.rows(); + let e0_coeffs_rows = e0_coeffs.rows(); + let e0_poly_coeffs_rows = e0_poly_coeffs.rows(); + + // Perform the main computation logic + let results: Vec<_> = izip!( + ctx.moduli_operators(), + ct0_coeffs_rows, + ct1_coeffs_rows, + pk0_coeffs_rows, + pk1_coeffs_rows, + e0_coeffs_rows, + e0_poly_coeffs_rows, + ) + .enumerate() + .par_bridge() + .map( + |( + i, + (qi, ct0_coeffs, ct1_coeffs, pk0_coeffs, pk1_coeffs, e0_coeffs, e0_poly_coeffs), + )| { + // --------------------------------------------------- ct0i --------------------------------------------------- + + // Convert to vectors of bigint, center, and reverse order. + let mut ct0i = Polynomial::from_u64_vector(ct0_coeffs.to_vec()); + let mut ct1i = Polynomial::from_u64_vector(ct1_coeffs.to_vec()); + let mut pk0i = Polynomial::from_u64_vector(pk0_coeffs.to_vec()); + let mut pk1i = Polynomial::from_u64_vector(pk1_coeffs.to_vec()); + + ct0i.reverse(); + ct1i.reverse(); + pk0i.reverse(); + pk1i.reverse(); + + let qi_bigint = BigInt::from(qi.modulus()); + + ct0i.reduce(&qi_bigint); + ct0i.center(&qi_bigint); + ct1i.reduce(&qi_bigint); + ct1i.center(&qi_bigint); + pk0i.reduce(&qi_bigint); + pk0i.center(&qi_bigint); + pk1i.reduce(&qi_bigint); + pk1i.center(&qi_bigint); + + let e0i: Vec = unsafe { + qi.center_vec_vt( + e0_coeffs + .as_slice() + .ok_or_else(|| "Cannot center coefficients.".to_string()) + .unwrap(), + ) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + // Explicitly check e1is[i] == e1 mod qi (after centering and reversal) + let e0i_from_poly: Vec = unsafe { + qi.center_vec_vt( + e0_poly_coeffs + .as_slice() + .ok_or_else(|| "Cannot center coefficients.".to_string()) + .unwrap(), + ) + .iter() + .rev() + .map(|&x| BigInt::from(x)) + .collect() + }; + + // Check that e0i equals e0 reduced modulo q_i (from e0_poly) + assert_eq!(e0i, e0i_from_poly); + + // Compute e0_quotients[i] = (e0 - e0i) / qi for each coefficient + // This is used for CRT consistency check: e0[j] = e0i[j] + e0_quotients[i][j] * qi + let e0_quotient: Vec = e0_vec + .coefficients() + .iter() + .zip(e0i.iter()) + .map(|(e0_coeff, e0i_coeff)| { + let diff = e0_coeff - e0i_coeff; + // Division should be exact since e0 = e0i (mod qi) + let quotient = &diff / &qi_bigint; + // Verify the CRT relationship + assert_eq!(e0_coeff, &(e0i_coeff + "ient * &qi_bigint)); + quotient + }) + .collect(); + + // k0qi = -t^{-1} mod qi + let koqi_u64 = qi.inv(qi.neg(t.modulus())).unwrap(); + let k0qi = BigInt::from(koqi_u64); // Do not need to center this + + // ki = k1 * k0qi + let ki_poly = Polynomial::new(k1.coefficients().to_vec()).scalar_mul(&k0qi); + let ki = ki_poly.coefficients().to_vec(); + + // Calculate ct0i_hat = pk0 * ui + e0i + ki + let ct0i_hat = { + let pk0i_poly = pk0i.clone(); + let u_poly = Polynomial::new(u.clone()); + let pk0i_times_u = pk0i_poly.mul(&u_poly); + assert_eq!((pk0i_times_u.coefficients().len() as u64) - 1, 2 * (n - 1)); + + let e0i_poly = Polynomial::new(e0i.clone()); + let ki_poly = Polynomial::new(ki.clone()); + let e0_plus_ki = e0i_poly.add(&ki_poly); + assert_eq!((e0_plus_ki.coefficients().len() as u64) - 1, n - 1); + + pk0i_times_u.add(&e0_plus_ki).coefficients().to_vec() + }; + assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (n - 1)); + + // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i + let mut ct0i_hat_mod_rqi = Polynomial::new(ct0i_hat.clone()); + + ct0i_hat_mod_rqi = ct0i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); + + ct0i_hat_mod_rqi.reduce(&qi_bigint); + ct0i_hat_mod_rqi.center(&qi_bigint); + + assert_eq!(&ct0i, &ct0i_hat_mod_rqi); + + // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial + let ct0i_poly = ct0i.clone(); + let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); + let ct0i_minus_ct0i_hat = ct0i_poly.sub(&ct0i_hat_poly).coefficients().to_vec(); + assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (n - 1)); + + let mut ct0i_minus_ct0i_hat_mod_zqi = Polynomial::new(ct0i_minus_ct0i_hat.clone()); + + ct0i_minus_ct0i_hat_mod_zqi.reduce(&qi_bigint); + ct0i_minus_ct0i_hat_mod_zqi.center(&qi_bigint); + + // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial + // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let ct0i_minus_ct0i_hat_poly = ct0i_minus_ct0i_hat_mod_zqi.clone(); + let cyclo_poly = Polynomial::new(cyclo.clone()); + let (r2i_poly, r2i_rem_poly) = ct0i_minus_ct0i_hat_poly.div(&cyclo_poly).unwrap(); + let r2i = r2i_poly.coefficients().to_vec(); + let r2i_rem = r2i_rem_poly.coefficients().to_vec(); + assert!(r2i_rem.iter().all(|x| x.is_zero())); + assert_eq!((r2i.len() as u64) - 1, n - 2); // Order(r2i) = N - 2 + + // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi + let r2i_poly = Polynomial::new(r2i.clone()); + let r2i_times_cyclo = r2i_poly.mul(&cyclo_poly).coefficients().to_vec(); + + let mut r2i_times_cyclo_mod_zqi = Polynomial::new(r2i_times_cyclo.clone()); + + r2i_times_cyclo_mod_zqi.reduce(&qi_bigint); + r2i_times_cyclo_mod_zqi.center(&qi_bigint); + + assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); + assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); + + // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. + let ct0i_minus_ct0i_hat_poly = Polynomial::new(ct0i_minus_ct0i_hat.clone()); + let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); + let r1i_num = ct0i_minus_ct0i_hat_poly + .sub(&r2i_times_cyclo_poly) + .coefficients() + .to_vec(); + assert_eq!((r1i_num.len() as u64) - 1, 2 * (n - 1)); + + let r1i_num_poly = Polynomial::new(r1i_num.clone()); + let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); + let (r1i_poly, r1i_rem_poly) = r1i_num_poly.div(&qi_poly).unwrap(); + let r1i = r1i_poly.coefficients().to_vec(); + let r1i_rem = r1i_rem_poly.coefficients().to_vec(); + assert!(r1i_rem.iter().all(|x| x.is_zero())); + assert_eq!((r1i.len() as u64) - 1, 2 * (n - 1)); // Order(r1i) = 2*(N-1) + let r1i_poly_check = Polynomial::new(r1i.clone()); + assert_eq!( + &r1i_num, + &r1i_poly_check.mul(&qi_poly).coefficients().to_vec() + ); + + // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p + let r1i_poly = Polynomial::new(r1i.clone()); + let r1i_times_qi = r1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); + let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); + let r1i_times_qi_poly = Polynomial::new(r1i_times_qi.clone()); + let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); + let mut ct0i_calculated = ct0i_hat_poly + .add(&r1i_times_qi_poly) + .add(&r2i_times_cyclo_poly) + .coefficients() + .to_vec(); + + while !ct0i_calculated.is_empty() && ct0i_calculated[0].is_zero() { + ct0i_calculated.remove(0); + } + + assert_eq!(&ct0i, &Polynomial::new(ct0i_calculated.clone())); + + // --------------------------------------------------- ct1i --------------------------------------------------- + + // Calculate ct1i_hat = pk1i * ui + e1 + let ct1i_hat = { + let pk1i_poly = pk1i.clone(); + let u_poly = Polynomial::new(u.clone()); + let pk1i_times_u = pk1i_poly.mul(&u_poly); + assert_eq!((pk1i_times_u.coefficients().len() as u64) - 1, 2 * (n - 1)); + + let e1_poly = Polynomial::new(e1.clone()); + pk1i_times_u.add(&e1_poly).coefficients().to_vec() + }; + assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (n - 1)); + + // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i + let mut ct1i_hat_mod_rqi = Polynomial::new(ct1i_hat.clone()); + + ct1i_hat_mod_rqi = ct1i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); + ct1i_hat_mod_rqi.reduce(&qi_bigint); + ct1i_hat_mod_rqi.center(&qi_bigint); + + assert_eq!(&ct1i, &ct1i_hat_mod_rqi); + + // Compute p2i numerator = ct1i - ct1i_hat + let ct1i_poly = ct1i.clone(); + let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); + let ct1i_minus_ct1i_hat = ct1i_poly.sub(&ct1i_hat_poly).coefficients().to_vec(); + assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (n - 1)); + let mut ct1i_minus_ct1i_hat_mod_zqi = Polynomial::new(ct1i_minus_ct1i_hat.clone()); + + ct1i_minus_ct1i_hat_mod_zqi.reduce(&qi_bigint); + ct1i_minus_ct1i_hat_mod_zqi.center(&qi_bigint); + + // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, + // and reduce/center the resulting coefficients to produce: + // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. + let ct1i_minus_ct1i_hat_poly = ct1i_minus_ct1i_hat_mod_zqi.clone(); + let (p2i_poly, p2i_rem_poly) = + ct1i_minus_ct1i_hat_poly.div(&cyclo_poly.clone()).unwrap(); + let p2i = p2i_poly.coefficients().to_vec(); + let p2i_rem = p2i_rem_poly.coefficients().to_vec(); + assert!(p2i_rem.iter().all(|x| x.is_zero())); + assert_eq!((p2i.len() as u64) - 1, n - 2); // Order(p2i) = N - 2 + + // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi + let p2i_poly = Polynomial::new(p2i.clone()); + let p2i_times_cyclo: Vec = + p2i_poly.mul(&cyclo_poly).coefficients().to_vec(); + let mut p2i_times_cyclo_mod_zqi = Polynomial::new(p2i_times_cyclo.clone()); + + p2i_times_cyclo_mod_zqi.reduce(&qi_bigint); + p2i_times_cyclo_mod_zqi.center(&qi_bigint); + + assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); + assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); + + // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. + let ct1i_minus_ct1i_hat_poly = Polynomial::new(ct1i_minus_ct1i_hat.clone()); + let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); + let p1i_num = ct1i_minus_ct1i_hat_poly + .sub(&p2i_times_cyclo_poly) + .coefficients() + .to_vec(); + assert_eq!((p1i_num.len() as u64) - 1, 2 * (n - 1)); + + let p1i_num_poly = Polynomial::new(p1i_num.clone()); + let qi_poly = Polynomial::new(vec![BigInt::from(qi.modulus())]); + let (p1i_poly, p1i_rem_poly) = p1i_num_poly.div(&qi_poly).unwrap(); + let p1i = p1i_poly.coefficients().to_vec(); + let p1i_rem = p1i_rem_poly.coefficients().to_vec(); + assert!(p1i_rem.iter().all(|x| x.is_zero())); + assert_eq!((p1i.len() as u64) - 1, 2 * (n - 1)); // Order(p1i) = 2*(N-1) + let p1i_poly_check = Polynomial::new(p1i.clone()); + assert_eq!( + &p1i_num, + &p1i_poly_check.mul(&qi_poly).coefficients().to_vec() + ); + + // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p + let p1i_poly = Polynomial::new(p1i.clone()); + let p1i_times_qi = p1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); + let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); + let p1i_times_qi_poly = Polynomial::new(p1i_times_qi.clone()); + let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); + let mut ct1i_calculated = ct1i_hat_poly + .add(&p1i_times_qi_poly) + .add(&p2i_times_cyclo_poly) + .coefficients() + .to_vec(); + + while !ct1i_calculated.is_empty() && ct1i_calculated[0].is_zero() { + ct1i_calculated.remove(0); + } + + assert_eq!(&ct1i, &Polynomial::new(ct1i_calculated.clone())); + ( + i, + r2i, + r1i, + k0qi, + ct0i, + ct1i, + pk0i, + pk1i, + p1i, + p2i, + e0i, + e0_quotient, + ) + }, + ) + .collect(); + + // Sort by modulus index so CRT limbs are in order + let mut results = results.clone(); + results.sort_by_key(|(i, ..)| *i); + + // results elements: (i, r2i, r1i, k0qi, ct0i, ct1i, pk0i, pk1i, p1i, p2i, e0i, e0_quotient) + let mut pk0is = CrtPolynomial::from_bigint_vectors( + results + .iter() + .map(|row| row.6.clone()) + .map(|pk0i| pk0i.coefficients().to_vec()) + .collect(), + ); + let mut pk1is = CrtPolynomial::from_bigint_vectors( + results + .iter() + .map(|row| row.7.clone()) + .map(|pk1i| pk1i.coefficients().to_vec()) + .collect(), + ); + let mut ct0is = CrtPolynomial::from_bigint_vectors( + results + .iter() + .map(|row| row.4.clone()) + .map(|ct0i| ct0i.coefficients().to_vec()) + .collect(), + ); + let mut ct1is = CrtPolynomial::from_bigint_vectors( + results + .iter() + .map(|row| row.5.clone()) + .map(|ct1i| ct1i.coefficients().to_vec()) + .collect(), + ); + let mut r1is = + CrtPolynomial::from_bigint_vectors(results.iter().map(|row| row.2.clone()).collect()); + let mut r2is = + CrtPolynomial::from_bigint_vectors(results.iter().map(|row| row.1.clone()).collect()); + let mut p1is = + CrtPolynomial::from_bigint_vectors(results.iter().map(|row| row.8.clone()).collect()); + let mut p2is = + CrtPolynomial::from_bigint_vectors(results.iter().map(|row| row.9.clone()).collect()); + let mut e0is = + CrtPolynomial::from_bigint_vectors(results.iter().map(|row| row.10.clone()).collect()); + let mut e0_quotients = + CrtPolynomial::from_bigint_vectors(results.iter().map(|row| row.11.clone()).collect()); + + let mut e1 = Polynomial::new(e1); + let mut u = Polynomial::new(u); + + let zkp_modulus = get_zkp_modulus(); + + pk0is.reduce_uniform(&zkp_modulus); + pk1is.reduce_uniform(&zkp_modulus); + ct0is.reduce_uniform(&zkp_modulus); + ct1is.reduce_uniform(&zkp_modulus); + r1is.reduce_uniform(&zkp_modulus); + r2is.reduce_uniform(&zkp_modulus); + p1is.reduce_uniform(&zkp_modulus); + p2is.reduce_uniform(&zkp_modulus); + e0is.reduce_uniform(&zkp_modulus); + e0_quotients.reduce_uniform(&zkp_modulus); + e1.reduce(&zkp_modulus); + u.reduce(&zkp_modulus); + e0_vec.reduce(&zkp_modulus); + k1.reduce(&zkp_modulus); + + let pk_commitment = compute_dkg_pk_commitment(&pk0is, &pk1is, pk_bit); + let msg_commitment = compute_share_encryption_commitment_from_message(&message, msg_bit); + + Ok(Witness { + pk0is, + pk1is, + ct0is, + ct1is, + r1is, + r2is, + p1is, + p2is, + e0is, + e0_quotients, + e0: e0_vec, + e1, + u, + message, + pk_commitment, + msg_commitment, + }) + } + + // Used as witness for Nargo execution. + fn to_json(&self) -> serde_json::Result { + let pk0is = crt_polynomial_to_toml_json(&self.pk0is); + let pk1is = crt_polynomial_to_toml_json(&self.pk1is); + let ct0is = crt_polynomial_to_toml_json(&self.ct0is); + let ct1is = crt_polynomial_to_toml_json(&self.ct1is); + let u = polynomial_to_toml_json(&self.u); + let e0 = polynomial_to_toml_json(&self.e0); + let e0is = crt_polynomial_to_toml_json(&self.e0is); + let e0_quotients = crt_polynomial_to_toml_json(&self.e0_quotients); + let e1 = polynomial_to_toml_json(&self.e1); + let message = polynomial_to_toml_json(&self.message); + let r1is = crt_polynomial_to_toml_json(&self.r1is); + let r2is = crt_polynomial_to_toml_json(&self.r2is); + let p1is = crt_polynomial_to_toml_json(&self.p1is); + let p2is = crt_polynomial_to_toml_json(&self.p2is); + let pk_commitment = self.pk_commitment.to_string(); + let msg_commitment = self.msg_commitment.to_string(); + + let json = serde_json::json!({ + "pk0is": pk0is, + "pk1is": pk1is, + "ct0is": ct0is, + "ct1is": ct1is, + "u": u, + "e0": e0, + "e0is": e0is, + "e0_quotients": e0_quotients, + "e1": e1, + "message": message, + "r1is": r1is, + "r2is": r2is, + "p1is": p1is, + "p2is": p2is, + "expected_pk_commitment": pk_commitment, + "expected_message_commitment": msg_commitment, + }); + + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use crate::dkg::share_encryption::sample::prepare_share_encryption_sample_for_test; + use crate::dkg::share_encryption::ShareEncryptionSample; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + + fn share_encryption_input_from_sample( + sample: &ShareEncryptionSample, + ) -> ShareEncryptionCircuitInput { + ShareEncryptionCircuitInput { + plaintext: sample.plaintext.clone(), + ciphertext: sample.ciphertext.clone(), + public_key: sample.public_key.clone(), + secret_key: sample.secret_key.clone(), + u_rns: sample.u_rns.clone(), + e0_rns: sample.e0_rns.clone(), + e1_rns: sample.e1_rns.clone(), + } + } + + #[test] + fn test_bound_and_bits_computation_consistency() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_encryption_input_from_sample(&sample); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + + let max_pk_bound = bounds.pk_bounds.iter().max().unwrap(); + let expected_bits = calculate_bit_width(BigInt::from(max_pk_bound.clone())); + + assert_eq!(max_pk_bound.clone(), BigUint::from(1125899906777088u128)); + assert_eq!(bits.pk_bit, expected_bits); + } + + #[test] + fn test_constants_json_roundtrip() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_encryption_input_from_sample(&sample); + let constants = Configs::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + + let json = constants.to_json().unwrap(); + let decoded: Configs = serde_json::from_value(json).unwrap(); + + assert_eq!(decoded.t, constants.t); + assert_eq!(decoded.q_mod_t, constants.q_mod_t); + assert_eq!(decoded.moduli, constants.moduli); + assert_eq!(decoded.k0is, constants.k0is); + assert_eq!(decoded.bits, constants.bits); + assert_eq!(decoded.bounds, constants.bounds); + } + + #[test] + fn test_witness_message_consistency() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_encryption_input_from_sample(&sample); + let witness = Witness::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + + // witness.message is plaintext coefficients (reversed, as used in circuit) + let expected_message = Polynomial::from_u64_vector(sample.plaintext.value.deref().to_vec()); + let mut expected = expected_message; + expected.reverse(); + + assert_eq!(witness.message.coefficients(), expected.coefficients()); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs new file mode 100644 index 0000000000..3446225c16 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs @@ -0,0 +1,15 @@ +// 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. + +//! Share-encryption circuit: proves correct encryption of a share under the DKG public key (CIRCUIT 3a/3b). + +pub mod circuit; +pub mod codegen; +pub mod computation; +pub mod sample; +pub use circuit::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; +pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; +pub use sample::{prepare_share_encryption_sample_for_test, ShareEncryptionSample}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs new file mode 100644 index 0000000000..2d6c639fe7 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -0,0 +1,186 @@ +// 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 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::computation::DkgInputType; +use crate::CircuitsErrors; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use fhe::bfv::Ciphertext; +use fhe::bfv::Encoding; +use fhe::bfv::Plaintext; +use fhe::bfv::{PublicKey, SecretKey}; +use fhe::trbfv::{ShareManager, TRBFV}; +use fhe_math::rq::Poly; +use fhe_traits::FheEncoder; +use rand::thread_rng; + +/// Sample data for the share-encryption circuit: plaintext, ciphertext, keys, and RNS randomness. +#[derive(Debug, Clone)] +pub struct ShareEncryptionSample { + pub plaintext: Plaintext, + pub ciphertext: Ciphertext, + pub public_key: PublicKey, + pub secret_key: SecretKey, + pub u_rns: Poly, + pub e0_rns: Poly, + pub e1_rns: Poly, +} + +impl ShareEncryptionSample { + /// Generates sample data for the share-encryption circuit (encrypts a share row under DKG pk). + pub fn generate( + preset: BfvPreset, + committee_size: CiphernodesCommitteeSize, + dkg_input_type: DkgInputType, + num_ciphertexts: u128, // z in the search defaults + lambda: u32, + ) -> Self { + 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); + + let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) + .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); + let mut share_manager = + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + + let share_row = match dkg_input_type { + DkgInputType::SecretKey => { + let threshold_secret_key = SecretKey::random(&threshold_params, &mut rng); + + let sk_poly = share_manager + .coeffs_to_poly_level0(threshold_secret_key.coeffs.clone().as_ref()) + .unwrap(); + + let sk_sss_u64 = share_manager + .generate_secret_shares_from_poly(sk_poly.clone(), &mut rng) + .unwrap(); + + sk_sss_u64[0].row(0).to_vec() + } + DkgInputType::SmudgingNoise => { + let esi_coeffs = trbfv + .generate_smudging_error(num_ciphertexts as usize, lambda as usize, &mut rng) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate smudging error: {:?}", + e + )) + }) + .unwrap(); + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).unwrap(); + let esi_sss_u64 = share_manager + .generate_secret_shares_from_poly(esi_poly.clone(), &mut rng.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to generate error shares: {:?}", e)) + }) + .unwrap(); + + esi_sss_u64[0].row(0).to_vec() + } + }; + + let pt = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params).unwrap(); + + let (_ct, u_rns, e0_rns, e1_rns) = + dkg_public_key.try_encrypt_extended(&pt, &mut rng).unwrap(); + + ShareEncryptionSample { + plaintext: pt, + ciphertext: _ct, + public_key: dkg_public_key, + secret_key: dkg_secret_key, + u_rns, + e0_rns, + e1_rns, + } + } +} + +/// Prepares a share-encryption sample for testing using a threshold preset. +pub fn prepare_share_encryption_sample_for_test( + preset: BfvPreset, + committee: CiphernodesCommitteeSize, + dkg_input_type: DkgInputType, +) -> ShareEncryptionSample { + let defaults = preset.search_defaults().unwrap(); + + ShareEncryptionSample::generate( + preset, + committee, + dkg_input_type, + defaults.z, + defaults.lambda, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use e3_fhe_params::{BfvPreset, DEFAULT_BFV_PRESET}; + + #[test] + fn test_generate_secret_key_sample() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + + assert_eq!(sample.public_key.c.c.len(), 2); + assert_eq!( + sample.plaintext.value.len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!(sample.ciphertext.c.len(), 2); + assert_eq!( + sample.u_rns.coefficients().len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!( + sample.e0_rns.coefficients().len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!( + sample.e1_rns.coefficients().len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + } + + #[test] + fn test_generate_smudging_noise_sample() { + let sample = prepare_share_encryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SmudgingNoise, + ); + + assert_eq!(sample.public_key.c.c.len(), 2); + assert_eq!(sample.ciphertext.c.len(), 2); + assert_eq!( + sample.u_rns.coefficients().len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!( + sample.e0_rns.coefficients().len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!( + sample.e1_rns.coefficients().len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + } +} diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index ba3e085af8..3c27b4cf43 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -166,6 +166,19 @@ pub fn compute_pk_bit(params: &BfvParameters) -> u32 { calculate_bit_width(bound) } +/// Computes the bit width of the message. +/// +/// # Arguments +/// * `params` - BFV parameters +/// +/// # Returns +/// The bit width of the message +pub fn compute_msg_bit(params: &BfvParameters) -> u32 { + let t = BigInt::from(params.plaintext()); + let bound = t.clone() - BigInt::from(1); + calculate_bit_width(bound) +} + /// Get the ZKP modulus as a BigInt. /// /// The ZKP modulus is the BN254 scalar field modulus: From 7f166bca021bcf3bf64c5d11574eaa74b628a317 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 5 Feb 2026 17:22:38 +0100 Subject: [PATCH 02/10] port of c4 --- .../bin/dkg/e_sm_share_decryption/src/main.nr | 4 +- .../bin/dkg/sk_share_decryption/src/main.nr | 4 +- circuits/lib/src/configs/insecure/dkg.nr | 15 +- circuits/lib/src/configs/secure/dkg.nr | 15 +- crates/zk-helpers/src/bin/zk_cli.rs | 27 +- crates/zk-helpers/src/circuits/dkg/mod.rs | 1 + .../circuits/dkg/share_decryption/circuit.rs | 33 ++ .../circuits/dkg/share_decryption/codegen.rs | 135 ++++++++ .../dkg/share_decryption/computation.rs | 292 ++++++++++++++++++ .../src/circuits/dkg/share_decryption/mod.rs | 15 + .../circuits/dkg/share_decryption/sample.rs | 207 +++++++++++++ .../circuits/dkg/share_encryption/circuit.rs | 2 +- crates/zk-helpers/src/utils.rs | 14 + 13 files changed, 734 insertions(+), 30 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs diff --git a/circuits/bin/dkg/e_sm_share_decryption/src/main.nr b/circuits/bin/dkg/e_sm_share_decryption/src/main.nr index d7806ef1d5..dc09eb6365 100644 --- a/circuits/bin/dkg/e_sm_share_decryption/src/main.nr +++ b/circuits/bin/dkg/e_sm_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::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG_E_SM}; +use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG}; use lib::configs::default::H; use lib::core::dkg::share_decryption::ShareDecryption; use lib::math::polynomial::Polynomial; @@ -13,7 +13,7 @@ fn main( expected_commitments: pub [[Field; L_THRESHOLD]; H], decrypted_shares: [[Polynomial; L_THRESHOLD]; H], ) -> pub Field { - let share_decryption: ShareDecryption = + let share_decryption: ShareDecryption = ShareDecryption::new(expected_commitments, decrypted_shares); share_decryption.execute() diff --git a/circuits/bin/dkg/sk_share_decryption/src/main.nr b/circuits/bin/dkg/sk_share_decryption/src/main.nr index 6a894eaee2..dc09eb6365 100644 --- a/circuits/bin/dkg/sk_share_decryption/src/main.nr +++ b/circuits/bin/dkg/sk_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::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG_SK}; +use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG}; use lib::configs::default::H; use lib::core::dkg::share_decryption::ShareDecryption; use lib::math::polynomial::Polynomial; @@ -13,7 +13,7 @@ fn main( expected_commitments: pub [[Field; L_THRESHOLD]; H], decrypted_shares: [[Polynomial; L_THRESHOLD]; H], ) -> pub Field { - let share_decryption: ShareDecryption = + let share_decryption: ShareDecryption = ShareDecryption::new(expected_commitments, decrypted_shares); share_decryption.execute() diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 06f8218579..4592a609bf 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -142,18 +142,9 @@ pub global SHARE_ENCRYPTION_CONFIGS_E_SM: ShareEncryptionConfigs = ShareEncry /************************************ ------------------------------------- -share_decryption_sk (CIRCUIT 4a) +share_decryption_sk (CIRCUIT 4a - BFV DECRYPTION SK) +share_decryption_e_sm (CIRCUIT 4b - BFV DECRYPTION E_SM) ------------------------------------- ************************************/ -// share_decryption_sk - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_SK: u32 = 37; - -/************************************ -------------------------------------- -share_decryption_e_sm (CIRCUIT 4b) -------------------------------------- -************************************/ - -// share_decryption_e_sm - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_E_SM: u32 = 37; +pub global SHARE_DECRYPTION_BIT_MSG: u32 = 36; diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index 2a8f97f23f..7a0d43747d 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -154,18 +154,9 @@ pub global SHARE_ENCRYPTION_CONFIGS_E_SM: ShareEncryptionConfigs = ShareEncry /************************************ ------------------------------------- -share_decryption_sk (CIRCUIT 4a) +share_decryption_sk (CIRCUIT 4a - BFV DECRYPTION SK) +share_decryption_e_sm (CIRCUIT 4b - BFV DECRYPTION E_SM) ------------------------------------- ************************************/ -// share_decryption_sk - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_SK: u32 = 56; - -/************************************ -------------------------------------- -share_decryption_e_sm (CIRCUIT 4b) -------------------------------------- -************************************/ - -// share_decryption_e_sm - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_E_SM: u32 = 56; +pub global SHARE_DECRYPTION_BIT_MSG: u32 = 55; diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 947de61b98..6afefc6f61 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -20,6 +20,9 @@ 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, ShareDecryptionSample, +}; use e3_zk_helpers::dkg::share_encryption::{ ShareEncryptionCircuit, ShareEncryptionCircuitInput, ShareEncryptionSample, }; @@ -157,6 +160,7 @@ fn main() -> Result<()> { registry.register(Arc::new(ShareComputationCircuit)); registry.register(Arc::new(UserDataEncryptionCircuit)); registry.register(Arc::new(ShareEncryptionCircuit)); + registry.register(Arc::new(ShareDecryptionCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -206,7 +210,8 @@ fn main() -> Result<()> { let write_prover_toml = args.toml; // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME - || circuit_meta.name() == ShareEncryptionCircuit::NAME; + || circuit_meta.name() == ShareEncryptionCircuit::NAME + || circuit_meta.name() == ShareDecryptionCircuit::NAME; let dkg_input_type = if has_witness_type { // Share-computation: require --witness when generating Prover.toml; default secret-key for configs-only. @@ -316,6 +321,26 @@ fn main() -> Result<()> { }, )? } + name if name == ::NAME => { + let sd = preset + .search_defaults() + .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; + let sample = ShareDecryptionSample::generate( + preset, + CiphernodesCommitteeSize::Small, + dkg_input_type, + sd.z, + sd.lambda, + ); + let circuit = ShareDecryptionCircuit; + circuit.codegen( + preset, + &ShareDecryptionCircuitInput { + secret_key: sample.secret_key, + honest_ciphertexts: sample.honest_ciphertexts, + }, + )? + } name => return Err(anyhow!("circuit {} not yet implemented", name)), }; diff --git a/crates/zk-helpers/src/circuits/dkg/mod.rs b/crates/zk-helpers/src/circuits/dkg/mod.rs index deb387206b..364bd7aa79 100644 --- a/crates/zk-helpers/src/circuits/dkg/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/mod.rs @@ -6,4 +6,5 @@ pub mod pk; pub mod share_computation; +pub mod share_decryption; pub mod share_encryption; diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs new file mode 100644 index 0000000000..bec0bf27d1 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs @@ -0,0 +1,33 @@ +// 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. + +//! Circuit definition and input type for the share-decryption ZK circuit (CIRCUIT 4a/4b). + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use e3_fhe_params::ParameterType; +use fhe::bfv::Ciphertext; +use fhe::bfv::SecretKey; + +/// Share-decryption circuit: proves correct decryption of H honest parties' ciphertexts under the DKG secret key. +#[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::DKG; + /// None: circuit accepts runtime-varying input type (SecretKey or SmudgingNoise). + const DKG_INPUT_TYPE: Option = None; +} + +/// Input to the share-decryption circuit: secret key and honest parties' ciphertexts. +pub struct ShareDecryptionCircuitInput { + /// DKG secret key used to decrypt (private witness). + pub secret_key: SecretKey, + /// Ciphertexts from H honest parties: [party_idx][mod_idx] (one ciphertext per party per TRBFV modulus). + pub honest_ciphertexts: Vec>, +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs new file mode 100644 index 0000000000..8535b8ae68 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs @@ -0,0 +1,135 @@ +// 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 share-decryption BFV circuit: Prover.toml and configs.nr. + +use crate::circuits::computation::CircuitComputation; +use crate::circuits::dkg::share_decryption::Configs; +use crate::circuits::dkg::share_decryption::ShareDecryptionCircuit; +use crate::circuits::dkg::share_decryption::ShareDecryptionCircuitInput; +use crate::circuits::dkg::share_decryption::ShareDecryptionOutput; +use crate::circuits::dkg::share_decryption::Witness; +use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; +use crate::codegen::CodegenConfigs; +use crate::computation::Computation; +use crate::registry::Circuit; +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 ShareDecryptionOutput { witness, .. } = ShareDecryptionCircuit::compute(preset, input)?; + + let toml = generate_toml(&witness)?; + let configs = Configs::compute(preset, input)?; + let configs_str = generate_configs(preset, &configs); + + Ok(Artifacts { + toml, + configs: configs_str, + }) + } +} + +/// Serializes the witness to TOML string for the Noir prover (Prover.toml). +pub fn generate_toml(witness: &Witness) -> Result { + let json = witness + .to_json() + .map_err(|e| CircuitsErrors::SerdeJson(e))?; + + Ok(toml::to_string(&json)?) +} + +/// Builds the configs.nr string (N, L, bit parameters, and ShareDecryptionConfigs) for the Noir prover. +pub fn generate_configs(preset: BfvPreset, configs: &Configs) -> CodegenConfigs { + let prefix = ::PREFIX; + + format!( + r#"pub global N: u32 = {}; +pub global L: u32 = {}; + +/************************************ +------------------------------------- +share_decryption_sk (CIRCUIT 4a - BFV DECRYPTION SK) +share_decryption_e_sm (CIRCUIT 4b - BFV DECRYPTION E_SM) +------------------------------------- +************************************/ + +pub global {}_BIT_MSG: u32 = {}; +"#, + preset.dkg_counterpart().unwrap().metadata().degree, + preset.dkg_counterpart().unwrap().metadata().num_moduli, + prefix, + configs.bits.msg_bit, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::circuits::dkg::share_decryption::{Configs, ShareDecryptionCircuitInput}; + use crate::computation::{Computation, DkgInputType}; + use crate::dkg::share_decryption::sample::prepare_share_decryption_sample_for_test; + use crate::dkg::share_decryption::ShareDecryptionSample; + use crate::Circuit; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + + fn share_decryption_input_from_sample( + sample: &ShareDecryptionSample, + ) -> ShareDecryptionCircuitInput { + ShareDecryptionCircuitInput { + secret_key: sample.secret_key.clone(), + honest_ciphertexts: sample.honest_ciphertexts.clone(), + } + } + + #[test] + fn test_toml_generation_and_structure() { + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_decryption_input_from_sample(&sample); + + let artifacts = ShareDecryptionCircuit + .codegen(DEFAULT_BFV_PRESET, &input) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + assert!(parsed.get("expected_commitments").is_some()); + assert!(parsed.get("decrypted_shares").is_some()); + } + + #[test] + fn test_configs_generation_contains_expected() { + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_decryption_input_from_sample(&sample); + + let artifacts = ShareDecryptionCircuit + .codegen(DEFAULT_BFV_PRESET, &input) + .unwrap(); + + let configs = Configs::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let prefix = ::PREFIX; + + assert!(artifacts.configs.contains("ShareDecryptionConfigs")); + assert!(artifacts + .configs + .contains(format!("{}_BIT_MSG: u32 = {}", prefix, configs.bits.msg_bit).as_str())); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs new file mode 100644 index 0000000000..29db2b4434 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs @@ -0,0 +1,292 @@ +// 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 share-decryption circuit: configs, bounds, bit widths, and witness. +//! +//! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) honest ciphertexts and secret key. Witness values are normalized for the ZKP +//! field so the Noir circuit's range checks and commitment checks succeed. + +use crate::circuits::commitments::compute_share_encryption_commitment_from_message; +use crate::dkg::share_decryption::ShareDecryptionCircuit; +use crate::dkg::share_decryption::ShareDecryptionCircuitInput; +use crate::CircuitsErrors; +use crate::{bigint_2d_to_json_values, calculate_bit_width, poly_coefficients_to_toml_json}; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_polynomial::Polynomial; +use fhe_traits::FheDecrypter; +use num_bigint::BigInt; +use serde::{Deserialize, Serialize}; +use std::ops::Deref; + +/// Output of [`CircuitComputation::compute`] for [`ShareDecryptionCircuit`]: bounds, bit widths, and witness. +#[derive(Debug)] +pub struct ShareDecryptionOutput { + /// Coefficient bounds used to derive bit widths. + pub bounds: Bounds, + /// Bit widths used by the Noir prover for packing. + pub bits: Bits, + /// Witness data for the share-decryption circuit. + pub witness: Witness, +} + +/// Implementation of [`CircuitComputation`] for [`ShareDecryptionCircuit`]. +impl CircuitComputation for ShareDecryptionCircuit { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Output = ShareDecryptionOutput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; + + Ok(ShareDecryptionOutput { + bounds, + bits, + witness, + }) + } +} + +/// Global configs for the share-decryption circuit: degree, number of moduli, honest parties, bits, and bounds. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Configs { + /// Polynomial degree (N). + pub n: usize, + /// Number of CRT moduli (L). + pub l: usize, + /// Number of honest parties (H). + pub h: usize, + pub bits: Bits, + pub bounds: Bounds, +} + +/// Bit widths used by the Noir prover (e.g. for packing message coefficients). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + /// Bit width for plaintext/message coefficients (in [0, t)). + pub msg_bit: u32, +} + +/// Coefficient bounds for the share-decryption circuit (currently empty; bounds are derived from plaintext modulus). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds {} + +/// Witness data for the share-decryption circuit: expected commitments and decrypted shares. +/// +/// Coefficients are reduced to the ZKP field modulus for serialization. The circuit verifies +/// that decrypted shares match the expected commitments from the share-encryption circuit. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + /// Expected message commitments from share-encryption (CIRCUIT 3) for H honest parties: [party_idx][mod_idx]. + pub expected_commitments: Vec>, // [H][L] + /// Decrypted share coefficients per party and modulus: [party_idx][mod_idx][coeff_idx]. + pub decrypted_shares: Vec>>, // [H][L][N] +} + +impl Computation for Configs { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (_, dkg_params) = build_pair_for_preset(preset) + .map_err(|e| crate::utils::ZkHelpersUtilsError::ParseBound(e.to_string()))?; + + let n = dkg_params.degree() as usize; + let l = dkg_params.moduli().len(); + let h = input.honest_ciphertexts.len(); + + let bounds = Bounds::compute(preset, &input)?; + let bits = Bits::compute(preset, &bounds)?; + + Ok(Configs { + n, + l, + h, + bits, + bounds, + }) + } +} + +impl Computation for Bits { + type Preset = BfvPreset; + type Input = Bounds; + type Error = crate::utils::ZkHelpersUtilsError; + + fn compute(preset: Self::Preset, _: &Self::Input) -> Result { + let (_, dkg_params) = build_pair_for_preset(preset) + .map_err(|e| crate::utils::ZkHelpersUtilsError::ParseBound(e.to_string()))?; + + Ok(Bits { + msg_bit: calculate_bit_width(BigInt::from(dkg_params.plaintext())), + }) + } +} + +impl Computation for Bounds { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(_: Self::Preset, _: &Self::Input) -> Result { + 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, dkg_params) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + let threshold_l = threshold_params.moduli().len(); + + let mut expected_commitments: Vec> = Vec::new(); + let mut decrypted_shares: Vec>> = Vec::new(); + + let msg_bit = calculate_bit_width(BigInt::from(dkg_params.plaintext())); + + // Decrypt each ciphertext and compute its commitment + for party_cts in input.honest_ciphertexts.iter() { + let mut party_commitments = Vec::new(); + let mut party_shares = Vec::new(); + for mod_idx in 0..threshold_l { + if mod_idx < party_cts.len() { + // Decrypt the ciphertext to get the plaintext share + let decrypted_pt = input.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap(); + let share_coeffs = decrypted_pt.value.deref().to_vec(); + party_commitments.push(compute_share_encryption_commitment_from_message( + &Polynomial::from_u64_vector(share_coeffs.clone()), + msg_bit, + )); + party_shares.push( + share_coeffs + .iter() + .map(|c| BigInt::from(*c)) + .collect::>(), + ); + } + } + expected_commitments.push(party_commitments); + decrypted_shares.push(party_shares); + } + + Ok(Witness { + expected_commitments, + decrypted_shares, + }) + } + + // Used as witness for Nargo execution. + /// Serializes witness so that `decrypted_shares` matches Noir's `[[Polynomial; L]; H]`: + /// each polynomial is `{ "coefficients": [string, ...] }`. + fn to_json(&self) -> serde_json::Result { + let expected_commitments = bigint_2d_to_json_values(&self.expected_commitments); + let decrypted_shares: Vec> = self + .decrypted_shares + .iter() + .map(|party_shares| { + party_shares + .iter() + .map(|share| poly_coefficients_to_toml_json(share)) + .collect::>() + }) + .collect::>(); + + let json = serde_json::json!({ + "expected_commitments": expected_commitments, + "decrypted_shares": decrypted_shares, + }); + + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use crate::dkg::share_decryption::sample::prepare_share_decryption_sample_for_test; + use crate::dkg::share_decryption::ShareDecryptionSample; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + + fn share_decryption_input_from_sample( + sample: &ShareDecryptionSample, + ) -> ShareDecryptionCircuitInput { + ShareDecryptionCircuitInput { + secret_key: sample.secret_key.clone(), + honest_ciphertexts: sample.honest_ciphertexts.clone(), + } + } + + #[test] + fn test_bound_and_bits_computation_consistency() { + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_decryption_input_from_sample(&sample); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + + let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); + let expected_msg_bit = calculate_bit_width(BigInt::from(dkg_params.plaintext())); + assert_eq!(bits.msg_bit, expected_msg_bit); + } + + #[test] + fn test_constants_json_roundtrip() { + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_decryption_input_from_sample(&sample); + let constants = Configs::compute(DEFAULT_BFV_PRESET, &input).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.h, constants.h); + assert_eq!(decoded.bits, constants.bits); + assert_eq!(decoded.bounds, constants.bounds); + } + + #[test] + fn test_witness_decryption_consistency() { + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let input = share_decryption_input_from_sample(&sample); + let witness = Witness::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + + // Witness should have one row per honest party + assert_eq!( + witness.expected_commitments.len(), + input.honest_ciphertexts.len() + ); + assert_eq!( + witness.decrypted_shares.len(), + input.honest_ciphertexts.len() + ); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs new file mode 100644 index 0000000000..b630c01e08 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs @@ -0,0 +1,15 @@ +// 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. + +//! Share-decryption circuit: proves correct decryption of honest parties' ciphertexts under the DKG secret key (CIRCUIT 4a/4b). + +pub mod circuit; +pub mod codegen; +pub mod computation; +pub mod sample; +pub use circuit::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; +pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; +pub use sample::{prepare_share_decryption_sample_for_test, ShareDecryptionSample}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs new file mode 100644 index 0000000000..9c9698915e --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -0,0 +1,207 @@ +// 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 the share-decryption circuit: honest ciphertexts, sum ciphertexts, secret key, and message. + +use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::computation::DkgInputType; +use crate::CircuitsErrors; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use fhe::bfv::Ciphertext; +use fhe::bfv::Encoding; +use fhe::bfv::Plaintext; +use fhe::bfv::{PublicKey, SecretKey}; +use fhe::trbfv::{ShareManager, TRBFV}; +use fhe_traits::FheDecrypter; +use fhe_traits::FheEncoder; +use fhe_traits::FheEncrypter; +use rand::thread_rng; + +/// Sample data for the share-decryption circuit: honest ciphertexts, sum ciphertexts, secret key, and message. +#[derive(Debug, Clone)] +pub struct ShareDecryptionSample { + /// H honest party ciphertexts (multiple encrypted shares from different parties) + /// Structure: honest_ciphertexts[party_idx][trbfv_basis] + pub honest_ciphertexts: Vec>, + /// The sum of all honest ciphertexts per TRBFV basis (what we're actually decrypting) + /// Structure: sum_ciphertexts[trbfv_basis] + pub sum_ciphertexts: Vec, + /// BFV secret key used for decryption (private witness) + pub secret_key: SecretKey, + /// The decrypted message (aggregate share values) - same for all TRBFV bases + pub message: Plaintext, +} + +impl ShareDecryptionSample { + /// Generates sample data for the share-decryption circuit (decrypts a sum of honest ciphertexts under DKG secret key). + pub fn generate( + preset: BfvPreset, + committee_size: CiphernodesCommitteeSize, + dkg_input_type: DkgInputType, + num_ciphertexts: u128, // z in the search defaults + lambda: u32, + ) -> Self { + 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); + + let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) + .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); + let mut share_manager = + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + + let mut honest_ciphertexts: Vec> = Vec::new(); + let num_honest = committee.n; + for _ in 0..num_honest { + let mut party_cts = Vec::new(); + for _ in 0..threshold_params.moduli().len() { + let share_row = match dkg_input_type { + DkgInputType::SecretKey => { + let threshold_secret_key = SecretKey::random(&threshold_params, &mut rng); + + let sk_poly = share_manager + .coeffs_to_poly_level0(threshold_secret_key.coeffs.clone().as_ref()) + .unwrap(); + + let sk_sss_u64 = share_manager + .generate_secret_shares_from_poly(sk_poly.clone(), &mut rng) + .unwrap(); + + sk_sss_u64[0].row(0).to_vec() + } + DkgInputType::SmudgingNoise => { + let esi_coeffs = trbfv + .generate_smudging_error( + num_ciphertexts as usize, + lambda as usize, + &mut rng, + ) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate smudging error: {:?}", + e + )) + }) + .unwrap(); + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).unwrap(); + let esi_sss_u64 = share_manager + .generate_secret_shares_from_poly(esi_poly.clone(), &mut rng.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate error shares: {:?}", + e + )) + }) + .unwrap(); + + esi_sss_u64[0].row(0).to_vec() + } + }; + + let pt = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params).unwrap(); + + let ct = dkg_public_key.try_encrypt(&pt, &mut rng).unwrap(); + party_cts.push(ct); + } + honest_ciphertexts.push(party_cts); + } + + // Compute the sum of all honest ciphertexts per TRBFV basis (homomorphic addition) + // For each TRBFV basis: sum_ct[l] = ct_1[l] + ct_2[l] + ... + ct_H[l] + let mut sum_ciphertexts: Vec = Vec::new(); + let num_moduli = threshold_params.moduli().len(); + for trbfv_basis_idx in 0..num_moduli { + let mut sum_ct = honest_ciphertexts[0][trbfv_basis_idx].clone(); + for party_idx in 1..honest_ciphertexts.len() { + sum_ct = &sum_ct + &honest_ciphertexts[party_idx][trbfv_basis_idx]; + } + sum_ciphertexts.push(sum_ct); + } + + // Decrypt the sum for the first TRBFV basis to get the aggregate plaintext + // (The message should be the same for all TRBFV bases since we're decrypting the same aggregate) + let decrypted_pt = dkg_secret_key.try_decrypt(&sum_ciphertexts[0]).unwrap(); + + ShareDecryptionSample { + honest_ciphertexts, + sum_ciphertexts, + secret_key: dkg_secret_key, + message: decrypted_pt, + } + } +} + +/// Prepares a share-decryption sample for testing using a threshold preset. +pub fn prepare_share_decryption_sample_for_test( + preset: BfvPreset, + committee: CiphernodesCommitteeSize, + dkg_input_type: DkgInputType, +) -> ShareDecryptionSample { + let defaults = preset.search_defaults().unwrap(); + + ShareDecryptionSample::generate( + preset, + committee, + dkg_input_type, + defaults.z, + defaults.lambda, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use e3_fhe_params::{BfvPreset, DEFAULT_BFV_PRESET}; + + #[test] + fn test_generate_secret_key_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + + assert_eq!(sample.honest_ciphertexts.len(), committee.n); + assert_eq!(sample.sum_ciphertexts.len(), committee.threshold); + assert_eq!( + sample.secret_key.coeffs.len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!( + sample.message.value.len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + } + + #[test] + fn test_generate_smudging_noise_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = prepare_share_decryption_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SmudgingNoise, + ); + + assert_eq!(sample.honest_ciphertexts.len(), committee.n); + assert_eq!(sample.sum_ciphertexts.len(), committee.threshold); + assert_eq!( + sample.secret_key.coeffs.len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + assert_eq!( + sample.message.value.len(), + DEFAULT_BFV_PRESET.metadata().degree + ); + } +} 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 93bc22c389..ec3bcd3bf5 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/circuit.rs @@ -22,7 +22,7 @@ pub struct ShareEncryptionCircuit; impl Circuit for ShareEncryptionCircuit { const NAME: &'static str = "share-encryption"; const PREFIX: &'static str = "SHARE_ENCRYPTION"; - const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + 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/utils.rs b/crates/zk-helpers/src/utils.rs index 3c27b4cf43..e1c547cf59 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -214,6 +214,20 @@ pub fn crt_polynomial_to_toml_json(crt_polynomial: &CrtPolynomial) -> Vec]) -> Vec> { + y.iter() + .map(|coeff| { + coeff + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect() + }) + .collect() +} + /// Nested BigInt structure to JSON: map each value to `Value::String(s)`. /// /// Use for witness arrays (e.g. y) that need to be serialized as nested arrays of string values. From 4ca9b8d8ce18c1c6a1e728ab325e33fbe493a657 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 17:20:27 +0100 Subject: [PATCH 03/10] fix missing parenthesis --- crates/zk-helpers/src/bin/zk_cli.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 7b403397b6..baf05a86ee 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -353,6 +353,7 @@ fn main() -> Result<()> { honest_ciphertexts: sample.honest_ciphertexts, }, )? + } name if name == ::NAME => { let sample = PkAggregationCircuitInput::generate_sample( preset, From 9149896972eccc855d002ae85e770c6e57672432 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 17:40:17 +0100 Subject: [PATCH 04/10] reuse code from parity matrix gen --- .../circuits/dkg/share_computation/codegen.rs | 51 +------------ .../src/circuits/dkg/share_computation/mod.rs | 1 + .../circuits/dkg/share_computation/sample.rs | 21 ++---- .../circuits/dkg/share_computation/utils.rs | 73 +++++++++++++++++++ 4 files changed, 81 insertions(+), 65 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs 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 c16fc73fbb..6a01df7c1d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -6,11 +6,11 @@ //! Code generation for the share-computation BFV circuit: Prover.toml and configs.nr. -use crate::bigint_to_field; use crate::circuits::computation::CircuitComputation; use crate::circuits::computation::Computation; use crate::circuits::dkg::share_computation::{ - Bits, ShareComputationCircuit, ShareComputationCircuitInput, ShareComputationOutput, Witness, + utils::parity_matrix_constant_string, Bits, ShareComputationCircuit, + ShareComputationCircuitInput, ShareComputationOutput, Witness, }; use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; use crate::codegen::CodegenConfigs; @@ -20,9 +20,6 @@ use crate::poly_coefficients_to_toml_json; use crate::registry::Circuit; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use e3_parity_matrix::{build_generator_matrix, null_space, ParityMatrixConfig}; -use num_bigint::BigInt; -use num_bigint::BigUint; use serde_json; /// Implementation of [`CircuitCodegen`] for [`ShareComputationCircuit`]. @@ -77,50 +74,6 @@ pub fn generate_toml( Ok(toml::to_string(&json)?) } -/// Builds the PARITY_MATRIX constant string for Noir (one matrix per modulus via null_space). -fn parity_matrix_constant_string( - threshold_params: &std::sync::Arc, - n_parties: usize, - threshold: usize, -) -> Result { - let moduli = threshold_params.moduli(); - let mut parity_matrix_strings = Vec::with_capacity(moduli.len()); - - for &qi in moduli { - let q = BigUint::from(qi); - let g = build_generator_matrix(&ParityMatrixConfig { - q: q.clone(), - t: threshold, - n: n_parties, - }) - .map_err(|e| { - CircuitsErrors::Sample(format!("Failed to build generator matrix: {:?}", e)) - })?; - let h_mod = null_space(&g, &q).map_err(|e| { - CircuitsErrors::Sample(format!("Failed to compute null space: {:?}", e)) - })?; - - let mut modulus_rows = Vec::new(); - for row in h_mod.data() { - let row_values: Vec = row - .iter() - .map(|val| { - let bigint_val = BigInt::from(val.clone()); - let field_val = bigint_to_field(&bigint_val); - field_val.to_string() - }) - .collect(); - modulus_rows.push(format!("[{}]", row_values.join(", "))); - } - parity_matrix_strings.push(format!("[\n {}]", modulus_rows.join(",\n "))); - } - - Ok(format!( - "pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [\n {}];", - parity_matrix_strings.join(",\n ") - )) -} - /// Builds the configs.nr string (N, L, parity matrix, bit parameters, configs) for the Noir prover. /// /// `n_parties` and `threshold` are used to build the parity matrix (Reed–Solomon generator null space) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs index eaee5a4f5c..85fbb5cbf1 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -8,6 +8,7 @@ pub mod circuit; pub mod codegen; pub mod computation; pub mod sample; +pub mod utils; pub use circuit::{ShareComputationCircuit, ShareComputationCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareComputationOutput, Witness}; 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 01b97283d5..f6a9006ef1 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -9,17 +9,16 @@ use crate::ciphernodes_committee::CiphernodesCommittee; use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::circuits::dkg::share_computation::utils::compute_parity_matrix; use crate::computation::DkgInputType; use crate::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use e3_parity_matrix::build_generator_matrix; -use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; +use e3_parity_matrix::ParityMatrix; use e3_polynomial::CrtPolynomial; use fhe::bfv::{PublicKey, SecretKey}; use fhe::trbfv::{ShareManager, TRBFV}; use num_bigint::BigInt; -use num_bigint::BigUint; use rand::thread_rng; /// Shamir secret shares: one limb per CRT modulus (rows = parties, cols = polynomial coefficients). @@ -63,19 +62,9 @@ impl ShareComputationSample { let mut share_manager = ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); - // Parity check matrix (null space of generator) per modulus: [L][N_PARTIES-T][N_PARTIES+1]. - let mut parity_matrix = Vec::with_capacity(threshold_params.moduli().len()); - for &qi in threshold_params.moduli() { - let q = BigUint::from(qi); - let g = build_generator_matrix(&ParityMatrixConfig { - q: q.clone(), - t: committee.threshold, - n: committee.n, - }) - .unwrap(); - let h = null_space(&g, &q).unwrap(); - parity_matrix.push(h); - } + let parity_matrix = + compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) + .unwrap_or_else(|e| panic!("Failed to compute parity matrix: {}", e)); let (secret, secret_sss) = match dkg_input_type { DkgInputType::SecretKey => { diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs new file mode 100644 index 0000000000..a9f280fab8 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs @@ -0,0 +1,73 @@ +// 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. + +//! Shared utilities for the share-computation circuit (e.g. parity matrix). + +use crate::utils::bigint_to_field; +use crate::CircuitsErrors; +use e3_parity_matrix::build_generator_matrix; +use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; +use num_bigint::{BigInt, BigUint}; + +/// Computes the parity check matrix (null space of the Reed–Solomon generator) per modulus. +/// +/// Returns one `ParityMatrix` per modulus in `moduli`, each of shape `[n_parties - threshold][n_parties + 1]`. +pub fn compute_parity_matrix( + moduli: &[u64], + n_parties: usize, + threshold: usize, +) -> Result, String> { + let mut parity_matrix = Vec::with_capacity(moduli.len()); + for &qi in moduli { + let q = BigUint::from(qi); + let g = build_generator_matrix(&ParityMatrixConfig { + q: q.clone(), + t: threshold, + n: n_parties, + }) + .map_err(|e| format!("Failed to build generator matrix: {:?}", e))?; + let h = null_space(&g, &q).map_err(|e| format!("Failed to compute null space: {:?}", e))?; + parity_matrix.push(h); + } + Ok(parity_matrix) +} + +/// Builds the PARITY_MATRIX constant string for Noir (one matrix per modulus via null_space). +pub fn parity_matrix_constant_string( + threshold_params: &std::sync::Arc, + n_parties: usize, + threshold: usize, +) -> Result { + let parity_matrix = compute_parity_matrix(threshold_params.moduli(), n_parties, threshold) + .map_err(|e| CircuitsErrors::Sample(e))?; + + let parity_matrix_strings: Vec = parity_matrix + .iter() + .map(|h_mod| { + let modulus_rows: Vec = h_mod + .data() + .iter() + .map(|row| { + let row_values: Vec = row + .iter() + .map(|val| { + let bigint_val = BigInt::from(val.clone()); + let field_val = bigint_to_field(&bigint_val); + field_val.to_string() + }) + .collect(); + format!("[{}]", row_values.join(", ")) + }) + .collect(); + format!("[\n {}]", modulus_rows.join(",\n ")) + }) + .collect(); + + Ok(format!( + "pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [\n {}];", + parity_matrix_strings.join(",\n ") + )) +} From faf18a807c76bd252baa64903f2e24830f6fd14c Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 17:46:30 +0100 Subject: [PATCH 05/10] add ring utilities --- .../dkg/share_encryption/computation.rs | 172 +----------------- .../threshold/pk_generation/computation.rs | 66 +------ .../user_data_encryption/computation.rs | 172 +----------------- crates/zk-helpers/src/lib.rs | 2 + crates/zk-helpers/src/ring.rs | 88 +++++++++ 5 files changed, 110 insertions(+), 390 deletions(-) create mode 100644 crates/zk-helpers/src/ring.rs 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 8576487235..1f30828849 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -19,6 +19,7 @@ use crate::dkg::share_encryption::ShareEncryptionCircuit; use crate::dkg::share_encryption::ShareEncryptionCircuitInput; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; +use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::utils::{compute_msg_bit, compute_pk_bit}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; @@ -34,7 +35,6 @@ use fhe_math::zq::Modulus; use itertools::izip; use num_bigint::ToBigInt; use num_bigint::{BigInt, BigUint}; -use num_traits::Zero; use num_traits::{Signed, ToPrimitive}; use rayon::iter::ParallelIterator; use rayon::prelude::ParallelBridge; @@ -454,11 +454,7 @@ impl Computation for Witness { pk0.change_representation(Representation::PowerBasis); pk1.change_representation(Representation::PowerBasis); - // 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 + let cyclo = cyclotomic_polynomial(n); let ct0_coeffs = ct0.coefficients(); let ct1_coeffs = ct1.coefficients(); @@ -585,88 +581,11 @@ impl Computation for Witness { }; assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i - let mut ct0i_hat_mod_rqi = Polynomial::new(ct0i_hat.clone()); - - ct0i_hat_mod_rqi = ct0i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - - ct0i_hat_mod_rqi.reduce(&qi_bigint); - ct0i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct0i, &ct0i_hat_mod_rqi); - - // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial - let ct0i_poly = ct0i.clone(); let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let ct0i_minus_ct0i_hat = ct0i_poly.sub(&ct0i_hat_poly).coefficients().to_vec(); - assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - - let mut ct0i_minus_ct0i_hat_mod_zqi = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - - ct0i_minus_ct0i_hat_mod_zqi.reduce(&qi_bigint); - ct0i_minus_ct0i_hat_mod_zqi.center(&qi_bigint); - - // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = ct0i_minus_ct0i_hat_mod_zqi.clone(); - let cyclo_poly = Polynomial::new(cyclo.clone()); - let (r2i_poly, r2i_rem_poly) = ct0i_minus_ct0i_hat_poly.div(&cyclo_poly).unwrap(); - let r2i = r2i_poly.coefficients().to_vec(); - let r2i_rem = r2i_rem_poly.coefficients().to_vec(); - assert!(r2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r2i.len() as u64) - 1, n - 2); // Order(r2i) = N - 2 - - // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi - let r2i_poly = Polynomial::new(r2i.clone()); - let r2i_times_cyclo = r2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - - let mut r2i_times_cyclo_mod_zqi = Polynomial::new(r2i_times_cyclo.clone()); - - r2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - r2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); - assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let r1i_num = ct0i_minus_ct0i_hat_poly - .sub(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((r1i_num.len() as u64) - 1, 2 * (n - 1)); - - let r1i_num_poly = Polynomial::new(r1i_num.clone()); - let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); - let (r1i_poly, r1i_rem_poly) = r1i_num_poly.div(&qi_poly).unwrap(); + let (r1i_poly, r2i_poly) = + decompose_residue(&ct0i, &ct0i_hat_poly, &qi_bigint, &cyclo, n); let r1i = r1i_poly.coefficients().to_vec(); - let r1i_rem = r1i_rem_poly.coefficients().to_vec(); - assert!(r1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r1i.len() as u64) - 1, 2 * (n - 1)); // Order(r1i) = 2*(N-1) - let r1i_poly_check = Polynomial::new(r1i.clone()); - assert_eq!( - &r1i_num, - &r1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p - let r1i_poly = Polynomial::new(r1i.clone()); - let r1i_times_qi = r1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let r1i_times_qi_poly = Polynomial::new(r1i_times_qi.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let mut ct0i_calculated = ct0i_hat_poly - .add(&r1i_times_qi_poly) - .add(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct0i_calculated.is_empty() && ct0i_calculated[0].is_zero() { - ct0i_calculated.remove(0); - } - - assert_eq!(&ct0i, &Polynomial::new(ct0i_calculated.clone())); + let r2i = r2i_poly.coefficients().to_vec(); // --------------------------------------------------- ct1i --------------------------------------------------- @@ -682,87 +601,12 @@ impl Computation for Witness { }; assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i - let mut ct1i_hat_mod_rqi = Polynomial::new(ct1i_hat.clone()); - - ct1i_hat_mod_rqi = ct1i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - ct1i_hat_mod_rqi.reduce(&qi_bigint); - ct1i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct1i, &ct1i_hat_mod_rqi); - - // Compute p2i numerator = ct1i - ct1i_hat - let ct1i_poly = ct1i.clone(); let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let ct1i_minus_ct1i_hat = ct1i_poly.sub(&ct1i_hat_poly).coefficients().to_vec(); - assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - let mut ct1i_minus_ct1i_hat_mod_zqi = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - - ct1i_minus_ct1i_hat_mod_zqi.reduce(&qi_bigint); - ct1i_minus_ct1i_hat_mod_zqi.center(&qi_bigint); - - // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, - // and reduce/center the resulting coefficients to produce: - // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = ct1i_minus_ct1i_hat_mod_zqi.clone(); - let (p2i_poly, p2i_rem_poly) = - ct1i_minus_ct1i_hat_poly.div(&cyclo_poly.clone()).unwrap(); - let p2i = p2i_poly.coefficients().to_vec(); - let p2i_rem = p2i_rem_poly.coefficients().to_vec(); - assert!(p2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p2i.len() as u64) - 1, n - 2); // Order(p2i) = N - 2 - - // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi - let p2i_poly = Polynomial::new(p2i.clone()); - let p2i_times_cyclo: Vec = - p2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - let mut p2i_times_cyclo_mod_zqi = Polynomial::new(p2i_times_cyclo.clone()); - - p2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - p2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); - assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let p1i_num = ct1i_minus_ct1i_hat_poly - .sub(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((p1i_num.len() as u64) - 1, 2 * (n - 1)); - - let p1i_num_poly = Polynomial::new(p1i_num.clone()); - let qi_poly = Polynomial::new(vec![BigInt::from(qi.modulus())]); - let (p1i_poly, p1i_rem_poly) = p1i_num_poly.div(&qi_poly).unwrap(); + let (p1i_poly, p2i_poly) = + decompose_residue(&ct1i, &ct1i_hat_poly, &qi_bigint, &cyclo, n); let p1i = p1i_poly.coefficients().to_vec(); - let p1i_rem = p1i_rem_poly.coefficients().to_vec(); - assert!(p1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p1i.len() as u64) - 1, 2 * (n - 1)); // Order(p1i) = 2*(N-1) - let p1i_poly_check = Polynomial::new(p1i.clone()); - assert_eq!( - &p1i_num, - &p1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p - let p1i_poly = Polynomial::new(p1i.clone()); - let p1i_times_qi = p1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let p1i_times_qi_poly = Polynomial::new(p1i_times_qi.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let mut ct1i_calculated = ct1i_hat_poly - .add(&p1i_times_qi_poly) - .add(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct1i_calculated.is_empty() && ct1i_calculated[0].is_zero() { - ct1i_calculated.remove(0); - } + let p2i = p2i_poly.coefficients().to_vec(); - assert_eq!(&ct1i, &Polynomial::new(ct1i_calculated.clone())); ( i, r2i, diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index db09ab92fb..68b8186320 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -13,6 +13,7 @@ use crate::calculate_bit_width; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; +use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::pk_generation::circuit::PkGenerationCircuit; use crate::threshold::pk_generation::circuit::PkGenerationCircuitInput; use crate::CiphernodesCommittee; @@ -254,12 +255,7 @@ impl Computation for Witness { .map(BigInt::from) .collect(); let n = threshold_params.degree() as u64; - - // 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 + let cyclo = cyclotomic_polynomial(n); // Perform the main computation logic let mut results: Vec<( @@ -309,62 +305,8 @@ impl Computation for Witness { assert_eq!((pk0_share_hat.coefficients().len() as u64) - 1, 2 * (n - 1)); - // Check whether pk0_share_hat mod R_qi (the ring) is equal to pk0_share - let mut pk0_share_hat_mod_rqi = pk0_share_hat.reduce_by_cyclotomic(&cyclo).unwrap(); - - pk0_share_hat_mod_rqi.reduce(&qi); - pk0_share_hat_mod_rqi.center(&qi); - - assert_eq!(&pk0_share, &pk0_share_hat_mod_rqi); - - // Compute r2_numerator = pk0_share - pk0_share_hat and reduce/center the polynomial - let r2_numerator = pk0_share.sub(&pk0_share_hat); - - assert_eq!((r2_numerator.coefficients().len() as u64) - 1, 2 * (n - 1)); - - let mut r2_numerator_centered = r2_numerator.clone(); - r2_numerator_centered.reduce(&qi); - r2_numerator_centered.center(&qi); - - // Compute r2 as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (pk0_share - pk0_share_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - 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(r2i) = N - 2 - - // Assert that (pk0_share - pk0_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) - ); - - // Calculate r1 = (pk0_share - pk0_share_hat - r2 * cyclo) / qi mod Z_p. Remainder should be empty. - let r1_numerator = r2_numerator.sub(&r2_cyclo_times); - - assert_eq!((r2_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 pk0_share = (pk0_share_hat + r1 * qi + r2 * cyclo) mod R_qi - let r1_qi_times = r1.scalar_mul(&qi); - let pk0_share_calculated = pk0_share_hat.add(&r1_qi_times).add(&r2_cyclo_times); - - assert_eq!(&pk0_share, &pk0_share_calculated.trim_leading_zeros()); + let (r1, r2) = + decompose_residue(&pk0_share, &pk0_share_hat, &qi, &cyclo, n); (i, r2, r1, pk0_share.clone(), a.clone(), e_sm.clone()) }, 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 64f3a327ac..a177fb8f3c 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 @@ -15,6 +15,7 @@ use crate::compute_ciphertext_commitment; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; +use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use crate::utils::compute_pk_bit; @@ -37,7 +38,6 @@ use num_bigint::BigUint; use num_bigint::ToBigInt; use num_traits::Signed; use num_traits::ToPrimitive; -use num_traits::Zero; use rand::thread_rng; use rayon::iter::ParallelIterator; use rayon::prelude::ParallelBridge; @@ -469,11 +469,7 @@ impl Computation for Witness { pk0.change_representation(Representation::PowerBasis); pk1.change_representation(Representation::PowerBasis); - // 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 + let cyclo = cyclotomic_polynomial(n); let ct0_coeffs = ct0.coefficients(); let ct1_coeffs = ct1.coefficients(); @@ -600,88 +596,11 @@ impl Computation for Witness { }; assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i - let mut ct0i_hat_mod_rqi = Polynomial::new(ct0i_hat.clone()); - - ct0i_hat_mod_rqi = ct0i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - - ct0i_hat_mod_rqi.reduce(&qi_bigint); - ct0i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct0i, &ct0i_hat_mod_rqi); - - // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial - let ct0i_poly = ct0i.clone(); let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let ct0i_minus_ct0i_hat = ct0i_poly.sub(&ct0i_hat_poly).coefficients().to_vec(); - assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - - let mut ct0i_minus_ct0i_hat_mod_zqi = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - - ct0i_minus_ct0i_hat_mod_zqi.reduce(&qi_bigint); - ct0i_minus_ct0i_hat_mod_zqi.center(&qi_bigint); - - // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = ct0i_minus_ct0i_hat_mod_zqi.clone(); - let cyclo_poly = Polynomial::new(cyclo.clone()); - let (r2i_poly, r2i_rem_poly) = ct0i_minus_ct0i_hat_poly.div(&cyclo_poly).unwrap(); - let r2i = r2i_poly.coefficients().to_vec(); - let r2i_rem = r2i_rem_poly.coefficients().to_vec(); - assert!(r2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r2i.len() as u64) - 1, n - 2); // Order(r2i) = N - 2 - - // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi - let r2i_poly = Polynomial::new(r2i.clone()); - let r2i_times_cyclo = r2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - - let mut r2i_times_cyclo_mod_zqi = Polynomial::new(r2i_times_cyclo.clone()); - - r2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - r2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); - assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let r1i_num = ct0i_minus_ct0i_hat_poly - .sub(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((r1i_num.len() as u64) - 1, 2 * (n - 1)); - - let r1i_num_poly = Polynomial::new(r1i_num.clone()); - let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); - let (r1i_poly, r1i_rem_poly) = r1i_num_poly.div(&qi_poly).unwrap(); + let (r1i_poly, r2i_poly) = + decompose_residue(&ct0i, &ct0i_hat_poly, &qi_bigint, &cyclo, n); let r1i = r1i_poly.coefficients().to_vec(); - let r1i_rem = r1i_rem_poly.coefficients().to_vec(); - assert!(r1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r1i.len() as u64) - 1, 2 * (n - 1)); // Order(r1i) = 2*(N-1) - let r1i_poly_check = Polynomial::new(r1i.clone()); - assert_eq!( - &r1i_num, - &r1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p - let r1i_poly = Polynomial::new(r1i.clone()); - let r1i_times_qi = r1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let r1i_times_qi_poly = Polynomial::new(r1i_times_qi.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let mut ct0i_calculated = ct0i_hat_poly - .add(&r1i_times_qi_poly) - .add(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct0i_calculated.is_empty() && ct0i_calculated[0].is_zero() { - ct0i_calculated.remove(0); - } - - assert_eq!(&ct0i, &Polynomial::new(ct0i_calculated.clone())); + let r2i = r2i_poly.coefficients().to_vec(); // --------------------------------------------------- ct1i --------------------------------------------------- @@ -697,87 +616,12 @@ impl Computation for Witness { }; assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i - let mut ct1i_hat_mod_rqi = Polynomial::new(ct1i_hat.clone()); - - ct1i_hat_mod_rqi = ct1i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - ct1i_hat_mod_rqi.reduce(&qi_bigint); - ct1i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct1i, &ct1i_hat_mod_rqi); - - // Compute p2i numerator = ct1i - ct1i_hat - let ct1i_poly = ct1i.clone(); let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let ct1i_minus_ct1i_hat = ct1i_poly.sub(&ct1i_hat_poly).coefficients().to_vec(); - assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - let mut ct1i_minus_ct1i_hat_mod_zqi = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - - ct1i_minus_ct1i_hat_mod_zqi.reduce(&qi_bigint); - ct1i_minus_ct1i_hat_mod_zqi.center(&qi_bigint); - - // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, - // and reduce/center the resulting coefficients to produce: - // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = ct1i_minus_ct1i_hat_mod_zqi.clone(); - let (p2i_poly, p2i_rem_poly) = - ct1i_minus_ct1i_hat_poly.div(&cyclo_poly.clone()).unwrap(); - let p2i = p2i_poly.coefficients().to_vec(); - let p2i_rem = p2i_rem_poly.coefficients().to_vec(); - assert!(p2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p2i.len() as u64) - 1, n - 2); // Order(p2i) = N - 2 - - // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi - let p2i_poly = Polynomial::new(p2i.clone()); - let p2i_times_cyclo: Vec = - p2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - let mut p2i_times_cyclo_mod_zqi = Polynomial::new(p2i_times_cyclo.clone()); - - p2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - p2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); - assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let p1i_num = ct1i_minus_ct1i_hat_poly - .sub(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((p1i_num.len() as u64) - 1, 2 * (n - 1)); - - let p1i_num_poly = Polynomial::new(p1i_num.clone()); - let qi_poly = Polynomial::new(vec![BigInt::from(qi.modulus())]); - let (p1i_poly, p1i_rem_poly) = p1i_num_poly.div(&qi_poly).unwrap(); + let (p1i_poly, p2i_poly) = + decompose_residue(&ct1i, &ct1i_hat_poly, &qi_bigint, &cyclo, n); let p1i = p1i_poly.coefficients().to_vec(); - let p1i_rem = p1i_rem_poly.coefficients().to_vec(); - assert!(p1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p1i.len() as u64) - 1, 2 * (n - 1)); // Order(p1i) = 2*(N-1) - let p1i_poly_check = Polynomial::new(p1i.clone()); - assert_eq!( - &p1i_num, - &p1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p - let p1i_poly = Polynomial::new(p1i.clone()); - let p1i_times_qi = p1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let p1i_times_qi_poly = Polynomial::new(p1i_times_qi.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let mut ct1i_calculated = ct1i_hat_poly - .add(&p1i_times_qi_poly) - .add(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct1i_calculated.is_empty() && ct1i_calculated[0].is_zero() { - ct1i_calculated.remove(0); - } + let p2i = p2i_poly.coefficients().to_vec(); - assert_eq!(&ct1i, &Polynomial::new(ct1i_calculated.clone())); ( i, r2i, diff --git a/crates/zk-helpers/src/lib.rs b/crates/zk-helpers/src/lib.rs index 979ae79c99..8f68e5d72e 100644 --- a/crates/zk-helpers/src/lib.rs +++ b/crates/zk-helpers/src/lib.rs @@ -8,10 +8,12 @@ pub mod ciphernodes_committee; pub mod circuits; pub mod packing; pub mod registry; +pub mod ring; pub mod utils; pub use ciphernodes_committee::*; pub use circuits::*; pub use packing::*; pub use registry::*; +pub use ring::*; pub use utils::*; diff --git a/crates/zk-helpers/src/ring.rs b/crates/zk-helpers/src/ring.rs new file mode 100644 index 0000000000..396461f0b6 --- /dev/null +++ b/crates/zk-helpers/src/ring.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Polynomial ring helpers for ZK circuit computations (R_qi = Z_q_i[x] / (x^N + 1)). +//! +//! Cyclotomic polynomial and residue decomposition used by pk_generation, share_encryption, +//! and user_data_encryption when reducing by R_qi and decomposing residues. + +use e3_polynomial::Polynomial; +use num_bigint::BigInt; +use num_traits::Zero; + +/// Returns the coefficient vector for the cyclotomic polynomial x^N + 1 (degree N). +/// +/// Used by share_encryption, user_data_encryption, and pk_generation when reducing by R_qi. +#[must_use] +pub fn cyclotomic_polynomial(n: u64) -> Vec { + let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; + cyclo[0] = BigInt::from(1u64); + cyclo[n as usize] = BigInt::from(1u64); + cyclo +} + +/// Decomposes the residue `xi - xi_hat` into `r1 * qi + r2 * cyclo` mod R_qi. +/// +/// Verifies that `xi == xi_hat (mod R_qi)`, then computes quotient polynomials +/// such that `xi - xi_hat = r1 * qi + r2 * cyclo` (with cyclo = x^N + 1). +/// Returns `(r1, r2)` as polynomials. Panics on assertion failures (exact division, +/// degree checks, reconstruction). +pub fn decompose_residue( + xi: &Polynomial, + xi_hat: &Polynomial, + qi_bigint: &BigInt, + cyclo: &[BigInt], + n: u64, +) -> (Polynomial, Polynomial) { + let cyclo_poly = Polynomial::new(cyclo.to_vec()); + let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); + + let mut xi_hat_mod_rqi = xi_hat.clone(); + xi_hat_mod_rqi = xi_hat_mod_rqi.reduce_by_cyclotomic(cyclo).unwrap(); + xi_hat_mod_rqi.reduce(qi_bigint); + xi_hat_mod_rqi.center(qi_bigint); + assert_eq!(xi, &xi_hat_mod_rqi); + + let num_coeffs = xi.sub(xi_hat).coefficients().to_vec(); + assert_eq!((num_coeffs.len() as u64) - 1, 2 * (n - 1)); + + let mut num_mod_zqi = Polynomial::new(num_coeffs.clone()); + num_mod_zqi.reduce(qi_bigint); + num_mod_zqi.center(qi_bigint); + + let (r2_poly, r2_rem_poly) = num_mod_zqi.clone().div(&cyclo_poly).unwrap(); + assert!(r2_rem_poly.coefficients().iter().all(|c| c.is_zero())); + assert_eq!((r2_poly.coefficients().len() as u64) - 1, n - 2); + + let r2_times_cyclo = r2_poly.mul(&cyclo_poly); + let mut r2_times_cyclo_mod = r2_times_cyclo.clone(); + r2_times_cyclo_mod.reduce(qi_bigint); + r2_times_cyclo_mod.center(qi_bigint); + assert_eq!(&num_mod_zqi, &r2_times_cyclo_mod); + assert_eq!( + (r2_times_cyclo.coefficients().len() as u64) - 1, + 2 * (n - 1) + ); + + let num_poly = Polynomial::new(num_coeffs); + let r1_num = num_poly.sub(&r2_times_cyclo); + assert_eq!((r1_num.coefficients().len() as u64) - 1, 2 * (n - 1)); + + let (r1_poly, r1_rem_poly) = r1_num.div(&qi_poly).unwrap(); + assert!(r1_rem_poly.coefficients().iter().all(|c| c.is_zero())); + assert_eq!((r1_poly.coefficients().len() as u64) - 1, 2 * (n - 1)); + assert_eq!(&r1_num, &r1_poly.mul(&qi_poly)); + + let r1_times_qi = r1_poly.clone().scalar_mul(qi_bigint); + let xi_calculated = xi_hat + .clone() + .add(&r1_times_qi) + .add(&r2_times_cyclo) + .trim_leading_zeros(); + assert_eq!(xi, &xi_calculated); + + (r1_poly, r2_poly) +} From 35a3439b7265e5a55dbef60ef23c875a348d89e2 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 17:54:12 +0100 Subject: [PATCH 06/10] add crt utils --- .../src/circuits/dkg/pk/computation.rs | 10 +--- .../dkg/share_encryption/computation.rs | 35 +++++--------- .../threshold/pk_generation/computation.rs | 3 +- .../user_data_encryption/computation.rs | 39 +++++---------- .../threshold/user_data_encryption/utils.rs | 21 ++------ crates/zk-helpers/src/crt.rs | 48 +++++++++++++++++++ crates/zk-helpers/src/lib.rs | 2 + 7 files changed, 80 insertions(+), 78 deletions(-) create mode 100644 crates/zk-helpers/src/crt.rs diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 9f9a5a50c2..55bf3d0658 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -153,14 +153,8 @@ impl Computation for Witness { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let moduli = dkg_params.moduli(); - let mut pk0is = CrtPolynomial::from_fhe_polynomial(&input.public_key.c.c[0]); - let mut pk1is = CrtPolynomial::from_fhe_polynomial(&input.public_key.c.c[1]); - - pk0is.reverse(); - pk1is.reverse(); - - pk0is.center(&moduli)?; - pk1is.center(&moduli)?; + let mut pk0is = crate::crt::fhe_poly_to_crt_centered(&input.public_key.c.c[0], moduli)?; + let mut pk1is = crate::crt::fhe_poly_to_crt_centered(&input.public_key.c.c[1], moduli)?; let zkp_modulus = &get_zkp_modulus(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index 1f30828849..c61c1ae276 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -15,6 +15,7 @@ use crate::circuits::commitments::{ }; use std::ops::Deref; +use crate::crt::compute_k0is; use crate::dkg::share_encryption::ShareEncryptionCircuit; use crate::dkg::share_encryption::ShareEncryptionCircuitInput; use crate::get_zkp_modulus; @@ -159,17 +160,7 @@ impl Computation for Configs { let q_mod_t = center(&reduce(&modulus, &t), &t); let q_mod_t_mod_p = reduce(&q_mod_t, &p); - let mut k0is: Vec = Vec::new(); - - for qi in ctx.moduli_operators() { - let k0qi = BigInt::from(qi.inv(qi.neg(dkg_params.plaintext())).ok_or_else(|| { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - })?); - - k0is.push(k0qi.to_u64().unwrap_or(0)); - } + let k0is = compute_k0is(&moduli, dkg_params.plaintext())?; let bounds = Bounds::compute(preset, input)?; let bits = Bits::compute(preset, &bounds)?; @@ -275,29 +266,25 @@ impl Computation for Bounds { }; // Calculate bounds for each CRT basis - let _num_moduli = ctx.moduli().len(); + let moduli: Vec = ctx + .moduli_operators() + .into_iter() + .map(|q| q.modulus()) + .collect(); + let k0is = compute_k0is(&moduli, dkg_params.plaintext())?; + let mut pk_bounds: Vec = Vec::new(); let mut r1_low_bounds: Vec = Vec::new(); let mut r1_up_bounds: Vec = Vec::new(); let mut r2_bounds: Vec = Vec::new(); let mut p1_bounds: Vec = Vec::new(); let mut p2_bounds: Vec = Vec::new(); - let mut moduli: Vec = Vec::new(); - let mut k0is: Vec = Vec::new(); - for qi in ctx.moduli_operators() { + for (i, qi) in ctx.moduli_operators().into_iter().enumerate() { let qi_bigint = BigInt::from(qi.modulus()); let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); - moduli.push(qi.modulus()); - - // Calculate k0qi for bounds - let k0qi = BigInt::from(qi.inv(qi.neg(dkg_params.plaintext())).ok_or_else(|| { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - })?); - k0is.push(k0qi.to_u64().unwrap_or(0)); + let k0qi = BigInt::from(k0is[i]); // PK and R2 bounds (same as qi_bound) pk_bounds.push(qi_bound.clone()); diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index 68b8186320..e5f2832a1c 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -305,8 +305,7 @@ impl Computation for Witness { assert_eq!((pk0_share_hat.coefficients().len() as u64) - 1, 2 * (n - 1)); - let (r1, r2) = - decompose_residue(&pk0_share, &pk0_share_hat, &qi, &cyclo, n); + let (r1, r2) = decompose_residue(&pk0_share, &pk0_share_hat, &qi, &cyclo, n); (i, r2, r1, pk0_share.clone(), a.clone(), e_sm.clone()) }, 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 a177fb8f3c..2210269b18 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs @@ -12,6 +12,7 @@ use crate::calculate_bit_width; use crate::commitments::compute_pk_aggregation_commitment; use crate::compute_ciphertext_commitment; +use crate::crt::compute_k0is; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; @@ -151,19 +152,7 @@ impl Computation for Configs { let q_mod_t = center(&reduce(&modulus, &t), &t); let q_mod_t_mod_p = reduce(&q_mod_t, &p); - let mut k0is: Vec = Vec::new(); - - for qi in ctx.moduli_operators() { - let k0qi = BigInt::from(qi.inv(qi.neg(threshold_params.plaintext())).ok_or_else( - || { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - }, - )?); - - k0is.push(k0qi.to_u64().unwrap_or(0)); - } + let k0is = compute_k0is(threshold_params.moduli(), threshold_params.plaintext())?; let bounds = Bounds::compute(preset, &())?; let bits = Bits::compute(preset, &bounds)?; @@ -284,31 +273,25 @@ impl Computation for Bounds { let k1_up_bound: BigInt = ptxt_up_bound.clone(); // Calculate bounds for each CRT basis - let _num_moduli = ctx.moduli().len(); + let moduli: Vec = ctx + .moduli_operators() + .into_iter() + .map(|q| q.modulus()) + .collect(); + let k0is = compute_k0is(&moduli, threshold_params.plaintext())?; + let mut pk_bounds: Vec = Vec::new(); let mut r1_low_bounds: Vec = Vec::new(); let mut r1_up_bounds: Vec = Vec::new(); let mut r2_bounds: Vec = Vec::new(); let mut p1_bounds: Vec = Vec::new(); let mut p2_bounds: Vec = Vec::new(); - let mut moduli: Vec = Vec::new(); - let mut k0is: Vec = Vec::new(); - for qi in ctx.moduli_operators() { + for (i, qi) in ctx.moduli_operators().into_iter().enumerate() { let qi_bigint = BigInt::from(qi.modulus()); let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); - moduli.push(qi.modulus()); - - // Calculate k0qi for bounds - let k0qi = BigInt::from(qi.inv(qi.neg(threshold_params.plaintext())).ok_or_else( - || { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - }, - )?); - k0is.push(k0qi.to_u64().unwrap_or(0)); + let k0qi = BigInt::from(k0is[i]); // PK and R2 bounds (same as qi_bound) pk_bounds.push(qi_bound.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 4f60bf352c..3f0dd81b98 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs @@ -4,6 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +use crate::crt::fhe_poly_to_crt_centered; use crate::utils::{compute_pk_bit, get_zkp_modulus, ZkHelpersUtilsError}; use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; use fhe::bfv::{BfvParameters, Ciphertext, PublicKey}; @@ -29,14 +30,8 @@ pub fn bfv_ciphertext_to_greco( let moduli = params.moduli(); let zkp_modulus = get_zkp_modulus(); - let mut ct0is = CrtPolynomial::from_fhe_polynomial(&ciphertext.c[0]); - let mut ct1is = CrtPolynomial::from_fhe_polynomial(&ciphertext.c[1]); - - ct0is.reverse(); - ct1is.reverse(); - - ct0is.center(&moduli)?; - ct1is.center(&moduli)?; + let mut ct0is = fhe_poly_to_crt_centered(&ciphertext.c[0], moduli)?; + let mut ct1is = fhe_poly_to_crt_centered(&ciphertext.c[1], moduli)?; ct0is.reduce_uniform(&zkp_modulus); ct1is.reduce_uniform(&zkp_modulus); @@ -65,14 +60,8 @@ pub fn bfv_public_key_to_greco( let moduli = params.moduli(); let zkp_modulus = get_zkp_modulus(); - let mut pk0is = CrtPolynomial::from_fhe_polynomial(&public_key.c.c[0]); - let mut pk1is = CrtPolynomial::from_fhe_polynomial(&public_key.c.c[1]); - - pk0is.reverse(); - pk1is.reverse(); - - pk0is.center(&moduli)?; - pk1is.center(&moduli)?; + let mut pk0is = fhe_poly_to_crt_centered(&public_key.c.c[0], moduli)?; + let mut pk1is = fhe_poly_to_crt_centered(&public_key.c.c[1], moduli)?; pk0is.reduce_uniform(&zkp_modulus); pk1is.reduce_uniform(&zkp_modulus); diff --git a/crates/zk-helpers/src/crt.rs b/crates/zk-helpers/src/crt.rs new file mode 100644 index 0000000000..c9e264d772 --- /dev/null +++ b/crates/zk-helpers/src/crt.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! CRT (Chinese Remainder) operations for ZK circuit computations. +//! +//! Used by share_encryption, user_data_encryption, pk, and other circuits that work with +//! polynomials in CRT form or need to convert FHE types to CRT limbs. + +use crate::CircuitsErrors; +use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; +use fhe_math::rq::Poly; +use fhe_math::zq::Modulus; + +/// Computes k0_i = (-t)^{-1} mod q_i for each modulus (used in Configs and bounds). +/// +/// Same logic as share_encryption and user_data_encryption when building k0is. +pub fn compute_k0is(moduli: &[u64], plaintext_modulus: u64) -> Result, CircuitsErrors> { + let mut k0is = Vec::with_capacity(moduli.len()); + for &qi in moduli { + let m = Modulus::new(qi).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create modulus for k0is: {:?}", e)) + })?; + let k0qi = m.inv(m.neg(plaintext_modulus)).ok_or_else(|| { + CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( + "Failed to calculate modulus inverse for k0qi".into(), + ))) + })?; + k0is.push(k0qi); + } + Ok(k0is) +} + +/// Converts an FHE polynomial to CRT form with reverse + center (no ZKP reduce). +/// +/// Same pattern used by share_encryption, user_data_encryption, and pk circuits +/// when building circuit input from FHE types. +pub fn fhe_poly_to_crt_centered( + poly: &Poly, + moduli: &[u64], +) -> Result { + let mut crt = CrtPolynomial::from_fhe_polynomial(poly); + crt.reverse(); + crt.center(moduli)?; + Ok(crt) +} diff --git a/crates/zk-helpers/src/lib.rs b/crates/zk-helpers/src/lib.rs index 8f68e5d72e..dd1d9e7a3c 100644 --- a/crates/zk-helpers/src/lib.rs +++ b/crates/zk-helpers/src/lib.rs @@ -6,6 +6,7 @@ pub mod ciphernodes_committee; pub mod circuits; +pub mod crt; pub mod packing; pub mod registry; pub mod ring; @@ -13,6 +14,7 @@ pub mod utils; pub use ciphernodes_committee::*; pub use circuits::*; +pub use crt::*; pub use packing::*; pub use registry::*; pub use ring::*; From f2e339b3ecec9643b98944080cfab62055ade393 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 18:06:34 +0100 Subject: [PATCH 07/10] generate_sample using the same input in user_data_enc --- crates/zk-helpers/src/bin/zk_cli.rs | 12 +++------ .../threshold/user_data_encryption/codegen.rs | 13 +++------- .../threshold/user_data_encryption/mod.rs | 1 - .../threshold/user_data_encryption/sample.rs | 17 +++++-------- .../threshold/user_data_encryption/utils.rs | 25 +++++-------------- 5 files changed, 19 insertions(+), 49 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index baf05a86ee..2a457c0756 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -31,7 +31,7 @@ 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::user_data_encryption::{ - UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, UserDataEncryptionSample, + UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, }; use e3_zk_helpers::{PkSample, ShareComputationSample}; use std::io::Write; @@ -315,16 +315,10 @@ fn main() -> Result<()> { )? } name if name == ::NAME => { - let sample = UserDataEncryptionSample::generate(preset); + let sample = UserDataEncryptionCircuitInput::generate_sample(preset); let circuit = UserDataEncryptionCircuit; - circuit.codegen( - preset, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key, - plaintext: sample.plaintext, - }, - )? + circuit.codegen(preset, &sample)? } name if name == ::NAME => { let sample = PkGenerationCircuitInput::generate_sample( diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs index b6aea6491b..fbee84ad23 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs @@ -203,23 +203,18 @@ mod tests { use super::*; use crate::circuits::computation::Computation; use crate::codegen::write_artifacts; + use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use crate::threshold::user_data_encryption::computation::{Bits, Bounds}; - use crate::threshold::user_data_encryption::sample::UserDataEncryptionSample; use e3_fhe_params::BfvPreset; use tempfile::TempDir; #[test] fn test_toml_generation_and_structure() { - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); let artifacts = UserDataEncryptionCircuit - .codegen( - BfvPreset::InsecureThreshold512, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key, - plaintext: sample.plaintext, - }, - ) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs index aac70235fb..1f52058a87 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs @@ -18,5 +18,4 @@ pub mod utils; pub use circuit::*; pub use codegen::*; pub use computation::*; -pub use sample::*; pub use utils::*; diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs index f77b310e4c..63b9785555 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs @@ -9,21 +9,15 @@ //! [`Sample`] produces a random BFV key pair and plaintext; the public key and plaintext are used as input //! for codegen and tests. +use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::FheEncoder; use rand::thread_rng; -/// A sample BFV public key and plaintext for user data encryption circuit codegen or tests. -#[derive(Debug, Clone)] -pub struct UserDataEncryptionSample { - pub public_key: PublicKey, - pub plaintext: Plaintext, -} - -impl UserDataEncryptionSample { +impl UserDataEncryptionCircuitInput { /// Generates a random secret key, public key, and plaintext for the given BFV parameters. - pub fn generate(preset: BfvPreset) -> Self { + pub fn generate_sample(preset: BfvPreset) -> Self { let (threshold_params, _) = build_pair_for_preset(preset).unwrap(); let mut rng = thread_rng(); @@ -43,12 +37,13 @@ impl UserDataEncryptionSample { #[cfg(test)] mod tests { - use super::*; + use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_fhe_params::BfvPreset; #[test] fn test_generate_sample() { - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); assert_eq!(sample.public_key.c.c.len(), 2); assert_eq!( 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 3f0dd81b98..9ec418fef4 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 @@ -164,7 +164,6 @@ mod tests { use super::*; use crate::circuits::computation::Computation; use crate::threshold::user_data_encryption::computation::Witness; - use crate::threshold::user_data_encryption::sample::UserDataEncryptionSample; use crate::threshold::user_data_encryption::UserDataEncryptionCircuitInput; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use fhe_traits::DeserializeParametrized; @@ -172,16 +171,10 @@ mod tests { #[test] fn test_bfv_public_key_to_greco() { let (threshold_params, _) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); - let witness = Witness::compute( - BfvPreset::InsecureThreshold512, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key.clone(), - plaintext: sample.plaintext, - }, - ) - .unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); // Convert using our function let (actual_pk0is, actual_pk1is) = @@ -196,16 +189,10 @@ mod tests { fn test_bfv_ciphertext_to_greco() { let (threshold_params, _) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); - let witness = Witness::compute( - BfvPreset::InsecureThreshold512, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key.clone(), - plaintext: sample.plaintext, - }, - ) - .unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let ciphertext = Ciphertext::from_bytes(&witness.ciphertext, &threshold_params).unwrap(); From 624186f58c19e4c4742b19afd92172d9e5b675a7 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 6 Feb 2026 18:26:23 +0100 Subject: [PATCH 08/10] simplify and uniform the samples --- crates/zk-helpers/src/bin/zk_cli.rs | 79 ++++------------- crates/zk-helpers/src/circuits/computation.rs | 2 +- .../zk-helpers/src/circuits/dkg/pk/codegen.rs | 15 +--- crates/zk-helpers/src/circuits/dkg/pk/mod.rs | 1 - .../zk-helpers/src/circuits/dkg/pk/sample.rs | 42 ++------- .../circuits/dkg/share_computation/codegen.rs | 23 +---- .../dkg/share_computation/computation.rs | 32 ++----- .../src/circuits/dkg/share_computation/mod.rs | 2 +- .../circuits/dkg/share_computation/sample.rs | 88 +++++-------------- .../circuits/dkg/share_decryption/codegen.rs | 23 ++--- .../dkg/share_decryption/computation.rs | 30 ++----- .../src/circuits/dkg/share_decryption/mod.rs | 1 - .../circuits/dkg/share_decryption/sample.rs | 73 +++------------ .../circuits/dkg/share_encryption/codegen.rs | 35 +++----- .../dkg/share_encryption/computation.rs | 41 ++++----- .../src/circuits/dkg/share_encryption/mod.rs | 1 - .../circuits/dkg/share_encryption/sample.rs | 53 +++-------- crates/zk-helpers/src/circuits/mod.rs | 7 -- 18 files changed, 125 insertions(+), 423 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 2a457c0756..2138dc48a2 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -20,12 +20,8 @@ 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, ShareDecryptionSample, -}; -use e3_zk_helpers::dkg::share_encryption::{ - ShareEncryptionCircuit, ShareEncryptionCircuitInput, ShareEncryptionSample, -}; +use e3_zk_helpers::dkg::share_decryption::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; +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; @@ -33,7 +29,6 @@ use e3_zk_helpers::threshold::pk_generation::{PkGenerationCircuit, PkGenerationC use e3_zk_helpers::threshold::user_data_encryption::{ UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, }; -use e3_zk_helpers::{PkSample, ShareComputationSample}; use std::io::Write; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; @@ -254,70 +249,38 @@ fn main() -> Result<()> { let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { name if name == ::NAME => { - let sample = PkSample::generate(preset, CiphernodesCommitteeSize::Small); + let sample = PkCircuitInput::generate_sample(preset); + let circuit = PkCircuit; - circuit.codegen( - preset, - &PkCircuitInput { - public_key: sample.dkg_public_key, - }, - )? + circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = ShareComputationSample::generate( + let sample = ShareComputationCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, - sd.z, - sd.lambda, ); let circuit = ShareComputationCircuit; - circuit.codegen( - preset, - &ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample.parity_matrix.clone(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - }, - )? + circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = ShareEncryptionSample::generate( + let sd = preset.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, sd.z, sd.lambda, ); - let circuit = ShareEncryptionCircuit; - circuit.codegen( - preset, - &ShareEncryptionCircuitInput { - plaintext: sample.plaintext, - ciphertext: sample.ciphertext, - public_key: sample.public_key, - secret_key: sample.secret_key, - u_rns: sample.u_rns, - e0_rns: sample.e0_rns, - e1_rns: sample.e1_rns, - }, - )? + let circuit = ShareEncryptionCircuit; + circuit.codegen(preset, &sample)? } name if name == ::NAME => { let sample = UserDataEncryptionCircuitInput::generate_sample(preset); - let circuit = UserDataEncryptionCircuit; + let circuit = UserDataEncryptionCircuit; circuit.codegen(preset, &sample)? } name if name == ::NAME => { @@ -325,34 +288,26 @@ fn main() -> Result<()> { preset, CiphernodesCommitteeSize::Small.values(), )?; + let circuit = PkGenerationCircuit; circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = ShareDecryptionSample::generate( + let sample = ShareDecryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, - sd.z, - sd.lambda, ); + let circuit = ShareDecryptionCircuit; - circuit.codegen( - preset, - &ShareDecryptionCircuitInput { - secret_key: sample.secret_key, - honest_ciphertexts: sample.honest_ciphertexts, - }, - )? + circuit.codegen(preset, &sample)? } name if name == ::NAME => { let sample = PkAggregationCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small.values(), )?; + let circuit = PkAggregationCircuit; circuit.codegen(preset, &sample)? } diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index 1eed828568..d3ae2aa0f5 100644 --- a/crates/zk-helpers/src/circuits/computation.rs +++ b/crates/zk-helpers/src/circuits/computation.rs @@ -11,7 +11,7 @@ //! [`Toml`] and [`Configs`] are the string types used for Prover.toml and configs.nr. /// Variant for input types for DKG. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DkgInputType { /// The input type that generates shares of a secret key using secret sharing. SecretKey, diff --git a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 152601e60e..8eb9a8f426 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -71,9 +71,8 @@ pub global {}_BIT_PK: u32 = {}; #[cfg(test)] mod tests { use super::*; - use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::codegen::write_artifacts; - use crate::prepare_pk_sample_for_test; + use crate::dkg::pk::PkCircuitInput; use crate::utils::compute_pk_bit; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; @@ -82,18 +81,10 @@ mod tests { #[test] fn test_toml_generation_and_structure() { let (_, dkg_params) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = prepare_pk_sample_for_test( - BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, - ); + let sample = PkCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); let artifacts = PkCircuit - .codegen( - BfvPreset::InsecureThreshold512, - &PkCircuitInput { - public_key: sample.dkg_public_key, - }, - ) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs index 6cb4b8a4f6..deb60507dc 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs @@ -12,4 +12,3 @@ pub mod sample; pub use circuit::{PkCircuit, PkCircuitInput}; pub use codegen::{generate_configs, generate_toml}; pub use computation::{Bits, Bounds, Configs, PkComputationOutput, Witness}; -pub use sample::{prepare_pk_sample_for_test, PkSample}; diff --git a/crates/zk-helpers/src/circuits/dkg/pk/sample.rs b/crates/zk-helpers/src/circuits/dkg/pk/sample.rs index ed87a576c6..f43f6cb988 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/sample.rs @@ -6,64 +6,36 @@ //! Sample data generation for the pk circuit: committee and DKG public key only. -use crate::ciphernodes_committee::CiphernodesCommittee; -use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::dkg::pk::PkCircuitInput; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; use fhe::bfv::{PublicKey, SecretKey}; use rand::thread_rng; -/// Sample data for the **pk** circuit: committee and DKG public key only. -#[derive(Debug, Clone)] -pub struct PkSample { - /// Committee information. - pub committee: CiphernodesCommittee, - /// DKG BFV public key. - pub dkg_public_key: PublicKey, -} - -impl PkSample { +impl PkCircuitInput { /// Generates sample data for the pk circuit. - pub fn generate(preset: BfvPreset, committee_size: CiphernodesCommitteeSize) -> Self { + pub fn generate_sample(preset: BfvPreset) -> Self { let (_, 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); Self { - committee, - dkg_public_key, + public_key: dkg_public_key, } } } -/// Prepares a pk sample for testing using a threshold preset (DKG params come from its pair). -pub fn prepare_pk_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, -) -> PkSample { - PkSample::generate(preset, committee) -} - #[cfg(test)] mod tests { - use super::*; - use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::dkg::pk::PkCircuitInput; use e3_fhe_params::BfvPreset; #[test] fn test_generate_pk_sample() { - let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_pk_sample_for_test( - BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, - ); + let sample = PkCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); - assert_eq!(sample.committee.n, committee.n); - assert_eq!(sample.committee.threshold, committee.threshold); - assert_eq!(sample.committee.h, committee.h); - assert_eq!(sample.dkg_public_key.c.c.len(), 2); + assert_eq!(sample.public_key.c.c.len(), 2); } } 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 6a01df7c1d..f1f3344de0 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -148,36 +148,19 @@ mod tests { use crate::codegen::write_artifacts; use crate::computation::DkgInputType; use crate::Circuit; - use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use e3_fhe_params::BfvPreset; use tempfile::TempDir; - fn share_computation_input_from_sample( - sample: &ShareComputationSample, - dkg_input_type: DkgInputType, - ) -> ShareComputationCircuitInput { - ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample.parity_matrix.clone(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - } - } - #[test] fn test_toml_generation_and_structure() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); - let artifacts = ShareComputationCircuit - .codegen(BfvPreset::InsecureThreshold512, &input) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -210,7 +193,7 @@ mod tests { assert!(configs_path.exists()); let configs_content = std::fs::read_to_string(&configs_path).unwrap(); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let prefix = ::PREFIX; 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 304e193f6d..d60d10212a 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -267,33 +267,16 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; use crate::dkg::share_computation::ShareComputationCircuitInput; - use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use e3_fhe_params::BfvPreset; - fn share_computation_input_from_sample( - sample: &ShareComputationSample, - dkg_input_type: DkgInputType, - ) -> ShareComputationCircuitInput { - ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample.parity_matrix.clone(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - } - } - #[test] fn test_bound_and_bits_computation_consistency() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - - let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let expected_sk_bits = calculate_bit_width(BigInt::from(bounds.sk_bound.clone())); @@ -302,14 +285,12 @@ mod tests { #[test] fn test_witness_smudging_noise_secret_consistency() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SmudgingNoise, ); - - let input = share_computation_input_from_sample(&sample, DkgInputType::SmudgingNoise); - let witness = Witness::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let degree = witness.secret_crt.limb(0).coefficients().len(); let num_moduli = witness.secret_crt.limbs.len(); for coeff_idx in 0..degree { @@ -327,14 +308,13 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); - let constants = Configs::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs index 85fbb5cbf1..d1105ce5d0 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -12,4 +12,4 @@ pub mod utils; pub use circuit::{ShareComputationCircuit, ShareComputationCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareComputationOutput, Witness}; -pub use sample::{prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample}; +pub use sample::SecretShares; 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 f6a9006ef1..ad5fc4732e 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -7,56 +7,33 @@ //! 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::CiphernodesCommittee; 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::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use e3_parity_matrix::ParityMatrix; use e3_polynomial::CrtPolynomial; -use fhe::bfv::{PublicKey, SecretKey}; +use fhe::bfv::SecretKey; use fhe::trbfv::{ShareManager, TRBFV}; use num_bigint::BigInt; use rand::thread_rng; -/// Shamir secret shares: one limb per CRT modulus (rows = parties, cols = polynomial coefficients). pub type SecretShares = Vec>; -/// Sample data for the **share-computation** circuit: committee, DKG public key, secret in CRT form, -/// Shamir shares, and parity matrices (secret-key or smudging-noise). -#[derive(Debug, Clone)] -pub struct ShareComputationSample { - /// Committee information. - pub committee: CiphernodesCommittee, - /// DKG BFV public key. - pub dkg_public_key: PublicKey, - /// Secret in CRT form (SK or smudging noise). - pub secret: CrtPolynomial, - /// Secret shares (one [`ndarray::Array2`] per modulus). - pub secret_sss: SecretShares, - /// Parity check matrix per modulus (null space of generator). - pub parity_matrix: Vec, -} - -impl ShareComputationSample { +impl ShareComputationCircuitInput { /// Generates sample data for the share-computation circuit. - pub fn generate( + pub fn generate_sample( preset: BfvPreset, committee_size: CiphernodesCommitteeSize, dkg_input_type: DkgInputType, - num_ciphertexts: u128, // z in the search defaults - lambda: u32, ) -> Self { - let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap(); - + 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 dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); - 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)); let mut share_manager = @@ -96,7 +73,7 @@ impl ShareComputationSample { } DkgInputType::SmudgingNoise => { let esi_coeffs = trbfv - .generate_smudging_error(num_ciphertexts as usize, lambda as usize, &mut rng) + .generate_smudging_error(committee.n as usize, sd.lambda as usize, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", @@ -125,8 +102,9 @@ impl ShareComputationSample { }; Self { - committee, - dkg_public_key, + dkg_input_type, + n_parties: committee.n as u32, + threshold: committee.threshold as u32, secret, secret_sss, parity_matrix, @@ -134,59 +112,39 @@ impl ShareComputationSample { } } -/// Prepares a share-computation sample for testing using a threshold preset. -pub fn prepare_share_computation_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, - dkg_input_type: DkgInputType, -) -> ShareComputationSample { - let defaults = preset.search_defaults().unwrap(); - - ShareComputationSample::generate( - preset, - committee, - dkg_input_type, - defaults.z, - defaults.lambda, - ) -} - #[cfg(test)] mod tests { - use super::*; use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; + use crate::dkg::share_computation::ShareComputationCircuitInput; use e3_fhe_params::BfvPreset; #[test] fn test_generate_secret_key_sample() { - let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_share_computation_sample_for_test( + let committee_size = CiphernodesCommitteeSize::Small; + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee_size, DkgInputType::SecretKey, ); - - assert_eq!(sample.committee.n, committee.n); - assert_eq!(sample.committee.threshold, committee.threshold); - assert_eq!(sample.committee.h, committee.h); - assert_eq!(sample.dkg_public_key.c.c.len(), 2); + assert_eq!(sample.n_parties, committee_size.values().n as u32); + assert_eq!(sample.threshold, committee_size.values().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); } #[test] fn test_generate_smudging_noise_sample() { - let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_share_computation_sample_for_test( + let committee_size = CiphernodesCommitteeSize::Small; + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee_size, DkgInputType::SmudgingNoise, ); - - assert_eq!(sample.committee.n, committee.n); - assert_eq!(sample.committee.threshold, committee.threshold); - assert_eq!(sample.dkg_public_key.c.c.len(), 2); + assert_eq!(sample.n_parties, committee_size.values().n as u32); + assert_eq!(sample.threshold, committee_size.values().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/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs index 4e6b65ea8c..3dc375b33d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs @@ -78,32 +78,20 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::dkg::share_decryption::{Configs, ShareDecryptionCircuitInput}; use crate::computation::{Computation, DkgInputType}; - use crate::dkg::share_decryption::sample::prepare_share_decryption_sample_for_test; - use crate::dkg::share_decryption::ShareDecryptionSample; use crate::Circuit; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; - fn share_decryption_input_from_sample( - sample: &ShareDecryptionSample, - ) -> ShareDecryptionCircuitInput { - ShareDecryptionCircuitInput { - secret_key: sample.secret_key.clone(), - honest_ciphertexts: sample.honest_ciphertexts.clone(), - } - } - #[test] fn test_toml_generation_and_structure() { - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_decryption_input_from_sample(&sample); let artifacts = ShareDecryptionCircuit - .codegen(DEFAULT_BFV_PRESET, &input) + .codegen(DEFAULT_BFV_PRESET, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -113,18 +101,17 @@ mod tests { #[test] fn test_configs_generation_contains_expected() { - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_decryption_input_from_sample(&sample); let artifacts = ShareDecryptionCircuit - .codegen(DEFAULT_BFV_PRESET, &input) + .codegen(DEFAULT_BFV_PRESET, &sample) .unwrap(); - let configs = Configs::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let configs = Configs::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); let prefix = ::PREFIX; assert!(artifacts .configs 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 29db2b4434..2c7b079e7d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs @@ -219,29 +219,17 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; - use crate::dkg::share_decryption::sample::prepare_share_decryption_sample_for_test; - use crate::dkg::share_decryption::ShareDecryptionSample; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; - fn share_decryption_input_from_sample( - sample: &ShareDecryptionSample, - ) -> ShareDecryptionCircuitInput { - ShareDecryptionCircuitInput { - secret_key: sample.secret_key.clone(), - honest_ciphertexts: sample.honest_ciphertexts.clone(), - } - } - #[test] fn test_bound_and_bits_computation_consistency() { - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_decryption_input_from_sample(&sample); - let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); @@ -251,13 +239,12 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_decryption_input_from_sample(&sample); - let constants = Configs::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let constants = Configs::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); @@ -271,22 +258,21 @@ mod tests { #[test] fn test_witness_decryption_consistency() { - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_decryption_input_from_sample(&sample); - let witness = Witness::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let witness = Witness::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); // Witness should have one row per honest party assert_eq!( witness.expected_commitments.len(), - input.honest_ciphertexts.len() + sample.honest_ciphertexts.len() ); assert_eq!( witness.decrypted_shares.len(), - input.honest_ciphertexts.len() + sample.honest_ciphertexts.len() ); } } diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs index b630c01e08..5313a42a56 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs @@ -12,4 +12,3 @@ pub mod computation; pub mod sample; pub use circuit::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; -pub use sample::{prepare_share_decryption_sample_for_test, ShareDecryptionSample}; 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 9c9698915e..08533c8085 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -7,6 +7,7 @@ //! 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::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; @@ -16,39 +17,22 @@ use fhe::bfv::Encoding; use fhe::bfv::Plaintext; use fhe::bfv::{PublicKey, SecretKey}; use fhe::trbfv::{ShareManager, TRBFV}; -use fhe_traits::FheDecrypter; use fhe_traits::FheEncoder; use fhe_traits::FheEncrypter; use rand::thread_rng; -/// Sample data for the share-decryption circuit: honest ciphertexts, sum ciphertexts, secret key, and message. -#[derive(Debug, Clone)] -pub struct ShareDecryptionSample { - /// H honest party ciphertexts (multiple encrypted shares from different parties) - /// Structure: honest_ciphertexts[party_idx][trbfv_basis] - pub honest_ciphertexts: Vec>, - /// The sum of all honest ciphertexts per TRBFV basis (what we're actually decrypting) - /// Structure: sum_ciphertexts[trbfv_basis] - pub sum_ciphertexts: Vec, - /// BFV secret key used for decryption (private witness) - pub secret_key: SecretKey, - /// The decrypted message (aggregate share values) - same for all TRBFV bases - pub message: Plaintext, -} - -impl ShareDecryptionSample { +impl ShareDecryptionCircuitInput { /// Generates sample data for the share-decryption circuit (decrypts a sum of honest ciphertexts under DKG secret key). - pub fn generate( + pub fn generate_sample( preset: BfvPreset, - committee_size: CiphernodesCommitteeSize, + committee: CiphernodesCommitteeSize, dkg_input_type: DkgInputType, - num_ciphertexts: u128, // z in the search defaults - lambda: u32, ) -> Self { let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap(); + let sd = preset.search_defaults().unwrap(); let mut rng = thread_rng(); - let committee = committee_size.values(); + 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); @@ -79,11 +63,7 @@ impl ShareDecryptionSample { } DkgInputType::SmudgingNoise => { let esi_coeffs = trbfv - .generate_smudging_error( - num_ciphertexts as usize, - lambda as usize, - &mut rng, - ) + .generate_smudging_error(sd.z as usize, sd.lambda as usize, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", @@ -126,36 +106,13 @@ impl ShareDecryptionSample { sum_ciphertexts.push(sum_ct); } - // Decrypt the sum for the first TRBFV basis to get the aggregate plaintext - // (The message should be the same for all TRBFV bases since we're decrypting the same aggregate) - let decrypted_pt = dkg_secret_key.try_decrypt(&sum_ciphertexts[0]).unwrap(); - - ShareDecryptionSample { + ShareDecryptionCircuitInput { honest_ciphertexts, - sum_ciphertexts, secret_key: dkg_secret_key, - message: decrypted_pt, } } } -/// Prepares a share-decryption sample for testing using a threshold preset. -pub fn prepare_share_decryption_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, - dkg_input_type: DkgInputType, -) -> ShareDecryptionSample { - let defaults = preset.search_defaults().unwrap(); - - ShareDecryptionSample::generate( - preset, - committee, - dkg_input_type, - defaults.z, - defaults.lambda, - ) -} - #[cfg(test)] mod tests { use super::*; @@ -166,42 +123,32 @@ mod tests { #[test] fn test_generate_secret_key_sample() { let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); assert_eq!(sample.honest_ciphertexts.len(), committee.n); - assert_eq!(sample.sum_ciphertexts.len(), committee.threshold); assert_eq!( sample.secret_key.coeffs.len(), DEFAULT_BFV_PRESET.metadata().degree ); - assert_eq!( - sample.message.value.len(), - DEFAULT_BFV_PRESET.metadata().degree - ); } #[test] fn test_generate_smudging_noise_sample() { let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_share_decryption_sample_for_test( + let sample = ShareDecryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SmudgingNoise, ); assert_eq!(sample.honest_ciphertexts.len(), committee.n); - assert_eq!(sample.sum_ciphertexts.len(), committee.threshold); assert_eq!( sample.secret_key.coeffs.len(), DEFAULT_BFV_PRESET.metadata().degree ); - assert_eq!( - sample.message.value.len(), - DEFAULT_BFV_PRESET.metadata().degree - ); } } 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 33aacc1a9c..06c000c8b3 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs @@ -191,36 +191,21 @@ mod tests { use crate::circuits::dkg::share_encryption::{Bounds, ShareEncryptionCircuitInput}; use crate::computation::Computation; use crate::computation::DkgInputType; - use crate::dkg::share_encryption::sample::prepare_share_encryption_sample_for_test; - use crate::dkg::share_encryption::ShareEncryptionSample; use crate::Circuit; use e3_fhe_params::BfvPreset; - fn share_encryption_input_from_sample( - sample: &ShareEncryptionSample, - ) -> ShareEncryptionCircuitInput { - ShareEncryptionCircuitInput { - plaintext: sample.plaintext.clone(), - ciphertext: sample.ciphertext.clone(), - public_key: sample.public_key.clone(), - secret_key: sample.secret_key.clone(), - u_rns: sample.u_rns.clone(), - e0_rns: sample.e0_rns.clone(), - e1_rns: sample.e1_rns.clone(), - } - } - #[test] fn test_toml_generation_and_structure() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let artifacts = ShareEncryptionCircuit - .codegen(BfvPreset::InsecureThreshold512, &input) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -232,18 +217,20 @@ mod tests { #[test] fn test_configs_generation_contains_expected() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); let artifacts = ShareEncryptionCircuit - .codegen(BfvPreset::InsecureThreshold512, &input) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = crate::circuits::dkg::share_encryption::Bits::compute( BfvPreset::InsecureThreshold512, &bounds, 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 c61c1ae276..07ad48542d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -749,33 +749,20 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; - use crate::dkg::share_encryption::sample::prepare_share_encryption_sample_for_test; - use crate::dkg::share_encryption::ShareEncryptionSample; use e3_fhe_params::BfvPreset; - fn share_encryption_input_from_sample( - sample: &ShareEncryptionSample, - ) -> ShareEncryptionCircuitInput { - ShareEncryptionCircuitInput { - plaintext: sample.plaintext.clone(), - ciphertext: sample.ciphertext.clone(), - public_key: sample.public_key.clone(), - secret_key: sample.secret_key.clone(), - u_rns: sample.u_rns.clone(), - e0_rns: sample.e0_rns.clone(), - e1_rns: sample.e1_rns.clone(), - } - } - #[test] fn test_bound_and_bits_computation_consistency() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let max_pk_bound = bounds.pk_bounds.iter().max().unwrap(); @@ -787,13 +774,15 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let constants = Configs::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); @@ -808,13 +797,15 @@ mod tests { #[test] fn test_witness_message_consistency() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let witness = Witness::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); // witness.message is plaintext coefficients (reversed, as used in circuit) let expected_message = Polynomial::from_u64_vector(sample.plaintext.value.deref().to_vec()); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs index 3446225c16..a52d988af2 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs @@ -12,4 +12,3 @@ pub mod computation; pub mod sample; pub use circuit::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; -pub use sample::{prepare_share_encryption_sample_for_test, ShareEncryptionSample}; 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 888f8d140a..3cbc963dbe 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -8,34 +8,21 @@ //! 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::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use fhe::bfv::Ciphertext; use fhe::bfv::Encoding; use fhe::bfv::Plaintext; use fhe::bfv::{PublicKey, SecretKey}; use fhe::trbfv::{ShareManager, TRBFV}; -use fhe_math::rq::Poly; use fhe_traits::FheEncoder; use rand::thread_rng; -/// Sample data for the share-encryption circuit: plaintext, ciphertext, keys, and RNS randomness. -#[derive(Debug, Clone)] -pub struct ShareEncryptionSample { - pub plaintext: Plaintext, - pub ciphertext: Ciphertext, - pub public_key: PublicKey, - pub secret_key: SecretKey, - pub u_rns: Poly, - pub e0_rns: Poly, - pub e1_rns: Poly, -} - -impl ShareEncryptionSample { +impl ShareEncryptionCircuitInput { /// Generates sample data for the share-encryption circuit (encrypts a share row under DKG pk). - pub fn generate( + pub fn generate_sample( preset: BfvPreset, committee_size: CiphernodesCommitteeSize, dkg_input_type: DkgInputType, @@ -50,8 +37,7 @@ impl ShareEncryptionSample { let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); 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)); + let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()).unwrap(); let mut share_manager = ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); @@ -96,7 +82,7 @@ impl ShareEncryptionSample { let (_ct, u_rns, e0_rns, e1_rns) = dkg_public_key.try_encrypt_extended(&pt, &mut rng).unwrap(); - ShareEncryptionSample { + ShareEncryptionCircuitInput { plaintext: pt, ciphertext: _ct, public_key: dkg_public_key, @@ -108,23 +94,6 @@ impl ShareEncryptionSample { } } -/// Prepares a share-encryption sample for testing using a threshold preset. -pub fn prepare_share_encryption_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, - dkg_input_type: DkgInputType, -) -> ShareEncryptionSample { - let defaults = preset.search_defaults().unwrap(); - - ShareEncryptionSample::generate( - preset, - committee, - dkg_input_type, - defaults.z, - defaults.lambda, - ) -} - #[cfg(test)] mod tests { use super::*; @@ -134,10 +103,13 @@ mod tests { #[test] fn test_generate_secret_key_sample() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); assert_eq!(sample.public_key.c.c.len(), 2); @@ -162,10 +134,13 @@ mod tests { #[test] fn test_generate_smudging_noise_sample() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - DkgInputType::SmudgingNoise, + DkgInputType::SecretKey, + sd.z, + sd.lambda, ); assert_eq!(sample.public_key.c.c.len(), 2); diff --git a/crates/zk-helpers/src/circuits/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index 09ce541a62..f63ddfae9a 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -15,11 +15,4 @@ pub use computation::{CircuitComputation, Computation}; pub use errors::CircuitsErrors; pub mod dkg; -pub use dkg::pk::codegen::{generate_configs, generate_toml}; -pub use dkg::pk::computation::{Bits, Bounds, PkComputationOutput, Witness}; -pub use dkg::pk::{prepare_pk_sample_for_test, PkCircuit, PkSample}; -pub use dkg::share_computation::{ - prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample, -}; - pub mod threshold; From 85b818b1e8bd1eacbde094a9ef1d27abbe6fb77f Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 9 Feb 2026 10:57:36 +0100 Subject: [PATCH 09/10] amend coderabbit changes --- crates/zk-helpers/src/bin/zk_cli.rs | 4 +- .../circuits/dkg/share_decryption/codegen.rs | 7 ++- .../dkg/share_decryption/computation.rs | 51 ++++++++++--------- .../circuits/dkg/share_decryption/sample.rs | 18 ++----- .../threshold/pk_generation/codegen.rs | 29 +++++++---- .../threshold/pk_generation/sample.rs | 7 +-- 6 files changed, 61 insertions(+), 55 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 2138dc48a2..7c50e14de4 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -265,7 +265,9 @@ fn main() -> Result<()> { circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset.search_defaults().unwrap(); + let sd = preset.search_defaults().ok_or_else(|| { + anyhow!("preset does not define search defaults for {}", name) + })?; let sample = ShareEncryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, 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 3dc375b33d..990c1dbd83 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs @@ -80,7 +80,6 @@ mod tests { use crate::computation::{Computation, DkgInputType}; use crate::Circuit; use e3_fhe_params::BfvPreset; - use e3_fhe_params::DEFAULT_BFV_PRESET; #[test] fn test_toml_generation_and_structure() { @@ -91,7 +90,7 @@ mod tests { ); let artifacts = ShareDecryptionCircuit - .codegen(DEFAULT_BFV_PRESET, &sample) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -108,10 +107,10 @@ mod tests { ); let artifacts = ShareDecryptionCircuit - .codegen(DEFAULT_BFV_PRESET, &sample) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); - let configs = Configs::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); + let configs = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let prefix = ::PREFIX; assert!(artifacts .configs 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 2c7b079e7d..b446530d98 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs @@ -159,24 +159,30 @@ impl Computation for Witness { // Decrypt each ciphertext and compute its commitment for party_cts in input.honest_ciphertexts.iter() { - let mut party_commitments = Vec::new(); - let mut party_shares = Vec::new(); + if party_cts.len() < threshold_l { + return Err(CircuitsErrors::Other(format!( + "honest_ciphertexts party has {} ciphertexts but threshold_l is {}; \ + each party must have at least threshold_l ciphertexts", + party_cts.len(), + threshold_l + ))); + } + let mut party_commitments = Vec::with_capacity(threshold_l); + let mut party_shares = Vec::with_capacity(threshold_l); for mod_idx in 0..threshold_l { - if mod_idx < party_cts.len() { - // Decrypt the ciphertext to get the plaintext share - let decrypted_pt = input.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap(); - let share_coeffs = decrypted_pt.value.deref().to_vec(); - party_commitments.push(compute_share_encryption_commitment_from_message( - &Polynomial::from_u64_vector(share_coeffs.clone()), - msg_bit, - )); - party_shares.push( - share_coeffs - .iter() - .map(|c| BigInt::from(*c)) - .collect::>(), - ); - } + // Decrypt the ciphertext to get the plaintext share + let decrypted_pt = input.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap(); + let share_coeffs = decrypted_pt.value.deref().to_vec(); + party_commitments.push(compute_share_encryption_commitment_from_message( + &Polynomial::from_u64_vector(share_coeffs.clone()), + msg_bit, + )); + party_shares.push( + share_coeffs + .iter() + .map(|c| BigInt::from(*c)) + .collect::>(), + ); } expected_commitments.push(party_commitments); decrypted_shares.push(party_shares); @@ -220,7 +226,6 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; use e3_fhe_params::BfvPreset; - use e3_fhe_params::DEFAULT_BFV_PRESET; #[test] fn test_bound_and_bits_computation_consistency() { @@ -229,10 +234,10 @@ mod tests { CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); - let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); - let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); + let (_, dkg_params) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); let expected_msg_bit = calculate_bit_width(BigInt::from(dkg_params.plaintext())); assert_eq!(bits.msg_bit, expected_msg_bit); } @@ -244,7 +249,7 @@ mod tests { CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let constants = Configs::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); @@ -263,7 +268,7 @@ mod tests { CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let witness = Witness::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); // Witness should have one row per honest party assert_eq!( 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 08533c8085..3030019e0c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -94,18 +94,6 @@ impl ShareDecryptionCircuitInput { honest_ciphertexts.push(party_cts); } - // Compute the sum of all honest ciphertexts per TRBFV basis (homomorphic addition) - // For each TRBFV basis: sum_ct[l] = ct_1[l] + ct_2[l] + ... + ct_H[l] - let mut sum_ciphertexts: Vec = Vec::new(); - let num_moduli = threshold_params.moduli().len(); - for trbfv_basis_idx in 0..num_moduli { - let mut sum_ct = honest_ciphertexts[0][trbfv_basis_idx].clone(); - for party_idx in 1..honest_ciphertexts.len() { - sum_ct = &sum_ct + &honest_ciphertexts[party_idx][trbfv_basis_idx]; - } - sum_ciphertexts.push(sum_ct); - } - ShareDecryptionCircuitInput { honest_ciphertexts, secret_key: dkg_secret_key, @@ -118,7 +106,7 @@ mod tests { use super::*; use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; - use e3_fhe_params::{BfvPreset, DEFAULT_BFV_PRESET}; + use e3_fhe_params::BfvPreset; #[test] fn test_generate_secret_key_sample() { @@ -132,7 +120,7 @@ mod tests { assert_eq!(sample.honest_ciphertexts.len(), committee.n); assert_eq!( sample.secret_key.coeffs.len(), - DEFAULT_BFV_PRESET.metadata().degree + BfvPreset::InsecureThreshold512.metadata().degree ); } @@ -148,7 +136,7 @@ mod tests { assert_eq!(sample.honest_ciphertexts.len(), committee.n); assert_eq!( sample.secret_key.coeffs.len(), - DEFAULT_BFV_PRESET.metadata().degree + BfvPreset::InsecureThreshold512.metadata().degree ); } } diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs index 395f268b1f..abf295203e 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs @@ -130,16 +130,17 @@ mod tests { use crate::threshold::pk_generation::PkGenerationCircuitInput; use crate::CiphernodesCommitteeSize; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::BfvPreset; use tempfile::TempDir; #[test] fn test_toml_generation_and_structure() { let committee = CiphernodesCommitteeSize::Small.values(); let sample = - PkGenerationCircuitInput::generate_sample(DEFAULT_BFV_PRESET, committee).unwrap(); + PkGenerationCircuitInput::generate_sample(BfvPreset::InsecureThreshold512, committee) + .unwrap(); let artifacts = PkGenerationCircuit - .codegen(DEFAULT_BFV_PRESET, &sample) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -176,13 +177,23 @@ mod tests { assert!(configs_path.exists()); let configs_content = std::fs::read_to_string(&configs_path).unwrap(); - let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &sample.committee).unwrap(); - let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample.committee).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); - assert!(configs_content - .contains(format!("N: u32 = {}", DEFAULT_BFV_PRESET.metadata().degree).as_str())); - assert!(configs_content - .contains(format!("L: u32 = {}", DEFAULT_BFV_PRESET.metadata().num_moduli).as_str())); + 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_PK: u32 = {}", diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs index f7c53f8155..315209b937 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs @@ -87,14 +87,15 @@ mod tests { CiphernodesCommitteeSize, }; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::BfvPreset; #[test] fn test_generate_sample() { let committee = CiphernodesCommitteeSize::Small.values(); let sample = - PkGenerationCircuitInput::generate_sample(DEFAULT_BFV_PRESET, committee).unwrap(); - let witness = Witness::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); + PkGenerationCircuitInput::generate_sample(BfvPreset::InsecureThreshold512, committee) + .unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); assert_eq!(witness.pk0is.limbs.len(), 2); assert_eq!(witness.a.limbs.len(), 2); From 63211d665285dd331aecdae6cc17065bafe1f60f Mon Sep 17 00:00:00 2001 From: 0xjei Date: Mon, 9 Feb 2026 11:50:30 +0100 Subject: [PATCH 10/10] update --- Cargo.lock | 32 +++++++++---------- .../threshold/pk_generation/computation.rs | 8 ++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4288fe69d0..01d6e25262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5236,9 +5236,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b00d05442c2106c75b7410f820b152f61ec0edc7befcb9b381b673a20314753" +checksum = "53bf2b0e0785c5394a7392f66d7c4fb9c653633c29b27a932280da3cb344c66a" dependencies = [ "doctest-file", "futures-core", @@ -6083,9 +6083,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -7034,9 +7034,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -8296,9 +8296,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safe-proc-macro2" @@ -9642,9 +9642,9 @@ dependencies = [ [[package]] name = "tracing-test-macro" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" dependencies = [ "quote", "syn 2.0.114", @@ -9716,9 +9716,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-segmentation" @@ -10573,18 +10573,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index e5f2832a1c..81608583cd 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -389,13 +389,13 @@ mod tests { use super::*; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::BfvPreset; #[test] fn test_bound_and_bits_computation_consistency() { let committee = CiphernodesCommitteeSize::Small.values(); - let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &committee).unwrap(); - let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &committee).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let expected_bit = calculate_bit_width(BigInt::from(bounds.pk_bound.clone())); @@ -405,7 +405,7 @@ mod tests { #[test] fn test_constants_json_roundtrip() { let committee = CiphernodesCommitteeSize::Small.values(); - let constants = Configs::compute(DEFAULT_BFV_PRESET, &committee).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &committee).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap();