redjubjub/src/messages.rs

270 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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 partys 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,
}