Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the messages spec #114

Merged
merged 71 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
8344932
start messages and validation
oxarbitrage May 13, 2021
b20bc86
add missing docs to constants
oxarbitrage May 13, 2021
f9bc28c
change validation to matches, fix constant doc
oxarbitrage May 14, 2021
be6c9f5
fix the build
oxarbitrage May 14, 2021
e949f2b
validate share_commitment
oxarbitrage May 14, 2021
baa4552
add new constants and validations
oxarbitrage May 14, 2021
2891486
fix validation
oxarbitrage May 14, 2021
c650406
derive serde Serialize and Deserialize in all messages structs
oxarbitrage May 14, 2021
e19e34e
update created structs
oxarbitrage May 17, 2021
474997b
fix build
oxarbitrage May 17, 2021
873dc95
define and use a new MAX_SIGNERS constant
oxarbitrage May 17, 2021
590e812
change group_public type
oxarbitrage May 17, 2021
dbad358
Merge branch 'messages_impl1' into messages_impl2
oxarbitrage May 17, 2021
5de661c
add some test cases
oxarbitrage May 18, 2021
c259682
add validation and serialization tests for SigningCommitments
oxarbitrage May 18, 2021
2e33163
add validation and serialization test to SigningPackage
oxarbitrage May 18, 2021
6b26a1e
change some fields order matching the spec
oxarbitrage May 19, 2021
7f86b2b
Merge branch 'messages_impl1' into messages_impl3
oxarbitrage May 19, 2021
f54616b
fix field order in tests according to last updates to the spec
oxarbitrage May 19, 2021
1eb5d7e
implement serialize and deserialize for ParticipantId
oxarbitrage May 19, 2021
beb3e97
move serde-json to dev-dependencies section
oxarbitrage May 20, 2021
951633b
change to pub(crate)
oxarbitrage May 20, 2021
a8d2357
fix serialize of VerificationKey
oxarbitrage May 20, 2021
6dfb4bc
add assert to serialize
oxarbitrage May 20, 2021
6cae46b
add note, fix typo
oxarbitrage May 20, 2021
30b72b7
improve some code in tests
oxarbitrage May 20, 2021
54e5f29
test serialization of individual fields
oxarbitrage May 20, 2021
4f73414
start messages and validation
oxarbitrage May 13, 2021
e81e223
add missing docs to constants
oxarbitrage May 13, 2021
766e33f
change validation to matches, fix constant doc
oxarbitrage May 14, 2021
3ab77eb
fix the build
oxarbitrage May 14, 2021
c75bddd
validate share_commitment
oxarbitrage May 14, 2021
a82e4d2
add new constants and validations
oxarbitrage May 14, 2021
952b02b
fix validation
oxarbitrage May 14, 2021
65ef751
define and use a new MAX_SIGNERS constant
oxarbitrage May 17, 2021
2078664
change group_public type
oxarbitrage May 17, 2021
2d1ff77
change some fields order matching the spec
oxarbitrage May 19, 2021
916a180
change message fields to new spec
oxarbitrage May 28, 2021
34afee9
Merge remote-tracking branch 'mioarriba/messages_impl1' into messages…
oxarbitrage May 28, 2021
4eb856d
Merge remote-tracking branch 'mioarriba/messages_impl2' into messages…
oxarbitrage May 28, 2021
4fce7ff
remove some non needed conversions
oxarbitrage May 28, 2021
3f19aa7
use a BTreeMap to guarantee the order
oxarbitrage May 29, 2021
90b0200
remove some calls to `clone()` by implementing `Copy`
oxarbitrage May 30, 2021
2363dda
change message type in frost and add validate_signatureshare test
oxarbitrage May 31, 2021
7797a63
change `share_commitment` to BTreeMap
oxarbitrage May 31, 2021
a2d7e93
add `serialize_signatureshare` test
oxarbitrage May 31, 2021
3d03f20
add aggregatesignature tests
oxarbitrage Jun 1, 2021
1b58a12
add some test header messages utility functions
oxarbitrage Jun 1, 2021
c69802f
add a setup utility
oxarbitrage Jun 1, 2021
126287d
move the general serialization checks into an utility function
oxarbitrage Jun 1, 2021
7bfca23
fi some typos
oxarbitrage Jun 1, 2021
3e5b7e7
add and use a `generate_share_commitment` utility
oxarbitrage Jun 2, 2021
d2090b8
add create_signing_commitments utility function
oxarbitrage Jun 2, 2021
b461a86
improve the serialization tests
oxarbitrage Jun 2, 2021
98f1f3e
make room for prop tests
oxarbitrage Jun 2, 2021
602b4f7
add arbitrary tests for serialization
oxarbitrage Jun 2, 2021
97ffcc3
remove allow dead code from messages
oxarbitrage Jun 2, 2021
3817e63
fix some imports
oxarbitrage Jun 2, 2021
7e4ba0a
make signature module public only to the crate
oxarbitrage Jun 3, 2021
ac9d5e6
simplify a bit the frost tests
oxarbitrage Jun 3, 2021
5df8a42
improve the generated docs
oxarbitrage Jun 3, 2021
34246d3
add a `prop_filter` to Header arbitrary
oxarbitrage Jun 3, 2021
4c60947
(ab)use proptest_derive
oxarbitrage Jun 3, 2021
95fb882
improve validation for Message
oxarbitrage Jun 3, 2021
27a8cc2
improve some utility functions
oxarbitrage Jun 3, 2021
2a87605
change frost to serialization id conversion
oxarbitrage Jun 3, 2021
a066bb7
add a quick btreemap test
oxarbitrage Jun 3, 2021
760115e
change the `MsgType` to `u32`
oxarbitrage Jun 3, 2021
59638f3
add no leftover bytes checks
oxarbitrage Jun 3, 2021
8369111
add a full_setup utility
oxarbitrage Jun 3, 2021
503af0e
add map len checks
oxarbitrage Jun 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ lazy_static = "1.4"
proptest = "1.0"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"

[features]
nightly = []
Expand Down
42 changes: 25 additions & 17 deletions src/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,17 +63,19 @@ impl From<jubjub::ExtendedPoint> for Public {
#[derive(Clone)]
pub struct Share {
receiver_index: u64,
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
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
/// polynomial.
///
/// 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.
Expand All @@ -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`].
Expand Down Expand Up @@ -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 {
Expand All @@ -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>,
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ mod constants;
mod error;
pub mod frost;
mod hash;
mod messages;
mod scalar_mul;
mod signature;
pub mod signature;
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
mod signing_key;
mod verification_key;

Expand Down
251 changes: 251 additions & 0 deletions src/messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
//! 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)]
mod arbitrary;
mod constants;
mod serialize;
#[cfg(test)]
mod tests;
mod validate;

/// Define our own `Secret` type instead of using `frost::Secret`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// The serialization design specifies that `Secret` is a `Scalar` that uses:
/// "a 32-byte little-endian canonical representation".
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct Secret([u8; 32]);

/// Define our own `Commitment` type instead of using `frost::Commitment`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// The serialization design specifies that `Commitment` is a `AffinePoint` that uses:
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
/// "a 32-byte little-endian canonical representation".
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Copy)]
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 a `AffinePoint` that uses:
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
/// "a 32-byte little-endian canonical representation".
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
pub struct GroupCommitment([u8; 32]);

/// Define our own `SignatureResponse` type instead of using `frost::SignatureResponse`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// The serialization design specifies that `SignatureResponse` is a `Scalar` that uses:
/// "a 32-byte little-endian canonical representation".
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
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 `frost::VerificationKey<SpendAuth>`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// The serialization design specifies that `VerificationKey<SpendAuth>` uses:
/// "a 32-byte little-endian canonical representation".
#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)]
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)]
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)]
pub enum Payload {
SharePackage(SharePackage),
SigningCommitments(SigningCommitments),
SigningPackage(SigningPackage),
SignatureShare(SignatureShare),
AggregateSignature(AggregateSignature),
}

/// The numeric values used to identify each `Payload` variant during serialization.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
#[repr(u8)]
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug)]
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 `MAX_SIGNER_PARTICIPANT_ID`.
Signer(u64),
/// The fixed participant ID for the dealer.
Dealer,
/// The fixed participant ID for the aggregator.
Aggregator,
}

impl From<ParticipantId> for u64 {
fn from(value: ParticipantId) -> u64 {
match value {
ParticipantId::Signer(id) => id,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be id + 1, in order to make 0 invalid? I mean, I'm not really sure I understood the conversion, but I thought that Frost used IDs as 1..=n and serialization used IDs as 0..n.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, i think you are right here and i made the change at 2a87605

What i am unsure is that if we should change some of the related constants (MAX_SIGNER_PARTICIPANT_ID) with this change.

ParticipantId::Dealer => constants::DEALER_PARTICIPANT_ID,
ParticipantId::Aggregator => constants::AGGREGATOR_PARTICIPANT_ID,
}
}
}

/// The data required to serialize `frost::SharePackage`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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 `sign()` function.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// Note: `frost::SharePackage.public` can be calculated from `secret_share`.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
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.value`.
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`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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)]
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`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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)]
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.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
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`.
oxarbitrage marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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)]
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)]
pub struct AggregateSignature {
/// The aggregated group commitment: `Signature<SpendAuth>.r_bytes` returned by `frost::aggregate`
group_commitment: GroupCommitment,
/// A plain Schnorr signature created by summing all the signature shares:
/// `Signature<SpendAuth>.s_bytes` returned by `frost::aggregate`
schnorr_signature: SignatureResponse,
}
Loading