Implement the messages spec (#114)
* start messages and validation * add missing docs to constants * change validation to matches, fix constant doc Co-authored-by: teor <teor@riseup.net> * fix the build * validate share_commitment * add new constants and validations * fix validation * derive serde Serialize and Deserialize in all messages structs * update created structs Co-authored-by: teor <teor@riseup.net> * fix build * define and use a new MAX_SIGNERS constant * change group_public type * add some test cases * add validation and serialization tests for SigningCommitments * add validation and serialization test to SigningPackage * change some fields order matching the spec * fix field order in tests according to last updates to the spec * implement serialize and deserialize for ParticipantId * move serde-json to dev-dependencies section * change to pub(crate) * fix serialize of VerificationKey * add assert to serialize * add note, fix typo * improve some code in tests * test serialization of individual fields * start messages and validation * add missing docs to constants * change validation to matches, fix constant doc Co-authored-by: teor <teor@riseup.net> * fix the build * validate share_commitment * add new constants and validations * fix validation * define and use a new MAX_SIGNERS constant * change group_public type * change some fields order matching the spec * change message fields to new spec * remove some non needed conversions * use a BTreeMap to guarantee the order * remove some calls to `clone()` by implementing `Copy` * change message type in frost and add validate_signatureshare test * change `share_commitment` to BTreeMap * add `serialize_signatureshare` test * add aggregatesignature tests * add some test header messages utility functions * add a setup utility * move the general serialization checks into an utility function * fi some typos * add and use a `generate_share_commitment` utility * add create_signing_commitments utility function * improve the serialization tests * make room for prop tests * add arbitrary tests for serialization * remove allow dead code from messages * fix some imports * make signature module public only to the crate * simplify a bit the frost tests * improve the generated docs * add a `prop_filter` to Header arbitrary * (ab)use proptest_derive * improve validation for Message * improve some utility functions * change frost to serialization id conversion * add a quick btreemap test * change the `MsgType` to `u32` * add no leftover bytes checks * add a full_setup utility * add map len checks Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
00190de7c6
commit
fa3c602698
|
@ -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 = []
|
||||
|
|
42
src/frost.rs
42
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<jubjub::ExtendedPoint> 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<Commitment>);
|
||||
pub struct ShareCommitment(pub(crate) Vec<Commitment>);
|
||||
|
||||
/// 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<u8>,
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<frost::Commitment> 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<signature::Signature<SpendAuth>> for SignatureResponse {
|
||||
fn from(value: signature::Signature<SpendAuth>) -> SignatureResponse {
|
||||
SignatureResponse(value.s_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<signature::Signature<SpendAuth>> for GroupCommitment {
|
||||
fn from(value: signature::Signature<SpendAuth>) -> GroupCommitment {
|
||||
GroupCommitment(value.r_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define our own `VerificationKey` type instead of using [`verification_key::VerificationKey<SpendAuth>`].
|
||||
///
|
||||
/// 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<verification_key::VerificationKey<SpendAuth>> for VerificationKey {
|
||||
fn from(value: verification_key::VerificationKey<SpendAuth>) -> 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<ParticipantId> 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<ParticipantId, Commitment>,
|
||||
}
|
||||
|
||||
/// 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<ParticipantId, SigningCommitments>,
|
||||
/// The message to be signed: [`frost::SigningPackage::message`].
|
||||
///
|
||||
/// Each signer should perform protocol-specific verification on the message.
|
||||
message: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<SigningPackage> 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,
|
||||
}
|
|
@ -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::<MsgVersion>(),
|
||||
any::<ParticipantId>(),
|
||||
any::<ParticipantId>(),
|
||||
)
|
||||
.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<Self>;
|
||||
}
|
||||
|
||||
impl Arbitrary for MsgVersion {
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
Just(constants::BASIC_FROST_SERIALIZATION).boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
||||
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<Self>;
|
||||
}
|
|
@ -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;
|
|
@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<E>(self, value: u64) -> Result<Self::Value, E>
|
||||
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<D>(deserializer: D) -> Result<ParticipantId, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_u64(ParticipantIdVisitor)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
mod integration;
|
||||
mod prop;
|
|
@ -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<ParticipantId, Commitment> =
|
||||
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::<ParticipantId, SigningCommitments>::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<ParticipantId, SigningCommitments> =
|
||||
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<u8> =
|
||||
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<u8>,
|
||||
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<SpendAuth>) {
|
||||
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<u64, Vec<frost::SigningNonces>> =
|
||||
std::collections::HashMap::with_capacity(setup.threshold as usize);
|
||||
let mut commitments: Vec<frost::SigningCommitments> =
|
||||
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<frost::SignatureShare> =
|
||||
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<frost::SharePackage>,
|
||||
participants: Vec<ParticipantId>,
|
||||
) -> BTreeMap<ParticipantId, Commitment> {
|
||||
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<frost::SigningCommitments>,
|
||||
participants: Vec<ParticipantId>,
|
||||
) -> BTreeMap<ParticipantId, SigningCommitments> {
|
||||
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()
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use proptest::prelude::*;
|
||||
|
||||
use crate::messages::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn serialize_message(
|
||||
message in any::<Message>(),
|
||||
) {
|
||||
let serialized = bincode::serialize(&message).unwrap();
|
||||
let deserialized: Message = bincode::deserialize(serialized.as_slice()).unwrap();
|
||||
|
||||
prop_assert_eq!(message, deserialized);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
// Authors:
|
||||
// - Henry de Valence <hdevalence@hdevalence.ca>
|
||||
|
||||
//! Redjubjub Signatures
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::SigType;
|
||||
|
|
|
@ -66,7 +66,7 @@ impl<T: SigType> Hash for VerificationKeyBytes<T> {
|
|||
///
|
||||
/// 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<T>"))]
|
||||
#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes<T>"))]
|
||||
|
|
|
@ -29,7 +29,7 @@ fn check_sign_with_dealer() {
|
|||
let mut signature_shares: Vec<frost::SignatureShare> = 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,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue