diff --git a/Cargo.toml b/Cargo.toml index c409aad9..75bebf82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,12 @@ funty = "=1.1.0" [dev-dependencies] bincode = "1" criterion = "0.3" +proptest-derive = "0.3" lazy_static = "1.4" proptest = "1.0" rand = "0.8" rand_chacha = "0.3" +serde_json = "1.0" [features] nightly = [] diff --git a/src/frost.rs b/src/frost.rs index f45f622c..ae3481fa 100644 --- a/src/frost.rs +++ b/src/frost.rs @@ -33,8 +33,8 @@ use crate::private::Sealed; use crate::{HStar, Signature, SpendAuth, VerificationKey}; /// A secret scalar value representing a single signer's secret key. -#[derive(Clone, Copy, Default)] -pub struct Secret(Scalar); +#[derive(Clone, Copy, Default, PartialEq)] +pub struct Secret(pub(crate) Scalar); // Zeroizes `Secret` to be the `Default` value on drop (when it goes out of // scope). Luckily the derived `Default` includes the `Default` impl of @@ -63,8 +63,10 @@ impl From for Public { #[derive(Clone)] pub struct Share { receiver_index: u64, - value: Secret, - commitment: ShareCommitment, + /// Secret Key. + pub(crate) value: Secret, + /// The commitments to be distributed among signers. + pub(crate) commitment: ShareCommitment, } /// A Jubjub point that is a commitment to one coefficient of our secret @@ -72,8 +74,8 @@ pub struct Share { /// /// This is a (public) commitment to one coefficient of a secret polynomial used /// for performing verifiable secret sharing for a Shamir secret share. -#[derive(Clone)] -struct Commitment(jubjub::AffinePoint); +#[derive(Clone, PartialEq)] +pub(crate) struct Commitment(pub(crate) jubjub::AffinePoint); /// Contains the commitments to the coefficients for our secret polynomial _f_, /// used to generate participants' key shares. @@ -88,11 +90,12 @@ struct Commitment(jubjub::AffinePoint); /// some agreed-upon public location for publication, where each participant can /// ensure that they received the correct (and same) value. #[derive(Clone)] -pub struct ShareCommitment(Vec); +pub struct ShareCommitment(pub(crate) Vec); /// The product of all signers' individual commitments, published as part of the /// final signature. -pub struct GroupCommitment(jubjub::AffinePoint); +#[derive(PartialEq)] +pub struct GroupCommitment(pub(crate) jubjub::AffinePoint); /// Secret and public key material generated by a dealer performing /// [`keygen_with_dealer`]. @@ -363,9 +366,12 @@ impl SigningNonces { /// SigningCommitment can be used for exactly *one* signature. #[derive(Copy, Clone)] pub struct SigningCommitments { - index: u64, - hiding: jubjub::ExtendedPoint, - binding: jubjub::ExtendedPoint, + /// The participant index + pub(crate) index: u64, + /// The hiding point. + pub(crate) hiding: jubjub::ExtendedPoint, + /// The binding point. + pub(crate) binding: jubjub::ExtendedPoint, } impl From<(u64, &SigningNonces)> for SigningCommitments { @@ -388,12 +394,12 @@ pub struct SigningPackage { /// Message which each participant will sign. /// /// Each signer should perform protocol-specific verification on the message. - pub message: &'static [u8], + pub message: Vec, } /// A representation of a single signature used in FROST structures and messages. -#[derive(Clone, Copy, Default)] -pub struct SignatureResponse(Scalar); +#[derive(Clone, Copy, Default, PartialEq)] +pub struct SignatureResponse(pub(crate) Scalar); /// A participant's signature share, which the coordinator will use to aggregate /// with all other signer's shares into the joint signature. @@ -438,7 +444,7 @@ impl SignatureShare { /// nonce/commitment pair at a time. Nonces should be stored in secret storage /// for later use, whereas the commitments are published. -/// The number of nonces is limited to 255. This limit can be increased if it +/// The number of nonces is limited to 255. This limit can be increased if it /// turns out to be too conservative. // TODO: Make sure the above is a correct statement, fix if needed in: // https://github.com/ZcashFoundation/redjubjub/issues/111 @@ -471,7 +477,9 @@ fn gen_rho_i(index: u64, signing_package: &SigningPackage) -> Scalar { // binding factor, we should hash our input message first. Our 'standard' // hash is HStar, which uses a domain separator already, and is the same one // that generates the binding factor. - let message_hash = HStar::default().update(signing_package.message).finalize(); + let message_hash = HStar::default() + .update(signing_package.message.as_slice()) + .finalize(); let mut hasher = HStar::default(); hasher @@ -526,7 +534,7 @@ fn gen_challenge( HStar::default() .update(group_commitment_bytes) .update(group_public.bytes.bytes) - .update(signing_package.message) + .update(signing_package.message.as_slice()) .finalize() } diff --git a/src/lib.rs b/src/lib.rs index 3584aa85..52b204e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,8 +20,9 @@ mod constants; mod error; pub mod frost; mod hash; +mod messages; mod scalar_mul; -mod signature; +pub(crate) mod signature; mod signing_key; mod verification_key; diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 00000000..1b335e66 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,269 @@ +//! The FROST communication messages specified in [RFC-001] +//! +//! [RFC-001]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md + +use crate::{frost, signature, verification_key, SpendAuth}; +use serde::{Deserialize, Serialize}; + +use std::collections::BTreeMap; + +#[cfg(test)] +use proptest_derive::Arbitrary; + +#[cfg(test)] +mod arbitrary; +mod constants; +mod serialize; +#[cfg(test)] +mod tests; +mod validate; + +/// Define our own `Secret` type instead of using [`frost::Secret`]. +/// +/// The serialization design specifies that `Secret` is a [`jubjub::Scalar`] that uses: +/// "a 32-byte little-endian canonical representation". +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Secret([u8; 32]); + +/// Define our own `Commitment` type instead of using [`frost::Commitment`]. +/// +/// The serialization design specifies that `Commitment` is an [`jubjub::AffinePoint`] that uses: +/// "a 32-byte little-endian canonical representation". +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Commitment([u8; 32]); + +impl From for Commitment { + fn from(value: frost::Commitment) -> Commitment { + Commitment(jubjub::AffinePoint::from(value.0).to_bytes()) + } +} + +/// Define our own `GroupCommitment` type instead of using [`frost::GroupCommitment`]. +/// +/// The serialization design specifies that `GroupCommitment` is an [`jubjub::AffinePoint`] that uses: +/// "a 32-byte little-endian canonical representation". +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct GroupCommitment([u8; 32]); + +/// Define our own `SignatureResponse` type instead of using [`frost::SignatureResponse`]. +/// +/// The serialization design specifies that `SignatureResponse` is a [`jubjub::Scalar`] that uses: +/// "a 32-byte little-endian canonical representation". +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SignatureResponse([u8; 32]); + +impl From> for SignatureResponse { + fn from(value: signature::Signature) -> SignatureResponse { + SignatureResponse(value.s_bytes) + } +} + +impl From> for GroupCommitment { + fn from(value: signature::Signature) -> GroupCommitment { + GroupCommitment(value.r_bytes) + } +} + +/// Define our own `VerificationKey` type instead of using [`verification_key::VerificationKey`]. +/// +/// The serialization design specifies that `VerificationKey` is a [`verification_key::VerificationKeyBytes`] that uses: +/// "a 32-byte little-endian canonical representation". +#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct VerificationKey([u8; 32]); + +impl From> for VerificationKey { + fn from(value: verification_key::VerificationKey) -> VerificationKey { + VerificationKey(<[u8; 32]>::from(value)) + } +} + +/// The data required to serialize a frost message. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct Message { + header: Header, + payload: Payload, +} + +/// The data required to serialize the common header fields for every message. +/// +/// Note: the `msg_type` is derived from the `payload` enum variant. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)] +pub struct Header { + version: MsgVersion, + sender: ParticipantId, + receiver: ParticipantId, +} + +/// The data required to serialize the payload for a message. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub enum Payload { + SharePackage(SharePackage), + SigningCommitments(SigningCommitments), + SigningPackage(SigningPackage), + SignatureShare(SignatureShare), + AggregateSignature(AggregateSignature), +} + +/// The numeric values used to identify each [`Payload`] variant during serialization. +// TODO: spec says `#[repr(u8)]` but it is incompatible with `bincode` +// manual serialization and deserialization is needed. +#[repr(u32)] +#[non_exhaustive] +#[derive(Serialize, Deserialize, Debug, PartialEq)] +enum MsgType { + SharePackage, + SigningCommitments, + SigningPackage, + SignatureShare, + AggregateSignature, +} + +/// The numeric values used to identify the protocol version during serialization. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)] +pub struct MsgVersion(u8); + +/// The numeric values used to identify each participant during serialization. +/// +/// In the `frost` module, participant ID `0` should be invalid. +/// But in serialization, we want participants to be indexed from `0..n`, +/// where `n` is the number of participants. +/// This helps us look up their shares and commitments in serialized arrays. +/// So in serialization, we assign the dealer and aggregator the highest IDs, +/// and mark those IDs as invalid for signers. +/// +/// "When performing Shamir secret sharing, a polynomial `f(x)` is used to generate +/// each party’s share of the secret. The actual secret is `f(0)` and the party with +/// ID `i` will be given a share with value `f(i)`. +/// Since a DKG may be implemented in the future, we recommend that the ID `0` be declared invalid." +/// https://raw.githubusercontent.com/ZcashFoundation/redjubjub/main/zcash-frost-audit-report-20210323.pdf#d +#[derive(PartialEq, Eq, Hash, PartialOrd, Debug, Copy, Clone, Ord)] +pub enum ParticipantId { + /// A serialized participant ID for a signer. + /// + /// Must be less than or equal to [`constants::MAX_SIGNER_PARTICIPANT_ID`]. + Signer(u64), + /// The fixed participant ID for the dealer as defined in [`constants::DEALER_PARTICIPANT_ID`]. + Dealer, + /// The fixed participant ID for the aggregator as defined in [`constants::AGGREGATOR_PARTICIPANT_ID`]. + Aggregator, +} + +impl From for u64 { + fn from(value: ParticipantId) -> u64 { + match value { + // An id of `0` is invalid in frost. + ParticipantId::Signer(id) => id + 1, + ParticipantId::Dealer => constants::DEALER_PARTICIPANT_ID, + ParticipantId::Aggregator => constants::AGGREGATOR_PARTICIPANT_ID, + } + } +} + +/// The data required to serialize [`frost::SharePackage`]. +/// +/// The dealer sends this message to each signer for this round. +/// With this, the signer should be able to build a [`SharePackage`] and use +/// the [`frost::sign()`] function. +/// +/// Note: [`frost::SharePackage::public`] can be calculated from [`SharePackage::secret_share`]. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SharePackage { + /// The public signing key that represents the entire group: + /// [`frost::SharePackage::group_public`]. + group_public: VerificationKey, + /// This participant's secret key share: [`frost::SharePackage::share`]. + secret_share: Secret, + /// The commitments to the coefficients for our secret polynomial _f_, + /// used to generate participants' key shares. Participants use these to perform + /// verifiable secret sharing. + /// Share packages that contain duplicate or missing [`ParticipantId`]s are invalid. + /// [`ParticipantId`]s must be serialized in ascending numeric order. + share_commitment: BTreeMap, +} + +/// The data required to serialize [`frost::SigningCommitments`]. +/// +/// Each signer must send this message to the aggregator. +/// A signing commitment from the first round of the signing protocol. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SigningCommitments { + /// The hiding point: [`frost::SigningCommitments::hiding`] + hiding: Commitment, + /// The binding point: [`frost::SigningCommitments::binding`] + binding: Commitment, +} + +/// The data required to serialize [`frost::SigningPackage`]. +/// +/// The aggregator decides what message is going to be signed and +/// sends it to each signer with all the commitments collected. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SigningPackage { + /// The collected commitments for each signer as a hashmap of + /// unique participant identifiers: [`frost::SigningPackage::signing_commitments`] + /// + /// Signing packages that contain duplicate or missing [`ParticipantId`]s are invalid. + signing_commitments: BTreeMap, + /// The message to be signed: [`frost::SigningPackage::message`]. + /// + /// Each signer should perform protocol-specific verification on the message. + message: Vec, +} + +impl From for frost::SigningPackage { + fn from(value: SigningPackage) -> frost::SigningPackage { + let mut signing_commitments = Vec::new(); + for (participant_id, commitment) in &value.signing_commitments { + let s = frost::SigningCommitments { + index: u64::from(*participant_id), + // TODO: The `from_bytes()` response is a `CtOption` so we have to `unwrap()` + hiding: jubjub::ExtendedPoint::from( + jubjub::AffinePoint::from_bytes(commitment.hiding.0).unwrap(), + ), + binding: jubjub::ExtendedPoint::from( + jubjub::AffinePoint::from_bytes(commitment.binding.0).unwrap(), + ), + }; + signing_commitments.push(s); + } + + frost::SigningPackage { + signing_commitments, + message: value.message, + } + } +} + +/// The data required to serialize [`frost::SignatureShare`]. +/// +/// Each signer sends their signatures to the aggregator who is going to collect them +/// and generate a final spend signature. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct SignatureShare { + /// This participant's signature over the message: [`frost::SignatureShare::signature`] + signature: SignatureResponse, +} + +/// The data required to serialize a successful output from [`frost::aggregate()`]. +/// +/// The final signature is broadcasted by the aggregator to all signers. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[cfg_attr(test, derive(Arbitrary))] +pub struct AggregateSignature { + /// The aggregated group commitment: [`signature::Signature::r_bytes`] returned by [`frost::aggregate()`] + group_commitment: GroupCommitment, + /// A plain Schnorr signature created by summing all the signature shares: + /// [`signature::Signature::s_bytes`] returned by [`frost::aggregate()`] + schnorr_signature: SignatureResponse, +} diff --git a/src/messages/arbitrary.rs b/src/messages/arbitrary.rs new file mode 100644 index 00000000..515b7a03 --- /dev/null +++ b/src/messages/arbitrary.rs @@ -0,0 +1,55 @@ +use proptest::{ + arbitrary::{any, Arbitrary}, + prelude::*, +}; + +use super::*; + +impl Arbitrary for Header { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::(), + any::(), + any::(), + ) + .prop_filter( + "Sender and receiver participant IDs can not be the same", + |(_, sender, receiver)| sender != receiver, + ) + .prop_map(|(version, sender, receiver)| Header { + version: version, + sender: sender, + receiver: receiver, + }) + .boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for MsgVersion { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + Just(constants::BASIC_FROST_SERIALIZATION).boxed() + } + + type Strategy = BoxedStrategy; +} + +impl Arbitrary for ParticipantId { + type Parameters = (); + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + prop_oneof![ + (u64::MIN..=constants::MAX_SIGNER_PARTICIPANT_ID).prop_map(ParticipantId::Signer), + Just(ParticipantId::Dealer), + Just(ParticipantId::Aggregator), + ] + .boxed() + } + + type Strategy = BoxedStrategy; +} diff --git a/src/messages/constants.rs b/src/messages/constants.rs new file mode 100644 index 00000000..1a77103a --- /dev/null +++ b/src/messages/constants.rs @@ -0,0 +1,31 @@ +//! Definitions of constants. + +use super::MsgVersion; + +/// The first version of FROST messages +pub const BASIC_FROST_SERIALIZATION: MsgVersion = MsgVersion(0); + +/// The fixed participant ID for the dealer. +pub const DEALER_PARTICIPANT_ID: u64 = u64::MAX - 1; + +/// The fixed participant ID for the aggregator. +pub const AGGREGATOR_PARTICIPANT_ID: u64 = u64::MAX; + +/// The maximum `ParticipantId::Signer` in this serialization format. +/// +/// We reserve two participant IDs for the dealer and aggregator. +pub const MAX_SIGNER_PARTICIPANT_ID: u64 = u64::MAX - 2; + +/// The maximum number of signers +/// +/// By protocol the number of signers can'e be more than 255. +pub const MAX_SIGNERS: u8 = 255; + +/// The maximum length of a Zcash message, in bytes. +pub const ZCASH_MAX_PROTOCOL_MESSAGE_LEN: usize = 2 * 1024 * 1024; + +/// The minimum number of signers of any FROST setup. +pub const MIN_SIGNERS: usize = 2; + +/// The minimum number of signers that must sign. +pub const MIN_THRESHOLD: usize = 2; diff --git a/src/messages/serialize.rs b/src/messages/serialize.rs new file mode 100644 index 00000000..0fba8fb0 --- /dev/null +++ b/src/messages/serialize.rs @@ -0,0 +1,68 @@ +//! Serialization rules specified in [RFC-001#Serialize-Deserialize] +//! +//! We automatically serialize and deserialize using serde derivations where possible. +//! Sometimes we need to implement ourselves, this file holds that code. +//! +//! [RFC-001#Serialize-Deserialize]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md#serializationdeserialization + +use serde::ser::{Serialize, Serializer}; + +use serde::de::{self, Deserialize, Deserializer, Visitor}; + +use super::constants::{ + AGGREGATOR_PARTICIPANT_ID, DEALER_PARTICIPANT_ID, MAX_SIGNER_PARTICIPANT_ID, +}; +use super::*; + +use std::fmt; + +impl Serialize for ParticipantId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + ParticipantId::Signer(id) => { + assert!(id <= MAX_SIGNER_PARTICIPANT_ID); + serializer.serialize_u64(id) + } + ParticipantId::Dealer => serializer.serialize_u64(DEALER_PARTICIPANT_ID), + ParticipantId::Aggregator => serializer.serialize_u64(AGGREGATOR_PARTICIPANT_ID), + } + } +} + +struct ParticipantIdVisitor; + +impl<'de> Visitor<'de> for ParticipantIdVisitor { + type Value = ParticipantId; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str( + format!("an integer between {} and {}", std::u64::MIN, std::u64::MAX).as_str(), + ) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + // Note: deserialization can't fail, because all values are valid. + if value == DEALER_PARTICIPANT_ID { + return Ok(ParticipantId::Dealer); + } else if value == AGGREGATOR_PARTICIPANT_ID { + return Ok(ParticipantId::Aggregator); + } else { + return Ok(ParticipantId::Signer(value)); + } + } +} + +impl<'de> Deserialize<'de> for ParticipantId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_u64(ParticipantIdVisitor) + } +} diff --git a/src/messages/tests.rs b/src/messages/tests.rs new file mode 100644 index 00000000..f0f320e4 --- /dev/null +++ b/src/messages/tests.rs @@ -0,0 +1,2 @@ +mod integration; +mod prop; diff --git a/src/messages/tests/integration.rs b/src/messages/tests/integration.rs new file mode 100644 index 00000000..e571669a --- /dev/null +++ b/src/messages/tests/integration.rs @@ -0,0 +1,805 @@ +use crate::{ + frost, + messages::{ + validate::{MsgErr, Validate}, + *, + }, + verification_key, +}; +use rand::thread_rng; +use serde_json; +use std::convert::TryFrom; + +#[test] +fn validate_version() { + // A version number that we expect to be always invalid + const INVALID_VERSION: u8 = u8::MAX; + + let setup = basic_setup(); + + let header = Header { + version: MsgVersion(INVALID_VERSION), + sender: setup.dealer, + receiver: setup.signer1, + }; + + let validate = Validate::validate(&header); + assert_eq!(validate, Err(MsgErr::WrongVersion)); + + let validate = Validate::validate(&Header { + version: constants::BASIC_FROST_SERIALIZATION, + sender: setup.dealer, + receiver: setup.signer1, + }) + .err(); + + assert_eq!(validate, None); +} + +#[test] +fn validate_sender_receiver() { + let setup = basic_setup(); + + let header = Header { + version: constants::BASIC_FROST_SERIALIZATION, + sender: setup.signer1, + receiver: setup.signer1, + }; + + let validate = Validate::validate(&header); + assert_eq!(validate, Err(MsgErr::SameSenderAndReceiver)); +} + +#[test] +fn validate_sharepackage() { + let setup = basic_setup(); + let (mut shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + let header = create_valid_header(setup.signer1, setup.signer2); + + let group_public = VerificationKey::from( + verification_key::VerificationKey::try_from(shares[0].group_public.bytes).unwrap(), + ); + let secret_share = Secret(shares[0].share.value.0.to_bytes()); + + let participants = vec![setup.signer1, setup.signer2]; + shares.truncate(2); + let share_commitment = generate_share_commitment(&shares, participants); + + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share: secret_share, + share_commitment: share_commitment, + }); + let validate_payload = Validate::validate(&payload); + let valid_payload = validate_payload.expect("a valid payload").clone(); + + let message = Message { + header, + payload: valid_payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeDealer)); + + // change the header + let header = create_valid_header(setup.dealer, setup.aggregator); + + let message = Message { + header, + payload: valid_payload, + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); + + let participants = vec![setup.signer1]; + shares.truncate(1); + let mut share_commitment = generate_share_commitment(&shares, participants); + + // change the payload to have only 1 commitment + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share: secret_share, + share_commitment: share_commitment.clone(), + }); + let validate_payload = Validate::validate(&payload); + assert_eq!( + validate_payload, + Err(MsgErr::NotEnoughCommitments(constants::MIN_SIGNERS)) + ); + + // build and use too many commitments + for i in 2..constants::MAX_SIGNERS as u64 + 2 { + share_commitment.insert( + ParticipantId::Signer(i), + share_commitment.clone()[&setup.signer1], + ); + } + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share, + share_commitment, + }); + let validate_payload = Validate::validate(&payload); + assert_eq!(validate_payload, Err(MsgErr::TooManyCommitments)); +} + +#[test] +fn serialize_sharepackage() { + let setup = basic_setup(); + + let (mut shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + let header = create_valid_header(setup.dealer, setup.signer1); + + let group_public = VerificationKey::from( + verification_key::VerificationKey::try_from(shares[0].group_public.bytes).unwrap(), + ); + let secret_share = Secret(shares[0].share.value.0.to_bytes()); + + let participants = vec![setup.signer1]; + shares.truncate(1); + let share_commitment = generate_share_commitment(&shares, participants); + + let payload = Payload::SharePackage(SharePackage { + group_public, + secret_share, + share_commitment: share_commitment.clone(), + }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure and header serialization/deserialization + serialize_message(message, setup.dealer, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SharePackage); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // group_public is 32 bytes + let deserialized_group_public: VerificationKey = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + // secret share is 32 bytes + let deserialized_secret_share: Secret = + bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); + // rest of the message is the map: 32(Commitment) + 8(ParticipantId) + 8(map.len()) + let deserialized_share_commitment: BTreeMap = + bincode::deserialize(&payload_serialized_bytes[64..112]).unwrap(); + + // check the map len + let deserialized_map_len: u64 = + bincode::deserialize(&payload_serialized_bytes[64..72]).unwrap(); + assert_eq!(deserialized_map_len, 1); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 112); + + assert_eq!(deserialized_group_public, group_public); + assert_eq!(deserialized_secret_share, secret_share); + assert_eq!(deserialized_share_commitment, share_commitment); +} + +#[test] +fn validate_signingcommitments() { + let mut setup = basic_setup(); + + let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + + let header = create_valid_header(setup.aggregator, setup.signer2); + + let payload = Payload::SigningCommitments(SigningCommitments { + hiding: Commitment(jubjub::AffinePoint::from(commitment[0].hiding).to_bytes()), + binding: Commitment(jubjub::AffinePoint::from(commitment[0].binding).to_bytes()), + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeSigner)); + + // change the header + let header = create_valid_header(setup.signer1, setup.signer2); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeAggregator)); + + // change the header to be valid + let header = create_valid_header(setup.signer1, setup.aggregator); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_signingcommitments() { + let mut setup = basic_setup(); + + let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + + let header = create_valid_header(setup.aggregator, setup.signer1); + + let hiding = Commitment(jubjub::AffinePoint::from(commitment[0].hiding).to_bytes()); + let binding = Commitment(jubjub::AffinePoint::from(commitment[0].binding).to_bytes()); + + let payload = Payload::SigningCommitments(SigningCommitments { hiding, binding }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.aggregator, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SigningCommitments); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // hiding is 32 bytes + let deserialized_hiding: Commitment = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + // binding is 43 bytes kore + let deserialized_binding: Commitment = + bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 64); + + assert_eq!(deserialized_hiding, hiding); + assert_eq!(deserialized_binding, binding); +} + +#[test] +fn validate_signingpackage() { + let mut setup = basic_setup(); + + let (_nonce, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + + let header = create_valid_header(setup.signer1, setup.signer2); + + // try with only 1 commitment + let commitments = vec![commitment1[0]]; + let participants = vec![setup.signer1]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + let validate_payload = Validate::validate(&payload); + assert_eq!( + validate_payload, + Err(MsgErr::NotEnoughCommitments(constants::MIN_SIGNERS)) + ); + + // add too many commitments + let mut big_signing_commitments = BTreeMap::::new(); + for i in 0..constants::MAX_SIGNERS as u64 + 1 { + big_signing_commitments.insert( + ParticipantId::Signer(i), + signing_commitments[&setup.signer1].clone(), + ); + } + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: big_signing_commitments, + message: "hola".as_bytes().to_vec(), + }); + let validate_payload = Validate::validate(&payload); + assert_eq!(validate_payload, Err(MsgErr::TooManyCommitments)); + + // change to 2 commitments + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let big_message = [0u8; constants::ZCASH_MAX_PROTOCOL_MESSAGE_LEN + 1].to_vec(); + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: big_message, + }); + let validate_payload = Validate::validate(&payload); + assert_eq!(validate_payload, Err(MsgErr::MsgTooBig)); + + let message = Message { + header, + payload: payload.clone(), + }; + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeAggregator)); + + // change header + let header = create_valid_header(setup.aggregator, setup.dealer); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); + + let header = create_valid_header(setup.aggregator, setup.signer1); + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments, + message: "hola".as_bytes().to_vec(), + }); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_signingpackage() { + let mut setup = basic_setup(); + + let (_nonce, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + + let header = create_valid_header(setup.aggregator, setup.signer1); + + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let payload = Payload::SigningPackage(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.aggregator, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SigningPackage); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // check the map len + let deserialized_map_len: u64 = bincode::deserialize(&payload_serialized_bytes[0..8]).unwrap(); + assert_eq!(deserialized_map_len, 2); + + // Each SigningCommitment is 64 bytes and the ParticipantId is 8 bytes. + // This is multiplied by the map len, also include the map len bytes. + let deserialized_signing_commitments: BTreeMap = + bincode::deserialize(&payload_serialized_bytes[0..152]).unwrap(); + + // Message is from the end of the map up to the end of the message. + let deserialized_message: Vec = + bincode::deserialize(&payload_serialized_bytes[152..payload_serialized_bytes.len()]) + .unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 164); + + assert_eq!(deserialized_signing_commitments, signing_commitments); + assert_eq!(deserialized_message, "hola".as_bytes().to_vec()); +} + +#[test] +fn validate_signatureshare() { + let mut setup = basic_setup(); + + // signers and aggregator should have this data from `SharePackage` + let (shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + // create a signing package, this is done in the aggregator side. + // the signrs should have this data from `SigningPackage` + let (nonce1, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce2, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let signing_package = frost::SigningPackage::from(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + + // here we get started with the `SignatureShare` message. + let signature_share = frost::sign(&signing_package, nonce1[0], &shares[0]).unwrap(); + + // this header is invalid + let header = create_valid_header(setup.aggregator, setup.signer1); + + let payload = Payload::SignatureShare(SignatureShare { + signature: SignatureResponse(signature_share.signature.0.to_bytes()), + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeSigner)); + + // change the header, still invalid. + let header = create_valid_header(setup.signer1, setup.signer2); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeAggregator)); + + // change the header to be valid + let header = create_valid_header(setup.signer1, setup.aggregator); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_signatureshare() { + let mut setup = basic_setup(); + + // signers and aggregator should have this data from `SharePackage` + let (shares, _pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + // create a signing package, this is done in the aggregator side. + // the signers should have this data from `SigningPackage` + let (nonce1, commitment1) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + let (_nonce2, commitment2) = frost::preprocess(1, u64::from(setup.signer2), &mut setup.rng); + let commitments = vec![commitment1[0], commitment2[0]]; + let participants = vec![setup.signer1, setup.signer2]; + let signing_commitments = create_signing_commitments(commitments, participants); + + let signing_package = frost::SigningPackage::from(SigningPackage { + signing_commitments: signing_commitments.clone(), + message: "hola".as_bytes().to_vec(), + }); + + // here we get started with the `SignatureShare` message. + let signature_share = frost::sign(&signing_package, nonce1[0], &shares[0]).unwrap(); + + // valid header + let header = create_valid_header(setup.signer1, setup.aggregator); + + let signature = SignatureResponse(signature_share.signature.0.to_bytes()); + let payload = Payload::SignatureShare(SignatureShare { signature }); + + let message = Message { + header: header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.signer1, setup.aggregator); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::SignatureShare); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // signature is 32 bytes + let deserialized_signature: SignatureResponse = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 32); + + assert_eq!(deserialized_signature, signature); +} + +#[test] +fn validate_aggregatesignature() { + let (setup, group_signature_res) = full_setup(); + + // this header is invalid + let header = create_valid_header(setup.signer1, setup.aggregator); + + let payload = Payload::AggregateSignature(AggregateSignature { + group_commitment: GroupCommitment::from(group_signature_res), + schnorr_signature: SignatureResponse::from(group_signature_res), + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::SenderMustBeAggregator)); + + // change the header, still invalid. + let header = create_valid_header(setup.aggregator, setup.dealer); + + let message = Message { + header, + payload: payload.clone(), + }; + + let validate_message = Validate::validate(&message); + assert_eq!(validate_message, Err(MsgErr::ReceiverMustBeSigner)); + + // change the header to be valid + let header = create_valid_header(setup.aggregator, setup.signer1); + + let validate_message = Validate::validate(&Message { header, payload }).err(); + + assert_eq!(validate_message, None); +} + +#[test] +fn serialize_aggregatesignature() { + let (setup, group_signature_res) = full_setup(); + + let header = create_valid_header(setup.aggregator, setup.signer1); + + let group_commitment = GroupCommitment::from(group_signature_res); + let schnorr_signature = SignatureResponse::from(group_signature_res); + let payload = Payload::AggregateSignature(AggregateSignature { + group_commitment, + schnorr_signature, + }); + + let message = Message { + header, + payload: payload.clone(), + }; + + // check general structure serialization/deserialization + serialize_message(message, setup.aggregator, setup.signer1); + + // check payload serialization/deserialization + let mut payload_serialized_bytes = bincode::serialize(&payload).unwrap(); + + // check the message type is correct + let deserialized_msg_type: MsgType = + bincode::deserialize(&payload_serialized_bytes[0..4]).unwrap(); + assert_eq!(deserialized_msg_type, MsgType::AggregateSignature); + + // remove the msg_type from the the payload + payload_serialized_bytes = + (&payload_serialized_bytes[4..payload_serialized_bytes.len()]).to_vec(); + + // group_commitment is 32 bytes + let deserialized_group_commiment: GroupCommitment = + bincode::deserialize(&payload_serialized_bytes[0..32]).unwrap(); + // schnorr_signature is 32 bytes + let deserialized_schnorr_signature: SignatureResponse = + bincode::deserialize(&payload_serialized_bytes[32..64]).unwrap(); + + // no leftover bytes + assert_eq!(payload_serialized_bytes.len(), 64); + + assert_eq!(deserialized_group_commiment, group_commitment); + assert_eq!(deserialized_schnorr_signature, schnorr_signature); +} + +#[test] +fn btreemap() { + let mut setup = basic_setup(); + let mut map = BTreeMap::new(); + + let (_nonce, commitment) = frost::preprocess(1, u64::from(setup.signer1), &mut setup.rng); + + let commitments = vec![commitment[0]]; + let participants = vec![setup.signer1]; + let signing_commitments = create_signing_commitments(commitments, participants); + + map.insert(ParticipantId::Signer(1), &signing_commitments); + map.insert(ParticipantId::Signer(2), &signing_commitments); + map.insert(ParticipantId::Signer(0), &signing_commitments); + + // Check the ascending order + let mut map_iter = map.iter(); + let (key, _) = map_iter.next().unwrap(); + assert_eq!(*key, ParticipantId::Signer(0)); + let (key, _) = map_iter.next().unwrap(); + assert_eq!(*key, ParticipantId::Signer(1)); + let (key, _) = map_iter.next().unwrap(); + assert_eq!(*key, ParticipantId::Signer(2)); + + // Add a repeated key + map.insert(ParticipantId::Signer(1), &signing_commitments); + // BTreeMap is not increasing + assert_eq!(map.len(), 3); +} + +// utility functions + +fn create_valid_header(sender: ParticipantId, receiver: ParticipantId) -> Header { + Validate::validate(&Header { + version: constants::BASIC_FROST_SERIALIZATION, + sender: sender, + receiver: receiver, + }) + .expect("always a valid header") + .clone() +} + +fn serialize_header( + header_serialized_bytes: Vec, + sender: ParticipantId, + receiver: ParticipantId, +) { + let deserialized_version: MsgVersion = + bincode::deserialize(&header_serialized_bytes[0..1]).unwrap(); + let deserialized_sender: ParticipantId = + bincode::deserialize(&header_serialized_bytes[1..9]).unwrap(); + let deserialized_receiver: ParticipantId = + bincode::deserialize(&header_serialized_bytes[9..17]).unwrap(); + assert_eq!(deserialized_version, constants::BASIC_FROST_SERIALIZATION); + assert_eq!(deserialized_sender, sender); + assert_eq!(deserialized_receiver, receiver); +} + +fn serialize_message(message: Message, sender: ParticipantId, receiver: ParticipantId) { + let serialized_bytes = bincode::serialize(&message).unwrap(); + let deserialized_bytes: Message = bincode::deserialize(&serialized_bytes).unwrap(); + assert_eq!(message, deserialized_bytes); + + let serialized_json = serde_json::to_string(&message).unwrap(); + let deserialized_json: Message = serde_json::from_str(serialized_json.as_str()).unwrap(); + assert_eq!(message, deserialized_json); + + let header_serialized_bytes = bincode::serialize(&message.header).unwrap(); + serialize_header(header_serialized_bytes, sender, receiver); + + // make sure the message fields are in the right order + let message_serialized_bytes = bincode::serialize(&message).unwrap(); + let deserialized_header: Header = + bincode::deserialize(&message_serialized_bytes[0..17]).unwrap(); + let deserialized_payload: Payload = + bincode::deserialize(&message_serialized_bytes[17..message_serialized_bytes.len()]) + .unwrap(); + assert_eq!(deserialized_header, message.header); + assert_eq!(deserialized_payload, message.payload); +} + +struct Setup { + rng: rand::rngs::ThreadRng, + num_signers: u8, + threshold: u8, + dealer: ParticipantId, + aggregator: ParticipantId, + signer1: ParticipantId, + signer2: ParticipantId, +} + +fn basic_setup() -> Setup { + Setup { + rng: thread_rng(), + num_signers: 3, + threshold: 2, + dealer: ParticipantId::Dealer, + aggregator: ParticipantId::Aggregator, + signer1: ParticipantId::Signer(0), + signer2: ParticipantId::Signer(1), + } +} + +fn full_setup() -> (Setup, signature::Signature) { + let mut setup = basic_setup(); + + // aggregator creates the shares and pubkeys for this round + let (shares, pubkeys) = + frost::keygen_with_dealer(setup.num_signers, setup.threshold, setup.rng.clone()).unwrap(); + + let mut nonces: std::collections::HashMap> = + std::collections::HashMap::with_capacity(setup.threshold as usize); + let mut commitments: Vec = + Vec::with_capacity(setup.threshold as usize); + + // aggregator generates nonces and signing commitments for each participant. + for participant_index in 1..(setup.threshold + 1) { + let (nonce, commitment) = frost::preprocess(1, participant_index as u64, &mut setup.rng); + nonces.insert(participant_index as u64, nonce); + commitments.push(commitment[0]); + } + + // aggregator generates a signing package + let mut signature_shares: Vec = + Vec::with_capacity(setup.threshold as usize); + let message = "message to sign".as_bytes().to_vec(); + let signing_package = frost::SigningPackage { + message: message.clone(), + signing_commitments: commitments, + }; + + // each participant generates their signature share + for (participant_index, nonce) in nonces { + let share_package = shares + .iter() + .find(|share| participant_index == share.index) + .unwrap(); + let nonce_to_use = nonce[0]; + let signature_share = frost::sign(&signing_package, nonce_to_use, share_package).unwrap(); + signature_shares.push(signature_share); + } + + // aggregator generate the final signature + let final_signature = + frost::aggregate(&signing_package, &signature_shares[..], &pubkeys).unwrap(); + (setup, final_signature) +} + +fn generate_share_commitment( + shares: &Vec, + participants: Vec, +) -> BTreeMap { + assert_eq!(shares.len(), participants.len()); + participants + .into_iter() + .zip(shares) + .map(|(participant_id, share)| { + ( + participant_id, + Commitment::from(share.share.commitment.0[0].clone()), + ) + }) + .collect() +} + +fn create_signing_commitments( + commitments: Vec, + participants: Vec, +) -> BTreeMap { + assert_eq!(commitments.len(), participants.len()); + participants + .into_iter() + .zip(commitments) + .map(|(participant_id, commitment)| { + let signing_commitment = SigningCommitments { + hiding: Commitment(jubjub::AffinePoint::from(commitment.hiding).to_bytes()), + binding: Commitment(jubjub::AffinePoint::from(commitment.binding).to_bytes()), + }; + (participant_id, signing_commitment) + }) + .collect() +} diff --git a/src/messages/tests/prop.rs b/src/messages/tests/prop.rs new file mode 100644 index 00000000..e307a929 --- /dev/null +++ b/src/messages/tests/prop.rs @@ -0,0 +1,15 @@ +use proptest::prelude::*; + +use crate::messages::*; + +proptest! { + #[test] + fn serialize_message( + message in any::(), + ) { + let serialized = bincode::serialize(&message).unwrap(); + let deserialized: Message = bincode::deserialize(serialized.as_slice()).unwrap(); + + prop_assert_eq!(message, deserialized); + } +} diff --git a/src/messages/validate.rs b/src/messages/validate.rs new file mode 100644 index 00000000..5d1d9ce7 --- /dev/null +++ b/src/messages/validate.rs @@ -0,0 +1,143 @@ +//! Validation rules specified in [RFC-001#rules] +//! +//! [RFC-001#rules]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md#rules + +use super::constants::{ + BASIC_FROST_SERIALIZATION, MAX_SIGNERS, MIN_SIGNERS, MIN_THRESHOLD, + ZCASH_MAX_PROTOCOL_MESSAGE_LEN, +}; +use super::*; + +use thiserror::Error; + +pub trait Validate { + fn validate(&self) -> Result<&Self, MsgErr>; +} + +impl Validate for Message { + fn validate(&self) -> Result<&Self, MsgErr> { + match self.payload { + Payload::SharePackage(_) => { + if self.header.sender != ParticipantId::Dealer { + return Err(MsgErr::SenderMustBeDealer); + } + if !matches!(self.header.receiver, ParticipantId::Signer(_)) { + return Err(MsgErr::ReceiverMustBeSigner); + } + } + Payload::SigningCommitments(_) => { + if !matches!(self.header.sender, ParticipantId::Signer(_)) { + return Err(MsgErr::SenderMustBeSigner); + } + if self.header.receiver != ParticipantId::Aggregator { + return Err(MsgErr::ReceiverMustBeAggregator); + } + } + Payload::SigningPackage(_) => { + if self.header.sender != ParticipantId::Aggregator { + return Err(MsgErr::SenderMustBeAggregator); + } + if !matches!(self.header.receiver, ParticipantId::Signer(_)) { + return Err(MsgErr::ReceiverMustBeSigner); + } + } + Payload::SignatureShare(_) => { + if !matches!(self.header.sender, ParticipantId::Signer(_)) { + return Err(MsgErr::SenderMustBeSigner); + } + if self.header.receiver != ParticipantId::Aggregator { + return Err(MsgErr::ReceiverMustBeAggregator); + } + } + Payload::AggregateSignature(_) => { + if self.header.sender != ParticipantId::Aggregator { + return Err(MsgErr::SenderMustBeAggregator); + } + if !matches!(self.header.receiver, ParticipantId::Signer(_)) { + return Err(MsgErr::ReceiverMustBeSigner); + } + } + } + self.header.validate()?; + self.payload.validate()?; + Ok(self) + } +} + +impl Validate for Header { + fn validate(&self) -> Result<&Self, MsgErr> { + // Validate the message version. + // By now we only have 1 valid version so we compare against that. + if self.version != BASIC_FROST_SERIALIZATION { + return Err(MsgErr::WrongVersion); + } + + // Make sure the sender and the receiver are not the same. + if self.sender == self.receiver { + return Err(MsgErr::SameSenderAndReceiver); + } + Ok(self) + } +} + +impl Validate for Payload { + fn validate(&self) -> Result<&Self, MsgErr> { + match self { + Payload::SharePackage(share_package) => { + if share_package.share_commitment.len() < MIN_SIGNERS { + return Err(MsgErr::NotEnoughCommitments(MIN_SIGNERS)); + } + + if share_package.share_commitment.len() > MAX_SIGNERS.into() { + return Err(MsgErr::TooManyCommitments); + } + } + Payload::SigningCommitments(_) => {} + Payload::SigningPackage(signing_package) => { + if signing_package.message.len() > ZCASH_MAX_PROTOCOL_MESSAGE_LEN { + return Err(MsgErr::MsgTooBig); + } + + if signing_package.signing_commitments.len() < MIN_THRESHOLD { + return Err(MsgErr::NotEnoughCommitments(MIN_THRESHOLD)); + } + + if signing_package.signing_commitments.len() > MAX_SIGNERS.into() { + return Err(MsgErr::TooManyCommitments); + } + } + Payload::SignatureShare(_) => {} + Payload::AggregateSignature(_) => {} + } + + Ok(self) + } +} + +/// The error a message can produce if it fails validation. +#[derive(Error, Debug, PartialEq)] +pub enum MsgErr { + #[error("wrong version number")] + WrongVersion, + #[error("sender and receiver are the same")] + SameSenderAndReceiver, + #[error("the sender of this message must be the dealer")] + SenderMustBeDealer, + #[error("the receiver of this message must be a signer")] + ReceiverMustBeSigner, + #[error("the sender of this message must be a signer")] + SenderMustBeSigner, + #[error("the receiver of this message must be the aggregator")] + ReceiverMustBeAggregator, + #[error("the sender of this message must be the aggregator")] + SenderMustBeAggregator, + #[error("the number of signers must be at least {0}")] + NotEnoughCommitments(usize), + #[error("the number of signers can't be more than {}", MAX_SIGNERS)] + TooManyCommitments, + #[error( + "the message field can't be bigger than {}", + ZCASH_MAX_PROTOCOL_MESSAGE_LEN + )] + MsgTooBig, +} diff --git a/src/signature.rs b/src/signature.rs index 8ad09f56..43b1fbfd 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -7,6 +7,7 @@ // Authors: // - Henry de Valence +//! Redjubjub Signatures use std::marker::PhantomData; use crate::SigType; diff --git a/src/verification_key.rs b/src/verification_key.rs index 80d4e523..b7ea8a21 100644 --- a/src/verification_key.rs +++ b/src/verification_key.rs @@ -66,7 +66,7 @@ impl Hash for VerificationKeyBytes { /// /// 1. The check that the bytes are a canonical encoding of a verification key; /// 2. The check that the verification key is not a point of small order. -#[derive(Copy, Clone, Debug)] +#[derive(PartialEq, Copy, Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] #[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] diff --git a/tests/frost.rs b/tests/frost.rs index 84971e94..fa47b767 100644 --- a/tests/frost.rs +++ b/tests/frost.rs @@ -29,7 +29,7 @@ fn check_sign_with_dealer() { let mut signature_shares: Vec = Vec::with_capacity(threshold as usize); let message = "message to sign".as_bytes(); let signing_package = frost::SigningPackage { - message, + message: message.to_vec(), signing_commitments: commitments, };