diff --git a/ferveo/src/api.rs b/ferveo/src/api.rs index 396c8aff..c00acbc1 100644 --- a/ferveo/src/api.rs +++ b/ferveo/src/api.rs @@ -640,6 +640,7 @@ impl PrivateKeyShare { mod test_ferveo_api { use std::collections::HashMap; + use ark_std::iterable::Iterable; use ferveo_tdec::SecretBox; use itertools::{izip, Itertools}; use rand::{ @@ -1395,362 +1396,4 @@ mod test_ferveo_api { "Shared secret reconstruction failed" ); } - - fn make_share_update_test_inputs( - shares_num: u32, - validators_num: u32, - rng: &mut StdRng, - security_threshold: u32, - ) -> ( - Vec, - Vec, - Vec, - Vec, - CiphertextHeader, - SharedSecret, - ) { - let (messages, validators, validator_keypairs) = make_test_inputs( - rng, - TAU, - security_threshold, - shares_num, - validators_num, - ); - let mut dkgs = validators - .iter() - .map(|validator| { - Dkg::new( - TAU, - shares_num, - security_threshold, - &validators, - validator, - ) - .unwrap() - }) - .collect::>(); - let pvss_aggregated = dkgs[0].aggregate_transcripts(&messages).unwrap(); - assert!(pvss_aggregated.verify(validators_num, &messages).unwrap()); - - // Create an initial shared secret for testing purposes - let public_key = &dkgs[0].public_key(); - let ciphertext = - encrypt(SecretBox::new(MSG.to_vec()), AAD, public_key).unwrap(); - let ciphertext_header = ciphertext.header().unwrap(); - let (_, _, old_shared_secret) = - crate::test_dkg_full::create_shared_secret_simple_tdec( - &dkgs[0].0, - AAD, - &ciphertext_header.0, - validator_keypairs.as_slice(), - ); - - ( - messages, - validators, - validator_keypairs, - dkgs, - ciphertext_header, - SharedSecret(old_shared_secret), - ) - } - - #[test_case(4, 4, true; "number of shares (validators) is a power of 2")] - #[test_case(7, 7, true; "number of shares (validators) is not a power of 2")] - #[test_case(4, 6, true; "number of validators greater than the number of shares")] - #[test_case(4, 6, false; "recovery at a specific point")] - fn test_dkg_simple_tdec_share_recovery( - shares_num: u32, - validators_num: u32, - recover_at_random_point: bool, - ) { - let rng = &mut StdRng::seed_from_u64(0); - let security_threshold = shares_num / 2 + 1; - - let ( - mut messages, - mut validators, - mut validator_keypairs, - mut dkgs, - ciphertext_header, - old_shared_secret, - ) = make_share_update_test_inputs( - shares_num, - validators_num, - rng, - security_threshold, - ); - - // We assume that all participants have the same aggregate, and that participants created - // their own aggregates before the off-boarding of the validator - // If we didn't create this aggregate here, we risk having a "dangling validator message" - // later when we off-board the validator - let aggregated_transcript = - dkgs[0].clone().aggregate_transcripts(&messages).unwrap(); - assert!(aggregated_transcript - .verify(validators_num, &messages) - .unwrap()); - - // We need to save this domain point to be user in the recovery testing scenario - let mut domain_points = dkgs[0].domain_points(); - let removed_domain_point = domain_points.pop().unwrap(); - - // Remove one participant from the contexts and all nested structure - // to simulate off-boarding a validator - messages.pop().unwrap(); - dkgs.pop(); - validator_keypairs.pop().unwrap(); - - let removed_validator = validators.pop().unwrap(); - for dkg in dkgs.iter_mut() { - dkg.0 - .offboard_validator(&removed_validator.address) - .expect("Unable to off-board a validator from the DKG context"); - } - - // Now, we're going to recover a new share at a random point or at a specific point - // and check that the shared secret is still the same. - let x_r = if recover_at_random_point { - // Onboarding a validator with a completely new private key share - DomainPoint::::rand(rng) - } else { - // Onboarding a validator with a private key share recovered from the removed validator - removed_domain_point - }; - - // Each participant prepares an update for each other participant - let share_updates = dkgs - .iter() - .map(|validator_dkg| { - let share_update = ShareRecoveryUpdate::create_share_updates( - validator_dkg, - &x_r, - ) - .unwrap(); - (validator_dkg.me().address.clone(), share_update) - }) - .collect::>(); - - // Participants share updates and update their shares - - // Now, every participant separately: - let updated_shares: Vec<_> = dkgs - .iter() - .map(|validator_dkg| { - // Current participant receives updates from other participants - let updates_for_participant: Vec<_> = share_updates - .values() - .map(|updates| { - updates - .get(validator_dkg.me().share_index as usize) - .unwrap() - }) - .cloned() - .collect(); - - // Each validator uses their decryption key to update their share - let validator_keypair = validator_keypairs - .get(validator_dkg.me().share_index as usize) - .unwrap(); - - // And creates updated private key shares - aggregated_transcript - .get_private_key_share( - validator_keypair, - validator_dkg.me().share_index, - ) - .unwrap() - .create_updated_private_key_share_for_recovery( - &updates_for_participant, - ) - .unwrap() - }) - .collect(); - - // Now, we have to combine new share fragments into a new share - let recovered_key_share = - PrivateKeyShare::recover_share_from_updated_private_shares( - &x_r, - &domain_points, - &updated_shares, - ) - .unwrap(); - - // Get decryption shares from remaining participants - let mut decryption_shares: Vec = - validator_keypairs - .iter() - .zip_eq(dkgs.iter()) - .map(|(validator_keypair, validator_dkg)| { - aggregated_transcript - .create_decryption_share_simple( - validator_dkg, - &ciphertext_header, - AAD, - validator_keypair, - ) - .unwrap() - }) - .collect(); - decryption_shares.shuffle(rng); - - // In order to test the recovery, we need to create a new decryption share from the recovered - // private key share. To do that, we need a new validator - - // Let's create and onboard a new validator - // TODO: Add test scenarios for onboarding and offboarding validators - let new_validator_keypair = Keypair::random(); - // Normally, we would get these from the Coordinator: - let new_validator_share_index = removed_validator.share_index; - let new_validator = Validator { - address: gen_address(new_validator_share_index as usize), - public_key: new_validator_keypair.public_key(), - share_index: new_validator_share_index, - }; - validators.push(new_validator.clone()); - let new_validator_dkg = Dkg::new( - TAU, - shares_num, - security_threshold, - &validators, - &new_validator, - ) - .unwrap(); - - let new_decryption_share = recovered_key_share - .create_decryption_share_simple( - &new_validator_dkg, - &ciphertext_header, - &new_validator_keypair, - AAD, - ) - .unwrap(); - decryption_shares.push(new_decryption_share); - domain_points.push(x_r); - assert_eq!(domain_points.len(), validators_num as usize); - assert_eq!(decryption_shares.len(), validators_num as usize); - - let domain_points = &domain_points[..security_threshold as usize]; - let decryption_shares = - &decryption_shares[..security_threshold as usize]; - assert_eq!(domain_points.len(), security_threshold as usize); - assert_eq!(decryption_shares.len(), security_threshold as usize); - - let new_shared_secret = combine_shares_simple(decryption_shares); - assert_eq!( - old_shared_secret, new_shared_secret, - "Shared secret reconstruction failed" - ); - } - - #[test_case(4, 4; "number of shares (validators) is a power of 2")] - #[test_case(7, 7; "number of shares (validators) is not a power of 2")] - #[test_case(4, 6; "number of validators greater than the number of shares")] - fn test_dkg_simple_tdec_share_refresh( - shares_num: u32, - validators_num: u32, - ) { - let rng = &mut StdRng::seed_from_u64(0); - let security_threshold = shares_num / 2 + 1; - - let ( - messages, - _validators, - validator_keypairs, - dkgs, - ciphertext_header, - old_shared_secret, - ) = make_share_update_test_inputs( - shares_num, - validators_num, - rng, - security_threshold, - ); - - // Each participant prepares an update for each other participant - let share_updates = dkgs - .iter() - .map(|validator_dkg| { - let share_update = - ShareRefreshUpdate::create_share_updates(validator_dkg) - .unwrap(); - (validator_dkg.me().address.clone(), share_update) - }) - .collect::>(); - - // Participants share updates and update their shares - - // Now, every participant separately: - let updated_shares: Vec<_> = dkgs - .iter() - .map(|validator_dkg| { - // Current participant receives updates from other participants - let updates_for_participant: Vec<_> = share_updates - .values() - .map(|updates| { - updates - .get(validator_dkg.me().share_index as usize) - .unwrap() - }) - .cloned() - .collect(); - - // Each validator uses their decryption key to update their share - let validator_keypair = validator_keypairs - .get(validator_dkg.me().share_index as usize) - .unwrap(); - - // And creates updated private key shares - // We need an aggregate for that - let aggregate = validator_dkg - .clone() - .aggregate_transcripts(&messages) - .unwrap(); - assert!(aggregate.verify(validators_num, &messages).unwrap()); - - aggregate - .get_private_key_share( - validator_keypair, - validator_dkg.me().share_index, - ) - .unwrap() - .create_updated_private_key_share_for_refresh( - &updates_for_participant, - ) - .unwrap() - }) - .collect(); - - // Participants create decryption shares - let mut decryption_shares: Vec = - validator_keypairs - .iter() - .zip_eq(dkgs.iter()) - .map(|(validator_keypair, validator_dkg)| { - let pks = updated_shares - .get(validator_dkg.me().share_index as usize) - .unwrap() - .clone() - .into_private_key_share(); - pks.create_decryption_share_simple( - validator_dkg, - &ciphertext_header, - validator_keypair, - AAD, - ) - .unwrap() - }) - .collect(); - decryption_shares.shuffle(rng); - - let decryption_shares = - &decryption_shares[..security_threshold as usize]; - assert_eq!(decryption_shares.len(), security_threshold as usize); - - let new_shared_secret = combine_shares_simple(decryption_shares); - assert_eq!( - old_shared_secret, new_shared_secret, - "Shared secret reconstruction failed" - ); - } }