diff --git a/ferveo-python/ferveo/__init__.py b/ferveo-python/ferveo/__init__.py index 869f3a18..43b4bbd6 100644 --- a/ferveo-python/ferveo/__init__.py +++ b/ferveo-python/ferveo/__init__.py @@ -17,7 +17,6 @@ ValidatorMessage, FerveoVariant, ThresholdEncryptionError, - InvalidShareNumberParameter, InvalidDkgStateToDeal, InvalidDkgStateToAggregate, InvalidDkgStateToVerify, diff --git a/ferveo-python/ferveo/__init__.pyi b/ferveo-python/ferveo/__init__.pyi index afd3d142..e16189d1 100644 --- a/ferveo-python/ferveo/__init__.pyi +++ b/ferveo-python/ferveo/__init__.pyi @@ -209,10 +209,6 @@ class ThresholdEncryptionError(Exception): pass -class InvalidShareNumberParameter(Exception): - pass - - class InvalidDkgStateToDeal(Exception): pass 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..23cd8065 --- /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::GeneralEvaluationDomain::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::GeneralEvaluationDomain::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/api.rs b/ferveo/src/api.rs index 8bfc3898..10b283a7 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -1,6 +1,6 @@ use std::{fmt, io}; -use ark_poly::{EvaluationDomain, Radix2EvaluationDomain}; +use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::UniformRand; use bincode; @@ -236,7 +236,7 @@ impl AggregatedTranscript { messages: &[ValidatorMessage], ) -> Result { let pvss_params = PubliclyVerifiableParams::::default(); - let domain = Radix2EvaluationDomain::::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(); @@ -270,7 +270,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, @@ -406,179 +411,193 @@ 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()); + + // And then each validator creates their own decryption share + 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] diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 8add85c8..cf19ebc9 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("") } @@ -114,7 +111,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); @@ -617,10 +613,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 0da37084..07dee015 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)] @@ -61,7 +59,7 @@ pub struct PubliclyVerifiableDkg { pub pvss_params: PubliclyVerifiableParams, pub validators: ValidatorsMap, pub vss: PVSSMap, - pub domain: ark_poly::Radix2EvaluationDomain, + pub domain: ark_poly::GeneralEvaluationDomain, pub me: DkgValidator, pub state: DkgState, } @@ -78,13 +76,7 @@ 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::Radix2EvaluationDomain::::new( + let domain = ark_poly::GeneralEvaluationDomain::::new( dkg_params.shares_num as usize, ) .expect("unable to construct domain"); diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 036e2fca..214f9444 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, @@ -173,17 +169,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, @@ -196,89 +192,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 d79f7c21..0d6433fd 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( @@ -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::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::Radix2EvaluationDomain, + domain: &ark_poly::GeneralEvaluationDomain, vss: &PVSSMap, ) -> Result { let is_valid = do_verify_full( @@ -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/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, diff --git a/subproductdomain/src/lib.rs b/subproductdomain/src/lib.rs index 63dd11cf..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, - Polynomial, Radix2EvaluationDomain, + 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 = Radix2EvaluationDomain::::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 39091e88..a46477fb 100644 --- a/tpke/src/combine.rs +++ b/tpke/src/combine.rs @@ -161,13 +161,13 @@ mod tests { use ark_poly::EvaluationDomain; use ark_std::One; let fft_domain = - ark_poly::Radix2EvaluationDomain::::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/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..651935ae 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::GeneralEvaluationDomain::::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); @@ -121,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![]; @@ -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::GeneralEvaluationDomain::::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);