From 9392b06903a014975fd208f78176698e0b9046c0 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 14:25:09 +0100 Subject: [PATCH 01/16] rename c0 --- crates/zk-helpers/src/bin/zk_cli.rs | 10 +++--- crates/zk-helpers/src/circuits/dkg/mod.rs | 14 ++++++++ .../circuits/{pk_bfv => dkg/pk}/circuit.rs | 12 +++---- .../circuits/{pk_bfv => dkg/pk}/codegen.rs | 33 +++++++++++-------- .../{pk_bfv => dkg/pk}/computation.rs | 22 ++++++------- .../src/circuits/{pk_bfv => dkg/pk}/mod.rs | 8 ++--- crates/zk-helpers/src/circuits/mod.rs | 8 ++--- 7 files changed, 64 insertions(+), 43 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/mod.rs rename crates/zk-helpers/src/circuits/{pk_bfv => dkg/pk}/circuit.rs (65%) rename crates/zk-helpers/src/circuits/{pk_bfv => dkg/pk}/codegen.rs (84%) rename crates/zk-helpers/src/circuits/{pk_bfv => dkg/pk}/computation.rs (92%) rename crates/zk-helpers/src/circuits/{pk_bfv => dkg/pk}/mod.rs (58%) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index b22e8473a9..fa23826e6d 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -13,7 +13,7 @@ use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; use e3_fhe_params::{BfvParamSet, BfvPreset}; -use e3_zk_helpers::circuits::pk_bfv::circuit::{PkBfvCircuit, PkBfvCircuitInput}; +use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; use e3_zk_helpers::codegen::{write_artifacts, CircuitCodegen}; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; use e3_zk_helpers::sample::Sample; @@ -55,7 +55,7 @@ fn main() -> Result<()> { // Register all circuits in the registry (metadata only). let mut registry = CircuitRegistry::new(); - registry.register(Arc::new(PkBfvCircuit)); + registry.register(Arc::new(PkCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -103,11 +103,11 @@ fn main() -> Result<()> { let sample = Sample::generate(¶ms); let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { - name if name == ::NAME => { - let circuit = PkBfvCircuit; + name if name == ::NAME => { + let circuit = PkCircuit; circuit.codegen( ¶ms, - &PkBfvCircuitInput { + &PkCircuitInput { public_key: sample.public_key, }, )? diff --git a/crates/zk-helpers/src/circuits/dkg/mod.rs b/crates/zk-helpers/src/circuits/dkg/mod.rs new file mode 100644 index 0000000000..793503cb7f --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/mod.rs @@ -0,0 +1,14 @@ +// 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. + +//! DKG public-key BFV commitment circuit. +//! +//! This circuit proves knowledge of a DKG BFV public key (pk0, pk1) and produces +//! Prover.toml and configs.nr for the Noir prover. See [`PkCircuit`] and +//! [`PkCircuitInput`]. + +pub mod pk; +pub use pk::*; diff --git a/crates/zk-helpers/src/circuits/pk_bfv/circuit.rs b/crates/zk-helpers/src/circuits/dkg/pk/circuit.rs similarity index 65% rename from crates/zk-helpers/src/circuits/pk_bfv/circuit.rs rename to crates/zk-helpers/src/circuits/dkg/pk/circuit.rs index 35d9474884..28e8c708e4 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/circuit.rs @@ -10,15 +10,15 @@ use e3_fhe_params::ParameterType; use fhe::bfv::PublicKey; #[derive(Debug)] -pub struct PkBfvCircuit; +pub struct PkCircuit; -impl Circuit for PkBfvCircuit { - const NAME: &'static str = "pk-bfv"; - const PREFIX: &'static str = "PK_BFV"; +impl Circuit for PkCircuit { + const NAME: &'static str = "pk"; + const PREFIX: &'static str = "PK"; const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; - const DKG_INPUT_TYPE: Option = None; + const DKG_INPUT_TYPE: Option = Some(DkgInputType::SecretKey); } -pub struct PkBfvCircuitInput { +pub struct PkCircuitInput { pub public_key: PublicKey, } diff --git a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs similarity index 84% rename from crates/zk-helpers/src/circuits/pk_bfv/codegen.rs rename to crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 1b17b0e335..e56f40034c 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -6,10 +6,10 @@ //! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. -use crate::circuits::pk_bfv::circuit::{PkBfvCircuit, PkBfvCircuitInput}; +use crate::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; use crate::{ crt_polynomial_to_toml_json, Artifacts, Bits, Circuit, CircuitCodegen, CircuitComputation, - CircuitsErrors, Configs, PkBfvComputationOutput, Toml, Witness, + CircuitsErrors, Configs, PkComputationOutput, Toml, Witness, }; use fhe::bfv::BfvParameters; @@ -17,10 +17,10 @@ use serde::{Deserialize, Serialize}; use serde_json; use std::sync::Arc; -/// Implementation of [`CircuitCodegen`] for [`PkBfvCircuit`]. -impl CircuitCodegen for PkBfvCircuit { +/// Implementation of [`CircuitCodegen`] for [`PkCircuit`]. +impl CircuitCodegen for PkCircuit { type Params = Arc; - type Input = PkBfvCircuitInput; + type Input = PkCircuitInput; type Error = CircuitsErrors; fn codegen( @@ -28,7 +28,7 @@ impl CircuitCodegen for PkBfvCircuit { params: &Self::Params, input: &Self::Input, ) -> Result { - let PkBfvComputationOutput { witness, bits, .. } = PkBfvCircuit::compute(params, input)?; + let PkComputationOutput { witness, bits, .. } = PkCircuit::compute(params, input)?; let toml = generate_toml(witness)?; let configs = generate_configs(¶ms, &bits); @@ -59,22 +59,22 @@ pub fn generate_toml(witness: Witness) -> Result { /// Builds the configs.nr string (N, L, bit parameters) for the Noir prover. pub fn generate_configs(params: &Arc, bits: &Bits) -> Configs { format!( - r#"// Global configs for Public Key BFV circuit + r#" pub global N: u32 = {}; pub global L: u32 = {}; /************************************ ------------------------------------- -pk_bfv (CIRCUIT 0 - PUBLIC KEY BFV COMMITMENT) +pk (CIRCUIT 0 - DKG BFV PUBLIC KEY) ------------------------------------- ************************************/ -// pk_bfv - bit parameters +// pk - bit parameters pub global {}_BIT_PK: u32 = {}; "#, params.degree(), params.moduli().len(), - ::PREFIX, + ::PREFIX, bits.pk_bit, ) } @@ -95,10 +95,10 @@ mod tests { fn test_toml_generation_and_structure() { let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); let sample = Sample::generate(¶ms); - let artifacts = PkBfvCircuit + let artifacts = PkCircuit .codegen( ¶ms, - &PkBfvCircuitInput { + &PkCircuitInput { public_key: sample.public_key, }, ) @@ -143,6 +143,13 @@ mod tests { assert!(configs_content.contains(format!("N: u32 = {}", params.degree()).as_str())); assert!(configs_content.contains(format!("L: u32 = {}", params.moduli().len()).as_str())); - assert!(configs_content.contains(format!("PK_BFV_BIT_PK: u32 = {}", bits.pk_bit).as_str())); + assert!(configs_content.contains( + format!( + "{}_BIT_PK: u32 = {}", + ::PREFIX, + bits.pk_bit + ) + .as_str() + )); } } diff --git a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs similarity index 92% rename from crates/zk-helpers/src/circuits/pk_bfv/computation.rs rename to crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 71ac9d1604..3df1f58c75 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -10,30 +10,30 @@ //! and (for witness) a public key. They implement [`Computation`] and are used by codegen. use crate::calculate_bit_width; +use crate::dkg::pk::PkCircuitInput; use crate::get_zkp_modulus; -use crate::pk_bfv::PkBfvCircuitInput; use crate::CircuitsErrors; use crate::ConvertToJson; -use crate::PkBfvCircuit; +use crate::PkCircuit; use crate::{CircuitComputation, Computation}; use e3_polynomial::CrtPolynomial; use fhe::bfv::BfvParameters; use num_bigint::BigUint; use serde::{Deserialize, Serialize}; -/// Output of [`CircuitComputation::compute`] for [`PkBfvCircuit`]: bounds, bit widths, and witness. +/// Output of [`CircuitComputation::compute`] for [`PkCircuit`]: bounds, bit widths, and witness. #[derive(Debug)] -pub struct PkBfvComputationOutput { +pub struct PkComputationOutput { pub bounds: Bounds, pub bits: Bits, pub witness: Witness, } -/// Implementation of [`CircuitComputation`] for [`PkBfvCircuit`]. -impl CircuitComputation for PkBfvCircuit { +/// Implementation of [`CircuitComputation`] for [`PkCircuit`]. +impl CircuitComputation for PkCircuit { type Params = BfvParameters; - type Input = PkBfvCircuitInput; - type Output = PkBfvComputationOutput; + type Input = PkCircuitInput; + type Output = PkComputationOutput; type Error = CircuitsErrors; fn compute(params: &Self::Params, input: &Self::Input) -> Result { @@ -41,7 +41,7 @@ impl CircuitComputation for PkBfvCircuit { let bits = Bits::compute(params, &bounds)?; let witness = Witness::compute(params, input)?; - Ok(PkBfvComputationOutput { + Ok(PkComputationOutput { bounds, bits, witness, @@ -135,7 +135,7 @@ impl Computation for Bounds { impl Computation for Witness { type Params = BfvParameters; - type Input = PkBfvCircuitInput; + type Input = PkCircuitInput; type Error = CircuitsErrors; fn compute(params: &Self::Params, input: &Self::Input) -> Result { @@ -203,7 +203,7 @@ mod tests { let encryption_data = Sample::generate(¶ms); let witness = Witness::compute( ¶ms, - &PkBfvCircuitInput { + &PkCircuitInput { public_key: encryption_data.public_key, }, ) diff --git a/crates/zk-helpers/src/circuits/pk_bfv/mod.rs b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs similarity index 58% rename from crates/zk-helpers/src/circuits/pk_bfv/mod.rs rename to crates/zk-helpers/src/circuits/dkg/pk/mod.rs index adfe084f06..7a9ed7a4b5 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs @@ -4,11 +4,11 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Public-key BFV commitment circuit. +//! DKG public-key BFV commitment circuit. //! -//! This circuit proves knowledge of a BFV public key (pk0, pk1) and produces -//! Prover.toml and configs.nr for the Noir prover. See [`PkBfvCircuit`] and -//! [`PkBfvCodegenInput`]. +//! This circuit proves knowledge of a DKG BFV public key (pk0, pk1) and produces +//! Prover.toml and configs.nr for the Noir prover. See [`PkCircuit`] and +//! [`PkCircuitInput`]. pub mod circuit; pub mod codegen; diff --git a/crates/zk-helpers/src/circuits/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index 1c7e123a1b..fcb0243ef5 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -25,7 +25,7 @@ pub use computation::{ pub use errors::CircuitsErrors; pub use sample::Sample; -pub mod pk_bfv; -pub use pk_bfv::codegen::{generate_configs, generate_toml, TomlJson}; -pub use pk_bfv::computation::{Bits, Bounds, PkBfvComputationOutput, Witness}; -pub use pk_bfv::PkBfvCircuit; +pub mod dkg; +pub use dkg::pk::codegen::{generate_configs, generate_toml, TomlJson}; +pub use dkg::pk::computation::{Bits, Bounds, PkComputationOutput, Witness}; +pub use dkg::pk::PkCircuit; From b685cde47c9757364bf4466f2a45aa2b3feac5eb Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 15:35:10 +0100 Subject: [PATCH 02/16] expand sample --- Cargo.lock | 2 + Cargo.toml | 1 + crates/fhe-params/src/lib.rs | 2 +- crates/zk-helpers/Cargo.toml | 2 + crates/zk-helpers/src/bin/zk_cli.rs | 61 ++++-- .../zk-helpers/src/ciphernodes_committee.rs | 6 +- crates/zk-helpers/src/circuits/dkg/mod.rs | 8 +- .../zk-helpers/src/circuits/dkg/pk/codegen.rs | 20 +- .../src/circuits/dkg/pk/computation.rs | 31 ++-- .../circuits/dkg/share_computation/circuit.rs | 39 ++++ .../src/circuits/dkg/share_computation/mod.rs | 8 + crates/zk-helpers/src/circuits/errors.rs | 2 + crates/zk-helpers/src/circuits/sample.rs | 175 ++++++++++++++++-- 13 files changed, 297 insertions(+), 60 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 2aa6f62c55..78f6e6a761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3581,11 +3581,13 @@ dependencies = [ "ark-ff 0.5.0", "clap", "e3-fhe-params", + "e3-parity-matrix", "e3-polynomial 0.1.8", "e3-safe 0.1.8", "fhe", "fhe-math", "itertools 0.14.0", + "ndarray", "num-bigint", "num-traits", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index b53155d9d7..b7e6eccab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,7 @@ e3-trbfv = { version = "0.1.8", path = "./crates/trbfv" } e3-utils = { version = "0.1.8", path = "./crates/utils" } e3-safe = { version = "0.1.8", path = "./crates/safe" } e3-zk-helpers = { version = "0.1.8", path = "./crates/zk-helpers" } +e3-parity-matrix = { version = "0.1.8", path = "./crates/parity-matrix" } actix = "=0.13.5" actix-web = "=4.11.0" diff --git a/crates/fhe-params/src/lib.rs b/crates/fhe-params/src/lib.rs index 8ec73e8a9f..162fb6a422 100644 --- a/crates/fhe-params/src/lib.rs +++ b/crates/fhe-params/src/lib.rs @@ -15,7 +15,7 @@ pub mod search; pub use builder::{ build_bfv_params, build_bfv_params_arc, build_bfv_params_from_set, - build_bfv_params_from_set_arc, + build_bfv_params_from_set_arc, build_pair_for_preset, }; #[cfg(feature = "abi-encoding")] pub use encoding::{decode_bfv_params, decode_bfv_params_arc, encode_bfv_params, EncodingError}; diff --git a/crates/zk-helpers/Cargo.toml b/crates/zk-helpers/Cargo.toml index 72e4f7c847..750dbe3d48 100644 --- a/crates/zk-helpers/Cargo.toml +++ b/crates/zk-helpers/Cargo.toml @@ -25,6 +25,8 @@ serde = { workspace = true } serde_json = { workspace = true } toml = "0.8.23" itertools = "0.14.0" +ndarray = { workspace = true } +e3-parity-matrix = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index fa23826e6d..660996d983 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -8,11 +8,11 @@ //! //! This binary lists available circuits and generates Prover.toml and configs.nr //! for use with the Noir prover. Use `--list_circuits` to see circuits and -//! `--circuit --preset ` to generate artifacts. +//! `--circuit --preset insecure|secure` to generate artifacts. use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; -use e3_fhe_params::{BfvParamSet, BfvPreset}; +use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; use e3_zk_helpers::codegen::{write_artifacts, CircuitCodegen}; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; @@ -30,7 +30,7 @@ struct Cli { /// Circuit name to generate artifacts for (e.g. pk-bfv). #[arg(long, required_unless_present = "list_circuits")] circuit: Option, - /// BFV preset name (must match circuit's parameter type). + /// Preset: "insecure" (512) or "secure" (8192). Drives both threshold and DKG params. #[arg(long, required_unless_present = "list_circuits")] preset: Option, /// Output directory for generated artifacts. @@ -41,13 +41,31 @@ struct Cli { toml: bool, } -/// Parses a preset name (e.g. `"default"`) into a [`BfvPreset`]. -/// Returns an error listing available presets if the name is unknown. -fn parse_preset(name: &str) -> Result { - BfvPreset::from_name(name).map_err(|_| { - let available = BfvPreset::list().join(", "); - anyhow!("unknown preset: {name}. Available: {available}") - }) +/// Security preset: chooses both threshold and DKG params (insecure = 512, secure = 8192). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SecurityPreset { + Insecure, + Secure, +} + +impl SecurityPreset { + fn threshold_preset(self) -> BfvPreset { + match self { + SecurityPreset::Insecure => BfvPreset::InsecureThreshold512, + SecurityPreset::Secure => BfvPreset::SecureThreshold8192, + } + } +} + +/// Parses preset name "insecure" or "secure" into a [`SecurityPreset`]. +fn parse_preset(name: &str) -> Result { + match name.trim() { + s if s.eq_ignore_ascii_case("insecure") => Ok(SecurityPreset::Insecure), + s if s.eq_ignore_ascii_case("secure") => Ok(SecurityPreset::Secure), + _ => Err(anyhow!( + "unknown preset: {name}. Use \"insecure\" or \"secure\"" + )), + } } fn main() -> Result<()> { @@ -75,7 +93,7 @@ fn main() -> Result<()> { // Unwrap required arguments (clap ensures they're present when list_circuits is false). let circuit = args.circuit.unwrap(); - let preset = parse_preset(&args.preset.unwrap())?; + let security_preset = parse_preset(&args.preset.unwrap())?; std::fs::create_dir_all(&args.output) .with_context(|| format!("failed to create output dir {}", args.output.display()))?; @@ -86,8 +104,16 @@ fn main() -> Result<()> { anyhow!("unknown circuit: {}. Available: {}", circuit, available) })?; - // Validate preset parameter type matches circuit's supported parameter type. - let preset_param_type = preset.metadata().parameter_type; + // Build threshold and DKG params from the security preset (insecure → 512, secure → 8192). + let (threshold_params, dkg_params) = build_pair_for_preset(security_preset.threshold_preset()) + .map_err(|e| anyhow!("failed to build params: {}", e))?; + + // Validate DKG preset parameter type matches circuit's supported parameter type. + let dkg_preset = security_preset + .threshold_preset() + .dkg_counterpart() + .expect("threshold preset has DKG counterpart"); + let preset_param_type = dkg_preset.metadata().parameter_type; let circuit_param_type = circuit_meta.supported_parameter(); if preset_param_type != circuit_param_type { return Err(anyhow!( @@ -98,17 +124,16 @@ fn main() -> Result<()> { )); } - // Generate artifacts based on circuit name from registry. - let params = BfvParamSet::from(preset).build_arc(); - let sample = Sample::generate(¶ms); + // Generate sample and artifacts based on circuit name from registry. + let sample = Sample::generate(&threshold_params, &dkg_params, None, 0, 0)?; let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { name if name == ::NAME => { let circuit = PkCircuit; circuit.codegen( - ¶ms, + &dkg_params, &PkCircuitInput { - public_key: sample.public_key, + public_key: sample.dkg_public_key, }, )? } diff --git a/crates/zk-helpers/src/ciphernodes_committee.rs b/crates/zk-helpers/src/ciphernodes_committee.rs index c397e51721..0c690fec1d 100644 --- a/crates/zk-helpers/src/ciphernodes_committee.rs +++ b/crates/zk-helpers/src/ciphernodes_committee.rs @@ -20,11 +20,11 @@ pub enum CiphernodesCommitteeSize { #[derive(Debug, Clone, PartialEq, Eq)] pub struct CiphernodesCommittee { /// Total number of parties (N_PARTIES). - n: usize, + pub n: usize, /// Number of honest parties (H). - h: usize, + pub h: usize, /// Threshold value (T). - threshold: usize, + pub threshold: usize, } impl CiphernodesCommitteeSize { diff --git a/crates/zk-helpers/src/circuits/dkg/mod.rs b/crates/zk-helpers/src/circuits/dkg/mod.rs index 793503cb7f..905b80d19e 100644 --- a/crates/zk-helpers/src/circuits/dkg/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/mod.rs @@ -4,11 +4,5 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! DKG public-key BFV commitment circuit. -//! -//! This circuit proves knowledge of a DKG BFV public key (pk0, pk1) and produces -//! Prover.toml and configs.nr for the Noir prover. See [`PkCircuit`] and -//! [`PkCircuitInput`]. - pub mod pk; -pub use pk::*; +pub mod share_computation; diff --git a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index e56f40034c..6ae3b05adc 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -82,24 +82,30 @@ pub global {}_BIT_PK: u32 = {}; #[cfg(test)] mod tests { use super::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::computation::Computation; use crate::codegen::write_artifacts; - use crate::sample::Sample; + use crate::sample::prepare_sample_for_test; use crate::Bounds; - - use e3_fhe_params::BfvParamSet; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::build_pair_for_preset; + use e3_fhe_params::BfvPreset; use tempfile::TempDir; #[test] fn test_toml_generation_and_structure() { - let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let sample = Sample::generate(¶ms); + let (_, params) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + None, + ) + .unwrap(); + let artifacts = PkCircuit .codegen( ¶ms, &PkCircuitInput { - public_key: sample.public_key, + public_key: sample.dkg_public_key, }, ) .unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 3df1f58c75..8238e89794 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -181,30 +181,37 @@ impl ConvertToJson for Witness { mod tests { use super::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::sample::prepare_sample_for_test; use crate::ConvertToJson; - use crate::Sample; - use e3_fhe_params::BfvParamSet; + use e3_fhe_params::build_pair_for_preset; + use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; #[test] fn test_bound_and_bits_computation_consistency() { - let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let bounds = Bounds::compute(¶ms, &()).unwrap(); - let bits = Bits::compute(¶ms, &bounds).unwrap(); + let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); + let bounds = Bounds::compute(&dkg_params, &()).unwrap(); + let bits = Bits::compute(&dkg_params, &bounds).unwrap(); let expected_bits = calculate_bit_width(&bounds.pk_bound.to_string()).unwrap(); - assert_eq!(bounds.pk_bound, BigUint::from(34359701504u64)); + assert_eq!(bounds.pk_bound, BigUint::from(1125899906777088u128)); assert_eq!(bits.pk_bit, expected_bits); } #[test] fn test_witness_reduction_and_json_roundtrip() { - let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let encryption_data = Sample::generate(¶ms); + let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + None, + ) + .unwrap(); let witness = Witness::compute( - ¶ms, + &dkg_params, &PkCircuitInput { - public_key: encryption_data.public_key, + public_key: sample.dkg_public_key, }, ) .unwrap(); @@ -217,8 +224,8 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let constants = Configs::compute(¶ms, &()).unwrap(); + let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); + let constants = Configs::compute(&dkg_params, &()).unwrap(); let json = constants.convert_to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs new file mode 100644 index 0000000000..c2b6590bab --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use e3_fhe_params::ParameterType; +use ndarray::Array2; +use num_bigint::BigInt; + +#[derive(Debug)] +pub struct ShareComputationSkCircuit; + +impl Circuit for ShareComputationSkCircuit { + const NAME: &'static str = "share_computation_sk"; + const PREFIX: &'static str = "SHARE_COMPUTATION_SK"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; + const DKG_INPUT_TYPE: Option = Some(DkgInputType::SecretKey); +} + +#[derive(Debug)] +pub struct ShareComputationEsmCircuit; + +impl Circuit for ShareComputationEsmCircuit { + const NAME: &'static str = "share_computation_e_sm"; + const PREFIX: &'static str = "SHARE_COMPUTATION_E_SM"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; + const DKG_INPUT_TYPE: Option = Some(DkgInputType::SmudgingNoise); +} + +pub struct ShareComputationCircuitInput { + pub secret_coefficients: Vec, + pub secret_sss: Vec>, + pub parity_matrix: Vec>>, + pub n_parties: u32, + pub threshold: u32, +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs new file mode 100644 index 0000000000..d43cea6216 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -0,0 +1,8 @@ +// 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. + +pub mod circuit; +pub use circuit::*; diff --git a/crates/zk-helpers/src/circuits/errors.rs b/crates/zk-helpers/src/circuits/errors.rs index ea0b95e3ce..f59b6569c2 100644 --- a/crates/zk-helpers/src/circuits/errors.rs +++ b/crates/zk-helpers/src/circuits/errors.rs @@ -23,6 +23,8 @@ pub enum CircuitsErrors { CrtPolynomial(#[from] CrtPolynomialError), #[error("ZK helper error: {0}")] ZkHelpers(#[from] ZkHelpersUtilsError), + #[error("Sample error: {0}")] + Sample(String), #[error("Unexpected error: {0}")] Other(String), } diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/sample.rs index be3bfc4ff1..7757104dfe 100644 --- a/crates/zk-helpers/src/circuits/sample.rs +++ b/crates/zk-helpers/src/circuits/sample.rs @@ -9,40 +9,191 @@ //! [`Sample`] produces a random BFV key pair; the public key is used as input //! for codegen and tests (e.g. pk-bfv circuit). +use crate::ciphernodes_committee::CiphernodesCommittee; +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 e3_parity_matrix::build_generator_matrix; +use e3_parity_matrix::{ParityMatrix, ParityMatrixConfig}; use fhe::bfv::{BfvParameters, PublicKey, SecretKey}; +use fhe::trbfv::{ShareManager, TRBFV}; +use num_bigint::BigInt; +use num_bigint::BigUint; use rand::thread_rng; use std::sync::Arc; /// A sample BFV public key (and optionally related data) for circuit codegen or tests. #[derive(Debug, Clone)] pub struct Sample { - /// Randomly generated BFV public key. - pub public_key: PublicKey, + /// Committee information. + pub committee: CiphernodesCommittee, + /// DKG BFV public key. + pub dkg_public_key: PublicKey, + /// Secret shares. + pub secret_sss: Vec>, + /// Parity matrix. + pub parity_matrix: ParityMatrix, } impl Sample { /// Generates a random secret key and public key for the given BFV parameters. - pub fn generate(params: &Arc) -> Self { + pub fn generate( + threshold_params: &Arc, + dkg_params: &Arc, + dkg_input_type: Option, + num_ciphertexts: u128, // z in the search defaults + lambda: u32, + ) -> Result { let mut rng = thread_rng(); - let secret_key = SecretKey::random(¶ms, &mut rng); - let public_key = PublicKey::new(&secret_key, &mut rng); + let committee = CiphernodesCommitteeSize::Small.values(); - Self { public_key } + 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()) + .map_err(|e| CircuitsErrors::Sample(format!("Failed to create TRBFV: {:?}", e)))?; + let mut share_manager = + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + + // Generate parity matrix for each modulus. + let parity_matrix = build_generator_matrix(&ParityMatrixConfig { + q: BigUint::from(threshold_params.moduli()[0]), + t: committee.threshold, + n: committee.n, + }) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to build generator matrix: {:?}", e)) + })?; + + let (_, secret_sss) = match dkg_input_type { + Some(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()) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to convert SK coeffs to poly: {:?}", + e + )) + }) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to convert SK coeffs to poly: {:?}", + e + )) + })?; + + let sk_sss = share_manager + .generate_secret_shares_from_poly(sk_poly.clone(), rng) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to generate SK shares: {:?}", e)) + })?; + + let secret_coeffs: Vec = threshold_secret_key + .coeffs + .iter() + .map(|&c| BigInt::from(c)) + .collect(); + + (secret_coeffs, sk_sss) + } + Some(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 + )) + })?; + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to convert error to poly: {:?}", e)) + })?; + let esi_sss = share_manager + .generate_secret_shares_from_poly(esi_poly.clone(), rng) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to generate error shares: {:?}", e)) + })?; + + let secret_coeffs = esi_coeffs.clone(); + + (secret_coeffs, esi_sss) + } + None => (Vec::new(), Vec::new()), + }; + + Ok(Self { + committee, + dkg_public_key, + secret_sss, + parity_matrix, + }) } } +/// Prepares a sample for testing using a threshold preset (DKG params come from its pair). +pub fn prepare_sample_for_test( + threshold_preset: BfvPreset, + committee: CiphernodesCommitteeSize, + dkg_input_type: Option, +) -> Result { + let (threshold_params, dkg_params) = build_pair_for_preset(threshold_preset) + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + let num_ciphertexts = threshold_preset.search_defaults().unwrap().z; + let lambda = threshold_preset.search_defaults().unwrap().lambda; + let sample = Sample::generate( + &threshold_params, + &dkg_params, + dkg_input_type, + num_ciphertexts, + lambda, + ) + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + Ok(sample) +} + #[cfg(test)] mod tests { + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use e3_fhe_params::BfvPreset; + use super::*; - use e3_fhe_params::BfvParamSet; - use e3_fhe_params::DEFAULT_BFV_PRESET; #[test] - fn test_generate_sample() { - let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let sample = Sample::generate(¶ms); + fn test_generate_secret_key_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SecretKey), + ) + .unwrap(); + + 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.secret_sss.len(), 2); + } + + #[test] + fn test_generate_smudging_noise_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SmudgingNoise), + ) + .unwrap(); - assert_eq!(sample.public_key.c.c.len(), 2); + 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.secret_sss.len(), 2); } } From cf24e075082d0bcc5296c61047d7ab05c5ad8c8e Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 16:05:52 +0100 Subject: [PATCH 03/16] upgrade traits --- crates/fhe-params/src/presets.rs | 8 ++ crates/zk-helpers/src/bin/zk_cli.rs | 2 +- crates/zk-helpers/src/circuits/codegen.rs | 11 ++- crates/zk-helpers/src/circuits/computation.rs | 14 +++- .../zk-helpers/src/circuits/dkg/pk/codegen.rs | 42 +++++----- .../src/circuits/dkg/pk/computation.rs | 78 ++++++++++++------- .../circuits/dkg/share_computation/circuit.rs | 26 +++---- 7 files changed, 105 insertions(+), 76 deletions(-) diff --git a/crates/fhe-params/src/presets.rs b/crates/fhe-params/src/presets.rs index e5af7d655f..ddb6a73783 100644 --- a/crates/fhe-params/src/presets.rs +++ b/crates/fhe-params/src/presets.rs @@ -94,6 +94,10 @@ pub struct PresetMetadata { /// This determines the size of the polynomial ring R_q = Z_q[X]/(X^d + 1). /// Common values are 512, 1024, 2048, 4096, 8192, etc. pub degree: usize, + /// Number of moduli (l) - the number of moduli used in the ciphertext space + /// + /// This determines the size of the ciphertext space. + pub num_moduli: usize, /// Number of parties (n) - the number of ciphernodes in the system supported by /// the preset. /// @@ -260,6 +264,7 @@ impl BfvPreset { BfvPreset::InsecureThreshold512 => PresetMetadata { name: self.name(), degree: insecure_512::DEGREE, + num_moduli: insecure_512::threshold::MODULI.len(), num_parties: insecure_512::NUM_PARTIES, lambda: DEFAULT_INSECURE_LAMBDA, parameter_type: ParameterType::THRESHOLD, @@ -267,6 +272,7 @@ impl BfvPreset { BfvPreset::InsecureDkg512 => PresetMetadata { name: self.name(), degree: insecure_512::DEGREE, + num_moduli: insecure_512::dkg::MODULI.len(), num_parties: insecure_512::NUM_PARTIES, lambda: DEFAULT_INSECURE_LAMBDA, parameter_type: ParameterType::DKG, @@ -274,6 +280,7 @@ impl BfvPreset { BfvPreset::SecureThreshold8192 => PresetMetadata { name: self.name(), degree: secure_8192::DEGREE, + num_moduli: secure_8192::threshold::MODULI.len(), num_parties: secure_8192::NUM_PARTIES, lambda: DEFAULT_SECURE_LAMBDA, parameter_type: ParameterType::THRESHOLD, @@ -281,6 +288,7 @@ impl BfvPreset { BfvPreset::SecureDkg8192 => PresetMetadata { name: self.name(), degree: secure_8192::DEGREE, + num_moduli: secure_8192::dkg::MODULI.len(), num_parties: secure_8192::NUM_PARTIES, lambda: DEFAULT_SECURE_LAMBDA, parameter_type: ParameterType::DKG, diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 660996d983..d9dc91a3b5 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -131,7 +131,7 @@ fn main() -> Result<()> { name if name == ::NAME => { let circuit = PkCircuit; circuit.codegen( - &dkg_params, + security_preset.threshold_preset(), &PkCircuitInput { public_key: sample.dkg_public_key, }, diff --git a/crates/zk-helpers/src/circuits/codegen.rs b/crates/zk-helpers/src/circuits/codegen.rs index fb3844e44d..538be4217a 100644 --- a/crates/zk-helpers/src/circuits/codegen.rs +++ b/crates/zk-helpers/src/circuits/codegen.rs @@ -25,16 +25,19 @@ pub struct Artifacts { /// Trait for circuits that can generate Prover.toml and configs.nr from circuit-specific input. pub trait CircuitCodegen: crate::registry::Circuit { - /// Circuit-specific parameters (e.g. BFV parameters). - type Params; + /// Circuit-specific BFV threshold parameters preset. + type BfvThresholdParametersPreset; /// Circuit-specific codegen input (e.g. preset + public key). type Input; /// Error type for codegen failures. type Error; /// Produces [`Artifacts`] for this circuit from the given input. - fn codegen(&self, params: &Self::Params, input: &Self::Input) - -> Result; + fn codegen( + &self, + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result; } /// Writes the Prover TOML string to `path/Prover.toml`, or `./Prover.toml` if `path` is `None`. diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index b43ce9cebc..07dbda07ad 100644 --- a/crates/zk-helpers/src/circuits/computation.rs +++ b/crates/zk-helpers/src/circuits/computation.rs @@ -26,23 +26,29 @@ pub type Configs = String; /// Generic computation from parameters and input to a result. pub trait Computation: Sized { - type Params; + type BfvThresholdParametersPreset; type Input; type Error; /// Computes the result from parameters and input. - fn compute(params: &Self::Params, input: &Self::Input) -> Result; + fn compute( + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result; } /// Circuit-specific computation: parameters and input produce bounds, bits, witness, etc. pub trait CircuitComputation: crate::registry::Circuit { - type Params; + type BfvThresholdParametersPreset; type Input; type Output; type Error; /// Computes circuit-specific data (bounds, bits, witness) from parameters and input. - fn compute(params: &Self::Params, input: &Self::Input) -> Result; + fn compute( + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result; } /// Converts a value to a JSON [`serde_json::Value`] for serialization. diff --git a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 6ae3b05adc..87f2ab0062 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -12,26 +12,25 @@ use crate::{ CircuitsErrors, Configs, PkComputationOutput, Toml, Witness, }; -use fhe::bfv::BfvParameters; +use e3_fhe_params::BfvPreset; use serde::{Deserialize, Serialize}; use serde_json; -use std::sync::Arc; /// Implementation of [`CircuitCodegen`] for [`PkCircuit`]. impl CircuitCodegen for PkCircuit { - type Params = Arc; + type BfvThresholdParametersPreset = BfvPreset; type Input = PkCircuitInput; type Error = CircuitsErrors; fn codegen( &self, - params: &Self::Params, + preset: Self::BfvThresholdParametersPreset, input: &Self::Input, ) -> Result { - let PkComputationOutput { witness, bits, .. } = PkCircuit::compute(params, input)?; + let PkComputationOutput { witness, bits, .. } = PkCircuit::compute(preset, input)?; let toml = generate_toml(witness)?; - let configs = generate_configs(¶ms, &bits); + let configs = generate_configs(preset, &bits); Ok(Artifacts { toml, configs }) } @@ -57,7 +56,7 @@ pub fn generate_toml(witness: Witness) -> Result { } /// Builds the configs.nr string (N, L, bit parameters) for the Noir prover. -pub fn generate_configs(params: &Arc, bits: &Bits) -> Configs { +pub fn generate_configs(preset: BfvPreset, bits: &Bits) -> Configs { format!( r#" pub global N: u32 = {}; @@ -72,8 +71,8 @@ pk (CIRCUIT 0 - DKG BFV PUBLIC KEY) // pk - bit parameters pub global {}_BIT_PK: u32 = {}; "#, - params.degree(), - params.moduli().len(), + preset.metadata().degree, + preset.metadata().num_moduli, ::PREFIX, bits.pk_bit, ) @@ -87,23 +86,18 @@ mod tests { use crate::codegen::write_artifacts; use crate::sample::prepare_sample_for_test; use crate::Bounds; - use e3_fhe_params::build_pair_for_preset; - use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; use tempfile::TempDir; #[test] fn test_toml_generation_and_structure() { - let (_, params) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = prepare_sample_for_test( - BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, - None, - ) - .unwrap(); + let sample = + prepare_sample_for_test(DEFAULT_BFV_PRESET, CiphernodesCommitteeSize::Small, None) + .unwrap(); let artifacts = PkCircuit .codegen( - ¶ms, + DEFAULT_BFV_PRESET, &PkCircuitInput { public_key: sample.dkg_public_key, }, @@ -144,11 +138,13 @@ mod tests { assert!(configs_path.exists()); let configs_content = std::fs::read_to_string(&configs_path).unwrap(); - let bounds = Bounds::compute(¶ms, &()).unwrap(); - let bits = Bits::compute(¶ms, &bounds).unwrap(); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &()).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); - assert!(configs_content.contains(format!("N: u32 = {}", params.degree()).as_str())); - assert!(configs_content.contains(format!("L: u32 = {}", params.moduli().len()).as_str())); + 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 = {}", diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 8238e89794..02ff3e143a 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -16,8 +16,9 @@ use crate::CircuitsErrors; use crate::ConvertToJson; use crate::PkCircuit; use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; use e3_polynomial::CrtPolynomial; -use fhe::bfv::BfvParameters; use num_bigint::BigUint; use serde::{Deserialize, Serialize}; @@ -31,15 +32,18 @@ pub struct PkComputationOutput { /// Implementation of [`CircuitComputation`] for [`PkCircuit`]. impl CircuitComputation for PkCircuit { - type Params = BfvParameters; + type BfvThresholdParametersPreset = BfvPreset; type Input = PkCircuitInput; type Output = PkComputationOutput; type Error = CircuitsErrors; - fn compute(params: &Self::Params, input: &Self::Input) -> Result { - let bounds = Bounds::compute(params, &())?; - let bits = Bits::compute(params, &bounds)?; - let witness = Witness::compute(params, input)?; + fn compute( + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result { + let bounds = Bounds::compute(preset, &())?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; Ok(PkComputationOutput { bounds, @@ -80,17 +84,23 @@ pub struct Witness { } impl Computation for Configs { - type Params = BfvParameters; + type BfvThresholdParametersPreset = BfvPreset; type Input = (); type Error = CircuitsErrors; - fn compute(params: &Self::Params, _: &Self::Input) -> Result { - let moduli = params.moduli().to_vec(); - let bounds = Bounds::compute(¶ms, &())?; - let bits = Bits::compute(¶ms, &bounds)?; + fn compute( + preset: Self::BfvThresholdParametersPreset, + _: &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 bounds = Bounds::compute(preset, &())?; + let bits = Bits::compute(preset, &bounds)?; Ok(Configs { - n: params.degree(), + n: dkg_params.degree(), l: moduli.len(), moduli, bits, @@ -100,11 +110,14 @@ impl Computation for Configs { } impl Computation for Bits { - type Params = BfvParameters; + type BfvThresholdParametersPreset = BfvPreset; type Input = Bounds; type Error = crate::utils::ZkHelpersUtilsError; - fn compute(_: &Self::Params, input: &Self::Input) -> Result { + fn compute( + _: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result { Ok(Bits { pk_bit: calculate_bit_width(&input.pk_bound.to_string())?, }) @@ -112,14 +125,20 @@ impl Computation for Bits { } impl Computation for Bounds { - type Params = BfvParameters; + type BfvThresholdParametersPreset = BfvPreset; type Input = (); - type Error = fhe::Error; + type Error = CircuitsErrors; + + fn compute( + preset: Self::BfvThresholdParametersPreset, + _: &Self::Input, + ) -> Result { + let (_, dkg_params) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - fn compute(params: &Self::Params, _: &Self::Input) -> Result { let mut pk_bound_max = BigUint::from(0u32); - for &qi in params.moduli() { + for &qi in dkg_params.moduli() { let qi_bound: BigUint = (&BigUint::from(qi) - 1u32) / 2u32; if qi_bound > pk_bound_max { @@ -134,12 +153,17 @@ impl Computation for Bounds { } impl Computation for Witness { - type Params = BfvParameters; + type BfvThresholdParametersPreset = BfvPreset; type Input = PkCircuitInput; type Error = CircuitsErrors; - fn compute(params: &Self::Params, input: &Self::Input) -> Result { - let moduli = params.moduli(); + fn compute( + preset: Self::BfvThresholdParametersPreset, + 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(); 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]); @@ -184,15 +208,13 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::sample::prepare_sample_for_test; use crate::ConvertToJson; - use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; #[test] fn test_bound_and_bits_computation_consistency() { - let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); - let bounds = Bounds::compute(&dkg_params, &()).unwrap(); - let bits = Bits::compute(&dkg_params, &bounds).unwrap(); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &()).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); let expected_bits = calculate_bit_width(&bounds.pk_bound.to_string()).unwrap(); assert_eq!(bounds.pk_bound, BigUint::from(1125899906777088u128)); @@ -201,7 +223,6 @@ mod tests { #[test] fn test_witness_reduction_and_json_roundtrip() { - let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); let sample = prepare_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, @@ -209,7 +230,7 @@ mod tests { ) .unwrap(); let witness = Witness::compute( - &dkg_params, + DEFAULT_BFV_PRESET, &PkCircuitInput { public_key: sample.dkg_public_key, }, @@ -224,8 +245,7 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let (_, dkg_params) = build_pair_for_preset(DEFAULT_BFV_PRESET).unwrap(); - let constants = Configs::compute(&dkg_params, &()).unwrap(); + let constants = Configs::compute(DEFAULT_BFV_PRESET, &()).unwrap(); let json = constants.convert_to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index c2b6590bab..f1f266a1ac 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -11,23 +11,19 @@ use ndarray::Array2; use num_bigint::BigInt; #[derive(Debug)] -pub struct ShareComputationSkCircuit; +pub struct ShareComputationCircuit; -impl Circuit for ShareComputationSkCircuit { - const NAME: &'static str = "share_computation_sk"; - const PREFIX: &'static str = "SHARE_COMPUTATION_SK"; +impl Circuit for ShareComputationCircuit { + const NAME: &'static str = "share_computation"; + const PREFIX: &'static str = match DkgInputType::SecretKey { + DkgInputType::SecretKey => "SHARE_COMPUTATION_SK", + DkgInputType::SmudgingNoise => "SHARE_COMPUTATION_E_SM", + }; const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; - const DKG_INPUT_TYPE: Option = Some(DkgInputType::SecretKey); -} - -#[derive(Debug)] -pub struct ShareComputationEsmCircuit; - -impl Circuit for ShareComputationEsmCircuit { - const NAME: &'static str = "share_computation_e_sm"; - const PREFIX: &'static str = "SHARE_COMPUTATION_E_SM"; - const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; - const DKG_INPUT_TYPE: Option = Some(DkgInputType::SmudgingNoise); + const DKG_INPUT_TYPE: Option = match DkgInputType::SecretKey { + DkgInputType::SecretKey => Some(DkgInputType::SecretKey), + DkgInputType::SmudgingNoise => Some(DkgInputType::SmudgingNoise), + }; } pub struct ShareComputationCircuitInput { From 26739e0c15496bb631192d40f8d1f582396caad3 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 18:00:37 +0100 Subject: [PATCH 04/16] first draft of c2 generator --- crates/fhe-params/src/presets.rs | 17 + crates/parity-matrix/src/matrix.rs | 10 +- crates/polynomial/src/crt_polynomial.rs | 22 ++ crates/zk-helpers/src/bin/zk_cli.rs | 118 ++++-- crates/zk-helpers/src/circuits/dkg/pk/mod.rs | 7 +- .../circuits/dkg/share_computation/circuit.rs | 12 +- .../circuits/dkg/share_computation/codegen.rs | 293 +++++++++++++++ .../dkg/share_computation/computation.rs | 354 ++++++++++++++++++ .../src/circuits/dkg/share_computation/mod.rs | 8 +- crates/zk-helpers/src/circuits/sample.rs | 84 +++-- 10 files changed, 848 insertions(+), 77 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs create mode 100644 crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs diff --git a/crates/fhe-params/src/presets.rs b/crates/fhe-params/src/presets.rs index ddb6a73783..b763b7aaf6 100644 --- a/crates/fhe-params/src/presets.rs +++ b/crates/fhe-params/src/presets.rs @@ -234,6 +234,23 @@ impl BfvPreset { } } + /// Security config name: `"insecure"` or `"secure"` (e.g. for config paths). + pub fn security_config_name(self) -> &'static str { + match self { + BfvPreset::InsecureThreshold512 | BfvPreset::InsecureDkg512 => "insecure", + BfvPreset::SecureThreshold8192 | BfvPreset::SecureDkg8192 => "secure", + } + } + + /// Parses security preset name `"insecure"` or `"secure"` into the corresponding threshold preset. + pub fn from_security_config_name(name: &str) -> Result { + match name.trim().to_lowercase().as_str() { + "insecure" => Ok(Self::InsecureThreshold512), + "secure" => Ok(Self::SecureThreshold8192), + _ => Err(PresetError::UnknownPreset(name.to_string())), + } + } + pub fn list() -> Vec<&'static str> { Self::ALL.iter().map(BfvPreset::name).collect() } diff --git a/crates/parity-matrix/src/matrix.rs b/crates/parity-matrix/src/matrix.rs index 369e24004a..6f91bdf393 100644 --- a/crates/parity-matrix/src/matrix.rs +++ b/crates/parity-matrix/src/matrix.rs @@ -12,7 +12,7 @@ use crate::errors::{ConstraintError, MathError, ParityMatrixError, ParityMatrixResult}; use crate::math::mod_inverse; use crate::math::mod_pow; -use num_bigint::BigUint; +use num_bigint::{BigInt, BigUint}; use num_traits::{One, Zero}; use serde::{Deserialize, Serialize}; @@ -105,6 +105,14 @@ impl ParityMatrix { pub fn get(&self, row: usize, col: usize) -> &BigUint { &self.data[row][col] } + + /// Converts the matrix to rows of [`BigInt`] (non-negative; elements are in `[0, q)`). + pub fn to_bigint_rows(&self) -> Vec> { + self.data + .iter() + .map(|row| row.iter().map(|c| BigInt::from(c.clone())).collect()) + .collect() + } } impl From for Vec> { diff --git a/crates/polynomial/src/crt_polynomial.rs b/crates/polynomial/src/crt_polynomial.rs index ddf244b31b..2689b4e8e8 100644 --- a/crates/polynomial/src/crt_polynomial.rs +++ b/crates/polynomial/src/crt_polynomial.rs @@ -7,6 +7,7 @@ //! CRT (Chinese Remainder Theorem) polynomial representation. use crate::polynomial::Polynomial; +use crate::utils::reduce; use fhe_math::rq::{Poly, Representation}; use num_bigint::BigInt; #[cfg(feature = "serde")] @@ -52,6 +53,27 @@ impl CrtPolynomial { Self { limbs } } + /// Builds a CRT polynomial from a single coefficient vector and moduli. + /// + /// For each modulus `q_i`, the i-th limb is built by reducing each coefficient + /// modulo `q_i` into `[0, q_i)`. Call [`center`](Self::center) afterward if + /// centered coefficients are required. + /// + /// # Arguments + /// + /// * `coeffs` - Polynomial coefficients (e.g. secret key or smudging error). + /// * `moduli` - One modulus per limb. + pub fn from_bigint_coeffs(coeffs: &[BigInt], moduli: &[u64]) -> Self { + let limbs: Vec> = moduli + .iter() + .map(|&qi| { + let qi_big = BigInt::from(qi); + coeffs.iter().map(|c| reduce(c, &qi_big)).collect() + }) + .collect(); + Self::from_bigint_vectors(limbs) + } + /// Builds a `CrtPolynomial` from an fhe-math `Poly` in PowerBasis representation. /// /// Used to prepare inputs for ZK circuits by converting FHE BFV ciphertext polynomials diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index d9dc91a3b5..06e6199ed1 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -14,12 +14,33 @@ use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; +use e3_zk_helpers::circuits::dkg::share_computation::circuit::{ + ShareComputationCircuit, ShareComputationCircuitInput, +}; use e3_zk_helpers::codegen::{write_artifacts, CircuitCodegen}; +use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; use e3_zk_helpers::sample::Sample; use std::path::PathBuf; use std::sync::Arc; +/// DKG input type for share-computation circuit: secret key or smudging noise. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DkgInputTypeArg { + SecretKey, + SmudgingNoise, +} + +fn parse_input_type(s: &str) -> Result { + match s.trim().to_lowercase().as_str() { + "secret-key" => Ok(DkgInputTypeArg::SecretKey), + "smudging-noise" => Ok(DkgInputTypeArg::SmudgingNoise), + _ => Err(anyhow!( + "unknown input-type: {s}. Use \"secret-key\" or \"smudging-noise\"" + )), + } +} + /// Minimal ZK CLI for generating circuit artifacts. #[derive(Debug, Parser)] #[command(name = "zk-cli")] @@ -27,12 +48,15 @@ struct Cli { /// List all available circuits and exit. #[arg(long)] list_circuits: bool, - /// Circuit name to generate artifacts for (e.g. pk-bfv). + /// Circuit name to generate artifacts for (e.g. pk-bfv, share-computation). #[arg(long, required_unless_present = "list_circuits")] circuit: Option, /// Preset: "insecure" (512) or "secure" (8192). Drives both threshold and DKG params. #[arg(long, required_unless_present = "list_circuits")] preset: Option, + /// For share-computation: witness type "secret-key" or "smudging-noise". Required when circuit is share-computation. + #[arg(long)] + witness: Option, /// Output directory for generated artifacts. #[arg(long, default_value = "output")] output: PathBuf, @@ -41,39 +65,13 @@ struct Cli { toml: bool, } -/// Security preset: chooses both threshold and DKG params (insecure = 512, secure = 8192). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum SecurityPreset { - Insecure, - Secure, -} - -impl SecurityPreset { - fn threshold_preset(self) -> BfvPreset { - match self { - SecurityPreset::Insecure => BfvPreset::InsecureThreshold512, - SecurityPreset::Secure => BfvPreset::SecureThreshold8192, - } - } -} - -/// Parses preset name "insecure" or "secure" into a [`SecurityPreset`]. -fn parse_preset(name: &str) -> Result { - match name.trim() { - s if s.eq_ignore_ascii_case("insecure") => Ok(SecurityPreset::Insecure), - s if s.eq_ignore_ascii_case("secure") => Ok(SecurityPreset::Secure), - _ => Err(anyhow!( - "unknown preset: {name}. Use \"insecure\" or \"secure\"" - )), - } -} - fn main() -> Result<()> { let args = Cli::parse(); // Register all circuits in the registry (metadata only). let mut registry = CircuitRegistry::new(); registry.register(Arc::new(PkCircuit)); + registry.register(Arc::new(ShareComputationCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -93,7 +91,8 @@ fn main() -> Result<()> { // Unwrap required arguments (clap ensures they're present when list_circuits is false). let circuit = args.circuit.unwrap(); - let security_preset = parse_preset(&args.preset.unwrap())?; + let preset = BfvPreset::from_security_config_name(&args.preset.unwrap()) + .map_err(|e| anyhow!("{}", e))?; std::fs::create_dir_all(&args.output) .with_context(|| format!("failed to create output dir {}", args.output.display()))?; @@ -104,13 +103,12 @@ fn main() -> Result<()> { anyhow!("unknown circuit: {}. Available: {}", circuit, available) })?; - // Build threshold and DKG params from the security preset (insecure → 512, secure → 8192). - let (threshold_params, dkg_params) = build_pair_for_preset(security_preset.threshold_preset()) - .map_err(|e| anyhow!("failed to build params: {}", e))?; + // Build threshold and DKG params from the preset (insecure → 512, secure → 8192). + let (threshold_params, dkg_params) = + build_pair_for_preset(preset).map_err(|e| anyhow!("failed to build params: {}", e))?; // Validate DKG preset parameter type matches circuit's supported parameter type. - let dkg_preset = security_preset - .threshold_preset() + let dkg_preset = preset .dkg_counterpart() .expect("threshold preset has DKG counterpart"); let preset_param_type = dkg_preset.metadata().parameter_type; @@ -124,19 +122,65 @@ fn main() -> Result<()> { )); } - // Generate sample and artifacts based on circuit name from registry. - let sample = Sample::generate(&threshold_params, &dkg_params, None, 0, 0)?; + // For share-computation: require --witness only when generating Prover.toml (configs are shared). + let dkg_input_type = if circuit_meta.dkg_input_type().is_some() { + let witness_str = if args.toml { + // Only configs: use default (configs.nr is the same for both witness types). + args.witness.as_deref().unwrap_or("secret-key") + } else { + // Prover.toml will be written: witness type is required. + args.witness.as_deref().ok_or_else(|| { + anyhow!( + "circuit {} requires --witness (secret-key or smudging-noise) when generating Prover.toml", + circuit + ) + })? + }; + let arg = parse_input_type(witness_str)?; + match arg { + DkgInputTypeArg::SecretKey => DkgInputType::SecretKey, + DkgInputTypeArg::SmudgingNoise => DkgInputType::SmudgingNoise, + } + } else { + DkgInputType::SecretKey + }; + + let sample = Sample::generate( + &threshold_params, + &dkg_params, + Some(dkg_input_type.clone()), + 0, + 0, + )?; let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { name if name == ::NAME => { let circuit = PkCircuit; circuit.codegen( - security_preset.threshold_preset(), + preset, &PkCircuitInput { public_key: sample.dkg_public_key, }, )? } + name if name == ::NAME => { + let circuit = ShareComputationCircuit; + circuit.codegen( + preset, + &ShareComputationCircuitInput { + dkg_input_type, + secret: sample.secret.as_ref().unwrap().clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample + .parity_matrix + .iter() + .map(|m| m.to_bigint_rows()) + .collect(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + }, + )? + } name => return Err(anyhow!("circuit {} not yet implemented", name)), }; diff --git a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs index 7a9ed7a4b5..a3241a09e6 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs @@ -13,6 +13,7 @@ pub mod circuit; pub mod codegen; pub mod computation; -pub use circuit::*; -pub use codegen::*; -pub use computation::*; + +pub use circuit::{PkCircuit, PkCircuitInput}; +pub use codegen::{generate_configs, generate_toml, TomlJson}; +pub use computation::{Bits, Bounds, Configs, PkComputationOutput, Witness}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index f1f266a1ac..fa0f651fcd 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -7,6 +7,7 @@ use crate::computation::DkgInputType; use crate::registry::Circuit; use e3_fhe_params::ParameterType; +use e3_polynomial::CrtPolynomial; use ndarray::Array2; use num_bigint::BigInt; @@ -14,11 +15,8 @@ use num_bigint::BigInt; pub struct ShareComputationCircuit; impl Circuit for ShareComputationCircuit { - const NAME: &'static str = "share_computation"; - const PREFIX: &'static str = match DkgInputType::SecretKey { - DkgInputType::SecretKey => "SHARE_COMPUTATION_SK", - DkgInputType::SmudgingNoise => "SHARE_COMPUTATION_E_SM", - }; + const NAME: &'static str = "share-computation"; + const PREFIX: &'static str = "SHARE_COMPUTATION"; const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; const DKG_INPUT_TYPE: Option = match DkgInputType::SecretKey { DkgInputType::SecretKey => Some(DkgInputType::SecretKey), @@ -27,7 +25,9 @@ impl Circuit for ShareComputationCircuit { } pub struct ShareComputationCircuitInput { - pub secret_coefficients: Vec, + /// Which secret type this input is for (determines which branch to use in witness). + pub dkg_input_type: DkgInputType, + pub secret: CrtPolynomial, pub secret_sss: Vec>, pub parity_matrix: Vec>>, pub n_parties: u32, diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs new file mode 100644 index 0000000000..d19b7d04e2 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. + +use crate::bigint_to_field; +use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::circuits::computation::CircuitComputation; +use crate::circuits::dkg::share_computation::{ + Bits, ShareComputationCircuit, ShareComputationCircuitInput, ShareComputationOutput, Witness, +}; +use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, Toml}; +use crate::computation::Configs; +use crate::computation::DkgInputType; +use crate::crt_polynomial_to_toml_json; +use crate::registry::Circuit; +use crate::to_string_1d_vec; +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::{Deserialize, Serialize}; +use serde_json; + +/// Implementation of [`CircuitCodegen`] for [`ShareComputationCircuit`]. +impl CircuitCodegen for ShareComputationCircuit { + type BfvThresholdParametersPreset = BfvPreset; + type Input = ShareComputationCircuitInput; + type Error = CircuitsErrors; + + fn codegen( + &self, + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result { + let ShareComputationOutput { witness, bits, .. } = + ShareComputationCircuit::compute(preset, input)?; + + let toml = generate_toml(&witness, input.dkg_input_type.clone())?; + let configs = generate_configs(preset, &bits)?; + + Ok(Artifacts { toml, configs }) + } +} + +/// JSON-serializable structure for Prover.toml (sk_secret or e_sm_secret, y, expected_secret_commitment). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TomlJson { + expected_secret_commitment: String, + /// Either {"sk_secret": {...}} or {"e_sm_secret": [...]}; flattened so the key appears at top level. + #[serde(flatten)] + secret: serde_json::Value, + y: Vec>>, // [N][L][N_PARTIES+1] +} + +pub fn generate_toml( + witness: &Witness, + dkg_input_type: DkgInputType, +) -> Result { + let secret = match dkg_input_type { + DkgInputType::SecretKey => serde_json::json!({ + "sk_secret": { + "coefficients": to_string_1d_vec(witness.secret_crt.limb(0).coefficients()) + } + }), + DkgInputType::SmudgingNoise => serde_json::json!({ + "e_sm_secret": crt_polynomial_to_toml_json(&witness.secret_crt) + }), + }; + + let y: Vec>> = witness + .y + .iter() + .map(|coeff| { + coeff + .iter() + .map(|modulus| { + modulus + .iter() + .map(|share| serde_json::Value::String(share.to_string())) + .collect() + }) + .collect() + }) + .collect(); + + let toml_json = TomlJson { + expected_secret_commitment: witness.expected_secret_commitment.to_string(), + secret, + y, + }; + + Ok(toml::to_string(&toml_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. +pub fn generate_configs(preset: BfvPreset, bits: &Bits) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + let config_name = preset.security_config_name(); + let committee = CiphernodesCommitteeSize::Small.values(); + let parity_matrix_str = + parity_matrix_constant_string(&threshold_params, committee.n, committee.threshold)?; + let prefix = ::PREFIX; + let configs = format!( + r#" +pub use crate::configs::{}::threshold::{{L as L_THRESHOLD, QIS as QIS_THRESHOLD}}; + +pub global N: u32 = {}; + +{} +/************************************ +------------------------------------- +share_computation_sk (CIRCUIT 2a) +------------------------------------- +************************************/ + +// share_computation_sk - bit parameters +pub global {}_BIT_SHARE: u32 = {}; +pub global {}_SK_BIT_SECRET: u32 = {}; + +// share_computation_sk - configs +pub global {}_SK_CONFIGS: ShareComputationConfigs = + ShareComputationConfigs::new(QIS_THRESHOLD); + +/************************************ +------------------------------------- +share_computation_e_sm (CIRCUIT 2b) +------------------------------------- +************************************/ + +// share_computation_e_sm - bit parameters +pub global {}_E_SM_BIT_SECRET: u32 = {}; + +// verify_shares - configs +pub global {}_E_SM_CONFIGS: ShareComputationConfigs = + ShareComputationConfigs::new(QIS_THRESHOLD); +"#, + config_name, + preset.metadata().degree, + parity_matrix_str, + prefix, + bits.bit_share, + prefix, + bits.bit_sk_secret, + prefix, + prefix, + bits.bit_e_sm_secret, + prefix, + ); + + Ok(configs) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::circuits::computation::Computation; + use crate::circuits::dkg::share_computation::{Bits, Bounds}; + use crate::codegen::write_artifacts; + use crate::computation::DkgInputType; + use crate::sample::{prepare_sample_for_test, Sample}; + use crate::Circuit; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + use tempfile::TempDir; + + fn share_computation_input_from_sample( + sample: &Sample, + dkg_input_type: DkgInputType, + ) -> ShareComputationCircuitInput { + ShareComputationCircuitInput { + dkg_input_type, + secret: sample.secret.as_ref().unwrap().clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample + .parity_matrix + .iter() + .map(|m| m.to_bigint_rows()) + .collect(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + } + } + + #[test] + fn test_toml_generation_and_structure() { + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SecretKey), + ) + .unwrap(); + let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); + + let artifacts = ShareComputationCircuit + .codegen(DEFAULT_BFV_PRESET, &input) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + let sk_secret = parsed.get("sk_secret").unwrap(); + assert!(sk_secret + .get("coefficients") + .and_then(|c| c.as_array()) + .is_some()); + let y = parsed.get("y").and_then(|v| v.as_array()).unwrap(); + assert!(!y.is_empty()); + assert!(parsed.get("expected_secret_commitment").is_some()); + + 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("sk_secret")); + assert!(content.contains("expected_secret_commitment")); + assert!(content.contains("y")); + + 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, &input).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let prefix = ::NAME.to_uppercase(); + + assert!(configs_content + .contains(format!("N: u32 = {}", DEFAULT_BFV_PRESET.metadata().degree).as_str())); + assert!(configs_content + .contains(format!("{}_BIT_SHARE: u32 = {}", prefix, bits.bit_share).as_str())); + assert!(configs_content + .contains(format!("{}_SK_BIT_SECRET: u32 = {}", prefix, bits.bit_sk_secret).as_str())); + assert!(configs_content.contains( + format!("{}_E_SM_BIT_SECRET: u32 = {}", prefix, bits.bit_e_sm_secret).as_str() + )); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs new file mode 100644 index 0000000000..1e95da37b0 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -0,0 +1,354 @@ +// 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 pk-bfv circuit: constants, bounds, bit widths, and witness. +//! +//! [`Constants`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) a public key. They implement [`Computation`] and are used by codegen. + +use crate::calculate_bit_width; +use crate::circuits::commitments::{ + compute_share_computation_e_sm_commitment, compute_share_computation_sk_commitment, +}; +use crate::computation::DkgInputType; +use crate::dkg::share_computation::ShareComputationCircuit; +use crate::dkg::share_computation::ShareComputationCircuitInput; +use crate::get_zkp_modulus; +use crate::CircuitsErrors; +use crate::ConvertToJson; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_polynomial::{reduce, CrtPolynomial}; +use fhe::bfv::SecretKey; +use fhe::trbfv::{SmudgingBoundCalculator, SmudgingBoundCalculatorConfig}; +use num_bigint::{BigInt, BigUint}; +use serde::{Deserialize, Serialize}; + +/// Output of [`CircuitComputation::compute`] for [`ShareComputationCircuit`]: bounds, bit widths, and witness. +#[derive(Debug)] +pub struct ShareComputationOutput { + pub bounds: Bounds, + pub bits: Bits, + pub witness: Witness, +} + +/// Implementation of [`CircuitComputation`] for [`ShareComputationCircuit`]. +impl CircuitComputation for ShareComputationCircuit { + type BfvThresholdParametersPreset = BfvPreset; + type Input = ShareComputationCircuitInput; + type Output = ShareComputationOutput; + type Error = CircuitsErrors; + + fn compute( + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result { + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; + + Ok(ShareComputationOutput { + 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, +} + +/// Bit widths used by the Noir prover (e.g. for packing coefficients). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + pub bit_sk_secret: u32, + pub bit_e_sm_secret: u32, + pub bit_share: u32, +} + +/// Coefficient bounds for public key polynomials (used to derive bit widths). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds { + pub sk_bound: BigUint, + pub e_sm_bound: BigUint, +} + +/// Witness data for the share-computation circuit: secret in CRT form, y (secret + shares per coeff/modulus), h (parity matrix), and commitment. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + /// Secret polynomial in CRT form (SK or smudging noise). + pub secret_crt: CrtPolynomial, + /// y[coeff_idx][mod_idx][0] = secret at (mod_idx, coeff_idx); y[coeff_idx][mod_idx][1 + party] = share for party. + pub y: Vec>>, + /// Expected secret commitment (matches C1's compute_secret_commitment). + pub expected_secret_commitment: BigInt, +} + +impl Computation for Configs { + type BfvThresholdParametersPreset = BfvPreset; + type Input = ShareComputationCircuitInput; + type Error = CircuitsErrors; + + fn compute( + preset: Self::BfvThresholdParametersPreset, + 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 bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + + Ok(Configs { + n: dkg_params.degree(), + l: moduli.len(), + moduli, + bits, + bounds, + }) + } +} + +impl Computation for Bits { + type BfvThresholdParametersPreset = BfvPreset; + type Input = Bounds; + type Error = crate::utils::ZkHelpersUtilsError; + + fn compute( + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result { + let (threshold_params, _) = build_pair_for_preset(preset) + .map_err(|e| crate::utils::ZkHelpersUtilsError::ParseBound(e.to_string()))?; + + let mut bit_share = 0; + for &qi in threshold_params.moduli() { + let share_bound = BigUint::from(qi - 1); + let bit_width = calculate_bit_width(&share_bound.to_string())?; + bit_share = bit_share.max(bit_width); + } + + Ok(Bits { + bit_sk_secret: calculate_bit_width(&input.sk_bound.to_string())?, + bit_e_sm_secret: calculate_bit_width(&input.e_sm_bound.to_string())?, + bit_share, + }) + } +} + +impl Computation for Bounds { + type BfvThresholdParametersPreset = BfvPreset; + type Input = ShareComputationCircuitInput; + type Error = CircuitsErrors; + + fn compute( + preset: Self::BfvThresholdParametersPreset, + input: &Self::Input, + ) -> Result { + let (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + let num_ciphertexts = preset.search_defaults().unwrap().z; + let lambda = preset.search_defaults().unwrap().lambda; + + let e_sm_config = SmudgingBoundCalculatorConfig::new( + threshold_params, + input.n_parties as usize, + num_ciphertexts as usize, + lambda as usize, + ); + + let e_sm_calculator = SmudgingBoundCalculator::new(e_sm_config); + + let e_sm_bound = e_sm_calculator.calculate_sm_bound()?; + + Ok(Bounds { + sk_bound: BigUint::from(SecretKey::sk_bound() as u128), + e_sm_bound, + }) + } +} + +impl Computation for Witness { + type BfvThresholdParametersPreset = BfvPreset; + type Input = ShareComputationCircuitInput; + type Error = CircuitsErrors; + + fn compute( + preset: Self::BfvThresholdParametersPreset, + 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(); + let degree = threshold_params.degree(); + let num_moduli = moduli.len(); + let n_parties = input.n_parties as usize; + + let mut secret_crt = input.secret.clone(); + let sss = &input.secret_sss; + + // y[coeff_idx][mod_idx][0] = secret_crt[mod_idx][coeff_idx]; y[coeff_idx][mod_idx][1 + party] = sss[mod_idx][party, coeff_idx] + let mut y: Vec>> = Vec::with_capacity(degree); + for coeff_idx in 0..degree { + let mut y_coeff: Vec> = Vec::with_capacity(num_moduli); + for mod_idx in 0..num_moduli { + let mut y_mod: Vec = Vec::with_capacity(1 + n_parties); + y_mod.push(secret_crt.limb(mod_idx).coefficients()[coeff_idx].clone()); + for party_idx in 0..n_parties { + let share_value = sss[mod_idx][[party_idx, coeff_idx]].clone(); + y_mod.push(share_value); + } + y_coeff.push(y_mod); + } + y.push(y_coeff); + } + + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + let expected_secret_commitment = match input.dkg_input_type { + DkgInputType::SecretKey => { + compute_share_computation_sk_commitment(secret_crt.limb(0), bits.bit_sk_secret) + } + DkgInputType::SmudgingNoise => { + compute_share_computation_e_sm_commitment(&secret_crt, bits.bit_e_sm_secret) + } + }; + + let zkp_modulus = &get_zkp_modulus(); + + secret_crt.reduce_uniform(zkp_modulus); + for coeff in &mut y { + for mod_row in coeff.iter_mut() { + for value in mod_row.iter_mut() { + *value = reduce(value, zkp_modulus); + } + } + } + + Ok(Witness { + secret_crt, + y, + expected_secret_commitment, + }) + } +} + +impl ConvertToJson for Configs { + fn convert_to_json(&self) -> serde_json::Result { + serde_json::to_value(self) + } +} + +impl ConvertToJson for Bounds { + fn convert_to_json(&self) -> serde_json::Result { + serde_json::to_value(self) + } +} + +impl ConvertToJson for Witness { + fn convert_to_json(&self) -> serde_json::Result { + serde_json::to_value(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use crate::dkg::share_computation::ShareComputationCircuitInput; + use crate::sample::{prepare_sample_for_test, Sample}; + use crate::ConvertToJson; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + + fn share_computation_input_from_sample( + sample: &Sample, + dkg_input_type: DkgInputType, + ) -> ShareComputationCircuitInput { + ShareComputationCircuitInput { + dkg_input_type, + secret: sample.secret.as_ref().unwrap().clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample + .parity_matrix + .iter() + .map(|m| m.to_bigint_rows()) + .collect(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + } + } + + #[test] + fn test_bound_and_bits_computation_consistency() { + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SecretKey), + ) + .unwrap(); + let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let expected_sk_bits = calculate_bit_width(&bounds.sk_bound.to_string()).unwrap(); + + assert_eq!(bits.bit_sk_secret, expected_sk_bits); + } + + #[test] + fn test_witness_reduction_and_json_roundtrip() { + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SecretKey), + ) + .unwrap(); + let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); + let witness = Witness::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let json = witness.convert_to_json().unwrap(); + let decoded: Witness = serde_json::from_value(json).unwrap(); + + assert_eq!( + decoded.secret_crt.limbs.len(), + witness.secret_crt.limbs.len() + ); + assert_eq!( + decoded.expected_secret_commitment, + witness.expected_secret_commitment + ); + } + + #[test] + fn test_constants_json_roundtrip() { + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SecretKey), + ) + .unwrap(); + let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); + let constants = Configs::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + + let json = constants.convert_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/dkg/share_computation/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs index d43cea6216..67a6f4a4b0 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -4,5 +4,11 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +//! DKG share-computation circuit (SK or smudging noise). + pub mod circuit; -pub use circuit::*; +pub mod codegen; +pub mod computation; + +pub use circuit::{ShareComputationCircuit, ShareComputationCircuitInput}; +pub use computation::{Bits, Bounds, Configs, ShareComputationOutput, Witness}; diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/sample.rs index 7757104dfe..bd6ba8b244 100644 --- a/crates/zk-helpers/src/circuits/sample.rs +++ b/crates/zk-helpers/src/circuits/sample.rs @@ -16,7 +16,8 @@ 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::{ParityMatrix, ParityMatrixConfig}; +use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; +use e3_polynomial::CrtPolynomial; use fhe::bfv::{BfvParameters, PublicKey, SecretKey}; use fhe::trbfv::{ShareManager, TRBFV}; use num_bigint::BigInt; @@ -24,6 +25,9 @@ use num_bigint::BigUint; use rand::thread_rng; use std::sync::Arc; +/// Shamir secret shares: one limb per CRT modulus (rows = parties, cols = polynomial coefficients). +pub type SecretShares = Vec>; + /// A sample BFV public key (and optionally related data) for circuit codegen or tests. #[derive(Debug, Clone)] pub struct Sample { @@ -31,10 +35,12 @@ pub struct Sample { pub committee: CiphernodesCommittee, /// DKG BFV public key. pub dkg_public_key: PublicKey, - /// Secret shares. - pub secret_sss: Vec>, - /// Parity matrix. - pub parity_matrix: ParityMatrix, + /// Secret in CRT form (SK or smudging noise, depending on [`DkgInputType`]). + pub secret: Option, + /// Secret shares (one [`ndarray::Array2`] per modulus; empty when [`DkgInputType`] is `None`). + pub secret_sss: SecretShares, + /// Parity check matrix per modulus (null space of generator), one [`ParityMatrix`] per CRT modulus. + pub parity_matrix: Vec, } impl Sample { @@ -58,28 +64,30 @@ impl Sample { let mut share_manager = ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); - // Generate parity matrix for each modulus. - let parity_matrix = build_generator_matrix(&ParityMatrixConfig { - q: BigUint::from(threshold_params.moduli()[0]), - t: committee.threshold, - n: committee.n, - }) - .map_err(|e| { - CircuitsErrors::Sample(format!("Failed to build generator matrix: {:?}", e)) - })?; - - let (_, secret_sss) = match dkg_input_type { + // 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, + }) + .map_err(|e| { + CircuitsErrors::Sample(format!("Failed to build generator matrix: {:?}", e)) + })?; + let h = null_space(&g, &q).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to compute null space: {:?}", e)) + })?; + parity_matrix.push(h); + } + + let (secret, secret_sss) = match dkg_input_type { Some(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()) - .map_err(|e| { - CircuitsErrors::Sample(format!( - "Failed to convert SK coeffs to poly: {:?}", - e - )) - }) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to convert SK coeffs to poly: {:?}", @@ -87,19 +95,26 @@ impl Sample { )) })?; - let sk_sss = share_manager + let sk_sss_u64 = share_manager .generate_secret_shares_from_poly(sk_poly.clone(), rng) .map_err(|e| { CircuitsErrors::Sample(format!("Failed to generate SK shares: {:?}", e)) })?; + let secret_sss: SecretShares = sk_sss_u64 + .into_iter() + .map(|arr| arr.mapv(BigInt::from)) + .collect(); - let secret_coeffs: Vec = threshold_secret_key + let sk_coeffs: Vec = threshold_secret_key .coeffs .iter() .map(|&c| BigInt::from(c)) .collect(); + let mut secret_crt = + CrtPolynomial::from_bigint_coeffs(&sk_coeffs, threshold_params.moduli()); + secret_crt.center(threshold_params.moduli())?; - (secret_coeffs, sk_sss) + (Some(secret_crt), secret_sss) } Some(DkgInputType::SmudgingNoise) => { let esi_coeffs = trbfv @@ -113,22 +128,29 @@ impl Sample { let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).map_err(|e| { CircuitsErrors::Sample(format!("Failed to convert error to poly: {:?}", e)) })?; - let esi_sss = share_manager + let esi_sss_u64 = share_manager .generate_secret_shares_from_poly(esi_poly.clone(), rng) .map_err(|e| { CircuitsErrors::Sample(format!("Failed to generate error shares: {:?}", e)) })?; + let secret_sss: SecretShares = esi_sss_u64 + .into_iter() + .map(|arr| arr.mapv(BigInt::from)) + .collect(); - let secret_coeffs = esi_coeffs.clone(); + let mut secret_crt = + CrtPolynomial::from_bigint_coeffs(&esi_coeffs, threshold_params.moduli()); + secret_crt.center(threshold_params.moduli())?; - (secret_coeffs, esi_sss) + (Some(secret_crt), secret_sss) } - None => (Vec::new(), Vec::new()), + None => (None, Vec::new()), }; Ok(Self { committee, dkg_public_key, + secret, secret_sss, parity_matrix, }) @@ -179,6 +201,8 @@ mod tests { assert_eq!(sample.committee.h, committee.h); assert_eq!(sample.dkg_public_key.c.c.len(), 2); assert_eq!(sample.secret_sss.len(), 2); + let secret = sample.secret.as_ref().unwrap(); + assert_eq!(secret.limbs.len(), 2); } #[test] @@ -195,5 +219,7 @@ mod tests { assert_eq!(sample.committee.threshold, committee.threshold); assert_eq!(sample.dkg_public_key.c.c.len(), 2); assert_eq!(sample.secret_sss.len(), 2); + let secret = sample.secret.as_ref().unwrap(); + assert_eq!(secret.limbs.len(), 2); } } From d5d3fdb64056c13b4048ad318b8ae2ef7ac38fdc Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 22:54:07 +0100 Subject: [PATCH 05/16] complete c2 porting --- circuits/lib/src/configs/insecure/dkg.nr | 6 +- circuits/lib/src/configs/secure/dkg.nr | 8 +- crates/zk-helpers/README.md | 30 +-- crates/zk-helpers/src/bin/zk_cli.rs | 183 +++++++++++++----- crates/zk-helpers/src/circuits/computation.rs | 2 +- .../circuits/dkg/share_computation/codegen.rs | 2 +- .../dkg/share_computation/computation.rs | 55 +++++- crates/zk-helpers/src/circuits/sample.rs | 9 +- examples/CRISP/Cargo.lock | 12 ++ templates/default/Cargo.lock | 12 ++ 10 files changed, 241 insertions(+), 78 deletions(-) diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index e1c3e3151a..3797eb9a2b 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -48,8 +48,8 @@ share_computation_sk (CIRCUIT 2a) ************************************/ // share_computation_sk - bit parameters -pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 37; -pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 2; +pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 36; +pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 1; // share_computation_sk - configs pub global SHARE_COMPUTATION_SK_CONFIGS: ShareComputationConfigs = @@ -62,7 +62,7 @@ share_computation_e_sm (CIRCUIT 2b) ************************************/ // share_computation_e_sm - bit parameters -pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 18; +pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 24; // verify_shares - configs pub global SHARE_COMPUTATION_E_SM_CONFIGS: ShareComputationConfigs = diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index b92c731f59..27e544b637 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -60,8 +60,8 @@ share_computation_sk (CIRCUIT 2a) ************************************/ // share_computation_sk - bit parameters -pub global SHARE_COMPUTATION_SK_BIT_SHARE: u32 = 54; -pub global SHARE_COMPUTATION_SK_BIT_SECRET_SK: u32 = 2; +pub global SHARE_COMPUTATION_BIT_SHARE: u32 = 53; +pub global SHARE_COMPUTATION_SK_BIT_SECRET: u32 = 1; // share_computation_sk - configs pub global SHARE_COMPUTATION_SK_CONFIGS: ShareComputationConfigs = @@ -74,9 +74,9 @@ share_computation_e_sm (CIRCUIT 2b) ************************************/ // share_computation_e_sm - bit parameters -pub global SHARE_COMPUTATION_E_SM_BIT_E_SM: u32 = 187; +pub global SHARE_COMPUTATION_E_SM_BIT_SECRET: u32 = 192; -// share_computation_e_sm - configs +// verify_shares - configs pub global SHARE_COMPUTATION_E_SM_CONFIGS: ShareComputationConfigs = ShareComputationConfigs::new(QIS_THRESHOLD); diff --git a/crates/zk-helpers/README.md b/crates/zk-helpers/README.md index b34559a6ac..9937d9711a 100644 --- a/crates/zk-helpers/README.md +++ b/crates/zk-helpers/README.md @@ -1,6 +1,6 @@ # zk-helpers -ZK circuit artifact generation for the Noir prover. +ZK circuit artifact generation for the Noir prover. Produces `configs.nr` and optionally `Prover.toml` for the pk-bfv and share-computation circuits. ## zk-cli @@ -10,17 +10,23 @@ Generate `Prover.toml` and `configs.nr` for a circuit. # List circuits cargo run -p e3-zk-helpers --bin zk_cli -- --list_circuits -# Generate artifacts (default: output/) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset default +# Generate artifacts for pk-bfv (default output: output/) +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure -# Custom output dir; skip Prover.toml (only configs.nr) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset default --output ./artifacts --toml +# Generate artifacts for share-computation (--witness required when writing Prover.toml) +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure --witness secret-key +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset secure --witness smudging-noise + +# Configs only (no Prover.toml): --witness optional for share-computation (configs are shared) +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure --toml +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure --output ./artifacts --toml ``` -| Flag | Description | -| ------------------ | -------------------------------------------------- | -| `--list_circuits` | List circuits and exit | -| `--circuit ` | Circuit (e.g. `pk-bfv`) | -| `--preset ` | BFV preset (must match circuit) | -| `--output ` | Output dir (default: `output`) | -| `--toml` | Skip writing Prover.toml; always writes configs.nr | +| Flag | Description | +| ------------------ | --------------------------------------------------------------------------------------------------------- | +| `--list_circuits` | List circuits and exit | +| `--circuit ` | Circuit: `pk-bfv` or `share-computation` | +| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | +| `--witness ` | For `share-computation` when writing Prover.toml: `secret-key` or `smudging-noise` (optional if `--toml`) | +| `--output ` | Output dir (default: `output`) | +| `--toml` | Skip writing Prover.toml; always writes configs.nr | diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 06e6199ed1..ac3ab776ca 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -13,6 +13,7 @@ use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; +use e3_zk_helpers::ciphernodes_committee::CiphernodesCommitteeSize; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; use e3_zk_helpers::circuits::dkg::share_computation::circuit::{ ShareComputationCircuit, ShareComputationCircuitInput, @@ -21,8 +22,12 @@ use e3_zk_helpers::codegen::{write_artifacts, CircuitCodegen}; use e3_zk_helpers::computation::DkgInputType; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; use e3_zk_helpers::sample::Sample; +use std::io::Write; use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::thread; +use std::time::Duration; /// DKG input type for share-computation circuit: secret key or smudging noise. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -41,6 +46,78 @@ fn parse_input_type(s: &str) -> Result { } } +/// Clear the terminal screen (ANSI escape codes; works on Unix, macOS, and most Windows terminals). +fn clear_terminal() { + print!("\x1b[2J\x1b[1H"); + let _ = std::io::stdout().flush(); +} + +/// Print a summary of what will be generated (circuit, preset, witness, output, artifacts). +fn print_generation_info( + circuit: &str, + preset: BfvPreset, + has_witness: bool, + dkg_input_type: DkgInputType, + output: &std::path::Path, + toml_only: bool, +) { + let meta = preset.metadata(); + println!(" Circuit: {}", circuit); + println!( + " Preset: {} (degree {}, {} moduli)", + preset.security_config_name(), + meta.degree, + meta.num_moduli + ); + if has_witness { + println!( + " Witness: {}", + match dkg_input_type { + DkgInputType::SecretKey => "secret-key", + DkgInputType::SmudgingNoise => "smudging-noise", + } + ); + } + println!(" Output: {}", output.display()); + println!(" Artifacts:"); + if !toml_only { + println!(" • configs.nr only (--toml: Prover.toml skipped)"); + } else { + println!(" • configs.nr"); + println!(" • Prover.toml"); + } + println!(); +} + +/// Run a closure while showing a spinner. Returns the closure's result. +fn run_with_spinner(f: F) -> Result +where + F: FnOnce() -> Result, +{ + let done = Arc::new(AtomicBool::new(false)); + let done_clone = Arc::clone(&done); + let spinner = thread::spawn(move || { + let frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let mut i = 0usize; + while !done_clone.load(Ordering::Relaxed) { + print!("\r {} Generating artifacts... ", frames[i % frames.len()]); + i = i.wrapping_add(1); + std::io::stdout().flush().ok(); + thread::sleep(Duration::from_millis(80)); + } + }); + + let result = f(); + done.store(true, Ordering::Relaxed); + spinner.join().ok(); + result +} + +/// Print the final success message. +fn print_success(output: &std::path::Path) { + println!("\r ✓ Artifacts written to {}", output.display()); +} + /// Minimal ZK CLI for generating circuit artifacts. #[derive(Debug, Parser)] #[command(name = "zk-cli")] @@ -61,7 +138,7 @@ struct Cli { #[arg(long, default_value = "output")] output: PathBuf, /// Skip generating Prover.toml (configs.nr is always generated). - #[arg(long)] + #[arg(long, default_value = "false")] toml: bool, } @@ -145,52 +222,66 @@ fn main() -> Result<()> { DkgInputType::SecretKey }; - let sample = Sample::generate( - &threshold_params, - &dkg_params, - Some(dkg_input_type.clone()), - 0, - 0, - )?; - let circuit_name = circuit_meta.name(); - let artifacts = match circuit_name { - name if name == ::NAME => { - let circuit = PkCircuit; - circuit.codegen( - preset, - &PkCircuitInput { - public_key: sample.dkg_public_key, - }, - )? - } - name if name == ::NAME => { - let circuit = ShareComputationCircuit; - circuit.codegen( - preset, - &ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.as_ref().unwrap().clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample - .parity_matrix - .iter() - .map(|m| m.to_bigint_rows()) - .collect(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - }, - )? - } - name => return Err(anyhow!("circuit {} not yet implemented", name)), - }; + clear_terminal(); + print_generation_info( + &circuit, + preset, + circuit_meta.dkg_input_type().is_some(), + dkg_input_type.clone(), + &args.output, + args.toml, + ); - let toml = if !args.toml { - None - } else { - Some(&artifacts.toml) - }; - write_artifacts(toml, &artifacts.configs, Some(args.output.as_path()))?; + run_with_spinner(|| { + let sample = Sample::generate( + &threshold_params, + &dkg_params, + Some(dkg_input_type.clone()), + CiphernodesCommitteeSize::Small, + preset.search_defaults().unwrap().z, + preset.search_defaults().unwrap().lambda, + )?; + let circuit_name = circuit_meta.name(); + let artifacts = match circuit_name { + name if name == ::NAME => { + let circuit = PkCircuit; + circuit.codegen( + preset, + &PkCircuitInput { + public_key: sample.dkg_public_key, + }, + )? + } + name if name == ::NAME => { + let circuit = ShareComputationCircuit; + circuit.codegen( + preset, + &ShareComputationCircuitInput { + dkg_input_type, + secret: sample.secret.as_ref().unwrap().clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample + .parity_matrix + .iter() + .map(|m| m.to_bigint_rows()) + .collect(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + }, + )? + } + name => return Err(anyhow!("circuit {} not yet implemented", name)), + }; + + let toml = if !args.toml { + None + } else { + Some(&artifacts.toml) + }; + write_artifacts(toml, &artifacts.configs, Some(args.output.as_path()))?; + Ok(()) + })?; - println!("Artifacts written to {}", args.output.display()); + print_success(&args.output); Ok(()) } diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index 07dbda07ad..1f4785e68e 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)] +#[derive(Clone, PartialEq, Eq)] 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/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index d19b7d04e2..58a7e45a33 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -278,7 +278,7 @@ mod tests { let configs_content = std::fs::read_to_string(&configs_path).unwrap(); let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &input).unwrap(); let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); - let prefix = ::NAME.to_uppercase(); + let prefix = ::PREFIX; assert!(configs_content .contains(format!("N: u32 = {}", DEFAULT_BFV_PRESET.metadata().degree).as_str())); 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 1e95da37b0..44a00f9b4e 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -4,10 +4,11 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Computation types for the pk-bfv circuit: constants, bounds, bit widths, and witness. +//! Computation types for the share-computation circuit: constants, bounds, bit widths, and witness. //! -//! [`Constants`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters -//! and (for witness) a public key. They implement [`Computation`] and are used by codegen. +//! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) secret plus shares. Witness values are normalized to [0, q_j) per modulus +//! and then to the ZKP field modulus so the Noir circuit's range check and parity check succeed. use crate::calculate_bit_width; use crate::circuits::commitments::{ @@ -83,12 +84,16 @@ pub struct Bounds { pub e_sm_bound: BigUint, } -/// Witness data for the share-computation circuit: secret in CRT form, y (secret + shares per coeff/modulus), h (parity matrix), and commitment. +/// Witness data for the share-computation circuit: secret in CRT form, y (secret + shares per coeff/modulus), and commitment. +/// +/// All coefficients are reduced to the ZKP field modulus for serialization. Before that, +/// secret_crt and y are normalized so that per modulus j: secret and shares are in [0, q_j), +/// ensuring the circuit's secret consistency (y[i][j][0] == e_sm_secret[j][i]), range check, and parity check pass. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Witness { - /// Secret polynomial in CRT form (SK or smudging noise). + /// Secret polynomial in CRT form (SK or smudging noise). Coefficients in [0, zkp_modulus) for serialization. pub secret_crt: CrtPolynomial, - /// y[coeff_idx][mod_idx][0] = secret at (mod_idx, coeff_idx); y[coeff_idx][mod_idx][1 + party] = share for party. + /// y[coeff_idx][mod_idx][0] = secret at (mod_idx, coeff_idx); y[coeff_idx][mod_idx][1 + party] = share for party. Values in [0, zkp_modulus). pub y: Vec>>, /// Expected secret commitment (matches C1's compute_secret_commitment). pub expected_secret_commitment: BigInt, @@ -198,16 +203,24 @@ impl Computation for Witness { let mut secret_crt = input.secret.clone(); let sss = &input.secret_sss; - // y[coeff_idx][mod_idx][0] = secret_crt[mod_idx][coeff_idx]; y[coeff_idx][mod_idx][1 + party] = sss[mod_idx][party, coeff_idx] + if input.dkg_input_type == DkgInputType::SmudgingNoise { + // Normalize secret_crt to [0, q_j) per limb so it matches what we put in y and what the circuit expects (e_sm_secret[j][i] == y[i][j][0]). + secret_crt + .reduce(moduli) + .map_err(|e| CircuitsErrors::Sample(format!("secret_crt reduce: {:?}", e)))?; + } + + // y[coeff_idx][mod_idx][0] = secret_crt[mod_idx][coeff_idx] (already in [0, q_j)); y[coeff_idx][mod_idx][1+party] = share in [0, q_j). let mut y: Vec>> = Vec::with_capacity(degree); for coeff_idx in 0..degree { let mut y_coeff: Vec> = Vec::with_capacity(num_moduli); for mod_idx in 0..num_moduli { + let q_j = BigInt::from(moduli[mod_idx]); let mut y_mod: Vec = Vec::with_capacity(1 + n_parties); y_mod.push(secret_crt.limb(mod_idx).coefficients()[coeff_idx].clone()); for party_idx in 0..n_parties { - let share_value = sss[mod_idx][[party_idx, coeff_idx]].clone(); - y_mod.push(share_value); + let share_value = &sss[mod_idx][[party_idx, coeff_idx]]; + y_mod.push(reduce(share_value, &q_j)); } y_coeff.push(y_mod); } @@ -331,6 +344,30 @@ mod tests { ); } + #[test] + fn test_witness_smudging_noise_secret_consistency() { + let sample = prepare_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + Some(DkgInputType::SmudgingNoise), + ) + .unwrap(); + let input = share_computation_input_from_sample(&sample, DkgInputType::SmudgingNoise); + let witness = Witness::compute(DEFAULT_BFV_PRESET, &input).unwrap(); + let degree = witness.secret_crt.limb(0).coefficients().len(); + let num_moduli = witness.secret_crt.limbs.len(); + for coeff_idx in 0..degree { + for mod_idx in 0..num_moduli { + let secret_coeff = witness.secret_crt.limb(mod_idx).coefficients()[coeff_idx].clone(); + let y_secret = witness.y[coeff_idx][mod_idx][0].clone(); + assert_eq!( + secret_coeff, y_secret, + "secret consistency: secret_crt[{mod_idx}][{coeff_idx}] must equal y[{coeff_idx}][{mod_idx}][0]" + ); + } + } + } + #[test] fn test_constants_json_roundtrip() { let sample = prepare_sample_for_test( diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/sample.rs index bd6ba8b244..ec323941b3 100644 --- a/crates/zk-helpers/src/circuits/sample.rs +++ b/crates/zk-helpers/src/circuits/sample.rs @@ -7,7 +7,10 @@ //! Sample data generation for circuits. //! //! [`Sample`] produces a random BFV key pair; the public key is used as input -//! for codegen and tests (e.g. pk-bfv circuit). +//! for codegen and tests (e.g. pk-bfv circuit). For share-computation, it can +//! also produce secret-key or smudging-noise data: secret in CRT form, Shamir +//! shares, and parity matrices. Smudging noise is generated with the computed +//! smudging bound (error_size) so coefficients are non-zero. use crate::ciphernodes_committee::CiphernodesCommittee; use crate::ciphernodes_committee::CiphernodesCommitteeSize; @@ -49,12 +52,13 @@ impl Sample { threshold_params: &Arc, dkg_params: &Arc, dkg_input_type: Option, + committee_size: CiphernodesCommitteeSize, num_ciphertexts: u128, // z in the search defaults lambda: u32, ) -> Result { let mut rng = thread_rng(); - let committee = CiphernodesCommitteeSize::Small.values(); + 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); @@ -171,6 +175,7 @@ pub fn prepare_sample_for_test( &threshold_params, &dkg_params, dkg_input_type, + committee, num_ciphertexts, lambda, ) diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 306cbc5d04..9b8ee32528 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2457,6 +2457,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "e3-parity-matrix" +version = "0.1.8" +dependencies = [ + "num-bigint", + "num-traits", + "serde", + "thiserror 1.0.69", +] + [[package]] name = "e3-polynomial" version = "0.1.8" @@ -2573,11 +2583,13 @@ dependencies = [ "ark-ff 0.5.0", "clap", "e3-fhe-params", + "e3-parity-matrix", "e3-polynomial 0.1.8", "e3-safe 0.1.8", "fhe", "fhe-math", "itertools 0.14.0", + "ndarray", "num-bigint", "num-traits", "rand 0.8.5", diff --git a/templates/default/Cargo.lock b/templates/default/Cargo.lock index a48d246b18..51225f50f3 100644 --- a/templates/default/Cargo.lock +++ b/templates/default/Cargo.lock @@ -1318,6 +1318,16 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "e3-parity-matrix" +version = "0.1.8" +dependencies = [ + "num-bigint", + "num-traits", + "serde", + "thiserror", +] + [[package]] name = "e3-polynomial" version = "0.1.8" @@ -1411,11 +1421,13 @@ dependencies = [ "ark-ff 0.5.0", "clap", "e3-fhe-params", + "e3-parity-matrix", "e3-polynomial 0.1.8", "e3-safe 0.1.8", "fhe", "fhe-math", "itertools 0.14.0", + "ndarray", "num-bigint", "num-traits", "rand 0.8.5", From 2996bcc7769190a0980969c808acd2524769780c Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 22:54:47 +0100 Subject: [PATCH 06/16] format --- .../src/circuits/dkg/share_computation/computation.rs | 3 ++- templates/default/program/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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 44a00f9b4e..bbc2f6e425 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -358,7 +358,8 @@ mod tests { let num_moduli = witness.secret_crt.limbs.len(); for coeff_idx in 0..degree { for mod_idx in 0..num_moduli { - let secret_coeff = witness.secret_crt.limb(mod_idx).coefficients()[coeff_idx].clone(); + let secret_coeff = + witness.secret_crt.limb(mod_idx).coefficients()[coeff_idx].clone(); let y_secret = witness.y[coeff_idx][mod_idx][0].clone(); assert_eq!( secret_coeff, y_secret, diff --git a/templates/default/program/src/lib.rs b/templates/default/program/src/lib.rs index 857848e665..ae4b4332dd 100644 --- a/templates/default/program/src/lib.rs +++ b/templates/default/program/src/lib.rs @@ -27,7 +27,7 @@ mod tests { use super::*; use anyhow::Result; use e3_fhe_params::DEFAULT_BFV_PRESET; - use e3_fhe_params::{build_bfv_params_arc, encode_bfv_params, BfvParamSet}; + use e3_fhe_params::{BfvParamSet, build_bfv_params_arc, encode_bfv_params}; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::FheEncoder; use fhe_traits::FheEncrypter; From 0c02bcdfc9d3438d10fa7328d6c7990c5a5e3d27 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 23:14:14 +0100 Subject: [PATCH 07/16] coderabbits stuff --- crates/zk-helpers/README.md | 32 ++++++++--------- crates/zk-helpers/src/bin/zk_cli.rs | 35 +++++++++++-------- .../circuits/dkg/share_computation/circuit.rs | 6 ++-- .../circuits/dkg/share_computation/codegen.rs | 22 ++++++++---- 4 files changed, 53 insertions(+), 42 deletions(-) diff --git a/crates/zk-helpers/README.md b/crates/zk-helpers/README.md index 9937d9711a..d91c71e85a 100644 --- a/crates/zk-helpers/README.md +++ b/crates/zk-helpers/README.md @@ -4,29 +4,27 @@ ZK circuit artifact generation for the Noir prover. Produces `configs.nr` and op ## zk-cli -Generate `Prover.toml` and `configs.nr` for a circuit. +Generate `configs.nr` for a circuit; use `--toml` to also generate `Prover.toml`. ```bash # List circuits cargo run -p e3-zk-helpers --bin zk_cli -- --list_circuits -# Generate artifacts for pk-bfv (default output: output/) +# Generate configs.nr only (default) cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure -# Generate artifacts for share-computation (--witness required when writing Prover.toml) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure --witness secret-key -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset secure --witness smudging-noise - -# Configs only (no Prover.toml): --witness optional for share-computation (configs are shared) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure --toml -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure --output ./artifacts --toml +# Generate configs.nr and Prover.toml (--witness required for share-computation) +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure --toml +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure --witness secret-key --toml +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset secure --witness smudging-noise --toml ``` -| Flag | Description | -| ------------------ | --------------------------------------------------------------------------------------------------------- | -| `--list_circuits` | List circuits and exit | -| `--circuit ` | Circuit: `pk-bfv` or `share-computation` | -| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | -| `--witness ` | For `share-computation` when writing Prover.toml: `secret-key` or `smudging-noise` (optional if `--toml`) | -| `--output ` | Output dir (default: `output`) | -| `--toml` | Skip writing Prover.toml; always writes configs.nr | +| Flag | Description | +| ------------------- | -------------------------------------------------------------------------------------------------------------- | +| `--list_circuits` | List circuits and exit | +| `--circuit ` | Circuit: `pk-bfv` or `share-computation` | +| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | +| `--witness ` | For `share-computation` when using `--toml`: `secret-key` or `smudging-noise` | +| `--output ` | Output dir (default: `output`) | +| `--toml` | Also write Prover.toml (default: configs.nr only) | diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index ac3ab776ca..3fb41bc2cc 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -59,7 +59,7 @@ fn print_generation_info( has_witness: bool, dkg_input_type: DkgInputType, output: &std::path::Path, - toml_only: bool, + write_prover_toml: bool, ) { let meta = preset.metadata(); println!(" Circuit: {}", circuit); @@ -80,11 +80,11 @@ fn print_generation_info( } println!(" Output: {}", output.display()); println!(" Artifacts:"); - if !toml_only { - println!(" • configs.nr only (--toml: Prover.toml skipped)"); - } else { + if write_prover_toml { println!(" • configs.nr"); println!(" • Prover.toml"); + } else { + println!(" • configs.nr only (pass --toml to also generate Prover.toml)"); } println!(); } @@ -137,7 +137,7 @@ struct Cli { /// Output directory for generated artifacts. #[arg(long, default_value = "output")] output: PathBuf, - /// Skip generating Prover.toml (configs.nr is always generated). + /// Also write Prover.toml (default: configs.nr only). #[arg(long, default_value = "false")] toml: bool, } @@ -199,16 +199,21 @@ fn main() -> Result<()> { )); } + let write_prover_toml = args.toml; + // Circuits that accept runtime witness type (e.g. share-computation with SecretKey or SmudgingNoise) have DKG_INPUT_TYPE == None but still need witness handling. + let has_witness_type = circuit_meta.dkg_input_type().is_some() + || circuit_meta.name() == ShareComputationCircuit::NAME; + // For share-computation: require --witness only when generating Prover.toml (configs are shared). - let dkg_input_type = if circuit_meta.dkg_input_type().is_some() { - let witness_str = if args.toml { - // Only configs: use default (configs.nr is the same for both witness types). + let dkg_input_type = if has_witness_type { + let witness_str = if !args.toml { + // Configs-only: witness optional (configs.nr is the same for both witness types). args.witness.as_deref().unwrap_or("secret-key") } else { - // Prover.toml will be written: witness type is required. + // Generating Prover.toml: witness type is required. args.witness.as_deref().ok_or_else(|| { anyhow!( - "circuit {} requires --witness (secret-key or smudging-noise) when generating Prover.toml", + "circuit {} requires --witness (secret-key or smudging-noise) when writing Prover.toml", circuit ) })? @@ -226,10 +231,10 @@ fn main() -> Result<()> { print_generation_info( &circuit, preset, - circuit_meta.dkg_input_type().is_some(), + has_witness_type, dkg_input_type.clone(), &args.output, - args.toml, + write_prover_toml, ); run_with_spinner(|| { @@ -273,10 +278,10 @@ fn main() -> Result<()> { name => return Err(anyhow!("circuit {} not yet implemented", name)), }; - let toml = if !args.toml { - None - } else { + let toml = if write_prover_toml { Some(&artifacts.toml) + } else { + None }; write_artifacts(toml, &artifacts.configs, Some(args.output.as_path()))?; Ok(()) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index fa0f651fcd..44aa0c2c49 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -18,10 +18,8 @@ impl Circuit for ShareComputationCircuit { const NAME: &'static str = "share-computation"; const PREFIX: &'static str = "SHARE_COMPUTATION"; const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; - const DKG_INPUT_TYPE: Option = match DkgInputType::SecretKey { - DkgInputType::SecretKey => Some(DkgInputType::SecretKey), - DkgInputType::SmudgingNoise => Some(DkgInputType::SmudgingNoise), - }; + /// None: circuit accepts runtime-varying input type (SecretKey or SmudgingNoise via `ShareComputationCircuitInput::dkg_input_type`). + const DKG_INPUT_TYPE: Option = None; } pub struct ShareComputationCircuitInput { 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 58a7e45a33..cab21a1cf3 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -7,7 +7,6 @@ //! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. use crate::bigint_to_field; -use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::computation::CircuitComputation; use crate::circuits::dkg::share_computation::{ Bits, ShareComputationCircuit, ShareComputationCircuitInput, ShareComputationOutput, Witness, @@ -41,7 +40,12 @@ impl CircuitCodegen for ShareComputationCircuit { ShareComputationCircuit::compute(preset, input)?; let toml = generate_toml(&witness, input.dkg_input_type.clone())?; - let configs = generate_configs(preset, &bits)?; + let configs = generate_configs( + preset, + &bits, + input.n_parties as usize, + input.threshold as usize, + )?; Ok(Artifacts { toml, configs }) } @@ -142,13 +146,19 @@ fn parity_matrix_constant_string( } /// Builds the configs.nr string (N, L, parity matrix, bit parameters, configs) for the Noir prover. -pub fn generate_configs(preset: BfvPreset, bits: &Bits) -> Result { +/// +/// `n_parties` and `threshold` are used to build the parity matrix (Reed–Solomon generator null space) +/// and must match the committee size used for the witness/sample. +pub fn generate_configs( + preset: BfvPreset, + bits: &Bits, + n_parties: usize, + threshold: usize, +) -> Result { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let config_name = preset.security_config_name(); - let committee = CiphernodesCommitteeSize::Small.values(); - let parity_matrix_str = - parity_matrix_constant_string(&threshold_params, committee.n, committee.threshold)?; + let parity_matrix_str = parity_matrix_constant_string(&threshold_params, n_parties, threshold)?; let prefix = ::PREFIX; let configs = format!( r#" From a5a9ba6a8514925fbc80637221d73fca837514ea Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 23:23:01 +0100 Subject: [PATCH 08/16] other minor consistency suggestions --- crates/zk-helpers/src/bin/zk_cli.rs | 19 ++++++++++--------- .../circuits/dkg/share_computation/circuit.rs | 2 +- .../dkg/share_computation/computation.rs | 9 +++++---- crates/zk-helpers/src/circuits/sample.rs | 7 +++++-- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 3fb41bc2cc..c19a43ca48 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -12,7 +12,7 @@ use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; -use e3_fhe_params::{build_pair_for_preset, BfvPreset}; +use e3_fhe_params::{build_pair_for_preset, BfvPreset, ParameterType}; use e3_zk_helpers::ciphernodes_committee::CiphernodesCommitteeSize; use e3_zk_helpers::circuits::dkg::pk::circuit::{PkCircuit, PkCircuitInput}; use e3_zk_helpers::circuits::dkg::share_computation::circuit::{ @@ -184,16 +184,17 @@ fn main() -> Result<()> { let (threshold_params, dkg_params) = build_pair_for_preset(preset).map_err(|e| anyhow!("failed to build params: {}", e))?; - // Validate DKG preset parameter type matches circuit's supported parameter type. - let dkg_preset = preset - .dkg_counterpart() - .expect("threshold preset has DKG counterpart"); - let preset_param_type = dkg_preset.metadata().parameter_type; + // Validate preset matches circuit's supported parameter type (THRESHOLD or DKG). let circuit_param_type = circuit_meta.supported_parameter(); - if preset_param_type != circuit_param_type { + let preset_ok = match circuit_param_type { + ParameterType::THRESHOLD => preset.metadata().parameter_type == ParameterType::THRESHOLD, + ParameterType::DKG => preset + .dkg_counterpart() + .is_some_and(|dkg| dkg.metadata().parameter_type == ParameterType::DKG), + }; + if !preset_ok { return Err(anyhow!( - "preset has parameter type {:?}, but circuit {} requires {:?}", - preset_param_type, + "preset does not match circuit {} which requires {:?} (use insecure/secure for threshold circuits)", circuit, circuit_param_type )); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index 44aa0c2c49..c33618fa50 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -17,7 +17,7 @@ pub struct ShareComputationCircuit; impl Circuit for ShareComputationCircuit { const NAME: &'static str = "share-computation"; const PREFIX: &'static str = "SHARE_COMPUTATION"; - const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::THRESHOLD; /// None: circuit accepts runtime-varying input type (SecretKey or SmudgingNoise via `ShareComputationCircuitInput::dkg_input_type`). const DKG_INPUT_TYPE: Option = None; } 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 bbc2f6e425..0f0c3ec71a 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -108,16 +108,17 @@ impl Computation for Configs { preset: Self::BfvThresholdParametersPreset, input: &Self::Input, ) -> Result { - let (_, dkg_params) = + let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - let moduli = dkg_params.moduli().to_vec(); + let moduli = threshold_params.moduli().to_vec(); + let l = moduli.len(); let bounds = Bounds::compute(preset, input)?; let bits = Bits::compute(preset, &bounds)?; Ok(Configs { - n: dkg_params.degree(), - l: moduli.len(), + n: threshold_params.degree(), + l, moduli, bits, bounds, diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/sample.rs index ec323941b3..de01cc48f5 100644 --- a/crates/zk-helpers/src/circuits/sample.rs +++ b/crates/zk-helpers/src/circuits/sample.rs @@ -169,8 +169,11 @@ pub fn prepare_sample_for_test( ) -> Result { let (threshold_params, dkg_params) = build_pair_for_preset(threshold_preset) .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - let num_ciphertexts = threshold_preset.search_defaults().unwrap().z; - let lambda = threshold_preset.search_defaults().unwrap().lambda; + let defaults = threshold_preset + .search_defaults() + .ok_or_else(|| CircuitsErrors::Sample("preset has no search defaults".to_string()))?; + let num_ciphertexts = defaults.z; + let lambda = defaults.lambda; let sample = Sample::generate( &threshold_params, &dkg_params, From 5b6598d9503dd8d823f90f4237aa4570259b31e3 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 23:27:19 +0100 Subject: [PATCH 09/16] other small nits --- .../src/circuits/dkg/share_computation/codegen.rs | 2 +- .../src/circuits/dkg/share_computation/computation.rs | 7 +++++-- crates/zk-helpers/src/circuits/mod.rs | 5 +++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index cab21a1cf3..131352446f 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Code generation for the public-key BFV circuit: Prover.toml and configs.nr. +//! Code generation for the share-computation BFV circuit: Prover.toml and configs.nr. use crate::bigint_to_field; use crate::circuits::computation::CircuitComputation; 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 0f0c3ec71a..d0390f453c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -164,8 +164,11 @@ impl Computation for Bounds { ) -> Result { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - let num_ciphertexts = preset.search_defaults().unwrap().z; - let lambda = preset.search_defaults().unwrap().lambda; + let defaults = preset + .search_defaults() + .ok_or_else(|| CircuitsErrors::Sample("missing search defaults".to_string()))?; + let num_ciphertexts = defaults.z; + let lambda = defaults.lambda; let e_sm_config = SmudgingBoundCalculatorConfig::new( threshold_params, diff --git a/crates/zk-helpers/src/circuits/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index fcb0243ef5..744d44dcee 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -8,8 +8,9 @@ //! //! This module provides circuit metadata ([`Circuit`](crate::registry::Circuit)), artifact //! codegen ([`CircuitCodegen`], [`Artifacts`]), commitment helpers ([`commitments`]), -//! and sample data generation ([`Sample`]). The `pk_bfv` submodule implements the -//! public-key BFV commitment circuit. +//! and sample data generation ([`Sample`]). The [`dkg::pk`](dkg::pk) submodule implements the +//! public-key BFV commitment circuit and is re-exported here: [`generate_configs`], [`generate_toml`], +//! [`TomlJson`], [`Bits`], [`Bounds`], [`PkComputationOutput`], [`Witness`], and [`PkCircuit`]. pub mod codegen; pub mod commitments; From cc9833de3f82b5f639cff6bb391efe62d887f74a Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 23:33:40 +0100 Subject: [PATCH 10/16] remove pk-bfv terms --- crates/zk-helpers/README.md | 23 ++++++++++--------- crates/zk-helpers/src/bin/zk_cli.rs | 2 +- .../zk-helpers/src/circuits/dkg/pk/codegen.rs | 2 +- .../src/circuits/dkg/pk/computation.rs | 4 ++-- crates/zk-helpers/src/circuits/sample.rs | 2 +- crates/zk-helpers/src/registry/mod.rs | 2 +- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/crates/zk-helpers/README.md b/crates/zk-helpers/README.md index d91c71e85a..85f452cadf 100644 --- a/crates/zk-helpers/README.md +++ b/crates/zk-helpers/README.md @@ -1,6 +1,7 @@ # zk-helpers -ZK circuit artifact generation for the Noir prover. Produces `configs.nr` and optionally `Prover.toml` for the pk-bfv and share-computation circuits. +ZK circuit artifact generation for the Noir prover. Produces `configs.nr` and optionally +`Prover.toml` for the Enclave circuits. ## zk-cli @@ -11,20 +12,20 @@ Generate `configs.nr` for a circuit; use `--toml` to also generate `Prover.toml` cargo run -p e3-zk-helpers --bin zk_cli -- --list_circuits # Generate configs.nr only (default) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk --preset insecure cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure # Generate configs.nr and Prover.toml (--witness required for share-computation) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset insecure --toml +cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk --preset insecure --toml cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset insecure --witness secret-key --toml cargo run -p e3-zk-helpers --bin zk_cli -- --circuit share-computation --preset secure --witness smudging-noise --toml ``` -| Flag | Description | -| ------------------- | -------------------------------------------------------------------------------------------------------------- | -| `--list_circuits` | List circuits and exit | -| `--circuit ` | Circuit: `pk-bfv` or `share-computation` | -| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | -| `--witness ` | For `share-computation` when using `--toml`: `secret-key` or `smudging-noise` | -| `--output ` | Output dir (default: `output`) | -| `--toml` | Also write Prover.toml (default: configs.nr only) | +| Flag | Description | +| ------------------ | ----------------------------------------------------------------------------- | +| `--list_circuits` | List circuits and exit | +| `--circuit ` | Circuit: `pk` or `share-computation` | +| `--preset ` | Security preset: `insecure` (512) or `secure` (8192) | +| `--witness ` | For `share-computation` when using `--toml`: `secret-key` or `smudging-noise` | +| `--output ` | Output dir (default: `output`) | +| `--toml` | Also write Prover.toml (default: configs.nr only) | diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index c19a43ca48..b2e556c31e 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -125,7 +125,7 @@ struct Cli { /// List all available circuits and exit. #[arg(long)] list_circuits: bool, - /// Circuit name to generate artifacts for (e.g. pk-bfv, share-computation). + /// Circuit name to generate artifacts for (e.g. pk, share-computation). #[arg(long, required_unless_present = "list_circuits")] circuit: Option, /// Preset: "insecure" (512) or "secure" (8192). Drives both threshold and DKG params. diff --git a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 87f2ab0062..edd3c1ba89 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -45,7 +45,7 @@ pub struct TomlJson { pub pk1is: Vec, } -/// Builds the Prover TOML string from the pk-bfv witness (pk0is, pk1is). +/// Builds the Prover TOML string from the pk witness (pk0is, pk1is). pub fn generate_toml(witness: Witness) -> Result { let pk0is = crt_polynomial_to_toml_json(&witness.pk0is); let pk1is = crt_polynomial_to_toml_json(&witness.pk1is); diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 02ff3e143a..fe9f4581bd 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Computation types for the pk-bfv circuit: constants, bounds, bit widths, and witness. +//! Computation types for the pk circuit: constants, bounds, bit widths, and witness. //! //! [`Constants`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters //! and (for witness) a public key. They implement [`Computation`] and are used by codegen. @@ -75,7 +75,7 @@ pub struct Bounds { pub pk_bound: BigUint, } -/// Witness data for the pk-bfv circuit: public key polynomials in CRT form for the prover. +/// Witness data for the pk circuit: public key polynomials in CRT form for the prover. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Witness { /// Public key polynomials (pk0, pk1) for each CRT basis. diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/sample.rs index de01cc48f5..37263ee618 100644 --- a/crates/zk-helpers/src/circuits/sample.rs +++ b/crates/zk-helpers/src/circuits/sample.rs @@ -7,7 +7,7 @@ //! Sample data generation for circuits. //! //! [`Sample`] produces a random BFV key pair; the public key is used as input -//! for codegen and tests (e.g. pk-bfv circuit). For share-computation, it can +//! for codegen and tests (e.g. pk circuit). For share-computation, it can //! also produce secret-key or smudging-noise data: secret in CRT form, Shamir //! shares, and parity matrices. Smudging noise is generated with the computed //! smudging bound (error_size) so coefficients are non-zero. diff --git a/crates/zk-helpers/src/registry/mod.rs b/crates/zk-helpers/src/registry/mod.rs index 02e9b54a4c..5ccad625fa 100644 --- a/crates/zk-helpers/src/registry/mod.rs +++ b/crates/zk-helpers/src/registry/mod.rs @@ -6,7 +6,7 @@ //! Circuit registry and metadata. //! -//! The registry maps circuit names (e.g. `pk-bfv`) to [`CircuitMetadata`]. Use +//! The registry maps circuit names (e.g. `pk`) to [`CircuitMetadata`]. Use //! [`CircuitRegistry`] to register and look up circuits by name. pub mod registry; From 4f7fe41cbd1e0a6442ad5726b50e5d980a6475be Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 3 Feb 2026 23:50:30 +0100 Subject: [PATCH 11/16] avoid panic on search defaults --- crates/zk-helpers/src/bin/zk_cli.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index b2e556c31e..aea9d78060 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -239,13 +239,16 @@ fn main() -> Result<()> { ); run_with_spinner(|| { + let sd = preset + .search_defaults() + .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; let sample = Sample::generate( &threshold_params, &dkg_params, Some(dkg_input_type.clone()), CiphernodesCommitteeSize::Small, - preset.search_defaults().unwrap().z, - preset.search_defaults().unwrap().lambda, + sd.z, + sd.lambda, )?; let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { From a53934dfb1887ed9c5d098d4e51a9d94cca1453c Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 4 Feb 2026 11:48:53 +0100 Subject: [PATCH 12/16] minor improvements and refactoring --- crates/fhe-params/src/lib.rs | 2 +- crates/fhe-params/src/presets.rs | 64 +++++++++++--- crates/polynomial/src/crt_polynomial.rs | 12 +-- crates/zk-helpers/src/bin/zk_cli.rs | 48 +++++------ crates/zk-helpers/src/circuits/computation.rs | 2 +- .../zk-helpers/src/circuits/dkg/pk/codegen.rs | 4 +- .../src/circuits/dkg/pk/computation.rs | 5 +- crates/zk-helpers/src/circuits/dkg/pk/mod.rs | 8 +- .../zk-helpers/src/circuits/dkg/pk/sample.rs | 75 +++++++++++++++++ .../circuits/dkg/share_computation/circuit.rs | 3 +- .../circuits/dkg/share_computation/codegen.rs | 18 ++-- .../dkg/share_computation/computation.rs | 28 +++---- .../src/circuits/dkg/share_computation/mod.rs | 4 +- .../{ => dkg/share_computation}/sample.rs | 84 ++++++++----------- crates/zk-helpers/src/circuits/mod.rs | 15 +--- 15 files changed, 225 insertions(+), 147 deletions(-) create mode 100644 crates/zk-helpers/src/circuits/dkg/pk/sample.rs rename crates/zk-helpers/src/circuits/{ => dkg/share_computation}/sample.rs (75%) diff --git a/crates/fhe-params/src/lib.rs b/crates/fhe-params/src/lib.rs index 162fb6a422..dd12901d84 100644 --- a/crates/fhe-params/src/lib.rs +++ b/crates/fhe-params/src/lib.rs @@ -21,5 +21,5 @@ pub use builder::{ pub use encoding::{decode_bfv_params, decode_bfv_params_arc, encode_bfv_params, EncodingError}; pub use presets::{ default_param_set, BfvParamSet, BfvPreset, ParameterType, PresetError, PresetMetadata, - PresetSearchDefaults, DEFAULT_BFV_PRESET, + PresetSearchDefaults, SecurityTier, DEFAULT_BFV_PRESET, }; diff --git a/crates/fhe-params/src/presets.rs b/crates/fhe-params/src/presets.rs index b763b7aaf6..1d6c9cb16c 100644 --- a/crates/fhe-params/src/presets.rs +++ b/crates/fhe-params/src/presets.rs @@ -81,6 +81,37 @@ pub enum ParameterType { DKG, } +/// Security tier for BFV presets +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SecurityTier { + /// Insecure security tier + INSECURE, + /// Secure security tier + SECURE, +} + +impl SecurityTier { + /// Config path segment for Noir (e.g. `configs::{}::threshold`). + pub fn as_config_str(self) -> &'static str { + match self { + SecurityTier::INSECURE => "insecure", + SecurityTier::SECURE => "secure", + } + } +} + +impl core::str::FromStr for SecurityTier { + type Err = PresetError; + + fn from_str(s: &str) -> Result { + match s.trim().to_lowercase().as_str() { + "insecure" => Ok(Self::INSECURE), + "secure" => Ok(Self::SECURE), + _ => Err(PresetError::UnknownPreset(s.to_string())), + } + } +} + /// Metadata describing a BFV preset configuration /// /// This struct contains high-level information about a preset, including @@ -110,6 +141,8 @@ pub struct PresetMetadata { pub lambda: usize, /// Parameter type (DKG (BFV) / Threshold (trBFV)). pub parameter_type: ParameterType, + /// Security tier (e.g. for Noir `configs::{}::threshold`). Use [`SecurityTier::as_config_str`] for the path segment. + pub security: SecurityTier, } /// Default search parameters for BFV parameter generation @@ -234,21 +267,22 @@ impl BfvPreset { } } - /// Security config name: `"insecure"` or `"secure"` (e.g. for config paths). - pub fn security_config_name(self) -> &'static str { - match self { - BfvPreset::InsecureThreshold512 | BfvPreset::InsecureDkg512 => "insecure", - BfvPreset::SecureThreshold8192 | BfvPreset::SecureDkg8192 => "secure", - } - } - - /// Parses security preset name `"insecure"` or `"secure"` into the corresponding threshold preset. + /// Parses "insecure"|"secure" or λ (e.g. 2|80) into the threshold preset. Uses [`PAIR_PRESETS`] and [`PresetMetadata`]. pub fn from_security_config_name(name: &str) -> Result { - match name.trim().to_lowercase().as_str() { - "insecure" => Ok(Self::InsecureThreshold512), - "secure" => Ok(Self::SecureThreshold8192), - _ => Err(PresetError::UnknownPreset(name.to_string())), + let s = name.trim(); + if let Ok(lambda) = s.parse::() { + return Self::PAIR_PRESETS + .iter() + .copied() + .find(|p| p.metadata().lambda == lambda) + .ok_or_else(|| PresetError::UnknownPreset(format!("lambda {lambda}"))); } + let tier: SecurityTier = s.parse()?; + Self::PAIR_PRESETS + .iter() + .copied() + .find(|p| p.metadata().security == tier) + .ok_or_else(|| PresetError::UnknownPreset(name.to_string())) } pub fn list() -> Vec<&'static str> { @@ -285,6 +319,7 @@ impl BfvPreset { num_parties: insecure_512::NUM_PARTIES, lambda: DEFAULT_INSECURE_LAMBDA, parameter_type: ParameterType::THRESHOLD, + security: SecurityTier::INSECURE, }, BfvPreset::InsecureDkg512 => PresetMetadata { name: self.name(), @@ -293,6 +328,7 @@ impl BfvPreset { num_parties: insecure_512::NUM_PARTIES, lambda: DEFAULT_INSECURE_LAMBDA, parameter_type: ParameterType::DKG, + security: SecurityTier::INSECURE, }, BfvPreset::SecureThreshold8192 => PresetMetadata { name: self.name(), @@ -301,6 +337,7 @@ impl BfvPreset { num_parties: secure_8192::NUM_PARTIES, lambda: DEFAULT_SECURE_LAMBDA, parameter_type: ParameterType::THRESHOLD, + security: SecurityTier::SECURE, }, BfvPreset::SecureDkg8192 => PresetMetadata { name: self.name(), @@ -309,6 +346,7 @@ impl BfvPreset { num_parties: secure_8192::NUM_PARTIES, lambda: DEFAULT_SECURE_LAMBDA, parameter_type: ParameterType::DKG, + security: SecurityTier::SECURE, }, } } diff --git a/crates/polynomial/src/crt_polynomial.rs b/crates/polynomial/src/crt_polynomial.rs index 2689b4e8e8..69a5914e96 100644 --- a/crates/polynomial/src/crt_polynomial.rs +++ b/crates/polynomial/src/crt_polynomial.rs @@ -53,22 +53,18 @@ impl CrtPolynomial { Self { limbs } } - /// Builds a CRT polynomial from a single coefficient vector and moduli. - /// - /// For each modulus `q_i`, the i-th limb is built by reducing each coefficient - /// modulo `q_i` into `[0, q_i)`. Call [`center`](Self::center) afterward if - /// centered coefficients are required. + /// Builds a CRT polynomial from a polynomial mod Q (Q>>128) and moduli. /// /// # Arguments /// - /// * `coeffs` - Polynomial coefficients (e.g. secret key or smudging error). + /// * `coeffs` - Polynomial coefficients mod Q (Q>>128). /// * `moduli` - One modulus per limb. - pub fn from_bigint_coeffs(coeffs: &[BigInt], moduli: &[u64]) -> Self { + pub fn from_mod_q_polynomial(poly: &Vec, moduli: &[u64]) -> Self { let limbs: Vec> = moduli .iter() .map(|&qi| { let qi_big = BigInt::from(qi); - coeffs.iter().map(|c| reduce(c, &qi_big)).collect() + poly.iter().map(|c| reduce(c, &qi_big)).collect() }) .collect(); Self::from_bigint_vectors(limbs) diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index aea9d78060..f5f963ce2e 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -8,7 +8,7 @@ //! //! This binary lists available circuits and generates Prover.toml and configs.nr //! for use with the Noir prover. Use `--list_circuits` to see circuits and -//! `--circuit --preset insecure|secure` to generate artifacts. +//! `--circuit --preset insecure|secure|2|80` to generate artifacts. use anyhow::{anyhow, Context, Result}; use clap::{arg, command, Parser}; @@ -21,7 +21,7 @@ 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::registry::{Circuit, CircuitRegistry}; -use e3_zk_helpers::sample::Sample; +use e3_zk_helpers::{PkSample, ShareComputationSample}; use std::io::Write; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; @@ -65,7 +65,7 @@ fn print_generation_info( println!(" Circuit: {}", circuit); println!( " Preset: {} (degree {}, {} moduli)", - preset.security_config_name(), + meta.security.as_config_str(), meta.degree, meta.num_moduli ); @@ -128,7 +128,7 @@ struct Cli { /// Circuit name to generate artifacts for (e.g. pk, share-computation). #[arg(long, required_unless_present = "list_circuits")] circuit: Option, - /// Preset: "insecure" (512) or "secure" (8192). Drives both threshold and DKG params. + /// Preset: "insecure"|"secure" or λ (2|80). Drives both threshold and DKG params. #[arg(long, required_unless_present = "list_circuits")] preset: Option, /// For share-computation: witness type "secret-key" or "smudging-noise". Required when circuit is share-computation. @@ -168,8 +168,7 @@ fn main() -> Result<()> { // Unwrap required arguments (clap ensures they're present when list_circuits is false). let circuit = args.circuit.unwrap(); - let preset = BfvPreset::from_security_config_name(&args.preset.unwrap()) - .map_err(|e| anyhow!("{}", e))?; + let preset = BfvPreset::from_security_config_name(&args.preset.unwrap())?; std::fs::create_dir_all(&args.output) .with_context(|| format!("failed to create output dir {}", args.output.display()))?; @@ -194,7 +193,7 @@ fn main() -> Result<()> { }; if !preset_ok { return Err(anyhow!( - "preset does not match circuit {} which requires {:?} (use insecure/secure for threshold circuits)", + "preset does not match circuit {} which requires {:?} (use insecure or secure)", circuit, circuit_param_type )); @@ -239,20 +238,14 @@ fn main() -> Result<()> { ); run_with_spinner(|| { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = Sample::generate( - &threshold_params, - &dkg_params, - Some(dkg_input_type.clone()), - CiphernodesCommitteeSize::Small, - sd.z, - sd.lambda, - )?; let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { name if name == ::NAME => { + let sample = PkSample::generate( + &threshold_params, + &dkg_params, + CiphernodesCommitteeSize::Small, + )?; let circuit = PkCircuit; circuit.codegen( preset, @@ -262,18 +255,25 @@ fn main() -> Result<()> { )? } name if name == ::NAME => { + let sd = preset + .search_defaults() + .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; + let sample = ShareComputationSample::generate( + &threshold_params, + &dkg_params, + CiphernodesCommitteeSize::Small, + dkg_input_type, + sd.z, + sd.lambda, + )?; let circuit = ShareComputationCircuit; circuit.codegen( preset, &ShareComputationCircuitInput { dkg_input_type, - secret: sample.secret.as_ref().unwrap().clone(), + secret: sample.secret.clone(), secret_sss: sample.secret_sss.clone(), - parity_matrix: sample - .parity_matrix - .iter() - .map(|m| m.to_bigint_rows()) - .collect(), + parity_matrix: sample.parity_matrix.clone(), n_parties: sample.committee.n as u32, threshold: sample.committee.threshold as u32, }, diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index 1f4785e68e..25661b24d2 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, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] 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 edd3c1ba89..219fb0894b 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -84,7 +84,7 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::circuits::computation::Computation; use crate::codegen::write_artifacts; - use crate::sample::prepare_sample_for_test; + use crate::prepare_pk_sample_for_test; use crate::Bounds; use e3_fhe_params::DEFAULT_BFV_PRESET; use tempfile::TempDir; @@ -92,7 +92,7 @@ mod tests { #[test] fn test_toml_generation_and_structure() { let sample = - prepare_sample_for_test(DEFAULT_BFV_PRESET, CiphernodesCommitteeSize::Small, None) + prepare_pk_sample_for_test(DEFAULT_BFV_PRESET, CiphernodesCommitteeSize::Small) .unwrap(); let artifacts = PkCircuit diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index fe9f4581bd..56f6ea6c3b 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -206,7 +206,7 @@ mod tests { use super::*; use crate::ciphernodes_committee::CiphernodesCommitteeSize; - use crate::sample::prepare_sample_for_test; + use crate::prepare_pk_sample_for_test; use crate::ConvertToJson; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; @@ -223,10 +223,9 @@ mod tests { #[test] fn test_witness_reduction_and_json_roundtrip() { - let sample = prepare_sample_for_test( + let sample = prepare_pk_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - None, ) .unwrap(); let witness = Witness::compute( diff --git a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs index a3241a09e6..570fcd6093 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs @@ -4,16 +4,12 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! DKG public-key BFV commitment circuit. -//! -//! This circuit proves knowledge of a DKG BFV public key (pk0, pk1) and produces -//! Prover.toml and configs.nr for the Noir prover. See [`PkCircuit`] and -//! [`PkCircuitInput`]. - pub mod circuit; pub mod codegen; pub mod computation; +pub mod sample; pub use circuit::{PkCircuit, PkCircuitInput}; pub use codegen::{generate_configs, generate_toml, TomlJson}; 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 new file mode 100644 index 0000000000..4edfa5e4c7 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/pk/sample.rs @@ -0,0 +1,75 @@ +// 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 pk circuit: committee and DKG public key only. + +use crate::ciphernodes_committee::CiphernodesCommittee; +use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::CircuitsErrors; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use fhe::bfv::{BfvParameters, PublicKey, SecretKey}; +use rand::thread_rng; +use std::sync::Arc; + +/// 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 { + /// Generates sample data for the pk circuit. + pub fn generate( + _threshold_params: &Arc, + dkg_params: &Arc, + committee_size: CiphernodesCommitteeSize, + ) -> Result { + 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); + Ok(Self { + committee, + 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( + threshold_preset: BfvPreset, + committee: CiphernodesCommitteeSize, +) -> Result { + let (threshold_params, dkg_params) = build_pair_for_preset(threshold_preset) + .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + PkSample::generate(&threshold_params, &dkg_params, committee) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + 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, + ) + .unwrap(); + + 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); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs index c33618fa50..fcde32480d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -7,6 +7,7 @@ use crate::computation::DkgInputType; use crate::registry::Circuit; use e3_fhe_params::ParameterType; +use e3_parity_matrix::ParityMatrix; use e3_polynomial::CrtPolynomial; use ndarray::Array2; use num_bigint::BigInt; @@ -27,7 +28,7 @@ pub struct ShareComputationCircuitInput { pub dkg_input_type: DkgInputType, pub secret: CrtPolynomial, pub secret_sss: Vec>, - pub parity_matrix: Vec>>, + pub parity_matrix: Vec, pub n_parties: u32, pub threshold: u32, } 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 131352446f..9b72f74ab7 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -157,7 +157,7 @@ pub fn generate_configs( ) -> Result { let (threshold_params, _) = build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - let config_name = preset.security_config_name(); + let config_name = preset.metadata().security.as_config_str(); let parity_matrix_str = parity_matrix_constant_string(&threshold_params, n_parties, threshold)?; let prefix = ::PREFIX; let configs = format!( @@ -218,25 +218,21 @@ mod tests { use crate::circuits::dkg::share_computation::{Bits, Bounds}; use crate::codegen::write_artifacts; use crate::computation::DkgInputType; - use crate::sample::{prepare_sample_for_test, Sample}; + use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use crate::Circuit; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; use tempfile::TempDir; fn share_computation_input_from_sample( - sample: &Sample, + sample: &ShareComputationSample, dkg_input_type: DkgInputType, ) -> ShareComputationCircuitInput { ShareComputationCircuitInput { dkg_input_type, - secret: sample.secret.as_ref().unwrap().clone(), + secret: sample.secret.clone(), secret_sss: sample.secret_sss.clone(), - parity_matrix: sample - .parity_matrix - .iter() - .map(|m| m.to_bigint_rows()) - .collect(), + parity_matrix: sample.parity_matrix.clone(), n_parties: sample.committee.n as u32, threshold: sample.committee.threshold as u32, } @@ -244,10 +240,10 @@ mod tests { #[test] fn test_toml_generation_and_structure() { - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SecretKey), + DkgInputType::SecretKey, ) .unwrap(); let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index d0390f453c..d52c6fa9ff 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -286,24 +286,20 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; use crate::dkg::share_computation::ShareComputationCircuitInput; - use crate::sample::{prepare_sample_for_test, Sample}; + use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use crate::ConvertToJson; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; fn share_computation_input_from_sample( - sample: &Sample, + sample: &ShareComputationSample, dkg_input_type: DkgInputType, ) -> ShareComputationCircuitInput { ShareComputationCircuitInput { dkg_input_type, - secret: sample.secret.as_ref().unwrap().clone(), + secret: sample.secret.clone(), secret_sss: sample.secret_sss.clone(), - parity_matrix: sample - .parity_matrix - .iter() - .map(|m| m.to_bigint_rows()) - .collect(), + parity_matrix: sample.parity_matrix.clone(), n_parties: sample.committee.n as u32, threshold: sample.committee.threshold as u32, } @@ -311,10 +307,10 @@ mod tests { #[test] fn test_bound_and_bits_computation_consistency() { - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SecretKey), + DkgInputType::SecretKey, ) .unwrap(); let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); @@ -327,10 +323,10 @@ mod tests { #[test] fn test_witness_reduction_and_json_roundtrip() { - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SecretKey), + DkgInputType::SecretKey, ) .unwrap(); let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); @@ -350,10 +346,10 @@ mod tests { #[test] fn test_witness_smudging_noise_secret_consistency() { - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SmudgingNoise), + DkgInputType::SmudgingNoise, ) .unwrap(); let input = share_computation_input_from_sample(&sample, DkgInputType::SmudgingNoise); @@ -375,10 +371,10 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SecretKey), + DkgInputType::SecretKey, ) .unwrap(); let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); 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 67a6f4a4b0..eaee5a4f5c 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -4,11 +4,11 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! DKG share-computation circuit (SK or smudging noise). - pub mod circuit; pub mod codegen; pub mod computation; +pub mod sample; pub use circuit::{ShareComputationCircuit, ShareComputationCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareComputationOutput, Witness}; +pub use sample::{prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample}; diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs similarity index 75% rename from crates/zk-helpers/src/circuits/sample.rs rename to crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs index 37263ee618..43cad33cd1 100644 --- a/crates/zk-helpers/src/circuits/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -4,13 +4,8 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Sample data generation for circuits. -//! -//! [`Sample`] produces a random BFV key pair; the public key is used as input -//! for codegen and tests (e.g. pk circuit). For share-computation, it can -//! also produce secret-key or smudging-noise data: secret in CRT form, Shamir -//! shares, and parity matrices. Smudging noise is generated with the computed -//! smudging bound (error_size) so coefficients are non-zero. +//! 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; @@ -31,36 +26,36 @@ use std::sync::Arc; /// Shamir secret shares: one limb per CRT modulus (rows = parties, cols = polynomial coefficients). pub type SecretShares = Vec>; -/// A sample BFV public key (and optionally related data) for circuit codegen or tests. +/// 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 Sample { +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, depending on [`DkgInputType`]). - pub secret: Option, - /// Secret shares (one [`ndarray::Array2`] per modulus; empty when [`DkgInputType`] is `None`). + /// 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), one [`ParityMatrix`] per CRT modulus. + /// Parity check matrix per modulus (null space of generator). pub parity_matrix: Vec, } -impl Sample { - /// Generates a random secret key and public key for the given BFV parameters. +impl ShareComputationSample { + /// Generates sample data for the share-computation circuit. pub fn generate( threshold_params: &Arc, dkg_params: &Arc, - dkg_input_type: Option, committee_size: CiphernodesCommitteeSize, + dkg_input_type: DkgInputType, num_ciphertexts: u128, // z in the search defaults lambda: u32, ) -> Result { let mut rng = thread_rng(); - let committee = committee_size.values(); - let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); + 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()) @@ -87,8 +82,8 @@ impl Sample { } let (secret, secret_sss) = match dkg_input_type { - Some(DkgInputType::SecretKey) => { - let threshold_secret_key = SecretKey::random(&threshold_params, &mut rng); + 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()) @@ -115,12 +110,12 @@ impl Sample { .map(|&c| BigInt::from(c)) .collect(); let mut secret_crt = - CrtPolynomial::from_bigint_coeffs(&sk_coeffs, threshold_params.moduli()); + CrtPolynomial::from_mod_q_polynomial(&sk_coeffs, threshold_params.moduli()); secret_crt.center(threshold_params.moduli())?; - (Some(secret_crt), secret_sss) + (secret_crt, secret_sss) } - Some(DkgInputType::SmudgingNoise) => { + DkgInputType::SmudgingNoise => { let esi_coeffs = trbfv .generate_smudging_error(num_ciphertexts as usize, lambda as usize, &mut rng) .map_err(|e| { @@ -143,12 +138,11 @@ impl Sample { .collect(); let mut secret_crt = - CrtPolynomial::from_bigint_coeffs(&esi_coeffs, threshold_params.moduli()); + CrtPolynomial::from_mod_q_polynomial(&esi_coeffs, threshold_params.moduli()); secret_crt.center(threshold_params.moduli())?; - (Some(secret_crt), secret_sss) + (secret_crt, secret_sss) } - None => (None, Vec::new()), }; Ok(Self { @@ -161,46 +155,42 @@ impl Sample { } } -/// Prepares a sample for testing using a threshold preset (DKG params come from its pair). -pub fn prepare_sample_for_test( +/// Prepares a share-computation sample for testing using a threshold preset. +pub fn prepare_share_computation_sample_for_test( threshold_preset: BfvPreset, committee: CiphernodesCommitteeSize, - dkg_input_type: Option, -) -> Result { + dkg_input_type: DkgInputType, +) -> Result { let (threshold_params, dkg_params) = build_pair_for_preset(threshold_preset) .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let defaults = threshold_preset .search_defaults() .ok_or_else(|| CircuitsErrors::Sample("preset has no search defaults".to_string()))?; - let num_ciphertexts = defaults.z; - let lambda = defaults.lambda; - let sample = Sample::generate( + ShareComputationSample::generate( &threshold_params, &dkg_params, - dkg_input_type, committee, - num_ciphertexts, - lambda, + dkg_input_type, + defaults.z, + defaults.lambda, ) - .map_err(|e| CircuitsErrors::Sample(e.to_string()))?; - Ok(sample) + .map_err(|e| CircuitsErrors::Sample(e.to_string())) } #[cfg(test)] mod tests { + use super::*; use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; use e3_fhe_params::BfvPreset; - use super::*; - #[test] fn test_generate_secret_key_sample() { let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SecretKey), + DkgInputType::SecretKey, ) .unwrap(); @@ -209,17 +199,16 @@ mod tests { assert_eq!(sample.committee.h, committee.h); assert_eq!(sample.dkg_public_key.c.c.len(), 2); assert_eq!(sample.secret_sss.len(), 2); - let secret = sample.secret.as_ref().unwrap(); - assert_eq!(secret.limbs.len(), 2); + assert_eq!(sample.secret.limbs.len(), 2); } #[test] fn test_generate_smudging_noise_sample() { let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_sample_for_test( + let sample = prepare_share_computation_sample_for_test( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - Some(DkgInputType::SmudgingNoise), + DkgInputType::SmudgingNoise, ) .unwrap(); @@ -227,7 +216,6 @@ mod tests { assert_eq!(sample.committee.threshold, committee.threshold); assert_eq!(sample.dkg_public_key.c.c.len(), 2); assert_eq!(sample.secret_sss.len(), 2); - let secret = sample.secret.as_ref().unwrap(); - assert_eq!(secret.limbs.len(), 2); + assert_eq!(sample.secret.limbs.len(), 2); } } diff --git a/crates/zk-helpers/src/circuits/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index 744d44dcee..62caa07809 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -4,19 +4,10 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -//! Zero-knowledge circuit types and code generation. -//! -//! This module provides circuit metadata ([`Circuit`](crate::registry::Circuit)), artifact -//! codegen ([`CircuitCodegen`], [`Artifacts`]), commitment helpers ([`commitments`]), -//! and sample data generation ([`Sample`]). The [`dkg::pk`](dkg::pk) submodule implements the -//! public-key BFV commitment circuit and is re-exported here: [`generate_configs`], [`generate_toml`], -//! [`TomlJson`], [`Bits`], [`Bounds`], [`PkComputationOutput`], [`Witness`], and [`PkCircuit`]. - pub mod codegen; pub mod commitments; pub mod computation; pub mod errors; -pub mod sample; pub use codegen::{write_artifacts, Artifacts, CircuitCodegen}; pub use commitments::*; @@ -24,9 +15,11 @@ pub use computation::{ CircuitComputation, Computation, Configs, ConvertToJson, ReduceToZkpModulus, Toml, }; pub use errors::CircuitsErrors; -pub use sample::Sample; pub mod dkg; pub use dkg::pk::codegen::{generate_configs, generate_toml, TomlJson}; pub use dkg::pk::computation::{Bits, Bounds, PkComputationOutput, Witness}; -pub use dkg::pk::PkCircuit; +pub use dkg::pk::{prepare_pk_sample_for_test, PkCircuit, PkSample}; +pub use dkg::share_computation::{ + prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample, +}; From 239c1c4849ce468b99d6b11b09537a3571a862e6 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 4 Feb 2026 11:49:37 +0100 Subject: [PATCH 13/16] format --- crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs | 2 +- .../src/circuits/dkg/share_computation/computation.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index 9b72f74ab7..e970000672 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -218,8 +218,8 @@ mod tests { use crate::circuits::dkg::share_computation::{Bits, Bounds}; use crate::codegen::write_artifacts; use crate::computation::DkgInputType; - use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use crate::Circuit; + use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; use tempfile::TempDir; 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 d52c6fa9ff..9d52523869 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -286,8 +286,8 @@ 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 crate::ConvertToJson; + use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use e3_fhe_params::BfvPreset; use e3_fhe_params::DEFAULT_BFV_PRESET; From 5f5fb8204ea2205556ca79170eacddc8c4a4c8b5 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 4 Feb 2026 12:02:44 +0100 Subject: [PATCH 14/16] update params --- crates/fhe-params/src/constants.rs | 2 +- crates/zk-helpers/src/bin/zk_cli.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/fhe-params/src/constants.rs b/crates/fhe-params/src/constants.rs index ee35c7641c..d072259df0 100644 --- a/crates/fhe-params/src/constants.rs +++ b/crates/fhe-params/src/constants.rs @@ -16,7 +16,7 @@ pub mod insecure_512 { /// Threshold BFV parameters pub mod threshold { - pub const PLAINTEXT_MODULUS: u64 = 10; + pub const PLAINTEXT_MODULUS: u64 = 100; pub const MODULI: &[u64] = &[0xffffee001, 0xffffc4001]; pub const ERROR1_VARIANCE: &str = "3"; pub const ERROR1_VARIANCE_BIGUINT: u32 = 3; diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index f5f963ce2e..ede0f515f6 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -131,7 +131,7 @@ struct Cli { /// Preset: "insecure"|"secure" or λ (2|80). Drives both threshold and DKG params. #[arg(long, required_unless_present = "list_circuits")] preset: Option, - /// For share-computation: witness type "secret-key" or "smudging-noise". Required when circuit is share-computation. + /// For share-computation only: witness type "secret-key" or "smudging-noise". Required when writing Prover.toml for share-computation. Ignored for pk (always secret key). #[arg(long)] witness: Option, /// Output directory for generated artifacts. @@ -200,17 +200,14 @@ fn main() -> Result<()> { } let write_prover_toml = args.toml; - // Circuits that accept runtime witness type (e.g. share-computation with SecretKey or SmudgingNoise) have DKG_INPUT_TYPE == None but still need witness handling. - let has_witness_type = circuit_meta.dkg_input_type().is_some() - || circuit_meta.name() == ShareComputationCircuit::NAME; + // 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; - // For share-computation: require --witness only when generating Prover.toml (configs are shared). let dkg_input_type = if has_witness_type { + // Share-computation: require --witness when generating Prover.toml; default secret-key for configs-only. let witness_str = if !args.toml { - // Configs-only: witness optional (configs.nr is the same for both witness types). args.witness.as_deref().unwrap_or("secret-key") } else { - // Generating Prover.toml: witness type is required. args.witness.as_deref().ok_or_else(|| { anyhow!( "circuit {} requires --witness (secret-key or smudging-noise) when writing Prover.toml", @@ -224,6 +221,7 @@ fn main() -> Result<()> { DkgInputTypeArg::SmudgingNoise => DkgInputType::SmudgingNoise, } } else { + // pk circuit: always secret key (no smudging noise). DkgInputType::SecretKey }; From be767ac8f4bc4534a096e372acaa2d00ee7fb071 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 4 Feb 2026 12:11:07 +0100 Subject: [PATCH 15/16] utilities and simplify json conversions --- crates/zk-helpers/src/circuits/computation.rs | 8 ++-- .../src/circuits/dkg/pk/computation.rs | 19 ---------- .../circuits/dkg/share_computation/codegen.rs | 23 ++--------- .../dkg/share_computation/computation.rs | 19 ---------- crates/zk-helpers/src/circuits/mod.rs | 4 +- crates/zk-helpers/src/utils.rs | 38 ++++++++++++++----- packages/enclave-sdk/tests/sdk.test.ts | 2 +- 7 files changed, 39 insertions(+), 74 deletions(-) diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index 25661b24d2..2178a83b8d 100644 --- a/crates/zk-helpers/src/circuits/computation.rs +++ b/crates/zk-helpers/src/circuits/computation.rs @@ -56,7 +56,9 @@ pub trait ConvertToJson { fn convert_to_json(&self) -> serde_json::Result; } -/// Reduces coefficients (or similar) to the ZKP field modulus for use in the prover. -pub trait ReduceToZkpModulus: Sized { - fn reduce_to_zkp_modulus(&self) -> Self; +/// Any `Serialize` type can be converted to JSON for round-trip tests and artifact generation. +impl ConvertToJson for T { + fn convert_to_json(&self) -> serde_json::Result { + serde_json::to_value(self) + } } diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 56f6ea6c3b..ed371db3cc 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -13,7 +13,6 @@ use crate::calculate_bit_width; use crate::dkg::pk::PkCircuitInput; use crate::get_zkp_modulus; use crate::CircuitsErrors; -use crate::ConvertToJson; use crate::PkCircuit; use crate::{CircuitComputation, Computation}; use e3_fhe_params::build_pair_for_preset; @@ -183,24 +182,6 @@ impl Computation for Witness { } } -impl ConvertToJson for Configs { - fn convert_to_json(&self) -> serde_json::Result { - serde_json::to_value(self) - } -} - -impl ConvertToJson for Bounds { - fn convert_to_json(&self) -> serde_json::Result { - serde_json::to_value(self) - } -} - -impl ConvertToJson for Witness { - fn convert_to_json(&self) -> serde_json::Result { - serde_json::to_value(self) - } -} - #[cfg(test)] mod tests { use super::*; 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 e970000672..2a3d8bd4d9 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -6,6 +6,7 @@ //! Code generation for the share-computation BFV circuit: Prover.toml and configs.nr. +use crate::bigint_3d_to_json_values; use crate::bigint_to_field; use crate::circuits::computation::CircuitComputation; use crate::circuits::dkg::share_computation::{ @@ -15,8 +16,8 @@ use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, Toml}; use crate::computation::Configs; use crate::computation::DkgInputType; use crate::crt_polynomial_to_toml_json; +use crate::poly_coefficients_to_toml_json; use crate::registry::Circuit; -use crate::to_string_1d_vec; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; use e3_parity_matrix::{build_generator_matrix, null_space, ParityMatrixConfig}; @@ -67,30 +68,14 @@ pub fn generate_toml( ) -> Result { let secret = match dkg_input_type { DkgInputType::SecretKey => serde_json::json!({ - "sk_secret": { - "coefficients": to_string_1d_vec(witness.secret_crt.limb(0).coefficients()) - } + "sk_secret": poly_coefficients_to_toml_json(witness.secret_crt.limb(0).coefficients()) }), DkgInputType::SmudgingNoise => serde_json::json!({ "e_sm_secret": crt_polynomial_to_toml_json(&witness.secret_crt) }), }; - let y: Vec>> = witness - .y - .iter() - .map(|coeff| { - coeff - .iter() - .map(|modulus| { - modulus - .iter() - .map(|share| serde_json::Value::String(share.to_string())) - .collect() - }) - .collect() - }) - .collect(); + let y = bigint_3d_to_json_values(&witness.y); let toml_json = TomlJson { expected_secret_commitment: witness.expected_secret_commitment.to_string(), 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 9d52523869..c0e2751534 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -19,7 +19,6 @@ use crate::dkg::share_computation::ShareComputationCircuit; use crate::dkg::share_computation::ShareComputationCircuitInput; use crate::get_zkp_modulus; use crate::CircuitsErrors; -use crate::ConvertToJson; use crate::{CircuitComputation, Computation}; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; @@ -261,24 +260,6 @@ impl Computation for Witness { } } -impl ConvertToJson for Configs { - fn convert_to_json(&self) -> serde_json::Result { - serde_json::to_value(self) - } -} - -impl ConvertToJson for Bounds { - fn convert_to_json(&self) -> serde_json::Result { - serde_json::to_value(self) - } -} - -impl ConvertToJson for Witness { - fn convert_to_json(&self) -> serde_json::Result { - serde_json::to_value(self) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/zk-helpers/src/circuits/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index 62caa07809..4ebd750f62 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -11,9 +11,7 @@ pub mod errors; pub use codegen::{write_artifacts, Artifacts, CircuitCodegen}; pub use commitments::*; -pub use computation::{ - CircuitComputation, Computation, Configs, ConvertToJson, ReduceToZkpModulus, Toml, -}; +pub use computation::{CircuitComputation, Computation, Configs, ConvertToJson, Toml}; pub use errors::CircuitsErrors; pub mod dkg; diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index 34661eac9d..73c1865119 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -151,21 +151,39 @@ pub fn get_zkp_modulus() -> BigInt { .expect("Invalid ZKP modulus") } -/// Map a CRT polynomial to a vector of JSON values. +/// Poly-with-coefficients shape for TOML JSON: `{"coefficients": [string, ...]}`. /// -/// # Arguments -/// * `crt_polynomial` - CRT polynomial to convert to TOML JSON -/// -/// # Returns -/// A vector of JSON values +/// Use for a single limb (e.g. sk_secret) where the circuit expects one "coefficients" array. +pub fn poly_coefficients_to_toml_json(coefficients: &[BigInt]) -> serde_json::Value { + serde_json::json!({ + "coefficients": to_string_1d_vec(coefficients) + }) +} + +/// Map a CRT polynomial to a vector of JSON values (one `{"coefficients": [...]}` per limb). pub fn crt_polynomial_to_toml_json(crt_polynomial: &CrtPolynomial) -> Vec { crt_polynomial .limbs .iter() - .map(|limb| { - serde_json::json!({ - "coefficients": to_string_1d_vec(limb.coefficients()) - }) + .map(|limb| poly_coefficients_to_toml_json(limb.coefficients())) + .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. +pub fn bigint_3d_to_json_values(y: &[Vec>]) -> Vec>> { + y.iter() + .map(|coeff| { + coeff + .iter() + .map(|modulus| { + modulus + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect() + }) + .collect() }) .collect() } diff --git a/packages/enclave-sdk/tests/sdk.test.ts b/packages/enclave-sdk/tests/sdk.test.ts index 000c37ea1b..8314b0b3bd 100644 --- a/packages/enclave-sdk/tests/sdk.test.ts +++ b/packages/enclave-sdk/tests/sdk.test.ts @@ -45,7 +45,7 @@ describe('encryptNumber', () => { expect(value.proof).to.be.an.instanceOf(Object) }, 9999999) - it('should encrypt a vecor of numbers without crashing in a node environent', async () => { + it('should encrypt a vector of numbers without crashing in a node environent', async () => { const buffer = await fs.readFile(path.resolve(__dirname, './fixtures/pubkey.bin')) const value = await sdk.encryptVector(new BigUint64Array([1n, 2n]), Uint8Array.from(buffer)) expect(value).to.be.an.instanceof(Uint8Array) From cc088389da98bd71c6f72f0e3e1c226d41326b20 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 4 Feb 2026 12:47:37 +0100 Subject: [PATCH 16/16] get back to previous params --- crates/fhe-params/src/constants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fhe-params/src/constants.rs b/crates/fhe-params/src/constants.rs index d072259df0..ee35c7641c 100644 --- a/crates/fhe-params/src/constants.rs +++ b/crates/fhe-params/src/constants.rs @@ -16,7 +16,7 @@ pub mod insecure_512 { /// Threshold BFV parameters pub mod threshold { - pub const PLAINTEXT_MODULUS: u64 = 100; + pub const PLAINTEXT_MODULUS: u64 = 10; pub const MODULI: &[u64] = &[0xffffee001, 0xffffc4001]; pub const ERROR1_VARIANCE: &str = "3"; pub const ERROR1_VARIANCE_BIGUINT: u32 = 3;