use crate::crypto::{ ciphersuite::Ciphersuite, hash::{DomainSeparator, HashOutput, domain_separate_hash}, polynomials::{Polynomial, PolynomialCommitment}, }; use crate::errors::{InitializationError, ProtocolError}; use crate::participants::{Participant, ParticipantList, ParticipantMap}; use crate::protocol::{ echo_broadcast::do_broadcast, helpers::recv_from_others, internal::SharedChannel, }; use crate::{KeygenOutput, ReconstructionThreshold}; use frost_core::keys::{ CoefficientCommitment, SecretShare, SigningShare, VerifiableSecretSharingCommitment, }; use frost_core::{ Challenge, Element, Error, Field, Group, Scalar, Signature, SigningKey, VerifyingKey, }; use rand_core::CryptoRngCore; /// This function prevents calling keyshare function with inproper inputs fn assert_keyshare_inputs( me: Participant, secret: &Scalar, old_reshare_package: Option<(VerifyingKey, ParticipantList)>, ) -> Result<(Option>, Option), ProtocolError> { let is_zero_secret = *secret == ::Field::zero(); if let Some((old_key, old_participants)) = old_reshare_package { if is_zero_secret { // return error if me is not a purely new joiner to the participants set // prevents accidentally calling keyshare with extremely old keyshares // that have nothing to do with the current resharing if old_participants.contains(me) { return Err(ProtocolError::AssertionFailed(format!( "{me:?} is running Resharing with a zero share but does belong to the old participant set" ))); } } else { // return error if me is part of the old participants set if !old_participants.contains(me) { return Err(ProtocolError::AssertionFailed(format!( "{me:?} is running Resharing with a non-zero share but does not belong to the old participant set" ))); } } Ok((Some(old_key), Some(old_participants))) } else { if is_zero_secret { return Err(ProtocolError::AssertionFailed(format!( "{me:?} is running DKG with a zero share" ))); } Ok((None, None)) } } /// Creates a commitment vector of coefficients * G /// If the first coefficient is set to zero then skip it fn generate_coefficient_commitment( secret_coefficients: &Polynomial, ) -> Result, ProtocolError> { let mut secret_coefficients = secret_coefficients.get_coefficients(); // we skip the zero share as neither zero scalar // nor identity group element are serializable if secret_coefficients.first() == Some(&::Field::zero()) { secret_coefficients.remove(0); } Polynomial::new(&secret_coefficients)?.commit_polynomial() } /// Generates the challenge for the proof of knowledge /// H(`domain_separator`, id, g^{secret} , R) fn challenge( domain_separator: &mut DomainSeparator, session_id: &HashOutput, id: Scalar, vk_share: &CoefficientCommitment, big_r: &Element, ) -> Result, ProtocolError> { let mut preimage = vec![]; let serialized_id = ::Field::serialize(&id); // Should not return Error // The function should not be called when the first coefficient is zero let serialized_vk_share = vk_share.serialize().map_err(|_| { ProtocolError::AssertionFailed( "The verification share could not be serialized as it is null" .to_string(), ) })?; let serialized_big_r = ::serialize(big_r).map_err(|_| { ProtocolError::AssertionFailed( "The group element R could not be serialized as it is the identity" .to_string(), ) })?; preimage.extend_from_slice(&domain_separator.to_le_bytes()); preimage.extend_from_slice(session_id.as_ref()); preimage.extend_from_slice(serialized_id.as_ref()); preimage.extend_from_slice(serialized_vk_share.as_ref()); preimage.extend_from_slice(serialized_big_r.as_ref()); domain_separator.increment(); let hash = C::HDKG(&preimage[..]).ok_or(ProtocolError::DKGNotSupported)?; Ok(Challenge::from_scalar(hash)) } /// Computes the proof of knowledge of the secret coefficient `a_0` /// used to generate the public polynomial. /// Generate a random k and compute R = g^k /// Compute mu = k + `a_0` * H(id, `context_string`, `g^{a_0`} , R) /// Output (R, mu) fn proof_of_knowledge( session_id: &HashOutput, domain_separator: &mut DomainSeparator, me: Participant, coefficients: &Polynomial, coefficient_commitment: &PolynomialCommitment, rng: &mut impl CryptoRngCore, ) -> Result, ProtocolError> { // creates an identifier for the participant let id = me.scalar::(); let vk_share = coefficient_commitment.eval_at_zero()?; // pick a random k_i and compute R_id = g^{k_id}, // Step 2.5 let (k, big_r) = ::generate_nonce(rng); // Step 2.6 // compute H(domain_separator, id, me, g^{a_0}, R_id) as a scalar let hash = challenge::(domain_separator, session_id, id, &vk_share, &big_r)?; let a_0 = coefficients.eval_at_zero()?.0; // Step 2.7 let mu = k + a_0 * hash.to_scalar(); Ok(Signature::new(big_r, mu)) } /// Verifies the proof of knowledge of the secret coefficients used to generate the /// public secret sharing commitment. fn internal_verify_proof_of_knowledge( session_id: &HashOutput, domain_separator: &mut DomainSeparator, participant: Participant, commitment: &VerifiableSecretSharingCommitment, proof_of_knowledge: &Signature, ) -> Result<(), ProtocolError> { // creates an identifier for the participant let id = participant.scalar::(); let vk_share = commitment .coefficients() .first() .ok_or_else(|| ProtocolError::AssertionFailed("Empty coefficient list".to_string()))?; let big_r = proof_of_knowledge.R(); let z = proof_of_knowledge.z(); let c = challenge::(domain_separator, session_id, id, vk_share, big_r)?; if *big_r != ::generator() * *z - vk_share.value() * c.to_scalar() { return Err(ProtocolError::InvalidProofOfKnowledge(participant)); } Ok(()) } /// Verifies the proof of knowledge of the secret coefficients used to generate the /// public secret sharing commitment. /// if the proof of knowledge is none then make sure that the participant is /// performing reshare and does not exist in the set of old participants fn verify_proof_of_knowledge( session_id: &HashOutput, domain_separator: &mut DomainSeparator, threshold: ReconstructionThreshold, participant: Participant, old_participants: Option, commitment: &VerifiableSecretSharingCommitment, proof_of_knowledge: Option<&Signature>, ) -> Result<(), ProtocolError> { let threshold = threshold.value(); match proof_of_knowledge { // if participant did not send anything but he is actually an old participant None => { // if basic dkg or participant is old if old_participants.is_none_or(|p| p.contains(participant)) { return Err(ProtocolError::MaliciousParticipant(participant)); } // since previous line did not abort, then we know participant is new indeed // check the commitment length is threshold - 1 if commitment.coefficients().len() != threshold - 1 { return Err(ProtocolError::IncorrectNumberOfCommitments); } // nothing to verify Ok(()) } // now we know the proof is not none Some(proof_of_knowledge) => { // if participant sent something but he is actually a new participant if old_participants.is_some_and(|p| !p.contains(participant)) { return Err(ProtocolError::MaliciousParticipant(participant)); } // since the previous did not abort, we know the participant is old or we are dealing with a dkg if commitment.coefficients().len() != threshold { return Err(ProtocolError::IncorrectNumberOfCommitments); } // creating an identifier as required by the syntax of verify_proof_of_knowledge of frost_core internal_verify_proof_of_knowledge( session_id, domain_separator, participant, commitment, proof_of_knowledge, ) } } } /// Takes a commitment and a commitment hash and checks that /// H(commitment) = `commitment_hash` fn verify_commitment_hash( session_id: &HashOutput, participant: Participant, domain_separator: &mut DomainSeparator, commitment: &VerifiableSecretSharingCommitment, all_hash_commitments: &ParticipantMap<'_, HashOutput>, ) -> Result<(), ProtocolError> { let actual_commitment_hash = all_hash_commitments.index(participant)?; let commitment_hash = domain_separate_hash(domain_separator, &(&participant, &commitment, &session_id))?; if *actual_commitment_hash != commitment_hash { return Err(ProtocolError::InvalidCommitmentHash); } Ok(()) } /// This function is called when the commitment length is threshold -1 /// i.e. when the new participant sent a polynomial with a non-existant constant term /// such a participant would do so as the identity is not serializable fn insert_identity_if_missing( threshold: ReconstructionThreshold, commitment_i: &VerifiableSecretSharingCommitment, ) -> VerifiableSecretSharingCommitment { // in case the participant was new and it sent a polynomial of length // threshold -1 (because the zero term is not serializable) let mut commitment_i = commitment_i.clone(); let mut coefficients_i = commitment_i.coefficients().to_vec(); if coefficients_i.len() == threshold.value() - 1 { let identity = CoefficientCommitment::new(::identity()); coefficients_i.insert(0, identity); commitment_i = VerifiableSecretSharingCommitment::new(coefficients_i); } commitment_i } // creates a signing share structure using my identifier, the received // signing share and the received commitment fn validate_received_share( me: Participant, from: Participant, signing_share_from: &SigningShare, commitment: &VerifiableSecretSharingCommitment, ) -> Result<(), ProtocolError> { let id = me.to_identifier::()?; // The verification is exactly the same as the regular SecretShare verification; // however the required components are in different places. // Build a temporary SecretShare so what we can call verify(). let secret_share = SecretShare::new(id, *signing_share_from, commitment.clone()); // Verify the share. We don't need the result. // Identify the culprit if an InvalidSecretShare error is returned. secret_share.verify().map_err(|e| { if let Error::InvalidSecretShare { .. } = e { ProtocolError::InvalidSecretShare(from) } else { ProtocolError::AssertionFailed(format!( "could not extract the verification key matching the secret share sent by {from:?}" )) } })?; Ok(()) } /// generates a verification key out of a public commited polynomial // TODO: Fixing this one is not trivial #[allow(clippy::needless_pass_by_value)] fn public_key_from_commitments( commitments: Vec<&VerifiableSecretSharingCommitment>, ) -> Result, ProtocolError> { let commitment = frost_core::keys::sum_commitments(&commitments) .map_err(|_| ProtocolError::IncorrectNumberOfCommitments)?; let vk = VerifyingKey::from_commitment(&commitment) .map_err(|_| ProtocolError::ErrorExtractVerificationKey)?; Ok(vk) } /// This function takes err as input. /// If err is None then broadcast success /// otherwise, broadcast failure /// If during broadcast it receives an error then propagates it /// This function is used in the final round of DKG async fn broadcast_success( chan: &mut SharedChannel, participants: &ParticipantList, me: Participant, session_id: HashOutput, ) -> Result<(), ProtocolError> { // broadcast node me succeded let vote_list = do_broadcast(chan, participants, me, (true, session_id)).await?; // unwrap here would never fail as the broadcast protocol ends only when the map is full let vote_list = vote_list .into_vec_or_none() .ok_or_else(|| ProtocolError::AssertionFailed("vote_list is empty".to_string()))?; // go through all the list of votes and check if any is fail or some does not contain the session id if !vote_list.iter().all(|(_, sid)| sid == &session_id) { return Err(ProtocolError::AssertionFailed( "A participant broadcast the wrong session id. Aborting Protocol!" .to_string(), )); } if !vote_list.iter().all(|&(boolean, _)| boolean) { return Err(ProtocolError::AssertionFailed( "A participant seems to have failed its checks. Aborting Protocol!" .to_string(), )); } // Wait for all the tasks to complete Ok(()) } /// Performs the heart of DKG, Reshare and Refresh protocols #[allow(clippy::too_many_lines)] async fn do_keyshare( mut chan: SharedChannel, participants: ParticipantList, me: Participant, threshold: ReconstructionThreshold, secret: Scalar, old_reshare_package: Option<(VerifyingKey, ParticipantList)>, rng: &mut impl CryptoRngCore, ) -> Result, ProtocolError> { let mut all_full_commitments = ParticipantMap::new(&participants); let mut domain_separator = DomainSeparator::new(); // Make sure you do not call do_keyshare with zero as secret on an old participant let (old_verification_key, old_participants) = assert_keyshare_inputs(me, &secret, old_reshare_package)?; // Start Round 1 // Step 1.2 let mut my_session_id = [0u8; 32]; // 256 bits rng.fill_bytes(&mut my_session_id); // Step 1.3 & 2.1 let session_ids = do_broadcast(&mut chan, &participants, me, my_session_id).await?; // Start Round 2 // generate your secret polynomial p with the constant term set to the secret // and the rest of the coefficients are picked at random // because the library does not allow serializing the zero and identity term, // this function does not add the zero coefficient // Step 2.2 let session_id = domain_separate_hash(&mut domain_separator, &session_ids)?; // Step 2.3 // the degree of the polynomial is threshold - 1 let degree = threshold .value() .checked_sub(1) .ok_or(ProtocolError::IntegerOverflow)?; let secret_coefficients = Polynomial::::generate_polynomial(Some(secret), degree, rng)?; // Compute the multiplication of every coefficient of p with the generator G // Step 2.4 let coefficient_commitment = generate_coefficient_commitment::(&secret_coefficients)?; // Generates a proof of knowledge if me is not holding the zero secret. let proof_domain_separator = domain_separator.clone(); // Send none if me is a new participant let generate_proof: bool = old_participants.as_ref().is_none_or(|old| old.contains(me)); // Step 2.5 2.6 2.7 let proof_of_knowledge = if generate_proof { Some(proof_of_knowledge( &session_id, &mut domain_separator, me, &secret_coefficients, &coefficient_commitment, rng, )?) } else { // increment domain separator to match the old participants domain_separator.increment(); None }; // Create the public polynomial = secret coefficients times G let commitment = VerifiableSecretSharingCommitment::new(coefficient_commitment.get_coefficients()); // hash commitment and send it // Step 2.8 let commit_domain_separator = domain_separator.clone(); let commitment_hash = domain_separate_hash(&mut domain_separator, &(&me, &commitment, &session_id))?; // Step 2.9 let wait_round_1 = chan.next_waitpoint(); chan.send_many(wait_round_1, &commitment_hash)?; // receive commitment_hash let mut all_hash_commitments = ParticipantMap::new(&participants); all_hash_commitments.put(me, commitment_hash); // Step 3.1 for (from, their_commitment_hash) in recv_from_others(&chan, wait_round_1, &participants, me).await? { all_hash_commitments.put(from, their_commitment_hash); } // Start Round 3 // add my commitment to the map with the proper commitment sizes = threshold let my_full_commitment = insert_identity_if_missing(threshold, &commitment); all_full_commitments.put(me, my_full_commitment); // Broadcast the commitment and the proof of knowledge // Step 3.2 and 4.1 let commitments_and_proofs_map = do_broadcast( &mut chan, &participants, me, (commitment, proof_of_knowledge), ) .await?; // Start Round 4 let wait_round_3 = chan.next_waitpoint(); // Step 4.2 4.3 and 4.4 for p in participants.others(me) { let (commitment_i, proof_i) = commitments_and_proofs_map.index(p)?; // verify the proof of knowledge // if proof is none then make sure the participant is new // and performing a resharing not a DKG verify_proof_of_knowledge( &session_id, &mut proof_domain_separator.clone(), // you want to have the same state threshold, p, old_participants.clone(), commitment_i, proof_i.as_ref(), )?; // verify that the commitment sent hashes to the received commitment_hash in round 1 verify_commitment_hash( &session_id, p, &mut commit_domain_separator.clone(), // you want to have the same state commitment_i, &all_hash_commitments, )?; // in case the participant was new and it sent a polynomial of length // threshold -1 (because the zero term is not serializable) let full_commitment_i = insert_identity_if_missing(threshold, commitment_i); // add received full commitment all_full_commitments.put(p, full_commitment_i); chan.yield_point().await; } // Verify vk asap // cannot fail as all_commitments at least contains my commitment let all_commitments_refs = all_full_commitments.to_refs_or_none().ok_or_else(|| { ProtocolError::AssertionFailed("all_full_commitments is empty".to_string()) })?; // Step 4.5 let verifying_key = public_key_from_commitments(all_commitments_refs)?; // Step 4.5 +++ // In the case of Resharing, check if the old public key is the same as the new one if let Some(old_vk) = old_verification_key { // check the equality between the old key and the new key without failing the unwrap if old_vk != verifying_key { return Err(ProtocolError::AssertionFailed( "new public key does not match old public key".to_string(), )); } } // Step 4.6 for p in participants.others(me) { // securely send to each other participant a secret share // using the evaluation secret polynomial on the identifier of the recipient // should not panic as secret_coefficients are created internally let signing_share_to_p = secret_coefficients.eval_at_participant(p)?; // send the evaluation privately to participant p chan.send_private(wait_round_3, p, &signing_share_to_p)?; } // Start Round 5 // compute my secret evaluation of my private polynomial // should not panic as secret_coefficients are created internally let mut my_signing_share = secret_coefficients.eval_at_participant(me)?.0; // receive evaluations from all participants // Step 5.1 for (from, signing_share_from) in recv_from_others(&chan, wait_round_3, &participants, me).await? { // Verify the share // this deviates from the original FROST DKG paper // however it matches the FROST implementation of ZCash let full_commitment_from = all_full_commitments.index(from)?; // Step 5.2 validate_received_share::(me, from, &signing_share_from, full_commitment_from)?; // Compute the sum of all the owned secret shares // At the end of this loop, I will be owning a valid secret signing share // Step 5.3 my_signing_share = my_signing_share + signing_share_from.to_scalar(); chan.yield_point().await; } // Step 5.4 and Step 5.5 broadcast_success(&mut chan, &participants, me, session_id).await?; // Return the key pair Ok(KeygenOutput { private_share: SigningShare::new(my_signing_share), public_key: verifying_key, }) } pub async fn do_keygen( chan: SharedChannel, participants: ParticipantList, me: Participant, threshold: impl Into, mut rng: impl CryptoRngCore, ) -> Result, ProtocolError> { let threshold = threshold.into(); // pick share at random let secret = SigningKey::::new(&mut rng).to_scalar(); // call keyshare let keygen_output = do_keyshare::(chan, participants, me, threshold, secret, None, &mut rng).await?; Ok(keygen_output) } /// This function is to be called before running DKG /// It ensures that the input parameters are valid pub fn assert_key_invariants( participants: &[Participant], me: Participant, threshold: impl Into, ) -> Result { let threshold = usize::from(threshold.into()); // need enough participants if participants.len() < 2 { return Err(InitializationError::NotEnoughParticipants { participants: participants.len(), }); } // Step 1.1 // validate threshold if threshold > participants.len() { return Err(InitializationError::ThresholdTooLarge { threshold, max: participants.len(), }); } // Step 1.1 if threshold < 2 { return Err(InitializationError::ThresholdTooSmall { threshold, min: 2 }); } // ensure uniqueness of participants in the participant list let participants = ParticipantList::new(participants).ok_or(InitializationError::DuplicateParticipants)?; // ensure my presence in the participant list if !participants.contains(me) { return Err(InitializationError::MissingParticipant { role: "self", participant: me, }); } Ok(participants) } /// reshares the keyshares between the parties and allows changing the threshold #[allow(clippy::too_many_arguments)] pub async fn do_reshare( chan: SharedChannel, participants: ParticipantList, me: Participant, threshold: impl Into, old_signing_key: Option>, old_public_key: VerifyingKey, old_participants: ParticipantList, mut rng: impl CryptoRngCore, ) -> Result, ProtocolError> { let threshold = threshold.into(); let intersection = old_participants.intersection(&participants); // either extract the share and linearize it or set it to zero let secret = old_signing_key .map(|x_i| { intersection .lagrange::(me) .map(|lambda| lambda * x_i.to_scalar()) }) .transpose()? .unwrap_or_else(::Field::zero); let old_reshare_package = Some((old_public_key, old_participants)); let keygen_output = do_keyshare::( chan, participants, me, threshold, secret, old_reshare_package, &mut rng, ) .await?; Ok(keygen_output) } // Step 1.1 pub fn assert_reshare_keys_invariants( participants: &[Participant], me: Participant, threshold: impl Into, old_signing_key: Option>, old_threshold: impl Into, old_participants: &[Participant], ) -> Result<(ParticipantList, ParticipantList), InitializationError> { let threshold = usize::from(threshold.into()); let old_threshold = usize::from(old_threshold.into()); let participants = assert_key_invariants(participants, me, threshold)?; let old_participants = ParticipantList::new(old_participants).ok_or(InitializationError::DuplicateParticipants)?; // Step 1.1 if old_participants.intersection(&participants).len() < old_threshold { return Err(InitializationError::NotEnoughParticipantsForNewThreshold { threshold: old_threshold, participants: old_participants.intersection(&participants).len(), }); } // Step 1.1 // if me is not in the old participant set then ensure that old_signing_key is None if old_participants.contains(me) && old_signing_key.is_none() { return Err(InitializationError::BadParameters(format!( "party {me:?} is present in the old participant list but provided no share" ))); } Ok((participants, old_participants)) } #[cfg(test)] pub mod test { use super::{ assert_key_invariants, assert_reshare_keys_invariants, do_keygen, do_reshare, domain_separate_hash, }; use crate::crypto::ciphersuite::Ciphersuite; use crate::crypto::hash::DomainSeparator; use crate::errors::InitializationError; use crate::participants::{Participant, ParticipantList}; use crate::test_utils::MockCryptoRng; use crate::test_utils::{ GenOutput, assert_buffer_capacity, assert_public_key_invariant, build_buffer_test, generate_participants, run_and_assert_buffer_entries, run_keygen, run_refresh, run_reshare, }; use crate::{DKG_MAX_INCOMING_BUFFER_ENTRIES, keygen, reshare}; use crate::{KeygenOutput, ReconstructionThreshold}; use frost_core::{Field, Group}; use rand_core::{CryptoRngCore, SeedableRng}; use rstest::rstest; #[test] fn test_domain_separate_hash() { let mut cnt = DomainSeparator::new(); let participants_1 = generate_participants(3); let participants_2 = generate_participants(3); let hash_1 = domain_separate_hash(&mut cnt.clone(), &participants_1); let hash_2 = domain_separate_hash(&mut cnt, &participants_2); assert!(hash_1 == hash_2); // incremented let hash_2 = domain_separate_hash(&mut cnt, &participants_2); assert!(hash_1 != hash_2); } fn compute_private_key( keygen_result: &GenOutput, ) -> <::Field as Field>::Scalar { let participants = keygen_result.iter().map(|p| p.0).collect::>(); let shares = keygen_result .iter() .map(|r| r.1.private_share.to_scalar()) .collect::>(); let p_list = ParticipantList::new(&participants).unwrap(); let mut x = <::Field>::zero(); for i in 0..participants.len() { x = x + p_list.lagrange::(participants[i]).unwrap() * shares[i]; } x } pub fn test_keygen( participants: &[Participant], threshold: impl Into + Copy + Send + 'static, rng: &mut R, ) -> Vec<(Participant, KeygenOutput)> where ::Element: std::fmt::Debug, { let result = run_keygen::(participants, threshold, rng); assert!(result.len() == participants.len()); assert_public_key_invariant(&result); let pub_key = result[0].1.public_key.to_element(); let x = compute_private_key(&result); assert_eq!(::generator() * x, pub_key); result } #[allow(non_snake_case)] pub fn keygen__should_fail_if_threshold_is_below_limit< C: Ciphersuite, R: CryptoRngCore + SeedableRng + Send + 'static, >( rng: &mut R, ) where ::Element: std::fmt::Debug, { let threshold = 1; let participants = generate_participants(2); let rng_keygen = R::seed_from_u64(rng.next_u64()); let result = keygen::(&participants, participants[0], threshold, rng_keygen); assert_eq!( result.err().unwrap(), InitializationError::ThresholdTooSmall { threshold, min: 2 } ); // this threshold should work correctly test_keygen::(&participants, 2, rng); } pub fn test_refresh( participants: &[Participant], threshold: impl Into + Copy + Send + 'static, rng: &mut R, ) -> Vec<(Participant, KeygenOutput)> where ::Element: std::fmt::Debug, { let result0 = run_keygen::(participants, threshold, rng); assert_public_key_invariant(&result0); let pub_key0 = result0[0].1.public_key.to_element(); let result1 = run_refresh(participants, &result0, threshold, rng); assert_public_key_invariant(&result1); let x1 = compute_private_key(&result1); assert_eq!(::generator() * x1, pub_key0); result1 } pub fn test_reshare( participants: &[Participant], threshold0: impl Into + Copy + Send + 'static, threshold1: impl Into + Copy + Send + 'static, rng: &mut R, ) -> Vec<(Participant, KeygenOutput)> where ::Element: std::fmt::Debug, { let result0 = run_keygen::(participants, threshold0, rng); assert_public_key_invariant(&result0); let pub_key0 = result0[0].1.public_key; let mut new_participant = participants.to_vec(); new_participant.push(Participant::from(31u32)); let result1 = run_reshare( participants, &pub_key0, &result0, threshold0, threshold1, &new_participant, rng, ); assert_public_key_invariant(&result1); let x1 = compute_private_key(&result1); assert_eq!(::generator() * x1, pub_key0.to_element()); result1 } #[allow(non_snake_case)] pub fn reshare__should_fail_if_threshold_is_below_limit< C: Ciphersuite, R: CryptoRngCore + SeedableRng + Send + 'static, >( rng: &mut R, ) where ::Element: std::fmt::Debug, { let participants = generate_participants(2); let threshold0 = 2; let threshold1 = 1; let mut rng_keygen = R::seed_from_u64(rng.next_u64()); let result0 = run_keygen(&participants, threshold0, &mut rng_keygen); let pub_key = result0[0].1.public_key; let rng_reshare = R::seed_from_u64(rng.next_u64()); let result = reshare::( &participants, threshold0, None, pub_key, &participants, threshold1, participants[0], rng_reshare, ); assert_eq!( result.err().unwrap(), InitializationError::ThresholdTooSmall { threshold: threshold1, min: 2 } ); // These threshold parameters should work correctly test_reshare::(&participants, 2, 2, rng); } #[rstest] #[case(3, 2)] #[case(5, 3)] #[case(10, 4)] fn test_keygen_buffer_entries(#[case] num_participants: usize, #[case] threshold: usize) { // Given let participants = generate_participants(num_participants); let mut rng = MockCryptoRng::seed_from_u64(42); // When + Then assert_buffer_capacity( &participants, &mut rng, |comms, _p_list, p, rng_p| { let participant_list = assert_key_invariants(&participants, p, threshold).unwrap(); do_keygen::( comms.shared_channel(), participant_list, p, threshold, rng_p, ) }, |_| DKG_MAX_INCOMING_BUFFER_ENTRIES, ); } #[rstest] #[case(5, 2, 3)] #[case(10, 4, 5)] fn test_reshare_buffer_entries( #[case] num_participants: usize, #[case] old_threshold: usize, #[case] new_threshold: usize, ) { type C = frost_ed25519::Ed25519Sha512; // Given let participants = generate_participants(num_participants); let mut rng = MockCryptoRng::seed_from_u64(42); let keygen_result = run_keygen::(&participants, old_threshold, &mut rng); let old_public_key = keygen_result[0].1.public_key; let mut new_participants = participants.clone(); new_participants.push(Participant::from(31u32)); let (protocols, comms_refs) = build_buffer_test(&new_participants, &mut rng, |comms, _p_list, p, rng_p| { let old_signing_key = keygen_result .iter() .find(|(kp, _)| *kp == p) .map(|(_, ko)| ko.private_share); let (participant_list, old_participant_list) = assert_reshare_keys_invariants::( &new_participants, p, new_threshold, old_signing_key, old_threshold, &participants, ) .unwrap(); do_reshare::( comms.shared_channel(), participant_list, p, new_threshold, old_signing_key, old_public_key, old_participant_list, rng_p, ) }); // When + Then run_and_assert_buffer_entries(protocols, &comms_refs, |_| DKG_MAX_INCOMING_BUFFER_ENTRIES); } }