diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 47e28a7d..8f5ebe87 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -238,7 +238,12 @@ impl AggregatedTranscript { aad: &[u8], validator_keypair: &Keypair, ) -> Result { - let domain_points: Vec<_> = dkg.0.domain.elements().collect(); + let domain_points: Vec<_> = dkg + .0 + .domain + .elements() + .take(dkg.0.dkg_params.shares_num as usize) + .collect(); self.0.make_decryption_share_simple_precomputed( ciphertext, aad, @@ -428,6 +433,8 @@ mod test_ferveo_api { assert!(pvss_aggregated .verify(shares_num, &messages) .unwrap()); + + // And then each validator creates their own decryption share aggregate .create_decryption_share_precomputed( &dkg, diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index c520b3f3..0a225536 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -166,17 +166,17 @@ mod test_dkg_full { }) .collect(); - let domain = &dkg + let domain_points = &dkg .domain .elements() .take(decryption_shares.len()) .collect::>(); - assert_eq!(domain.len(), decryption_shares.len()); + assert_eq!(domain_points.len(), decryption_shares.len()); // TODO: Consider refactor this part into tpke::combine_simple and expose it // as a public API in tpke::api - let lagrange_coeffs = tpke::prepare_combine_simple::(domain); + let lagrange_coeffs = tpke::prepare_combine_simple::(domain_points); let shared_secret = tpke::share_combine_simple::( &decryption_shares, &lagrange_coeffs, @@ -189,89 +189,103 @@ mod test_dkg_full { fn test_dkg_simple_tdec() { let rng = &mut test_rng(); - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4); - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - let public_key = dkg.public_key(); - let ciphertext = tpke::encrypt::( - SecretBox::new(msg.clone()), - aad, - &public_key, - rng, - ) - .unwrap(); + // Works for both power of 2 and non-power of 2 + for shares_num in [4, 7] { + let threshold = shares_num / 2 + 1; + let (dkg, validator_keypairs) = + setup_dealt_dkg_with_n_validators(threshold, shares_num); + let msg = "my-msg".as_bytes().to_vec(); + let aad: &[u8] = "my-aad".as_bytes(); + let public_key = dkg.public_key(); + let ciphertext = tpke::encrypt::( + SecretBox::new(msg.clone()), + aad, + &public_key, + rng, + ) + .unwrap(); - let (_, _, shared_secret) = make_shared_secret_simple_tdec( - &dkg, - aad, - &ciphertext, - &validator_keypairs, - ); + let (_, _, shared_secret) = make_shared_secret_simple_tdec( + &dkg, + aad, + &ciphertext, + &validator_keypairs, + ); - let plaintext = tpke::decrypt_with_shared_secret( - &ciphertext, - aad, - &shared_secret, - &dkg.pvss_params.g_inv(), - ) - .unwrap(); - assert_eq!(plaintext, msg); + let plaintext = tpke::decrypt_with_shared_secret( + &ciphertext, + aad, + &shared_secret, + &dkg.pvss_params.g_inv(), + ) + .unwrap(); + assert_eq!(plaintext, msg); + } } #[test] fn test_dkg_simple_tdec_precomputed() { let rng = &mut test_rng(); - let (dkg, validator_keypairs) = setup_dealt_dkg_with_n_validators(3, 4); - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - let public_key = dkg.public_key(); - let ciphertext = tpke::encrypt::( - SecretBox::new(msg.clone()), - aad, - &public_key, - rng, - ) - .unwrap(); - - let pvss_aggregated = aggregate(&dkg.vss); - pvss_aggregated.verify_aggregation(&dkg).unwrap(); - let domain_points = dkg - .domain - .elements() - .take(validator_keypairs.len()) - .collect::>(); - - let decryption_shares: Vec> = - validator_keypairs - .iter() - .enumerate() - .map(|(validator_address, validator_keypair)| { - pvss_aggregated - .make_decryption_share_simple_precomputed( - &ciphertext, - aad, - &validator_keypair.decryption_key, - validator_address, - &domain_points, - &dkg.pvss_params.g_inv(), - ) - .unwrap() - }) - .collect(); - - let shared_secret = - tpke::share_combine_precomputed::(&decryption_shares); - - // Combination works, let's decrypt - let plaintext = tpke::decrypt_with_shared_secret( - &ciphertext, - aad, - &shared_secret, - &dkg.pvss_params.g_inv(), - ) - .unwrap(); - assert_eq!(plaintext, msg); + // Works for both power of 2 and non-power of 2 + for shares_num in [4, 7] { + // In precomputed variant, threshold must be equal to shares_num + let threshold = shares_num; + let (dkg, validator_keypairs) = + setup_dealt_dkg_with_n_validators(threshold, shares_num); + let msg = "my-msg".as_bytes().to_vec(); + let aad: &[u8] = "my-aad".as_bytes(); + let public_key = dkg.public_key(); + let ciphertext = tpke::encrypt::( + SecretBox::new(msg.clone()), + aad, + &public_key, + rng, + ) + .unwrap(); + + let pvss_aggregated = aggregate(&dkg.vss); + pvss_aggregated.verify_aggregation(&dkg).unwrap(); + let domain_points = dkg + .domain + .elements() + .take(validator_keypairs.len()) + .collect::>(); + + let decryption_shares: Vec> = + validator_keypairs + .iter() + .map(|validator_keypair| { + let validator = dkg + .get_validator(&validator_keypair.public_key()) + .unwrap(); + pvss_aggregated + .make_decryption_share_simple_precomputed( + &ciphertext, + aad, + &validator_keypair.decryption_key, + validator.share_index, + &domain_points, + &dkg.pvss_params.g_inv(), + ) + .unwrap() + }) + .collect(); + assert_eq!(domain_points.len(), decryption_shares.len()); + + let shared_secret = + tpke::share_combine_precomputed::(&decryption_shares); + + // Combination works, let's decrypt + let plaintext = tpke::decrypt_with_shared_secret( + &ciphertext, + aad, + &shared_secret, + &dkg.pvss_params.g_inv(), + ) + .unwrap(); + assert_eq!(plaintext, msg); + } } #[test] diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 50c7066c..cfc47925 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -346,6 +346,7 @@ impl PubliclyVerifiableSS { ) .map_err(|e| e.into()) } + pub fn make_decryption_share_simple_precomputed( &self, ciphertext: &Ciphertext, @@ -358,6 +359,7 @@ impl PubliclyVerifiableSS { let private_key_share = self .decrypt_private_key_share(validator_decryption_key, share_index); + // We use the `prepare_combine_simple` function to precompute the lagrange coefficients let lagrange_coeffs = prepare_combine_simple::(domain_points); DecryptionSharePrecomputed::new( diff --git a/subproductdomain/src/lib.rs b/subproductdomain/src/lib.rs index 63dd11cf..d1bc5fd0 100644 --- a/subproductdomain/src/lib.rs +++ b/subproductdomain/src/lib.rs @@ -9,7 +9,7 @@ use ark_ec::{ use ark_ff::{FftField, Field, Zero}; use ark_poly::{ univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, - Polynomial, Radix2EvaluationDomain, + MixedRadixEvaluationDomain, Polynomial, }; /// Compute a fast multiexp of many scalars times the same base @@ -342,7 +342,7 @@ pub fn toeplitz_mul( let m = polynomial.coeffs.len() - 1; let size = ark_std::cmp::max(size, m); - let domain = Radix2EvaluationDomain::::new(2 * size) + let domain = MixedRadixEvaluationDomain::::new(2 * size) .ok_or_else(|| { anyhow::anyhow!("toeplitz multiplication on too large a domain") })?; diff --git a/tpke/src/combine.rs b/tpke/src/combine.rs index 39091e88..dced0df5 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -161,7 +161,8 @@ mod tests { use ark_poly::EvaluationDomain; use ark_std::One; let fft_domain = - ark_poly::Radix2EvaluationDomain::::new(500).unwrap(); + ark_poly::MixedRadixEvaluationDomain::::new(500) + .unwrap(); let mut domain = Vec::with_capacity(500); let mut point = ScalarField::one(); diff --git a/tpke/src/decryption.rs b/tpke/src/decryption.rs index 9eb62471..c3b85eb5 100644 --- a/tpke/src/decryption.rs +++ b/tpke/src/decryption.rs @@ -166,7 +166,6 @@ impl DecryptionSharePrecomputed { g_inv: &E::G1Prepared, ) -> Result { check_ciphertext_validity::(ciphertext, aad, g_inv)?; - Self::create_unchecked( validator_index, validator_decryption_key, diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index 43ebdaa4..fcd35e9a 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -94,8 +94,10 @@ pub mod test_common { DensePolynomial::::rand(threshold - 1, rng); // Domain, or omega Ω let fft_domain = - ark_poly::Radix2EvaluationDomain::::new(shares_num) - .unwrap(); + ark_poly::MixedRadixEvaluationDomain::::new( + shares_num, + ) + .unwrap(); // `evals` are evaluations of the polynomial f over the domain, omega: f(ω_j) for ω_j in Ω let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain); @@ -193,8 +195,10 @@ pub mod test_common { DensePolynomial::::rand(threshold - 1, rng); // Domain, or omega Ω let fft_domain = - ark_poly::Radix2EvaluationDomain::::new(shares_num) - .unwrap(); + ark_poly::MixedRadixEvaluationDomain::::new( + shares_num, + ) + .unwrap(); // `evals` are evaluations of the polynomial f over the domain, omega: f(ω_j) for ω_j in Ω let evals = threshold_poly.evaluate_over_domain_by_ref(fft_domain);