diff --git a/benches/naive_ot_based_ecdsa.rs b/benches/naive_ot_based_ecdsa.rs index b458d73a..ed0b9f3c 100644 --- a/benches/naive_ot_based_ecdsa.rs +++ b/benches/naive_ot_based_ecdsa.rs @@ -1,10 +1,7 @@ -mod utils; - use criterion::{criterion_group, Criterion}; use frost_secp256k1::{Secp256K1Sha256, VerifyingKey}; use rand::Rng; use rand_core::OsRng; -use utils::ecdsa_generate_rerandpresig_args; use threshold_signatures::{ ecdsa::{ @@ -18,10 +15,21 @@ use threshold_signatures::{ }, participants::Participant, protocol::Protocol, - test_utils::{generate_participants_with_random_ids, run_keygen, run_protocol}, + test_utils::{ + ecdsa_generate_rerandpresig_args, generate_participants_with_random_ids, run_keygen, + run_protocol, + }, }; -use utils::MAX_MALICIOUS; +use std::{env, sync::LazyLock}; + +// fix malicious number of participants +pub static MAX_MALICIOUS: LazyLock = std::sync::LazyLock::new(|| { + env::var("MAX_MALICIOUS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(6) +}); fn threshold() -> usize { *crate::MAX_MALICIOUS + 1 diff --git a/benches/naive_robust_ecdsa.rs b/benches/naive_robust_ecdsa.rs index e898e89e..7891b3cd 100644 --- a/benches/naive_robust_ecdsa.rs +++ b/benches/naive_robust_ecdsa.rs @@ -1,9 +1,7 @@ -mod utils; use criterion::{criterion_group, Criterion}; use frost_secp256k1::{Secp256K1Sha256, VerifyingKey}; use rand::Rng; use rand_core::OsRng; -use utils::ecdsa_generate_rerandpresig_args; use threshold_signatures::{ ecdsa::{ @@ -15,10 +13,21 @@ use threshold_signatures::{ }, participants::Participant, protocol::Protocol, - test_utils::{generate_participants_with_random_ids, run_keygen, run_protocol}, + test_utils::{ + ecdsa_generate_rerandpresig_args, generate_participants_with_random_ids, run_keygen, + run_protocol, + }, }; -use utils::MAX_MALICIOUS; +use std::{env, sync::LazyLock}; + +// fix malicious number of participants +pub static MAX_MALICIOUS: LazyLock = std::sync::LazyLock::new(|| { + env::var("MAX_MALICIOUS") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(6) +}); fn participants_num() -> usize { 2 * *crate::MAX_MALICIOUS + 1 diff --git a/benches/utils/mod.rs b/benches/utils/mod.rs deleted file mode 100644 index bd056b7a..00000000 --- a/benches/utils/mod.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![allow(clippy::missing_panics_doc)] -use std::{env, sync::LazyLock}; - -use k256::AffinePoint; -use threshold_signatures::{ - ecdsa::{RerandomizationArguments, Scalar, Secp256K1Sha256, Tweak}, - participants::{Participant, ParticipantList}, - test_utils::random_32_bytes, -}; - -use frost_secp256k1::{Secp256K1ScalarField, VerifyingKey}; -use rand_core::{CryptoRngCore, OsRng}; - -// fix malicious number of participants -pub static MAX_MALICIOUS: LazyLock = std::sync::LazyLock::new(|| { - env::var("MAX_MALICIOUS") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(6) -}); - -// Outputs pk, R, hash, participants, entropy, randomness -pub fn ecdsa_generate_rerandpresig_args( - rng: &mut impl CryptoRngCore, - participants: &[Participant], - pk: VerifyingKey, - big_r: AffinePoint, -) -> (RerandomizationArguments, Scalar) { - let pk = pk.to_element().to_affine(); - let tweak = Tweak::new(frost_core::random_nonzero::(&mut OsRng)); - - let msg_hash = ::random(&mut OsRng); - let entropy = random_32_bytes(rng); - // Generate unique ten ParticipantId values - let participants = - ParticipantList::new(participants).expect("Participant list generation should not fail"); - - let args = RerandomizationArguments::new( - pk, - tweak, - msg_hash.to_bytes().into(), - big_r, - participants, - entropy, - ); - (args, msg_hash) -} diff --git a/src/confidential_key_derivation/ciphersuite.rs b/src/confidential_key_derivation/ciphersuite.rs index 36a18cd8..4eb5b9f5 100644 --- a/src/confidential_key_derivation/ciphersuite.rs +++ b/src/confidential_key_derivation/ciphersuite.rs @@ -299,20 +299,14 @@ impl FromOkm for ScalarWrapper { mod tests { use blstrs::Scalar; use digest::generic_array::GenericArray; - use elliptic_curve::hash2curve::FromOkm; - use elliptic_curve::Field; - use elliptic_curve::Group; - use rand::Rng; - use rand::RngCore; + use elliptic_curve::{hash2curve::FromOkm, Field, Group}; + use rand::{Rng, RngCore}; use rand_core::OsRng; - use crate::confidential_key_derivation::ciphersuite::verify_signature; - use crate::confidential_key_derivation::ciphersuite::ScalarWrapper; - use crate::confidential_key_derivation::VerifyingKey; use crate::{ confidential_key_derivation::{ - ciphersuite::{hash_to_curve, BLS12381SHA256}, - ElementG2, + ciphersuite::{hash_to_curve, verify_signature, ScalarWrapper, BLS12381SHA256}, + ElementG2, VerifyingKey, }, test_utils::check_common_traits_for_type, }; diff --git a/src/confidential_key_derivation/protocol.rs b/src/confidential_key_derivation/protocol.rs index 9dd7dbf1..b62eed87 100644 --- a/src/confidential_key_derivation/protocol.rs +++ b/src/confidential_key_derivation/protocol.rs @@ -181,7 +181,7 @@ fn compute_signature_share( mod test { use super::*; use crate::confidential_key_derivation::ciphersuite::hash_to_curve; - use crate::test_utils::{one_coordinator_output, run_protocol, GenProtocol}; + use crate::test_utils::{check_one_coordinator_output, run_protocol, GenProtocol}; use rand::Rng; #[test] @@ -240,7 +240,7 @@ mod test { let result = run_protocol(protocols).unwrap(); // test one single some for the coordinator - let ckd = one_coordinator_output(result, coordinator).unwrap(); + let ckd = check_one_coordinator_output(result, coordinator).unwrap(); // compute msk . H(app_id) let confidential_key = ckd.unmask(app_sk); diff --git a/src/dkg.rs b/src/dkg.rs index 9e5c2bb3..050a8eba 100644 --- a/src/dkg.rs +++ b/src/dkg.rs @@ -659,8 +659,9 @@ pub mod test { use super::domain_separate_hash; use crate::crypto::ciphersuite::Ciphersuite; use crate::participants::{Participant, ParticipantList}; - use crate::test_utils::generate_participants; - use crate::test_utils::{assert_public_key_invariant, run_keygen, run_refresh, run_reshare}; + use crate::test_utils::{ + assert_public_key_invariant, generate_participants, run_keygen, run_refresh, run_reshare, + }; use frost_core::{Field, Group}; #[test] diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 54a8eccc..b5a9f70a 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -190,20 +190,17 @@ mod test { }, participants::ParticipantList, test_utils::{ - generate_participants, generate_participants_with_random_ids, random_32_bytes, - MockCryptoRng, + ecdsa_generate_rerandpresig_args, generate_participants, + generate_participants_with_random_ids, MockCryptoRng, }, }; use elliptic_curve::ops::{Invert, LinearCombination, Reduce}; - use frost_core::{ - keys::SigningShare, Ciphersuite, SigningKey as FrostSigningKey, - VerifyingKey as FrostVerifyingKey, - }; + use frost_core::{keys::SigningShare, Ciphersuite, SigningKey as FrostSigningKey}; use k256::{ - ecdsa::{signature::Verifier, SigningKey, VerifyingKey}, + ecdsa::{signature::Verifier, SigningKey}, ProjectivePoint, Secp256k1, }; use rand_core::{CryptoRngCore, OsRng, RngCore}; @@ -217,7 +214,7 @@ mod test { hasher.update(msg); let sk = SigningKey::random(&mut OsRng); - let pk = VerifyingKey::from(&sk); + let pk = ecdsa::VerifyingKey::from(&sk); let (sig, _) = sk.sign_digest_recoverable(hasher.clone()).unwrap(); assert!(pk.verify(msg, &sig).is_ok()); @@ -250,7 +247,7 @@ mod test { let keygen_output = KeygenOutput { private_share: SigningShare::::new(Scalar::ONE), - public_key: FrostVerifyingKey::::from(signing_key), + public_key: frost_core::VerifyingKey::::from(signing_key), }; // When @@ -269,19 +266,16 @@ mod test { rng: &mut impl CryptoRngCore, num_participants: usize, ) -> (RerandomizationArguments, Scalar) { - let sk = SigningKey::random(&mut OsRng); - let pk = *VerifyingKey::from(sk).as_affine(); - let tweak = Tweak::new(frost_core::random_nonzero::(&mut OsRng)); let (_, big_r) = ::generate_nonce(&mut OsRng); + let (_, pk) = ::generate_nonce(&mut OsRng); + let pk = frost_core::VerifyingKey::new(pk); let big_r = big_r.to_affine(); - let msg_hash = random_32_bytes(rng); - let entropy = random_32_bytes(rng); // Generate unique ten ParticipantId values let participants = generate_participants_with_random_ids(num_participants, rng); - let participants = ParticipantList::new(&participants).unwrap(); + // Generate Rerandomization arguments + let (args, _) = ecdsa_generate_rerandpresig_args(rng, &participants, pk, big_r); - let args = RerandomizationArguments::new(pk, tweak, msg_hash, big_r, participants, entropy); let delta = args.derive_randomness().unwrap(); (args, delta) } @@ -315,7 +309,7 @@ mod test { let mut rng = OsRng; let (mut args, delta) = compute_random_outputs(&mut rng, num_participants); // different msg_hash - args.msg_hash = random_32_bytes(&mut rng); + args.msg_hash = [0; 32]; let delta_prime = args.derive_randomness().unwrap(); assert_ne!(delta, delta_prime); } diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index 5fff72a8..3621bb3a 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -188,9 +188,10 @@ async fn do_presign( #[cfg(test)] mod test { use super::*; + use crate::test_utils::{run_protocol, GenProtocol}; use crate::{ ecdsa::{ot_based_ecdsa::triples::test::deal, KeygenOutput, Polynomial, ProjectivePoint}, - test_utils::{generate_participants, run_protocol, GenProtocol}, + test_utils::generate_participants, }; use frost_secp256k1::{ keys::{PublicKeyPackage, SigningShare}, diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index 2fb890e1..19f74fbd 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -2,27 +2,23 @@ use super::{ presign::presign, sign::sign, triples::{generate_triple_many, test::deal, TriplePub, TripleShare}, - PresignArguments, PresignOutput, + PresignArguments, PresignOutput, RerandomizedPresignOutput, }; use crate::participants::Participant; use crate::protocol::Protocol; use crate::test_utils::{ - assert_public_key_invariant, generate_participants, generate_participants_with_random_ids, - one_coordinator_output, run_keygen, run_protocol, run_refresh, run_reshare, run_sign, + assert_public_key_invariant, check_one_coordinator_output, generate_participants, + generate_participants_with_random_ids, run_keygen, run_protocol, run_refresh, run_reshare, + run_sign, GenOutput, GenProtocol, }; -use crate::{ - crypto::hash::test::scalar_hash_secp256k1, ecdsa::ot_based_ecdsa::RerandomizedPresignOutput, -}; -use crate::{ - ecdsa::{ - Element, ParticipantList, RerandomizationArguments, Secp256K1Sha256, Signature, - SignatureOption, Tweak, - }, - test_utils::{GenOutput, GenProtocol}, + +use crate::crypto::hash::test::scalar_hash_secp256k1; +use crate::ecdsa::{ + Element, ParticipantList, RerandomizationArguments, Secp256K1Sha256, Signature, + SignatureOption, Tweak, }; -use rand::rngs::OsRng; -use rand::Rng; +use rand::{rngs::OsRng, Rng}; use rand_core::RngCore; use std::error::Error; @@ -62,7 +58,7 @@ pub fn run_sign_without_rerandomization( ) .unwrap(); // test one single some for the coordinator - let signature = one_coordinator_output(result, coordinator).unwrap(); + let signature = check_one_coordinator_output(result, coordinator).unwrap(); (coordinator, signature) } @@ -111,7 +107,7 @@ pub fn run_sign_with_rerandomization( let coordinator = participants_presign[index].0; // run sign instanciation with the necessary arguments - let result = crate::test_utils::run_sign::( + let result = run_sign::( rerand_participants_presign, coordinator, derived_pk, @@ -124,7 +120,7 @@ pub fn run_sign_with_rerandomization( )?; // test one single some for the coordinator - let signature = one_coordinator_output(result, coordinator)?; + let signature = check_one_coordinator_output(result, coordinator)?; Ok((tweak, coordinator, signature)) } diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index 8e55495e..76b5494a 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -1136,8 +1136,7 @@ mod test { use crate::{ ecdsa::{ot_based_ecdsa::triples::generate_triple, ProjectivePoint}, - participants::Participant, - participants::ParticipantList, + participants::{Participant, ParticipantList}, protocol::Protocol, test_utils::{generate_participants, run_protocol}, }; diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 988b4424..b9e0862b 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -355,15 +355,11 @@ impl Shares { #[cfg(test)] mod test { use super::*; - use rand_core::OsRng; - use crate::ecdsa::KeygenOutput; use crate::test_utils::{generate_participants, run_protocol, GenProtocol}; - use frost_secp256k1::keys::PublicKeyPackage; use frost_secp256k1::VerifyingKey; - use k256::ProjectivePoint; - use std::collections::BTreeMap; + use rand_core::OsRng; #[test] fn test_presign() { @@ -380,10 +376,9 @@ mod test { // simulating the key packages for each participant let private_share = f.eval_at_participant(*p).unwrap(); let verifying_key = VerifyingKey::new(big_x); - let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); let keygen_out = KeygenOutput { private_share: SigningShare::new(private_share.0), - public_key: *public_key_package.verifying_key(), + public_key: verifying_key, }; let protocol = presign( @@ -403,9 +398,6 @@ mod test { assert!(result.len() == 5); // testing that big_r is the same accross participants - assert_eq!(result[0].1.big_r, result[1].1.big_r); - assert_eq!(result[1].1.big_r, result[2].1.big_r); - assert_eq!(result[2].1.big_r, result[3].1.big_r); - assert_eq!(result[3].1.big_r, result[4].1.big_r); + assert!(result.windows(2).all(|w| w[0].1.big_r == w[1].1.big_r)); } } diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index f8d6338a..dd21d646 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -300,9 +300,6 @@ mod test { #[test] fn test_sign_fails_if_s_is_zero() { - use crate::ecdsa::{ProjectivePoint, Secp256K1ScalarField}; - use crate::test_utils::generate_participants; - let participants = generate_participants(2); // presignatures with s_me = 0 for each participant diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index 85568106..87f6162d 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -11,9 +11,9 @@ use crate::ecdsa::{ use crate::participants::Participant; use crate::protocol::Protocol; use crate::test_utils::{ - assert_public_key_invariant, generate_participants, generate_participants_with_random_ids, - one_coordinator_output, run_keygen, run_protocol, run_refresh, run_reshare, GenOutput, - GenProtocol, + assert_public_key_invariant, check_one_coordinator_output, generate_participants, + generate_participants_with_random_ids, run_keygen, run_protocol, run_refresh, run_reshare, + run_sign, GenOutput, GenProtocol, }; use rand::rngs::OsRng; @@ -35,7 +35,7 @@ pub fn run_sign_without_rerandomization( let coordinator = participants_presign[index].0; // run sign instanciation with the necessary arguments - let result = crate::test_utils::run_sign::( + let result = run_sign::( participants_presign.to_vec(), coordinator, public_key, @@ -49,7 +49,7 @@ pub fn run_sign_without_rerandomization( }, )?; // test one single some for the coordinator - let signature = one_coordinator_output(result, coordinator)?; + let signature = check_one_coordinator_output(result, coordinator)?; Ok((coordinator, signature)) } @@ -104,7 +104,7 @@ pub fn run_sign_with_rerandomization( let coordinator = participants_presign[index].0; // run sign instanciation with the necessary arguments - let result = crate::test_utils::run_sign::( + let result = run_sign::( rerand_participants_presign, coordinator, derived_pk, @@ -116,7 +116,7 @@ pub fn run_sign_with_rerandomization( }, )?; // test one single some for the coordinator - let signature = one_coordinator_output(result, coordinator)?; + let signature = check_one_coordinator_output(result, coordinator)?; Ok((tweak, coordinator, signature)) } diff --git a/src/eddsa/sign.rs b/src/eddsa/sign.rs index 6ad2821f..439b32c2 100644 --- a/src/eddsa/sign.rs +++ b/src/eddsa/sign.rs @@ -279,15 +279,14 @@ async fn fut_wrapper( #[cfg(test)] mod test { use crate::crypto::hash::hash; - use crate::participants::ParticipantList; - use crate::test_utils::generate_participants; + use crate::eddsa::test::{build_key_packages_with_dealer, test_run_signature_protocols}; + use crate::participants::{Participant, ParticipantList}; + use crate::test_utils::{ + assert_public_key_invariant, generate_participants, run_keygen, run_refresh, run_reshare, + }; use frost_core::{Field, Group}; use frost_ed25519::{Ed25519Group, Ed25519ScalarField, Ed25519Sha512}; - use crate::eddsa::test::{build_key_packages_with_dealer, test_run_signature_protocols}; - use crate::participants::Participant; - use crate::test_utils::{assert_public_key_invariant, run_keygen, run_refresh, run_reshare}; - fn assert_single_coordinator_result( data: &[(Participant, super::SignatureOption)], ) -> frost_ed25519::Signature { diff --git a/src/lib.rs b/src/lib.rs index c409dd9d..bd0988de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ pub mod confidential_key_derivation; pub mod ecdsa; pub mod eddsa; pub mod errors; + #[cfg(feature = "test-utils")] pub mod test_utils; diff --git a/src/test_utils.rs b/src/test_utils.rs deleted file mode 100644 index 78f93c9b..00000000 --- a/src/test_utils.rs +++ /dev/null @@ -1,576 +0,0 @@ -// This module provides generic functions to be used -// in the implemented schemes testing cases -#![allow( - clippy::panic, - clippy::missing_panics_doc, - clippy::unwrap_used, - clippy::cast_possible_truncation -)] - -use k256::elliptic_curve::PrimeField; -use k256::AffinePoint; -use rand_core::{CryptoRngCore, OsRng}; -use std::collections::HashMap; -use std::error::Error; - -use crate::confidential_key_derivation as ckd; -use crate::ecdsa::ot_based_ecdsa::triples::TripleGenerationOutput; -use crate::errors::{InitializationError, ProtocolError}; -use crate::frost_ed25519::Ed25519Sha512; -use crate::frost_secp256k1::Secp256K1Sha256; -use crate::participants::Participant; -use crate::protocol::{Action, Protocol}; -use crate::{ecdsa, eddsa, ParticipantList}; -use crate::{keygen, refresh, reshare, Ciphersuite, Element, KeygenOutput, Scalar, VerifyingKey}; - -pub type GenProtocol = Vec<(Participant, Box>)>; -use rand::{CryptoRng, RngCore}; -use rand_chacha::{rand_core::SeedableRng, ChaCha12Rng}; - -// +++++++++++++++++ General Utilities +++++++++++++++++ // -pub struct MockCryptoRng(ChaCha12Rng); - -impl MockCryptoRng { - pub fn seed_from_u64(seed: u64) -> Self { - Self(ChaCha12Rng::seed_from_u64(seed)) - } -} - -impl RngCore for MockCryptoRng { - fn next_u32(&mut self) -> u32 { - self.0.next_u32() - } - fn next_u64(&mut self) -> u64 { - self.0.next_u64() - } - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.0.fill_bytes(dest); - } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - self.0.try_fill_bytes(dest) - } -} - -impl CryptoRng for MockCryptoRng {} - -pub fn random_32_bytes(rng: &mut impl CryptoRngCore) -> [u8; 32] { - let mut bytes: [u8; 32] = [0u8; 32]; - rng.fill_bytes(&mut bytes); - bytes -} - -// +++++++++++++++++ Participants Utilities +++++++++++++++++ // -/// Generates a vector of `number` participants, sorted by the participant id. -/// The participants ids range from 0 to `number`-1 -pub fn generate_participants(number: usize) -> Vec { - (0..u32::try_from(number).unwrap()) - .map(Participant::from) - .collect::>() -} - -/// Generates a vector of `number` participants, sorted by the participant id. -/// The participants ids are drawn from rng. -pub fn generate_participants_with_random_ids( - number: usize, - rng: &mut impl CryptoRngCore, -) -> Vec { - let mut participants = (0..number) - .map(|_| Participant::from(rng.next_u32())) - .collect::>(); - participants.sort(); - participants -} - -// +++++++++++++++++ Any Protocol +++++++++++++++++ // -/// Run a protocol to completion, synchronously. -/// -/// This works by executing each participant in order. -/// -/// The reason this function exists is as a convenient testing utility. -/// In practice each protocol participant is likely running on a different machine, -/// and so orchestrating the protocol would happen differently. -pub fn run_protocol( - mut ps: Vec<(Participant, Box>)>, -) -> Result, ProtocolError> { - let indices: HashMap = - ps.iter().enumerate().map(|(i, (p, _))| (*p, i)).collect(); - - let size = ps.len(); - let mut out = Vec::with_capacity(size); - while out.len() < size { - for i in 0..size { - while { - let action = ps[i].1.poke()?; - match action { - Action::Wait => false, - Action::SendMany(m) => { - for j in 0..size { - if i == j { - continue; - } - let from = ps[i].0; - ps[j].1.message(from, m.clone()); - } - true - } - Action::SendPrivate(to, m) => { - let from = ps[i].0; - ps[indices[&to]].1.message(from, m); - true - } - Action::Return(r) => { - out.push((ps[i].0, r)); - false - } - } - } {} - } - } - Ok(out) -} - -/// Like [`run_protocol()`], except for just two parties. -/// -/// This is more useful for testing two party protocols with assymetric results, -/// since the return types for the two protocols can be different. -pub fn run_two_party_protocol( - p0: Participant, - p1: Participant, - prot0: &mut dyn Protocol, - prot1: &mut dyn Protocol, -) -> Result<(T0, T1), ProtocolError> { - let mut active0 = true; - - let mut out0 = None; - let mut out1 = None; - - while out0.is_none() || out1.is_none() { - if active0 { - let action = prot0.poke()?; - match action { - Action::Wait => active0 = false, - Action::SendMany(m) => prot1.message(p0, m), - Action::SendPrivate(to, m) if to == p1 => { - prot1.message(p0, m); - } - Action::Return(out) => out0 = Some(out), - // Ignore other actions, which means sending private messages to other people. - Action::SendPrivate(..) => {} - } - } else { - let action = prot1.poke()?; - match action { - Action::Wait => active0 = true, - Action::SendMany(m) => prot0.message(p1, m), - Action::SendPrivate(to, m) if to == p0 => { - prot0.message(p1, m); - } - Action::Return(out) => out1 = Some(out), - // Ignore other actions, which means sending private messages to other people. - Action::SendPrivate(..) => {} - } - } - } - - Ok(( - out0.ok_or_else(|| ProtocolError::Other("out0 is None".to_string()))?, - out1.ok_or_else(|| ProtocolError::Other("out1 is None".to_string()))?, - )) -} - -// +++++++++++++++++ DKG Functions +++++++++++++++++ // -pub type GenOutput = Vec<(Participant, KeygenOutput)>; -type DKGGenProtocol = GenProtocol>; - -/// Runs distributed keygen -/// If the protocol succeeds, returns a sorted vector based on participants id -pub fn run_keygen(participants: &[Participant], threshold: usize) -> GenOutput -where - Element: Send, - Scalar: Send, -{ - let mut protocols: DKGGenProtocol = Vec::with_capacity(participants.len()); - - for p in participants { - let protocol = keygen::(participants, *p, threshold, OsRng).unwrap(); - protocols.push((*p, Box::new(protocol))); - } - - let mut result = run_protocol(protocols).unwrap(); - result.sort_by_key(|(p, _)| *p); - result -} - -/// Runs distributed refresh -/// If the protocol succeeds, returns a sorted vector based on participants id -pub fn run_refresh( - participants: &[Participant], - keys: &[(Participant, KeygenOutput)], - threshold: usize, -) -> GenOutput -where - Element: Send, - Scalar: Send, -{ - let mut protocols: DKGGenProtocol = Vec::with_capacity(participants.len()); - - for (p, out) in keys { - let protocol = refresh::( - Some(out.private_share), - out.public_key, - participants, - threshold, - *p, - OsRng, - ) - .unwrap(); - protocols.push((*p, Box::new(protocol))); - } - - let mut result = run_protocol(protocols).unwrap(); - result.sort_by_key(|(p, _)| *p); - result -} - -/// Runs distributed reshare -/// If the protocol succeeds, returns a sorted vector based on participants id -pub fn run_reshare( - participants: &[Participant], - pub_key: &VerifyingKey, - keys: &[(Participant, KeygenOutput)], - old_threshold: usize, - new_threshold: usize, - new_participants: &[Participant], -) -> GenOutput -where - Element: Send, - Scalar: Send, -{ - assert!(!new_participants.is_empty()); - let mut setup = vec![]; - - for new_participant in new_participants { - let mut is_break = false; - for (p, k) in keys { - if p == new_participant { - setup.push((*p, (Some(k.private_share), k.public_key))); - is_break = true; - break; - } - } - if !is_break { - setup.push((*new_participant, (None, *pub_key))); - } - } - - let mut protocols: DKGGenProtocol = Vec::with_capacity(participants.len()); - - for (p, out) in &setup { - let protocol = reshare( - participants, - old_threshold, - out.0, - out.1, - new_participants, - new_threshold, - *p, - OsRng, - ) - .unwrap(); - protocols.push((*p, Box::new(protocol))); - } - - let mut result = run_protocol(protocols).unwrap(); - result.sort_by_key(|(p, _)| *p); - result -} - -/// Assert that each participant has the same view of the public key -pub fn assert_public_key_invariant( - participants: &[(Participant, KeygenOutput)], -) { - let vk = participants.first().unwrap().1.public_key; - - if participants - .iter() - .any(|(_, key_pair)| key_pair.public_key != vk) - { - panic!("public key package is not the same for all participants"); - } -} - -// +++++++++++++++++ Signing Functions +++++++++++++++++ // -/// Runs the signing algorithm for ECDSA. -/// The scheme must be asymmetric as in: there exists a coordinator that is different than participants. -/// Only used for unit tests. -pub fn run_sign( - participants_presign: Vec<(Participant, PresignOutput)>, - coordinator: Participant, - public_key: Element, - msg_hash: Scalar, - sign: F, -) -> Result, Box> -where - F: Fn( - &[Participant], - Participant, - Participant, - Element, - PresignOutput, - Scalar, - ) -> Result>, InitializationError>, -{ - let mut protocols: GenProtocol = Vec::with_capacity(participants_presign.len()); - - let participants: Vec = participants_presign.iter().map(|(p, _)| *p).collect(); - let participants = participants.as_slice(); - for (p, presignature) in participants_presign { - let protocol = sign( - participants, - coordinator, - p, - public_key, - presignature, - msg_hash, - )?; - - protocols.push((p, protocol)); - } - - Ok(run_protocol(protocols)?) -} - -/// Checks that the list contains all None but one element -/// and verifies such element belongs to the coordinator -pub fn one_coordinator_output( - all_sigs: Vec<(Participant, Option)>, - coordinator: Participant, -) -> Result { - let mut some_iter = all_sigs.into_iter().filter(|(_, sig)| sig.is_some()); - - // test there is at least one not None element - let (p, c_opt) = some_iter - .next() - .ok_or(ProtocolError::MismatchCoordinatorOutput)?; - - // test the coordinator is the one owning the output - if coordinator != p { - return Err(ProtocolError::MismatchCoordinatorOutput); - } - - // test the participant is unique - let out = c_opt.ok_or(ProtocolError::MismatchCoordinatorOutput)?; - - if some_iter.next().is_some() { - return Err(ProtocolError::MismatchCoordinatorOutput); - } - Ok(out) -} - -// Taken from https://github.com/ZcashFoundation/frost/blob/3ffc19d8f473d5bc4e07ed41bc884bdb42d6c29f/frost-secp256k1/tests/common_traits_tests.rs#L9 -#[allow(clippy::unnecessary_literal_unwrap)] -pub fn check_common_traits_for_type(v: &T) { - // Make sure can be debug-printed. This also catches if the Debug does not - // have an endless recursion (a popular mistake). - println!("{v:?}"); - // Test Clone and Eq - assert_eq!(*v, v.clone()); - // Make sure it can be unwrapped in a Result (which requires Debug). - let e: Result = Ok(v.clone()); - assert_eq!(*v, e.unwrap()); -} - -pub struct TestGenerators { - pub participants: Vec, - pub threshold: usize, -} - -type ParticipantAndProtocol = (Participant, Box>); - -impl TestGenerators { - pub fn new(num_participants: usize, threshold: usize) -> Self { - Self { - participants: (0..num_participants) - .map(|_| Participant::from(rand::random::())) - .collect::>(), - threshold, - } - } - - pub fn new_contiguous_participant_ids(num_participants: usize, threshold: usize) -> Self { - Self { - participants: (0..num_participants) - .map(|i| Participant::from(i as u32)) - .collect::>(), - threshold, - } - } - - pub fn make_ecdsa_keygens(&self) -> HashMap { - let mut protocols: Vec> = Vec::new(); - for participant in &self.participants { - protocols.push(( - *participant, - Box::new( - keygen::( - &self.participants, - *participant, - self.threshold, - OsRng, - ) - .unwrap(), - ), - )); - } - run_protocol(protocols).unwrap().into_iter().collect() - } - - pub fn make_eddsa_keygens(&self) -> HashMap { - let mut protocols: Vec> = Vec::new(); - for participant in &self.participants { - protocols.push(( - *participant, - Box::new( - keygen::( - &self.participants, - *participant, - self.threshold, - OsRng, - ) - .unwrap(), - ), - )); - } - run_protocol(protocols).unwrap().into_iter().collect() - } - - pub fn make_ckd_keygens(&self) -> HashMap { - let mut protocols: Vec> = Vec::new(); - for participant in &self.participants { - protocols.push(( - *participant, - Box::new( - keygen::( - &self.participants, - *participant, - self.threshold, - OsRng, - ) - .unwrap(), - ), - )); - } - run_protocol(protocols).unwrap().into_iter().collect() - } - - pub fn make_triples(&self) -> HashMap { - let mut protocols: Vec> = Vec::new(); - for participant in &self.participants { - protocols.push(( - *participant, - Box::new( - ecdsa::ot_based_ecdsa::triples::generate_triple( - &self.participants, - *participant, - self.threshold, - OsRng, - ) - .unwrap(), - ), - )); - } - run_protocol(protocols).unwrap().into_iter().collect() - } - - pub fn make_presignatures( - &self, - triple0s: &HashMap, - triple1s: &HashMap, - keygens: &HashMap, - ) -> HashMap { - let mut protocols: Vec> = - Vec::new(); - for participant in &self.participants { - protocols.push(( - *participant, - Box::new( - ecdsa::ot_based_ecdsa::presign::presign( - &self.participants, - *participant, - ecdsa::ot_based_ecdsa::PresignArguments { - triple0: triple0s[participant].clone(), - triple1: triple1s[participant].clone(), - keygen_out: keygens[participant].clone(), - threshold: self.threshold, - }, - ) - .unwrap(), - ), - )); - } - run_protocol(protocols).unwrap().into_iter().collect() - } - - pub fn make_signature( - &self, - presignatures: &HashMap, - public_key: AffinePoint, - msg_hash: ecdsa::Scalar, - ) -> ecdsa::Signature { - let mut protocols: Vec>> = Vec::new(); - let leader = self.participants[0]; - for participant in &self.participants { - let msg_hash_bytes: [u8; 32] = msg_hash.to_bytes().into(); - let presign_out = presignatures[participant].clone(); - let entropy = [0u8; 32]; - - let tweak = [1u8; 32]; - let tweak = ecdsa::Scalar::from_repr(tweak.into()).unwrap(); - let tweak = crate::Tweak::new(tweak); - - let rerand_args = ecdsa::RerandomizationArguments::new( - public_key, - tweak, - msg_hash_bytes, - presign_out.big_r, - ParticipantList::new(&self.participants).unwrap(), - entropy, - ); - - let derived_public_key = tweak - .derive_verifying_key(&VerifyingKey::new(public_key.into())) - .to_element() - .to_affine(); - - let rerandomized_presignature = - ecdsa::ot_based_ecdsa::RerandomizedPresignOutput::rerandomize_presign( - &presign_out, - &rerand_args, - ) - .unwrap(); - - protocols.push(( - *participant, - Box::new( - ecdsa::ot_based_ecdsa::sign::sign( - &self.participants, - leader, - *participant, - derived_public_key, - rerandomized_presignature, - msg_hash, - ) - .unwrap(), - ), - )); - } - run_protocol(protocols) - .unwrap() - .iter() - .find_map(|(p, sig)| if *p == leader { Some(sig) } else { None }) - .unwrap() - .as_ref() - .unwrap() - .clone() - } -} diff --git a/src/test_utils/dkg.rs b/src/test_utils/dkg.rs new file mode 100644 index 00000000..91ffeb6c --- /dev/null +++ b/src/test_utils/dkg.rs @@ -0,0 +1,125 @@ +use rand_core::OsRng; + +use crate::participants::Participant; +use crate::test_utils::{run_protocol, GenOutput, GenProtocol}; +use crate::{keygen, refresh, reshare, Ciphersuite, Element, KeygenOutput, Scalar, VerifyingKey}; + +// +++++++++++++++++ DKG Functions +++++++++++++++++ // +type DKGGenProtocol = GenProtocol>; + +/// Runs distributed keygen +/// If the protocol succeeds, returns a sorted vector based on participants id +pub fn run_keygen(participants: &[Participant], threshold: usize) -> GenOutput +where + Element: Send, + Scalar: Send, +{ + let mut protocols: DKGGenProtocol = Vec::with_capacity(participants.len()); + + for p in participants { + let protocol = keygen::(participants, *p, threshold, OsRng).unwrap(); + protocols.push((*p, Box::new(protocol))); + } + + let mut result = run_protocol(protocols).unwrap(); + result.sort_by_key(|(p, _)| *p); + result +} + +/// Runs distributed refresh +/// If the protocol succeeds, returns a sorted vector based on participants id +pub fn run_refresh( + participants: &[Participant], + keys: &[(Participant, KeygenOutput)], + threshold: usize, +) -> GenOutput +where + Element: Send, + Scalar: Send, +{ + let mut protocols: DKGGenProtocol = Vec::with_capacity(participants.len()); + + for (p, out) in keys { + let protocol = refresh::( + Some(out.private_share), + out.public_key, + participants, + threshold, + *p, + OsRng, + ) + .unwrap(); + protocols.push((*p, Box::new(protocol))); + } + + let mut result = run_protocol(protocols).unwrap(); + result.sort_by_key(|(p, _)| *p); + result +} + +/// Runs distributed reshare +/// If the protocol succeeds, returns a sorted vector based on participants id +pub fn run_reshare( + participants: &[Participant], + pub_key: &VerifyingKey, + keys: &[(Participant, KeygenOutput)], + old_threshold: usize, + new_threshold: usize, + new_participants: &[Participant], +) -> GenOutput +where + Element: Send, + Scalar: Send, +{ + assert!(!new_participants.is_empty()); + let mut setup = vec![]; + + for new_participant in new_participants { + let mut is_break = false; + for (p, k) in keys { + if p == new_participant { + setup.push((*p, (Some(k.private_share), k.public_key))); + is_break = true; + break; + } + } + if !is_break { + setup.push((*new_participant, (None, *pub_key))); + } + } + + let mut protocols: DKGGenProtocol = Vec::with_capacity(participants.len()); + + for (p, out) in &setup { + let protocol = reshare( + participants, + old_threshold, + out.0, + out.1, + new_participants, + new_threshold, + *p, + OsRng, + ) + .unwrap(); + protocols.push((*p, Box::new(protocol))); + } + + let mut result = run_protocol(protocols).unwrap(); + result.sort_by_key(|(p, _)| *p); + result +} + +/// Assert that each participant has the same view of the public key +pub fn assert_public_key_invariant( + participants: &[(Participant, KeygenOutput)], +) { + let vk = participants.first().unwrap().1.public_key; + + if participants + .iter() + .any(|(_, key_pair)| key_pair.public_key != vk) + { + panic!("public key package is not the same for all participants"); + } +} diff --git a/src/test_utils/mockrng.rs b/src/test_utils/mockrng.rs new file mode 100644 index 00000000..baaf96fe --- /dev/null +++ b/src/test_utils/mockrng.rs @@ -0,0 +1,28 @@ +use rand::{CryptoRng, RngCore}; +use rand_chacha::{rand_core::SeedableRng, ChaCha12Rng}; + +/// Used for deterministic Rngs and only in testing +pub struct MockCryptoRng(ChaCha12Rng); + +impl MockCryptoRng { + pub fn seed_from_u64(seed: u64) -> Self { + Self(ChaCha12Rng::seed_from_u64(seed)) + } +} + +impl RngCore for MockCryptoRng { + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest); + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.0.try_fill_bytes(dest) + } +} + +impl CryptoRng for MockCryptoRng {} diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs new file mode 100644 index 00000000..18e5d15f --- /dev/null +++ b/src/test_utils/mod.rs @@ -0,0 +1,49 @@ +#![allow( + clippy::panic, + clippy::missing_panics_doc, + clippy::unwrap_used, + clippy::cast_possible_truncation +)] + +mod dkg; +mod mockrng; +mod participants; +mod presign; +mod run_protocol; +mod sign; +pub mod test_generators; + +use crate::errors::ProtocolError; +use crate::participants::Participant; +use crate::protocol::Protocol; +use crate::KeygenOutput; +use rand_core::CryptoRngCore; + +/// Type representing DKG output keys +pub type GenOutput = Vec<(Participant, KeygenOutput)>; +/// Type representing DKG output protocols runs +pub type GenProtocol = Vec<(Participant, Box>)>; +/// Type for a deterministic RNG +pub use mockrng::MockCryptoRng; + +pub use dkg::{assert_public_key_invariant, run_keygen, run_refresh, run_reshare}; +pub use participants::{generate_participants, generate_participants_with_random_ids}; +pub use presign::ecdsa_generate_rerandpresig_args; +pub use run_protocol::{run_protocol, run_two_party_protocol}; +pub use sign::{check_one_coordinator_output, run_sign}; +pub use test_generators::*; + +/// Checks that the list contains all None but one element +/// and verifies such element belongs to the coordinator +pub fn one_coordinator_output( + all_sigs: Vec<(Participant, Option)>, + coordinator: Participant, +) -> Result { + check_one_coordinator_output(all_sigs, coordinator) +} + +pub fn random_32_bytes(rng: &mut impl CryptoRngCore) -> [u8; 32] { + let mut bytes: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut bytes); + bytes +} diff --git a/src/test_utils/participants.rs b/src/test_utils/participants.rs new file mode 100644 index 00000000..f7a1f1ac --- /dev/null +++ b/src/test_utils/participants.rs @@ -0,0 +1,24 @@ +use crate::participants::Participant; +use rand_core::CryptoRngCore; + +// +++++++++++++++++ Participants Utilities +++++++++++++++++ // +/// Generates a vector of `number` participants, sorted by the participant id. +/// The participants ids range from 0 to `number`-1 +pub fn generate_participants(number: usize) -> Vec { + (0..u32::try_from(number).unwrap()) + .map(Participant::from) + .collect::>() +} + +/// Generates a vector of `number` participants, sorted by the participant id. +/// The participants ids are drawn from rng. +pub fn generate_participants_with_random_ids( + number: usize, + rng: &mut impl CryptoRngCore, +) -> Vec { + let mut participants = (0..number) + .map(|_| Participant::from(rng.next_u32())) + .collect::>(); + participants.sort(); + participants +} diff --git a/src/test_utils/presign.rs b/src/test_utils/presign.rs new file mode 100644 index 00000000..70fc0a72 --- /dev/null +++ b/src/test_utils/presign.rs @@ -0,0 +1,43 @@ +use k256::AffinePoint; +use rand_core::{CryptoRngCore, OsRng}; + +use crate::ecdsa::{RerandomizationArguments, Tweak}; +use crate::frost_secp256k1::Secp256K1Sha256; +use crate::{Participant, ParticipantList, Scalar, VerifyingKey}; + +/// Generates at random 32 bytes +fn random_32_bytes(rng: &mut impl CryptoRngCore) -> [u8; 32] { + let mut bytes: [u8; 32] = [0u8; 32]; + rng.fill_bytes(&mut bytes); + bytes +} + +// +++++++++++++++++ Presignature Rerandomization +++++++++++++++++ // +/// Rerandomizes an ECDSA presignature. +/// Takes pk and R as input and generates a random message hash and entropy. +/// Outputs rerandomization arguments and the message hash +pub fn ecdsa_generate_rerandpresig_args( + rng: &mut impl CryptoRngCore, + participants: &[Participant], + pk: VerifyingKey, + big_r: AffinePoint, +) -> (RerandomizationArguments, Scalar) { + let pk = pk.to_element().to_affine(); + let tweak = Tweak::new(frost_core::random_nonzero::(&mut OsRng)); + + let msg_hash = ::random(&mut OsRng); + let entropy = random_32_bytes(rng); + // Generate unique ten ParticipantId values + let participants = + ParticipantList::new(participants).expect("Participant list generation should not fail"); + + let args = RerandomizationArguments::new( + pk, + tweak, + msg_hash.to_bytes().into(), + big_r, + participants, + entropy, + ); + (args, msg_hash) +} diff --git a/src/test_utils/run_protocol.rs b/src/test_utils/run_protocol.rs new file mode 100644 index 00000000..0e85e547 --- /dev/null +++ b/src/test_utils/run_protocol.rs @@ -0,0 +1,101 @@ +use crate::errors::ProtocolError; +use crate::participants::Participant; +use crate::protocol::{Action, Protocol}; +use std::collections::HashMap; + +// +++++++++++++++++ Any Protocol +++++++++++++++++ // +/// Run a protocol to completion, synchronously. +/// +/// This works by executing each participant in order. +/// +/// The reason this function exists is as a convenient testing utility. +/// In practice each protocol participant is likely running on a different machine, +/// and so orchestrating the protocol would happen differently. +pub fn run_protocol( + mut ps: Vec<(Participant, Box>)>, +) -> Result, ProtocolError> { + let indices: HashMap = + ps.iter().enumerate().map(|(i, (p, _))| (*p, i)).collect(); + + let size = ps.len(); + let mut out = Vec::with_capacity(size); + while out.len() < size { + for i in 0..size { + while { + let action = ps[i].1.poke()?; + match action { + Action::Wait => false, + Action::SendMany(m) => { + for j in 0..size { + if i == j { + continue; + } + let from = ps[i].0; + ps[j].1.message(from, m.clone()); + } + true + } + Action::SendPrivate(to, m) => { + let from = ps[i].0; + ps[indices[&to]].1.message(from, m); + true + } + Action::Return(r) => { + out.push((ps[i].0, r)); + false + } + } + } {} + } + } + Ok(out) +} + +/// Like [`run_protocol()`], except for just two parties. +/// +/// This is more useful for testing two party protocols with assymetric results, +/// since the return types for the two protocols can be different. +pub fn run_two_party_protocol( + p0: Participant, + p1: Participant, + prot0: &mut dyn Protocol, + prot1: &mut dyn Protocol, +) -> Result<(T0, T1), ProtocolError> { + let mut active0 = true; + + let mut out0 = None; + let mut out1 = None; + + while out0.is_none() || out1.is_none() { + if active0 { + let action = prot0.poke()?; + match action { + Action::Wait => active0 = false, + Action::SendMany(m) => prot1.message(p0, m), + Action::SendPrivate(to, m) if to == p1 => { + prot1.message(p0, m); + } + Action::Return(out) => out0 = Some(out), + // Ignore other actions, which means sending private messages to other people. + Action::SendPrivate(..) => {} + } + } else { + let action = prot1.poke()?; + match action { + Action::Wait => active0 = true, + Action::SendMany(m) => prot0.message(p1, m), + Action::SendPrivate(to, m) if to == p0 => { + prot0.message(p1, m); + } + Action::Return(out) => out1 = Some(out), + // Ignore other actions, which means sending private messages to other people. + Action::SendPrivate(..) => {} + } + } + } + + Ok(( + out0.ok_or_else(|| ProtocolError::Other("out0 is None".to_string()))?, + out1.ok_or_else(|| ProtocolError::Other("out1 is None".to_string()))?, + )) +} diff --git a/src/test_utils/sign.rs b/src/test_utils/sign.rs new file mode 100644 index 00000000..ad77b880 --- /dev/null +++ b/src/test_utils/sign.rs @@ -0,0 +1,74 @@ +use crate::errors::{InitializationError, ProtocolError}; +use crate::participants::Participant; +use crate::protocol::Protocol; +use crate::test_utils::{run_protocol, GenProtocol}; +use crate::{Ciphersuite, Element, Scalar}; +use std::error::Error; + +// +++++++++++++++++ Signing Functions +++++++++++++++++ // +/// Runs the signing algorithm for ECDSA. +/// The scheme must be asymmetric as in: there exists a coordinator that is different than participants. +/// Only used for unit tests. +pub fn run_sign( + participants_presign: Vec<(Participant, PresignOutput)>, + coordinator: Participant, + public_key: Element, + msg_hash: Scalar, + sign: F, +) -> Result, Box> +where + F: Fn( + &[Participant], + Participant, + Participant, + Element, + PresignOutput, + Scalar, + ) -> Result>, InitializationError>, +{ + let mut protocols: GenProtocol = Vec::with_capacity(participants_presign.len()); + + let participants: Vec = participants_presign.iter().map(|(p, _)| *p).collect(); + let participants = participants.as_slice(); + for (p, presignature) in participants_presign { + let protocol = sign( + participants, + coordinator, + p, + public_key, + presignature, + msg_hash, + )?; + + protocols.push((p, protocol)); + } + + Ok(run_protocol(protocols)?) +} + +/// Checks that the list contains all None but one element +/// and verifies such element belongs to the coordinator +pub fn check_one_coordinator_output( + all_sigs: Vec<(Participant, Option)>, + coordinator: Participant, +) -> Result { + let mut some_iter = all_sigs.into_iter().filter(|(_, sig)| sig.is_some()); + + // test there is at least one not None element + let (p, c_opt) = some_iter + .next() + .ok_or(ProtocolError::MismatchCoordinatorOutput)?; + + // test the coordinator is the one owning the output + if coordinator != p { + return Err(ProtocolError::MismatchCoordinatorOutput); + } + + // test the participant is unique + let out = c_opt.ok_or(ProtocolError::MismatchCoordinatorOutput)?; + + if some_iter.next().is_some() { + return Err(ProtocolError::MismatchCoordinatorOutput); + } + Ok(out) +} diff --git a/src/test_utils/test_generators.rs b/src/test_utils/test_generators.rs new file mode 100644 index 00000000..c9608564 --- /dev/null +++ b/src/test_utils/test_generators.rs @@ -0,0 +1,224 @@ +// This module provides generic functions to be used in the mpc repository +use k256::elliptic_curve::PrimeField; +use k256::AffinePoint; +use rand_core::OsRng; +use std::collections::HashMap; + +use crate::confidential_key_derivation as ckd; +use crate::ecdsa::ot_based_ecdsa::triples::TripleGenerationOutput; +use crate::frost_ed25519::Ed25519Sha512; +use crate::frost_secp256k1::Secp256K1Sha256; +use crate::participants::Participant; +use crate::protocol::Protocol; +use crate::{ecdsa, eddsa, ParticipantList}; +use crate::{keygen, VerifyingKey}; + +use crate::test_utils::run_protocol; + +// Taken from https://github.com/ZcashFoundation/frost/blob/3ffc19d8f473d5bc4e07ed41bc884bdb42d6c29f/frost-secp256k1/tests/common_traits_tests.rs#L9 +#[allow(clippy::unnecessary_literal_unwrap)] +pub fn check_common_traits_for_type(v: &T) { + // Make sure can be debug-printed. This also catches if the Debug does not + // have an endless recursion (a popular mistake). + println!("{v:?}"); + // Test Clone and Eq + assert_eq!(*v, v.clone()); + // Make sure it can be unwrapped in a Result (which requires Debug). + let e: Result = Ok(v.clone()); + assert_eq!(*v, e.unwrap()); +} + +pub struct TestGenerators { + pub participants: Vec, + pub threshold: usize, +} + +type ParticipantAndProtocol = (Participant, Box>); + +impl TestGenerators { + pub fn new(num_participants: usize, threshold: usize) -> Self { + Self { + participants: (0..num_participants) + .map(|_| Participant::from(rand::random::())) + .collect::>(), + threshold, + } + } + + pub fn new_contiguous_participant_ids(num_participants: usize, threshold: usize) -> Self { + Self { + participants: (0..num_participants) + .map(|i| Participant::from(i as u32)) + .collect::>(), + threshold, + } + } + + pub fn make_ecdsa_keygens(&self) -> HashMap { + let mut protocols: Vec> = Vec::new(); + for participant in &self.participants { + protocols.push(( + *participant, + Box::new( + keygen::( + &self.participants, + *participant, + self.threshold, + OsRng, + ) + .unwrap(), + ), + )); + } + run_protocol(protocols).unwrap().into_iter().collect() + } + + pub fn make_eddsa_keygens(&self) -> HashMap { + let mut protocols: Vec> = Vec::new(); + for participant in &self.participants { + protocols.push(( + *participant, + Box::new( + keygen::( + &self.participants, + *participant, + self.threshold, + OsRng, + ) + .unwrap(), + ), + )); + } + run_protocol(protocols).unwrap().into_iter().collect() + } + + pub fn make_ckd_keygens(&self) -> HashMap { + let mut protocols: Vec> = Vec::new(); + for participant in &self.participants { + protocols.push(( + *participant, + Box::new( + keygen::( + &self.participants, + *participant, + self.threshold, + OsRng, + ) + .unwrap(), + ), + )); + } + run_protocol(protocols).unwrap().into_iter().collect() + } + + pub fn make_triples(&self) -> HashMap { + let mut protocols: Vec> = Vec::new(); + for participant in &self.participants { + protocols.push(( + *participant, + Box::new( + ecdsa::ot_based_ecdsa::triples::generate_triple( + &self.participants, + *participant, + self.threshold, + OsRng, + ) + .unwrap(), + ), + )); + } + run_protocol(protocols).unwrap().into_iter().collect() + } + + pub fn make_presignatures( + &self, + triple0s: &HashMap, + triple1s: &HashMap, + keygens: &HashMap, + ) -> HashMap { + let mut protocols: Vec> = + Vec::new(); + for participant in &self.participants { + protocols.push(( + *participant, + Box::new( + ecdsa::ot_based_ecdsa::presign::presign( + &self.participants, + *participant, + ecdsa::ot_based_ecdsa::PresignArguments { + triple0: triple0s[participant].clone(), + triple1: triple1s[participant].clone(), + keygen_out: keygens[participant].clone(), + threshold: self.threshold, + }, + ) + .unwrap(), + ), + )); + } + run_protocol(protocols).unwrap().into_iter().collect() + } + + pub fn make_signature( + &self, + presignatures: &HashMap, + public_key: AffinePoint, + msg_hash: ecdsa::Scalar, + ) -> ecdsa::Signature { + let mut protocols: Vec>> = Vec::new(); + let leader = self.participants[0]; + for participant in &self.participants { + let msg_hash_bytes: [u8; 32] = msg_hash.to_bytes().into(); + let presign_out = presignatures[participant].clone(); + let entropy = [0u8; 32]; + + let tweak = [1u8; 32]; + let tweak = ecdsa::Scalar::from_repr(tweak.into()).unwrap(); + let tweak = crate::Tweak::new(tweak); + + let rerand_args = ecdsa::RerandomizationArguments::new( + public_key, + tweak, + msg_hash_bytes, + presign_out.big_r, + ParticipantList::new(&self.participants).unwrap(), + entropy, + ); + + let derived_public_key = tweak + .derive_verifying_key(&VerifyingKey::new(public_key.into())) + .to_element() + .to_affine(); + + let rerandomized_presignature = + ecdsa::ot_based_ecdsa::RerandomizedPresignOutput::rerandomize_presign( + &presign_out, + &rerand_args, + ) + .unwrap(); + + protocols.push(( + *participant, + Box::new( + ecdsa::ot_based_ecdsa::sign::sign( + &self.participants, + leader, + *participant, + derived_public_key, + rerandomized_presignature, + msg_hash, + ) + .unwrap(), + ), + )); + } + run_protocol(protocols) + .unwrap() + .iter() + .find_map(|(p, sig)| if *p == leader { Some(sig) } else { None }) + .unwrap() + .as_ref() + .unwrap() + .clone() + } +}