diff --git a/Cargo.lock b/Cargo.lock index 4288fe69d0..01d6e25262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5236,9 +5236,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b00d05442c2106c75b7410f820b152f61ec0edc7befcb9b381b673a20314753" +checksum = "53bf2b0e0785c5394a7392f66d7c4fb9c653633c29b27a932280da3cb344c66a" dependencies = [ "doctest-file", "futures-core", @@ -6083,9 +6083,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -7034,9 +7034,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -8296,9 +8296,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safe-proc-macro2" @@ -9642,9 +9642,9 @@ dependencies = [ [[package]] name = "tracing-test-macro" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" dependencies = [ "quote", "syn 2.0.114", @@ -9716,9 +9716,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-segmentation" @@ -10573,18 +10573,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", diff --git a/circuits/bin/dkg/e_sm_share_decryption/src/main.nr b/circuits/bin/dkg/e_sm_share_decryption/src/main.nr index d7806ef1d5..dc09eb6365 100644 --- a/circuits/bin/dkg/e_sm_share_decryption/src/main.nr +++ b/circuits/bin/dkg/e_sm_share_decryption/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG_E_SM}; +use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG}; use lib::configs::default::H; use lib::core::dkg::share_decryption::ShareDecryption; use lib::math::polynomial::Polynomial; @@ -13,7 +13,7 @@ fn main( expected_commitments: pub [[Field; L_THRESHOLD]; H], decrypted_shares: [[Polynomial; L_THRESHOLD]; H], ) -> pub Field { - let share_decryption: ShareDecryption = + let share_decryption: ShareDecryption = ShareDecryption::new(expected_commitments, decrypted_shares); share_decryption.execute() diff --git a/circuits/bin/dkg/sk_share_decryption/src/main.nr b/circuits/bin/dkg/sk_share_decryption/src/main.nr index 6a894eaee2..dc09eb6365 100644 --- a/circuits/bin/dkg/sk_share_decryption/src/main.nr +++ b/circuits/bin/dkg/sk_share_decryption/src/main.nr @@ -4,7 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. -use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG_SK}; +use lib::configs::default::dkg::{L_THRESHOLD, N, SHARE_DECRYPTION_BIT_MSG}; use lib::configs::default::H; use lib::core::dkg::share_decryption::ShareDecryption; use lib::math::polynomial::Polynomial; @@ -13,7 +13,7 @@ fn main( expected_commitments: pub [[Field; L_THRESHOLD]; H], decrypted_shares: [[Polynomial; L_THRESHOLD]; H], ) -> pub Field { - let share_decryption: ShareDecryption = + let share_decryption: ShareDecryption = ShareDecryption::new(expected_commitments, decrypted_shares); share_decryption.execute() diff --git a/circuits/lib/src/configs/insecure/dkg.nr b/circuits/lib/src/configs/insecure/dkg.nr index 5775be709e..5f7440a104 100644 --- a/circuits/lib/src/configs/insecure/dkg.nr +++ b/circuits/lib/src/configs/insecure/dkg.nr @@ -136,18 +136,9 @@ pub global SHARE_ENCRYPTION_CONFIGS: ShareEncryptionConfigs = ShareEncryption /************************************ ------------------------------------- -share_decryption_sk (CIRCUIT 4a) +share_decryption_sk (CIRCUIT 4a - BFV DECRYPTION SK) +share_decryption_e_sm (CIRCUIT 4b - BFV DECRYPTION E_SM) ------------------------------------- ************************************/ -// share_decryption_sk - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_SK: u32 = 37; - -/************************************ -------------------------------------- -share_decryption_e_sm (CIRCUIT 4b) -------------------------------------- -************************************/ - -// share_decryption_e_sm - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_E_SM: u32 = 37; +pub global SHARE_DECRYPTION_BIT_MSG: u32 = 36; diff --git a/circuits/lib/src/configs/secure/dkg.nr b/circuits/lib/src/configs/secure/dkg.nr index 11190a7251..3cb36ed46a 100644 --- a/circuits/lib/src/configs/secure/dkg.nr +++ b/circuits/lib/src/configs/secure/dkg.nr @@ -132,18 +132,9 @@ pub global SHARE_ENCRYPTION_CONFIGS: ShareEncryptionConfigs = ShareEncryption /************************************ ------------------------------------- -share_decryption_sk (CIRCUIT 4a) +share_decryption_sk (CIRCUIT 4a - BFV DECRYPTION SK) +share_decryption_e_sm (CIRCUIT 4b - BFV DECRYPTION E_SM) ------------------------------------- ************************************/ -// share_decryption_sk - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_SK: u32 = 56; - -/************************************ -------------------------------------- -share_decryption_e_sm (CIRCUIT 4b) -------------------------------------- -************************************/ - -// share_decryption_e_sm - bit parameters -pub global SHARE_DECRYPTION_BIT_MSG_E_SM: u32 = 56; +pub global SHARE_DECRYPTION_BIT_MSG: u32 = 55; diff --git a/crates/zk-helpers/src/bin/zk_cli.rs b/crates/zk-helpers/src/bin/zk_cli.rs index 0b1b6b9d5c..7c50e14de4 100644 --- a/crates/zk-helpers/src/bin/zk_cli.rs +++ b/crates/zk-helpers/src/bin/zk_cli.rs @@ -20,17 +20,15 @@ use e3_zk_helpers::circuits::dkg::share_computation::circuit::{ }; use e3_zk_helpers::codegen::{write_artifacts, CircuitCodegen}; use e3_zk_helpers::computation::DkgInputType; -use e3_zk_helpers::dkg::share_encryption::{ - ShareEncryptionCircuit, ShareEncryptionCircuitInput, ShareEncryptionSample, -}; +use e3_zk_helpers::dkg::share_decryption::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; +use e3_zk_helpers::dkg::share_encryption::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; use e3_zk_helpers::registry::{Circuit, CircuitRegistry}; use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuit; use e3_zk_helpers::threshold::pk_aggregation::PkAggregationCircuitInput; use e3_zk_helpers::threshold::pk_generation::{PkGenerationCircuit, PkGenerationCircuitInput}; use e3_zk_helpers::threshold::user_data_encryption::{ - UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, UserDataEncryptionSample, + UserDataEncryptionCircuit, UserDataEncryptionCircuitInput, }; -use e3_zk_helpers::{PkSample, ShareComputationSample}; use std::io::Write; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; @@ -159,8 +157,9 @@ fn main() -> Result<()> { registry.register(Arc::new(PkCircuit)); registry.register(Arc::new(ShareComputationCircuit)); registry.register(Arc::new(UserDataEncryptionCircuit)); - registry.register(Arc::new(ShareEncryptionCircuit)); registry.register(Arc::new(PkGenerationCircuit)); + registry.register(Arc::new(ShareEncryptionCircuit)); + registry.register(Arc::new(ShareDecryptionCircuit)); registry.register(Arc::new(PkAggregationCircuit)); // Handle list circuits flag. @@ -211,7 +210,8 @@ fn main() -> Result<()> { let write_prover_toml = args.toml; // Only share-computation has a witness-type choice (secret-key vs smudging-noise). pk always uses secret key. let has_witness_type = circuit_meta.name() == ShareComputationCircuit::NAME - || circuit_meta.name() == ShareEncryptionCircuit::NAME; + || circuit_meta.name() == ShareEncryptionCircuit::NAME + || circuit_meta.name() == ShareDecryptionCircuit::NAME; let dkg_input_type = if has_witness_type { // Share-computation: require --witness when generating Prover.toml; default secret-key for configs-only. @@ -249,91 +249,67 @@ fn main() -> Result<()> { let circuit_name = circuit_meta.name(); let artifacts = match circuit_name { name if name == ::NAME => { - let sample = PkSample::generate(preset, CiphernodesCommitteeSize::Small); + let sample = PkCircuitInput::generate_sample(preset); + let circuit = PkCircuit; - circuit.codegen( - preset, - &PkCircuitInput { - public_key: sample.dkg_public_key, - }, - )? + circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = ShareComputationSample::generate( + let sample = ShareComputationCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, - sd.z, - sd.lambda, ); let circuit = ShareComputationCircuit; - circuit.codegen( - preset, - &ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample.parity_matrix.clone(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - }, - )? + circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sd = preset - .search_defaults() - .ok_or_else(|| anyhow!("missing search_defaults for preset"))?; - let sample = ShareEncryptionSample::generate( + let sd = preset.search_defaults().ok_or_else(|| { + anyhow!("preset does not define search defaults for {}", name) + })?; + let sample = ShareEncryptionCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small, dkg_input_type, sd.z, sd.lambda, ); - let circuit = ShareEncryptionCircuit; - circuit.codegen( - preset, - &ShareEncryptionCircuitInput { - plaintext: sample.plaintext, - ciphertext: sample.ciphertext, - public_key: sample.public_key, - secret_key: sample.secret_key, - u_rns: sample.u_rns, - e0_rns: sample.e0_rns, - e1_rns: sample.e1_rns, - }, - )? + let circuit = ShareEncryptionCircuit; + circuit.codegen(preset, &sample)? } name if name == ::NAME => { - let sample = UserDataEncryptionSample::generate(preset); - let circuit = UserDataEncryptionCircuit; + let sample = UserDataEncryptionCircuitInput::generate_sample(preset); - circuit.codegen( - preset, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key, - plaintext: sample.plaintext, - }, - )? + let circuit = UserDataEncryptionCircuit; + circuit.codegen(preset, &sample)? } name if name == ::NAME => { let sample = PkGenerationCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small.values(), )?; + let circuit = PkGenerationCircuit; circuit.codegen(preset, &sample)? } + name if name == ::NAME => { + let sample = ShareDecryptionCircuitInput::generate_sample( + preset, + CiphernodesCommitteeSize::Small, + dkg_input_type, + ); + + let circuit = ShareDecryptionCircuit; + circuit.codegen(preset, &sample)? + } name if name == ::NAME => { let sample = PkAggregationCircuitInput::generate_sample( preset, CiphernodesCommitteeSize::Small.values(), )?; + let circuit = PkAggregationCircuit; circuit.codegen(preset, &sample)? } diff --git a/crates/zk-helpers/src/circuits/computation.rs b/crates/zk-helpers/src/circuits/computation.rs index 1eed828568..d3ae2aa0f5 100644 --- a/crates/zk-helpers/src/circuits/computation.rs +++ b/crates/zk-helpers/src/circuits/computation.rs @@ -11,7 +11,7 @@ //! [`Toml`] and [`Configs`] are the string types used for Prover.toml and configs.nr. /// Variant for input types for DKG. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DkgInputType { /// The input type that generates shares of a secret key using secret sharing. SecretKey, diff --git a/crates/zk-helpers/src/circuits/dkg/mod.rs b/crates/zk-helpers/src/circuits/dkg/mod.rs index deb387206b..364bd7aa79 100644 --- a/crates/zk-helpers/src/circuits/dkg/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/mod.rs @@ -6,4 +6,5 @@ pub mod pk; pub mod share_computation; +pub mod share_decryption; pub mod share_encryption; diff --git a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs index 152601e60e..8eb9a8f426 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/codegen.rs @@ -71,9 +71,8 @@ pub global {}_BIT_PK: u32 = {}; #[cfg(test)] mod tests { use super::*; - use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::codegen::write_artifacts; - use crate::prepare_pk_sample_for_test; + use crate::dkg::pk::PkCircuitInput; use crate::utils::compute_pk_bit; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; @@ -82,18 +81,10 @@ mod tests { #[test] fn test_toml_generation_and_structure() { let (_, dkg_params) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = prepare_pk_sample_for_test( - BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, - ); + let sample = PkCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); let artifacts = PkCircuit - .codegen( - BfvPreset::InsecureThreshold512, - &PkCircuitInput { - public_key: sample.dkg_public_key, - }, - ) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs index 9f9a5a50c2..55bf3d0658 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/computation.rs @@ -153,14 +153,8 @@ impl Computation for Witness { build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; let moduli = dkg_params.moduli(); - let mut pk0is = CrtPolynomial::from_fhe_polynomial(&input.public_key.c.c[0]); - let mut pk1is = CrtPolynomial::from_fhe_polynomial(&input.public_key.c.c[1]); - - pk0is.reverse(); - pk1is.reverse(); - - pk0is.center(&moduli)?; - pk1is.center(&moduli)?; + let mut pk0is = crate::crt::fhe_poly_to_crt_centered(&input.public_key.c.c[0], moduli)?; + let mut pk1is = crate::crt::fhe_poly_to_crt_centered(&input.public_key.c.c[1], moduli)?; let zkp_modulus = &get_zkp_modulus(); diff --git a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs index 6cb4b8a4f6..deb60507dc 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/mod.rs @@ -12,4 +12,3 @@ pub mod sample; pub use circuit::{PkCircuit, PkCircuitInput}; pub use codegen::{generate_configs, generate_toml}; pub use computation::{Bits, Bounds, Configs, PkComputationOutput, Witness}; -pub use sample::{prepare_pk_sample_for_test, PkSample}; diff --git a/crates/zk-helpers/src/circuits/dkg/pk/sample.rs b/crates/zk-helpers/src/circuits/dkg/pk/sample.rs index ed87a576c6..f43f6cb988 100644 --- a/crates/zk-helpers/src/circuits/dkg/pk/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/pk/sample.rs @@ -6,64 +6,36 @@ //! Sample data generation for the pk circuit: committee and DKG public key only. -use crate::ciphernodes_committee::CiphernodesCommittee; -use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::dkg::pk::PkCircuitInput; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; use fhe::bfv::{PublicKey, SecretKey}; use rand::thread_rng; -/// Sample data for the **pk** circuit: committee and DKG public key only. -#[derive(Debug, Clone)] -pub struct PkSample { - /// Committee information. - pub committee: CiphernodesCommittee, - /// DKG BFV public key. - pub dkg_public_key: PublicKey, -} - -impl PkSample { +impl PkCircuitInput { /// Generates sample data for the pk circuit. - pub fn generate(preset: BfvPreset, committee_size: CiphernodesCommitteeSize) -> Self { + pub fn generate_sample(preset: BfvPreset) -> Self { let (_, dkg_params) = build_pair_for_preset(preset).unwrap(); let mut rng = thread_rng(); - let committee = committee_size.values(); let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); Self { - committee, - dkg_public_key, + public_key: dkg_public_key, } } } -/// Prepares a pk sample for testing using a threshold preset (DKG params come from its pair). -pub fn prepare_pk_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, -) -> PkSample { - PkSample::generate(preset, committee) -} - #[cfg(test)] mod tests { - use super::*; - use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::dkg::pk::PkCircuitInput; use e3_fhe_params::BfvPreset; #[test] fn test_generate_pk_sample() { - let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_pk_sample_for_test( - BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, - ); + let sample = PkCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); - assert_eq!(sample.committee.n, committee.n); - assert_eq!(sample.committee.threshold, committee.threshold); - assert_eq!(sample.committee.h, committee.h); - assert_eq!(sample.dkg_public_key.c.c.len(), 2); + assert_eq!(sample.public_key.c.c.len(), 2); } } diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs index c16fc73fbb..f1f3344de0 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/codegen.rs @@ -6,11 +6,11 @@ //! Code generation for the share-computation BFV circuit: Prover.toml and configs.nr. -use crate::bigint_to_field; use crate::circuits::computation::CircuitComputation; use crate::circuits::computation::Computation; use crate::circuits::dkg::share_computation::{ - Bits, ShareComputationCircuit, ShareComputationCircuitInput, ShareComputationOutput, Witness, + utils::parity_matrix_constant_string, Bits, ShareComputationCircuit, + ShareComputationCircuitInput, ShareComputationOutput, Witness, }; use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; use crate::codegen::CodegenConfigs; @@ -20,9 +20,6 @@ use crate::poly_coefficients_to_toml_json; use crate::registry::Circuit; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use e3_parity_matrix::{build_generator_matrix, null_space, ParityMatrixConfig}; -use num_bigint::BigInt; -use num_bigint::BigUint; use serde_json; /// Implementation of [`CircuitCodegen`] for [`ShareComputationCircuit`]. @@ -77,50 +74,6 @@ pub fn generate_toml( Ok(toml::to_string(&json)?) } -/// Builds the PARITY_MATRIX constant string for Noir (one matrix per modulus via null_space). -fn parity_matrix_constant_string( - threshold_params: &std::sync::Arc, - n_parties: usize, - threshold: usize, -) -> Result { - let moduli = threshold_params.moduli(); - let mut parity_matrix_strings = Vec::with_capacity(moduli.len()); - - for &qi in moduli { - let q = BigUint::from(qi); - let g = build_generator_matrix(&ParityMatrixConfig { - q: q.clone(), - t: threshold, - n: n_parties, - }) - .map_err(|e| { - CircuitsErrors::Sample(format!("Failed to build generator matrix: {:?}", e)) - })?; - let h_mod = null_space(&g, &q).map_err(|e| { - CircuitsErrors::Sample(format!("Failed to compute null space: {:?}", e)) - })?; - - let mut modulus_rows = Vec::new(); - for row in h_mod.data() { - let row_values: Vec = row - .iter() - .map(|val| { - let bigint_val = BigInt::from(val.clone()); - let field_val = bigint_to_field(&bigint_val); - field_val.to_string() - }) - .collect(); - modulus_rows.push(format!("[{}]", row_values.join(", "))); - } - parity_matrix_strings.push(format!("[\n {}]", modulus_rows.join(",\n "))); - } - - Ok(format!( - "pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [\n {}];", - parity_matrix_strings.join(",\n ") - )) -} - /// Builds the configs.nr string (N, L, parity matrix, bit parameters, configs) for the Noir prover. /// /// `n_parties` and `threshold` are used to build the parity matrix (Reed–Solomon generator null space) @@ -195,36 +148,19 @@ mod tests { use crate::codegen::write_artifacts; use crate::computation::DkgInputType; use crate::Circuit; - use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use e3_fhe_params::BfvPreset; use tempfile::TempDir; - fn share_computation_input_from_sample( - sample: &ShareComputationSample, - dkg_input_type: DkgInputType, - ) -> ShareComputationCircuitInput { - ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample.parity_matrix.clone(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - } - } - #[test] fn test_toml_generation_and_structure() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); - let artifacts = ShareComputationCircuit - .codegen(BfvPreset::InsecureThreshold512, &input) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -257,7 +193,7 @@ mod tests { assert!(configs_path.exists()); let configs_content = std::fs::read_to_string(&configs_path).unwrap(); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let prefix = ::PREFIX; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs index 304e193f6d..d60d10212a 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/computation.rs @@ -267,33 +267,16 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; use crate::dkg::share_computation::ShareComputationCircuitInput; - use crate::{prepare_share_computation_sample_for_test, ShareComputationSample}; use e3_fhe_params::BfvPreset; - fn share_computation_input_from_sample( - sample: &ShareComputationSample, - dkg_input_type: DkgInputType, - ) -> ShareComputationCircuitInput { - ShareComputationCircuitInput { - dkg_input_type, - secret: sample.secret.clone(), - secret_sss: sample.secret_sss.clone(), - parity_matrix: sample.parity_matrix.clone(), - n_parties: sample.committee.n as u32, - threshold: sample.committee.threshold as u32, - } - } - #[test] fn test_bound_and_bits_computation_consistency() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - - let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let expected_sk_bits = calculate_bit_width(BigInt::from(bounds.sk_bound.clone())); @@ -302,14 +285,12 @@ mod tests { #[test] fn test_witness_smudging_noise_secret_consistency() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SmudgingNoise, ); - - let input = share_computation_input_from_sample(&sample, DkgInputType::SmudgingNoise); - let witness = Witness::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let degree = witness.secret_crt.limb(0).coefficients().len(); let num_moduli = witness.secret_crt.limbs.len(); for coeff_idx in 0..degree { @@ -327,14 +308,13 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let sample = prepare_share_computation_sample_for_test( + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, ); - let input = share_computation_input_from_sample(&sample, DkgInputType::SecretKey); - let constants = Configs::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs index eaee5a4f5c..d1105ce5d0 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/mod.rs @@ -8,7 +8,8 @@ pub mod circuit; pub mod codegen; pub mod computation; pub mod sample; +pub mod utils; pub use circuit::{ShareComputationCircuit, ShareComputationCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareComputationOutput, Witness}; -pub use sample::{prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample}; +pub use sample::SecretShares; diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs index 01b97283d5..ad5fc4732e 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/sample.rs @@ -7,75 +7,41 @@ //! Sample data generation for the share-computation circuit: committee, DKG public key, //! secret (SK or smudging noise) in CRT form, Shamir shares, and parity matrices. -use crate::ciphernodes_committee::CiphernodesCommittee; use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::circuits::dkg::share_computation::utils::compute_parity_matrix; use crate::computation::DkgInputType; +use crate::dkg::share_computation::ShareComputationCircuitInput; use crate::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use e3_parity_matrix::build_generator_matrix; -use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; use e3_polynomial::CrtPolynomial; -use fhe::bfv::{PublicKey, SecretKey}; +use fhe::bfv::SecretKey; use fhe::trbfv::{ShareManager, TRBFV}; use num_bigint::BigInt; -use num_bigint::BigUint; use rand::thread_rng; -/// Shamir secret shares: one limb per CRT modulus (rows = parties, cols = polynomial coefficients). pub type SecretShares = Vec>; -/// Sample data for the **share-computation** circuit: committee, DKG public key, secret in CRT form, -/// Shamir shares, and parity matrices (secret-key or smudging-noise). -#[derive(Debug, Clone)] -pub struct ShareComputationSample { - /// Committee information. - pub committee: CiphernodesCommittee, - /// DKG BFV public key. - pub dkg_public_key: PublicKey, - /// Secret in CRT form (SK or smudging noise). - pub secret: CrtPolynomial, - /// Secret shares (one [`ndarray::Array2`] per modulus). - pub secret_sss: SecretShares, - /// Parity check matrix per modulus (null space of generator). - pub parity_matrix: Vec, -} - -impl ShareComputationSample { +impl ShareComputationCircuitInput { /// Generates sample data for the share-computation circuit. - pub fn generate( + pub fn generate_sample( preset: BfvPreset, committee_size: CiphernodesCommitteeSize, dkg_input_type: DkgInputType, - num_ciphertexts: u128, // z in the search defaults - lambda: u32, ) -> Self { - let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap(); - + let (threshold_params, _) = build_pair_for_preset(preset).unwrap(); + let sd = preset.search_defaults().unwrap(); let mut rng = thread_rng(); let committee = committee_size.values(); - let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); - let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); - let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); let mut share_manager = ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); - // Parity check matrix (null space of generator) per modulus: [L][N_PARTIES-T][N_PARTIES+1]. - let mut parity_matrix = Vec::with_capacity(threshold_params.moduli().len()); - for &qi in threshold_params.moduli() { - let q = BigUint::from(qi); - let g = build_generator_matrix(&ParityMatrixConfig { - q: q.clone(), - t: committee.threshold, - n: committee.n, - }) - .unwrap(); - let h = null_space(&g, &q).unwrap(); - parity_matrix.push(h); - } + let parity_matrix = + compute_parity_matrix(threshold_params.moduli(), committee.n, committee.threshold) + .unwrap_or_else(|e| panic!("Failed to compute parity matrix: {}", e)); let (secret, secret_sss) = match dkg_input_type { DkgInputType::SecretKey => { @@ -107,7 +73,7 @@ impl ShareComputationSample { } DkgInputType::SmudgingNoise => { let esi_coeffs = trbfv - .generate_smudging_error(num_ciphertexts as usize, lambda as usize, &mut rng) + .generate_smudging_error(committee.n as usize, sd.lambda as usize, &mut rng) .map_err(|e| { CircuitsErrors::Sample(format!( "Failed to generate smudging error: {:?}", @@ -136,8 +102,9 @@ impl ShareComputationSample { }; Self { - committee, - dkg_public_key, + dkg_input_type, + n_parties: committee.n as u32, + threshold: committee.threshold as u32, secret, secret_sss, parity_matrix, @@ -145,59 +112,39 @@ impl ShareComputationSample { } } -/// Prepares a share-computation sample for testing using a threshold preset. -pub fn prepare_share_computation_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, - dkg_input_type: DkgInputType, -) -> ShareComputationSample { - let defaults = preset.search_defaults().unwrap(); - - ShareComputationSample::generate( - preset, - committee, - dkg_input_type, - defaults.z, - defaults.lambda, - ) -} - #[cfg(test)] mod tests { - use super::*; use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; + use crate::dkg::share_computation::ShareComputationCircuitInput; use e3_fhe_params::BfvPreset; #[test] fn test_generate_secret_key_sample() { - let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_share_computation_sample_for_test( + let committee_size = CiphernodesCommitteeSize::Small; + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee_size, DkgInputType::SecretKey, ); - - assert_eq!(sample.committee.n, committee.n); - assert_eq!(sample.committee.threshold, committee.threshold); - assert_eq!(sample.committee.h, committee.h); - assert_eq!(sample.dkg_public_key.c.c.len(), 2); + assert_eq!(sample.n_parties, committee_size.values().n as u32); + assert_eq!(sample.threshold, committee_size.values().threshold as u32); + assert_eq!(sample.dkg_input_type, DkgInputType::SecretKey); assert_eq!(sample.secret_sss.len(), 2); assert_eq!(sample.secret.limbs.len(), 2); } #[test] fn test_generate_smudging_noise_sample() { - let committee = CiphernodesCommitteeSize::Small.values(); - let sample = prepare_share_computation_sample_for_test( + let committee_size = CiphernodesCommitteeSize::Small; + let sample = ShareComputationCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, - CiphernodesCommitteeSize::Small, + committee_size, DkgInputType::SmudgingNoise, ); - - assert_eq!(sample.committee.n, committee.n); - assert_eq!(sample.committee.threshold, committee.threshold); - assert_eq!(sample.dkg_public_key.c.c.len(), 2); + assert_eq!(sample.n_parties, committee_size.values().n as u32); + assert_eq!(sample.threshold, committee_size.values().threshold as u32); + assert_eq!(sample.dkg_input_type, DkgInputType::SmudgingNoise); assert_eq!(sample.secret_sss.len(), 2); assert_eq!(sample.secret.limbs.len(), 2); } diff --git a/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs new file mode 100644 index 0000000000..a9f280fab8 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_computation/utils.rs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Shared utilities for the share-computation circuit (e.g. parity matrix). + +use crate::utils::bigint_to_field; +use crate::CircuitsErrors; +use e3_parity_matrix::build_generator_matrix; +use e3_parity_matrix::{null_space, ParityMatrix, ParityMatrixConfig}; +use num_bigint::{BigInt, BigUint}; + +/// Computes the parity check matrix (null space of the Reed–Solomon generator) per modulus. +/// +/// Returns one `ParityMatrix` per modulus in `moduli`, each of shape `[n_parties - threshold][n_parties + 1]`. +pub fn compute_parity_matrix( + moduli: &[u64], + n_parties: usize, + threshold: usize, +) -> Result, String> { + let mut parity_matrix = Vec::with_capacity(moduli.len()); + for &qi in moduli { + let q = BigUint::from(qi); + let g = build_generator_matrix(&ParityMatrixConfig { + q: q.clone(), + t: threshold, + n: n_parties, + }) + .map_err(|e| format!("Failed to build generator matrix: {:?}", e))?; + let h = null_space(&g, &q).map_err(|e| format!("Failed to compute null space: {:?}", e))?; + parity_matrix.push(h); + } + Ok(parity_matrix) +} + +/// Builds the PARITY_MATRIX constant string for Noir (one matrix per modulus via null_space). +pub fn parity_matrix_constant_string( + threshold_params: &std::sync::Arc, + n_parties: usize, + threshold: usize, +) -> Result { + let parity_matrix = compute_parity_matrix(threshold_params.moduli(), n_parties, threshold) + .map_err(|e| CircuitsErrors::Sample(e))?; + + let parity_matrix_strings: Vec = parity_matrix + .iter() + .map(|h_mod| { + let modulus_rows: Vec = h_mod + .data() + .iter() + .map(|row| { + let row_values: Vec = row + .iter() + .map(|val| { + let bigint_val = BigInt::from(val.clone()); + let field_val = bigint_to_field(&bigint_val); + field_val.to_string() + }) + .collect(); + format!("[{}]", row_values.join(", ")) + }) + .collect(); + format!("[\n {}]", modulus_rows.join(",\n ")) + }) + .collect(); + + Ok(format!( + "pub global PARITY_MATRIX: [[[Field; N_PARTIES + 1]; N_PARTIES - T]; L_THRESHOLD] = [\n {}];", + parity_matrix_strings.join(",\n ") + )) +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs new file mode 100644 index 0000000000..bec0bf27d1 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/circuit.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Circuit definition and input type for the share-decryption ZK circuit (CIRCUIT 4a/4b). + +use crate::computation::DkgInputType; +use crate::registry::Circuit; +use e3_fhe_params::ParameterType; +use fhe::bfv::Ciphertext; +use fhe::bfv::SecretKey; + +/// Share-decryption circuit: proves correct decryption of H honest parties' ciphertexts under the DKG secret key. +#[derive(Debug)] +pub struct ShareDecryptionCircuit; + +impl Circuit for ShareDecryptionCircuit { + const NAME: &'static str = "share-decryption"; + const PREFIX: &'static str = "SHARE_DECRYPTION"; + const SUPPORTED_PARAMETER: ParameterType = ParameterType::DKG; + /// None: circuit accepts runtime-varying input type (SecretKey or SmudgingNoise). + const DKG_INPUT_TYPE: Option = None; +} + +/// Input to the share-decryption circuit: secret key and honest parties' ciphertexts. +pub struct ShareDecryptionCircuitInput { + /// DKG secret key used to decrypt (private witness). + pub secret_key: SecretKey, + /// Ciphertexts from H honest parties: [party_idx][mod_idx] (one ciphertext per party per TRBFV modulus). + pub honest_ciphertexts: Vec>, +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs new file mode 100644 index 0000000000..990c1dbd83 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/codegen.rs @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Code generation for the share-decryption BFV circuit: Prover.toml and configs.nr. + +use crate::circuits::computation::CircuitComputation; +use crate::circuits::dkg::share_decryption::Configs; +use crate::circuits::dkg::share_decryption::ShareDecryptionCircuit; +use crate::circuits::dkg::share_decryption::ShareDecryptionCircuitInput; +use crate::circuits::dkg::share_decryption::ShareDecryptionOutput; +use crate::circuits::dkg::share_decryption::Witness; +use crate::circuits::{Artifacts, CircuitCodegen, CircuitsErrors, CodegenToml}; +use crate::codegen::CodegenConfigs; +use crate::computation::Computation; +use crate::registry::Circuit; +use e3_fhe_params::BfvPreset; + +/// Implementation of [`CircuitCodegen`] for [`ShareDecryptionCircuit`]. +impl CircuitCodegen for ShareDecryptionCircuit { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn codegen(&self, preset: Self::Preset, input: &Self::Input) -> Result { + let ShareDecryptionOutput { witness, .. } = ShareDecryptionCircuit::compute(preset, input)?; + + let toml = generate_toml(&witness)?; + let configs = Configs::compute(preset, input)?; + let configs_str = generate_configs(preset, &configs); + + Ok(Artifacts { + toml, + configs: configs_str, + }) + } +} + +/// Serializes the witness to TOML string for the Noir prover (Prover.toml). +pub fn generate_toml(witness: &Witness) -> Result { + let json = witness + .to_json() + .map_err(|e| CircuitsErrors::SerdeJson(e))?; + + Ok(toml::to_string(&json)?) +} + +/// Builds the configs.nr string (N, L, bit parameters, and ShareDecryptionConfigs) for the Noir prover. +pub fn generate_configs(preset: BfvPreset, configs: &Configs) -> CodegenConfigs { + let prefix = ::PREFIX; + + format!( + r#"pub global N: u32 = {}; +pub global L: u32 = {}; + +/************************************ +------------------------------------- +share_decryption_sk (CIRCUIT 4a - BFV DECRYPTION SK) +share_decryption_e_sm (CIRCUIT 4b - BFV DECRYPTION E_SM) +------------------------------------- +************************************/ + +pub global {}_BIT_MSG: u32 = {}; +"#, + preset.dkg_counterpart().unwrap().metadata().degree, + preset.dkg_counterpart().unwrap().metadata().num_moduli, + prefix, + configs.bits.msg_bit, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::circuits::dkg::share_decryption::{Configs, ShareDecryptionCircuitInput}; + use crate::computation::{Computation, DkgInputType}; + use crate::Circuit; + use e3_fhe_params::BfvPreset; + + #[test] + fn test_toml_generation_and_structure() { + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + + let artifacts = ShareDecryptionCircuit + .codegen(BfvPreset::InsecureThreshold512, &sample) + .unwrap(); + + let parsed: toml::Value = artifacts.toml.parse().unwrap(); + assert!(parsed.get("expected_commitments").is_some()); + assert!(parsed.get("decrypted_shares").is_some()); + } + + #[test] + fn test_configs_generation_contains_expected() { + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + + let artifacts = ShareDecryptionCircuit + .codegen(BfvPreset::InsecureThreshold512, &sample) + .unwrap(); + + let configs = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + let prefix = ::PREFIX; + assert!(artifacts + .configs + .contains(format!("{}_BIT_MSG: u32 = {}", prefix, configs.bits.msg_bit).as_str())); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs new file mode 100644 index 0000000000..b446530d98 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/computation.rs @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Computation types for the share-decryption circuit: configs, bounds, bit widths, and witness. +//! +//! [`Configs`], [`Bounds`], [`Bits`], and [`Witness`] are produced from BFV parameters +//! and (for witness) honest ciphertexts and secret key. Witness values are normalized for the ZKP +//! field so the Noir circuit's range checks and commitment checks succeed. + +use crate::circuits::commitments::compute_share_encryption_commitment_from_message; +use crate::dkg::share_decryption::ShareDecryptionCircuit; +use crate::dkg::share_decryption::ShareDecryptionCircuitInput; +use crate::CircuitsErrors; +use crate::{bigint_2d_to_json_values, calculate_bit_width, poly_coefficients_to_toml_json}; +use crate::{CircuitComputation, Computation}; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use e3_polynomial::Polynomial; +use fhe_traits::FheDecrypter; +use num_bigint::BigInt; +use serde::{Deserialize, Serialize}; +use std::ops::Deref; + +/// Output of [`CircuitComputation::compute`] for [`ShareDecryptionCircuit`]: bounds, bit widths, and witness. +#[derive(Debug)] +pub struct ShareDecryptionOutput { + /// Coefficient bounds used to derive bit widths. + pub bounds: Bounds, + /// Bit widths used by the Noir prover for packing. + pub bits: Bits, + /// Witness data for the share-decryption circuit. + pub witness: Witness, +} + +/// Implementation of [`CircuitComputation`] for [`ShareDecryptionCircuit`]. +impl CircuitComputation for ShareDecryptionCircuit { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Output = ShareDecryptionOutput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let bounds = Bounds::compute(preset, input)?; + let bits = Bits::compute(preset, &bounds)?; + let witness = Witness::compute(preset, input)?; + + Ok(ShareDecryptionOutput { + bounds, + bits, + witness, + }) + } +} + +/// Global configs for the share-decryption circuit: degree, number of moduli, honest parties, bits, and bounds. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Configs { + /// Polynomial degree (N). + pub n: usize, + /// Number of CRT moduli (L). + pub l: usize, + /// Number of honest parties (H). + pub h: usize, + pub bits: Bits, + pub bounds: Bounds, +} + +/// Bit widths used by the Noir prover (e.g. for packing message coefficients). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bits { + /// Bit width for plaintext/message coefficients (in [0, t)). + pub msg_bit: u32, +} + +/// Coefficient bounds for the share-decryption circuit (currently empty; bounds are derived from plaintext modulus). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Bounds {} + +/// Witness data for the share-decryption circuit: expected commitments and decrypted shares. +/// +/// Coefficients are reduced to the ZKP field modulus for serialization. The circuit verifies +/// that decrypted shares match the expected commitments from the share-encryption circuit. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Witness { + /// Expected message commitments from share-encryption (CIRCUIT 3) for H honest parties: [party_idx][mod_idx]. + pub expected_commitments: Vec>, // [H][L] + /// Decrypted share coefficients per party and modulus: [party_idx][mod_idx][coeff_idx]. + pub decrypted_shares: Vec>>, // [H][L][N] +} + +impl Computation for Configs { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (_, dkg_params) = build_pair_for_preset(preset) + .map_err(|e| crate::utils::ZkHelpersUtilsError::ParseBound(e.to_string()))?; + + let n = dkg_params.degree() as usize; + let l = dkg_params.moduli().len(); + let h = input.honest_ciphertexts.len(); + + let bounds = Bounds::compute(preset, &input)?; + let bits = Bits::compute(preset, &bounds)?; + + Ok(Configs { + n, + l, + h, + bits, + bounds, + }) + } +} + +impl Computation for Bits { + type Preset = BfvPreset; + type Input = Bounds; + type Error = crate::utils::ZkHelpersUtilsError; + + fn compute(preset: Self::Preset, _: &Self::Input) -> Result { + let (_, dkg_params) = build_pair_for_preset(preset) + .map_err(|e| crate::utils::ZkHelpersUtilsError::ParseBound(e.to_string()))?; + + Ok(Bits { + msg_bit: calculate_bit_width(BigInt::from(dkg_params.plaintext())), + }) + } +} + +impl Computation for Bounds { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(_: Self::Preset, _: &Self::Input) -> Result { + Ok(Bounds {}) + } +} + +impl Computation for Witness { + type Preset = BfvPreset; + type Input = ShareDecryptionCircuitInput; + type Error = CircuitsErrors; + + fn compute(preset: Self::Preset, input: &Self::Input) -> Result { + let (threshold_params, dkg_params) = + build_pair_for_preset(preset).map_err(|e| CircuitsErrors::Sample(e.to_string()))?; + let threshold_l = threshold_params.moduli().len(); + + let mut expected_commitments: Vec> = Vec::new(); + let mut decrypted_shares: Vec>> = Vec::new(); + + let msg_bit = calculate_bit_width(BigInt::from(dkg_params.plaintext())); + + // Decrypt each ciphertext and compute its commitment + for party_cts in input.honest_ciphertexts.iter() { + if party_cts.len() < threshold_l { + return Err(CircuitsErrors::Other(format!( + "honest_ciphertexts party has {} ciphertexts but threshold_l is {}; \ + each party must have at least threshold_l ciphertexts", + party_cts.len(), + threshold_l + ))); + } + let mut party_commitments = Vec::with_capacity(threshold_l); + let mut party_shares = Vec::with_capacity(threshold_l); + for mod_idx in 0..threshold_l { + // Decrypt the ciphertext to get the plaintext share + let decrypted_pt = input.secret_key.try_decrypt(&party_cts[mod_idx]).unwrap(); + let share_coeffs = decrypted_pt.value.deref().to_vec(); + party_commitments.push(compute_share_encryption_commitment_from_message( + &Polynomial::from_u64_vector(share_coeffs.clone()), + msg_bit, + )); + party_shares.push( + share_coeffs + .iter() + .map(|c| BigInt::from(*c)) + .collect::>(), + ); + } + expected_commitments.push(party_commitments); + decrypted_shares.push(party_shares); + } + + Ok(Witness { + expected_commitments, + decrypted_shares, + }) + } + + // Used as witness for Nargo execution. + /// Serializes witness so that `decrypted_shares` matches Noir's `[[Polynomial; L]; H]`: + /// each polynomial is `{ "coefficients": [string, ...] }`. + fn to_json(&self) -> serde_json::Result { + let expected_commitments = bigint_2d_to_json_values(&self.expected_commitments); + let decrypted_shares: Vec> = self + .decrypted_shares + .iter() + .map(|party_shares| { + party_shares + .iter() + .map(|share| poly_coefficients_to_toml_json(share)) + .collect::>() + }) + .collect::>(); + + let json = serde_json::json!({ + "expected_commitments": expected_commitments, + "decrypted_shares": decrypted_shares, + }); + + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::ciphernodes_committee::CiphernodesCommitteeSize; + use crate::computation::DkgInputType; + use e3_fhe_params::BfvPreset; + + #[test] + fn test_bound_and_bits_computation_consistency() { + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); + + let (_, dkg_params) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); + let expected_msg_bit = calculate_bit_width(BigInt::from(dkg_params.plaintext())); + assert_eq!(bits.msg_bit, expected_msg_bit); + } + + #[test] + fn test_constants_json_roundtrip() { + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + + let json = constants.to_json().unwrap(); + let decoded: Configs = serde_json::from_value(json).unwrap(); + + assert_eq!(decoded.n, constants.n); + assert_eq!(decoded.l, constants.l); + assert_eq!(decoded.h, constants.h); + assert_eq!(decoded.bits, constants.bits); + assert_eq!(decoded.bounds, constants.bounds); + } + + #[test] + fn test_witness_decryption_consistency() { + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); + + // Witness should have one row per honest party + assert_eq!( + witness.expected_commitments.len(), + sample.honest_ciphertexts.len() + ); + assert_eq!( + witness.decrypted_shares.len(), + sample.honest_ciphertexts.len() + ); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/mod.rs new file mode 100644 index 0000000000..5313a42a56 --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/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. + +//! Share-decryption circuit: proves correct decryption of honest parties' ciphertexts under the DKG secret key (CIRCUIT 4a/4b). + +pub mod circuit; +pub mod codegen; +pub mod computation; +pub mod sample; +pub use circuit::{ShareDecryptionCircuit, ShareDecryptionCircuitInput}; +pub use computation::{Bits, Bounds, Configs, ShareDecryptionOutput, Witness}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs new file mode 100644 index 0000000000..3030019e0c --- /dev/null +++ b/crates/zk-helpers/src/circuits/dkg/share_decryption/sample.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Sample data generation for the share-decryption circuit: honest ciphertexts, sum ciphertexts, secret key, and message. + +use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::circuits::dkg::share_decryption::circuit::ShareDecryptionCircuitInput; +use crate::computation::DkgInputType; +use crate::CircuitsErrors; +use e3_fhe_params::build_pair_for_preset; +use e3_fhe_params::BfvPreset; +use fhe::bfv::Ciphertext; +use fhe::bfv::Encoding; +use fhe::bfv::Plaintext; +use fhe::bfv::{PublicKey, SecretKey}; +use fhe::trbfv::{ShareManager, TRBFV}; +use fhe_traits::FheEncoder; +use fhe_traits::FheEncrypter; +use rand::thread_rng; + +impl ShareDecryptionCircuitInput { + /// Generates sample data for the share-decryption circuit (decrypts a sum of honest ciphertexts under DKG secret key). + pub fn generate_sample( + preset: BfvPreset, + committee: CiphernodesCommitteeSize, + dkg_input_type: DkgInputType, + ) -> Self { + let (threshold_params, dkg_params) = build_pair_for_preset(preset).unwrap(); + let sd = preset.search_defaults().unwrap(); + + let mut rng = thread_rng(); + let committee = committee.values(); + + let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); + let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); + + let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) + .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); + let mut share_manager = + ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); + + let mut honest_ciphertexts: Vec> = Vec::new(); + let num_honest = committee.n; + for _ in 0..num_honest { + let mut party_cts = Vec::new(); + for _ in 0..threshold_params.moduli().len() { + let share_row = match dkg_input_type { + DkgInputType::SecretKey => { + let threshold_secret_key = SecretKey::random(&threshold_params, &mut rng); + + let sk_poly = share_manager + .coeffs_to_poly_level0(threshold_secret_key.coeffs.clone().as_ref()) + .unwrap(); + + let sk_sss_u64 = share_manager + .generate_secret_shares_from_poly(sk_poly.clone(), &mut rng) + .unwrap(); + + sk_sss_u64[0].row(0).to_vec() + } + DkgInputType::SmudgingNoise => { + let esi_coeffs = trbfv + .generate_smudging_error(sd.z as usize, sd.lambda as usize, &mut rng) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate smudging error: {:?}", + e + )) + }) + .unwrap(); + let esi_poly = share_manager.bigints_to_poly(&esi_coeffs).unwrap(); + let esi_sss_u64 = share_manager + .generate_secret_shares_from_poly(esi_poly.clone(), &mut rng.clone()) + .map_err(|e| { + CircuitsErrors::Sample(format!( + "Failed to generate error shares: {:?}", + e + )) + }) + .unwrap(); + + esi_sss_u64[0].row(0).to_vec() + } + }; + + let pt = Plaintext::try_encode(&share_row, Encoding::poly(), &dkg_params).unwrap(); + + let ct = dkg_public_key.try_encrypt(&pt, &mut rng).unwrap(); + party_cts.push(ct); + } + honest_ciphertexts.push(party_cts); + } + + ShareDecryptionCircuitInput { + honest_ciphertexts, + secret_key: dkg_secret_key, + } + } +} + +#[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 = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SecretKey, + ); + + assert_eq!(sample.honest_ciphertexts.len(), committee.n); + assert_eq!( + sample.secret_key.coeffs.len(), + BfvPreset::InsecureThreshold512.metadata().degree + ); + } + + #[test] + fn test_generate_smudging_noise_sample() { + let committee = CiphernodesCommitteeSize::Small.values(); + let sample = ShareDecryptionCircuitInput::generate_sample( + BfvPreset::InsecureThreshold512, + CiphernodesCommitteeSize::Small, + DkgInputType::SmudgingNoise, + ); + + assert_eq!(sample.honest_ciphertexts.len(), committee.n); + assert_eq!( + sample.secret_key.coeffs.len(), + BfvPreset::InsecureThreshold512.metadata().degree + ); + } +} diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs index 33aacc1a9c..06c000c8b3 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/codegen.rs @@ -191,36 +191,21 @@ mod tests { use crate::circuits::dkg::share_encryption::{Bounds, ShareEncryptionCircuitInput}; use crate::computation::Computation; use crate::computation::DkgInputType; - use crate::dkg::share_encryption::sample::prepare_share_encryption_sample_for_test; - use crate::dkg::share_encryption::ShareEncryptionSample; use crate::Circuit; use e3_fhe_params::BfvPreset; - fn share_encryption_input_from_sample( - sample: &ShareEncryptionSample, - ) -> ShareEncryptionCircuitInput { - ShareEncryptionCircuitInput { - plaintext: sample.plaintext.clone(), - ciphertext: sample.ciphertext.clone(), - public_key: sample.public_key.clone(), - secret_key: sample.secret_key.clone(), - u_rns: sample.u_rns.clone(), - e0_rns: sample.e0_rns.clone(), - e1_rns: sample.e1_rns.clone(), - } - } - #[test] fn test_toml_generation_and_structure() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let artifacts = ShareEncryptionCircuit - .codegen(BfvPreset::InsecureThreshold512, &input) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -232,18 +217,20 @@ mod tests { #[test] fn test_configs_generation_contains_expected() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); let artifacts = ShareEncryptionCircuit - .codegen(BfvPreset::InsecureThreshold512, &input) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = crate::circuits::dkg::share_encryption::Bits::compute( BfvPreset::InsecureThreshold512, &bounds, diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs index 8576487235..07ad48542d 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/computation.rs @@ -15,10 +15,12 @@ use crate::circuits::commitments::{ }; use std::ops::Deref; +use crate::crt::compute_k0is; use crate::dkg::share_encryption::ShareEncryptionCircuit; use crate::dkg::share_encryption::ShareEncryptionCircuitInput; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; +use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::utils::{compute_msg_bit, compute_pk_bit}; use crate::CircuitsErrors; use crate::{calculate_bit_width, crt_polynomial_to_toml_json}; @@ -34,7 +36,6 @@ use fhe_math::zq::Modulus; use itertools::izip; use num_bigint::ToBigInt; use num_bigint::{BigInt, BigUint}; -use num_traits::Zero; use num_traits::{Signed, ToPrimitive}; use rayon::iter::ParallelIterator; use rayon::prelude::ParallelBridge; @@ -159,17 +160,7 @@ impl Computation for Configs { let q_mod_t = center(&reduce(&modulus, &t), &t); let q_mod_t_mod_p = reduce(&q_mod_t, &p); - let mut k0is: Vec = Vec::new(); - - for qi in ctx.moduli_operators() { - let k0qi = BigInt::from(qi.inv(qi.neg(dkg_params.plaintext())).ok_or_else(|| { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - })?); - - k0is.push(k0qi.to_u64().unwrap_or(0)); - } + let k0is = compute_k0is(&moduli, dkg_params.plaintext())?; let bounds = Bounds::compute(preset, input)?; let bits = Bits::compute(preset, &bounds)?; @@ -275,29 +266,25 @@ impl Computation for Bounds { }; // Calculate bounds for each CRT basis - let _num_moduli = ctx.moduli().len(); + let moduli: Vec = ctx + .moduli_operators() + .into_iter() + .map(|q| q.modulus()) + .collect(); + let k0is = compute_k0is(&moduli, dkg_params.plaintext())?; + let mut pk_bounds: Vec = Vec::new(); let mut r1_low_bounds: Vec = Vec::new(); let mut r1_up_bounds: Vec = Vec::new(); let mut r2_bounds: Vec = Vec::new(); let mut p1_bounds: Vec = Vec::new(); let mut p2_bounds: Vec = Vec::new(); - let mut moduli: Vec = Vec::new(); - let mut k0is: Vec = Vec::new(); - for qi in ctx.moduli_operators() { + for (i, qi) in ctx.moduli_operators().into_iter().enumerate() { let qi_bigint = BigInt::from(qi.modulus()); let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); - moduli.push(qi.modulus()); - - // Calculate k0qi for bounds - let k0qi = BigInt::from(qi.inv(qi.neg(dkg_params.plaintext())).ok_or_else(|| { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - })?); - k0is.push(k0qi.to_u64().unwrap_or(0)); + let k0qi = BigInt::from(k0is[i]); // PK and R2 bounds (same as qi_bound) pk_bounds.push(qi_bound.clone()); @@ -454,11 +441,7 @@ impl Computation for Witness { pk0.change_representation(Representation::PowerBasis); pk1.change_representation(Representation::PowerBasis); - // Create cyclotomic polynomial x^N + 1 - let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; - - cyclo[0] = BigInt::from(1u64); // x^N term - cyclo[n as usize] = BigInt::from(1u64); // x^0 term + let cyclo = cyclotomic_polynomial(n); let ct0_coeffs = ct0.coefficients(); let ct1_coeffs = ct1.coefficients(); @@ -585,88 +568,11 @@ impl Computation for Witness { }; assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i - let mut ct0i_hat_mod_rqi = Polynomial::new(ct0i_hat.clone()); - - ct0i_hat_mod_rqi = ct0i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - - ct0i_hat_mod_rqi.reduce(&qi_bigint); - ct0i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct0i, &ct0i_hat_mod_rqi); - - // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial - let ct0i_poly = ct0i.clone(); let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let ct0i_minus_ct0i_hat = ct0i_poly.sub(&ct0i_hat_poly).coefficients().to_vec(); - assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - - let mut ct0i_minus_ct0i_hat_mod_zqi = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - - ct0i_minus_ct0i_hat_mod_zqi.reduce(&qi_bigint); - ct0i_minus_ct0i_hat_mod_zqi.center(&qi_bigint); - - // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = ct0i_minus_ct0i_hat_mod_zqi.clone(); - let cyclo_poly = Polynomial::new(cyclo.clone()); - let (r2i_poly, r2i_rem_poly) = ct0i_minus_ct0i_hat_poly.div(&cyclo_poly).unwrap(); - let r2i = r2i_poly.coefficients().to_vec(); - let r2i_rem = r2i_rem_poly.coefficients().to_vec(); - assert!(r2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r2i.len() as u64) - 1, n - 2); // Order(r2i) = N - 2 - - // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi - let r2i_poly = Polynomial::new(r2i.clone()); - let r2i_times_cyclo = r2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - - let mut r2i_times_cyclo_mod_zqi = Polynomial::new(r2i_times_cyclo.clone()); - - r2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - r2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); - assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let r1i_num = ct0i_minus_ct0i_hat_poly - .sub(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((r1i_num.len() as u64) - 1, 2 * (n - 1)); - - let r1i_num_poly = Polynomial::new(r1i_num.clone()); - let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); - let (r1i_poly, r1i_rem_poly) = r1i_num_poly.div(&qi_poly).unwrap(); + let (r1i_poly, r2i_poly) = + decompose_residue(&ct0i, &ct0i_hat_poly, &qi_bigint, &cyclo, n); let r1i = r1i_poly.coefficients().to_vec(); - let r1i_rem = r1i_rem_poly.coefficients().to_vec(); - assert!(r1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r1i.len() as u64) - 1, 2 * (n - 1)); // Order(r1i) = 2*(N-1) - let r1i_poly_check = Polynomial::new(r1i.clone()); - assert_eq!( - &r1i_num, - &r1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p - let r1i_poly = Polynomial::new(r1i.clone()); - let r1i_times_qi = r1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let r1i_times_qi_poly = Polynomial::new(r1i_times_qi.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let mut ct0i_calculated = ct0i_hat_poly - .add(&r1i_times_qi_poly) - .add(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct0i_calculated.is_empty() && ct0i_calculated[0].is_zero() { - ct0i_calculated.remove(0); - } - - assert_eq!(&ct0i, &Polynomial::new(ct0i_calculated.clone())); + let r2i = r2i_poly.coefficients().to_vec(); // --------------------------------------------------- ct1i --------------------------------------------------- @@ -682,87 +588,12 @@ impl Computation for Witness { }; assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i - let mut ct1i_hat_mod_rqi = Polynomial::new(ct1i_hat.clone()); - - ct1i_hat_mod_rqi = ct1i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - ct1i_hat_mod_rqi.reduce(&qi_bigint); - ct1i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct1i, &ct1i_hat_mod_rqi); - - // Compute p2i numerator = ct1i - ct1i_hat - let ct1i_poly = ct1i.clone(); let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let ct1i_minus_ct1i_hat = ct1i_poly.sub(&ct1i_hat_poly).coefficients().to_vec(); - assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - let mut ct1i_minus_ct1i_hat_mod_zqi = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - - ct1i_minus_ct1i_hat_mod_zqi.reduce(&qi_bigint); - ct1i_minus_ct1i_hat_mod_zqi.center(&qi_bigint); - - // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, - // and reduce/center the resulting coefficients to produce: - // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = ct1i_minus_ct1i_hat_mod_zqi.clone(); - let (p2i_poly, p2i_rem_poly) = - ct1i_minus_ct1i_hat_poly.div(&cyclo_poly.clone()).unwrap(); - let p2i = p2i_poly.coefficients().to_vec(); - let p2i_rem = p2i_rem_poly.coefficients().to_vec(); - assert!(p2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p2i.len() as u64) - 1, n - 2); // Order(p2i) = N - 2 - - // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi - let p2i_poly = Polynomial::new(p2i.clone()); - let p2i_times_cyclo: Vec = - p2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - let mut p2i_times_cyclo_mod_zqi = Polynomial::new(p2i_times_cyclo.clone()); - - p2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - p2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); - assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let p1i_num = ct1i_minus_ct1i_hat_poly - .sub(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((p1i_num.len() as u64) - 1, 2 * (n - 1)); - - let p1i_num_poly = Polynomial::new(p1i_num.clone()); - let qi_poly = Polynomial::new(vec![BigInt::from(qi.modulus())]); - let (p1i_poly, p1i_rem_poly) = p1i_num_poly.div(&qi_poly).unwrap(); + let (p1i_poly, p2i_poly) = + decompose_residue(&ct1i, &ct1i_hat_poly, &qi_bigint, &cyclo, n); let p1i = p1i_poly.coefficients().to_vec(); - let p1i_rem = p1i_rem_poly.coefficients().to_vec(); - assert!(p1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p1i.len() as u64) - 1, 2 * (n - 1)); // Order(p1i) = 2*(N-1) - let p1i_poly_check = Polynomial::new(p1i.clone()); - assert_eq!( - &p1i_num, - &p1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p - let p1i_poly = Polynomial::new(p1i.clone()); - let p1i_times_qi = p1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let p1i_times_qi_poly = Polynomial::new(p1i_times_qi.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let mut ct1i_calculated = ct1i_hat_poly - .add(&p1i_times_qi_poly) - .add(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct1i_calculated.is_empty() && ct1i_calculated[0].is_zero() { - ct1i_calculated.remove(0); - } + let p2i = p2i_poly.coefficients().to_vec(); - assert_eq!(&ct1i, &Polynomial::new(ct1i_calculated.clone())); ( i, r2i, @@ -918,33 +749,20 @@ mod tests { use crate::ciphernodes_committee::CiphernodesCommitteeSize; use crate::computation::DkgInputType; - use crate::dkg::share_encryption::sample::prepare_share_encryption_sample_for_test; - use crate::dkg::share_encryption::ShareEncryptionSample; use e3_fhe_params::BfvPreset; - fn share_encryption_input_from_sample( - sample: &ShareEncryptionSample, - ) -> ShareEncryptionCircuitInput { - ShareEncryptionCircuitInput { - plaintext: sample.plaintext.clone(), - ciphertext: sample.ciphertext.clone(), - public_key: sample.public_key.clone(), - secret_key: sample.secret_key.clone(), - u_rns: sample.u_rns.clone(), - e0_rns: sample.e0_rns.clone(), - e1_rns: sample.e1_rns.clone(), - } - } - #[test] fn test_bound_and_bits_computation_consistency() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let max_pk_bound = bounds.pk_bounds.iter().max().unwrap(); @@ -956,13 +774,15 @@ mod tests { #[test] fn test_constants_json_roundtrip() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let constants = Configs::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); @@ -977,13 +797,15 @@ mod tests { #[test] fn test_witness_message_consistency() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); - let input = share_encryption_input_from_sample(&sample); - let witness = Witness::compute(BfvPreset::InsecureThreshold512, &input).unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); // witness.message is plaintext coefficients (reversed, as used in circuit) let expected_message = Polynomial::from_u64_vector(sample.plaintext.value.deref().to_vec()); diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs index 3446225c16..a52d988af2 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/mod.rs @@ -12,4 +12,3 @@ pub mod computation; pub mod sample; pub use circuit::{ShareEncryptionCircuit, ShareEncryptionCircuitInput}; pub use computation::{Bits, Bounds, Configs, ShareEncryptionOutput, Witness}; -pub use sample::{prepare_share_encryption_sample_for_test, ShareEncryptionSample}; diff --git a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs index 888f8d140a..3cbc963dbe 100644 --- a/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/dkg/share_encryption/sample.rs @@ -8,34 +8,21 @@ //! ciphertext, and encryption randomness (u_rns, e0_rns, e1_rns) for testing and codegen. use crate::ciphernodes_committee::CiphernodesCommitteeSize; +use crate::circuits::dkg::share_encryption::circuit::ShareEncryptionCircuitInput; use crate::computation::DkgInputType; use crate::CircuitsErrors; use e3_fhe_params::build_pair_for_preset; use e3_fhe_params::BfvPreset; -use fhe::bfv::Ciphertext; use fhe::bfv::Encoding; use fhe::bfv::Plaintext; use fhe::bfv::{PublicKey, SecretKey}; use fhe::trbfv::{ShareManager, TRBFV}; -use fhe_math::rq::Poly; use fhe_traits::FheEncoder; use rand::thread_rng; -/// Sample data for the share-encryption circuit: plaintext, ciphertext, keys, and RNS randomness. -#[derive(Debug, Clone)] -pub struct ShareEncryptionSample { - pub plaintext: Plaintext, - pub ciphertext: Ciphertext, - pub public_key: PublicKey, - pub secret_key: SecretKey, - pub u_rns: Poly, - pub e0_rns: Poly, - pub e1_rns: Poly, -} - -impl ShareEncryptionSample { +impl ShareEncryptionCircuitInput { /// Generates sample data for the share-encryption circuit (encrypts a share row under DKG pk). - pub fn generate( + pub fn generate_sample( preset: BfvPreset, committee_size: CiphernodesCommitteeSize, dkg_input_type: DkgInputType, @@ -50,8 +37,7 @@ impl ShareEncryptionSample { let dkg_secret_key = SecretKey::random(&dkg_params, &mut rng); let dkg_public_key = PublicKey::new(&dkg_secret_key, &mut rng); - let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()) - .unwrap_or_else(|e| panic!("Failed to create TRBFV: {:?}", e)); + let trbfv = TRBFV::new(committee.n, committee.threshold, threshold_params.clone()).unwrap(); let mut share_manager = ShareManager::new(committee.n, committee.threshold, threshold_params.clone()); @@ -96,7 +82,7 @@ impl ShareEncryptionSample { let (_ct, u_rns, e0_rns, e1_rns) = dkg_public_key.try_encrypt_extended(&pt, &mut rng).unwrap(); - ShareEncryptionSample { + ShareEncryptionCircuitInput { plaintext: pt, ciphertext: _ct, public_key: dkg_public_key, @@ -108,23 +94,6 @@ impl ShareEncryptionSample { } } -/// Prepares a share-encryption sample for testing using a threshold preset. -pub fn prepare_share_encryption_sample_for_test( - preset: BfvPreset, - committee: CiphernodesCommitteeSize, - dkg_input_type: DkgInputType, -) -> ShareEncryptionSample { - let defaults = preset.search_defaults().unwrap(); - - ShareEncryptionSample::generate( - preset, - committee, - dkg_input_type, - defaults.z, - defaults.lambda, - ) -} - #[cfg(test)] mod tests { use super::*; @@ -134,10 +103,13 @@ mod tests { #[test] fn test_generate_secret_key_sample() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, DkgInputType::SecretKey, + sd.z, + sd.lambda, ); assert_eq!(sample.public_key.c.c.len(), 2); @@ -162,10 +134,13 @@ mod tests { #[test] fn test_generate_smudging_noise_sample() { - let sample = prepare_share_encryption_sample_for_test( + let sd = BfvPreset::InsecureThreshold512.search_defaults().unwrap(); + let sample = ShareEncryptionCircuitInput::generate_sample( BfvPreset::InsecureThreshold512, CiphernodesCommitteeSize::Small, - DkgInputType::SmudgingNoise, + DkgInputType::SecretKey, + sd.z, + sd.lambda, ); assert_eq!(sample.public_key.c.c.len(), 2); diff --git a/crates/zk-helpers/src/circuits/mod.rs b/crates/zk-helpers/src/circuits/mod.rs index 09ce541a62..f63ddfae9a 100644 --- a/crates/zk-helpers/src/circuits/mod.rs +++ b/crates/zk-helpers/src/circuits/mod.rs @@ -15,11 +15,4 @@ pub use computation::{CircuitComputation, Computation}; pub use errors::CircuitsErrors; pub mod dkg; -pub use dkg::pk::codegen::{generate_configs, generate_toml}; -pub use dkg::pk::computation::{Bits, Bounds, PkComputationOutput, Witness}; -pub use dkg::pk::{prepare_pk_sample_for_test, PkCircuit, PkSample}; -pub use dkg::share_computation::{ - prepare_share_computation_sample_for_test, SecretShares, ShareComputationSample, -}; - pub mod threshold; diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs index 395f268b1f..abf295203e 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/codegen.rs @@ -130,16 +130,17 @@ mod tests { use crate::threshold::pk_generation::PkGenerationCircuitInput; use crate::CiphernodesCommitteeSize; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::BfvPreset; use tempfile::TempDir; #[test] fn test_toml_generation_and_structure() { let committee = CiphernodesCommitteeSize::Small.values(); let sample = - PkGenerationCircuitInput::generate_sample(DEFAULT_BFV_PRESET, committee).unwrap(); + PkGenerationCircuitInput::generate_sample(BfvPreset::InsecureThreshold512, committee) + .unwrap(); let artifacts = PkGenerationCircuit - .codegen(DEFAULT_BFV_PRESET, &sample) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); @@ -176,13 +177,23 @@ mod tests { assert!(configs_path.exists()); let configs_content = std::fs::read_to_string(&configs_path).unwrap(); - let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &sample.committee).unwrap(); - let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &sample.committee).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); - assert!(configs_content - .contains(format!("N: u32 = {}", DEFAULT_BFV_PRESET.metadata().degree).as_str())); - assert!(configs_content - .contains(format!("L: u32 = {}", DEFAULT_BFV_PRESET.metadata().num_moduli).as_str())); + assert!(configs_content.contains( + format!( + "N: u32 = {}", + BfvPreset::InsecureThreshold512.metadata().degree + ) + .as_str() + )); + assert!(configs_content.contains( + format!( + "L: u32 = {}", + BfvPreset::InsecureThreshold512.metadata().num_moduli + ) + .as_str() + )); assert!(configs_content.contains( format!( "{}_BIT_PK: u32 = {}", diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs index db09ab92fb..81608583cd 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/computation.rs @@ -13,6 +13,7 @@ use crate::calculate_bit_width; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; +use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::pk_generation::circuit::PkGenerationCircuit; use crate::threshold::pk_generation::circuit::PkGenerationCircuitInput; use crate::CiphernodesCommittee; @@ -254,12 +255,7 @@ impl Computation for Witness { .map(BigInt::from) .collect(); let n = threshold_params.degree() as u64; - - // Create cyclotomic polynomial x^N + 1 - let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; - - cyclo[0] = BigInt::from(1u64); // x^N term - cyclo[n as usize] = BigInt::from(1u64); // x^0 term + let cyclo = cyclotomic_polynomial(n); // Perform the main computation logic let mut results: Vec<( @@ -309,62 +305,7 @@ impl Computation for Witness { assert_eq!((pk0_share_hat.coefficients().len() as u64) - 1, 2 * (n - 1)); - // Check whether pk0_share_hat mod R_qi (the ring) is equal to pk0_share - let mut pk0_share_hat_mod_rqi = pk0_share_hat.reduce_by_cyclotomic(&cyclo).unwrap(); - - pk0_share_hat_mod_rqi.reduce(&qi); - pk0_share_hat_mod_rqi.center(&qi); - - assert_eq!(&pk0_share, &pk0_share_hat_mod_rqi); - - // Compute r2_numerator = pk0_share - pk0_share_hat and reduce/center the polynomial - let r2_numerator = pk0_share.sub(&pk0_share_hat); - - assert_eq!((r2_numerator.coefficients().len() as u64) - 1, 2 * (n - 1)); - - let mut r2_numerator_centered = r2_numerator.clone(); - r2_numerator_centered.reduce(&qi); - r2_numerator_centered.center(&qi); - - // Compute r2 as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (pk0_share - pk0_share_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let cyclo_polynomial = Polynomial::new(cyclo.clone()); - let (r2, r2_rem) = r2_numerator_centered.div(&cyclo_polynomial).unwrap(); - - assert!(r2_rem.is_zero()); - assert_eq!((r2.coefficients().len() as u64) - 1, n - 2); // Order(r2i) = N - 2 - - // Assert that (pk0_share - pk0_share_hat) = (r2 * cyclo) mod Z_qi - let r2_cyclo_times = r2.mul(&cyclo_polynomial); - - let mut r2_cyclo_times_centered = r2_cyclo_times.clone(); - r2_cyclo_times_centered.reduce(&qi); - r2_cyclo_times_centered.center(&qi); - - assert_eq!(&r2_numerator_centered, &r2_cyclo_times_centered); - assert_eq!( - (r2_cyclo_times.coefficients().len() as u64) - 1, - 2 * (n - 1) - ); - - // Calculate r1 = (pk0_share - pk0_share_hat - r2 * cyclo) / qi mod Z_p. Remainder should be empty. - let r1_numerator = r2_numerator.sub(&r2_cyclo_times); - - assert_eq!((r2_numerator.coefficients().len() as u64) - 1, 2 * (n - 1)); - - let qi_polynomial = Polynomial::new(vec![qi.clone()]); - let (r1, r1_rem) = r1_numerator.div(&qi_polynomial).unwrap(); - - assert!(r1_rem.is_zero()); - assert_eq!((r1.coefficients().len() as u64) - 1, 2 * (n - 1)); // Order(r1) = 2*(N-1) - - assert_eq!(&r1_numerator, &r1.mul(&qi_polynomial)); - - // Assert that pk0_share = (pk0_share_hat + r1 * qi + r2 * cyclo) mod R_qi - let r1_qi_times = r1.scalar_mul(&qi); - let pk0_share_calculated = pk0_share_hat.add(&r1_qi_times).add(&r2_cyclo_times); - - assert_eq!(&pk0_share, &pk0_share_calculated.trim_leading_zeros()); + let (r1, r2) = decompose_residue(&pk0_share, &pk0_share_hat, &qi, &cyclo, n); (i, r2, r1, pk0_share.clone(), a.clone(), e_sm.clone()) }, @@ -448,13 +389,13 @@ mod tests { use super::*; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::BfvPreset; #[test] fn test_bound_and_bits_computation_consistency() { let committee = CiphernodesCommitteeSize::Small.values(); - let bounds = Bounds::compute(DEFAULT_BFV_PRESET, &committee).unwrap(); - let bits = Bits::compute(DEFAULT_BFV_PRESET, &bounds).unwrap(); + let bounds = Bounds::compute(BfvPreset::InsecureThreshold512, &committee).unwrap(); + let bits = Bits::compute(BfvPreset::InsecureThreshold512, &bounds).unwrap(); let expected_bit = calculate_bit_width(BigInt::from(bounds.pk_bound.clone())); @@ -464,7 +405,7 @@ mod tests { #[test] fn test_constants_json_roundtrip() { let committee = CiphernodesCommitteeSize::Small.values(); - let constants = Configs::compute(DEFAULT_BFV_PRESET, &committee).unwrap(); + let constants = Configs::compute(BfvPreset::InsecureThreshold512, &committee).unwrap(); let json = constants.to_json().unwrap(); let decoded: Configs = serde_json::from_value(json).unwrap(); diff --git a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs index f7c53f8155..315209b937 100644 --- a/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/pk_generation/sample.rs @@ -87,14 +87,15 @@ mod tests { CiphernodesCommitteeSize, }; - use e3_fhe_params::DEFAULT_BFV_PRESET; + use e3_fhe_params::BfvPreset; #[test] fn test_generate_sample() { let committee = CiphernodesCommitteeSize::Small.values(); let sample = - PkGenerationCircuitInput::generate_sample(DEFAULT_BFV_PRESET, committee).unwrap(); - let witness = Witness::compute(DEFAULT_BFV_PRESET, &sample).unwrap(); + PkGenerationCircuitInput::generate_sample(BfvPreset::InsecureThreshold512, committee) + .unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); assert_eq!(witness.pk0is.limbs.len(), 2); assert_eq!(witness.a.limbs.len(), 2); diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs index b6aea6491b..fbee84ad23 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/codegen.rs @@ -203,23 +203,18 @@ mod tests { use super::*; use crate::circuits::computation::Computation; use crate::codegen::write_artifacts; + use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use crate::threshold::user_data_encryption::computation::{Bits, Bounds}; - use crate::threshold::user_data_encryption::sample::UserDataEncryptionSample; use e3_fhe_params::BfvPreset; use tempfile::TempDir; #[test] fn test_toml_generation_and_structure() { - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); let artifacts = UserDataEncryptionCircuit - .codegen( - BfvPreset::InsecureThreshold512, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key, - plaintext: sample.plaintext, - }, - ) + .codegen(BfvPreset::InsecureThreshold512, &sample) .unwrap(); let parsed: toml::Value = artifacts.toml.parse().unwrap(); diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs index 64f3a327ac..2210269b18 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/computation.rs @@ -12,9 +12,11 @@ use crate::calculate_bit_width; use crate::commitments::compute_pk_aggregation_commitment; use crate::compute_ciphertext_commitment; +use crate::crt::compute_k0is; use crate::crt_polynomial_to_toml_json; use crate::get_zkp_modulus; use crate::polynomial_to_toml_json; +use crate::ring::{cyclotomic_polynomial, decompose_residue}; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuit; use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use crate::utils::compute_pk_bit; @@ -37,7 +39,6 @@ use num_bigint::BigUint; use num_bigint::ToBigInt; use num_traits::Signed; use num_traits::ToPrimitive; -use num_traits::Zero; use rand::thread_rng; use rayon::iter::ParallelIterator; use rayon::prelude::ParallelBridge; @@ -151,19 +152,7 @@ impl Computation for Configs { let q_mod_t = center(&reduce(&modulus, &t), &t); let q_mod_t_mod_p = reduce(&q_mod_t, &p); - let mut k0is: Vec = Vec::new(); - - for qi in ctx.moduli_operators() { - let k0qi = BigInt::from(qi.inv(qi.neg(threshold_params.plaintext())).ok_or_else( - || { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - }, - )?); - - k0is.push(k0qi.to_u64().unwrap_or(0)); - } + let k0is = compute_k0is(threshold_params.moduli(), threshold_params.plaintext())?; let bounds = Bounds::compute(preset, &())?; let bits = Bits::compute(preset, &bounds)?; @@ -284,31 +273,25 @@ impl Computation for Bounds { let k1_up_bound: BigInt = ptxt_up_bound.clone(); // Calculate bounds for each CRT basis - let _num_moduli = ctx.moduli().len(); + let moduli: Vec = ctx + .moduli_operators() + .into_iter() + .map(|q| q.modulus()) + .collect(); + let k0is = compute_k0is(&moduli, threshold_params.plaintext())?; + let mut pk_bounds: Vec = Vec::new(); let mut r1_low_bounds: Vec = Vec::new(); let mut r1_up_bounds: Vec = Vec::new(); let mut r2_bounds: Vec = Vec::new(); let mut p1_bounds: Vec = Vec::new(); let mut p2_bounds: Vec = Vec::new(); - let mut moduli: Vec = Vec::new(); - let mut k0is: Vec = Vec::new(); - for qi in ctx.moduli_operators() { + for (i, qi) in ctx.moduli_operators().into_iter().enumerate() { let qi_bigint = BigInt::from(qi.modulus()); let qi_bound = (&qi_bigint - BigInt::from(1)) / BigInt::from(2); - moduli.push(qi.modulus()); - - // Calculate k0qi for bounds - let k0qi = BigInt::from(qi.inv(qi.neg(threshold_params.plaintext())).ok_or_else( - || { - CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( - "Failed to calculate modulus inverse for k0qi".into(), - ))) - }, - )?); - k0is.push(k0qi.to_u64().unwrap_or(0)); + let k0qi = BigInt::from(k0is[i]); // PK and R2 bounds (same as qi_bound) pk_bounds.push(qi_bound.clone()); @@ -469,11 +452,7 @@ impl Computation for Witness { pk0.change_representation(Representation::PowerBasis); pk1.change_representation(Representation::PowerBasis); - // Create cyclotomic polynomial x^N + 1 - let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; - - cyclo[0] = BigInt::from(1u64); // x^N term - cyclo[n as usize] = BigInt::from(1u64); // x^0 term + let cyclo = cyclotomic_polynomial(n); let ct0_coeffs = ct0.coefficients(); let ct1_coeffs = ct1.coefficients(); @@ -600,88 +579,11 @@ impl Computation for Witness { }; assert_eq!((ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct0i_hat mod R_qi (the ring) is equal to ct0i - let mut ct0i_hat_mod_rqi = Polynomial::new(ct0i_hat.clone()); - - ct0i_hat_mod_rqi = ct0i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - - ct0i_hat_mod_rqi.reduce(&qi_bigint); - ct0i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct0i, &ct0i_hat_mod_rqi); - - // Compute r2i numerator = ct0i - ct0i_hat and reduce/center the polynomial - let ct0i_poly = ct0i.clone(); let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let ct0i_minus_ct0i_hat = ct0i_poly.sub(&ct0i_hat_poly).coefficients().to_vec(); - assert_eq!((ct0i_minus_ct0i_hat.len() as u64) - 1, 2 * (n - 1)); - - let mut ct0i_minus_ct0i_hat_mod_zqi = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - - ct0i_minus_ct0i_hat_mod_zqi.reduce(&qi_bigint); - ct0i_minus_ct0i_hat_mod_zqi.center(&qi_bigint); - - // Compute r2i as the quotient of numerator divided by the cyclotomic polynomial - // to produce: (ct0i - ct0i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = ct0i_minus_ct0i_hat_mod_zqi.clone(); - let cyclo_poly = Polynomial::new(cyclo.clone()); - let (r2i_poly, r2i_rem_poly) = ct0i_minus_ct0i_hat_poly.div(&cyclo_poly).unwrap(); - let r2i = r2i_poly.coefficients().to_vec(); - let r2i_rem = r2i_rem_poly.coefficients().to_vec(); - assert!(r2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r2i.len() as u64) - 1, n - 2); // Order(r2i) = N - 2 - - // Assert that (ct0i - ct0i_hat) = (r2i * cyclo) mod Z_qi - let r2i_poly = Polynomial::new(r2i.clone()); - let r2i_times_cyclo = r2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - - let mut r2i_times_cyclo_mod_zqi = Polynomial::new(r2i_times_cyclo.clone()); - - r2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - r2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct0i_minus_ct0i_hat_mod_zqi, &r2i_times_cyclo_mod_zqi); - assert_eq!((r2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate r1i = (ct0i - ct0i_hat - r2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct0i_minus_ct0i_hat_poly = Polynomial::new(ct0i_minus_ct0i_hat.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let r1i_num = ct0i_minus_ct0i_hat_poly - .sub(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((r1i_num.len() as u64) - 1, 2 * (n - 1)); - - let r1i_num_poly = Polynomial::new(r1i_num.clone()); - let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); - let (r1i_poly, r1i_rem_poly) = r1i_num_poly.div(&qi_poly).unwrap(); + let (r1i_poly, r2i_poly) = + decompose_residue(&ct0i, &ct0i_hat_poly, &qi_bigint, &cyclo, n); let r1i = r1i_poly.coefficients().to_vec(); - let r1i_rem = r1i_rem_poly.coefficients().to_vec(); - assert!(r1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((r1i.len() as u64) - 1, 2 * (n - 1)); // Order(r1i) = 2*(N-1) - let r1i_poly_check = Polynomial::new(r1i.clone()); - assert_eq!( - &r1i_num, - &r1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct0i = ct0i_hat + r1i * qi + r2i * cyclo mod Z_p - let r1i_poly = Polynomial::new(r1i.clone()); - let r1i_times_qi = r1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct0i_hat_poly = Polynomial::new(ct0i_hat.clone()); - let r1i_times_qi_poly = Polynomial::new(r1i_times_qi.clone()); - let r2i_times_cyclo_poly = Polynomial::new(r2i_times_cyclo.clone()); - let mut ct0i_calculated = ct0i_hat_poly - .add(&r1i_times_qi_poly) - .add(&r2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct0i_calculated.is_empty() && ct0i_calculated[0].is_zero() { - ct0i_calculated.remove(0); - } - - assert_eq!(&ct0i, &Polynomial::new(ct0i_calculated.clone())); + let r2i = r2i_poly.coefficients().to_vec(); // --------------------------------------------------- ct1i --------------------------------------------------- @@ -697,87 +599,12 @@ impl Computation for Witness { }; assert_eq!((ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - // Check whether ct1i_hat mod R_qi (the ring) is equal to ct1i - let mut ct1i_hat_mod_rqi = Polynomial::new(ct1i_hat.clone()); - - ct1i_hat_mod_rqi = ct1i_hat_mod_rqi.reduce_by_cyclotomic(&cyclo).unwrap(); - ct1i_hat_mod_rqi.reduce(&qi_bigint); - ct1i_hat_mod_rqi.center(&qi_bigint); - - assert_eq!(&ct1i, &ct1i_hat_mod_rqi); - - // Compute p2i numerator = ct1i - ct1i_hat - let ct1i_poly = ct1i.clone(); let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let ct1i_minus_ct1i_hat = ct1i_poly.sub(&ct1i_hat_poly).coefficients().to_vec(); - assert_eq!((ct1i_minus_ct1i_hat.len() as u64) - 1, 2 * (n - 1)); - let mut ct1i_minus_ct1i_hat_mod_zqi = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - - ct1i_minus_ct1i_hat_mod_zqi.reduce(&qi_bigint); - ct1i_minus_ct1i_hat_mod_zqi.center(&qi_bigint); - - // Compute p2i as the quotient of numerator divided by the cyclotomic polynomial, - // and reduce/center the resulting coefficients to produce: - // (ct1i - ct1i_hat) / (x^N + 1) mod Z_qi. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = ct1i_minus_ct1i_hat_mod_zqi.clone(); - let (p2i_poly, p2i_rem_poly) = - ct1i_minus_ct1i_hat_poly.div(&cyclo_poly.clone()).unwrap(); - let p2i = p2i_poly.coefficients().to_vec(); - let p2i_rem = p2i_rem_poly.coefficients().to_vec(); - assert!(p2i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p2i.len() as u64) - 1, n - 2); // Order(p2i) = N - 2 - - // Assert that (ct1i - ct1i_hat) = (p2i * cyclo) mod Z_qi - let p2i_poly = Polynomial::new(p2i.clone()); - let p2i_times_cyclo: Vec = - p2i_poly.mul(&cyclo_poly).coefficients().to_vec(); - let mut p2i_times_cyclo_mod_zqi = Polynomial::new(p2i_times_cyclo.clone()); - - p2i_times_cyclo_mod_zqi.reduce(&qi_bigint); - p2i_times_cyclo_mod_zqi.center(&qi_bigint); - - assert_eq!(&ct1i_minus_ct1i_hat_mod_zqi, &p2i_times_cyclo_mod_zqi); - assert_eq!((p2i_times_cyclo.len() as u64) - 1, 2 * (n - 1)); - - // Calculate p1i = (ct1i - ct1i_hat - p2i * cyclo) / qi mod Z_p. Remainder should be empty. - let ct1i_minus_ct1i_hat_poly = Polynomial::new(ct1i_minus_ct1i_hat.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let p1i_num = ct1i_minus_ct1i_hat_poly - .sub(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - assert_eq!((p1i_num.len() as u64) - 1, 2 * (n - 1)); - - let p1i_num_poly = Polynomial::new(p1i_num.clone()); - let qi_poly = Polynomial::new(vec![BigInt::from(qi.modulus())]); - let (p1i_poly, p1i_rem_poly) = p1i_num_poly.div(&qi_poly).unwrap(); + let (p1i_poly, p2i_poly) = + decompose_residue(&ct1i, &ct1i_hat_poly, &qi_bigint, &cyclo, n); let p1i = p1i_poly.coefficients().to_vec(); - let p1i_rem = p1i_rem_poly.coefficients().to_vec(); - assert!(p1i_rem.iter().all(|x| x.is_zero())); - assert_eq!((p1i.len() as u64) - 1, 2 * (n - 1)); // Order(p1i) = 2*(N-1) - let p1i_poly_check = Polynomial::new(p1i.clone()); - assert_eq!( - &p1i_num, - &p1i_poly_check.mul(&qi_poly).coefficients().to_vec() - ); - - // Assert that ct1i = ct1i_hat + p1i * qi + p2i * cyclo mod Z_p - let p1i_poly = Polynomial::new(p1i.clone()); - let p1i_times_qi = p1i_poly.scalar_mul(&qi_bigint).coefficients().to_vec(); - let ct1i_hat_poly = Polynomial::new(ct1i_hat.clone()); - let p1i_times_qi_poly = Polynomial::new(p1i_times_qi.clone()); - let p2i_times_cyclo_poly = Polynomial::new(p2i_times_cyclo.clone()); - let mut ct1i_calculated = ct1i_hat_poly - .add(&p1i_times_qi_poly) - .add(&p2i_times_cyclo_poly) - .coefficients() - .to_vec(); - - while !ct1i_calculated.is_empty() && ct1i_calculated[0].is_zero() { - ct1i_calculated.remove(0); - } + let p2i = p2i_poly.coefficients().to_vec(); - assert_eq!(&ct1i, &Polynomial::new(ct1i_calculated.clone())); ( i, r2i, diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs index aac70235fb..1f52058a87 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/mod.rs @@ -18,5 +18,4 @@ pub mod utils; pub use circuit::*; pub use codegen::*; pub use computation::*; -pub use sample::*; pub use utils::*; diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs index f77b310e4c..63b9785555 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/sample.rs @@ -9,21 +9,15 @@ //! [`Sample`] produces a random BFV key pair and plaintext; the public key and plaintext are used as input //! for codegen and tests. +use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use fhe::bfv::{Encoding, Plaintext, PublicKey, SecretKey}; use fhe_traits::FheEncoder; use rand::thread_rng; -/// A sample BFV public key and plaintext for user data encryption circuit codegen or tests. -#[derive(Debug, Clone)] -pub struct UserDataEncryptionSample { - pub public_key: PublicKey, - pub plaintext: Plaintext, -} - -impl UserDataEncryptionSample { +impl UserDataEncryptionCircuitInput { /// Generates a random secret key, public key, and plaintext for the given BFV parameters. - pub fn generate(preset: BfvPreset) -> Self { + pub fn generate_sample(preset: BfvPreset) -> Self { let (threshold_params, _) = build_pair_for_preset(preset).unwrap(); let mut rng = thread_rng(); @@ -43,12 +37,13 @@ impl UserDataEncryptionSample { #[cfg(test)] mod tests { - use super::*; + use crate::threshold::user_data_encryption::circuit::UserDataEncryptionCircuitInput; use e3_fhe_params::BfvPreset; #[test] fn test_generate_sample() { - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); assert_eq!(sample.public_key.c.c.len(), 2); assert_eq!( diff --git a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs index 4f60bf352c..9ec418fef4 100644 --- a/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs +++ b/crates/zk-helpers/src/circuits/threshold/user_data_encryption/utils.rs @@ -4,6 +4,7 @@ // without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. +use crate::crt::fhe_poly_to_crt_centered; use crate::utils::{compute_pk_bit, get_zkp_modulus, ZkHelpersUtilsError}; use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; use fhe::bfv::{BfvParameters, Ciphertext, PublicKey}; @@ -29,14 +30,8 @@ pub fn bfv_ciphertext_to_greco( let moduli = params.moduli(); let zkp_modulus = get_zkp_modulus(); - let mut ct0is = CrtPolynomial::from_fhe_polynomial(&ciphertext.c[0]); - let mut ct1is = CrtPolynomial::from_fhe_polynomial(&ciphertext.c[1]); - - ct0is.reverse(); - ct1is.reverse(); - - ct0is.center(&moduli)?; - ct1is.center(&moduli)?; + let mut ct0is = fhe_poly_to_crt_centered(&ciphertext.c[0], moduli)?; + let mut ct1is = fhe_poly_to_crt_centered(&ciphertext.c[1], moduli)?; ct0is.reduce_uniform(&zkp_modulus); ct1is.reduce_uniform(&zkp_modulus); @@ -65,14 +60,8 @@ pub fn bfv_public_key_to_greco( let moduli = params.moduli(); let zkp_modulus = get_zkp_modulus(); - let mut pk0is = CrtPolynomial::from_fhe_polynomial(&public_key.c.c[0]); - let mut pk1is = CrtPolynomial::from_fhe_polynomial(&public_key.c.c[1]); - - pk0is.reverse(); - pk1is.reverse(); - - pk0is.center(&moduli)?; - pk1is.center(&moduli)?; + let mut pk0is = fhe_poly_to_crt_centered(&public_key.c.c[0], moduli)?; + let mut pk1is = fhe_poly_to_crt_centered(&public_key.c.c[1], moduli)?; pk0is.reduce_uniform(&zkp_modulus); pk1is.reduce_uniform(&zkp_modulus); @@ -175,7 +164,6 @@ mod tests { use super::*; use crate::circuits::computation::Computation; use crate::threshold::user_data_encryption::computation::Witness; - use crate::threshold::user_data_encryption::sample::UserDataEncryptionSample; use crate::threshold::user_data_encryption::UserDataEncryptionCircuitInput; use e3_fhe_params::{build_pair_for_preset, BfvPreset}; use fhe_traits::DeserializeParametrized; @@ -183,16 +171,10 @@ mod tests { #[test] fn test_bfv_public_key_to_greco() { let (threshold_params, _) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); - let witness = Witness::compute( - BfvPreset::InsecureThreshold512, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key.clone(), - plaintext: sample.plaintext, - }, - ) - .unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); // Convert using our function let (actual_pk0is, actual_pk1is) = @@ -207,16 +189,10 @@ mod tests { fn test_bfv_ciphertext_to_greco() { let (threshold_params, _) = build_pair_for_preset(BfvPreset::InsecureThreshold512).unwrap(); - let sample = UserDataEncryptionSample::generate(BfvPreset::InsecureThreshold512); + let sample = + UserDataEncryptionCircuitInput::generate_sample(BfvPreset::InsecureThreshold512); - let witness = Witness::compute( - BfvPreset::InsecureThreshold512, - &UserDataEncryptionCircuitInput { - public_key: sample.public_key.clone(), - plaintext: sample.plaintext, - }, - ) - .unwrap(); + let witness = Witness::compute(BfvPreset::InsecureThreshold512, &sample).unwrap(); let ciphertext = Ciphertext::from_bytes(&witness.ciphertext, &threshold_params).unwrap(); diff --git a/crates/zk-helpers/src/crt.rs b/crates/zk-helpers/src/crt.rs new file mode 100644 index 0000000000..c9e264d772 --- /dev/null +++ b/crates/zk-helpers/src/crt.rs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! CRT (Chinese Remainder) operations for ZK circuit computations. +//! +//! Used by share_encryption, user_data_encryption, pk, and other circuits that work with +//! polynomials in CRT form or need to convert FHE types to CRT limbs. + +use crate::CircuitsErrors; +use e3_polynomial::{CrtPolynomial, CrtPolynomialError}; +use fhe_math::rq::Poly; +use fhe_math::zq::Modulus; + +/// Computes k0_i = (-t)^{-1} mod q_i for each modulus (used in Configs and bounds). +/// +/// Same logic as share_encryption and user_data_encryption when building k0is. +pub fn compute_k0is(moduli: &[u64], plaintext_modulus: u64) -> Result, CircuitsErrors> { + let mut k0is = Vec::with_capacity(moduli.len()); + for &qi in moduli { + let m = Modulus::new(qi).map_err(|e| { + CircuitsErrors::Sample(format!("Failed to create modulus for k0is: {:?}", e)) + })?; + let k0qi = m.inv(m.neg(plaintext_modulus)).ok_or_else(|| { + CircuitsErrors::Fhe(fhe::Error::MathError(fhe_math::Error::Default( + "Failed to calculate modulus inverse for k0qi".into(), + ))) + })?; + k0is.push(k0qi); + } + Ok(k0is) +} + +/// Converts an FHE polynomial to CRT form with reverse + center (no ZKP reduce). +/// +/// Same pattern used by share_encryption, user_data_encryption, and pk circuits +/// when building circuit input from FHE types. +pub fn fhe_poly_to_crt_centered( + poly: &Poly, + moduli: &[u64], +) -> Result { + let mut crt = CrtPolynomial::from_fhe_polynomial(poly); + crt.reverse(); + crt.center(moduli)?; + Ok(crt) +} diff --git a/crates/zk-helpers/src/lib.rs b/crates/zk-helpers/src/lib.rs index 979ae79c99..dd1d9e7a3c 100644 --- a/crates/zk-helpers/src/lib.rs +++ b/crates/zk-helpers/src/lib.rs @@ -6,12 +6,16 @@ pub mod ciphernodes_committee; pub mod circuits; +pub mod crt; pub mod packing; pub mod registry; +pub mod ring; pub mod utils; pub use ciphernodes_committee::*; pub use circuits::*; +pub use crt::*; pub use packing::*; pub use registry::*; +pub use ring::*; pub use utils::*; diff --git a/crates/zk-helpers/src/ring.rs b/crates/zk-helpers/src/ring.rs new file mode 100644 index 0000000000..396461f0b6 --- /dev/null +++ b/crates/zk-helpers/src/ring.rs @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +//! Polynomial ring helpers for ZK circuit computations (R_qi = Z_q_i[x] / (x^N + 1)). +//! +//! Cyclotomic polynomial and residue decomposition used by pk_generation, share_encryption, +//! and user_data_encryption when reducing by R_qi and decomposing residues. + +use e3_polynomial::Polynomial; +use num_bigint::BigInt; +use num_traits::Zero; + +/// Returns the coefficient vector for the cyclotomic polynomial x^N + 1 (degree N). +/// +/// Used by share_encryption, user_data_encryption, and pk_generation when reducing by R_qi. +#[must_use] +pub fn cyclotomic_polynomial(n: u64) -> Vec { + let mut cyclo = vec![BigInt::from(0u64); (n + 1) as usize]; + cyclo[0] = BigInt::from(1u64); + cyclo[n as usize] = BigInt::from(1u64); + cyclo +} + +/// Decomposes the residue `xi - xi_hat` into `r1 * qi + r2 * cyclo` mod R_qi. +/// +/// Verifies that `xi == xi_hat (mod R_qi)`, then computes quotient polynomials +/// such that `xi - xi_hat = r1 * qi + r2 * cyclo` (with cyclo = x^N + 1). +/// Returns `(r1, r2)` as polynomials. Panics on assertion failures (exact division, +/// degree checks, reconstruction). +pub fn decompose_residue( + xi: &Polynomial, + xi_hat: &Polynomial, + qi_bigint: &BigInt, + cyclo: &[BigInt], + n: u64, +) -> (Polynomial, Polynomial) { + let cyclo_poly = Polynomial::new(cyclo.to_vec()); + let qi_poly = Polynomial::new(vec![qi_bigint.clone()]); + + let mut xi_hat_mod_rqi = xi_hat.clone(); + xi_hat_mod_rqi = xi_hat_mod_rqi.reduce_by_cyclotomic(cyclo).unwrap(); + xi_hat_mod_rqi.reduce(qi_bigint); + xi_hat_mod_rqi.center(qi_bigint); + assert_eq!(xi, &xi_hat_mod_rqi); + + let num_coeffs = xi.sub(xi_hat).coefficients().to_vec(); + assert_eq!((num_coeffs.len() as u64) - 1, 2 * (n - 1)); + + let mut num_mod_zqi = Polynomial::new(num_coeffs.clone()); + num_mod_zqi.reduce(qi_bigint); + num_mod_zqi.center(qi_bigint); + + let (r2_poly, r2_rem_poly) = num_mod_zqi.clone().div(&cyclo_poly).unwrap(); + assert!(r2_rem_poly.coefficients().iter().all(|c| c.is_zero())); + assert_eq!((r2_poly.coefficients().len() as u64) - 1, n - 2); + + let r2_times_cyclo = r2_poly.mul(&cyclo_poly); + let mut r2_times_cyclo_mod = r2_times_cyclo.clone(); + r2_times_cyclo_mod.reduce(qi_bigint); + r2_times_cyclo_mod.center(qi_bigint); + assert_eq!(&num_mod_zqi, &r2_times_cyclo_mod); + assert_eq!( + (r2_times_cyclo.coefficients().len() as u64) - 1, + 2 * (n - 1) + ); + + let num_poly = Polynomial::new(num_coeffs); + let r1_num = num_poly.sub(&r2_times_cyclo); + assert_eq!((r1_num.coefficients().len() as u64) - 1, 2 * (n - 1)); + + let (r1_poly, r1_rem_poly) = r1_num.div(&qi_poly).unwrap(); + assert!(r1_rem_poly.coefficients().iter().all(|c| c.is_zero())); + assert_eq!((r1_poly.coefficients().len() as u64) - 1, 2 * (n - 1)); + assert_eq!(&r1_num, &r1_poly.mul(&qi_poly)); + + let r1_times_qi = r1_poly.clone().scalar_mul(qi_bigint); + let xi_calculated = xi_hat + .clone() + .add(&r1_times_qi) + .add(&r2_times_cyclo) + .trim_leading_zeros(); + assert_eq!(xi, &xi_calculated); + + (r1_poly, r2_poly) +} diff --git a/crates/zk-helpers/src/utils.rs b/crates/zk-helpers/src/utils.rs index 59e8510690..dd8a4bd11d 100644 --- a/crates/zk-helpers/src/utils.rs +++ b/crates/zk-helpers/src/utils.rs @@ -216,6 +216,18 @@ pub fn crt_polynomial_to_toml_json(crt_polynomial: &CrtPolynomial) -> Vec]) -> Vec> { + y.iter() + .map(|coeff| { + coeff + .iter() + .map(|v| serde_json::Value::String(v.to_string())) + .collect() + }) + .collect() +} + /// # Arguments /// * `bigint_1d` - 1D vector of BigInt values ///