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/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/fhe-params/src/lib.rs b/crates/fhe-params/src/lib.rs index 8ec73e8a9f..dd12901d84 100644 --- a/crates/fhe-params/src/lib.rs +++ b/crates/fhe-params/src/lib.rs @@ -15,11 +15,11 @@ 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}; 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 e5af7d655f..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 @@ -94,6 +125,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. /// @@ -106,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 @@ -230,6 +267,24 @@ impl BfvPreset { } } + /// 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 { + 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> { Self::ALL.iter().map(BfvPreset::name).collect() } @@ -260,30 +315,38 @@ 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, + security: SecurityTier::INSECURE, }, 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, + security: SecurityTier::INSECURE, }, 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, + security: SecurityTier::SECURE, }, 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, + security: SecurityTier::SECURE, }, } } 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..69a5914e96 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,23 @@ impl CrtPolynomial { Self { limbs } } + /// Builds a CRT polynomial from a polynomial mod Q (Q>>128) and moduli. + /// + /// # Arguments + /// + /// * `coeffs` - Polynomial coefficients mod Q (Q>>128). + /// * `moduli` - One modulus per limb. + pub fn from_mod_q_polynomial(poly: &Vec, moduli: &[u64]) -> Self { + let limbs: Vec> = moduli + .iter() + .map(|&qi| { + let qi_big = BigInt::from(qi); + poly.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/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/README.md b/crates/zk-helpers/README.md index b34559a6ac..85f452cadf 100644 --- a/crates/zk-helpers/README.md +++ b/crates/zk-helpers/README.md @@ -1,26 +1,31 @@ # 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 Enclave circuits. ## 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 (default: output/) -cargo run -p e3-zk-helpers --bin zk_cli -- --circuit pk-bfv --preset default +# Generate configs.nr only (default) +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 -# 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 configs.nr and Prover.toml (--witness required for share-computation) +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 (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` 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 b22e8473a9..ede0f515f6 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -8,17 +8,115 @@ //! //! 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|2|80` to generate artifacts. 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_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::{ + 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 e3_zk_helpers::{PkSample, ShareComputationSample}; +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)] +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\"" + )), + } +} + +/// 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, + write_prover_toml: bool, +) { + let meta = preset.metadata(); + println!(" Circuit: {}", circuit); + println!( + " Preset: {} (degree {}, {} moduli)", + meta.security.as_config_str(), + 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 write_prover_toml { + println!(" • configs.nr"); + println!(" • Prover.toml"); + } else { + println!(" • configs.nr only (pass --toml to also generate 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)] @@ -27,35 +125,30 @@ 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, share-computation). #[arg(long, required_unless_present = "list_circuits")] circuit: Option, - /// BFV preset name (must match circuit's parameter type). + /// Preset: "insecure"|"secure" or λ (2|80). Drives both threshold and DKG params. #[arg(long, required_unless_present = "list_circuits")] preset: Option, + /// 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. #[arg(long, default_value = "output")] output: PathBuf, - /// Skip generating Prover.toml (configs.nr is always generated). - #[arg(long)] + /// Also write Prover.toml (default: configs.nr only). + #[arg(long, default_value = "false")] 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}") - }) -} - fn main() -> Result<()> { let args = Cli::parse(); // Register all circuits in the registry (metadata only). let mut registry = CircuitRegistry::new(); - registry.register(Arc::new(PkBfvCircuit)); + registry.register(Arc::new(PkCircuit)); + registry.register(Arc::new(ShareComputationCircuit)); // Handle list circuits flag. if args.list_circuits { @@ -75,7 +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 = parse_preset(&args.preset.unwrap())?; + 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()))?; @@ -86,42 +179,116 @@ 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 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 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 or secure)", circuit, circuit_param_type )); } - // Generate artifacts based on circuit name from registry. - let params = BfvParamSet::from(preset).build_arc(); - let sample = Sample::generate(¶ms); - let circuit_name = circuit_meta.name(); - let artifacts = match circuit_name { - name if name == ::NAME => { - let circuit = PkBfvCircuit; - circuit.codegen( - ¶ms, - &PkBfvCircuitInput { - public_key: sample.public_key, - }, - )? - } - name => return Err(anyhow!("circuit {} not yet implemented", name)), - }; + let write_prover_toml = args.toml; + // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. + let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME; - let toml = if !args.toml { - None + 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 { + args.witness.as_deref().unwrap_or("secret-key") + } else { + args.witness.as_deref().ok_or_else(|| { + anyhow!( + "circuit {} requires --witness (secret-key or smudging-noise) when writing Prover.toml", + circuit + ) + })? + }; + let arg = parse_input_type(witness_str)?; + match arg { + DkgInputTypeArg::SecretKey => DkgInputType::SecretKey, + DkgInputTypeArg::SmudgingNoise => DkgInputType::SmudgingNoise, + } } else { - Some(&artifacts.toml) + // pk circuit: always secret key (no smudging noise). + DkgInputType::SecretKey }; - write_artifacts(toml, &artifacts.configs, Some(args.output.as_path()))?; - println!("Artifacts written to {}", args.output.display()); + clear_terminal(); + print_generation_info( + &circuit, + preset, + has_witness_type, + dkg_input_type.clone(), + &args.output, + write_prover_toml, + ); + + run_with_spinner(|| { + 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, + &PkCircuitInput { + public_key: sample.dkg_public_key, + }, + )? + } + 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.clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample.parity_matrix.clone(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + }, + )? + } + name => return Err(anyhow!("circuit {} not yet implemented", name)), + }; + + let toml = if write_prover_toml { + Some(&artifacts.toml) + } else { + None + }; + write_artifacts(toml, &artifacts.configs, Some(args.output.as_path()))?; + Ok(()) + })?; + + print_success(&args.output); Ok(()) } 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/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..2178a83b8d 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, Copy, PartialEq, Eq)] pub enum DkgInputType { /// The input type that generates shares of a secret key using secret sharing. SecretKey, @@ -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. @@ -50,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/mod.rs b/crates/zk-helpers/src/circuits/dkg/mod.rs new file mode 100644 index 0000000000..905b80d19e --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/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 pk; +pub mod share_computation; 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 62% rename from crates/zk-helpers/src/circuits/pk_bfv/codegen.rs rename to crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 1b17b0e335..219fb0894b 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -6,32 +6,31 @@ //! 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; +use e3_fhe_params::BfvPreset; use serde::{Deserialize, Serialize}; use serde_json; -use std::sync::Arc; -/// Implementation of [`CircuitCodegen`] for [`PkBfvCircuit`]. -impl CircuitCodegen for PkBfvCircuit { - type Params = Arc; - type Input = PkBfvCircuitInput; +/// Implementation of [`CircuitCodegen`] for [`PkCircuit`]. +impl CircuitCodegen for PkCircuit { + type BfvThresholdParametersPreset = BfvPreset; + type Input = PkCircuitInput; type Error = CircuitsErrors; fn codegen( &self, - params: &Self::Params, + preset: Self::BfvThresholdParametersPreset, input: &Self::Input, ) -> Result { - let PkBfvComputationOutput { witness, bits, .. } = PkBfvCircuit::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 }) } @@ -46,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); @@ -57,24 +56,24 @@ 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#"// 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, + preset.metadata().degree, + preset.metadata().num_moduli, + ::PREFIX, bits.pk_bit, ) } @@ -82,24 +81,25 @@ 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::prepare_pk_sample_for_test; use crate::Bounds; - - use e3_fhe_params::BfvParamSet; use e3_fhe_params::DEFAULT_BFV_PRESET; 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 artifacts = PkBfvCircuit + let sample = + prepare_pk_sample_for_test(DEFAULT_BFV_PRESET, CiphernodesCommitteeSize::Small) + .unwrap(); + + let artifacts = PkCircuit .codegen( - ¶ms, - &PkBfvCircuitInput { - public_key: sample.public_key, + DEFAULT_BFV_PRESET, + &PkCircuitInput { + public_key: sample.dkg_public_key, }, ) .unwrap(); @@ -138,11 +138,20 @@ 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(); - - 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())); + let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &()).unwrap(); + let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + + assert!(configs_content + .contains(format!("N: u32 = {}", DEFAULT_BFV_PRESET.metadata().degree).as_str())); + assert!(configs_content + .contains(format!("L: u32 = {}", DEFAULT_BFV_PRESET.metadata().num_moduli).as_str())); + assert!(configs_content.contains( + format!( + "{}_BIT_PK: u32 = {}", + ::PREFIX, + bits.pk_bit + ) + .as_str() + )); } } diff --git a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs similarity index 56% rename from crates/zk-helpers/src/circuits/pk_bfv/computation.rs rename to crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 71ac9d1604..ed371db3cc 100644 --- a/crates/zk-helpers/src/circuits/pk_bfv/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -4,44 +4,47 @@ // 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. 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_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}; -/// 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 { - type Params = BfvParameters; - type Input = PkBfvCircuitInput; - type Output = PkBfvComputationOutput; +/// Implementation of [`CircuitComputation`] for [`PkCircuit`]. +impl CircuitComputation for PkCircuit { + 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(PkBfvComputationOutput { + Ok(PkComputationOutput { bounds, bits, witness, @@ -71,7 +74,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. @@ -80,17 +83,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 +109,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 +124,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 +152,17 @@ impl Computation for Bounds { } impl Computation for Witness { - type Params = BfvParameters; - type Input = PkBfvCircuitInput; + 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]); @@ -159,52 +182,37 @@ 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::*; + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::prepare_pk_sample_for_test; use crate::ConvertToJson; - use crate::Sample; - use e3_fhe_params::BfvParamSet; + 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 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(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 sample = prepare_pk_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + ) + .unwrap(); let witness = Witness::compute( - ¶ms, - &PkBfvCircuitInput { - public_key: encryption_data.public_key, + DEFAULT_BFV_PRESET, + &PkCircuitInput { + public_key: sample.dkg_public_key, }, ) .unwrap(); @@ -217,8 +225,7 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let params = BfvParamSet::from(DEFAULT_BFV_PRESET).build_arc(); - let constants = Configs::compute(¶ms, &()).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/pk/mod.rs b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs new file mode 100644 index 0000000000..570fcd6093 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +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 new file mode 100644 index 0000000000..fcde32480d --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/circuit.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use e3_fhe_params::ParameterType; +use e3_parity_matrix::ParityMatrix; +use e3_polynomial::CrtPolynomial; +use ndarray::Array2; +use num_bigint::BigInt; + +#[derive(Debug)] +pub struct ShareComputationCircuit; + +impl Circuit for ShareComputationCircuit { + const NAME: &'static str = "share-computation"; + const PREFIX: &'static str = "SHARE_COMPUTATION"; + 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; +} + +pub struct ShareComputationCircuitInput { + /// 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, + 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 new file mode 100644 index 0000000000..2a3d8bd4d9 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Code generation for the share-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::{ + 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::poly_coefficients_to_toml_json; +use crate::registry::Circuit; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_parity_matrix::{build_generator_matrix, null_space, ParityMatrixConfig}; +use num_bigint::BigInt; +use num_bigint::BigUint; +use serde::{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, + input.n_parties as usize, + input.threshold as usize, + )?; + + 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": 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 = bigint_3d_to_json_values(&witness.y); + + 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. +/// +/// `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.metadata().security.as_config_str(); + let parity_matrix_str = parity_matrix_constant_string(&threshold_params, n_parties, 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::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; + + fn share_computation_input_from_sample( + sample: &ShareComputationSample, + dkg_input_type: DkgInputType, + ) -> ShareComputationCircuitInput { + ShareComputationCircuitInput { + dkg_input_type, + secret: sample.secret.clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample.parity_matrix.clone(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + } + } + + #[test] + fn test_toml_generation_and_structure() { + let sample = prepare_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + 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 = ::PREFIX; + + 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..c0e2751534 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Computation types for the share-computation circuit: constants, bounds, bit widths, and witness. +//! +//! [`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::{ + 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::{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), 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). 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. Values in [0, zkp_modulus). + 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 (threshold_params, _) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + + 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: threshold_params.degree(), + l, + 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 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, + 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; + + 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]]; + y_mod.push(reduce(share_value, &q_j)); + } + 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, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use crate::dkg::share_computation::ShareComputationCircuitInput; + use crate::ConvertToJson; + use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; + use e3_fhe_params::BfvPreset; + use e3_fhe_params::DEFAULT_BFV_PRESET; + + fn share_computation_input_from_sample( + sample: &ShareComputationSample, + dkg_input_type: DkgInputType, + ) -> ShareComputationCircuitInput { + ShareComputationCircuitInput { + dkg_input_type, + secret: sample.secret.clone(), + secret_sss: sample.secret_sss.clone(), + parity_matrix: sample.parity_matrix.clone(), + n_parties: sample.committee.n as u32, + threshold: sample.committee.threshold as u32, + } + } + + #[test] + fn test_bound_and_bits_computation_consistency() { + let sample = prepare_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + 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_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + 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_witness_smudging_noise_secret_consistency() { + let sample = prepare_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + 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_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + 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 new file mode 100644 index 0000000000..eaee5a4f5c --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/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. + +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/dkg/share_computation/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs new file mode 100644 index 0000000000..43cad33cd1 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Sample data generation for the share-computation circuit: committee, DKG public key, +//! secret (SK or smudging noise) in CRT form, Shamir shares, and parity matrices. + +use crate::ciphernodes_committee::CiphernodesCommittee; +use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::computation::DkgInputType; +use crate::CircuitsErrors; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_parity_matrix::build_generator_matrix; +use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; +use e3_polynomial::CrtPolynomial; +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; + +/// Shamir secret shares: one limb per CRT modulus (rows = parties, cols = polynomial coefficients). +pub type SecretShares = Vec>; + +/// Sample data for the **share-computation** circuit: committee, DKG public key, secret in CRT form, +/// Shamir shares, and parity matrices (secret-key or smudging-noise). +#[derive(Debug, Clone)] +pub struct ShareComputationSample { + /// Committee information. + pub committee: CiphernodesCommittee, + /// DKG BFV public key. + pub dkg_public_key: PublicKey, + /// Secret in CRT form (SK or smudging noise). + pub secret: CrtPolynomial, + /// Secret shares (one [`ndarray::Array2`] per modulus). + pub secret_sss: SecretShares, + /// Parity check matrix per modulus (null space of generator). + pub parity_matrix: Vec, +} + +impl ShareComputationSample { + /// Generates sample data for the share-computation circuit. + pub fn generate( + threshold_params: &Arc, + dkg_params: &Arc, + 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_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()); + + // 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 { + 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 + )) + })?; + + 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 sk_coeffs: Vec = threshold_secret_key + .coeffs + .iter() + .map(|&c| BigInt::from(c)) + .collect(); + let mut secret_crt = + CrtPolynomial::from_mod_q_polynomial(&sk_coeffs, threshold_params.moduli()); + secret_crt.center(threshold_params.moduli())?; + + (secret_crt, secret_sss) + } + 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_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 mut secret_crt = + CrtPolynomial::from_mod_q_polynomial(&esi_coeffs, threshold_params.moduli()); + secret_crt.center(threshold_params.moduli())?; + + (secret_crt, secret_sss) + } + }; + + Ok(Self { + committee, + dkg_public_key, + secret, + secret_sss, + parity_matrix, + }) + } +} + +/// 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: 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()))?; + ShareComputationSample::generate( + &threshold_params, + &dkg_params, + committee, + dkg_input_type, + defaults.z, + defaults.lambda, + ) + .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; + + #[test] + fn test_generate_secret_key_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = prepare_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + 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); + assert_eq!(sample.secret.limbs.len(), 2); + } + + #[test] + fn test_generate_smudging_noise_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = prepare_share_computation_sample_for_test( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SmudgingNoise, + ) + .unwrap(); + + 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); + assert_eq!(sample.secret.limbs.len(), 2); + } +} 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/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index 1c7e123a1b..4ebd750f62 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -4,28 +4,20 @@ // 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 `pk_bfv` submodule implements the -//! public-key BFV commitment circuit. - 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::*; -pub use computation::{ - CircuitComputation, Computation, Configs, ConvertToJson, ReduceToZkpModulus, Toml, -}; +pub use computation::{CircuitComputation, Computation, Configs, ConvertToJson, Toml}; 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::{prepare_pk_sample_for_test, PkCircuit, PkSample}; +pub use dkg::share_computation::{ + prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample, +}; diff --git a/crates/zk-helpers/src/circuits/pk_bfv/mod.rs b/crates/zk-helpers/src/circuits/pk_bfv/mod.rs deleted file mode 100644 index adfe084f06..0000000000 --- a/crates/zk-helpers/src/circuits/pk_bfv/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -//! 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`]. - -pub mod circuit; -pub mod codegen; -pub mod computation; -pub use circuit::*; -pub use codegen::*; -pub use computation::*; diff --git a/crates/zk-helpers/src/circuits/sample.rs b/crates/zk-helpers/src/circuits/sample.rs deleted file mode 100644 index be3bfc4ff1..0000000000 --- a/crates/zk-helpers/src/circuits/sample.rs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-only -// -// This file is provided WITHOUT ANY WARRANTY; -// without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. - -//! 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). - -use fhe::bfv::{BfvParameters, PublicKey, SecretKey}; -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, -} - -impl Sample { - /// Generates a random secret key and public key for the given BFV parameters. - pub fn generate(params: &Arc) -> Self { - let mut rng = thread_rng(); - - let secret_key = SecretKey::random(¶ms, &mut rng); - let public_key = PublicKey::new(&secret_key, &mut rng); - - Self { public_key } - } -} - -#[cfg(test)] -mod tests { - 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); - - assert_eq!(sample.public_key.c.c.len(), 2); - } -} 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; 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/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/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) 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", 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;