From 3fe080e1dff835286d9058a5e39d054f8b8c4da5 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Fri, 19 Jan 2024 12:54:29 +0100 Subject: [PATCH] refactor(test): deduplicate test utils --- ferveo/src/api.rs | 63 ++++++++--------- ferveo/src/bindings_python.rs | 64 ++++++++---------- ferveo/src/bindings_wasm.rs | 1 - ferveo/src/dkg.rs | 124 ++++------------------------------ ferveo/src/lib.rs | 99 +++++++++++++-------------- ferveo/src/pvss.rs | 6 +- ferveo/src/refresh.rs | 40 +++++------ ferveo/src/test_common.rs | 110 ++++++++++++++++++++++++++++++ 8 files changed, 242 insertions(+), 265 deletions(-) create mode 100644 ferveo/src/test_common.rs diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 0e610658..437bbc59 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -9,7 +9,10 @@ pub use ferveo_tdec::api::{ prepare_combine_simple, share_combine_precomputed, share_combine_simple, Fr, G1Affine, G1Prepared, G2Affine, SecretBox, E, }; -use generic_array::{typenum::U48, GenericArray}; +use generic_array::{ + typenum::{Unsigned, U48}, + GenericArray, +}; use rand::RngCore; use serde::{Deserialize, Serialize}; use serde_with::serde_as; @@ -167,7 +170,7 @@ impl DkgPublicKey { } pub fn serialized_size() -> usize { - 48 + U48::to_usize() } /// Generate a random DKG public key. @@ -399,12 +402,10 @@ mod test_ferveo_api { use rand::{prelude::StdRng, SeedableRng}; use test_case::test_case; - use crate::{api::*, dkg::test_common::*}; + use crate::{api::*, test_common::*}; type TestInputs = (Vec, Vec, Vec); - const TAU: u32 = 1; - fn make_test_inputs( rng: &mut StdRng, tau: u32, @@ -446,6 +447,7 @@ mod test_ferveo_api { let dkg_pk = DkgPublicKey::random(); let serialized = dkg_pk.to_bytes().unwrap(); let deserialized = DkgPublicKey::from_bytes(&serialized).unwrap(); + assert_eq!(serialized.len(), 48_usize); assert_eq!(dkg_pk, deserialized); } @@ -476,10 +478,9 @@ mod test_ferveo_api { 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 ciphertext = - encrypt(SecretBox::new(msg.clone()), aad, &dkg_public_key).unwrap(); + encrypt(SecretBox::new(MSG.to_vec()), AAD, &dkg_public_key) + .unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs) @@ -501,7 +502,7 @@ mod test_ferveo_api { .create_decryption_share_precomputed( &dkg, &ciphertext.header().unwrap(), - aad, + AAD, validator_keypair, ) .unwrap() @@ -514,11 +515,11 @@ mod test_ferveo_api { let shared_secret = share_combine_precomputed(&decryption_shares); let plaintext = decrypt_with_shared_secret( &ciphertext, - aad, + AAD, &SharedSecret(shared_secret), ) .unwrap(); - assert_eq!(plaintext, msg); + 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 @@ -528,7 +529,7 @@ mod test_ferveo_api { let shared_secret = share_combine_precomputed(&decryption_shares); let result = decrypt_with_shared_secret( &ciphertext, - aad, + AAD, &SharedSecret(shared_secret), ); assert!(result.is_err()); @@ -562,10 +563,8 @@ mod test_ferveo_api { 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 ciphertext = - encrypt(SecretBox::new(msg.clone()), aad, &public_key).unwrap(); + encrypt(SecretBox::new(MSG.to_vec()), AAD, &public_key).unwrap(); // Having aggregated the transcripts, the validators can now create decryption shares let decryption_shares: Vec<_> = izip!(&validators, &validator_keypairs) @@ -585,7 +584,7 @@ mod test_ferveo_api { .create_decryption_share_simple( &dkg, &ciphertext.header().unwrap(), - aad, + AAD, validator_keypair, ) .unwrap() @@ -601,9 +600,9 @@ mod test_ferveo_api { let shared_secret = combine_shares_simple(&decryption_shares); let plaintext = - decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) + decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret) .unwrap(); - assert_eq!(plaintext, msg); + 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 @@ -612,7 +611,7 @@ mod test_ferveo_api { let shared_secret = combine_shares_simple(&decryption_shares); let result = - decrypt_with_shared_secret(&ciphertext, aad, &shared_secret); + decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret); assert!(result.is_err()); } @@ -620,17 +619,14 @@ mod test_ferveo_api { fn server_side_local_verification() { let rng = &mut StdRng::seed_from_u64(0); - let security_threshold = 3; - let shares_num = 4; - let (messages, validators, _) = - make_test_inputs(rng, TAU, security_threshold, shares_num); + 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) + Dkg::new(TAU, SHARES_NUM, SECURITY_THRESHOLD, &validators, &me) .unwrap(); let local_aggregate = dkg.aggregate_transcripts(&messages).unwrap(); @@ -643,14 +639,11 @@ mod test_ferveo_api { fn client_side_local_verification() { let rng = &mut StdRng::seed_from_u64(0); - let security_threshold = 3; - let shares_num = 4; - let (messages, _, _) = - make_test_inputs(rng, TAU, security_threshold, shares_num); + make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM); // We only need `security_threshold` transcripts to aggregate - let messages = &messages[..security_threshold as usize]; + let messages = &messages[..SECURITY_THRESHOLD as usize]; // Create an aggregated transcript on the client side let aggregated_transcript = AggregatedTranscript::new(messages); @@ -659,27 +652,27 @@ mod test_ferveo_api { // the aggregate from a side-channel or decide to persist it and verify it later // Now, the client can verify the aggregated transcript - let result = aggregated_transcript.verify(shares_num, messages); + let result = aggregated_transcript.verify(SHARES_NUM, messages); assert!(result.is_ok()); assert!(result.unwrap()); // Test negative cases // Not enough transcripts - let not_enough_messages = &messages[..2]; - assert!(not_enough_messages.len() < security_threshold as usize); + let not_enough_messages = &messages[..SECURITY_THRESHOLD as usize - 1]; + assert!(not_enough_messages.len() < SECURITY_THRESHOLD as usize); let insufficient_aggregate = AggregatedTranscript::new(not_enough_messages); - let result = insufficient_aggregate.verify(shares_num, messages); + let result = insufficient_aggregate.verify(SHARES_NUM, messages); assert!(result.is_err()); // Unexpected transcripts in the aggregate or transcripts from a different ritual // Using same DKG parameters, but different DKG instances and validators let (bad_messages, _, _) = - make_test_inputs(rng, TAU, security_threshold, shares_num); + make_test_inputs(rng, TAU, SECURITY_THRESHOLD, SHARES_NUM); let mixed_messages = [&messages[..2], &bad_messages[..1]].concat(); let bad_aggregate = AggregatedTranscript::new(&mixed_messages); - let result = bad_aggregate.verify(shares_num, messages); + let result = bad_aggregate.verify(SHARES_NUM, messages); assert!(result.is_err()); } } diff --git a/ferveo/src/bindings_python.rs b/ferveo/src/bindings_python.rs index 00c455a3..9b472d34 100644 --- a/ferveo/src/bindings_python.rs +++ b/ferveo/src/bindings_python.rs @@ -739,7 +739,7 @@ pub fn make_ferveo_py_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { mod test_ferveo_python { use itertools::izip; - use crate::bindings_python::*; + use crate::{bindings_python::*, test_common::*}; type TestInputs = (Vec, Vec, Vec); @@ -785,21 +785,19 @@ mod test_ferveo_python { #[test] fn test_server_api_tdec_precomputed() { - let tau = 1; - let shares_num = 4; // In precomputed variant, the security threshold is equal to the number of shares - let security_threshold = shares_num; + let security_threshold = SHARES_NUM; let (messages, validators, validator_keypairs) = - make_test_inputs(tau, security_threshold, shares_num); + make_test_inputs(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, + TAU, + SHARES_NUM, security_threshold, validators.clone(), &me, @@ -811,24 +809,22 @@ mod test_ferveo_python { let pvss_aggregated = dkg.aggregate_transcripts(messages.clone()).unwrap(); assert!(pvss_aggregated - .verify(shares_num, messages.clone()) + .verify(SHARES_NUM, messages.clone()) .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: &[u8] = "my-msg".as_bytes(); - let aad: &[u8] = "my-aad".as_bytes(); - let ciphertext = encrypt(msg, aad, &dkg_public_key).unwrap(); + let ciphertext = encrypt(MSG, AAD, &dkg_public_key).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, + TAU, + SHARES_NUM, security_threshold, validators.clone(), validator, @@ -837,13 +833,13 @@ mod test_ferveo_python { let aggregate = dkg.aggregate_transcripts(messages.clone()).unwrap(); assert!(pvss_aggregated - .verify(shares_num, messages.clone()) + .verify(SHARES_NUM, messages.clone()) .is_ok()); aggregate .create_decryption_share_precomputed( &dkg, &ciphertext.header().unwrap(), - aad, + AAD, validator_keypair, ) .unwrap() @@ -857,56 +853,50 @@ mod test_ferveo_python { combine_decryption_shares_precomputed(decryption_shares); let plaintext = - decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) + decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret) .unwrap(); - assert_eq!(plaintext, msg); + assert_eq!(plaintext, MSG); } #[test] fn test_server_api_tdec_simple() { - let tau = 1; - let shares_num = 4; - let security_threshold = 3; - let (messages, validators, validator_keypairs) = - make_test_inputs(tau, security_threshold, shares_num); + make_test_inputs(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, + TAU, + SHARES_NUM, + SECURITY_THRESHOLD, validators.clone(), &me, ) .unwrap(); // Lets say that we've only receives `security_threshold` transcripts - let messages = messages[..security_threshold as usize].to_vec(); + let messages = messages[..SECURITY_THRESHOLD as usize].to_vec(); let pvss_aggregated = dkg.aggregate_transcripts(messages.clone()).unwrap(); assert!(pvss_aggregated - .verify(shares_num, messages.clone()) + .verify(SHARES_NUM, messages.clone()) .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: &[u8] = "my-msg".as_bytes(); - let aad: &[u8] = "my-aad".as_bytes(); - let ciphertext = encrypt(msg, aad, &dkg_public_key).unwrap(); + let ciphertext = encrypt(MSG, AAD, &dkg_public_key).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, + TAU, + SHARES_NUM, + SECURITY_THRESHOLD, validators.clone(), validator, ) @@ -914,13 +904,13 @@ mod test_ferveo_python { let aggregate = dkg.aggregate_transcripts(messages.clone()).unwrap(); assert!(aggregate - .verify(shares_num, messages.clone()) + .verify(SHARES_NUM, messages.clone()) .unwrap()); aggregate .create_decryption_share_simple( &dkg, &ciphertext.header().unwrap(), - aad, + AAD, validator_keypair, ) .unwrap() @@ -933,8 +923,8 @@ mod test_ferveo_python { let shared_secret = combine_decryption_shares_simple(decryption_shares); let plaintext = - decrypt_with_shared_secret(&ciphertext, aad, &shared_secret) + decrypt_with_shared_secret(&ciphertext, AAD, &shared_secret) .unwrap(); - assert_eq!(plaintext, msg); + assert_eq!(plaintext, MSG); } } diff --git a/ferveo/src/bindings_wasm.rs b/ferveo/src/bindings_wasm.rs index 5b03f188..a1310277 100644 --- a/ferveo/src/bindings_wasm.rs +++ b/ferveo/src/bindings_wasm.rs @@ -595,7 +595,6 @@ impl Keypair { } } -/// Factory functions for testing pub mod test_common { use crate::bindings_wasm::*; diff --git a/ferveo/src/dkg.rs b/ferveo/src/dkg.rs index dbab8e03..e13c4894 100644 --- a/ferveo/src/dkg.rs +++ b/ferveo/src/dkg.rs @@ -361,110 +361,14 @@ pub enum Message { Aggregate(Aggregation), } -/// Factory functions for testing -#[cfg(test)] -pub(crate) mod test_common { - use std::str::FromStr; - - pub use ark_bls12_381::Bls12_381 as E; - use ferveo_common::Keypair; - - pub use super::*; - - pub type G1 = ::G1Affine; - - pub fn gen_keypairs(n: u32) -> Vec> { - let rng = &mut ark_std::test_rng(); - (0..n).map(|_| Keypair::::new(rng)).collect() - } - - pub fn gen_address(i: usize) -> EthereumAddress { - EthereumAddress::from_str(&format!("0x{i:040}")).unwrap() - } - - pub fn gen_validators(keypairs: &[Keypair]) -> Vec> { - keypairs - .iter() - .enumerate() - .map(|(i, keypair)| Validator { - address: gen_address(i), - public_key: keypair.public_key(), - }) - .collect() - } - - pub type TestSetup = (PubliclyVerifiableDkg, Vec>); - - pub fn setup_dkg_for_n_validators( - security_threshold: u32, - shares_num: u32, - my_index: usize, - ) -> TestSetup { - let keypairs = gen_keypairs(shares_num); - let mut validators = gen_validators(keypairs.as_slice()); - validators.sort(); - let me = validators[my_index].clone(); - let dkg = PubliclyVerifiableDkg::new( - &validators, - &DkgParams { - tau: 0, - security_threshold, - shares_num, - }, - &me, - ) - .expect("Setup failed"); - (dkg, keypairs) - } - - /// Create a test dkg - /// - /// The [`test_dkg_init`] module checks correctness of this setup - pub fn setup_dkg(validator: usize) -> TestSetup { - setup_dkg_for_n_validators(2, 4, validator) - } - - /// Set up a dkg with enough pvss transcripts to meet the threshold - /// - /// The correctness of this function is tested in the module [`test_dealing`] - pub fn setup_dealt_dkg() -> TestSetup { - setup_dealt_dkg_with_n_validators(2, 4) - } - - pub fn setup_dealt_dkg_with_n_validators( - security_threshold: u32, - shares_num: u32, - ) -> TestSetup { - let rng = &mut ark_std::test_rng(); - - // Gather everyone's transcripts - let messages: Vec<_> = (0..shares_num) - .map(|my_index| { - let (mut dkg, _) = setup_dkg_for_n_validators( - security_threshold, - shares_num, - my_index as usize, - ); - let me = dkg.me.validator.clone(); - let message = dkg.share(rng).unwrap(); - (me, message) - }) - .collect(); - - // Create a test DKG instance - let (mut dkg, keypairs) = - setup_dkg_for_n_validators(security_threshold, shares_num, 0); - messages.iter().for_each(|(sender, message)| { - dkg.apply_message(sender, message).expect("Setup failed"); - }); - (dkg, keypairs) - } -} - /// Test initializing DKG #[cfg(test)] mod test_dkg_init { - use super::test_common::*; + use crate::{ + dkg::{PubliclyVerifiableDkg, Validator}, + test_common::*, + DkgParams, + }; /// Test that dkg fails to start if the `me` input /// is not in the validator set @@ -480,11 +384,7 @@ mod test_dkg_init { }; let err = PubliclyVerifiableDkg::::new( &gen_validators(&known_keypairs), - &DkgParams { - tau: 0, - security_threshold: shares_num / 2, - shares_num, - }, + &DkgParams::new(TAU, SECURITY_THRESHOLD, SHARES_NUM).unwrap(), &unknown_validator, ) .unwrap_err(); @@ -498,8 +398,7 @@ mod test_dkg_init { mod test_dealing { use ark_ec::AffineRepr; - use super::test_common::*; - use crate::DkgState::Dealt; + use crate::{test_common::*, DkgState, DkgState::Dealt, Validator}; /// Test that dealing correct PVSS transcripts /// pass verification an application and that @@ -716,7 +615,7 @@ mod test_dealing { mod test_aggregation { use ark_ec::AffineRepr; - use super::test_common::*; + use crate::{dkg::*, test_common::*, DkgState, Message}; /// Test that if the security threshold is /// met, we can create a final key @@ -802,17 +701,18 @@ mod test_aggregation { /// Test DKG parameters #[cfg(test)] mod test_dkg_params { - const TAU: u32 = 0; + use crate::test_common::*; #[test] fn test_shares_num_less_than_security_threshold() { - let dkg_params = super::DkgParams::new(TAU, 4, 3); + let dkg_params = super::DkgParams::new(TAU, SHARES_NUM + 1, SHARES_NUM); assert!(dkg_params.is_err()); } #[test] fn test_valid_dkg_params() { - let dkg_params = super::DkgParams::new(TAU, 2, 3); + let dkg_params = + super::DkgParams::new(TAU, SECURITY_THRESHOLD, SHARES_NUM); assert!(dkg_params.is_ok()); } } diff --git a/ferveo/src/lib.rs b/ferveo/src/lib.rs index 605dc0d7..394afb1a 100644 --- a/ferveo/src/lib.rs +++ b/ferveo/src/lib.rs @@ -21,6 +21,9 @@ pub mod validator; mod utils; +#[cfg(test)] +mod test_common; + pub use dkg::*; pub use primitives::*; pub use pvss::*; @@ -96,15 +99,19 @@ pub enum Error { #[error(transparent)] ArkSerializeError(#[from] ark_serialize::SerializationError), + /// Invalid byte length #[error("Invalid byte length. Expected {0}, got {1}")] InvalidByteLength(usize, usize), + /// Invalid variant #[error("Invalid variant: {0}")] InvalidVariant(String), + /// DKG parameters validaiton failed #[error("Invalid DKG parameters: number of shares {0}, threshold {1}")] InvalidDkgParameters(u32, u32), + /// Failed to access a share for a given share index #[error("Invalid share index: {0}")] InvalidShareIndex(u32), } @@ -127,7 +134,7 @@ mod test_dkg_full { use std::collections::HashMap; use ark_bls12_381::{Bls12_381 as E, Fr, G1Affine}; - use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; + use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{UniformRand, Zero}; use ark_poly::EvaluationDomain; use ark_std::test_rng; @@ -137,12 +144,11 @@ mod test_dkg_full { SharedSecret, }; use itertools::izip; + use rand::seq::SliceRandom; use test_case::test_case; use super::*; - use crate::dkg::test_common::*; - - type TargetField = ::TargetField; + use crate::test_common::*; fn make_shared_secret_simple_tdec( dkg: &PubliclyVerifiableDkg, @@ -203,13 +209,12 @@ mod test_dkg_full { 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(); + setup_dealt_dkg_with(threshold, shares_num); + let public_key = dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( - SecretBox::new(msg.clone()), - aad, + SecretBox::new(MSG.to_vec()), + AAD, &public_key, rng, ) @@ -217,19 +222,19 @@ mod test_dkg_full { let (_, _, shared_secret) = make_shared_secret_simple_tdec( &dkg, - aad, + AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), ); let plaintext = ferveo_tdec::decrypt_with_shared_secret( &ciphertext, - aad, + AAD, &shared_secret, &dkg.pvss_params.g_inv(), ) .unwrap(); - assert_eq!(plaintext, msg); + assert_eq!(plaintext, MSG); } #[test_case(4; "number of shares (validators) is a power of 2")] @@ -240,13 +245,11 @@ mod test_dkg_full { // 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(); + setup_dealt_dkg_with(threshold, shares_num); let public_key = dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( - SecretBox::new(msg.clone()), - aad, + SecretBox::new(MSG.to_vec()), + AAD, &public_key, rng, ) @@ -260,7 +263,7 @@ mod test_dkg_full { .take(validator_keypairs.len()) .collect::>(); - let decryption_shares: Vec> = + let mut decryption_shares: Vec> = validator_keypairs .iter() .map(|validator_keypair| { @@ -270,7 +273,7 @@ mod test_dkg_full { pvss_aggregated .make_decryption_share_simple_precomputed( &ciphertext.header().unwrap(), - aad, + AAD, &validator_keypair.decryption_key, validator.share_index, &domain_points, @@ -279,6 +282,7 @@ mod test_dkg_full { .unwrap() }) .collect(); + decryption_shares.shuffle(rng); assert_eq!(domain_points.len(), decryption_shares.len()); let shared_secret = @@ -287,25 +291,24 @@ mod test_dkg_full { // Combination works, let's decrypt let plaintext = ferveo_tdec::decrypt_with_shared_secret( &ciphertext, - aad, + AAD, &shared_secret, &dkg.pvss_params.g_inv(), ) .unwrap(); - assert_eq!(plaintext, msg); + assert_eq!(plaintext, MSG); } #[test] fn test_dkg_simple_tdec_share_verification() { 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 (dkg, validator_keypairs) = + setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM); let public_key = dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( - SecretBox::new(msg), - aad, + SecretBox::new(MSG.to_vec()), + AAD, &public_key, rng, ) @@ -314,7 +317,7 @@ mod test_dkg_full { let (pvss_aggregated, decryption_shares, _) = make_shared_secret_simple_tdec( &dkg, - aad, + AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), ); @@ -363,16 +366,12 @@ mod test_dkg_full { fn test_dkg_simple_tdec_share_recovery() { let rng = &mut test_rng(); - let security_threshold = 3; - let shares_num = 4; let (dkg, validator_keypairs) = - setup_dealt_dkg_with_n_validators(security_threshold, shares_num); - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); + setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM); let public_key = &dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( - SecretBox::new(msg), - aad, + SecretBox::new(MSG.to_vec()), + AAD, public_key, rng, ) @@ -381,7 +380,7 @@ mod test_dkg_full { // Create an initial shared secret let (_, _, old_shared_secret) = make_shared_secret_simple_tdec( &dkg, - aad, + AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), ); @@ -476,7 +475,7 @@ mod test_dkg_full { pvss_aggregated .make_decryption_share_simple( &ciphertext.header().unwrap(), - aad, + AAD, &validator_keypair.decryption_key, share_index, &dkg.pvss_params.g_inv(), @@ -492,21 +491,21 @@ mod test_dkg_full { &new_validator_decryption_key, &new_private_key_share, &ciphertext.header().unwrap(), - aad, + AAD, &dkg.pvss_params.g_inv(), ) .unwrap(), ); domain_points.push(x_r); - assert_eq!(domain_points.len(), shares_num as usize); - assert_eq!(decryption_shares.len(), shares_num as usize); + assert_eq!(domain_points.len(), SHARES_NUM as usize); + assert_eq!(decryption_shares.len(), SHARES_NUM as usize); // Maybe parametrize this test with [1..] and [..threshold] let domain_points = &domain_points[1..]; let decryption_shares = &decryption_shares[1..]; - assert_eq!(domain_points.len(), security_threshold as usize); - assert_eq!(decryption_shares.len(), security_threshold as usize); + assert_eq!(domain_points.len(), SECURITY_THRESHOLD as usize); + assert_eq!(decryption_shares.len(), SECURITY_THRESHOLD as usize); let lagrange = ferveo_tdec::prepare_combine_simple::(domain_points); let new_shared_secret = ferveo_tdec::share_combine_simple::( @@ -524,16 +523,12 @@ mod test_dkg_full { fn test_dkg_simple_tdec_share_refreshing() { let rng = &mut test_rng(); - let security_threshold = 3; - let shares_num = 4; let (dkg, validator_keypairs) = - setup_dealt_dkg_with_n_validators(security_threshold, shares_num); - let msg = "my-msg".as_bytes().to_vec(); - let aad: &[u8] = "my-aad".as_bytes(); + setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM); let public_key = &dkg.public_key(); let ciphertext = ferveo_tdec::encrypt::( - SecretBox::new(msg), - aad, + SecretBox::new(MSG.to_vec()), + AAD, public_key, rng, ) @@ -542,7 +537,7 @@ mod test_dkg_full { // Create an initial shared secret let (_, _, old_shared_secret) = make_shared_secret_simple_tdec( &dkg, - aad, + AAD, &ciphertext.header().unwrap(), validator_keypairs.as_slice(), ); @@ -607,7 +602,7 @@ mod test_dkg_full { &validator_keypair.decryption_key, updated_shares.get(share_index).unwrap(), &ciphertext.header().unwrap(), - aad, + AAD, &dkg.pvss_params.g_inv(), ) .unwrap() @@ -615,10 +610,10 @@ mod test_dkg_full { .collect(); let lagrange = ferveo_tdec::prepare_combine_simple::( - &domain_points[..security_threshold as usize], + &domain_points[..SECURITY_THRESHOLD as usize], ); let new_shared_secret = ferveo_tdec::share_combine_simple::( - &decryption_shares[..security_threshold as usize], + &decryption_shares[..SECURITY_THRESHOLD as usize], &lagrange, ); diff --git a/ferveo/src/pvss.rs b/ferveo/src/pvss.rs index 495018bf..c8498bb7 100644 --- a/ferveo/src/pvss.rs +++ b/ferveo/src/pvss.rs @@ -462,11 +462,7 @@ mod test_pvss { use rand::seq::SliceRandom; use super::*; - use crate::{dkg::test_common::*, utils::is_sorted}; - - type ScalarField = ::ScalarField; - type G1 = ::G1Affine; - type G2 = ::G2Affine; + use crate::{test_common::*, utils::is_sorted, DkgParams}; /// Test the happy flow that a pvss with the correct form is created /// and that appropriate validations pass diff --git a/ferveo/src/refresh.rs b/ferveo/src/refresh.rs index c9e692d4..524e6569 100644 --- a/ferveo/src/refresh.rs +++ b/ferveo/src/refresh.rs @@ -122,22 +122,18 @@ mod tests_refresh { use std::collections::HashMap; use ark_bls12_381::Fr; - use ark_ec::pairing::Pairing; use ark_std::{test_rng, UniformRand, Zero}; - use rand_core::RngCore; - - type E = ark_bls12_381::Bls12_381; - type ScalarField = ::ScalarField; - use ferveo_tdec::{ test_common::setup_simple, PrivateDecryptionContextSimple, PrivateKeyShare, }; + use rand_core::RngCore; + use test_case::test_matrix; use crate::{ apply_updates_to_private_share, prepare_share_updates_for_recovery, prepare_share_updates_for_refresh, - recover_share_from_updated_private_shares, + recover_share_from_updated_private_shares, test_common::*, }; fn make_new_share_fragments_for_recovery( @@ -191,14 +187,13 @@ mod tests_refresh { /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. /// The new share is intended to restore a previously existing share, e.g., due to loss or corruption. - #[test] - fn tdec_simple_variant_share_recovery_at_selected_point() { + #[test_matrix([4, 7, 11, 16])] + fn tdec_simple_variant_share_recovery_at_selected_point(shares_num: usize) { let rng = &mut test_rng(); - let shares_num = 16; - let threshold = shares_num * 2 / 3; + let security_threshold = shares_num * 2 / 3; let (_, _, mut contexts) = - setup_simple::(threshold, shares_num, rng); + setup_simple::(security_threshold, shares_num, rng); // Prepare participants @@ -220,7 +215,7 @@ mod tests_refresh { // Each participant prepares an update for each other participant, and uses it to create a new share fragment let new_share_fragments = make_new_share_fragments_for_recovery( rng, - threshold, + security_threshold, &x_r, &remaining_participants, ); @@ -233,8 +228,8 @@ mod tests_refresh { .collect::>(); let new_private_key_share = recover_share_from_updated_private_shares( &x_r, - &domain_points[..threshold], - &new_share_fragments[..threshold], + &domain_points[..security_threshold], + &new_share_fragments[..security_threshold], ); assert_eq!(new_private_key_share, original_private_key_share); @@ -244,8 +239,8 @@ mod tests_refresh { let incorrect_private_key_share = recover_share_from_updated_private_shares( &x_r, - &domain_points[..(threshold - 1)], - &new_share_fragments[..(threshold - 1)], + &domain_points[..(security_threshold - 1)], + &new_share_fragments[..(security_threshold - 1)], ); assert_ne!(incorrect_private_key_share, original_private_key_share); @@ -253,10 +248,9 @@ mod tests_refresh { /// Ñ parties (where t <= Ñ <= N) jointly execute a "share recovery" algorithm, and the output is 1 new share. /// The new share is independent from the previously existing shares. We can use this to on-board a new participant into an existing cohort. - #[test] - fn tdec_simple_variant_share_recovery_at_random_point() { + #[test_matrix([4, 7, 11, 16])] + fn tdec_simple_variant_share_recovery_at_random_point(shares_num: usize) { let rng = &mut test_rng(); - let shares_num = 16; let threshold = shares_num * 2 / 3; let (_, shared_private_key, mut contexts) = @@ -321,10 +315,10 @@ mod tests_refresh { /// Ñ parties (where t <= Ñ <= N) jointly execute a "share refresh" algorithm. /// The output is M new shares (with M <= Ñ), with each of the M new shares substituting the /// original share (i.e., the original share is deleted). - #[test] - fn tdec_simple_variant_share_refreshing() { + #[test_matrix([4, 7, 11, 16])] + + fn tdec_simple_variant_share_refreshing(shares_num: usize) { let rng = &mut test_rng(); - let shares_num = 16; let threshold = shares_num * 2 / 3; let (_, shared_private_key, contexts) = diff --git a/ferveo/src/test_common.rs b/ferveo/src/test_common.rs new file mode 100644 index 00000000..22d072a2 --- /dev/null +++ b/ferveo/src/test_common.rs @@ -0,0 +1,110 @@ +/// Factory functions and variables for testing +use std::str::FromStr; + +pub use ark_bls12_381::Bls12_381 as E; +use ark_ec::pairing::Pairing; +use ferveo_common::Keypair; +use rand::seq::SliceRandom; + +use crate::{DkgParams, EthereumAddress, PubliclyVerifiableDkg, Validator}; + +pub type ScalarField = ::ScalarField; +pub type G1 = ::G1Affine; +pub type G2 = ::G2Affine; +pub type TargetField = ::TargetField; + +pub const TAU: u32 = 0; +pub const MSG: &[u8] = b"my-msg"; +pub const AAD: &[u8] = b"my-aad"; +pub const SECURITY_THRESHOLD: u32 = 3; +pub const SHARES_NUM: u32 = 4; + +pub fn gen_keypairs(n: u32) -> Vec> { + let rng = &mut ark_std::test_rng(); + (0..n).map(|_| Keypair::::new(rng)).collect() +} + +pub fn gen_address(i: usize) -> EthereumAddress { + EthereumAddress::from_str(&format!("0x{i:040}")).unwrap() +} + +pub fn gen_validators(keypairs: &[Keypair]) -> Vec> { + keypairs + .iter() + .enumerate() + .map(|(i, keypair)| Validator { + address: gen_address(i), + public_key: keypair.public_key(), + }) + .collect() +} + +pub type TestSetup = (PubliclyVerifiableDkg, Vec>); + +pub fn setup_dkg_for_n_validators( + security_threshold: u32, + shares_num: u32, + my_validator_index: usize, +) -> TestSetup { + let keypairs = gen_keypairs(shares_num); + let mut validators = gen_validators(keypairs.as_slice()); + validators.sort(); + let me = validators[my_validator_index].clone(); + let dkg = PubliclyVerifiableDkg::new( + &validators, + &DkgParams::new(TAU, security_threshold, shares_num).unwrap(), + &me, + ) + .expect("Setup failed"); + (dkg, keypairs) +} + +/// Create a test dkg +/// +/// The [`crate::dkg::test_dkg_init`] module checks correctness of this setup +pub fn setup_dkg(my_validator_index: usize) -> TestSetup { + setup_dkg_for_n_validators( + SECURITY_THRESHOLD, + SHARES_NUM, + my_validator_index, + ) +} + +/// Set up a dkg with enough pvss transcripts to meet the threshold +/// +/// The correctness of this function is tested in the module [`crate::dkg::test_dealing`] +pub fn setup_dealt_dkg() -> TestSetup { + setup_dealt_dkg_with(SECURITY_THRESHOLD, SHARES_NUM) +} + +pub fn setup_dealt_dkg_with( + security_threshold: u32, + shares_num: u32, +) -> TestSetup { + let rng = &mut ark_std::test_rng(); + + // Gather everyone's transcripts + let mut messages: Vec<_> = (0..shares_num) + .map(|my_index| { + let (mut dkg, _) = setup_dkg_for_n_validators( + security_threshold, + shares_num, + my_index as usize, + ); + let me = dkg.me.validator.clone(); + let message = dkg.share(rng).unwrap(); + (me, message) + }) + .collect(); + + // Create a test DKG instance + let (mut dkg, keypairs) = + setup_dkg_for_n_validators(security_threshold, shares_num, 0); + + // The ordering of messages should not matter + messages.shuffle(rng); + messages.iter().for_each(|(sender, message)| { + dkg.apply_message(sender, message).expect("Setup failed"); + }); + (dkg, keypairs) +}