diff --git a/circuits/lib/src/configs/insecure/threshold.nr b/circuits/lib/src/configs/insecure/threshold.nr index 9d091191fb..b83ac49930 100644 --- a/circuits/lib/src/configs/insecure/threshold.nr +++ b/circuits/lib/src/configs/insecure/threshold.nr @@ -25,22 +25,19 @@ pk_generation (CIRCUIT 1) ------------------------------------- ************************************/ -// pk_generation - bit parameters -pub global PK_GENERATION_BIT_EEK: u32 = 6; -pub global PK_GENERATION_BIT_SK: u32 = 2; -pub global PK_GENERATION_BIT_E_SM: u32 = 18; -pub global PK_GENERATION_BIT_R1: u32 = 14; -pub global PK_GENERATION_BIT_R2: u32 = 36; -pub global PK_GENERATION_BIT_PK: u32 = 36; - -// pk_generation - bounds +pub global PK_GENERATION_BIT_EEK: u32 = 5; +pub global PK_GENERATION_BIT_SK: u32 = 1; +pub global PK_GENERATION_BIT_E_SM: u32 = 17; +pub global PK_GENERATION_BIT_R1: u32 = 13; +pub global PK_GENERATION_BIT_R2: u32 = 35; +pub global PK_GENERATION_BIT_PK: u32 = 35; + pub global PK_GENERATION_EEK_BOUND: Field = 20; pub global PK_GENERATION_SK_BOUND: Field = 1; -pub global PK_GENERATION_E_SM_BOUND: Field = 122892; +pub global PK_GENERATION_E_SM_BOUND: Field = 123072; pub global PK_GENERATION_R1_BOUNDS: [Field; L] = [5120, 5120]; pub global PK_GENERATION_R2_BOUNDS: [Field; L] = [34359701504, 34359615488]; -// pk_generation - configs pub global PK_GENERATION_CONFIGS: PkGenerationConfigs = PkGenerationConfigs::new( QIS, PK_GENERATION_EEK_BOUND, diff --git a/circuits/lib/src/configs/secure/threshold.nr b/circuits/lib/src/configs/secure/threshold.nr index 0c6211f82e..12ecede68e 100644 --- a/circuits/lib/src/configs/secure/threshold.nr +++ b/circuits/lib/src/configs/secure/threshold.nr @@ -28,24 +28,21 @@ pk_generation (CIRCUIT 1) ------------------------------------- ************************************/ -// pk_generation - bit parameters -pub global PK_GENERATION_BIT_EEK: u32 = 6; -pub global PK_GENERATION_BIT_SK: u32 = 2; -pub global PK_GENERATION_BIT_E_SM: u32 = 187; -pub global PK_GENERATION_BIT_R1: u32 = 18; -pub global PK_GENERATION_BIT_R2: u32 = 53; -pub global PK_GENERATION_BIT_PK: u32 = 53; - -// pk_generation - bounds +pub global PK_GENERATION_BIT_EEK: u32 = 5; +pub global PK_GENERATION_BIT_SK: u32 = 1; +pub global PK_GENERATION_BIT_E_SM: u32 = 192; +pub global PK_GENERATION_BIT_R1: u32 = 17; +pub global PK_GENERATION_BIT_R2: u32 = 52; +pub global PK_GENERATION_BIT_PK: u32 = 52; + pub global PK_GENERATION_EEK_BOUND: Field = 20; pub global PK_GENERATION_SK_BOUND: Field = 1; pub global PK_GENERATION_E_SM_BOUND: Field = - 66359003478811654788063765182202739927396573769378037760; + 4789048565205902682369834578696752269343944643454802329600; pub global PK_GENERATION_R1_BOUNDS: [Field; L] = [81920, 81920, 81920, 81920]; pub global PK_GENERATION_R2_BOUNDS: [Field; L] = [1125899911102464, 2251799813881856, 2251799815716864, 2251799817289728]; -// pk_generation - configs pub global PK_GENERATION_CONFIGS: PkGenerationConfigs = PkGenerationConfigs::new( QIS, PK_GENERATION_EEK_BOUND, diff --git a/crates/bfv-client/src/client.rs b/crates/bfv-client/src/client.rs index 7487daff81..7519bd7c96 100644 --- a/crates/bfv-client/src/client.rs +++ b/crates/bfv-client/src/client.rs @@ -6,9 +6,9 @@ use anyhow::{anyhow, Result}; use e3_fhe_params::{build_bfv_params_arc, DEFAULT_BFV_PRESET}; +use e3_zk_helpers::circuits::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_zk_helpers::circuits::threshold::user_data_encryption::Witness as UserDataEncryptionWitness; use e3_zk_helpers::circuits::Computation; -use e3_zk_helpers::threshold::UserDataEncryptionCircuitInput; use fhe::bfv::{Ciphertext, Encoding, Plaintext, PublicKey, SecretKey}; use fhe::Error as FheError; use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize}; diff --git a/crates/polynomial/src/crt_polynomial.rs b/crates/polynomial/src/crt_polynomial.rs index 69a5914e96..ceee0a1247 100644 --- a/crates/polynomial/src/crt_polynomial.rs +++ b/crates/polynomial/src/crt_polynomial.rs @@ -167,6 +167,15 @@ impl CrtPolynomial { } } + /// Adds a limb to the CRT polynomial. + /// + /// # Arguments + /// + /// * `limb` - The limb to add. + pub fn add_limb(&mut self, limb: Polynomial) { + self.limbs.push(limb); + } + /// Returns a reference to the limb polynomial at the given index. /// /// # Arguments diff --git a/crates/polynomial/src/polynomial.rs b/crates/polynomial/src/polynomial.rs index a497631e90..26a5879e8b 100644 --- a/crates/polynomial/src/polynomial.rs +++ b/crates/polynomial/src/polynomial.rs @@ -304,6 +304,11 @@ impl Polynomial { Polynomial::new(product) } + /// Remove a coefficient from the polynomial. + pub fn remove(&mut self, index: usize) { + self.coefficients.remove(index); + } + /// Divides one polynomial by another, returning the quotient and remainder. /// /// # Arguments @@ -396,7 +401,7 @@ impl Polynomial { /// /// # Returns /// - /// Mutates the polynomial in place. + /// A new polynomial containing the remainder. /// /// # Errors /// diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 947de61b98..23d48043c0 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -24,7 +24,8 @@ use e3_zk_helpers::dkg::share_encryption::{ ShareEncryptionCircuit, ShareEncryptionCircuitInput, ShareEncryptionSample, }; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; -use e3_zk_helpers::threshold::{ +use e3_zk_helpers::threshold::pk_generation::{PkGenerationCircuit, PkGenerationCircuitInput}; +use e3_zk_helpers::threshold::user_data_encryption::{ UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, UserDataEncryptionSample, }; use e3_zk_helpers::{PkSample, ShareComputationSample}; @@ -157,6 +158,7 @@ fn main() -> Result<()> { registry.register(Arc::new(ShareComputationCircuit)); registry.register(Arc::new(UserDataEncryptionCircuit)); registry.register(Arc::new(ShareEncryptionCircuit)); + registry.register(Arc::new(PkGenerationCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -316,6 +318,14 @@ fn main() -> Result<()> { }, )? } + name if name == ::NAME => { + let sample = PkGenerationCircuitInput::generate_sample( + preset, + CiphernodesCommitteeSize::Small.values(), + )?; + let circuit = PkGenerationCircuit; + circuit.codegen(preset, &sample)? + } name => return Err(anyhow!("circuit {} not yet implemented", name)), }; diff --git a/crates/zk-helpers/src/circuits/threshold/mod.rs b/crates/zk-helpers/src/circuits/threshold/mod.rs index 1724e4edb3..4d50273c2c 100644 --- a/crates/zk-helpers/src/circuits/threshold/mod.rs +++ b/crates/zk-helpers/src/circuits/threshold/mod.rs @@ -4,5 +4,5 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +pub mod pk_generation; pub mod user_data_encryption; -pub use user_data_encryption::*; diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/circuit.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/circuit.rs new file mode 100644 index 0000000000..d68b553305 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/circuit.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use crate::CiphernodesCommittee; +use e3_fhe_params::ParameterType; +use e3_polynomial::CrtPolynomial; + +#[derive(Debug)] +pub struct PkGenerationCircuit; + +impl Circuit for PkGenerationCircuit { + const NAME: &'static str = "pk-generation"; + const PREFIX: &'static str = "PK_GENERATION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; + const DKG_INPUT_TYPE: Option = None; +} + +#[derive(Debug, Clone)] +pub struct PkGenerationCircuitInput { + pub committee: CiphernodesCommittee, + pub pk_share: CrtPolynomial, + pub a: CrtPolynomial, + pub eek: CrtPolynomial, + pub e_sm: CrtPolynomial, + pub sk: CrtPolynomial, +} diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs new file mode 100644 index 0000000000..395f268b1f --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. + +use e3_fhe_params::BfvPreset; + +use crate::circuits::computation::Computation; +use crate::threshold::pk_generation::circuit::PkGenerationCircuit; +use crate::threshold::pk_generation::computation::{Configs, Witness}; +use crate::threshold::pk_generation::PkGenerationCircuitInput; +use crate::utils::join_display; +use crate::CircuitCodegen; +use crate::CircuitsErrors; +use crate::{Artifacts, CodegenToml}; +use crate::{Circuit, CodegenConfigs}; + +/// Implementation of [`CircuitCodegen`] for [`PkGenerationCircuit`]. +impl CircuitCodegen for PkGenerationCircuit { + type Preset = BfvPreset; + type Input = PkGenerationCircuitInput; + type Error = CircuitsErrors; + + fn codegen(&self, preset: Self::Preset, input: &Self::Input) -> Result { + let witness = Witness::compute(preset, input)?; + let configs = Configs::compute(preset, &input.committee)?; + + let toml = generate_toml(witness)?; + let configs = generate_configs(preset, &configs); + + Ok(Artifacts { toml, configs }) + } +} + +pub fn generate_toml(witness: Witness) -> Result { + let json = witness + .to_json() + .map_err(|e| CircuitsErrors::SerdeJson(e))?; + + Ok(toml::to_string(&json)?) +} + +pub fn generate_configs(_preset: BfvPreset, configs: &Configs) -> CodegenConfigs { + let prefix = ::PREFIX; + + let qis_str = join_display(&configs.moduli, ", "); + + let r1_bounds_str = join_display(&configs.bounds.r1_bounds, ", "); + let r2_bounds_str = join_display(&configs.bounds.r2_bounds, ", "); + + format!( + r#"use crate::core::threshold::pk_generation::Configs as PkGenerationConfigs; + +// Global configs for Threshold Public Key Generation circuit +pub global N: u32 = {}; +pub global L: u32 = {}; +pub global QIS: [Field; L] = [{}]; + +/************************************ +------------------------------------- +pk_generation (CIRCUIT 1 - PUBLIC KEY THRESHOLD BFV) +------------------------------------- +************************************/ + +pub global {}_BIT_EEK: u32 = {}; +pub global {}_BIT_SK: u32 = {}; +pub global {}_BIT_E_SM: u32 = {}; +pub global {}_BIT_R1: u32 = {}; +pub global {}_BIT_R2: u32 = {}; +pub global {}_BIT_PK: u32 = {}; + +pub global {}_EEK_BOUND: Field = {}; +pub global {}_SK_BOUND: Field = {}; +pub global {}_E_SM_BOUND: Field = {}; +pub global {}_R1_BOUNDS: [Field; L] = [{}]; +pub global {}_R2_BOUNDS: [Field; L] = [{}]; + +pub global {}_CONFIGS: PkGenerationConfigs = PkGenerationConfigs::new( +QIS, +{}_EEK_BOUND, +{}_SK_BOUND, +{}_E_SM_BOUND, +{}_R1_BOUNDS, +{}_R2_BOUNDS, +); +"#, + configs.n, + configs.l, + qis_str, + prefix, + configs.bits.eek_bit, + prefix, + configs.bits.sk_bit, + prefix, + configs.bits.e_sm_bit, + prefix, + configs.bits.r1_bit, + prefix, + configs.bits.r2_bit, + prefix, + configs.bits.pk_bit, + prefix, + configs.bounds.eek_bound, + prefix, + configs.bounds.sk_bound, + prefix, + configs.bounds.e_sm_bound, + prefix, + r1_bounds_str, + prefix, + r2_bounds_str, + prefix, + prefix, + prefix, + prefix, + prefix, + prefix, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::codegen::write_artifacts; + use crate::threshold::pk_generation::computation::{Bits, Bounds}; + use crate::threshold::pk_generation::PkGenerationCircuitInput; + use crate::CiphernodesCommitteeSize; + + use e3_fhe_params::DEFAULT_BFV_PRESET; + 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(); + let artifacts = PkGenerationCircuit + .codegen(DEFAULT_BFV_PRESET, &sample) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + let pk0is = parsed + .get("pk0is") + .and_then(|value| value.as_array()) + .unwrap(); + let pk1is = parsed + .get("pk1is") + .and_then(|value| value.as_array()) + .unwrap(); + assert!(!pk0is.is_empty()); + assert!(!pk1is.is_empty()); + + let temp_dir = TempDir::new().unwrap(); + write_artifacts( + Some(&artifacts.toml), + &artifacts.configs, + Some(temp_dir.path()), + ) + .unwrap(); + + let output_path = temp_dir.path().join("Prover.toml"); + assert!(output_path.exists()); + + let content = std::fs::read_to_string(&output_path).unwrap(); + assert!(content.contains("pk0is")); + assert!(content.contains("pk1is")); + + assert!(artifacts.toml.contains("[[pk0is]]")); + assert!(artifacts.toml.contains("[[pk1is]]")); + + let configs_path = temp_dir.path().join("configs.nr"); + assert!(configs_path.exists()); + + let configs_content = std::fs::read_to_string(&configs_path).unwrap(); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &sample.committee).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &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!( + "{}_BIT_PK: u32 = {}", + ::PREFIX, + bits.pk_bit + ) + .as_str() + )); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs new file mode 100644 index 0000000000..5df6020917 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -0,0 +1,478 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Computation types for the public key generation circuit: constants, bounds, bit widths, and witness. +//! +//! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) a public key. They implement [`Computation`] and are used by codegen. + +use crate::calculate_bit_width; +use crate::crt_polynomial_to_toml_json; +use crate::get_zkp_modulus; +use crate::polynomial_to_toml_json; +use crate::threshold::pk_generation::circuit::PkGenerationCircuit; +use crate::threshold::pk_generation::circuit::PkGenerationCircuitInput; +use crate::CiphernodesCommittee; +use crate::CircuitsErrors; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_polynomial::CrtPolynomial; +use e3_polynomial::Polynomial; +use fhe::bfv::SecretKey; +use fhe::trbfv::SmudgingBoundCalculator; +use fhe::trbfv::SmudgingBoundCalculatorConfig; +use itertools::izip; +use num_bigint::BigInt; +use num_bigint::BigUint; +use num_traits::ToPrimitive; +use rayon::iter::ParallelBridge; +use rayon::iter::ParallelIterator; +use serde::{Deserialize, Serialize}; + +/// Output of [`CircuitComputation::compute`] for [`PkGenerationCircuit`]: bounds, bit widths, and witness. +#[derive(Debug)] +pub struct PkGenerationComputationOutput { + pub bounds: Bounds, + pub bits: Bits, + pub witness: Witness, +} + +/// Implementation of [`CircuitComputation`] for [`PkGenerationCircuit`]. +impl CircuitComputation for PkGenerationCircuit { + type Preset = BfvPreset; + type Input = PkGenerationCircuitInput; + type Output = PkGenerationComputationOutput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let bounds = Bounds::compute(preset, &input.committee)?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; + + Ok(PkGenerationComputationOutput { + bounds, + bits, + witness, + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Configs { + pub n: usize, + pub l: usize, + pub moduli: Vec, + pub bits: Bits, + pub bounds: Bounds, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + pub eek_bit: u32, + pub sk_bit: u32, + pub e_sm_bit: u32, + pub r1_bit: u32, + pub r2_bit: u32, + pub pk_bit: u32, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds { + pub eek_bound: BigUint, + pub sk_bound: BigUint, + pub e_sm_bound: BigUint, + pub r1_bounds: Vec, + pub r2_bounds: Vec, + pub pk_bound: BigUint, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + pub a: CrtPolynomial, + pub eek: Polynomial, + pub sk: Polynomial, + pub e_sm: CrtPolynomial, + pub r1is: CrtPolynomial, + pub r2is: CrtPolynomial, + pub pk0is: CrtPolynomial, + pub pk1is: CrtPolynomial, +} + +impl Computation for Configs { + type Preset = BfvPreset; + type Input = CiphernodesCommittee; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + + let moduli = threshold_params.moduli().to_vec(); + + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + + Ok(Configs { + n: threshold_params.degree(), + l: moduli.len(), + moduli, + bits, + bounds, + }) + } +} + +impl Computation for Bits { + type Preset = BfvPreset; + type Input = Bounds; + type Error = CircuitsErrors; + + fn compute(_: Self::Preset, input: &Self::Input) -> Result { + // Calculate bit widths for each bound type + let eek_bit = calculate_bit_width(BigInt::from(input.eek_bound.clone())); + let sk_bit = calculate_bit_width(BigInt::from(input.sk_bound.clone())); + let e_sm_bit = calculate_bit_width(BigInt::from(input.e_sm_bound.clone())); + let pk_bit = calculate_bit_width(BigInt::from(input.pk_bound.clone())); + + // For r1, use the maximum of all low and up bounds + let mut r1_bit = 0; + for bound in &input.r1_bounds { + r1_bit = r1_bit.max(calculate_bit_width(BigInt::from(bound.clone()))); + } + + // For r2, use the maximum of all bounds + let mut r2_bit = 0; + for bound in &input.r2_bounds { + r2_bit = r2_bit.max(calculate_bit_width(BigInt::from(bound.clone()))); + } + + Ok(Bits { + eek_bit, + sk_bit, + e_sm_bit, + r1_bit, + r2_bit, + pk_bit, + }) + } +} + +impl Computation for Bounds { + type Preset = BfvPreset; + type Input = CiphernodesCommittee; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + + let n = BigInt::from(threshold_params.degree()); + let ctx = threshold_params.ctx_at_level(0)?; + + let cbd_bound = (threshold_params.variance() * 2) as u64; + + let sk_bound = SecretKey::sk_bound(); + let eek_bound = cbd_bound; + + let defaults = preset + .search_defaults() + .ok_or_else(|| CircuitsErrors::Sample("missing search defaults".to_string()))?; + let num_ciphertexts = defaults.z; + + let smudging_config = SmudgingBoundCalculatorConfig::new( + threshold_params.clone(), + input.n, + num_ciphertexts as usize, + preset.metadata().lambda, + ); + let smudging_calculator = SmudgingBoundCalculator::new(smudging_config); + let e_sm_bound = smudging_calculator.calculate_sm_bound().map_err(|e| { + CircuitsErrors::Other(format!("Failed to calculate smudging bound: {:?}", e)) + })?; + + // Calculate bounds for each CRT basis + let num_moduli = ctx.moduli().len(); + let mut r2_bounds = vec![BigInt::from(0); num_moduli]; + let mut r1_bounds = vec![BigInt::from(0); num_moduli]; + let mut moduli = Vec::new(); + let mut pk_bound_max = BigInt::from(0); + + for (i, qi) in ctx.moduli_operators().iter().enumerate() { + let qi_bigint = BigInt::from(qi.modulus()); + let qi_bound = (&qi_bigint - 1u32) / 2u32; + + moduli.push(qi.modulus()); + + r2_bounds[i] = qi_bound.clone(); + + // Compute asymmetric range for r1 bounds per modulus + r1_bounds[i] = ((&n * eek_bound + 2u32) * &qi_bound + eek_bound) / &qi_bigint; + + // Track maximum pk bound across all moduli + // We don't need to store them as we only need the maximum bound to compute the commitment bit width + if qi_bound > pk_bound_max { + pk_bound_max = qi_bound; + } + } + + let bounds = Bounds { + eek_bound: BigUint::from(eek_bound), + sk_bound: BigUint::from(sk_bound as u128), + e_sm_bound, + r1_bounds: r1_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + r2_bounds: r2_bounds + .iter() + .map(|b| BigUint::from(b.to_u128().unwrap())) + .collect(), + pk_bound: BigUint::from(pk_bound_max.to_u128().unwrap()), + }; + + Ok(bounds) + } +} + +impl Computation for Witness { + type Preset = BfvPreset; + type Input = PkGenerationCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + + let moduli: Vec = threshold_params + .moduli() + .iter() + .copied() + .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 + + // Perform the main computation logic + let mut results: Vec<( + usize, + Polynomial, + Polynomial, + Polynomial, + Polynomial, + Polynomial, + )> = izip!( + moduli.clone(), + input.pk_share.limbs.clone(), + input.a.limbs.clone(), + input.eek.limbs.clone(), + input.e_sm.limbs.clone(), + input.sk.limbs.clone(), + ) + .enumerate() + .par_bridge() + .map( + |(i, (qi, mut pk_share, mut a, mut eek, mut e_sm, mut sk))| { + pk_share.reverse(); + pk_share.reduce(&qi); + pk_share.center(&qi); + + a.reverse(); + a.center(&qi); + + eek.reverse(); + eek.center(&qi); + + e_sm.reverse(); + e_sm.center(&qi); + + sk.reverse(); + sk.center(&qi); + + // Calculate pk_share_hat = -a * sk + e + let pk_share_hat = { + let mut exp = a.neg(); + exp = exp.mul(&sk); + + assert_eq!((exp.coefficients().len() as u64) - 1, 2 * (n - 1)); + + exp.add(&eek) + }; + + assert_eq!((pk_share_hat.coefficients().len() as u64) - 1, 2 * (n - 1)); + + // Check whether pk_share_hat mod R_qi (the ring) is equal to pk_share + let mut pk_share_hat_mod_rqi = pk_share_hat.reduce_by_cyclotomic(&cyclo).unwrap(); + + pk_share_hat_mod_rqi.reduce(&qi); + pk_share_hat_mod_rqi.center(&qi); + + assert_eq!(&pk_share, &pk_share_hat_mod_rqi); + + // Compute r2_numerator = pk_share - pk_share_hat and reduce/center the polynomial + let r2_numerator = pk_share.sub(&pk_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: (pk_share - pk_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 (pk_share - pk_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 = (pk_share - pk_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 pk_share = (pk_share_hat + r1 * qi + r2 * cyclo) mod R_qi + let r1_qi_times = r1.scalar_mul(&qi); + let pk_share_calculated = pk_share_hat.add(&r1_qi_times).add(&r2_cyclo_times); + + assert_eq!(&pk_share, &pk_share_calculated.trim_leading_zeros()); + + (i, r2, r1, pk_share.clone(), a.clone(), e_sm.clone()) + }, + ) + .collect(); + + results.sort_by_key(|(i, _, _, _, _, _)| *i); + + let mut r2 = CrtPolynomial::new(vec![]); + let mut r1 = CrtPolynomial::new(vec![]); + let mut pk_share = CrtPolynomial::new(vec![]); + let mut a = CrtPolynomial::new(vec![]); + let mut e_sm = CrtPolynomial::new(vec![]); + + let mut sk = input.sk.limbs[0].clone(); + let mut eek = input.eek.limbs[0].clone(); + + sk.reverse(); + sk.center(&moduli[0]); + eek.reverse(); + eek.center(&moduli[0]); + + for (_i, r2i, r1i, pk_sharei, ai, e_smi) in results { + r2.add_limb(r2i); + r1.add_limb(r1i); + pk_share.add_limb(pk_sharei); + a.add_limb(ai); + e_sm.add_limb(e_smi); + } + + let zkp_modulus = &get_zkp_modulus(); + + pk_share.reduce_uniform(zkp_modulus); + a.reduce_uniform(zkp_modulus); + r1.reduce_uniform(zkp_modulus); + r2.reduce_uniform(zkp_modulus); + e_sm.reduce_uniform(zkp_modulus); + eek.reduce(zkp_modulus); + sk.reduce(zkp_modulus); + + Ok(Witness { + a: a.clone(), + eek, + sk, + e_sm, + r1is: r1, + r2is: r2, + pk0is: pk_share, + pk1is: a, + }) + } + + 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 a = crt_polynomial_to_toml_json(&self.a); + let e = polynomial_to_toml_json(&self.eek); + let sk = polynomial_to_toml_json(&self.sk); + let e_sm = crt_polynomial_to_toml_json(&self.e_sm); + let r1is = crt_polynomial_to_toml_json(&self.r1is); + let r2is = crt_polynomial_to_toml_json(&self.r2is); + + let json = serde_json::json!({ + "pk0is": pk0is, + "pk1is": pk1is, + "a": a, + "eek": e, + "sk": sk, + "e_sm": e_sm, + "r1is": r1is, + "r2is": r2is, + }); + + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use crate::CiphernodesCommitteeSize; + + use super::*; + + use e3_fhe_params::DEFAULT_BFV_PRESET; + + #[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 expected_bit = calculate_bit_width(BigInt::from(bounds.pk_bound.clone())); + + assert_eq!(bits.pk_bit, expected_bit); + } + + #[test] + fn test_constants_json_roundtrip() { + let committee = CiphernodesCommitteeSize::Small.values(); + let constants = Configs::compute(DEFAULT_BFV_PRESET, &committee).unwrap(); + + let json = constants.to_json().unwrap(); + let decoded: Configs = serde_json::from_value(json).unwrap(); + + assert_eq!(decoded.n, constants.n); + assert_eq!(decoded.l, constants.l); + assert_eq!(decoded.moduli, constants.moduli); + assert_eq!(decoded.bits, constants.bits); + assert_eq!(decoded.bounds, constants.bounds); + } +} diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/mod.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/mod.rs new file mode 100644 index 0000000000..c288f074ad --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/mod.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Public key generation circuit. +//! +//! This circuit proves public key generation with a BFV public key (pk0, pk1) and produces +//! Prover.toml and configs.nr for the Noir prover. See [`PkGenerationCircuit`] and +//! [`PkGenerationCircuitInput`]. + +pub mod circuit; +pub mod codegen; +pub mod computation; +pub mod sample; +pub use circuit::*; +pub use codegen::*; +pub use computation::*; diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs new file mode 100644 index 0000000000..dc4d9185c4 --- /dev/null +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs @@ -0,0 +1,100 @@ +// 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 pk generation circuit. +//! +//! [`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::pk_generation::PkGenerationCircuitInput, CiphernodesCommittee, CircuitsErrors, +}; +use e3_fhe_params::{build_pair_for_preset, BfvPreset}; +use e3_polynomial::CrtPolynomial; +use fhe::mbfv::PublicKeyShare; +use fhe::{ + bfv::SecretKey, + mbfv::CommonRandomPoly, + trbfv::{ShareManager, TRBFV}, +}; +use rand::thread_rng; +use std::ops::Deref; + +impl PkGenerationCircuitInput { + pub fn generate_sample( + preset: BfvPreset, + committee: CiphernodesCommittee, + ) -> Result { + let (threshold_params, _) = build_pair_for_preset(preset).unwrap(); + + let mut rng = thread_rng(); + + let secret_key = SecretKey::random(&threshold_params, &mut rng); + let crp = CommonRandomPoly::new(&threshold_params, &mut rng).unwrap(); + + let (public_key_share, a, sk, e) = + PublicKeyShare::new_extended(&secret_key, crp.clone(), &mut rng).unwrap(); + + let num_parties = committee.n; + let threshold = committee.threshold; + let preset_metadata = preset.metadata(); + + let defaults = preset + .search_defaults() + .ok_or_else(|| CircuitsErrors::Sample("missing search defaults".to_string()))?; + let num_ciphertexts = defaults.z; + + let trbfv = TRBFV::new(num_parties, threshold, threshold_params.clone())?; + let share_manager = ShareManager::new(num_parties, threshold, threshold_params); + + // Generate smudging error coefficients + let esi_coeffs = trbfv.generate_smudging_error( + num_ciphertexts as usize, + preset_metadata.lambda, + &mut rng, + )?; + + // Convert to polynomial in RNS representation + // bigints_to_poly returns Zeroizing, we need to clone the inner Poly + let e_sm_rns_zeroizing = share_manager.bigints_to_poly(&esi_coeffs)?; + + let e_sm = e_sm_rns_zeroizing.deref().clone(); + + Ok(PkGenerationCircuitInput { + committee, + pk_share: CrtPolynomial::from_fhe_polynomial(&public_key_share), + a: CrtPolynomial::from_fhe_polynomial(&a), + eek: CrtPolynomial::from_fhe_polynomial(&e), + e_sm: CrtPolynomial::from_fhe_polynomial(&e_sm), + sk: CrtPolynomial::from_fhe_polynomial(&sk), + }) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + computation::Computation, + threshold::pk_generation::{PkGenerationCircuitInput, Witness}, + CiphernodesCommitteeSize, + }; + + use e3_fhe_params::DEFAULT_BFV_PRESET; + + #[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(); + + assert_eq!(witness.pk0is.limbs.len(), 2); + assert_eq!(witness.a.limbs.len(), 2); + assert_eq!(witness.e_sm.limbs.len(), 2); + assert_eq!(witness.r1is.limbs.len(), 2); + assert_eq!(witness.r2is.limbs.len(), 2); + } +} 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 f0c17cc22c..b6aea6491b 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 @@ -64,7 +64,7 @@ pub fn generate_toml(witness: Witness) -> Result { Ok(toml::to_string(&json)?) } -pub fn generate_configs(preset: BfvPreset, configs: &Configs) -> CodegenConfigs { +pub fn generate_configs(_preset: BfvPreset, configs: &Configs) -> CodegenConfigs { let prefix = ::PREFIX; let qis_str = join_display(&configs.moduli, ", "); @@ -132,9 +132,9 @@ QIS, {}_K1_UP_BOUND ); "#, - preset.metadata().degree, // N - preset.metadata().num_moduli, // L - qis_str, // QIS array + configs.n, // N + configs.l, // L + qis_str, // QIS array prefix, configs.bits.pk_bit, // BIT_PK prefix, 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 37190a8a2d..4f60bf352c 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 @@ -176,7 +176,7 @@ mod tests { use crate::circuits::computation::Computation; use crate::threshold::user_data_encryption::computation::Witness; use crate::threshold::user_data_encryption::sample::UserDataEncryptionSample; - use crate::threshold::UserDataEncryptionCircuitInput; + use crate::threshold::user_data_encryption::UserDataEncryptionCircuitInput; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use fhe_traits::DeserializeParametrized; diff --git a/examples/CRISP/crates/zk-inputs/src/lib.rs b/examples/CRISP/crates/zk-inputs/src/lib.rs index 63c3f5d123..0eef17cf86 100644 --- a/examples/CRISP/crates/zk-inputs/src/lib.rs +++ b/examples/CRISP/crates/zk-inputs/src/lib.rs @@ -14,9 +14,9 @@ use e3_fhe_params::default_param_set; use e3_fhe_params::BfvParamSet; use e3_fhe_params::DEFAULT_BFV_PRESET; use e3_polynomial::CrtPolynomial; +use e3_zk_helpers::circuits::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; +use e3_zk_helpers::circuits::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_zk_helpers::commitments::compute_ciphertext_commitment; -use e3_zk_helpers::threshold::UserDataEncryptionCircuit; -use e3_zk_helpers::threshold::UserDataEncryptionCircuitInput; use e3_zk_helpers::utils::compute_pk_bit; use e3_zk_helpers::CircuitComputation; use e3_zk_helpers::Computation;