From 9d3cb63c2f50e7b556af5f388f4ca8a969907a08 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 26 Jun 2023 14:31:51 +0200 Subject: [PATCH 1/6] benchmarks evaluation domains --- ferveo/benches/bench_main.rs | 1 + ferveo/benches/benchmarks/eval_domain.rs | 57 ++++++++++++++++++++++++ ferveo/benches/benchmarks/mod.rs | 1 + ferveo/src/pvss.rs | 2 +- 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 ferveo/benches/benchmarks/eval_domain.rs diff --git a/ferveo/benches/bench_main.rs b/ferveo/benches/bench_main.rs index fbd7c746..81bdb5d9 100644 --- a/ferveo/benches/bench_main.rs +++ b/ferveo/benches/bench_main.rs @@ -7,4 +7,5 @@ criterion_main! { // bench_batch_inverse, // benchmarks::pairing::ec, benchmarks::validity_checks::validity_checks, + benchmarks::eval_domain::eval_domain, } diff --git a/ferveo/benches/benchmarks/eval_domain.rs b/ferveo/benches/benchmarks/eval_domain.rs new file mode 100644 index 00000000..cdb6d3c1 --- /dev/null +++ b/ferveo/benches/benchmarks/eval_domain.rs @@ -0,0 +1,57 @@ +#![allow(clippy::redundant_closure)] +#![allow(clippy::unit_arg)] + +pub use ark_bls12_381::Bls12_381 as EllipticCurve; +use ark_ff::Field; +use ark_poly::EvaluationDomain; +use criterion::{black_box, criterion_group, BenchmarkId, Criterion}; +use digest::crypto_common::rand_core::SeedableRng; +use ferveo_pre_release::*; +use rand::prelude::StdRng; + +const NUM_SHARES_CASES: [usize; 6] = [2, 4, 8, 16, 32, 64]; + +pub fn bench_eval_domain(c: &mut Criterion) { + let mut group = c.benchmark_group("EVAL DOMAIN"); + group.sample_size(10); + + let rng = &mut StdRng::seed_from_u64(0); + let s = ark_bls12_381::Fr::from_random_bytes(&[0u8; 32]).unwrap(); + + for shares_num in NUM_SHARES_CASES { + let eval_radix2_eval_domain = { + let domain = + ark_poly::Radix2EvaluationDomain::new(shares_num).unwrap(); + let phi = SecretPolynomial::::new( + &s, shares_num, rng, + ); + + move || { + black_box(phi.0.evaluate_over_domain_by_ref(domain)); + } + }; + + let eval_mixed_eval_domain = { + let domain = + ark_poly::MixedRadixEvaluationDomain::new(shares_num).unwrap(); + let phi = SecretPolynomial::::new( + &s, shares_num, rng, + ); + + move || { + black_box(phi.0.evaluate_over_domain_by_ref(domain)); + } + }; + + group.bench_function( + BenchmarkId::new("eval_radix2_eval_domain", shares_num), + |b| b.iter(|| eval_radix2_eval_domain()), + ); + group.bench_function( + BenchmarkId::new("eval_mixed_eval_domain", shares_num), + |b| b.iter(|| eval_mixed_eval_domain()), + ); + } +} + +criterion_group!(eval_domain, bench_eval_domain); diff --git a/ferveo/benches/benchmarks/mod.rs b/ferveo/benches/benchmarks/mod.rs index 0a6bfabe..7e19ab37 100644 --- a/ferveo/benches/benchmarks/mod.rs +++ b/ferveo/benches/benchmarks/mod.rs @@ -1,3 +1,4 @@ //pub mod block_proposer; // pub mod pairing; +pub mod eval_domain; pub mod validity_checks; diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index d79f7c21..af05c4da 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -71,7 +71,7 @@ impl Default for PubliclyVerifiableParams { /// Secret polynomial used in the PVSS protocol /// We wrap this in a struct so that we can zeroize it after use -struct SecretPolynomial(DensePolynomial); +pub struct SecretPolynomial(pub DensePolynomial); impl SecretPolynomial { pub fn new( From aa7818320fed7b93d6c2e312e5bd7978da5d4717 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 26 Jun 2023 14:56:26 +0200 Subject: [PATCH 2/6] replace radix2 eval domain to mixed radix eval domain in ferveo --- ferveo/benches/benchmarks/eval_domain.rs | 2 +- ferveo/src/api.rs | 4 ++-- ferveo/src/dkg.rs | 11 ++++++----- ferveo/src/pvss.rs | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ferveo/benches/benchmarks/eval_domain.rs b/ferveo/benches/benchmarks/eval_domain.rs index cdb6d3c1..6a583f16 100644 --- a/ferveo/benches/benchmarks/eval_domain.rs +++ b/ferveo/benches/benchmarks/eval_domain.rs @@ -21,7 +21,7 @@ pub fn bench_eval_domain(c: &mut Criterion) { for shares_num in NUM_SHARES_CASES { let eval_radix2_eval_domain = { let domain = - ark_poly::Radix2EvaluationDomain::new(shares_num).unwrap(); + ark_poly::MixedRadixEvaluationDomain::new(shares_num).unwrap(); let phi = SecretPolynomial::::new( &s, shares_num, rng, ); diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 398d4582..a6ee4cd2 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -1,6 +1,6 @@ use std::io; -use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; +use ark_poly::{EvaluationDomain, MixedRadixEvaluationDomain}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::UniformRand; use bincode; @@ -204,7 +204,7 @@ impl AggregatedTranscript { messages: &[ValidatorMessage], ) -> Result { let pvss_params = PubliclyVerifiableParams::::default(); - let domain = Radix2EvaluationDomain::::new(shares_num as usize) + let domain = MixedRadixEvaluationDomain::::new(shares_num as usize) .expect("Unable to construct an evaluation domain"); let is_valid_optimistic = self.0.verify_optimistic(); diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 0da37084..9607718f 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -61,7 +61,7 @@ pub struct PubliclyVerifiableDkg { pub pvss_params: PubliclyVerifiableParams, pub validators: ValidatorsMap, pub vss: PVSSMap, - pub domain: ark_poly::Radix2EvaluationDomain, + pub domain: ark_poly::MixedRadixEvaluationDomain, pub me: DkgValidator, pub state: DkgState, } @@ -84,10 +84,11 @@ impl PubliclyVerifiableDkg { dkg_params.shares_num, )); } - let domain = ark_poly::Radix2EvaluationDomain::::new( - dkg_params.shares_num as usize, - ) - .expect("unable to construct domain"); + let domain = + ark_poly::MixedRadixEvaluationDomain::::new( + dkg_params.shares_num as usize, + ) + .expect("unable to construct domain"); // Sort the validators to verify a global ordering if !is_sorted(validators) { diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index af05c4da..50c7066c 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -224,7 +224,7 @@ pub fn do_verify_full( pvss_encrypted_shares: &[E::G2Affine], pvss_params: &PubliclyVerifiableParams, validators: &[Validator], - domain: &ark_poly::Radix2EvaluationDomain, + domain: &ark_poly::MixedRadixEvaluationDomain, ) -> bool { let mut commitment = batch_to_projective_g1::(pvss_coefficients); domain.fft_in_place(&mut commitment); @@ -256,7 +256,7 @@ pub fn do_verify_aggregation( pvss_agg_encrypted_shares: &[E::G2Affine], pvss_params: &PubliclyVerifiableParams, validators: &[Validator], - domain: &ark_poly::Radix2EvaluationDomain, + domain: &ark_poly::MixedRadixEvaluationDomain, vss: &PVSSMap, ) -> Result { let is_valid = do_verify_full( From 27c55d0c818d5a8e42801612519897844863190d Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Mon, 26 Jun 2023 15:25:04 +0200 Subject: [PATCH 3/6] remove enforcement on number of shares --- ferveo-python/ferveo/__init__.py | 1 - ferveo-python/ferveo/__init__.pyi | 4 ---- ferveo/src/bindings_python.rs | 8 -------- ferveo/src/dkg.rs | 12 ++---------- ferveo/src/lib.rs | 4 ---- ferveo/src/utils.rs | 4 ---- 6 files changed, 2 insertions(+), 31 deletions(-) diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index fbaab504..3f0b872e 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -16,7 +16,6 @@ SharedSecret, ValidatorMessage, ThresholdEncryptionError, - InvalidShareNumberParameter, InvalidDkgStateToDeal, InvalidDkgStateToAggregate, InvalidDkgStateToVerify, diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index b648301e..0dff36ae 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -201,10 +201,6 @@ class ThresholdEncryptionError(Exception): pass -class InvalidShareNumberParameter(Exception): - pass - - class InvalidDkgStateToDeal(Exception): pass diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 614b3608..3bf8f219 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -34,9 +34,6 @@ impl From for PyErr { Error::ThresholdEncryptionError(err) => { ThresholdEncryptionError::new_err(err.to_string()) } - Error::InvalidShareNumberParameter(actual) => { - InvalidShareNumberParameter::new_err(actual.to_string()) - } Error::InvalidDkgStateToDeal => { InvalidDkgStateToDeal::new_err("") } @@ -111,7 +108,6 @@ impl Debug for FerveoPythonError { } create_exception!(exceptions, ThresholdEncryptionError, PyException); -create_exception!(exceptions, InvalidShareNumberParameter, PyValueError); create_exception!(exceptions, InvalidDkgStateToDeal, PyRuntimeError); create_exception!(exceptions, InvalidDkgStateToAggregate, PyRuntimeError); create_exception!(exceptions, InvalidDkgStateToVerify, PyRuntimeError); @@ -596,10 +592,6 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { "ThresholdEncryptionError", py.get_type::(), )?; - m.add( - "InvalidShareNumberParameter", - py.get_type::(), - )?; m.add( "InvalidDkgStateToDeal", py.get_type::(), diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 9607718f..666e68df 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -9,10 +9,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_with::serde_as; use crate::{ - aggregate, - utils::{is_power_of_2, is_sorted}, - AggregatedPvss, Error, EthereumAddress, PubliclyVerifiableParams, - PubliclyVerifiableSS, Pvss, Result, Validator, + aggregate, utils::is_sorted, AggregatedPvss, Error, EthereumAddress, + PubliclyVerifiableParams, PubliclyVerifiableSS, Pvss, Result, Validator, }; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] @@ -78,12 +76,6 @@ impl PubliclyVerifiableDkg { dkg_params: &DkgParams, me: &Validator, ) -> Result { - // Make sure that the number of shares is a power of 2 for the FFT to work (Radix-2 FFT domain is being used) - if !is_power_of_2(dkg_params.shares_num) { - return Err(Error::InvalidShareNumberParameter( - dkg_params.shares_num, - )); - } let domain = ark_poly::MixedRadixEvaluationDomain::::new( dkg_params.shares_num as usize, diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 7e1b3657..c520b3f3 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -31,10 +31,6 @@ pub enum Error { #[error(transparent)] ThresholdEncryptionError(#[from] tpke::Error), - /// Number of shares parameter must be a power of two - #[error("Number of shares parameter must be a power of two. Got {0}")] - InvalidShareNumberParameter(u32), - /// DKG is not in a valid state to deal PVSS shares #[error("Invalid DKG state to deal PVSS shares")] InvalidDkgStateToDeal, diff --git a/ferveo/src/utils.rs b/ferveo/src/utils.rs index 62277430..b8b67b10 100644 --- a/ferveo/src/utils.rs +++ b/ferveo/src/utils.rs @@ -1,7 +1,3 @@ -pub fn is_power_of_2(n: u32) -> bool { - n != 0 && (n & (n - 1)) == 0 -} - pub fn is_sorted(data: I) -> bool where I: IntoIterator, From 8f45430fb8b6198ae7895d8a598b9d0380f1e568 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 4 Jul 2023 15:58:20 +0200 Subject: [PATCH 4/6] precomputed variant fails for non-power-of-two number of shares --- ferveo/src/api.rs | 346 ++++++++++++++++++++++++---------------------- 1 file changed, 179 insertions(+), 167 deletions(-) diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index a6ee4cd2..47e28a7d 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -374,179 +374,191 @@ mod test_ferveo_api { fn test_server_api_tdec_precomputed() { let rng = &mut StdRng::seed_from_u64(0); - let tau = 1; - let shares_num = 4; - // In precomputed variant, the security threshold is equal to the number of shares - // TODO: Refactor DKG constructor to not require security threshold or this case. - // Or figure out a different way to simplify the precomputed variant API. - let security_threshold = shares_num; - - let (messages, validators, validator_keypairs) = - make_test_inputs(rng, tau, security_threshold, shares_num); - - // Now that every validator holds a dkg instance and a transcript for every other validator, - // every validator can aggregate the transcripts - let me = validators[0].clone(); - let mut dkg = - Dkg::new(tau, shares_num, security_threshold, &validators, &me) - .unwrap(); - - let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); - - // At this point, any given validator should be able to provide a DKG public key - let dkg_public_key = dkg.public_key(); - - // In the meantime, the client creates a ciphertext and decryption request - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - let rng = &mut thread_rng(); - let ciphertext = tpke::api::encrypt( - SecretBox::new(msg.clone()), - aad, - &dkg_public_key.0, - rng, - ) - .unwrap(); - - // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs) - .map(|(validator, validator_keypair)| { - // Each validator holds their own instance of DKG and creates their own aggregate - let mut dkg = Dkg::new( - tau, - shares_num, - security_threshold, - &validators, - validator, - ) - .unwrap(); - let aggregate = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); - aggregate - .create_decryption_share_precomputed( - &dkg, - &ciphertext, - aad, - validator_keypair, - ) - .unwrap() - }) - .collect(); - - // Now, the decryption share can be used to decrypt the ciphertext - // This part is part of the client API - - let shared_secret = share_combine_precomputed(&decryption_shares); - let plaintext = decrypt_with_shared_secret( - &ciphertext, - aad, - &SharedSecret(shared_secret), - ) - .unwrap(); - assert_eq!(plaintext, msg); - - // Since we're using a precomputed variant, we need all the shares to be able to decrypt - // So if we remove one share, we should not be able to decrypt - let decryption_shares = - decryption_shares[..shares_num as usize - 1].to_vec(); - - let shared_secret = share_combine_precomputed(&decryption_shares); - let result = decrypt_with_shared_secret( - &ciphertext, - aad, - &SharedSecret(shared_secret), - ); - assert!(result.is_err()); + // Works for both power of 2 and non-power of 2 + for shares_num in [4, 7] { + let tau = 1; + // In precomputed variant, the security threshold is equal to the number of shares + // TODO: Refactor DKG constructor to not require security threshold or this case. + // Or figure out a different way to simplify the precomputed variant API. + let security_threshold = shares_num; + + let (messages, validators, validator_keypairs) = + make_test_inputs(rng, tau, security_threshold, shares_num); + + // Now that every validator holds a dkg instance and a transcript for every other validator, + // every validator can aggregate the transcripts + let me = validators[0].clone(); + let mut dkg = + Dkg::new(tau, shares_num, security_threshold, &validators, &me) + .unwrap(); + + let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); + assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); + + // At this point, any given validator should be able to provide a DKG public key + let dkg_public_key = dkg.public_key(); + + // In the meantime, the client creates a ciphertext and decryption request + let msg = "my-msg".as_bytes().to_vec(); + let aad: &[u8] = "my-aad".as_bytes(); + let rng = &mut thread_rng(); + let ciphertext = tpke::api::encrypt( + SecretBox::new(msg.clone()), + aad, + &dkg_public_key.0, + rng, + ) + .unwrap(); + + // Having aggregated the transcripts, the validators can now create decryption shares + let decryption_shares: Vec<_> = + izip!(&validators, &validator_keypairs) + .map(|(validator, validator_keypair)| { + // Each validator holds their own instance of DKG and creates their own aggregate + let mut dkg = Dkg::new( + tau, + shares_num, + security_threshold, + &validators, + validator, + ) + .unwrap(); + let aggregate = + dkg.aggregate_transcripts(&messages).unwrap(); + assert!(pvss_aggregated + .verify(shares_num, &messages) + .unwrap()); + aggregate + .create_decryption_share_precomputed( + &dkg, + &ciphertext, + aad, + validator_keypair, + ) + .unwrap() + }) + .collect(); + + // Now, the decryption share can be used to decrypt the ciphertext + // This part is part of the client API + + let shared_secret = share_combine_precomputed(&decryption_shares); + let plaintext = decrypt_with_shared_secret( + &ciphertext, + aad, + &SharedSecret(shared_secret), + ) + .unwrap(); + assert_eq!(plaintext, msg); + + // Since we're using a precomputed variant, we need all the shares to be able to decrypt + // So if we remove one share, we should not be able to decrypt + let decryption_shares = + decryption_shares[..shares_num as usize - 1].to_vec(); + + let shared_secret = share_combine_precomputed(&decryption_shares); + let result = decrypt_with_shared_secret( + &ciphertext, + aad, + &SharedSecret(shared_secret), + ); + assert!(result.is_err()); + } } #[test] fn test_server_api_tdec_simple() { let rng = &mut StdRng::seed_from_u64(0); - let tau = 1; - let shares_num = 4; - let security_threshold = 3; - - let (messages, validators, validator_keypairs) = - make_test_inputs(rng, tau, security_threshold, shares_num); - - // Now that every validator holds a dkg instance and a transcript for every other validator, - // every validator can aggregate the transcripts - let mut dkg = Dkg::new( - tau, - shares_num, - security_threshold, - &validators, - &validators[0], - ) - .unwrap(); - - let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); - - // At this point, any given validator should be able to provide a DKG public key - let public_key = dkg.public_key(); - - // In the meantime, the client creates a ciphertext and decryption request - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); - let rng = &mut thread_rng(); - let ciphertext = tpke::api::encrypt( - SecretBox::new(msg.clone()), - aad, - &public_key.0, - rng, - ) - .unwrap(); - - // Having aggregated the transcripts, the validators can now create decryption shares - let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs) - .map(|(validator, validator_keypair)| { - // Each validator holds their own instance of DKG and creates their own aggregate - let mut dkg = Dkg::new( - tau, - shares_num, - security_threshold, - &validators, - validator, - ) - .unwrap(); - let aggregate = dkg.aggregate_transcripts(&messages).unwrap(); - assert!(aggregate.verify(shares_num, &messages).unwrap()); - aggregate - .create_decryption_share_simple( - &dkg, - &ciphertext, - aad, - validator_keypair, - ) - .unwrap() - }) - .collect(); - - // Now, the decryption share can be used to decrypt the ciphertext - // This part is part of the client API - - // In simple variant, we only need `security_threshold` shares to be able to decrypt - let decryption_shares = - decryption_shares[..security_threshold as usize].to_vec(); - - let shared_secret = combine_shares_simple(&decryption_shares); - let plaintext = - decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) - .unwrap(); - assert_eq!(plaintext, msg); - - // Let's say that we've only received `security_threshold - 1` shares - // In this case, we should not be able to decrypt - let decryption_shares = - decryption_shares[..security_threshold as usize - 1].to_vec(); - - let shared_secret = combine_shares_simple(&decryption_shares); - let result = - decrypt_with_shared_secret(&ciphertext, aad, &shared_secret); - assert!(result.is_err()); + // Works for both power of 2 and non-power of 2 + for shares_num in [4, 7] { + let tau = 1; + let security_threshold = shares_num / 2 + 1; + + let (messages, validators, validator_keypairs) = + make_test_inputs(rng, tau, security_threshold, shares_num); + + // Now that every validator holds a dkg instance and a transcript for every other validator, + // every validator can aggregate the transcripts + let mut dkg = Dkg::new( + tau, + shares_num, + security_threshold, + &validators, + &validators[0], + ) + .unwrap(); + + let pvss_aggregated = dkg.aggregate_transcripts(&messages).unwrap(); + assert!(pvss_aggregated.verify(shares_num, &messages).unwrap()); + + // At this point, any given validator should be able to provide a DKG public key + let public_key = dkg.public_key(); + + // In the meantime, the client creates a ciphertext and decryption request + let msg = "my-msg".as_bytes().to_vec(); + let aad: &[u8] = "my-aad".as_bytes(); + let rng = &mut thread_rng(); + let ciphertext = tpke::api::encrypt( + SecretBox::new(msg.clone()), + aad, + &public_key.0, + rng, + ) + .unwrap(); + + // Having aggregated the transcripts, the validators can now create decryption shares + let decryption_shares: Vec<_> = + izip!(&validators, &validator_keypairs) + .map(|(validator, validator_keypair)| { + // Each validator holds their own instance of DKG and creates their own aggregate + let mut dkg = Dkg::new( + tau, + shares_num, + security_threshold, + &validators, + validator, + ) + .unwrap(); + let aggregate = + dkg.aggregate_transcripts(&messages).unwrap(); + assert!(aggregate + .verify(shares_num, &messages) + .unwrap()); + aggregate + .create_decryption_share_simple( + &dkg, + &ciphertext, + aad, + validator_keypair, + ) + .unwrap() + }) + .collect(); + + // Now, the decryption share can be used to decrypt the ciphertext + // This part is part of the client API + + // In simple variant, we only need `security_threshold` shares to be able to decrypt + let decryption_shares = + decryption_shares[..security_threshold as usize].to_vec(); + + let shared_secret = combine_shares_simple(&decryption_shares); + let plaintext = + decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) + .unwrap(); + assert_eq!(plaintext, msg); + + // Let's say that we've only received `security_threshold - 1` shares + // In this case, we should not be able to decrypt + let decryption_shares = + decryption_shares[..security_threshold as usize - 1].to_vec(); + + let shared_secret = combine_shares_simple(&decryption_shares); + let result = + decrypt_with_shared_secret(&ciphertext, aad, &shared_secret); + assert!(result.is_err()); + } } #[test] From d5ec5e0f9d1303e51a805c4dafbab7ed2efcb7be Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 6 Jul 2023 13:41:46 +0200 Subject: [PATCH 5/6] fix using bad number of domain points --- ferveo/src/api.rs | 9 +- ferveo/src/lib.rs | 170 +++++++++++++++++++----------------- ferveo/src/pvss.rs | 2 + subproductdomain/src/lib.rs | 4 +- tpke/src/combine.rs | 3 +- tpke/src/decryption.rs | 1 - tpke/src/lib.rs | 12 ++- 7 files changed, 114 insertions(+), 87 deletions(-) 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); From 2c20efb59d7d1075d6b1413b2ae7fbb55c422143 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 6 Jul 2023 13:46:07 +0200 Subject: [PATCH 6/6] use general evaluation domain --- ferveo/benches/benchmarks/eval_domain.rs | 4 ++-- ferveo/src/api.rs | 4 ++-- ferveo/src/dkg.rs | 11 +++++------ ferveo/src/pvss.rs | 4 ++-- subproductdomain/src/lib.rs | 4 ++-- tpke/src/combine.rs | 5 ++--- tpke/src/lib.rs | 8 ++++---- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/ferveo/benches/benchmarks/eval_domain.rs b/ferveo/benches/benchmarks/eval_domain.rs index 6a583f16..23cd8065 100644 --- a/ferveo/benches/benchmarks/eval_domain.rs +++ b/ferveo/benches/benchmarks/eval_domain.rs @@ -21,7 +21,7 @@ pub fn bench_eval_domain(c: &mut Criterion) { for shares_num in NUM_SHARES_CASES { let eval_radix2_eval_domain = { let domain = - ark_poly::MixedRadixEvaluationDomain::new(shares_num).unwrap(); + ark_poly::GeneralEvaluationDomain::new(shares_num).unwrap(); let phi = SecretPolynomial::::new( &s, shares_num, rng, ); @@ -33,7 +33,7 @@ pub fn bench_eval_domain(c: &mut Criterion) { let eval_mixed_eval_domain = { let domain = - ark_poly::MixedRadixEvaluationDomain::new(shares_num).unwrap(); + ark_poly::GeneralEvaluationDomain::new(shares_num).unwrap(); let phi = SecretPolynomial::::new( &s, shares_num, rng, ); diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 8f5ebe87..77db4870 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -1,6 +1,6 @@ use std::io; -use ark_poly::{EvaluationDomain, MixedRadixEvaluationDomain}; +use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::UniformRand; use bincode; @@ -204,7 +204,7 @@ impl AggregatedTranscript { messages: &[ValidatorMessage], ) -> Result { let pvss_params = PubliclyVerifiableParams::::default(); - let domain = MixedRadixEvaluationDomain::::new(shares_num as usize) + let domain = GeneralEvaluationDomain::::new(shares_num as usize) .expect("Unable to construct an evaluation domain"); let is_valid_optimistic = self.0.verify_optimistic(); diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index 666e68df..07dee015 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -59,7 +59,7 @@ pub struct PubliclyVerifiableDkg { pub pvss_params: PubliclyVerifiableParams, pub validators: ValidatorsMap, pub vss: PVSSMap, - pub domain: ark_poly::MixedRadixEvaluationDomain, + pub domain: ark_poly::GeneralEvaluationDomain, pub me: DkgValidator, pub state: DkgState, } @@ -76,11 +76,10 @@ impl PubliclyVerifiableDkg { dkg_params: &DkgParams, me: &Validator, ) -> Result { - let domain = - ark_poly::MixedRadixEvaluationDomain::::new( - dkg_params.shares_num as usize, - ) - .expect("unable to construct domain"); + let domain = ark_poly::GeneralEvaluationDomain::::new( + dkg_params.shares_num as usize, + ) + .expect("unable to construct domain"); // Sort the validators to verify a global ordering if !is_sorted(validators) { diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index cfc47925..0d6433fd 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -224,7 +224,7 @@ pub fn do_verify_full( pvss_encrypted_shares: &[E::G2Affine], pvss_params: &PubliclyVerifiableParams, validators: &[Validator], - domain: &ark_poly::MixedRadixEvaluationDomain, + domain: &ark_poly::GeneralEvaluationDomain, ) -> bool { let mut commitment = batch_to_projective_g1::(pvss_coefficients); domain.fft_in_place(&mut commitment); @@ -256,7 +256,7 @@ pub fn do_verify_aggregation( pvss_agg_encrypted_shares: &[E::G2Affine], pvss_params: &PubliclyVerifiableParams, validators: &[Validator], - domain: &ark_poly::MixedRadixEvaluationDomain, + domain: &ark_poly::GeneralEvaluationDomain, vss: &PVSSMap, ) -> Result { let is_valid = do_verify_full( diff --git a/subproductdomain/src/lib.rs b/subproductdomain/src/lib.rs index d1bc5fd0..4fa52900 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, - MixedRadixEvaluationDomain, Polynomial, + GeneralEvaluationDomain, 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 = MixedRadixEvaluationDomain::::new(2 * size) + let domain = GeneralEvaluationDomain::::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 dced0df5..a46477fb 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -161,14 +161,13 @@ mod tests { use ark_poly::EvaluationDomain; use ark_std::One; let fft_domain = - ark_poly::MixedRadixEvaluationDomain::::new(500) - .unwrap(); + ark_poly::GeneralEvaluationDomain::::new(500).unwrap(); let mut domain = Vec::with_capacity(500); let mut point = ScalarField::one(); for _ in 0..500 { domain.push(point); - point *= fft_domain.group_gen; + point *= fft_domain.group_gen(); } let mut lagrange_n_0 = domain.iter().product::(); diff --git a/tpke/src/lib.rs b/tpke/src/lib.rs index fcd35e9a..651935ae 100644 --- a/tpke/src/lib.rs +++ b/tpke/src/lib.rs @@ -94,7 +94,7 @@ pub mod test_common { DensePolynomial::::rand(threshold - 1, rng); // Domain, or omega Ω let fft_domain = - ark_poly::MixedRadixEvaluationDomain::::new( + ark_poly::GeneralEvaluationDomain::::new( shares_num, ) .unwrap(); @@ -123,9 +123,9 @@ pub mod test_common { for _ in 0..shares_num { domain_points.push(point); // 1, t, t^2, t^3, ...; where t is a scalar generator fft_domain.group_gen - point *= fft_domain.group_gen; + point *= fft_domain.group_gen(); domain_points_inv.push(point_inv); - point_inv *= fft_domain.group_gen_inv; + point_inv *= fft_domain.group_gen_inv(); } let mut private_contexts = vec![]; @@ -195,7 +195,7 @@ pub mod test_common { DensePolynomial::::rand(threshold - 1, rng); // Domain, or omega Ω let fft_domain = - ark_poly::MixedRadixEvaluationDomain::::new( + ark_poly::GeneralEvaluationDomain::::new( shares_num, ) .unwrap();