diff --git a/Cargo.lock b/Cargo.lock index 95f47768f4..42386baacb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3312,6 +3312,38 @@ dependencies = [ "tokio", ] +[[package]] +name = "e3-pvss" +version = "0.1.7" +dependencies = [ + "anyhow", + "e3-fhe-params", + "e3-polynomial", + "e3-zk-helpers", + "fhe", + "fhe-math", + "itertools 0.14.0", + "num-bigint", + "num-traits", + "rand 0.8.5", + "rayon", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "toml", +] + +[[package]] +name = "e3-pvss-cli" +version = "0.1.7" +dependencies = [ + "anyhow", + "clap", + "e3-fhe-params", + "e3-pvss", +] + [[package]] name = "e3-request" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index d96e9db888..87908abada 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,9 @@ members = [ "crates/wasm", "crates/parity-matrix", "crates/polynomial", - "crates/zk-helpers" + "crates/zk-helpers", + "crates/pvss", + "crates/pvss-cli" ] exclude = [ "examples/CRISP", @@ -102,6 +104,7 @@ e3-trbfv = { version = "0.1.7", path = "./crates/trbfv" } e3-utils = { version = "0.1.7", path = "./crates/utils" } e3-safe = { version = "0.1.7", path = "./crates/safe" } e3-zk-helpers = { version = "0.1.7", path = "./crates/zk-helpers" } +e3-pvss = { version = "0.1.7", path = "./crates/pvss" } actix = "=0.13.5" actix-web = "=4.11.0" diff --git a/circuits/lib/src/configs/insecure/bfv.nr b/circuits/lib/src/configs/insecure/bfv.nr index 782e54b246..cb9681e600 100644 --- a/circuits/lib/src/configs/insecure/bfv.nr +++ b/circuits/lib/src/configs/insecure/bfv.nr @@ -24,7 +24,7 @@ pk_bfv (CIRCUIT 0 - PUBLIC KEY BFV) ************************************/ // pk_bfv - bit parameters -pub global PK_BFV_BIT_PK: u32 = 51; +pub global PK_BFV_BIT_PK: u32 = 50; /************************************ ------------------------------------- diff --git a/circuits/lib/src/configs/production/bfv.nr b/circuits/lib/src/configs/production/bfv.nr index 031444d633..5f541fc32f 100644 --- a/circuits/lib/src/configs/production/bfv.nr +++ b/circuits/lib/src/configs/production/bfv.nr @@ -27,7 +27,7 @@ pk_bfv (CIRCUIT 0 - PUBLIC KEY BFV) // pk_bfv - bit parameters -pub global PK_BFV_BIT_PK: u32 = 57; +pub global PK_BFV_BIT_PK: u32 = 58; /************************************ ------------------------------------- diff --git a/crates/Dockerfile b/crates/Dockerfile index f620dba64a..581ac52a31 100644 --- a/crates/Dockerfile +++ b/crates/Dockerfile @@ -80,6 +80,8 @@ COPY crates/trbfv/Cargo.toml ./trbfv/Cargo.toml COPY crates/utils/Cargo.toml ./utils/Cargo.toml COPY crates/wasm/Cargo.toml ./wasm/Cargo.toml COPY crates/zk-helpers/Cargo.toml ./zk-helpers/Cargo.toml +COPY crates/pvss/Cargo.toml ./pvss/Cargo.toml +COPY crates/pvss-cli/Cargo.toml ./pvss-cli/Cargo.toml RUN echo 'fn main() { println!("cargo:warning=dependency cache build"); }' > ./entrypoint/build.rs RUN echo 'fn main() { println!("cargo:warning=dependency cache build"); }' > ./cli/build.rs diff --git a/crates/fhe-params/src/lib.rs b/crates/fhe-params/src/lib.rs index 3f411e3566..e1fb4973aa 100644 --- a/crates/fhe-params/src/lib.rs +++ b/crates/fhe-params/src/lib.rs @@ -18,4 +18,6 @@ pub use builder::{ }; #[cfg(feature = "abi-encoding")] pub use encoding::{decode_bfv_params, decode_bfv_params_arc, encode_bfv_params, EncodingError}; -pub use presets::{BfvParamSet, BfvPreset, PresetError, PresetMetadata, PresetSearchDefaults}; +pub use presets::{ + BfvParamSet, BfvPreset, ParameterType, PresetError, PresetMetadata, PresetSearchDefaults, +}; diff --git a/crates/fhe-params/src/presets.rs b/crates/fhe-params/src/presets.rs index dfaed3e3a8..b76d8a1587 100644 --- a/crates/fhe-params/src/presets.rs +++ b/crates/fhe-params/src/presets.rs @@ -57,6 +57,15 @@ pub enum BfvPreset { SecureDkg8192, } +/// Parameter type for BFV presets +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ParameterType { + /// Threshold BFV (TRBFV) parameters + THRESHOLD, + /// DKG parameters (BFV) + DKG, +} + /// Metadata describing a BFV preset configuration /// /// This struct contains high-level information about a preset, including @@ -80,6 +89,8 @@ pub struct PresetMetadata { /// Higher values provide stronger security guarantees but may require /// larger parameters. Typically 80 for secure presets, 2 for insecure. pub lambda: usize, + /// Parameter type (BFV / trBFV). + pub parameter_type: ParameterType, } /// Default search parameters for BFV parameter generation @@ -218,17 +229,33 @@ impl BfvPreset { pub fn metadata(&self) -> PresetMetadata { match self { - BfvPreset::InsecureThresholdBfv512 | BfvPreset::InsecureDkg512 => PresetMetadata { + BfvPreset::InsecureThresholdBfv512 => PresetMetadata { + name: self.name(), + degree: insecure_512::DEGREE, + num_parties: insecure_512::NUM_PARTIES, + lambda: DEFAULT_INSECURE_LAMBDA, + parameter_type: ParameterType::THRESHOLD, + }, + BfvPreset::InsecureDkg512 => PresetMetadata { name: self.name(), degree: insecure_512::DEGREE, num_parties: insecure_512::NUM_PARTIES, lambda: DEFAULT_INSECURE_LAMBDA, + parameter_type: ParameterType::DKG, + }, + BfvPreset::SecureThresholdBfv8192 => PresetMetadata { + name: self.name(), + degree: secure_8192::DEGREE, + num_parties: secure_8192::NUM_PARTIES, + lambda: DEFAULT_SECURE_LAMBDA, + parameter_type: ParameterType::THRESHOLD, }, - BfvPreset::SecureThresholdBfv8192 | BfvPreset::SecureDkg8192 => PresetMetadata { + BfvPreset::SecureDkg8192 => PresetMetadata { name: self.name(), degree: secure_8192::DEGREE, num_parties: secure_8192::NUM_PARTIES, lambda: DEFAULT_SECURE_LAMBDA, + parameter_type: ParameterType::DKG, }, } } diff --git a/crates/pvss-cli/Cargo.toml b/crates/pvss-cli/Cargo.toml new file mode 100644 index 0000000000..46b892b552 --- /dev/null +++ b/crates/pvss-cli/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "e3-pvss-cli" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "PVSS CLI for artifact generation" +repository = "https://github.com/gnosisguild/enclave/crates/pvss-cli" + +[dependencies] +e3-pvss = { workspace = true } +e3-fhe-params = { workspace = true } +clap = { workspace = true } +anyhow = { workspace = true } diff --git a/crates/pvss-cli/src/main.rs b/crates/pvss-cli/src/main.rs new file mode 100644 index 0000000000..6f1204e70d --- /dev/null +++ b/crates/pvss-cli/src/main.rs @@ -0,0 +1,120 @@ +// 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 anyhow::{anyhow, Context, Result}; +use clap::Parser; +use e3_fhe_params::{BfvParamSet, BfvPreset}; +use e3_pvss::circuits::pk_bfv::circuit::{PkBfvCircuit, PkBfvCodegenInput}; +use e3_pvss::circuits::pk_bfv::codegen::write_artifacts; +use e3_pvss::registry::CircuitRegistry; +use e3_pvss::sample; +use e3_pvss::traits::Circuit; +use e3_pvss::traits::CircuitCodegen; +use std::path::PathBuf; +use std::sync::Arc; + +/// Minimal PVSS CLI for generating circuit artifacts. +#[derive(Debug, Parser)] +#[command(name = "pvss-cli")] +struct Cli { + /// List all available circuits and exit. + #[arg(long)] + list_circuits: bool, + /// 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). + #[arg(long, required_unless_present = "list_circuits")] + preset: Option, + /// Output directory for generated artifacts. + #[arg(long, default_value = "output")] + output: PathBuf, +} + +/// Parse a preset name into a BFV preset. +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)); + + // Handle list circuits flag. + if args.list_circuits { + let circuits = registry.list_circuits(); + println!("Available circuits:"); + for circuit_name in circuits { + if let Ok(circuit_meta) = registry.get(&circuit_name) { + println!( + " {} - params_type: {:?}, n_recursive_proofs: {}, pub_inputs: {}", + circuit_name, + circuit_meta.supported_parameter(), + circuit_meta.n_recursive_proofs(), + circuit_meta.n_public_inputs() + ); + } + } + return Ok(()); + } + + // 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())?; + + std::fs::create_dir_all(&args.output) + .with_context(|| format!("failed to create output dir {}", args.output.display()))?; + + // Validate circuit exists in registry. + let circuit_meta = registry.get(&circuit).map_err(|_| { + let available = registry.list_circuits().join(", "); + anyhow!("unknown circuit: {}. Available: {}", circuit, available) + })?; + + // Validate preset parameter type matches circuit's supported parameter type. + let preset_param_type = preset.metadata().parameter_type; + let circuit_param_type = circuit_meta.supported_parameter(); + if preset_param_type != circuit_param_type { + return Err(anyhow!( + "preset has parameter type {:?}, but circuit {} requires {:?}", + preset_param_type, + circuit, + circuit_param_type + )); + } + + // Generate artifacts based on circuit name from registry. + let params = BfvParamSet::from(preset).build_arc(); + let sample = sample::generate_sample(¶ms); + let circuit_name = circuit_meta.name(); + let artifacts = match circuit_name { + name if name == ::NAME => { + let circuit = PkBfvCircuit; + circuit.codegen(PkBfvCodegenInput { + preset, + public_key: sample.public_key, + })? + } + name => return Err(anyhow!("circuit {} not yet implemented", name)), + }; + + write_artifacts( + &artifacts.toml, + &artifacts.template, + &artifacts.configs, + &artifacts.wrapper, + Some(args.output.as_path()), + )?; + + println!("Artifacts written to {}", args.output.display()); + Ok(()) +} diff --git a/crates/pvss/Cargo.toml b/crates/pvss/Cargo.toml new file mode 100644 index 0000000000..1246c23752 --- /dev/null +++ b/crates/pvss/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "e3-pvss" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "PVSS core codegen and computation crate" +repository = "https://github.com/gnosisguild/enclave/crates/pvss" + +[dependencies] +e3-polynomial = { workspace = true } +fhe = { workspace = true } +fhe-math = { workspace = true } +e3-fhe-params = { workspace = true } +e3-zk-helpers = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } +rand = { workspace = true } +rayon = { workspace = true } +itertools = "0.14.0" +serde = { workspace = true } +serde_json = { workspace = true } +toml = "0.8.23" +anyhow = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } diff --git a/crates/pvss/src/circuits/mod.rs b/crates/pvss/src/circuits/mod.rs new file mode 100644 index 0000000000..78aa5fbb5d --- /dev/null +++ b/crates/pvss/src/circuits/mod.rs @@ -0,0 +1,7 @@ +// 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_bfv; diff --git a/crates/pvss/src/circuits/pk_bfv/circuit.rs b/crates/pvss/src/circuits/pk_bfv/circuit.rs new file mode 100644 index 0000000000..8b6d7a4bac --- /dev/null +++ b/crates/pvss/src/circuits/pk_bfv/circuit.rs @@ -0,0 +1,70 @@ +// 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::circuits::pk_bfv::codegen; +use crate::circuits::pk_bfv::computation::{Bits, Bounds, Witness}; +use crate::errors::CodegenError; +use crate::traits::{Circuit, CircuitCodegen, CircuitComputation, Computation}; +use crate::types::{Artifacts, DkgInputType}; +use e3_fhe_params::{BfvPreset, ParameterType}; +use fhe::bfv::{BfvParameters, PublicKey}; + +#[derive(Debug)] +pub struct PkBfvCircuit; + +#[derive(Debug)] +pub struct PkBfvComputationOutput { + pub bounds: Bounds, + pub bits: Bits, + pub witness: Witness, +} + +#[derive(Debug, Clone)] +pub struct PkBfvCodegenInput { + pub preset: BfvPreset, + pub public_key: PublicKey, +} + +impl Circuit for PkBfvCircuit { + const NAME: &'static str = "pk-bfv"; + const PREFIX: &'static str = "PK_BFV"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; + const DKG_INPUT_TYPE: Option = None; + const N_PROOFS: usize = 1; + const N_PUBLIC_INPUTS: usize = 1; +} + +impl CircuitCodegen for PkBfvCircuit { + type Input = PkBfvCodegenInput; + type Error = CodegenError; + + fn codegen(&self, input: Self::Input) -> Result { + codegen::codegen(input.preset, input.public_key) + } +} + +impl CircuitComputation for PkBfvCircuit { + type Params = BfvParameters; + type Input = PublicKey; + type Output = PkBfvComputationOutput; + type Error = CodegenError; + + fn compute( + &self, + params: &Self::Params, + input: &Self::Input, + ) -> Result { + let bounds = Bounds::compute(params, &())?; + let bits = Bits::compute(params, &bounds)?; + let witness = Witness::compute(params, input)?; + + Ok(PkBfvComputationOutput { + bounds, + bits, + witness, + }) + } +} diff --git a/crates/pvss/src/circuits/pk_bfv/codegen.rs b/crates/pvss/src/circuits/pk_bfv/codegen.rs new file mode 100644 index 0000000000..151403143e --- /dev/null +++ b/crates/pvss/src/circuits/pk_bfv/codegen.rs @@ -0,0 +1,188 @@ +// 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::circuits::pk_bfv::circuit::PkBfvCircuit; +use crate::circuits::pk_bfv::computation::{Bits, Bounds, Witness}; +use crate::errors::CodegenError; +use crate::traits::Circuit; +use crate::traits::Computation; +use crate::traits::ReduceToZkpModulus; +use crate::types::Artifacts; +use crate::types::{Configs, Template, Toml, Wrapper}; +use crate::utils::generate_wrapper; +use crate::utils::get_security_level; +use crate::utils::map_witness_2d_vector_to_json; +use crate::utils::write_configs; +use crate::utils::write_template; +use crate::utils::write_toml; +use crate::utils::write_wrapper; +use e3_fhe_params::BfvParamSet; +use e3_fhe_params::BfvPreset; +use fhe::bfv::BfvParameters; +use fhe::bfv::PublicKey; +use serde::{Deserialize, Serialize}; +use serde_json; +use std::path::Path; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TomlJson { + pub pk0is: Vec, + pub pk1is: Vec, +} + +pub fn generate_toml(witness: Witness) -> Result { + let pk0is = map_witness_2d_vector_to_json(&witness.pk0is); + let pk1is = map_witness_2d_vector_to_json(&witness.pk1is); + + let toml_json = TomlJson { pk0is, pk1is }; + Ok(toml::to_string(&toml_json)?) +} + +pub fn codegen(preset: BfvPreset, public_key: PublicKey) -> Result { + let params = BfvParamSet::from(preset).build_arc(); + // Compute. + let bounds = Bounds::compute(¶ms, &())?; + let bits = Bits::compute(¶ms, &bounds)?; + let witness = Witness::compute(¶ms, &public_key)?; + let zkp_witness = witness.reduce_to_zkp_modulus(); + + let toml = generate_toml(zkp_witness)?; + let configs = generate_configs(¶ms, &bits); + let template = generate_template(preset.metadata().lambda); + let wrapper = generate_wrapper( + ::N_PROOFS, + ::N_PUBLIC_INPUTS, + ); + + Ok(Artifacts { + toml, + configs, + template, + wrapper, + }) +} + +pub fn generate_template(lambda: usize) -> Template { + format!( + r#"use lib::configs::{}::bfv::{{L, N, {}_BIT_PK}}; +use lib::core::bfv_pk::BfvPkCommit; +use lib::math::polynomial::Polynomial; + +fn main(pk0is: [Polynomial; L], pk1is: [Polynomial; L]) -> pub Field {{ + let pk_bfv: BfvPkCommit = BfvPkCommit::new(pk0is, pk1is); + pk_bfv.verify() +}} +"#, + get_security_level(lambda).as_str(), + ::PREFIX, + ::PREFIX, + ) +} + +pub fn generate_configs(params: &Arc, bits: &Bits) -> Configs { + format!( + r#"// Global configs for Public Key BFV circuit +pub global N: u32 = {}; +pub global L: u32 = {}; + +/************************************ +------------------------------------- +pk_bfv (CIRCUIT 0 - PUBLIC KEY BFV COMMITMENT) +------------------------------------- +************************************/ + +// pk_bfv - bit parameters +pub global {}_BIT_PK: u32 = {}; +"#, + params.degree(), + params.moduli().len(), + ::PREFIX, + bits.pk_bit, + ) +} + +pub fn write_artifacts( + toml: &Toml, + template: &Template, + configs: &Configs, + wrapper: &Wrapper, + path: Option<&Path>, +) -> Result<(), CodegenError> { + write_toml(&toml, path)?; + write_template(&template, path)?; + write_configs(&configs, path)?; + write_wrapper(&wrapper, path)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sample; + use e3_fhe_params::{BfvParamSet, BfvPreset}; + use tempfile::TempDir; + + #[test] + fn test_toml_generation_and_structure() { + let preset = BfvPreset::InsecureThresholdBfv512; + let params = BfvParamSet::from(preset).build_arc(); + let sample = sample::generate_sample(¶ms); + let artifacts = codegen(preset, sample.public_key).unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + let pk0is = parsed + .get("pk0is") + .and_then(|value| value.as_array()) + .unwrap(); + let pk1is = parsed + .get("pk1is") + .and_then(|value| value.as_array()) + .unwrap(); + assert!(!pk0is.is_empty()); + assert!(!pk1is.is_empty()); + + let temp_dir = TempDir::new().unwrap(); + write_artifacts( + &artifacts.toml, + &artifacts.template, + &artifacts.configs, + &artifacts.wrapper, + Some(temp_dir.path()), + ) + .unwrap(); + + let output_path = temp_dir.path().join("Prover.toml"); + assert!(output_path.exists()); + + let content = std::fs::read_to_string(&output_path).unwrap(); + assert!(content.contains("pk0is")); + assert!(content.contains("pk1is")); + + assert!(artifacts.toml.contains("[[pk0is]]")); + assert!(artifacts.toml.contains("[[pk1is]]")); + + let template_path = temp_dir.path().join("main.nr"); + assert!(template_path.exists()); + + let template_content = std::fs::read_to_string(&template_path).unwrap(); + assert!(template_content.contains("pk0is: [Polynomial; L],")); + assert!(template_content.contains("pk1is: [Polynomial; L]")); + + let wrapper_path = temp_dir.path().join("wrapper.nr"); + assert!(wrapper_path.exists()); + + 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(¶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())); + } +} diff --git a/crates/pvss/src/circuits/pk_bfv/computation.rs b/crates/pvss/src/circuits/pk_bfv/computation.rs new file mode 100644 index 0000000000..5fc6b6d2e7 --- /dev/null +++ b/crates/pvss/src/circuits/pk_bfv/computation.rs @@ -0,0 +1,213 @@ +// 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::traits::Computation; +use crate::traits::ConvertToJson; +use crate::traits::ReduceToZkpModulus; +use e3_polynomial::reduce_coefficients_2d; +use e3_polynomial::utils::reduce_and_center_coefficients_mut; +use e3_zk_helpers::utils::calculate_bit_width; +use e3_zk_helpers::utils::get_zkp_modulus; +use fhe::bfv::BfvParameters; +use fhe::bfv::PublicKey; +use fhe_math::rq::Representation; +use itertools::izip; +use num_bigint::BigInt; +use num_bigint::BigUint; +use rayon::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Constants { + pub n: usize, + pub l: usize, + pub moduli: Vec, +} + +#[derive(Debug, Clone)] +pub struct Bits { + pub pk_bit: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Bounds { + /// Bound for public key polynomials (pk0, pk1) + pub pk_bound: BigUint, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + /// Public key polynomials (pk0, pk1) for each CRT basis. + pub pk0is: Vec>, + pub pk1is: Vec>, +} + +impl Computation for Constants { + type Params = BfvParameters; + type Input = (); + type Error = std::convert::Infallible; + + fn compute(params: &Self::Params, _: &Self::Input) -> Result { + let moduli = params.moduli().to_vec(); + + Ok(Constants { + n: params.degree(), + l: moduli.len(), + moduli, + }) + } +} + +impl Computation for Bits { + type Params = BfvParameters; + type Input = Bounds; + type Error = e3_zk_helpers::utils::ZkHelpersUtilsError; + + fn compute(_: &Self::Params, input: &Self::Input) -> Result { + Ok(Bits { + pk_bit: calculate_bit_width(&input.pk_bound.to_string())?, + }) + } +} + +impl Computation for Bounds { + type Params = BfvParameters; + type Input = (); + type Error = crate::errors::CodegenError; + + fn compute(params: &Self::Params, _: &Self::Input) -> Result { + let mut pk_bound_max = BigUint::from(0u32); + + for &qi in params.moduli() { + let qi_bound: BigUint = (&BigUint::from(qi) - 1u32) / 2u32; + + if qi_bound > pk_bound_max { + pk_bound_max = qi_bound; + } + } + + Ok(Bounds { + pk_bound: pk_bound_max, + }) + } +} + +impl Computation for Witness { + type Params = BfvParameters; + type Input = PublicKey; + type Error = fhe::Error; + + fn compute(params: &Self::Params, public_key: &Self::Input) -> Result { + let moduli = params.moduli(); + + // Extract public key components (pk0, pk1) from the ciphertext structure + // and change representation to Power Basis. + let mut pk0 = public_key.c.c[0].clone(); + let mut pk1 = public_key.c.c[1].clone(); + pk0.change_representation(Representation::PowerBasis); + pk1.change_representation(Representation::PowerBasis); + + let pk0_coeffs = pk0.coefficients(); + let pk1_coeffs = pk1.coefficients(); + let pk0_rows = pk0_coeffs.rows(); + let pk1_rows = pk1_coeffs.rows(); + + // Extract and convert public key polynomials per modulus + // Collect into Vec first to preserve moduli ordering with par_iter + let zipped: Vec<_> = izip!(moduli, pk0_rows, pk1_rows).collect(); + let results: Vec<(Vec, Vec)> = zipped + .par_iter() + .map(|(qi, pk0_coeffs, pk1_coeffs)| { + let mut pk0i: Vec = + pk0_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + let mut pk1i: Vec = + pk1_coeffs.iter().rev().map(|&x| BigInt::from(x)).collect(); + + reduce_and_center_coefficients_mut(&mut pk0i, &BigInt::from(**qi)); + reduce_and_center_coefficients_mut(&mut pk1i, &BigInt::from(**qi)); + + (pk0i, pk1i) + }) + .collect(); + + let (pk0is, pk1is): (Vec<_>, Vec<_>) = results.into_iter().unzip(); + + Ok(Witness { pk0is, pk1is }) + } +} + +impl ConvertToJson for Constants { + 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) + } +} + +impl ReduceToZkpModulus for Witness { + fn reduce_to_zkp_modulus(&self) -> Witness { + Witness { + pk0is: reduce_coefficients_2d(&self.pk0is, &get_zkp_modulus()), + pk1is: reduce_coefficients_2d(&self.pk1is, &get_zkp_modulus()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sample::generate_sample; + use crate::traits::ConvertToJson; + use crate::traits::ReduceToZkpModulus; + use e3_fhe_params::{BfvParamSet, BfvPreset}; + + #[test] + fn test_bound_and_bits_computation_consistency() { + let params = BfvParamSet::from(BfvPreset::InsecureThresholdBfv512).build_arc(); + let bounds = Bounds::compute(¶ms, &()).unwrap(); + let bits = Bits::compute(¶ms, &bounds).unwrap(); + let expected_bits = calculate_bit_width(&bounds.pk_bound.to_string()).unwrap(); + + assert_eq!(bounds.pk_bound, BigUint::from(34359701504u64)); + assert_eq!(bits.pk_bit, expected_bits); + } + + #[test] + fn test_witness_reduction_and_json_roundtrip() { + let params = BfvParamSet::from(BfvPreset::InsecureThresholdBfv512).build_arc(); + let encryption_data = generate_sample(¶ms); + let witness = Witness::compute(¶ms, &encryption_data.public_key).unwrap(); + let zkp_reduced = witness.reduce_to_zkp_modulus(); + let json = zkp_reduced.convert_to_json().unwrap(); + let decoded: Witness = serde_json::from_value(json.clone()).unwrap(); + + assert_eq!(decoded.pk0is, zkp_reduced.pk0is); + assert_eq!(decoded.pk1is, zkp_reduced.pk1is); + } + + #[test] + fn test_constants_json_roundtrip() { + let params = BfvParamSet::from(BfvPreset::InsecureThresholdBfv512).build_arc(); + let constants = Constants::compute(¶ms, &()).unwrap(); + + let json = constants.convert_to_json().unwrap(); + let decoded: Constants = serde_json::from_value(json).unwrap(); + + assert_eq!(decoded.n, constants.n); + assert_eq!(decoded.l, constants.l); + assert_eq!(decoded.moduli, constants.moduli); + } +} diff --git a/crates/pvss/src/circuits/pk_bfv/mod.rs b/crates/pvss/src/circuits/pk_bfv/mod.rs new file mode 100644 index 0000000000..63504b9145 --- /dev/null +++ b/crates/pvss/src/circuits/pk_bfv/mod.rs @@ -0,0 +1,9 @@ +// 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; diff --git a/crates/pvss/src/errors.rs b/crates/pvss/src/errors.rs new file mode 100644 index 0000000000..04ed43423a --- /dev/null +++ b/crates/pvss/src/errors.rs @@ -0,0 +1,21 @@ +// 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 thiserror::Error; + +#[derive(Error, Debug)] +pub enum CodegenError { + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), + #[error("TOML serialization error: {0}")] + Toml(#[from] toml::ser::Error), + #[error("BFV error: {0}")] + Fhe(#[from] fhe::Error), + #[error("ZK helper error: {0}")] + ZkHelpers(#[from] e3_zk_helpers::utils::ZkHelpersUtilsError), + #[error("Unexpected error: {0}")] + Other(String), +} diff --git a/crates/pvss/src/lib.rs b/crates/pvss/src/lib.rs new file mode 100644 index 0000000000..9be7fb848e --- /dev/null +++ b/crates/pvss/src/lib.rs @@ -0,0 +1,13 @@ +// 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 circuits; +pub mod errors; +pub mod registry; +pub mod sample; +pub mod traits; +pub mod types; +pub mod utils; diff --git a/crates/pvss/src/registry.rs b/crates/pvss/src/registry.rs new file mode 100644 index 0000000000..4229475fd1 --- /dev/null +++ b/crates/pvss/src/registry.rs @@ -0,0 +1,112 @@ +// 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::traits::CircuitMetadata; +use crate::types::DkgInputType; +use e3_fhe_params::ParameterType; +use std::collections::HashMap; +use std::sync::Arc; +use thiserror::Error; + +/// Errors produced by the circuit registry. +#[derive(Error, Debug)] +pub enum RegistryError { + #[error("Unknown circuit: {name}")] + UnknownCircuit { name: String }, +} + +/// Registry for PVSS circuits. +pub struct CircuitRegistry { + circuits: HashMap>, +} + +impl CircuitRegistry { + /// Build an empty registry. + pub fn new() -> Self { + Self { + circuits: HashMap::new(), + } + } + + /// Register a circuit descriptor under a name. + pub fn register(&mut self, circuit: Arc) { + self.circuits.insert(circuit.name().to_lowercase(), circuit); + } + + /// Get a circuit descriptor from the registry. + pub fn get(&self, name: &str) -> Result, RegistryError> { + self.circuits + .get(&name.to_lowercase()) + .cloned() + .ok_or_else(|| RegistryError::UnknownCircuit { + name: name.to_string(), + }) + } + + /// Return supported parameter types for a circuit. + pub fn supported_parameter_type(&self, name: &str) -> Result { + Ok(self.get(name)?.supported_parameter()) + } + + /// Return DKG input type for a circuit, if any. + pub fn dkg_input_type(&self, name: &str) -> Result, RegistryError> { + Ok(self.get(name)?.dkg_input_type()) + } + + /// Get number of recursive proofs for a circuit. + /// This is used for determine the number of proofs required for aggregation. + pub fn n_recursive_proofs(&self, name: &str) -> Result { + Ok(self.get(name)?.n_recursive_proofs()) + } + + /// Get number of public inputs for a circuit. + /// This is used for determine the number of public inputs required for aggregation. + pub fn n_public_inputs(&self, name: &str) -> Result { + Ok(self.get(name)?.n_public_inputs()) + } + + /// List all registered circuit names. + pub fn list_circuits(&self) -> Vec { + self.circuits.keys().cloned().collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::circuits::pk_bfv::circuit::PkBfvCircuit; + use crate::traits::Circuit; + + #[test] + /// Unknown circuits should return an error. + fn registry_rejects_unknown_circuit() { + let registry = CircuitRegistry::new(); + assert!(matches!( + registry.get("unknown"), + Err(RegistryError::UnknownCircuit { .. }) + )); + } + + #[test] + /// Registry should expose metadata for registered circuits. + fn registry_reports_expected_metadata() { + let mut registry = CircuitRegistry::new(); + registry.register(Arc::new(PkBfvCircuit)); + let circuit = registry.get(::NAME).unwrap(); + + assert_eq!(circuit.name(), ::NAME); + assert_eq!(circuit.supported_parameter(), ParameterType::DKG); + assert!(circuit.dkg_input_type().is_none()); + assert_eq!( + circuit.n_recursive_proofs(), + ::N_PROOFS + ); + assert_eq!( + circuit.n_public_inputs(), + ::N_PUBLIC_INPUTS + ); + } +} diff --git a/crates/pvss/src/sample.rs b/crates/pvss/src/sample.rs new file mode 100644 index 0000000000..6d89aa0fd9 --- /dev/null +++ b/crates/pvss/src/sample.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use crate::types::Sample; +use fhe::bfv::{BfvParameters, PublicKey, SecretKey}; +use rand::thread_rng; +use std::sync::Arc; + +pub fn generate_sample(params: &Arc) -> Sample { + let mut rng = thread_rng(); + + let secret_key = SecretKey::random(¶ms, &mut rng); + let public_key = PublicKey::new(&secret_key, &mut rng); + + Sample { public_key } +} + +#[cfg(test)] +mod tests { + use super::*; + use e3_fhe_params::{BfvParamSet, BfvPreset}; + + #[test] + fn test_generate_sample() { + let params = BfvParamSet::from(BfvPreset::InsecureThresholdBfv512).build_arc(); + let sample = generate_sample(¶ms); + + assert_eq!(sample.public_key.c.c.len(), 2); + } +} diff --git a/crates/pvss/src/traits.rs b/crates/pvss/src/traits.rs new file mode 100644 index 0000000000..78fa9dfaf1 --- /dev/null +++ b/crates/pvss/src/traits.rs @@ -0,0 +1,112 @@ +// 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. + +//! Common computation behavior across circuits. + +use crate::types::DkgInputType; +use e3_fhe_params::ParameterType; +use serde_json::Value; + +pub trait Computation: Sized { + type Params; + type Input; + type Error; + + fn compute(params: &Self::Params, input: &Self::Input) -> Result; +} + +pub trait ConvertToJson { + fn convert_to_json(&self) -> serde_json::Result; +} + +pub trait ReduceToZkpModulus: Sized { + fn reduce_to_zkp_modulus(&self) -> Self; +} + +pub trait Circuit: Send + Sync { + const NAME: &'static str; + const PREFIX: &'static str; + const SUPPORTED_PARAMETER: ParameterType; + const DKG_INPUT_TYPE: Option; + const N_PROOFS: usize; + const N_PUBLIC_INPUTS: usize; + + fn name(&self) -> &'static str { + Self::NAME + } + + fn prefix(&self) -> &'static str { + Self::PREFIX + } + + fn supported_parameter(&self) -> ParameterType { + Self::SUPPORTED_PARAMETER + } + + fn dkg_input_type(&self) -> Option { + Self::DKG_INPUT_TYPE + } + + fn n_recursive_proofs(&self) -> usize { + Self::N_PROOFS + } + + fn n_public_inputs(&self) -> usize { + Self::N_PUBLIC_INPUTS + } +} + +pub trait CircuitMetadata: Send + Sync { + fn name(&self) -> &'static str; + fn supported_parameter(&self) -> ParameterType; + fn dkg_input_type(&self) -> Option; + fn n_recursive_proofs(&self) -> usize; + fn n_public_inputs(&self) -> usize; +} + +impl CircuitMetadata for T { + fn name(&self) -> &'static str { + T::NAME + } + + fn supported_parameter(&self) -> ParameterType { + T::SUPPORTED_PARAMETER + } + + fn dkg_input_type(&self) -> Option { + T::DKG_INPUT_TYPE + } + + fn n_recursive_proofs(&self) -> usize { + T::N_PROOFS + } + + fn n_public_inputs(&self) -> usize { + T::N_PUBLIC_INPUTS + } +} + +pub trait CircuitCodegen: Circuit { + type Input; + type Error; + + /// Generate artifacts for a circuit. + fn codegen(&self, input: Self::Input) -> Result; +} + +pub trait CircuitComputation: Circuit { + type Params; + type Input; + type Output; + type Error; + + /// Compute circuit-specific data. + fn compute( + &self, + params: &Self::Params, + input: &Self::Input, + ) -> Result; +} diff --git a/crates/pvss/src/types.rs b/crates/pvss/src/types.rs new file mode 100644 index 0000000000..d244d8445a --- /dev/null +++ b/crates/pvss/src/types.rs @@ -0,0 +1,99 @@ +// 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 fhe::bfv::PublicKey; + +pub type Toml = String; +pub type Template = String; +pub type Configs = String; +pub type Wrapper = String; + +/// Variant for input types for DKG. +/// +/// This variant is used to determine the type of input that is used for the DKG +/// circuits (C2, C3, C4) +#[derive(Clone)] +pub enum DkgInputType { + /// The input type that generates shares of a secret key using secret sharing. + SecretKey, + /// The input type that generates shares of smudging noise instead of secret key shares. + SmudgingNoise, +} + +/// @todo this must be integrated inside Ciphernodes & Smart Contract +/// instead of being a separate type in here. The pvss crate should import this and +/// the default values that must be used and shared among the whole enclave repository. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CiphernodesCommitteeSize { + /// Small committee size (fast local/testing). + Small, + /// Medium committee size (default). + Medium, + /// Large committee size (higher assurance). + Large, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CiphernodesCommittee { + /// Total number of parties (N_PARTIES). + n: usize, + /// Number of honest parties (H). + h: usize, + /// Threshold value (T). + threshold: usize, +} + +impl CiphernodesCommitteeSize { + /// Returns `(num_parties, num_honest_parties, threshold)` for this size. + pub fn values(self) -> CiphernodesCommittee { + match self { + CiphernodesCommitteeSize::Small => CiphernodesCommittee { + n: 5, + h: 3, + threshold: 2, + }, + _ => unreachable!(), + } + // @todo add the other committee sizes + // CiphernodesCommitteeSize::Medium => CiphernodesCommittee { + // n: 5, + // h: 3, + // threshold: 2, + // }, + // CiphernodesCommitteeSize::Large => CiphernodesCommittee { + // n: 5, + // h: 3, + // threshold: 2, + // }, + } +} + +#[derive(Debug, Clone, Copy)] +pub enum SecurityLevel { + INSECURE, + PRODUCTION, +} + +impl SecurityLevel { + pub fn as_str(self) -> &'static str { + match self { + SecurityLevel::INSECURE => "insecure", + SecurityLevel::PRODUCTION => "production", + } + } +} + +#[derive(Debug, Clone)] +pub struct Sample { + pub public_key: PublicKey, +} + +#[derive(Debug, Clone)] +pub struct Artifacts { + pub toml: Toml, + pub configs: Configs, + pub template: Template, + pub wrapper: Wrapper, +} diff --git a/crates/pvss/src/utils.rs b/crates/pvss/src/utils.rs new file mode 100644 index 0000000000..df55859972 --- /dev/null +++ b/crates/pvss/src/utils.rs @@ -0,0 +1,90 @@ +// 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::errors::CodegenError; +use crate::types::{Configs, SecurityLevel, Template, Toml, Wrapper}; +use e3_zk_helpers::utils::to_string_1d_vec; +use num_bigint::BigInt; +use serde_json; +use std::path::Path; + +pub fn map_witness_2d_vector_to_json(values: &Vec>) -> Vec { + values + .iter() + .map(|value| { + serde_json::json!({ + "coefficients": to_string_1d_vec(value) + }) + }) + .collect() +} + +pub fn get_security_level(lambda: usize) -> SecurityLevel { + if lambda < 80 { + SecurityLevel::INSECURE + } else { + SecurityLevel::PRODUCTION + } +} + +pub fn generate_wrapper(n_recursive_proofs: usize, n_public_inputs: usize) -> Wrapper { + format!( + r#"use bb_proof_verification::{{UltraHonkProof, UltraHonkVerificationKey, verify_ultrahonk_proof}}; +use lib::math::commitments::compute_aggregation_commitment; + +// Number of proofs. +pub global N_PROOFS: u32 = {}; +/// Number of public inputs/outputs per proof. +pub global N_PUBLIC_INPUTS: u32 = {}; + +fn main( + verification_key: UltraHonkVerificationKey, + proofs: [UltraHonkProof; N_PROOFS], + public_inputs: pub [[Field; N_PUBLIC_INPUTS]; N_PROOFS], + key_hash: Field, +) -> pub Field {{ + for i in 0..N_PROOFS {{ + verify_ultrahonk_proof(verification_key, proofs[i], public_inputs[i], key_hash); + }} + + let mut aggregated_public_inputs = Vec::new(); + + for i in 0..N_PROOFS {{ + for j in 0..N_PUBLIC_INPUTS {{ + aggregated_public_inputs.push(public_inputs[i][j]); + }} + }} + + compute_aggregation_commitment(aggregated_public_inputs) +}} +"#, + n_recursive_proofs, n_public_inputs + ) +} + +pub fn write_toml(toml: &Toml, path: Option<&Path>) -> Result<(), CodegenError> { + let toml_path = path.unwrap_or_else(|| Path::new(".")); + let toml_path = toml_path.join("Prover.toml"); + Ok(std::fs::write(toml_path, toml)?) +} + +pub fn write_template(template: &Template, path: Option<&Path>) -> Result<(), CodegenError> { + let template_path = path.unwrap_or_else(|| Path::new(".")); + let template_path = template_path.join("main.nr"); + Ok(std::fs::write(template_path, template)?) +} + +pub fn write_configs(configs: &Configs, path: Option<&Path>) -> Result<(), CodegenError> { + let configs_path = path.unwrap_or_else(|| Path::new(".")); + let configs_path = configs_path.join("configs.nr"); + Ok(std::fs::write(configs_path, configs)?) +} + +pub fn write_wrapper(wrapper: &Wrapper, path: Option<&Path>) -> Result<(), CodegenError> { + let wrapper_path = path.unwrap_or_else(|| Path::new(".")); + let wrapper_path = wrapper_path.join("wrapper.nr"); + Ok(std::fs::write(wrapper_path, wrapper)?) +}