Port frost-ristretto255 to frost-core (#57)

* Start port to frost-core

* Fix Signature from_bytes, frost-ristretto255 README / src/lib.rs doc test

* Move frost-ristretto255 test vector tests to that crate

* Uncomment proptest checks to exercise signature and verifying key (de)serialization

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Deirdre Connolly 2022-06-17 14:54:54 -04:00 committed by GitHub
parent 1d5740f8ec
commit a0bf3c57f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 341 additions and 3295 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -16,7 +16,7 @@ on:
jobs: jobs:
coverage: coverage:
name: Coverage (+nightly) name: Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
@ -29,7 +29,7 @@ jobs:
- uses: actions-rs/toolchain@v1.0.7 - uses: actions-rs/toolchain@v1.0.7
with: with:
toolchain: nightly toolchain: stable
override: true override: true
profile: minimal profile: minimal
components: llvm-tools-preview components: llvm-tools-preview
@ -44,4 +44,4 @@ jobs:
run: cargo llvm-cov --lcov --no-run --output-path lcov.info run: cargo llvm-cov --lcov --no-run --output-path lcov.info
- name: Upload coverage report to Codecov - name: Upload coverage report to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3.1.0

View File

@ -23,7 +23,7 @@ parsers:
# This turns off the extra comment; the coverage %'s are still # This turns off the extra comment; the coverage %'s are still
# reported on the main PR page as check results # reported on the main PR page as check results
comment: false # comment: false
github_checks: github_checks:
annotations: false annotations: false

View File

@ -34,8 +34,6 @@ where
R_bytes[..].copy_from_slice(&bytes.as_ref()[0..R_bytes_len]); R_bytes[..].copy_from_slice(&bytes.as_ref()[0..R_bytes_len]);
println!("{:?}", R_bytes);
let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?;
let mut z_bytes = let mut z_bytes =
@ -43,9 +41,8 @@ where
let z_bytes_len = z_bytes.len(); let z_bytes_len = z_bytes.len();
z_bytes[..].copy_from_slice(&bytes.as_ref()[R_bytes_len..z_bytes_len]); // We extract the exact length of bytes we expect, not just the remaining bytes with `bytes[R_bytes_len..]`
z_bytes[..].copy_from_slice(&bytes.as_ref()[R_bytes_len..R_bytes_len + z_bytes_len]);
println!("{:?}", z_bytes);
let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?;

View File

@ -6,7 +6,7 @@ edition = "2021"
# - Update CHANGELOG.md # - Update CHANGELOG.md
# - Create git tag. # - Create git tag.
version = "0.1.0" version = "0.1.0"
authors = [ "Deirdre Connolly <durumcrustulum@gmail.com>", "Chelsea Komlo <me@chelseakomlo.com>", "Henry de Valence <hdevalence@hdevalence.ca>"] authors = ["Deirdre Connolly <durumcrustulum@gmail.com>", "Chelsea Komlo <me@chelseakomlo.com>"]
readme = "README.md" readme = "README.md"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/ZcashFoundation/frost" repository = "https://github.com/ZcashFoundation/frost"
@ -20,7 +20,8 @@ features = ["nightly"]
[dependencies] [dependencies]
byteorder = "1.4" byteorder = "1.4"
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] } curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
# digest = "0.9" digest = "0.9"
frost-core = { path = "../frost-core" }
hex = { version = "0.4.3", features = ["serde"] } hex = { version = "0.4.3", features = ["serde"] }
rand_core = "0.6" rand_core = "0.6"
serde = { version = "1", optional = true, features = ["derive"] } serde = { version = "1", optional = true, features = ["derive"] }

View File

@ -1,18 +1,12 @@
An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers
of signers (FROST). of signers (FROST).
In addition to the `Signature`, `SigningKey`, `VerificationKey` types, the library also provides
`VerificationKeyBytes`, a [refinement] of a `[u8; 32]` indicating that bytes represent an encoding
of a verification key. This allows the `VerificationKey` type to cache verification checks related to
the verification key encoding.
## Examples ## Examples
Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the
signature: signature:
```rust ```rust
# use std::convert::TryFrom;
use rand::thread_rng; use rand::thread_rng;
use frost_ristretto255::*; use frost_ristretto255::*;
@ -22,25 +16,17 @@ let msg = b"Hello!";
let sk = SigningKey::new(thread_rng()); let sk = SigningKey::new(thread_rng());
let sig = sk.sign(thread_rng(), msg); let sig = sk.sign(thread_rng(), msg);
// Types can be converted to raw byte arrays using From/Into // Types can be converted to raw byte arrays using `from_bytes`/`to_bytes`
let sig_bytes: [u8; 64] = sig.into(); let sig_bytes = sig.to_bytes();
let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); let pk_bytes = VerifyingKey::from(&sk).to_bytes();
// Deserialize and verify the signature. // Deserialize and verify the signature.
let sig: Signature = sig_bytes.into(); let sig = Signature::from_bytes(sig_bytes)?;
assert!( assert!(
VerificationKey::try_from(pk_bytes) VerifyingKey::from_bytes(pk_bytes)
.and_then(|pk| pk.verify(msg, &sig)) .and_then(|pk| pk.verify(msg, &sig))
.is_ok() .is_ok()
); );
# Ok::<(), Error>(())
``` ```
## docs
```shell,no_run
cargo doc --features "nightly" --open
```
[redjubjub]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa
[refinement]: https://en.wikipedia.org/wiki/Refinement_type

View File

@ -1,168 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of frost-ristretto255.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Deirdre Connolly <deirdre@zfnd.org>
// - Henry de Valence <hdevalence@hdevalence.ca>
//! Performs batch Schnorr signature verification on the Ristretto group.
//!
//! Batch verification asks whether *all* signatures in some set are valid,
//! rather than asking whether *each* of them is valid. This allows sharing
//! computations among all signature verifications, performing less work overall
//! at the cost of higher latency (the entire batch must complete), complexity
//! of caller code (which must assemble a batch of signatures across
//! work-items), and loss of the ability to easily pinpoint failing signatures.
use std::convert::TryFrom;
use curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::{Identity, VartimeMultiscalarMul},
};
use rand_core::{CryptoRng, RngCore};
use crate::*;
/// A batch verification item.
///
/// This struct exists to allow batch processing to be decoupled from the
/// lifetime of the message. This is useful when using the batch verification
/// API in an async context.
#[derive(Clone, Debug)]
pub struct Item {
vk_bytes: VerificationKeyBytes,
sig: Signature,
c: Scalar,
}
impl<'msg, M: AsRef<[u8]>> From<(VerificationKeyBytes, Signature, &'msg M)> for Item {
fn from((vk_bytes, sig, msg): (VerificationKeyBytes, Signature, &'msg M)) -> Self {
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::generate_challenge(&sig.R_bytes, &vk_bytes.bytes, msg.as_ref());
Self { vk_bytes, sig, c }
}
}
impl Item {
/// Perform non-batched verification of this `Item`.
///
/// This is useful (in combination with `Item::clone`) for implementing
/// fallback logic when batch verification fails. In contrast to
/// [`VerificationKey::verify`](crate::VerificationKey::verify), which
/// requires borrowing the message data, the `Item` type is unlinked
/// from the lifetime of the message.
#[allow(non_snake_case)]
pub fn verify_single(self) -> Result<(), Error> {
VerificationKey::try_from(self.vk_bytes)
.and_then(|vk| vk.verify_prehashed(&self.sig, self.c))
}
}
#[derive(Default)]
/// A batch verification context.
pub struct Verifier {
/// Signature data queued for verification.
signatures: Vec<Item>,
}
impl Verifier {
/// Construct a new batch verifier.
pub fn new() -> Verifier {
Verifier::default()
}
/// Queue an Item for verification.
pub fn queue<I: Into<Item>>(&mut self, item: I) {
self.signatures.push(item.into());
}
/// Perform batch verification, returning `Ok(())` if all signatures were
/// valid and `Err` otherwise.
///
/// The batch verification equation is:
///
/// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i + [z_i * c_i]VK_i) = 0_G
///
/// which we split out into:
///
/// h_G * -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) =
/// 0_G
///
/// so that we can use multiscalar multiplication speedups.
///
/// where for each signature i,
/// - VK_i is the verification key;
/// - R_i is the signature's R value;
/// - s_i is the signature's s value;
/// - c_i is the hash of the message and other data;
/// - z_i is a random 128-bit Scalar;
/// - h_G is the cofactor of the group;
/// - P_G is the generator of the subgroup;
///
/// As follows elliptic curve scalar multiplication convention,
/// scalar variables are lowercase and group point variables
/// are uppercase. This does not exactly match the RedDSA
/// notation in the [protocol specification §B.1][ps].
///
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify
#[allow(non_snake_case)]
pub fn verify<R: RngCore + CryptoRng>(self, mut rng: R) -> Result<(), Error> {
let n = self.signatures.len();
let mut VK_coeffs = Vec::with_capacity(n);
let mut VKs = Vec::with_capacity(n);
let mut R_coeffs = Vec::with_capacity(self.signatures.len());
let mut Rs = Vec::with_capacity(self.signatures.len());
let mut P_coeff_acc = Scalar::zero();
for item in self.signatures.iter() {
let (z_bytes, R_bytes, c) = (item.sig.z_bytes, item.sig.R_bytes, item.c);
let s = Scalar::from_bytes_mod_order(z_bytes);
let R = {
match CompressedRistretto::from_slice(&R_bytes).decompress() {
Some(point) => point,
None => return Err(Error::InvalidSignature),
}
};
let VK = VerificationKey::try_from(item.vk_bytes.bytes)?.point;
let z = Scalar::random(&mut rng);
let P_coeff = z * s;
P_coeff_acc -= P_coeff;
R_coeffs.push(z);
Rs.push(R);
VK_coeffs.push(Scalar::zero() + (z * c));
VKs.push(VK);
}
use std::iter::once;
let scalars = once(&P_coeff_acc)
.chain(VK_coeffs.iter())
.chain(R_coeffs.iter());
let basepoints = [curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT];
let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter());
let check = RistrettoPoint::vartime_multiscalar_mul(scalars, points);
if check == RistrettoPoint::identity() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
}

View File

@ -1,25 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of redjubjub.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Deirdre Connolly <deirdre@zfnd.org>
// - Henry de Valence <hdevalence@hdevalence.ca>
use thiserror::Error;
/// An error related to RedJubJub signatures.
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum Error {
/// The encoding of a signing key was malformed.
#[error("Malformed signing key encoding.")]
MalformedSigningKey,
/// The encoding of a verification key was malformed.
#[error("Malformed verification key encoding.")]
MalformedVerificationKey,
/// Signature verification failed.
#[error("Invalid signature.")]
InvalidSignature,
}

View File

@ -1,311 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of frost-ristretto255.
// Copyright (c) 2020-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Chelsea H. Komlo <me@chelseakomlo.com>
// - Deirdre Connolly <deirdre@zfnd.org>
// - isis agora lovecruft <isis@patternsinthevoid.net>
//! An implementation of FROST (Flexible Round-Optimized Schnorr Threshold)
//! signatures.
//!
//! If you are interested in deploying FROST, please do not hesitate to consult the FROST authors.
//!
//! This implementation currently only supports key generation using a central
//! dealer. In the future, we will add support for key generation via a DKG,
//! as specified in the FROST paper.
//!
//! Internally, keygen_with_dealer generates keys using Verifiable Secret
//! Sharing, where shares are generated using Shamir Secret Sharing.
use std::{collections::HashMap, convert::TryFrom, fmt, fmt::Debug};
use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar, traits::Identity};
use hex::FromHex;
pub mod keys;
pub mod round1;
pub mod round2;
#[cfg(test)]
mod tests;
use crate::{generate_challenge, Signature, H1, H3};
/// The binding factor, also known as _rho_ (ρ)
///
/// Ensures each signature share is strongly bound to a signing set, specific set
/// of commitments, and a specific message.
///
/// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md>
#[derive(Clone, Debug, PartialEq)]
struct Rho(Scalar);
impl From<&SigningPackage> for Rho {
// [`compute_binding_factor`] in the spec
//
// [`compute_binding_factor`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.4
fn from(signing_package: &SigningPackage) -> Rho {
let preimage = signing_package.rho_preimage();
let binding_factor = H1(&preimage[..]);
Rho(Scalar::from_bytes_mod_order_wide(&binding_factor))
}
}
impl FromHex for Rho {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Self::try_from(bytes),
Err(_) => Err("invalid hex"),
}
}
}
impl TryFrom<[u8; 32]> for Rho {
type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match Scalar::from_canonical_bytes(source) {
Some(scalar) => Ok(Self(scalar)),
None => Err("scalar was not canonically encoded"),
}
}
}
/// Generates the lagrange coefficient for the i'th participant.
fn derive_lagrange_coeff(
signer_index: u16,
signing_package: &SigningPackage,
) -> Result<Scalar, &'static str> {
let signer_index_scalar = Scalar::from(signer_index as u16);
if signer_index_scalar == Scalar::zero() {
return Err("Invalid parameters");
}
if signing_package
.signing_commitments()
.iter()
.any(|commitment| Scalar::from(commitment.index as u16) == Scalar::zero())
{
return Err("Invalid parameters");
}
let mut num = Scalar::one();
let mut den = Scalar::one();
// Ala the sorting of B, just always sort by index in ascending order
//
// https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding
for commitment in signing_package.signing_commitments() {
if commitment.index == signer_index {
continue;
}
let commitment_index_scalar = Scalar::from(commitment.index as u16);
num *= commitment_index_scalar;
den *= commitment_index_scalar - signer_index_scalar;
}
if den == Scalar::zero() {
return Err("Duplicate shares provided");
}
// TODO: handle this unwrap better like other CtOption's
let lagrange_coeff = num * den.invert();
Ok(lagrange_coeff)
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
#[derive(Debug)]
pub struct SigningPackage {
/// The set of commitments participants published in the first round of the
/// protocol.
signing_commitments: HashMap<u16, round1::SigningCommitments>,
/// Message which each participant will sign.
///
/// Each signer should perform protocol-specific verification on the
/// message.
message: Vec<u8>,
}
impl SigningPackage {
/// Create a new `SigingPackage`
///
/// The `signing_commitments` are sorted by participant `index`.
pub fn new(
mut signing_commitments: Vec<round1::SigningCommitments>,
message: Vec<u8>,
) -> SigningPackage {
signing_commitments.sort_by_key(|a| a.index);
SigningPackage {
signing_commitments: signing_commitments
.into_iter()
.map(|s| (s.index, s))
.collect(),
message,
}
}
/// Get a signing commitment by its participant index.
pub fn signing_commitment(&self, index: &u16) -> round1::SigningCommitments {
self.signing_commitments[index]
}
/// Get the signing commitments, sorted by the participant indices
pub fn signing_commitments(&self) -> Vec<round1::SigningCommitments> {
let mut signing_commitments: Vec<round1::SigningCommitments> =
self.signing_commitments.values().cloned().collect();
signing_commitments.sort_by_key(|a| a.index);
signing_commitments
}
/// Get the message to be signed
pub fn message(&self) -> &Vec<u8> {
&self.message
}
/// Compute the preimage to H3 to compute rho
// We separate this out into its own method so it can be tested
pub(super) fn rho_preimage(&self) -> Vec<u8> {
let mut preimage = vec![];
preimage
.extend_from_slice(&round1::encode_group_commitments(self.signing_commitments())[..]);
preimage.extend_from_slice(&H3(self.message.as_slice()));
preimage
}
}
/// The product of all signers' individual commitments, published as part of the
/// final signature.
#[derive(PartialEq)]
pub struct GroupCommitment(pub(super) RistrettoPoint);
impl Debug for GroupCommitment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("GroupCommitment")
.field(&hex::encode(self.0.compress().to_bytes()))
.finish()
}
}
impl TryFrom<&SigningPackage> for GroupCommitment {
type Error = &'static str;
/// Generates the group commitment which is published as part of the joint
/// Schnorr signature.
///
/// Implements [`compute_group_commitment`] from the spec.
///
/// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.4
fn try_from(signing_package: &SigningPackage) -> Result<GroupCommitment, &'static str> {
let rho: Rho = signing_package.into();
let identity = RistrettoPoint::identity();
let mut accumulator = identity;
// Ala the sorting of B, just always sort by index in ascending order
//
// https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding
for commitment in signing_package.signing_commitments() {
// The following check prevents a party from accidentally revealing their share.
// Note that the '&&' operator would be sufficient.
if identity == commitment.binding.0 || identity == commitment.hiding.0 {
return Err("Commitment equals the identity.");
}
accumulator += commitment.hiding.0 + (commitment.binding.0 * rho.0)
}
Ok(GroupCommitment(accumulator))
}
}
////////////////////////////////////////////////////////////////////////////////
// Aggregation
////////////////////////////////////////////////////////////////////////////////
/// Verifies each participant's signature share, and if all are valid,
/// aggregates the shares into a signature to publish.
///
/// Resulting signature is compatible with verification of a plain SpendAuth
/// signature.
///
/// This operation is performed by a coordinator that can communicate with all
/// the signing participants before publishing the final signature. The
/// coordinator can be one of the participants or a semi-trusted third party
/// (who is trusted to not perform denial of service attacks, but does not learn
/// any secret information). Note that because the coordinator is trusted to
/// report misbehaving parties in order to avoid publishing an invalid
/// signature, if the coordinator themselves is a signer and misbehaves, they
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.
pub fn aggregate(
signing_package: &SigningPackage,
signing_shares: &[round2::SignatureShare],
pubkeys: &keys::PublicKeyPackage,
) -> Result<Signature, &'static str> {
// Encodes the signing commitment list produced in round one as part of generating [`Rho`], the
// binding factor.
let rho: Rho = signing_package.into();
// Compute the group commitment from signing commitments produced in round one.
let group_commitment = GroupCommitment::try_from(signing_package)?;
// Compute the per-message challenge.
let challenge = generate_challenge(
&group_commitment.0.compress().to_bytes(),
&pubkeys.group_public.bytes.bytes,
signing_package.message().as_slice(),
);
// Verify the signature shares.
for signing_share in signing_shares {
// Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_,
// and where s[i] is a secret share of the constant term of _f_, the secret polynomial.
let signer_pubkey = pubkeys.signer_pubkeys.get(&signing_share.index).unwrap();
// Compute Lagrange coefficient.
let lambda_i = derive_lagrange_coeff(signing_share.index, signing_package)?;
// Compute the commitment share.
let R_share = signing_package
.signing_commitment(&signing_share.index)
.to_group_commitment_share(&rho);
// Compute relation values to verify this signing share.
signing_share.verify(R_share, signer_pubkey, lambda_i, challenge)?;
}
// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
//
// Implements [`frost_aggregate`] from the spec.
//
// [`frost_aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.3-4
let mut z = Scalar::zero();
for signature_share in signing_shares {
z += signature_share.signature.z_share;
}
Ok(Signature {
R_bytes: group_commitment.0.compress().to_bytes(),
z_bytes: z.to_bytes(),
})
}

View File

@ -1,348 +0,0 @@
//! FROST keys, keygen, key shares
use std::{collections::HashMap, convert::TryFrom, fmt::Debug};
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT, ristretto::RistrettoPoint, scalar::Scalar,
traits::Identity,
};
use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
use zeroize::DefaultIsZeroes;
use crate::VerificationKey;
/// A secret scalar value representing a single signer's secret key.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Secret(pub(super) Scalar);
impl Secret {
/// Generates a new uniformly random secret value using the provided RNG.
pub fn random<R>(rng: &mut R) -> Self
where
R: CryptoRng + RngCore,
{
Self(Scalar::random(rng))
}
}
// Zeroizes `Secret` to be the `Default` value on drop (when it goes out of scope). Luckily the
// derived `Default` includes the `Default` impl of Scalar, which is four 0u64's under the hood.
impl DefaultIsZeroes for Secret {}
impl From<Scalar> for Secret {
fn from(source: Scalar) -> Secret {
Secret(source)
}
}
impl FromHex for Secret {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Secret::try_from(bytes),
Err(_) => Err("invalid hex"),
}
}
}
impl TryFrom<[u8; 32]> for Secret {
type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match Scalar::from_canonical_bytes(source) {
Some(scalar) => Ok(Secret(scalar)),
None => Err("scalar was not canonically encoded"),
}
}
}
/// A public group element that represents a single signer's public key.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Public(pub(super) RistrettoPoint);
impl From<RistrettoPoint> for Public {
fn from(source: RistrettoPoint) -> Public {
Public(source)
}
}
impl From<Secret> for Public {
fn from(secret: Secret) -> Public {
Public(RISTRETTO_BASEPOINT_POINT * secret.0)
}
}
/// A Ristretto 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, Copy, Debug, PartialEq)]
pub(super) struct CoefficientCommitment(pub(super) RistrettoPoint);
/// Contains the commitments to the coefficients for our secret polynomial _f_,
/// used to generate participants' key shares.
///
/// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which
/// themselves are scalars) for a secret polynomial f, where f is used to
/// generate each ith participant's key share f(i). Participants use this set of
/// commitments to perform verifiable secret sharing.
///
/// Note that participants MUST be assured that they have the *same*
/// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using
/// some agreed-upon public location for publication, where each participant can
/// ensure that they received the correct (and same) value.
#[derive(Clone)]
pub struct VerifiableSecretSharingCommitment(pub(super) Vec<CoefficientCommitment>);
/// A secret share generated by performing a (t-out-of-n) secret sharing scheme.
///
/// `n` is the total number of shares and `t` is the threshold required to reconstruct the secret;
/// in this case we use Shamir's secret sharing.
#[derive(Clone)]
pub struct SecretShare {
pub(super) index: u16,
/// Secret Key.
pub(super) value: Secret,
/// The commitments to be distributed among signers.
pub(super) commitment: VerifiableSecretSharingCommitment,
}
impl SecretShare {
/// Verifies that a secret share is consistent with a verifiable secret sharing commitment.
///
/// This ensures that this participant's share has been generated using the same
/// mechanism as all other signing participants. Note that participants *MUST*
/// ensure that they have the same view as all other participants of the
/// commitment!
///
/// An implementation of `vss_verify()` from the [spec].
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#appendix-B.2-4
pub fn verify(&self) -> Result<(), &'static str> {
let f_result = RISTRETTO_BASEPOINT_POINT * self.value.0;
let x = Scalar::from(self.index as u16);
let (_, result) = self.commitment.0.iter().fold(
(Scalar::one(), RistrettoPoint::identity()),
|(x_to_the_i, sum_so_far), comm_i| (x_to_the_i * x, sum_so_far + comm_i.0 * x_to_the_i),
);
if !(f_result == result) {
return Err("SecretShare is invalid.");
}
Ok(())
}
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
///
/// To derive a FROST keypair, the receiver of the [`SharePackage`] *must* call
/// .into(), which under the hood also performs validation.
#[derive(Clone)]
pub struct SharePackage {
/// Denotes the participant index each share is owned by.
pub index: u16,
/// This participant's secret share.
pub(super) secret_share: SecretShare,
/// This participant's public key.
pub public: Public,
/// The public signing key that represents the entire group.
pub group_public: VerificationKey,
}
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
///
/// Under the hood, this performs verifiable secret sharing, which itself uses
/// Shamir secret sharing, from which each share becomes a participant's secret
/// key. The output from this function is a set of shares along with one single
/// commitment that participants use to verify the integrity of the share. The
/// number of signers is limited to 255.
///
/// Implements [`trusted_dealer_keygen`] from the spec.
///
/// [`trusted_dealer_keygen`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B
pub fn keygen_with_dealer<R: RngCore + CryptoRng>(
num_signers: u8,
threshold: u8,
mut rng: R,
) -> Result<(Vec<SharePackage>, PublicKeyPackage), &'static str> {
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
let secret = Secret::random(&mut rng);
let group_public = VerificationKey::from(&secret.0);
let secret_shares = generate_secret_shares(&secret, num_signers, threshold, rng)?;
let mut share_packages: Vec<SharePackage> = Vec::with_capacity(num_signers as usize);
let mut signer_pubkeys: HashMap<u16, Public> = HashMap::with_capacity(num_signers as usize);
for secret_share in secret_shares {
let signer_public = secret_share.value.into();
share_packages.push(SharePackage {
index: secret_share.index,
secret_share: secret_share.clone(),
public: signer_public,
group_public,
});
signer_pubkeys.insert(secret_share.index, signer_public);
}
Ok((
share_packages,
PublicKeyPackage {
signer_pubkeys,
group_public,
},
))
}
/// A FROST keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
/// When using a central dealer, [`SharePackage`]s are distributed to
/// participants, who then perform verification, before deriving
/// [`KeyPackage`]s, which they store to later use during signing.
#[derive(Copy, Clone, Debug)]
pub struct KeyPackage {
/// Denotes the participant index each secret share key package is owned by.
pub index: u16,
/// This participant's secret share.
pub(super) secret_share: Secret,
/// This participant's public key.
pub public: Public,
/// The public signing key that represents the entire group.
pub group_public: VerificationKey,
}
impl TryFrom<SharePackage> for KeyPackage {
type Error = &'static str;
/// Tries to verify a share and construct a [`KeyPackage`] from it.
///
/// When participants receive a [`SharePackage`] from the dealer, they
/// *MUST* verify the integrity of the share before continuing on to
/// transform it into a signing/verification keypair. Here, we assume that
/// every participant has the same view of the commitment issued by the
/// dealer, but implementations *MUST* make sure that all participants have
/// a consistent view of this commitment in practice.
fn try_from(share_package: SharePackage) -> Result<Self, &'static str> {
share_package.secret_share.verify()?;
Ok(KeyPackage {
index: share_package.index,
secret_share: share_package.secret_share.value,
public: share_package.public,
group_public: share_package.group_public,
})
}
}
/// Public data that contains all the signers' public keys as well as the
/// group public key.
///
/// Used for verification purposes before publishing a signature.
pub struct PublicKeyPackage {
/// When performing signing, the coordinator must ensure that they have the
/// correct view of participants' public keys to perform verification before
/// publishing a signature. `signer_pubkeys` represents all signers for a
/// signing operation.
pub(super) signer_pubkeys: HashMap<u16, Public>,
/// The joint public key for the entire group.
pub group_public: VerificationKey,
}
/// Creates secret shares for a given secret.
///
/// This function accepts a secret from which shares are generated. While in
/// FROST this secret should always be generated randomly, we allow this secret
/// to be specified for this internal function for testability.
///
/// Internally, [`generate_secret_shares`] performs verifiable secret sharing, which
/// generates shares via Shamir Secret Sharing, and then generates public
/// commitments to those shares.
///
/// More specifically, [`generate_secret_shares`]:
/// - Randomly samples of coefficients [a, b, c], this represents a secret
/// polynomial f
/// - For each participant i, their secret share is f(i)
/// - The commitment to the secret polynomial f is [g^a, g^b, g^c]
///
/// Implements [`secret_key_shard`] from the spec.
///
/// [`secret_key_shard`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#appendix-B.1
pub fn generate_secret_shares<R: RngCore + CryptoRng>(
secret: &Secret,
numshares: u8,
threshold: u8,
mut rng: R,
) -> Result<Vec<SecretShare>, &'static str> {
if threshold < 2 {
return Err("Threshold cannot be less than 2");
}
if numshares < 2 {
return Err("Number of shares cannot be less than the minimum threshold 2");
}
if threshold > numshares {
return Err("Threshold cannot exceed numshares");
}
let numcoeffs = threshold - 1;
let mut coefficients: Vec<Scalar> = Vec::with_capacity(threshold as usize);
let mut secret_shares: Vec<SecretShare> = Vec::with_capacity(numshares as usize);
let mut commitment: VerifiableSecretSharingCommitment =
VerifiableSecretSharingCommitment(Vec::with_capacity(threshold as usize));
for _ in 0..numcoeffs {
coefficients.push(Scalar::random(&mut rng));
}
// Verifiable secret sharing, to make sure that participants can ensure their
// secret is consistent with every other participant's.
commitment
.0
.push(CoefficientCommitment(RISTRETTO_BASEPOINT_POINT * secret.0));
for c in &coefficients {
commitment
.0
.push(CoefficientCommitment(RISTRETTO_BASEPOINT_POINT * c));
}
// Evaluate the polynomial with `secret` as the constant term
// and `coeffs` as the other coefficients at the point x=share_index,
// using Horner's method.
for index in 1..=numshares {
let scalar_index = Scalar::from(index as u16);
let mut value = Scalar::zero();
// Polynomial evaluation, for this index
for i in (0..numcoeffs).rev() {
value += &coefficients[i as usize];
value *= scalar_index;
}
value += secret.0;
secret_shares.push(SecretShare {
index: index as u16,
value: Secret(value),
commitment: commitment.clone(),
});
}
Ok(secret_shares)
}

View File

@ -1,261 +0,0 @@
//! FROST Round 1 functionality and types
use std::{
convert::TryFrom,
fmt::{self, Debug},
};
use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
};
use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
use zeroize::DefaultIsZeroes;
use crate::frost;
/// A scalar used in Ristretto that is a signing nonce.
#[derive(Clone, Copy, Default, PartialEq)]
pub(super) struct Nonce(pub(super) Scalar);
impl Nonce {
/// Generates a new uniformly random signing nonce.
///
/// Each participant generates signing nonces before performing a signing
/// operation.
///
/// An implementation of `RandomNonzeroScalar()` from the [spec].
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.html#section-3.1-3.4
pub fn random<R>(rng: &mut R) -> Self
where
R: CryptoRng + RngCore,
{
// The values of 'hiding' and 'binding' nonces must be non-zero so that commitments are
// not the identity.
loop {
let scalar = Scalar::random(rng);
if scalar != Scalar::zero() {
return Self(scalar);
}
}
}
}
impl AsRef<Scalar> for Nonce {
fn as_ref(&self) -> &Scalar {
&self.0
}
}
impl Debug for Nonce {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Nonce")
.field(&hex::encode(self.0.to_bytes()))
.finish()
}
}
// Zeroizes `Secret` to be the `Default` value on drop (when it goes out of scope). Luckily the
// derived `Default` includes the `Default` impl of Scalar, which is four 0u64's under the hood.
impl DefaultIsZeroes for Nonce {}
impl FromHex for Nonce {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Self::try_from(bytes),
Err(_) => Err("invalid hex"),
}
}
}
impl TryFrom<[u8; 32]> for Nonce {
type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match Scalar::from_canonical_bytes(source) {
Some(scalar) if scalar != Scalar::zero() => Ok(Self(scalar)),
None => Err("ristretto scalar not canonical byte representation"),
_ => Err("invalid nonce value"),
}
}
}
/// A Ristretto point that is a commitment to a signing nonce share.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct NonceCommitment(pub(super) RistrettoPoint);
impl From<Nonce> for NonceCommitment {
fn from(nonce: Nonce) -> Self {
Self(RISTRETTO_BASEPOINT_POINT * nonce.0)
}
}
impl FromHex for NonceCommitment {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Self::try_from(bytes),
Err(_) => Err("invalid hex"),
}
}
}
impl TryFrom<[u8; 32]> for NonceCommitment {
type Error = &'static str;
fn try_from(source: [u8; 32]) -> Result<Self, &'static str> {
match CompressedRistretto::from_slice(&source[..]).decompress() {
Some(point) => Ok(Self(point)),
None => Err("ristretto point was not canonically encoded"),
}
}
}
/// Comprised of hiding and binding nonces.
///
/// Note that [`SigningNonces`] must be used *only once* for a signing
/// operation; re-using nonces will result in leakage of a signer's long-lived
/// signing key.
#[derive(Clone, Copy, Default, Debug)]
pub struct SigningNonces {
pub(super) hiding: Nonce,
pub(super) binding: Nonce,
}
// Zeroizes `SigningNonces` to be the `Default` value on drop (when it goes out of scope). Luckily
// the derived `Default` includes the `Default` impl of the `curve25519_dalek::scalar::Scalar`s,
// which is 32 0u8's under the hood.
impl DefaultIsZeroes for SigningNonces {}
impl SigningNonces {
/// Generates a new signing nonce.
///
/// Each participant generates signing nonces before performing a signing
/// operation.
pub fn new<R>(rng: &mut R) -> Self
where
R: CryptoRng + RngCore,
{
// The values of 'hiding' and 'binding' must be non-zero so that commitments are
// not the identity.
let hiding = Nonce::random(rng);
let binding = Nonce::random(rng);
Self { hiding, binding }
}
}
/// Published by each participant in the first round of the signing protocol.
///
/// This step can be batched if desired by the implementation. Each
/// SigningCommitment can be used for exactly *one* signature.
#[derive(Copy, Clone, Debug)]
pub struct SigningCommitments {
/// The participant index
pub(super) index: u16,
/// The hiding point.
pub(super) hiding: NonceCommitment,
/// The binding point.
pub(super) binding: NonceCommitment,
}
impl SigningCommitments {
/// Computes the [signature commitment share] from these round one signing commitments.
///
/// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#name-signature-share-verificatio
pub(super) fn to_group_commitment_share(
self,
binding_factor: &frost::Rho,
) -> GroupCommitmentShare {
GroupCommitmentShare(self.hiding.0 + (self.binding.0 * binding_factor.0))
}
}
impl From<(u16, &SigningNonces)> for SigningCommitments {
fn from((index, nonces): (u16, &SigningNonces)) -> Self {
Self {
index,
hiding: nonces.hiding.into(),
binding: nonces.binding.into(),
}
}
}
/// One signer's share of the group commitment, derived from their individual signing commitments
/// and the binding factor _rho_.
#[derive(Clone, Copy, Default, PartialEq)]
pub struct GroupCommitmentShare(pub(super) RistrettoPoint);
/// Encode the list of group signing commitments.
///
/// Implements [`encode_group_commitment_list()`] from the spec.
///
/// Inputs:
/// - commitment_list = [(j, D_j, E_j), ...], a list of commitments issued by each signer,
/// where each element in the list indicates the signer index and their
/// two commitment Element values. B MUST be sorted in ascending order
/// by signer index.
///
/// Outputs:
/// - A byte string containing the serialized representation of B.
///
/// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.3
pub(super) fn encode_group_commitments(signing_commitments: Vec<SigningCommitments>) -> Vec<u8> {
// B MUST be sorted in ascending order by signer index.
//
// https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#encoding-operations-dep-encoding
let mut sorted_signing_commitments = signing_commitments;
sorted_signing_commitments.sort_by_key(|a| a.index);
let mut bytes = vec![];
for item in sorted_signing_commitments {
bytes.extend_from_slice(&item.index.to_be_bytes()[..]);
bytes.extend_from_slice(&item.hiding.0.compress().to_bytes()[..]);
bytes.extend_from_slice(&item.binding.0.compress().to_bytes()[..]);
}
bytes
}
/// Done once by each participant, to generate _their_ nonces and commitments
/// that are then used during signing.
///
/// When performing signing using two rounds, num_nonces would equal 1, to
/// perform the first round. Batching entails generating more than one
/// 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
/// 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
pub fn preprocess<R>(
num_nonces: u8,
participant_index: u16,
rng: &mut R,
) -> (Vec<SigningNonces>, Vec<SigningCommitments>)
where
R: CryptoRng + RngCore,
{
let mut signing_nonces: Vec<SigningNonces> = Vec::with_capacity(num_nonces as usize);
let mut signing_commitments: Vec<SigningCommitments> = Vec::with_capacity(num_nonces as usize);
for _ in 0..num_nonces {
let nonces = SigningNonces::new(rng);
signing_commitments.push(SigningCommitments::from((participant_index, &nonces)));
signing_nonces.push(nonces);
}
(signing_nonces, signing_commitments)
}

View File

@ -1,129 +0,0 @@
//! FROST Round 2 functionality and types, for signature share generation
use std::fmt::{self, Debug};
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar};
use zeroize::DefaultIsZeroes;
use crate::{
frost::{self, round1, *},
generate_challenge,
};
/// A representation of a single signature share used in FROST structures and messages.
#[derive(Clone, Copy, Default, PartialEq)]
pub struct SignatureResponse {
pub(super) z_share: Scalar,
}
impl Debug for SignatureResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SignatureResponse")
.field("z_share", &hex::encode(self.z_share.to_bytes()))
.finish()
}
}
impl From<SignatureResponse> for [u8; 32] {
fn from(sig: SignatureResponse) -> [u8; 32] {
sig.z_share.to_bytes()
}
}
/// A participant's signature share, which the coordinator will aggregate with all other signer's
/// shares into the joint signature.
#[derive(Clone, Copy, Default, PartialEq)]
pub struct SignatureShare {
/// Represents the participant index.
pub(super) index: u16,
/// This participant's signature over the message.
pub(super) signature: SignatureResponse,
}
impl Debug for SignatureShare {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SignatureShare")
.field("index", &self.index)
.field("signature", &self.signature)
.finish()
}
}
// Zeroizes `SignatureShare` to be the `Default` value on drop (when it goes out
// of scope). Luckily the derived `Default` includes the `Default` impl of
// Scalar, which is four 0u64's under the hood, and u32, which is
// 0u32.
impl DefaultIsZeroes for SignatureShare {}
impl SignatureShare {
/// Tests if a signature share issued by a participant is valid before
/// aggregating it into a final joint signature to publish.
///
/// This is the final step of [`verify_signature_share`] from the spec.
///
/// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.3
pub fn verify(
&self,
group_commitment_share: round1::GroupCommitmentShare,
public_key: &frost::keys::Public,
lambda_i: Scalar,
challenge: Scalar,
) -> Result<(), &'static str> {
if (RISTRETTO_BASEPOINT_POINT * self.signature.z_share)
!= (group_commitment_share.0 + (public_key.0 * challenge * lambda_i))
{
return Err("Invalid signature share");
}
Ok(())
}
}
/// Performed once by each participant selected for the signing operation.
///
/// Implements [`sign`] from the spec.
///
/// Receives the message to be signed and a set of signing commitments and a set
/// of randomizing commitments to be used in that signing operation, including
/// that for this participant.
///
/// Assumes the participant has already determined which nonce corresponds with
/// the commitment that was assigned by the coordinator in the SigningPackage.
///
/// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-5.2
pub fn sign(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &frost::keys::KeyPackage,
) -> Result<SignatureShare, &'static str> {
// Encodes the signing commitment list produced in round one as part of generating [`Rho`], the
// binding factor.
let rho: frost::Rho = signing_package.into();
// Compute the group commitment from signing commitments produced in round one.
let group_commitment = GroupCommitment::try_from(signing_package)?;
// Compute Lagrange coefficient.
let lambda_i = frost::derive_lagrange_coeff(key_package.index, signing_package)?;
// Compute the per-message challenge.
let challenge = generate_challenge(
&group_commitment.0.compress().to_bytes(),
&key_package.group_public.bytes.bytes,
signing_package.message.as_slice(),
);
// Compute the Schnorr signature share.
let z_share: Scalar = signer_nonces.hiding.0
+ (signer_nonces.binding.0 * rho.0)
+ (lambda_i * key_package.secret_share.0 * challenge);
let signature_share = SignatureShare {
index: key_package.index,
signature: SignatureResponse { z_share },
};
Ok(signature_share)
}

View File

@ -1,99 +1,259 @@
// -*- mode: rust; -*-
//
// This file is part of redjubjub.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Deirdre Connolly <deirdre@zfnd.org>
// - Henry de Valence <hdevalence@hdevalence.ca>
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![deny(missing_docs)] #![deny(missing_docs)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::{
constants::{BASEPOINT_ORDER, RISTRETTO_BASEPOINT_POINT},
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use rand_core::{CryptoRng, RngCore};
use sha2::{digest::Update, Digest, Sha512}; use sha2::{digest::Update, Digest, Sha512};
pub mod batch; use frost_core::{frost, Ciphersuite, Field, Group};
mod error;
pub mod frost;
// mod messages;
pub(crate) mod signature;
mod signing_key;
mod verification_key;
pub use error::Error; #[cfg(test)]
pub use signature::Signature; mod tests;
pub use signing_key::SigningKey;
pub use verification_key::{VerificationKey, VerificationKeyBytes}; pub use frost_core::Error;
#[derive(Clone, Copy)]
/// An implementation of the FROST ciphersuite scalar field.
pub struct RistrettoScalarField;
impl Field for RistrettoScalarField {
type Scalar = Scalar;
type Serialization = [u8; 32];
fn zero() -> Self::Scalar {
Scalar::zero()
}
fn one() -> Self::Scalar {
Scalar::one()
}
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, Error> {
// [`curve25519_dalek::scalar::Scalar`]'s Eq/PartialEq does a constant-time comparison using
// `ConstantTimeEq`
if *scalar == <Self as Field>::zero() {
Err(Error::InvalidZeroScalar)
} else {
Ok(scalar.invert())
}
}
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
}
fn random_nonzero<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop {
let scalar = Scalar::random(rng);
// This impl of `Eq` calls to `ConstantTimeEq` under the hood
if scalar != Scalar::zero() {
return scalar;
}
}
}
fn serialize(scalar: &Self::Scalar) -> Self::Serialization {
scalar.to_bytes()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, Error> {
match Scalar::from_canonical_bytes(*buf) {
Some(s) => Ok(s),
None => Err(Error::MalformedScalar),
}
}
}
#[derive(Clone, Copy, PartialEq)]
/// An implementation of the FROST ciphersuite group.
pub struct RistrettoGroup;
impl Group for RistrettoGroup {
type Field = RistrettoScalarField;
type Element = RistrettoPoint;
type Serialization = [u8; 32];
fn order() -> <Self::Field as Field>::Scalar {
BASEPOINT_ORDER
}
fn cofactor() -> <Self::Field as Field>::Scalar {
Scalar::one()
}
fn identity() -> Self::Element {
RistrettoPoint::identity()
}
fn generator() -> Self::Element {
RISTRETTO_BASEPOINT_POINT
}
fn serialize(element: &Self::Element) -> Self::Serialization {
element.compress().to_bytes()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, Error> {
match CompressedRistretto::from_slice(buf.as_ref()).decompress() {
Some(point) => Ok(point),
None => Err(Error::MalformedElement),
}
}
}
/// Context string 'FROST-RISTRETTO255-SHA512' from the ciphersuite in the [spec] /// Context string 'FROST-RISTRETTO255-SHA512' from the ciphersuite in the [spec]
/// ///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-01.txt /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-04.txt
const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512"; const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512";
/// H1 for FROST(ristretto255, SHA-512) #[derive(Clone, Copy, PartialEq)]
/// /// An implementation of the FROST ciphersuite Ristretto255-SHA512.
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash pub struct Ristretto255Sha512;
pub(crate) fn H1(m: &[u8]) -> [u8; 64] {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("rho")
.chain(m);
let mut output = [0u8; 64]; impl Ciphersuite for Ristretto255Sha512 {
output.copy_from_slice(h.finalize().as_slice()); type Group = RistrettoGroup;
output
type HashOutput = [u8; 64];
type SignatureSerialization = [u8; 64];
/// H1 for FROST(ristretto255, SHA-512)
///
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("rho")
.chain(m);
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
<<Self::Group as Group>::Field as Field>::Scalar::from_bytes_mod_order_wide(&output)
}
/// H2 for FROST(ristretto255, SHA-512)
///
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("chal")
.chain(m);
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
<<Self::Group as Group>::Field as Field>::Scalar::from_bytes_mod_order_wide(&output)
}
/// H3 for FROST(ristretto255, SHA-512)
///
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash
fn H3(m: &[u8]) -> Self::HashOutput {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("digest")
.chain(m);
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
output
}
} }
/// H2 for FROST(ristretto255, SHA-512) type R = Ristretto255Sha512;
///
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash
pub(crate) fn H2(m: &[u8]) -> [u8; 64] {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("chal")
.chain(m);
let mut output = [0u8; 64]; ///
output.copy_from_slice(h.finalize().as_slice()); pub mod keys {
output use super::*;
///
pub fn keygen_with_dealer<RNG: RngCore + CryptoRng>(
num_signers: u8,
threshold: u8,
mut rng: RNG,
) -> Result<(Vec<SharePackage>, PublicKeyPackage), &'static str> {
frost::keys::keygen_with_dealer(num_signers, threshold, &mut rng)
}
///
pub type SharePackage = frost::keys::SharePackage<R>;
///
pub type KeyPackage = frost::keys::KeyPackage<R>;
///
pub type PublicKeyPackage = frost::keys::PublicKeyPackage<R>;
} }
/// H3 for FROST(ristretto255, SHA-512)
/// ///
/// Yes, this is just an alias for SHA-512. pub mod round1 {
/// use super::*;
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash-function-dep-hash ///
pub(crate) fn H3(m: &[u8]) -> [u8; 64] { pub type SigningNonces = frost::round1::SigningNonces<R>;
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("digest")
.chain(m);
let mut output = [0u8; 64]; ///
output.copy_from_slice(h.finalize().as_slice()); pub type SigningCommitments = frost::round1::SigningCommitments<R>;
output
///
pub fn preprocess<RNG>(
num_nonces: u8,
participant_index: u16,
rng: &mut RNG,
) -> (Vec<SigningNonces>, Vec<SigningCommitments>)
where
RNG: CryptoRng + RngCore,
{
frost::round1::preprocess::<R, RNG>(num_nonces, participant_index, rng)
}
} }
/// Generates the challenge as is required for Schnorr signatures.
/// ///
/// Deals in bytes, so that [FROST] and singleton signing and verification can use it with different pub type SigningPackage = frost::SigningPackage<R>;
/// types.
/// ///
/// This is the only invocation of the H2 hash function from the [RFC]. pub mod round2 {
/// use super::*;
/// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-4.6
/// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-03.html#section-3.2
fn generate_challenge(R_bytes: &[u8; 32], pubkey_bytes: &[u8; 32], msg: &[u8]) -> Scalar {
let mut preimage = vec![];
preimage.extend_from_slice(R_bytes); ///
preimage.extend_from_slice(pubkey_bytes); pub type SignatureShare = frost::round2::SignatureShare<R>;
preimage.extend_from_slice(msg);
let challenge_wide = H2(&preimage[..]); ///
pub type SigningPackage = frost::SigningPackage<R>;
Scalar::from_bytes_mod_order_wide(&challenge_wide) ///
pub fn sign(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
) -> Result<SignatureShare, &'static str> {
frost::round2::sign(&signing_package, signer_nonces, key_package)
}
} }
///
pub type Signature = frost_core::Signature<R>;
///
pub fn aggregate(
signing_package: &round2::SigningPackage,
signature_shares: &[round2::SignatureShare],
pubkeys: &keys::PublicKeyPackage,
) -> Result<Signature, &'static str> {
frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)
}
///
pub type SigningKey = frost_core::SigningKey<R>;
///
pub type VerifyingKey = frost_core::VerifyingKey<R>;

View File

@ -1,296 +0,0 @@
//! The FROST communication messages specified in [RFC-001]
//!
//! [RFC-001]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md
use std::collections::BTreeMap;
use curve25519_dalek::ristretto::CompressedRistretto;
#[cfg(test)]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
#[cfg(test)]
mod arbitrary;
mod constants;
mod serialize;
#[cfg(test)]
mod tests;
mod validate;
use crate::{frost, signature, verification_key};
/// Define our own `Secret` type instead of using [`frost::Secret`].
///
/// The serialization design specifies that `Secret` is a
/// [`curve25519_dalek::scalar::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
/// [`RistrettoPoint`] 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::CoefficientCommitment> for Commitment {
fn from(value: frost::CoefficientCommitment) -> Commitment {
Commitment(value.0.compress().to_bytes())
}
}
impl From<frost::NonceCommitment> for Commitment {
fn from(value: frost::NonceCommitment) -> Commitment {
Commitment(value.0.compress().to_bytes())
}
}
/// Define our own `GroupCommitment` type instead of using
/// [`frost::GroupCommitment`].
///
/// The serialization design specifies that `GroupCommitment` is an
/// [`RistrettoPoint`] 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; 64]);
impl From<signature::Signature> for SignatureResponse {
fn from(value: signature::Signature) -> SignatureResponse {
SignatureResponse(value.into())
}
}
impl From<signature::Signature> for GroupCommitment {
fn from(value: signature::Signature) -> 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> for VerificationKey {
fn from(value: verification_key::VerificationKey) -> VerificationKey {
VerificationKey(<[u8; 32]>::from(value))
}
}
/// The data required to serialize a frost message.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct Message {
header: Header,
payload: Payload,
}
/// The data required to serialize the common header fields for every message.
///
/// Note: the `msg_type` is derived from the `payload` enum variant.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
pub struct Header {
version: MsgVersion,
sender: ParticipantId,
receiver: ParticipantId,
}
/// The data required to serialize the payload for a message.
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
#[cfg_attr(test, derive(Arbitrary))]
pub enum Payload {
SharePackage(SharePackage),
SigningCommitments(SigningCommitments),
SigningPackage(SigningPackage),
SignatureShare(SignatureShare),
AggregateSignature(AggregateSignature),
}
/// The numeric values used to identify each [`Payload`] variant during
/// serialization.
// TODO: spec says `#[repr(u8)]` but it is incompatible with `bincode`
// manual serialization and deserialization is needed.
#[repr(u32)]
#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum MsgType {
SharePackage,
SigningCommitments,
SigningPackage,
SignatureShare,
AggregateSignature,
}
/// The numeric values used to identify the protocol version during
/// serialization.
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)]
pub struct MsgVersion(u8);
/// The numeric values used to identify each participant during serialization.
///
/// In the `frost` module, participant ID `0` should be invalid.
/// But in serialization, we want participants to be indexed from `0..n`,
/// where `n` is the number of participants.
/// This helps us look up their shares and commitments in serialized arrays.
/// So in serialization, we assign the dealer and aggregator the highest IDs,
/// and mark those IDs as invalid for signers.
///
/// "When performing Shamir secret sharing, a polynomial `f(x)` is used to
/// generate each 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(u16),
/// 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 u16 {
fn from(value: ParticipantId) -> u16 {
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: u16::from(*participant_id),
hiding: frost::NonceCommitment(
CompressedRistretto::from_slice(&commitment.hiding.0)
.decompress()
.unwrap(),
),
binding: frost::NonceCommitment(
CompressedRistretto::from_slice(&commitment.binding.0)
.decompress()
.unwrap(),
),
};
signing_commitments.push(s);
}
frost::SigningPackage::new(signing_commitments, 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::z_bytes`] returned by [`frost::aggregate()`]
schnorr_signature: SignatureResponse,
}

View File

@ -1,55 +0,0 @@
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,
sender,
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![
(u16::MIN..=constants::MAX_SIGNER_PARTICIPANT_ID).prop_map(ParticipantId::Signer),
Just(ParticipantId::Dealer),
Just(ParticipantId::Aggregator),
]
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}

View File

@ -1,31 +0,0 @@
//! 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: u16 = u16::MAX - 1;
/// The fixed participant ID for the aggregator.
pub const AGGREGATOR_PARTICIPANT_ID: u16 = u16::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: u16 = u16::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;

View File

@ -1,68 +0,0 @@
//! 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_u16(id)
}
ParticipantId::Dealer => serializer.serialize_u16(DEALER_PARTICIPANT_ID),
ParticipantId::Aggregator => serializer.serialize_u16(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::u16::MIN, std::u16::MAX).as_str(),
)
}
fn visit_u16<E>(self, value: u16) -> Result<Self::Value, E>
where
E: de::Error,
{
// Note: deserialization can't fail, because all values are valid.
if value == DEALER_PARTICIPANT_ID {
Ok(ParticipantId::Dealer)
} else if value == AGGREGATOR_PARTICIPANT_ID {
Ok(ParticipantId::Aggregator)
} else {
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_u16(ParticipantIdVisitor)
}
}

View File

@ -1,2 +0,0 @@
mod integration;
mod prop;

View File

@ -1,815 +0,0 @@
use std::convert::TryFrom;
use rand::thread_rng;
use crate::{
frost,
messages::{
validate::{MsgErr, Validate},
*,
},
verification_key,
};
#[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_share_package() {
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(pubkeys.group_public.bytes).unwrap(),
);
let secret_share = Secret(shares[0].secret_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,
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,
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 u16 + 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_share_package() {
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].secret_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,
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, u16::from(setup.signer1), &mut setup.rng);
let header = create_valid_header(setup.aggregator, setup.signer2);
let payload = Payload::SigningCommitments(SigningCommitments {
hiding: Commitment::from(commitment[0].hiding),
binding: Commitment::from(commitment[0].binding),
});
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, u16::from(setup.signer1), &mut setup.rng);
let header = create_valid_header(setup.aggregator, setup.signer1);
let hiding = Commitment::from(commitment[0].hiding);
let binding = Commitment::from(commitment[0].binding);
let payload = Payload::SigningCommitments(SigningCommitments { hiding, binding });
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::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, u16::from(setup.signer1), &mut setup.rng);
let (_nonce, commitment2) = frost::preprocess(1, u16::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 u16 + 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,
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, u16::from(setup.signer1), &mut setup.rng);
let (_nonce, commitment2) = frost::preprocess(1, u16::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,
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 signers should have this data from `SigningPackage`
let (nonce1, commitment1) = frost::preprocess(1, u16::from(setup.signer1), &mut setup.rng);
let (_nonce2, commitment2) = frost::preprocess(1, u16::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,
message: "hola".as_bytes().to_vec(),
});
// here we get started with the `SignatureShare` message.
let signature_share = frost::sign(
&signing_package,
&nonce1[0],
&commitment1[0],
&frost::KeyPackage::try_from(shares[0].clone()).unwrap(),
)
.unwrap();
// this header is invalid
let header = create_valid_header(setup.aggregator, setup.signer1);
let payload = Payload::SignatureShare(SignatureShare {
signature: signature_share.signature,
});
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, u16::from(setup.signer1), &mut setup.rng);
let (_nonce2, commitment2) = frost::preprocess(1, u16::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,
message: "hola".as_bytes().to_vec(),
});
// here we get started with the `SignatureShare` message.
let signature_share = frost::sign(
&signing_package,
&nonce1[0],
&frost::KeyPackage::try_from(shares[0].clone()).unwrap(),
)
.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,
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, u16::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,
receiver,
})
.expect("always a valid header")
}
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) {
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<u16, 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 {
let (nonce, commitment) = frost::preprocess(1, participant_index as u16, &mut setup.rng);
nonces.insert(participant_index as u16, 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::new(commitments, message);
// 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,
&frost::KeyPackage::try_from(share_package.clone()).unwrap(),
)
.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.secret_share.commitment.0[0]),
)
})
.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::from(commitment.hiding),
binding: Commitment::from(commitment.binding),
};
(participant_id, signing_commitment)
})
.collect()
}

View File

@ -1,15 +0,0 @@
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);
}
}

View File

@ -1,143 +0,0 @@
//! 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,
}

View File

@ -1,60 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of frost-ristretto.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Henry de Valence <hdevalence@hdevalence.ca>
// - Deirdre Connolly <durumcrustulum@gmail.com>
//! Schnorr signatures on the Ristretto group
/// A Schnorr signature on the Ristretto group.
#[derive(Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Signature {
pub(crate) R_bytes: [u8; 32],
pub(crate) z_bytes: [u8; 32],
}
impl std::fmt::Debug for Signature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Signature")
.field("R", &hex::encode(self.R_bytes))
.field("z", &hex::encode(self.z_bytes))
.finish()
}
}
impl From<[u8; 64]> for Signature {
fn from(bytes: [u8; 64]) -> Signature {
let mut R_bytes = [0; 32];
R_bytes.copy_from_slice(&bytes[0..32]);
let mut z_bytes = [0; 32];
z_bytes.copy_from_slice(&bytes[32..64]);
Signature { R_bytes, z_bytes }
}
}
impl From<Signature> for [u8; 64] {
fn from(sig: Signature) -> [u8; 64] {
let mut bytes = [0; 64];
bytes[0..32].copy_from_slice(&sig.R_bytes[..]);
bytes[32..64].copy_from_slice(&sig.z_bytes[..]);
bytes
}
}
impl hex::FromHex for Signature {
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 64];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Ok(Self::from(bytes)),
Err(_) => Err("invalid hex"),
}
}
}

View File

@ -1,115 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of redjubjub.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Deirdre Connolly <deirdre@zfnd.org>
// - Henry de Valence <hdevalence@hdevalence.ca>
use std::convert::{TryFrom, TryInto};
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
use crate::{Error, Signature, VerificationKey};
/// A signing key for a Schnorr signature on the Ristretto group.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "SerdeHelper"))]
#[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))]
pub struct SigningKey {
sk: Scalar,
pk: VerificationKey,
}
impl<'a> From<&'a SigningKey> for VerificationKey {
fn from(sk: &'a SigningKey) -> VerificationKey {
sk.pk
}
}
impl From<SigningKey> for [u8; 32] {
fn from(sk: SigningKey) -> [u8; 32] {
sk.sk.to_bytes()
}
}
impl TryFrom<[u8; 32]> for SigningKey {
type Error = Error;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
match Scalar::from_canonical_bytes(bytes) {
Some(sk) => {
let pk = VerificationKey::from(&sk);
Ok(SigningKey { sk, pk })
}
None => Err(Error::MalformedSigningKey),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct SerdeHelper([u8; 32]);
impl TryFrom<SerdeHelper> for SigningKey {
type Error = Error;
fn try_from(helper: SerdeHelper) -> Result<Self, Self::Error> {
helper.0.try_into()
}
}
impl From<SigningKey> for SerdeHelper {
fn from(sk: SigningKey) -> Self {
Self(sk.into())
}
}
impl SigningKey {
/// Generate a new signing key.
pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> SigningKey {
let sk = {
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
Scalar::from_bytes_mod_order_wide(&bytes)
};
let pk = VerificationKey::from(&sk);
SigningKey { sk, pk }
}
/// Create a signature `msg` using this `SigningKey`.
// Similar to signature::Signer but without boxed errors.
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature {
// Choose a byte sequence uniformly at random of length
// (\ell_H + 128)/8 bytes. For RedJubjub this is (512 + 128)/8 = 80.
let random_bytes = {
let mut bytes = [0; 80];
rng.fill_bytes(&mut bytes);
bytes
};
let mut hasher = Sha512::new();
hasher.update(&random_bytes[..]);
hasher.update(&self.pk.bytes.bytes[..]);
hasher.update(msg);
let mut hash_bytes = [0u8; 64];
hash_bytes.copy_from_slice(hasher.finalize().as_slice());
let nonce = Scalar::from_bytes_mod_order_wide(&hash_bytes);
// XXX: does this need `RistrettoPoint::from_uniform_bytes()` ?
let R_bytes = (RISTRETTO_BASEPOINT_POINT * nonce).compress().to_bytes();
// Generate Schnorr challenge
let c = crate::generate_challenge(&R_bytes, &self.pk.bytes.bytes, msg);
let z_bytes = (nonce + (c * self.sk)).to_bytes();
Signature { R_bytes, z_bytes }
}
}

View File

@ -1,59 +1,18 @@
use curve25519_dalek::{constants::RISTRETTO_BASEPOINT_POINT, scalar::Scalar};
use rand::thread_rng; use rand::thread_rng;
use crate::frost::{self, *}; use crate::*;
mod vectors; mod vectors;
use vectors::*; use vectors::*;
fn reconstruct_secret(
secret_shares: Vec<frost::keys::SecretShare>,
) -> Result<Scalar, &'static str> {
let numshares = secret_shares.len();
if numshares < 1 {
return Err("No secret_shares provided");
}
let mut lagrange_coeffs: Vec<Scalar> = Vec::with_capacity(numshares as usize);
for i in 0..numshares {
let mut num = Scalar::one();
let mut den = Scalar::one();
for j in 0..numshares {
if j == i {
continue;
}
num *= Scalar::from(secret_shares[j].index as u64);
den *= Scalar::from(secret_shares[j].index as u64)
- Scalar::from(secret_shares[i].index as u64);
}
if den == Scalar::zero() {
return Err("Duplicate shares provided");
}
lagrange_coeffs.push(num * den.invert());
}
let mut secret = Scalar::zero();
for i in 0..numshares {
secret += lagrange_coeffs[i] * secret_shares[i].value.0;
}
Ok(secret)
}
/// This is testing that Shamir's secret sharing to compute and arbitrary /// This is testing that Shamir's secret sharing to compute and arbitrary
/// value is working. /// value is working.
#[test] #[test]
fn check_share_generation() { fn check_share_generation_ristretto255_sha512() {
let mut rng = thread_rng(); let mut rng = thread_rng();
let secret = frost::keys::Secret::random(&mut rng); let secret = frost::keys::Secret::<Ristretto255Sha512>::random(&mut rng);
let _ = RISTRETTO_BASEPOINT_POINT * secret.0;
let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap(); let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();
@ -61,7 +20,10 @@ fn check_share_generation() {
assert_eq!(secret_share.verify(), Ok(())); assert_eq!(secret_share.verify(), Ok(()));
} }
assert_eq!(reconstruct_secret(secret_shares).unwrap(), secret.0) assert_eq!(
frost::keys::reconstruct_secret::<Ristretto255Sha512>(secret_shares).unwrap(),
secret
)
} }
#[test] #[test]
@ -76,15 +38,20 @@ fn check_sign_with_test_vectors() {
group_binding_factor_input, group_binding_factor_input,
group_binding_factor, group_binding_factor,
signature_shares, signature_shares,
signature, signature_bytes,
) = parse_test_vectors(); ) = parse_test_vectors();
type R = Ristretto255Sha512;
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Key generation // Key generation
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
for key_package in key_packages.values() { for key_package in key_packages.values() {
assert_eq!(key_package.public, key_package.secret_share.into()); assert_eq!(
*key_package.public(),
frost::keys::Public::from(*key_package.secret_share())
);
} }
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
@ -97,13 +64,13 @@ fn check_sign_with_test_vectors() {
let nonce_commitments = signer_commitments.get(&i).unwrap(); let nonce_commitments = signer_commitments.get(&i).unwrap();
assert_eq!( assert_eq!(
frost::round1::NonceCommitment::from(nonces.hiding), &frost::round1::NonceCommitment::from(nonces.hiding()),
nonce_commitments.hiding nonce_commitments.hiding()
); );
assert_eq!( assert_eq!(
frost::round1::NonceCommitment::from(nonces.binding), &frost::round1::NonceCommitment::from(nonces.binding()),
nonce_commitments.binding nonce_commitments.binding()
); );
} }
@ -112,7 +79,6 @@ fn check_sign_with_test_vectors() {
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
let signer_commitments_vec = signer_commitments let signer_commitments_vec = signer_commitments
.clone()
.into_iter() .into_iter()
.map(|(_, signing_commitments)| signing_commitments) .map(|(_, signing_commitments)| signing_commitments)
.collect(); .collect();
@ -121,30 +87,30 @@ fn check_sign_with_test_vectors() {
assert_eq!(signing_package.rho_preimage(), group_binding_factor_input); assert_eq!(signing_package.rho_preimage(), group_binding_factor_input);
let rho: Rho = (&signing_package).into(); let rho: frost::Rho<R> = (&signing_package).into();
assert_eq!(rho, group_binding_factor); assert_eq!(rho, group_binding_factor);
let mut our_signature_shares: Vec<frost::round2::SignatureShare> = Vec::new(); let mut our_signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
// Each participant generates their signature share // Each participant generates their signature share
for index in signer_nonces.keys() { for index in signer_nonces.keys() {
let key_package = key_packages[index]; let key_package = &key_packages[index];
let nonces = signer_nonces[index]; let nonces = &signer_nonces[index];
// Each participant generates their signature share. // Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, &nonces, &key_package).unwrap(); let signature_share = frost::round2::sign(&signing_package, nonces, key_package).unwrap();
our_signature_shares.push(signature_share); our_signature_shares.push(signature_share);
} }
for sig_share in our_signature_shares.clone() { for sig_share in our_signature_shares.clone() {
assert_eq!(sig_share, signature_shares[&sig_share.index]); assert_eq!(sig_share, signature_shares[sig_share.index()]);
} }
let signer_pubkeys = key_packages let signer_pubkeys = key_packages
.into_iter() .into_iter()
.map(|(i, key_package)| (i, key_package.public)) .map(|(i, key_package)| (i, *key_package.public()))
.collect(); .collect();
let pubkey_package = frost::keys::PublicKeyPackage { let pubkey_package = frost::keys::PublicKeyPackage {
@ -163,7 +129,7 @@ fn check_sign_with_test_vectors() {
&signature_shares &signature_shares
.values() .values()
.cloned() .cloned()
.collect::<Vec<frost::round2::SignatureShare>>(), .collect::<Vec<frost::round2::SignatureShare<R>>>(),
&pubkey_package, &pubkey_package,
); );
@ -172,7 +138,7 @@ fn check_sign_with_test_vectors() {
// Check that the generated signature matches the test vector signature // Check that the generated signature matches the test vector signature
let group_signature = group_signature_result.unwrap(); let group_signature = group_signature_result.unwrap();
assert_eq!(group_signature, signature); assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
// Aggregate the FROST signature from our signature shares // Aggregate the FROST signature from our signature shares
let group_signature_result = let group_signature_result =
@ -183,5 +149,5 @@ fn check_sign_with_test_vectors() {
// Check that the generated signature matches the test vector signature // Check that the generated signature matches the test vector signature
let group_signature = group_signature_result.unwrap(); let group_signature = group_signature_result.unwrap();
assert_eq!(group_signature, signature); assert_eq!(group_signature.to_bytes().to_vec(), signature_bytes);
} }

View File

@ -1,16 +1,17 @@
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use curve25519_dalek::scalar::Scalar; use curve25519_dalek::scalar::Scalar;
use hex::{self, FromHex};
use hex;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde_json::Value; use serde_json::Value;
use crate::{ use frost_core::{
frost::{keys::*, round1::*, round2::*, *}, frost::{keys::*, round1::*, round2::*, *},
Signature, VerificationKey, VerifyingKey,
}; };
use crate::Ristretto255Sha512;
lazy_static! { lazy_static! {
pub static ref RISTRETTO255_SHA512: Value = pub static ref RISTRETTO255_SHA512: Value =
serde_json::from_str(include_str!("vectors.json").trim()) serde_json::from_str(include_str!("vectors.json").trim())
@ -18,24 +19,27 @@ lazy_static! {
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(super) fn parse_test_vectors() -> ( #[allow(dead_code)]
VerificationKey, pub(crate) fn parse_test_vectors() -> (
HashMap<u16, KeyPackage>, VerifyingKey<Ristretto255Sha512>,
HashMap<u16, KeyPackage<Ristretto255Sha512>>,
&'static str, &'static str,
Vec<u8>, Vec<u8>,
HashMap<u16, SigningNonces>, HashMap<u16, SigningNonces<Ristretto255Sha512>>,
HashMap<u16, SigningCommitments>, HashMap<u16, SigningCommitments<Ristretto255Sha512>>,
Vec<u8>, Vec<u8>,
Rho, Rho<Ristretto255Sha512>,
HashMap<u16, SignatureShare>, HashMap<u16, SignatureShare<Ristretto255Sha512>>,
Signature, Vec<u8>, // Signature<Ristretto255Sha512>,
) { ) {
type R = Ristretto255Sha512;
let inputs = &RISTRETTO255_SHA512["inputs"]; let inputs = &RISTRETTO255_SHA512["inputs"];
let message = inputs["message"].as_str().unwrap(); let message = inputs["message"].as_str().unwrap();
let message_bytes = hex::decode(message).unwrap(); let message_bytes = hex::decode(message).unwrap();
let mut key_packages: HashMap<u16, KeyPackage> = HashMap::new(); let mut key_packages: HashMap<u16, KeyPackage<R>> = HashMap::new();
let possible_signers = RISTRETTO255_SHA512["inputs"]["signers"] let possible_signers = RISTRETTO255_SHA512["inputs"]["signers"]
.as_object() .as_object()
@ -43,20 +47,20 @@ pub(super) fn parse_test_vectors() -> (
.iter(); .iter();
let group_public = let group_public =
VerificationKey::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap(); VerifyingKey::<R>::from_hex(inputs["group_public_key"].as_str().unwrap()).unwrap();
for (i, secret_share) in possible_signers { for (i, secret_share) in possible_signers {
let secret = Secret::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap(); let secret = Secret::<R>::from_hex(secret_share["signer_share"].as_str().unwrap()).unwrap();
let signer_public = secret.into(); let signer_public = secret.into();
let key_package = KeyPackage { let key_package = KeyPackage::<R> {
index: u16::from_str(i).unwrap(), index: u16::from_str(i).unwrap(),
secret_share: secret, secret_share: secret,
public: signer_public, public: signer_public,
group_public, group_public,
}; };
key_packages.insert(key_package.index, key_package); key_packages.insert(*key_package.index(), key_package);
} }
// Round one outputs // Round one outputs
@ -71,22 +75,22 @@ pub(super) fn parse_test_vectors() -> (
.unwrap(); .unwrap();
let group_binding_factor = let group_binding_factor =
Rho::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap(); Rho::<R>::from_hex(round_one_outputs["group_binding_factor"].as_str().unwrap()).unwrap();
let mut signer_nonces: HashMap<u16, SigningNonces> = HashMap::new(); let mut signer_nonces: HashMap<u16, SigningNonces<R>> = HashMap::new();
let mut signer_commitments: HashMap<u16, SigningCommitments> = HashMap::new(); let mut signer_commitments: HashMap<u16, SigningCommitments<R>> = HashMap::new();
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() { for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
let index = u16::from_str(i).unwrap(); let index = u16::from_str(i).unwrap();
let signing_nonces = SigningNonces { let signing_nonces = SigningNonces::<R> {
hiding: Nonce::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(), hiding: Nonce::<R>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
binding: Nonce::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(), binding: Nonce::<R>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
}; };
signer_nonces.insert(index, signing_nonces); signer_nonces.insert(index, signing_nonces);
let signing_commitments = SigningCommitments { let signing_commitments = SigningCommitments::<R> {
index, index,
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap()) hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
.unwrap(), .unwrap(),
@ -103,10 +107,10 @@ pub(super) fn parse_test_vectors() -> (
let round_two_outputs = &RISTRETTO255_SHA512["round_two_outputs"]; let round_two_outputs = &RISTRETTO255_SHA512["round_two_outputs"];
let mut signature_shares: HashMap<u16, SignatureShare> = HashMap::new(); let mut signature_shares: HashMap<u16, SignatureShare<R>> = HashMap::new();
for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() { for (i, signer) in round_two_outputs["signers"].as_object().unwrap().iter() {
let signature_share = SignatureShare { let signature_share = SignatureShare::<R> {
index: u16::from_str(i).unwrap(), index: u16::from_str(i).unwrap(),
signature: SignatureResponse { signature: SignatureResponse {
z_share: Scalar::from_canonical_bytes( z_share: Scalar::from_canonical_bytes(
@ -126,7 +130,7 @@ pub(super) fn parse_test_vectors() -> (
let final_output = &RISTRETTO255_SHA512["final_output"]; let final_output = &RISTRETTO255_SHA512["final_output"];
let signature = Signature::from_hex(final_output["sig"].as_str().unwrap()).unwrap(); let signature_bytes = FromHex::from_hex(final_output["sig"].as_str().unwrap()).unwrap();
( (
group_public, group_public,
@ -138,6 +142,6 @@ pub(super) fn parse_test_vectors() -> (
group_binding_factor_input, group_binding_factor_input,
group_binding_factor, group_binding_factor,
signature_shares, signature_shares,
signature, signature_bytes,
) )
} }

View File

@ -1,160 +0,0 @@
// -*- mode: rust; -*-
//
// This file is part of redjubjub.
// Copyright (c) 2019-2021 Zcash Foundation
// See LICENSE for licensing information.
//
// Authors:
// - Deirdre Connolly <deirdre@zfnd.org>
// - Henry de Valence <hdevalence@hdevalence.ca>
use std::convert::{TryFrom, TryInto};
use curve25519_dalek::{
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
traits::Identity,
};
use hex::FromHex;
use crate::{Error, Signature};
/// A refinement type for `[u8; 32]` indicating that the bytes represent an
/// encoding of a verification key for Schnorr signatures over the Ristretto
/// group.
///
/// This is useful for representing a compressed verification key; the
/// [`VerificationKey`] type in this library holds other decompressed state
/// used in signature verification.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VerificationKeyBytes {
pub(crate) bytes: [u8; 32],
}
impl From<[u8; 32]> for VerificationKeyBytes {
fn from(bytes: [u8; 32]) -> VerificationKeyBytes {
VerificationKeyBytes { bytes }
}
}
impl From<VerificationKeyBytes> for [u8; 32] {
fn from(refined: VerificationKeyBytes) -> [u8; 32] {
refined.bytes
}
}
/// A valid verification key for Schnorr signatures over the Ristretto group.
///
/// This type holds decompressed state used in signature verification; if the
/// verification key may not be used immediately, it is probably better to use
/// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`.
///
/// ## Consensus properties
///
/// The `TryFrom<VerificationKeyBytes>` conversion performs the following Zcash
/// consensus rule checks:
///
/// 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(PartialEq, Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))]
#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))]
pub struct VerificationKey {
pub(crate) point: RistrettoPoint,
pub(crate) bytes: VerificationKeyBytes,
}
impl From<VerificationKey> for VerificationKeyBytes {
fn from(pk: VerificationKey) -> VerificationKeyBytes {
pk.bytes
}
}
impl From<VerificationKey> for [u8; 32] {
fn from(pk: VerificationKey) -> [u8; 32] {
pk.bytes.bytes
}
}
impl FromHex for VerificationKey {
type Error = Error;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex, &mut bytes[..]) {
Ok(()) => Self::try_from(bytes),
Err(_) => Err(Error::MalformedVerificationKey),
}
}
}
impl TryFrom<VerificationKeyBytes> for VerificationKey {
type Error = Error;
fn try_from(bytes: VerificationKeyBytes) -> Result<Self, Self::Error> {
// This checks that the encoding is canonical...
match CompressedRistretto::from_slice(&bytes.bytes).decompress() {
Some(point) => Ok(VerificationKey { point, bytes }),
None => Err(Error::MalformedVerificationKey),
}
}
}
impl TryFrom<[u8; 32]> for VerificationKey {
type Error = Error;
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
VerificationKeyBytes::from(bytes).try_into()
}
}
impl VerificationKey {
pub(crate) fn from(s: &Scalar) -> VerificationKey {
let point = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT * s;
let bytes = VerificationKeyBytes {
bytes: point.compress().to_bytes(),
};
VerificationKey { bytes, point }
}
/// Verify a purported `signature` over `msg` made by this verification key.
// This is similar to impl signature::Verifier but without boxed errors
pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> {
self.verify_prehashed(
signature,
crate::generate_challenge(&signature.R_bytes, &self.bytes.bytes, msg),
)
}
/// Verify a purported `signature` with a prehashed challenge.
#[allow(non_snake_case)]
pub(crate) fn verify_prehashed(&self, signature: &Signature, c: Scalar) -> Result<(), Error> {
let R = match CompressedRistretto::from_slice(&signature.R_bytes).decompress() {
Some(point) => point,
None => return Err(Error::InvalidSignature),
};
let s = match Scalar::from_canonical_bytes(signature.z_bytes) {
Some(s) => s,
None => return Err(Error::InvalidSignature),
};
// XXX rewrite as normal double scalar mul
// Verify check is h * ( - s * B + R + c * A) == 0
// h * ( s * B - c * A - R) == 0
//
// For Ristretto, h = 1.
let sB = curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT * s;
let cA = self.point * c;
let check = sB - cA - R;
if check == RistrettoPoint::identity() {
Ok(())
} else {
Err(Error::InvalidSignature)
}
}
}

View File

@ -1,58 +0,0 @@
use rand::thread_rng;
use frost_ristretto255::*;
#[test]
fn batch_verify() {
let mut rng = thread_rng();
let mut batch = batch::Verifier::new();
for _ in 0..32 {
let sk = SigningKey::new(&mut rng);
let vk = VerificationKey::from(&sk);
let msg = b"BatchVerifyTest";
let sig = sk.sign(&mut rng, &msg[..]);
batch.queue((vk.into(), sig, msg));
}
assert!(batch.verify(rng).is_ok());
}
#[test]
fn bad_batch_verify() {
let mut rng = thread_rng();
let bad_index = 4; // must be even
let mut batch = batch::Verifier::new();
let mut items = Vec::new();
for i in 0..32 {
let item: batch::Item = match i % 2 {
0 => {
let sk = SigningKey::new(&mut rng);
let vk = VerificationKey::from(&sk);
let msg = b"BatchVerifyTest";
let sig = if i != bad_index {
sk.sign(&mut rng, &msg[..])
} else {
sk.sign(&mut rng, b"bad")
};
(vk.into(), sig, msg).into()
}
1 => {
let sk = SigningKey::new(&mut rng);
let vk = VerificationKey::from(&sk);
let msg = b"BatchVerifyTest";
let sig = sk.sign(&mut rng, &msg[..]);
(vk.into(), sig, msg).into()
}
_ => unreachable!(),
};
items.push(item.clone());
batch.queue(item);
}
assert!(batch.verify(rng).is_err());
for (i, item) in items.drain(..).enumerate() {
if i != bad_index {
assert!(item.verify_single().is_ok());
} else {
assert!(item.verify_single().is_err());
}
}
}

View File

@ -1,25 +1,28 @@
use std::{collections::HashMap, convert::TryFrom}; use std::{collections::HashMap, convert::TryFrom};
use frost_ristretto255::*;
use rand::thread_rng; use rand::thread_rng;
use frost_ristretto255::frost;
#[test] #[test]
fn check_sign_with_dealer() { fn check_sign_with_dealer() {
let mut rng = thread_rng(); let mut rng = thread_rng();
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////
let numsigners = 5; let numsigners = 5;
let threshold = 3; let threshold = 3;
let (shares, pubkeys) = let (shares, pubkeys) = keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
// Verifies the secret shares from the dealer // Verifies the secret shares from the dealer
let key_packages: Vec<frost::keys::KeyPackage> = shares let key_packages: Vec<keys::KeyPackage> = shares
.into_iter() .into_iter()
.map(|share| frost::keys::KeyPackage::try_from(share).unwrap()) .map(|share| keys::KeyPackage::try_from(share).unwrap())
.collect(); .collect();
let mut nonces: HashMap<u16, Vec<frost::round1::SigningNonces>> = HashMap::new(); let mut nonces: HashMap<u16, Vec<round1::SigningNonces>> = HashMap::new();
let mut commitments: HashMap<u16, Vec<frost::round1::SigningCommitments>> = HashMap::new(); let mut commitments: HashMap<u16, Vec<round1::SigningCommitments>> = HashMap::new();
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant // Round 1: generating nonces and signing commitments for each participant
@ -28,7 +31,7 @@ fn check_sign_with_dealer() {
for participant_index in 1..(threshold + 1) { for participant_index in 1..(threshold + 1) {
// Generate one (1) nonce and one SigningCommitments instance for each // Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_. // participant, up to _threshold_.
let (nonce, commitment) = frost::round1::preprocess(1, participant_index as u16, &mut rng); let (nonce, commitment) = round1::preprocess(1, participant_index as u16, &mut rng);
nonces.insert(participant_index as u16, nonce); nonces.insert(participant_index as u16, nonce);
commitments.insert(participant_index as u16, commitment); commitments.insert(participant_index as u16, commitment);
} }
@ -36,10 +39,10 @@ fn check_sign_with_dealer() {
// This is what the signature aggregator / coordinator needs to do: // This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign // - decide what message to sign
// - take one (unused) commitment per signing participant // - take one (unused) commitment per signing participant
let mut signature_shares: Vec<frost::round2::SignatureShare> = Vec::new(); let mut signature_shares: Vec<round2::SignatureShare> = Vec::new();
let message = "message to sign".as_bytes(); let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().flatten().collect(); let comms = commitments.clone().into_values().flatten().collect();
let signing_package = frost::SigningPackage::new(comms, message.to_vec()); let signing_package = SigningPackage::new(comms, message.to_vec());
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share // Round 2: each participant generates their signature share
@ -51,11 +54,10 @@ fn check_sign_with_dealer() {
.find(|key_package| *participant_index == key_package.index) .find(|key_package| *participant_index == key_package.index)
.unwrap(); .unwrap();
let nonces_to_use = nonces.get(participant_index).unwrap()[0]; let nonces_to_use = &nonces.get(participant_index).unwrap()[0];
// Each participant generates their signature share. // Each participant generates their signature share.
let signature_share = let signature_share = round2::sign(&signing_package, nonces_to_use, key_package).unwrap();
frost::round2::sign(&signing_package, &nonces_to_use, key_package).unwrap();
signature_shares.push(signature_share); signature_shares.push(signature_share);
} }
@ -65,7 +67,7 @@ fn check_sign_with_dealer() {
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares) // Aggregate (also verifies the signature shares)
let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys); let group_signature_res = aggregate(&signing_package, &signature_shares[..], &pubkeys);
assert!(group_signature_res.is_ok()); assert!(group_signature_res.is_ok());

View File

@ -1,17 +1,14 @@
use std::convert::TryFrom; use frost_ristretto255::*;
use proptest::prelude::*; use proptest::prelude::*;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use frost_ristretto255::*;
/// A signature test-case, containing signature data and expected validity. /// A signature test-case, containing signature data and expected validity.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct SignatureCase { struct SignatureCase {
msg: Vec<u8>, msg: Vec<u8>,
sig: Signature, sig: Signature,
pk_bytes: VerificationKeyBytes, vk: VerifyingKey,
invalid_pk_bytes: VerificationKeyBytes, invalid_vk: VerifyingKey,
is_valid: bool, is_valid: bool,
} }
@ -40,13 +37,13 @@ impl SignatureCase {
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self { fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
let sk = SigningKey::new(&mut rng); let sk = SigningKey::new(&mut rng);
let sig = sk.sign(&mut rng, &msg); let sig = sk.sign(&mut rng, &msg);
let pk_bytes = VerificationKey::from(&sk).into(); let vk = VerifyingKey::from(&sk);
let invalid_pk_bytes = VerificationKey::from(&SigningKey::new(&mut rng)).into(); let invalid_vk = VerifyingKey::from(&SigningKey::new(&mut rng));
Self { Self {
msg, msg,
sig, sig,
pk_bytes, vk,
invalid_pk_bytes, invalid_vk,
is_valid: true, is_valid: true,
} }
} }
@ -55,21 +52,17 @@ impl SignatureCase {
fn check(&self) -> bool { fn check(&self) -> bool {
// The signature data is stored in (refined) byte types, but do a round trip // The signature data is stored in (refined) byte types, but do a round trip
// conversion to raw bytes to exercise those code paths. // conversion to raw bytes to exercise those code paths.
let sig = { let _sig = {
let bytes: [u8; 64] = self.sig.into(); let bytes = self.sig.to_bytes();
Signature::from(bytes) Signature::from_bytes(bytes)
};
let pk_bytes = {
let bytes: [u8; 32] = self.pk_bytes.into();
VerificationKeyBytes::from(bytes)
}; };
// Check that the verification key is a valid RedJubjub verification key. // Check that the verification key is a valid key.
let pub_key = VerificationKey::try_from(pk_bytes) let _pub_key = VerifyingKey::from_bytes(self.vk.to_bytes())
.expect("The test verification key to be well-formed."); .expect("The test verification key to be well-formed.");
// Check that signature validation has the expected result. // Check that signature validation has the expected result.
self.is_valid == pub_key.verify(&self.msg, &sig).is_ok() self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok()
} }
fn apply_tweak(&mut self, tweak: &Tweak) { fn apply_tweak(&mut self, tweak: &Tweak) {
@ -82,7 +75,7 @@ impl SignatureCase {
} }
Tweak::ChangePubkey => { Tweak::ChangePubkey => {
// Changing the public key makes the signature invalid. // Changing the public key makes the signature invalid.
self.pk_bytes = self.invalid_pk_bytes; self.vk = self.invalid_vk;
self.is_valid = false; self.is_valid = false;
} }
} }
@ -102,7 +95,7 @@ use rand_core::SeedableRng;
proptest! { proptest! {
#[test] #[test]
fn tweak_signature( fn tweak_signature(
tweaks in prop::collection::vec(tweak_strategy(), (0,5)), tweaks in prop::collection::vec(tweak_strategy(), (0,5)),
rng_seed in prop::array::uniform32(any::<u8>()), rng_seed in prop::array::uniform32(any::<u8>()),
@ -114,17 +107,14 @@ proptest! {
// Create a test case for each signature type. // Create a test case for each signature type.
let msg = b"test message for proptests"; let msg = b"test message for proptests";
let mut binding = SignatureCase::new(&mut rng, msg.to_vec()); let mut sig = SignatureCase::new(&mut rng, msg.to_vec());
let mut spendauth = SignatureCase::new(&mut rng, msg.to_vec());
// Apply tweaks to each case. // Apply tweaks to each case.
for t in &tweaks { for t in &tweaks {
binding.apply_tweak(t); sig.apply_tweak(t);
spendauth.apply_tweak(t);
} }
assert!(binding.check()); assert!(sig.check());
assert!(spendauth.check());
} }