//! This module wraps a signature generation functionality from `Frost` library //! into `cait-sith::Protocol` representation. use super::{KeygenOutput, PresignOutput, SignatureOption}; use crate::{ Participant, ParticipantList, ReconstructionThreshold, errors::{InitializationError, ProtocolError}, frost::assert_sign_inputs, protocol::{ Protocol, helpers::recv_from_others, internal::{Comms, SharedChannel, make_protocol}, }, }; use frost_ed25519::{ CheaterDetection, SigningPackage, VerifyingKey, aggregate_custom, keys::{KeyPackage, PublicKeyPackage, SigningShare}, rand_core, round1, round2, }; use rand_core::CryptoRngCore; use std::collections::BTreeMap; use zeroize::Zeroizing; // for backwards compatibility pub use sign_v1 as sign; /// Maximum incoming buffer entries for the coordinator in the `EdDSA` sign v1 protocol. pub(crate) const EDDSA_SIGN_V1_MAX_INCOMING_COORDINATOR_ENTRIES: usize = 2; /// Maximum incoming buffer entries for non-coordinator participants in the `EdDSA` sign v1 protocol. #[cfg(test)] pub(crate) const EDDSA_SIGN_V1_MAX_INCOMING_PARTICIPANT_ENTRIES: usize = 1; /// Maximum incoming buffer entries for the coordinator in the `EdDSA` sign v2 protocol. pub(crate) const EDDSA_SIGN_V2_MAX_INCOMING_COORDINATOR_ENTRIES: usize = 1; /// Maximum incoming buffer entries for non-coordinator participants in the `EdDSA` sign v2 protocol. #[cfg(test)] pub(crate) const EDDSA_SIGN_V2_MAX_INCOMING_PARTICIPANT_ENTRIES: usize = 0; /// Depending on whether the current participant is a coordinator or not, /// runs the signature protocol as either a participant or a coordinator. /// /// WARNING: Extracted from FROST documentation: /// In all of the main FROST ciphersuites, the entire message must be sent /// to participants. In some cases, where the message is too big, it may be /// necessary to send a hash of the message instead. We strongly suggest /// creating a specific ciphersuite for this, and not just sending the hash /// as if it were the message. /// For reference, see how RFC 8032 handles "pre-hashing". pub fn sign_v1( participants: &[Participant], threshold: T, me: Participant, coordinator: Participant, keygen_output: KeygenOutput, message: Vec, rng: R, ) -> Result + use, InitializationError> where T: Into, R: CryptoRngCore + Send + 'static, { let threshold = threshold.into(); let participants = assert_sign_inputs(participants, threshold, me, coordinator)?; let comms = Comms::with_buffer_capacity(EDDSA_SIGN_V1_MAX_INCOMING_COORDINATOR_ENTRIES); let chan = comms.shared_channel(); let fut = fut_wrapper_v1( chan, participants, threshold, me, coordinator, keygen_output, message, rng, ); Ok(make_protocol(comms, fut)) } pub fn sign_v2( participants: &[Participant], threshold: T, me: Participant, coordinator: Participant, keygen_output: KeygenOutput, presignature: PresignOutput, message: Vec, ) -> Result + use, InitializationError> where T: Into + Copy, { let participants = assert_sign_inputs(participants, threshold, me, coordinator)?; let comms = Comms::with_buffer_capacity(EDDSA_SIGN_V2_MAX_INCOMING_COORDINATOR_ENTRIES); let chan = comms.shared_channel(); let fut = fut_wrapper_v2( chan, participants, threshold.into(), me, coordinator, keygen_output, presignature, message, ); Ok(make_protocol(comms, fut)) } /// Returns a future that executes signature protocol for *the Coordinator*. /// /// WARNING: Extracted from FROST documentation: /// In all of the main FROST ciphersuites, the entire message must be sent /// to participants. In some cases, where the message is too big, it may be /// necessary to send a hash of the message instead. We strongly suggest /// creating a specific ciphersuite for this, and not just sending the hash /// as if it were the message. /// For reference, see how RFC 8032 handles "pre-hashing". async fn do_sign_coordinator_v1( mut chan: SharedChannel, participants: ParticipantList, threshold: ReconstructionThreshold, me: Participant, keygen_output: KeygenOutput, message: Vec, rng: &mut impl CryptoRngCore, ) -> Result { // --- Round 1. // * Compute and (implicitly) send commitments. let mut commitments_map: BTreeMap = BTreeMap::new(); // signing share is the private_share let signing_share = keygen_output.private_share; // Step 1.1 (and implicitly 1.2) let (nonces, commitments) = round1::commit(&signing_share, rng); let nonces = Zeroizing::new(nonces); commitments_map.insert(me.to_identifier()?, commitments); // --- Round 2 // * Receive others' commitments, then send the signing package. let commit_waitpoint = chan.next_waitpoint(); // Step 2.1 and 2.2 for (from, commitment) in recv_from_others(&chan, commit_waitpoint, &participants, me).await? { // Step 2.2 commitments_map.insert(from.to_identifier()?, commitment); } let signing_package = frost_ed25519::SigningPackage::new(commitments_map, message.as_slice()); let mut signature_shares: BTreeMap = BTreeMap::new(); // Step 2.3 let r2_wait_point = chan.next_waitpoint(); chan.send_many(r2_wait_point, &signing_package)?; // --- Round 3 // * Wait for each other's signature share // Step 3.3 (3.1 and 3.2 are no-ops for the coordinator since it created the signing package) let vk_package = keygen_output.public_key; let key_package = construct_key_package(threshold, me, signing_share, &vk_package)?; let key_package = Zeroizing::new(key_package); let signature_share = round2::sign(&signing_package, &nonces, &key_package) .map_err(|e| ProtocolError::AssertionFailed(e.to_string()))?; // Step 3.5 (3.4 is a no-op for the coordinator since it doesn't send its own share to itself) signature_shares.insert(me.to_identifier()?, signature_share); for (from, signature_share) in recv_from_others(&chan, r2_wait_point, &participants, me).await? { signature_shares.insert(from.to_identifier()?, signature_share); } // --- Signature aggregation. // * Converted collected signature shares into the signature. // * Signature is verified internally during `aggregate()` call. // Step 3.6 and 3.7 // We supply empty map as `verifying_shares` because we have disabled "cheater-detection" feature flag. // Feature "cheater-detection" only points to a malicious participant, if there's such. // It doesn't bring any additional guarantees. let public_key_package = PublicKeyPackage::new(BTreeMap::new(), vk_package, None); let signature = aggregate_custom( &signing_package, &signature_shares, &public_key_package, CheaterDetection::Disabled, ) .map_err(|e| ProtocolError::AssertionFailed(e.to_string()))?; Ok(Some(signature)) } /// Returns a future that executes signature protocol for *the Coordinator*. /// /// WARNING: Extracted from FROST documentation: /// In all of the main FROST ciphersuites, the entire message must be sent /// to participants. In some cases, where the message is too big, it may be /// necessary to send a hash of the message instead. We strongly suggest /// creating a specific ciphersuite for this, and not just sending the hash /// as if it were the message. /// For reference, see how RFC 8032 handles "pre-hashing". async fn do_sign_coordinator_v2( mut chan: SharedChannel, participants: ParticipantList, threshold: ReconstructionThreshold, me: Participant, keygen_output: KeygenOutput, presignature: &PresignOutput, message: Vec, ) -> Result { // --- Round 1 // * Compute my signature share and receive other's shares. // * Output the signature. let signing_package = frost_ed25519::SigningPackage::new( presignature.commitments_map.clone(), message.as_slice(), ); let mut signature_shares: BTreeMap = BTreeMap::new(); let vk_package = keygen_output.public_key; let key_package = construct_key_package(threshold, me, keygen_output.private_share, &vk_package)?; let key_package = Zeroizing::new(key_package); // Step 1.1 let signature_share = round2::sign(&signing_package, &presignature.nonces, &key_package) .map_err(|e| ProtocolError::AssertionFailed(e.to_string()))?; signature_shares.insert(me.to_identifier()?, signature_share); // Step 1.3 (step 1.2 is performed by the other participants) let sign_waitpoint = chan.next_waitpoint(); for (from, signature_share) in recv_from_others(&chan, sign_waitpoint, &participants, me).await? { signature_shares.insert(from.to_identifier()?, signature_share); } // --- Signature aggregation. // * Converted collected signature shares into the signature. // * Signature is verified internally during `aggregate()` call. // We supply empty map as `verifying_shares` because we have disabled "cheater-detection" feature flag. // Feature "cheater-detection" only points to a malicious participant, if there's such. // It doesn't bring any additional guarantees. let public_key_package = PublicKeyPackage::new(BTreeMap::new(), vk_package, None); // Step 1.4 and 1.5 let signature = aggregate_custom( &signing_package, &signature_shares, &public_key_package, CheaterDetection::Disabled, ) .map_err(|e| ProtocolError::AssertionFailed(e.to_string()))?; Ok(Some(signature)) } /// Returns a future that executes signature protocol for *a Participant*. /// /// WARNING: Extracted from FROST documentation: /// In all of the main FROST ciphersuites, the entire message must be sent /// to participants. In some cases, where the message is too big, it may be /// necessary to send a hash of the message instead. We strongly suggest /// creating a specific ciphersuite for this, and not just sending the hash /// as if it were the message. /// For reference, see how RFC 8032 handles "pre-hashing". async fn do_sign_participant_v1( mut chan: SharedChannel, threshold: ReconstructionThreshold, me: Participant, coordinator: Participant, keygen_output: KeygenOutput, message: Vec, rng: &mut impl CryptoRngCore, ) -> Result { // --- Round 1. // * Compute and send commitments. if coordinator == me { return Err(ProtocolError::AssertionFailed( "the do_sign_participant function cannot be called for a coordinator" .to_string(), )); } // signing share is the private_share let signing_share = keygen_output.private_share; // Step 1.1 let (nonces, commitments) = round1::commit(&signing_share, rng); // Ensures the values are zeroized on drop let nonces = Zeroizing::new(nonces); // * Wait for an initial message from a coordinator. // * Send coordinator our commitment. // Step 1.2 let commit_waitpoint = chan.next_waitpoint(); chan.send_private(commit_waitpoint, coordinator, &commitments)?; // --- Round 3. // * Wait for a signing package. // * Send our signature share. // Step 3.1 let r2_wait_point = chan.next_waitpoint(); let signing_package = loop { let (from, signing_package): (_, frost_ed25519::SigningPackage) = chan.recv(r2_wait_point).await?; if from != coordinator { continue; } break signing_package; }; // Step 3.2 if signing_package.message() != message.as_slice() { return Err(ProtocolError::AssertionFailed( "Expected message doesn't match with the actual message received in a signing package" .to_string(), )); } // Step 3.3 let vk_package = keygen_output.public_key; let key_package = construct_key_package(threshold, me, signing_share, &vk_package)?; // Ensures the values are zeroized on drop let key_package = Zeroizing::new(key_package); let signature_share = round2::sign(&signing_package, &nonces, &key_package) .map_err(|e| ProtocolError::AssertionFailed(e.to_string()))?; // Step 3.4 chan.send_private(r2_wait_point, coordinator, &signature_share)?; Ok(None) } /// Returns a future that executes signature protocol for *a Participant*. /// /// WARNING: Extracted from FROST documentation: /// In all of the main FROST ciphersuites, the entire message must be sent /// to participants. In some cases, where the message is too big, it may be /// necessary to send a hash of the message instead. We strongly suggest /// creating a specific ciphersuite for this, and not just sending the hash /// as if it were the message. /// For reference, see how RFC 8032 handles "pre-hashing". fn do_sign_participant_v2( mut chan: SharedChannel, threshold: ReconstructionThreshold, me: Participant, coordinator: Participant, keygen_output: &KeygenOutput, presignature: &PresignOutput, message: &[u8], ) -> Result { // --- Round 1 // * Compute signature share. // * Send signature share to coordinator. if coordinator == me { return Err(ProtocolError::AssertionFailed( "the do_sign_participant function cannot be called for a coordinator" .to_string(), )); } let key_package = construct_key_package( threshold, me, keygen_output.private_share, &keygen_output.public_key, )?; // Ensures the values are zeroized on drop let key_package = Zeroizing::new(key_package); // Step 1.1 let signing_package = SigningPackage::new(presignature.commitments_map.clone(), message); let signature_share = round2::sign(&signing_package, &presignature.nonces, &key_package) .map_err(|e| ProtocolError::AssertionFailed(e.to_string()))?; // Step 1.2 let sign_waitpoint = chan.next_waitpoint(); chan.send_private(sign_waitpoint, coordinator, &signature_share)?; Ok(None) } /// A function that takes a signing share and a keygenOutput /// and construct a public key package used for frost signing fn construct_key_package( threshold: ReconstructionThreshold, me: Participant, signing_share: SigningShare, verifying_key: &VerifyingKey, ) -> Result { let identifier = me.to_identifier()?; let verifying_share = signing_share.into(); Ok(KeyPackage::new( identifier, signing_share, verifying_share, *verifying_key, u16::try_from(threshold.value()).map_err(|_| { ProtocolError::Other("threshold cannot be converted to u16".to_string()) })?, )) } #[allow(clippy::too_many_arguments)] async fn fut_wrapper_v1( chan: SharedChannel, participants: ParticipantList, threshold: ReconstructionThreshold, me: Participant, coordinator: Participant, keygen_output: KeygenOutput, message: Vec, mut rng: impl CryptoRngCore, ) -> Result { if me == coordinator { do_sign_coordinator_v1( chan, participants, threshold, me, keygen_output, message, &mut rng, ) .await } else { do_sign_participant_v1( chan, threshold, me, coordinator, keygen_output, message, &mut rng, ) .await } } #[allow(clippy::too_many_arguments)] async fn fut_wrapper_v2( chan: SharedChannel, participants: ParticipantList, threshold: ReconstructionThreshold, me: Participant, coordinator: Participant, keygen_output: KeygenOutput, presignature: PresignOutput, message: Vec, ) -> Result { if me == coordinator { do_sign_coordinator_v2( chan, participants, threshold, me, keygen_output, &presignature, message, ) .await } else { do_sign_participant_v2( chan, threshold, me, coordinator, &keygen_output, &presignature, &message, ) } } #[cfg(test)] mod test { use super::{ EDDSA_SIGN_V1_MAX_INCOMING_COORDINATOR_ENTRIES, EDDSA_SIGN_V1_MAX_INCOMING_PARTICIPANT_ENTRIES, EDDSA_SIGN_V2_MAX_INCOMING_COORDINATOR_ENTRIES, EDDSA_SIGN_V2_MAX_INCOMING_PARTICIPANT_ENTRIES, fut_wrapper_v1, fut_wrapper_v2, }; use crate::test_utils::{ MockCryptoRng, assert_buffer_capacity, assert_public_key_invariant, build_frost_key_packages_with_dealer, check_one_coordinator_output, expected_buffer_by_role, generate_participants, generate_participants_with_random_ids, run_keygen, run_refresh, run_reshare, }; use crate::{ Protocol, crypto::hash::hash, frost::eddsa::{ SignatureOption, sign::{sign_v1, sign_v2}, test::{run_presign, run_sign_v1, run_sign_v2}, }, participants::{Participant, ParticipantList}, }; use frost_core::{Field, Group, Scalar}; use frost_ed25519::{Ed25519Group, Ed25519ScalarField, Ed25519Sha512, VerifyingKey}; use rand::seq::SliceRandom as _; use rand::{RngCore, SeedableRng}; use rstest::rstest; #[test] fn stress_v1() { let mut rng = MockCryptoRng::seed_from_u64(42); let max_signers = 7; let msg = "hello_near"; let msg_hash = hash(&msg).unwrap(); for min_signers in 2..max_signers { for actual_signers in min_signers..=max_signers { let key_packages = build_frost_key_packages_with_dealer(max_signers, min_signers, &mut rng); let coordinator = key_packages[0].0; let min_signers: usize = min_signers.into(); let data = run_sign_v1( &key_packages, actual_signers.into(), coordinator, min_signers, msg_hash, &mut rng, ) .unwrap(); check_one_coordinator_output(data, coordinator).unwrap(); } } } #[test] fn stress_v2() { let mut rng = MockCryptoRng::seed_from_u64(42); let max_signers = 7; let msg = "hello_near"; let msg_hash = hash(&msg).unwrap(); for min_signers in 2..max_signers { for actual_signers in min_signers..=max_signers { let key_packages = build_frost_key_packages_with_dealer(max_signers, min_signers, &mut rng); let coordinator = key_packages[0].0; let min_signers: usize = min_signers.into(); let data = run_sign_v2( &key_packages, actual_signers.into(), coordinator, min_signers, msg_hash, MockCryptoRng::seed_from_u64(rng.next_u64()), ) .unwrap(); check_one_coordinator_output(data, coordinator).unwrap(); } } } #[test] fn test_sign_v1_correctness() { let mut rng = MockCryptoRng::seed_from_u64(42); let threshold = 6; let keys = build_frost_key_packages_with_dealer(11, threshold, &mut rng); let public_key = keys[0].1.public_key.to_element(); let msg = b"hello world with near".to_vec(); let coordinator = keys.choose(&mut rng).expect("keys list is not empty").0; let participants_sign_builder = keys .iter() .map(|(p, keygen_output)| { let rng_p = MockCryptoRng::seed_from_u64(rng.next_u64()); (*p, (keygen_output.clone(), rng_p)) }) .collect(); // This checks the output signature validity internally let result = crate::test_utils::run_sign::( participants_sign_builder, coordinator, public_key, Ed25519ScalarField::zero(), |participants, coordinator, me, _, (keygen_output, p_rng), _| { sign_v1( participants, threshold as usize, me, coordinator, keygen_output, msg.clone(), p_rng, ) .map(|sig| Box::new(sig) as Box>) }, ) .unwrap(); let signature = check_one_coordinator_output(result, coordinator).unwrap(); insta::assert_json_snapshot!(signature); } #[test] fn test_sign_v2_correctness() { let mut rng = MockCryptoRng::seed_from_u64(42); let actual_signers = 11; let threshold = 6; let keys = build_frost_key_packages_with_dealer(actual_signers, threshold, &mut rng); let public_key = keys[0].1.public_key.to_element(); let threshold: usize = threshold.into(); let presig = run_presign(&keys, threshold, actual_signers.into(), rng).unwrap(); let msg = b"hello world with near".to_vec(); let mut rng = MockCryptoRng::seed_from_u64(40); let coordinator = keys.choose(&mut rng).expect("participant is existent").0; // This checks the output signature validity internally let result = crate::test_utils::run_sign::( keys, coordinator, public_key, Ed25519ScalarField::zero(), |participants, coordinator, me, _, keygen_output, _| { let presign_output = presig .iter() .find(|(p, _)| p == &me) .map(|(_, output)| output) .unwrap(); sign_v2( participants, threshold, me, coordinator, keygen_output, presign_output.clone(), msg.clone(), ) .map(|sig| Box::new(sig) as Box>) }, ) .unwrap(); let signature = check_one_coordinator_output(result, coordinator).unwrap(); insta::assert_json_snapshot!(signature); } #[test] fn dkg_refresh_sign_v1_test() { let mut rng = MockCryptoRng::seed_from_u64(42); let participants = generate_participants_with_random_ids(4, &mut rng); let actual_signers = participants.len(); let threshold = 2; let mut key_packages = run_keygen(&participants, threshold, &mut rng); for i in 0..3 { let msg = format!("hello_near_{i}"); let msg_hash = hash(&msg).unwrap(); assert_public_key_invariant(&key_packages); let coordinator = participants[0]; // This internally verifies with the public key let data = run_sign_v1( &key_packages, actual_signers, coordinator, threshold, msg_hash, &mut rng, ) .unwrap(); let signature = check_one_coordinator_output(data, coordinator).unwrap(); // externally verify with the signature assert!( key_packages[0] .1 .public_key .verify(msg_hash.as_ref(), &signature) .is_ok() ); // test refresh key_packages = run_refresh(&participants, &key_packages, threshold, &mut rng); } } #[test] fn dkg_refresh_sign_v2_test() { let mut rng = MockCryptoRng::seed_from_u64(42); let participants = generate_participants_with_random_ids(4, &mut rng); let actual_signers = participants.len(); let threshold = 2; let mut key_packages = run_keygen(&participants, threshold, &mut rng); for i in 0..3 { let msg = format!("hello_near_{i}"); let msg_hash = hash(&msg).unwrap(); assert_public_key_invariant(&key_packages); let coordinator = participants[0]; // This internally verifies with the public key let data = run_sign_v2( &key_packages, actual_signers, coordinator, threshold, msg_hash, MockCryptoRng::seed_from_u64(rng.next_u64()), ) .unwrap(); let signature = check_one_coordinator_output(data, coordinator).unwrap(); // externally verify with the signature assert!( key_packages[0] .1 .public_key .verify(msg_hash.as_ref(), &signature) .is_ok() ); // test refresh key_packages = run_refresh(&participants, &key_packages, threshold, &mut rng); } } fn test_public_key( participants: &[Participant], pub_key: VerifyingKey, shares: &[Scalar], ) { let p_list = ParticipantList::new(participants).unwrap(); let mut x = Ed25519ScalarField::zero(); for (p, share) in participants.iter().zip(shares.iter()) { x += p_list.lagrange::(*p).unwrap() * share; } assert_eq!(::generator() * x, pub_key.to_element()); } #[test] fn test_reshare_sign_v1_more_participants() { let mut rng = MockCryptoRng::seed_from_u64(42); let mut participants = generate_participants(4); let mut threshold = 3; let mut new_participants = participants.clone(); let mut key_packages = run_keygen(&participants, threshold, &mut rng); let pub_key = key_packages[2].1.public_key; // test dkg for i in 0..3 { let msg = format!("hello_near_{i}"); let msg_hash = hash(&msg).unwrap(); assert_public_key_invariant(&key_packages); let coordinator = participants[0]; // This internally verifies with the rerandomized public key let data = run_sign_v1( &key_packages, participants.len(), coordinator, threshold, msg_hash, &mut rng, ) .unwrap(); let signature = check_one_coordinator_output(data, coordinator).unwrap(); // externally verify with the signature assert!( key_packages[0] .1 .public_key .verify(msg_hash.as_ref(), &signature) .is_ok() ); // test refresh new_participants.push(Participant::from(20u32 + i)); let new_threshold = threshold + 1; key_packages = run_reshare( &participants, &pub_key, &key_packages, threshold, new_threshold, &new_participants, &mut rng, ); let shares: Vec<_> = key_packages .iter() .map(|(_, keygen)| keygen.private_share.to_scalar()) .collect(); // update the old parameters threshold = new_threshold; participants = new_participants.clone(); // Test public key test_public_key(&participants, pub_key, &shares); } } #[test] fn test_reshare_sign_v2_more_participants() { let mut rng = MockCryptoRng::seed_from_u64(42); let mut participants = generate_participants(4); let mut threshold = 3; let mut new_participants = participants.clone(); let mut key_packages = run_keygen(&participants, threshold, &mut rng); let pub_key = key_packages[2].1.public_key; // test dkg for i in 0..3 { let msg = format!("hello_near_{i}"); let msg_hash = hash(&msg).unwrap(); assert_public_key_invariant(&key_packages); let coordinator = participants[0]; // This internally verifies with the rerandomized public key let data = run_sign_v2( &key_packages, participants.len(), coordinator, threshold, msg_hash, MockCryptoRng::seed_from_u64(rng.next_u64()), ) .unwrap(); let signature = check_one_coordinator_output(data, coordinator).unwrap(); // externally verify with the signature assert!( key_packages[0] .1 .public_key .verify(msg_hash.as_ref(), &signature) .is_ok() ); // test refresh new_participants.push(Participant::from(20u32 + i)); let new_threshold = threshold + 1; key_packages = run_reshare( &participants, &pub_key, &key_packages, threshold, new_threshold, &new_participants, &mut rng, ); let shares: Vec<_> = key_packages .iter() .map(|(_, keygen)| keygen.private_share.to_scalar()) .collect(); // update the old parameters threshold = new_threshold; participants = new_participants.clone(); // Test public key test_public_key(&participants, pub_key, &shares); } } #[test] fn test_reshare_sign_v1_less_participants() { let mut rng = MockCryptoRng::seed_from_u64(42); let mut participants = generate_participants(6); let mut threshold = 5; let mut new_participants = participants.clone(); let mut key_packages = run_keygen(&participants, threshold, &mut rng); let pub_key = key_packages[2].1.public_key; // test dkg for i in 0..3 { let msg = format!("hello_near_{i}"); let msg_hash = hash(&msg).unwrap(); assert_public_key_invariant(&key_packages); let coordinator = participants[0]; // This internally verifies with the rerandomized public key // This internally verifies with the public key let data = run_sign_v1( &key_packages, participants.len(), coordinator, threshold, msg_hash, &mut rng, ) .unwrap(); let signature = check_one_coordinator_output(data, coordinator).unwrap(); // externally verify with the signature assert!( key_packages[0] .1 .public_key .verify(msg_hash.as_ref(), &signature) .is_ok() ); // test refresh new_participants.pop(); let new_threshold = threshold - 1; key_packages = run_reshare( &participants, &pub_key, &key_packages, threshold, new_threshold, &new_participants, &mut rng, ); let shares: Vec<_> = key_packages .iter() .map(|(_, keygen)| keygen.private_share.to_scalar()) .collect(); // update the old parameters threshold = new_threshold; participants = new_participants.clone(); // Test public key test_public_key(&participants, pub_key, &shares); } } #[rstest] #[case(3, 2)] #[case(5, 3)] #[case(10, 4)] fn test_sign_v1_buffer_entries(#[case] num_participants: u16, #[case] threshold: u16) { // Given let mut rng = MockCryptoRng::seed_from_u64(42); let keys = build_frost_key_packages_with_dealer(num_participants, threshold, &mut rng); let coordinator = keys[0].0; let message = b"test message for buffer entries".to_vec(); let participants: Vec<_> = keys.iter().map(|(p, _)| *p).collect(); // When + Then assert_buffer_capacity( &participants, &mut rng, |comms, p_list, p, rng_p| { let keygen_output = keys.iter().find(|(kp, _)| *kp == p).unwrap().1.clone(); fut_wrapper_v1( comms.shared_channel(), p_list, (threshold as usize).into(), p, coordinator, keygen_output, message.clone(), rng_p, ) }, expected_buffer_by_role( coordinator, EDDSA_SIGN_V1_MAX_INCOMING_COORDINATOR_ENTRIES, EDDSA_SIGN_V1_MAX_INCOMING_PARTICIPANT_ENTRIES, ), ); } #[rstest] #[case(3, 2)] #[case(5, 3)] #[case(10, 4)] fn test_sign_v2_buffer_entries(#[case] num_participants: usize, #[case] threshold: usize) { // Given let mut rng = MockCryptoRng::seed_from_u64(42); let keys = build_frost_key_packages_with_dealer( num_participants.try_into().unwrap(), threshold.try_into().unwrap(), &mut rng, ); let coordinator = keys[0].0; let message = b"test message for buffer entries".to_vec(); let presig = run_presign( &keys, threshold, num_participants, MockCryptoRng::seed_from_u64(rng.next_u64()), ) .unwrap(); let participants: Vec<_> = keys.iter().map(|(p, _)| *p).collect(); // When + Then assert_buffer_capacity( &participants, &mut rng, |comms, p_list, p, _rng_p| { let keygen_output = keys.iter().find(|(kp, _)| *kp == p).unwrap().1.clone(); let presign_output = presig .iter() .find(|(pp, _)| *pp == p) .map(|(_, output)| output.clone()) .unwrap(); fut_wrapper_v2( comms.shared_channel(), p_list, threshold.into(), p, coordinator, keygen_output, presign_output, message.clone(), ) }, expected_buffer_by_role( coordinator, EDDSA_SIGN_V2_MAX_INCOMING_COORDINATOR_ENTRIES, EDDSA_SIGN_V2_MAX_INCOMING_PARTICIPANT_ENTRIES, ), ); } #[test] fn test_reshare_sign_v2_less_participants() { let mut rng = MockCryptoRng::seed_from_u64(42); let mut participants = generate_participants(6); let mut threshold = 5; let mut new_participants = participants.clone(); let mut key_packages = run_keygen(&participants, threshold, &mut rng); let pub_key = key_packages[2].1.public_key; // test dkg for i in 0..3 { let msg = format!("hello_near_{i}"); let msg_hash = hash(&msg).unwrap(); assert_public_key_invariant(&key_packages); let coordinator = participants[0]; // This internally verifies with the rerandomized public key // This internally verifies with the public key let data = run_sign_v2( &key_packages, participants.len(), coordinator, threshold, msg_hash, MockCryptoRng::seed_from_u64(rng.next_u64()), ) .unwrap(); let signature = check_one_coordinator_output(data, coordinator).unwrap(); // externally verify with the signature assert!( key_packages[0] .1 .public_key .verify(msg_hash.as_ref(), &signature) .is_ok() ); // test refresh new_participants.pop(); let new_threshold = threshold - 1; key_packages = run_reshare( &participants, &pub_key, &key_packages, threshold, new_threshold, &new_participants, &mut rng, ); let shares: Vec<_> = key_packages .iter() .map(|(_, keygen)| keygen.private_share.to_scalar()) .collect(); // update the old parameters threshold = new_threshold; participants = new_participants.clone(); // Test public key test_public_key(&participants, pub_key, &shares); } } }