diff --git a/crates/threshold-signatures/src/frost.rs b/crates/threshold-signatures/src/frost.rs index 3a54d648e..0178f6951 100644 --- a/crates/threshold-signatures/src/frost.rs +++ b/crates/threshold-signatures/src/frost.rs @@ -1,210 +1,7 @@ -use frost_core::{ - Identifier, - keys::SigningShare, - round1::{SigningCommitments, SigningNonces, commit}, -}; -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use zeroize::ZeroizeOnDrop; - -use crate::{ - Ciphersuite, KeygenOutput, ReconstructionLowerBound, - errors::{InitializationError, ProtocolError}, - participants::{Participant, ParticipantList}, - protocol::{ - Protocol, - helpers::recv_from_others, - internal::{Comms, SharedChannel, make_protocol}, - }, -}; - pub mod eddsa; +mod presign; pub mod redjubjub; +mod sign_utils; -/// The necessary inputs for the creation of a presignature. -pub struct PresignArguments { - /// The output of key generation, i.e. our share of the secret key. - pub private_share: SigningShare, - /// The threshold for the scheme - pub threshold: ReconstructionLowerBound, -} - -/// The output of the presigning protocol. -/// -/// This output is basically all the parts of the signature that we can perform -/// without knowing the message. -#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, ZeroizeOnDrop)] -pub struct PresignOutput { - /// The secret signing nonces. - pub nonces: SigningNonces, - #[zeroize(skip)] - pub commitments_map: BTreeMap, SigningCommitments>, -} - -impl_secret_debug!({C: Ciphersuite} PresignOutput { show: [commitments_map], redact: [nonces] }); - -/// Maximum incoming buffer entries for the FROST presign protocol. -pub(crate) const FROST_PRESIGN_MAX_INCOMING_BUFFER_ENTRIES: usize = 1; - -/// Runs Presigning of either `EdDSA` or `RedDSA` -pub fn presign( - participants: &[Participant], - me: Participant, - args: &PresignArguments, - rng: R, -) -> Result> + use, InitializationError> -where - C: Ciphersuite, - R: CryptoRngCore + Send + 'static, -{ - if participants.len() < 2 { - return Err(InitializationError::NotEnoughParticipants { - participants: participants.len(), - }); - } - - let participants = - ParticipantList::new(participants).ok_or(InitializationError::DuplicateParticipants)?; - - if !participants.contains(me) { - return Err(InitializationError::MissingParticipant { - role: "self", - participant: me, - }); - } - - // validate threshold - if args.threshold.value() > participants.len() { - return Err(InitializationError::ThresholdTooLarge { - threshold: args.threshold.into(), - max: participants.len(), - }); - } - - let ctx = Comms::with_buffer_capacity(FROST_PRESIGN_MAX_INCOMING_BUFFER_ENTRIES); - let fut = do_presign( - ctx.shared_channel(), - participants, - me, - args.private_share, - rng, - ); - Ok(make_protocol(ctx, fut)) -} - -async fn do_presign( - mut chan: SharedChannel, - participants: ParticipantList, - me: Participant, - signing_share: SigningShare, - mut rng: impl CryptoRngCore, -) -> Result, ProtocolError> { - // --- Round 1 - // * Compute and Receive commitments. - let mut commitments_map: BTreeMap, SigningCommitments> = BTreeMap::new(); - - // Step 1.1 - // Creating two commitments and corresponding nonces - let (nonces, commitments) = commit(&signing_share, &mut rng); - commitments_map.insert(me.to_identifier()?, commitments); - - // Step 1.2 - let commit_waitpoint = chan.next_waitpoint(); - // Sending the commitments to all - chan.send_many(commit_waitpoint, &commitments)?; - - // Step 1.3 and 1.4 - // Collecting the commitments - for (from, commitment) in recv_from_others(&chan, commit_waitpoint, &participants, me).await? { - commitments_map.insert(from.to_identifier()?, commitment); - } - - Ok(PresignOutput { - nonces, - commitments_map, - }) -} - -/// Verifies that the sign inputs are valid -pub fn assert_sign_inputs( - participants: &[Participant], - threshold: impl Into, - me: Participant, - coordinator: Participant, -) -> Result { - let threshold = threshold.into(); - if participants.len() < 2 { - return Err(InitializationError::NotEnoughParticipants { - participants: participants.len(), - }); - } - let Some(participants) = ParticipantList::new(participants) else { - return Err(InitializationError::DuplicateParticipants); - }; - - // ensure my presence in the participant list - if !participants.contains(me) { - return Err(InitializationError::MissingParticipant { - role: "self", - participant: me, - }); - } - - // validate threshold - if threshold.value() > participants.len() { - return Err(InitializationError::ThresholdTooLarge { - threshold: threshold.value(), - max: participants.len(), - }); - } - - // ensure the coordinator is a participant - if !participants.contains(coordinator) { - return Err(InitializationError::MissingParticipant { - role: "coordinator", - participant: coordinator, - }); - } - Ok(participants) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::test_utils::{MockCryptoRng, assert_buffer_capacity, generate_participants}; - use frost_ed25519::Ed25519Sha512; - use rand::SeedableRng; - use rstest::rstest; - - #[rstest] - #[case(3, 2)] - #[case(5, 3)] - #[case(10, 4)] - fn test_presign_buffer_entries(#[case] num_participants: usize, #[case] threshold: usize) { - // Given - let participants = generate_participants(num_participants); - let mut rng = MockCryptoRng::seed_from_u64(42); - let keygen_result = crate::test_utils::run_keygen::( - &participants, - threshold, - &mut rng, - ); - - // When + Then - assert_buffer_capacity( - &participants, - &mut rng, - |comms, p_list, p, rng_p| { - let private_share = keygen_result - .iter() - .find(|(kp, _)| *kp == p) - .unwrap() - .1 - .private_share; - do_presign::(comms.shared_channel(), p_list, p, private_share, rng_p) - }, - |_| FROST_PRESIGN_MAX_INCOMING_BUFFER_ENTRIES, - ); - } -} +pub(crate) use presign::{PresignArguments, PresignOutput, presign}; +pub(crate) use sign_utils::assert_sign_inputs; diff --git a/crates/threshold-signatures/src/frost/eddsa.rs b/crates/threshold-signatures/src/frost/eddsa.rs index 480a412ec..c8e4c92a6 100644 --- a/crates/threshold-signatures/src/frost/eddsa.rs +++ b/crates/threshold-signatures/src/frost/eddsa.rs @@ -1,43 +1,9 @@ //! This module serves as a wrapper for Ed25519 scheme. +mod presign; pub mod sign; #[cfg(test)] mod test; -use crate::{ - Ciphersuite, - crypto::ciphersuite::{BytesOrder, ScalarSerializationFormat}, - errors::InitializationError, - participants::Participant, - protocol::Protocol, +pub use presign::{ + Ed25519Sha512, KeygenOutput, PresignArguments, PresignOutput, SignatureOption, presign, }; -use rand_core::CryptoRngCore; - -pub use frost_ed25519::Ed25519Sha512; - -impl ScalarSerializationFormat for Ed25519Sha512 { - fn bytes_order() -> BytesOrder { - BytesOrder::LittleEndian - } -} - -impl Ciphersuite for Ed25519Sha512 {} - -/// Signature would be Some for coordinator and None for other participants -pub type SignatureOption = Option; - -pub type KeygenOutput = super::KeygenOutput; -pub type PresignArguments = super::PresignArguments; -pub type PresignOutput = super::PresignOutput; - -/// Ed25519 presigning function -pub fn presign( - participants: &[Participant], - me: Participant, - args: &PresignArguments, - rng: R, -) -> Result + use, InitializationError> -where - R: CryptoRngCore + Send + 'static, -{ - super::presign(participants, me, args, rng) -} diff --git a/crates/threshold-signatures/src/frost/eddsa/presign.rs b/crates/threshold-signatures/src/frost/eddsa/presign.rs new file mode 100644 index 000000000..0555c387c --- /dev/null +++ b/crates/threshold-signatures/src/frost/eddsa/presign.rs @@ -0,0 +1,38 @@ +use crate::{ + Ciphersuite, + crypto::ciphersuite::{BytesOrder, ScalarSerializationFormat}, + errors::InitializationError, + participants::Participant, + protocol::Protocol, +}; +use rand_core::CryptoRngCore; + +pub use frost_ed25519::Ed25519Sha512; + +impl ScalarSerializationFormat for Ed25519Sha512 { + fn bytes_order() -> BytesOrder { + BytesOrder::LittleEndian + } +} + +impl Ciphersuite for Ed25519Sha512 {} + +/// Signature would be Some for coordinator and None for other participants +pub type SignatureOption = Option; + +pub type KeygenOutput = crate::KeygenOutput; +pub type PresignArguments = crate::frost::PresignArguments; +pub type PresignOutput = crate::frost::PresignOutput; + +/// Ed25519 presigning function +pub fn presign( + participants: &[Participant], + me: Participant, + args: &PresignArguments, + rng: R, +) -> Result + use, InitializationError> +where + R: CryptoRngCore + Send + 'static, +{ + crate::frost::presign(participants, me, args, rng) +} diff --git a/crates/threshold-signatures/src/frost/presign.rs b/crates/threshold-signatures/src/frost/presign.rs new file mode 100644 index 000000000..06c64702d --- /dev/null +++ b/crates/threshold-signatures/src/frost/presign.rs @@ -0,0 +1,143 @@ +use frost_core::{ + Identifier, + keys::SigningShare, + round1::{SigningCommitments, SigningNonces, commit}, +}; +use rand_core::CryptoRngCore; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use zeroize::ZeroizeOnDrop; + +use crate::{ + Ciphersuite, ReconstructionLowerBound, + errors::{InitializationError, ProtocolError}, + frost::sign_utils::assert_participant_inputs, + participants::{Participant, ParticipantList}, + protocol::{ + Protocol, + helpers::recv_from_others, + internal::{Comms, SharedChannel, make_protocol}, + }, +}; + +/// The necessary inputs for the creation of a presignature. +pub struct PresignArguments { + /// The output of key generation, i.e. our share of the secret key. + pub private_share: SigningShare, + /// The threshold for the scheme + pub threshold: ReconstructionLowerBound, +} + +/// The output of the presigning protocol. +/// +/// This output is basically all the parts of the signature that we can perform +/// without knowing the message. +#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, ZeroizeOnDrop)] +pub struct PresignOutput { + /// The secret signing nonces. + pub nonces: SigningNonces, + #[zeroize(skip)] + pub commitments_map: BTreeMap, SigningCommitments>, +} + +impl_secret_debug!({C: Ciphersuite} PresignOutput { show: [commitments_map], redact: [nonces] }); + +/// Maximum incoming buffer entries for the FROST presign protocol. +const FROST_PRESIGN_MAX_INCOMING_BUFFER_ENTRIES: usize = 1; + +/// Runs Presigning of either `EdDSA` or `RedDSA` +pub fn presign( + participants: &[Participant], + me: Participant, + args: &PresignArguments, + rng: R, +) -> Result> + use, InitializationError> +where + C: Ciphersuite, + R: CryptoRngCore + Send + 'static, +{ + let participants = assert_participant_inputs(participants, args.threshold, me)?; + + let ctx = Comms::with_buffer_capacity(FROST_PRESIGN_MAX_INCOMING_BUFFER_ENTRIES); + let fut = do_presign( + ctx.shared_channel(), + participants, + me, + args.private_share, + rng, + ); + Ok(make_protocol(ctx, fut)) +} + +async fn do_presign( + mut chan: SharedChannel, + participants: ParticipantList, + me: Participant, + signing_share: SigningShare, + mut rng: impl CryptoRngCore, +) -> Result, ProtocolError> { + // --- Round 1 + // * Compute and Receive commitments. + let mut commitments_map: BTreeMap, SigningCommitments> = BTreeMap::new(); + + // Step 1.1 + // Creating two commitments and corresponding nonces + let (nonces, commitments) = commit(&signing_share, &mut rng); + commitments_map.insert(me.to_identifier()?, commitments); + + // Step 1.2 + let commit_waitpoint = chan.next_waitpoint(); + // Sending the commitments to all + chan.send_many(commit_waitpoint, &commitments)?; + + // Step 1.3 and 1.4 + // Collecting the commitments + for (from, commitment) in recv_from_others(&chan, commit_waitpoint, &participants, me).await? { + commitments_map.insert(from.to_identifier()?, commitment); + } + + Ok(PresignOutput { + nonces, + commitments_map, + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test_utils::{MockCryptoRng, assert_buffer_capacity, generate_participants}; + use frost_ed25519::Ed25519Sha512; + use rand::SeedableRng; + use rstest::rstest; + + #[rstest] + #[case(3, 2)] + #[case(5, 3)] + #[case(10, 4)] + fn test_presign_buffer_entries(#[case] num_participants: usize, #[case] threshold: usize) { + // Given + let participants = generate_participants(num_participants); + let mut rng = MockCryptoRng::seed_from_u64(42); + let keygen_result = crate::test_utils::run_keygen::( + &participants, + threshold, + &mut rng, + ); + + // When + Then + assert_buffer_capacity( + &participants, + &mut rng, + |comms, p_list, p, rng_p| { + let private_share = keygen_result + .iter() + .find(|(kp, _)| *kp == p) + .unwrap() + .1 + .private_share; + do_presign::(comms.shared_channel(), p_list, p, private_share, rng_p) + }, + |_| FROST_PRESIGN_MAX_INCOMING_BUFFER_ENTRIES, + ); + } +} diff --git a/crates/threshold-signatures/src/frost/redjubjub.rs b/crates/threshold-signatures/src/frost/redjubjub.rs index d86650af1..7fa9e5303 100644 --- a/crates/threshold-signatures/src/frost/redjubjub.rs +++ b/crates/threshold-signatures/src/frost/redjubjub.rs @@ -2,47 +2,11 @@ //! //! Check or +mod presign; pub mod sign; #[cfg(test)] mod test; -use crate::{ - Ciphersuite, - crypto::ciphersuite::{BytesOrder, ScalarSerializationFormat}, - errors::InitializationError, - participants::Participant, - protocol::Protocol, +pub use presign::{ + JubjubBlake2b512, KeygenOutput, PresignArguments, PresignOutput, SignatureOption, presign, }; - -use rand_core::CryptoRngCore; -use reddsa::frost::redjubjub::Signature; - -// JubJub + Blake2b512 Ciphersuite -pub use reddsa::frost::redjubjub::JubjubBlake2b512; - -impl ScalarSerializationFormat for JubjubBlake2b512 { - fn bytes_order() -> BytesOrder { - BytesOrder::LittleEndian - } -} -impl Ciphersuite for JubjubBlake2b512 {} - -pub type KeygenOutput = super::KeygenOutput; -pub type PresignArguments = super::PresignArguments; -pub type PresignOutput = super::PresignOutput; - -/// Signature would be Some for coordinator and None for other participants -pub type SignatureOption = Option; - -/// `RedJubJub` presigning function -pub fn presign( - participants: &[Participant], - me: Participant, - args: &PresignArguments, - rng: R, -) -> Result + use, InitializationError> -where - R: CryptoRngCore + Send + 'static, -{ - super::presign(participants, me, args, rng) -} diff --git a/crates/threshold-signatures/src/frost/redjubjub/presign.rs b/crates/threshold-signatures/src/frost/redjubjub/presign.rs new file mode 100644 index 000000000..49a6ae6dd --- /dev/null +++ b/crates/threshold-signatures/src/frost/redjubjub/presign.rs @@ -0,0 +1,39 @@ +use crate::{ + Ciphersuite, + crypto::ciphersuite::{BytesOrder, ScalarSerializationFormat}, + errors::InitializationError, + participants::Participant, + protocol::Protocol, +}; +use rand_core::CryptoRngCore; +use reddsa::frost::redjubjub::Signature; + +// JubJub + Blake2b512 Ciphersuite +pub use reddsa::frost::redjubjub::JubjubBlake2b512; + +impl ScalarSerializationFormat for JubjubBlake2b512 { + fn bytes_order() -> BytesOrder { + BytesOrder::LittleEndian + } +} +impl Ciphersuite for JubjubBlake2b512 {} + +pub type KeygenOutput = crate::KeygenOutput; +pub type PresignArguments = crate::frost::PresignArguments; +pub type PresignOutput = crate::frost::PresignOutput; + +/// Signature would be Some for coordinator and None for other participants +pub type SignatureOption = Option; + +/// `RedJubJub` presigning function +pub fn presign( + participants: &[Participant], + me: Participant, + args: &PresignArguments, + rng: R, +) -> Result + use, InitializationError> +where + R: CryptoRngCore + Send + 'static, +{ + crate::frost::presign(participants, me, args, rng) +} diff --git a/crates/threshold-signatures/src/frost/sign_utils.rs b/crates/threshold-signatures/src/frost/sign_utils.rs new file mode 100644 index 000000000..675a87d35 --- /dev/null +++ b/crates/threshold-signatures/src/frost/sign_utils.rs @@ -0,0 +1,63 @@ +use crate::{ + ReconstructionLowerBound, + errors::InitializationError, + participants::{Participant, ParticipantList}, +}; + +/// Verifies the participant-set and threshold inputs shared by every FROST protocol. +/// +/// Checks that there are at least two participants, that the list has no duplicates, that `me` +/// is among them, and that the threshold does not exceed the participant count. Returns the +/// validated [`ParticipantList`] so callers can layer on any protocol-specific checks. +pub fn assert_participant_inputs( + participants: &[Participant], + threshold: impl Into, + me: Participant, +) -> Result { + let threshold = threshold.into(); + if participants.len() < 2 { + return Err(InitializationError::NotEnoughParticipants { + participants: participants.len(), + }); + } + let Some(participants) = ParticipantList::new(participants) else { + return Err(InitializationError::DuplicateParticipants); + }; + + // ensure my presence in the participant list + if !participants.contains(me) { + return Err(InitializationError::MissingParticipant { + role: "self", + participant: me, + }); + } + + // validate threshold + if threshold.value() > participants.len() { + return Err(InitializationError::ThresholdTooLarge { + threshold: threshold.value(), + max: participants.len(), + }); + } + + Ok(participants) +} + +/// Verifies that the sign inputs are valid +pub fn assert_sign_inputs( + participants: &[Participant], + threshold: impl Into, + me: Participant, + coordinator: Participant, +) -> Result { + let participants = assert_participant_inputs(participants, threshold, me)?; + + // ensure the coordinator is a participant + if !participants.contains(coordinator) { + return Err(InitializationError::MissingParticipant { + role: "coordinator", + participant: coordinator, + }); + } + Ok(participants) +}