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:
coverage:
name: Coverage (+nightly)
name: Coverage
runs-on: ubuntu-latest
env:
CARGO_INCREMENTAL: 0
@ -29,7 +29,7 @@ jobs:
- uses: actions-rs/toolchain@v1.0.7
with:
toolchain: nightly
toolchain: stable
override: true
profile: minimal
components: llvm-tools-preview
@ -44,4 +44,4 @@ jobs:
run: cargo llvm-cov --lcov --no-run --output-path lcov.info
- 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
# reported on the main PR page as check results
comment: false
# comment: false
github_checks:
annotations: false

View File

@ -34,8 +34,6 @@ where
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 mut z_bytes =
@ -43,9 +41,8 @@ where
let z_bytes_len = z_bytes.len();
z_bytes[..].copy_from_slice(&bytes.as_ref()[R_bytes_len..z_bytes_len]);
println!("{:?}", z_bytes);
// 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]);
let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?;

View File

@ -6,7 +6,7 @@ edition = "2021"
# - Update CHANGELOG.md
# - Create git tag.
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"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ZcashFoundation/frost"
@ -20,7 +20,8 @@ features = ["nightly"]
[dependencies]
byteorder = "1.4"
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"] }
rand_core = "0.6"
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
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
Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the
signature:
```rust
# use std::convert::TryFrom;
use rand::thread_rng;
use frost_ristretto255::*;
@ -22,25 +16,17 @@ let msg = b"Hello!";
let sk = SigningKey::new(thread_rng());
let sig = sk.sign(thread_rng(), msg);
// Types can be converted to raw byte arrays using From/Into
let sig_bytes: [u8; 64] = sig.into();
let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into();
// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes`
let sig_bytes = sig.to_bytes();
let pk_bytes = VerifyingKey::from(&sk).to_bytes();
// Deserialize and verify the signature.
let sig: Signature = sig_bytes.into();
let sig = Signature::from_bytes(sig_bytes)?;
assert!(
VerificationKey::try_from(pk_bytes)
VerifyingKey::from_bytes(pk_bytes)
.and_then(|pk| pk.verify(msg, &sig))
.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)]
#![deny(missing_docs)]
#![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};
pub mod batch;
mod error;
pub mod frost;
// mod messages;
pub(crate) mod signature;
mod signing_key;
mod verification_key;
use frost_core::{frost, Ciphersuite, Field, Group};
pub use error::Error;
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use verification_key::{VerificationKey, VerificationKeyBytes};
#[cfg(test)]
mod tests;
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]
///
/// [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";
/// H1 for FROST(ristretto255, SHA-512)
///
/// [spec]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
pub(crate) fn H1(m: &[u8]) -> [u8; 64] {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("rho")
.chain(m);
#[derive(Clone, Copy, PartialEq)]
/// An implementation of the FROST ciphersuite Ristretto255-SHA512.
pub struct Ristretto255Sha512;
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
output
impl Ciphersuite for Ristretto255Sha512 {
type Group = RistrettoGroup;
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)
///
/// [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);
type R = Ristretto255Sha512;
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
output
///
pub mod keys {
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.
///
/// [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] {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("digest")
.chain(m);
pub mod round1 {
use super::*;
///
pub type SigningNonces = frost::round1::SigningNonces<R>;
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
output
///
pub type SigningCommitments = frost::round1::SigningCommitments<R>;
///
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
/// types.
pub type SigningPackage = frost::SigningPackage<R>;
///
/// This is the only invocation of the H2 hash function from the [RFC].
///
/// [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![];
pub mod round2 {
use super::*;
preimage.extend_from_slice(R_bytes);
preimage.extend_from_slice(pubkey_bytes);
preimage.extend_from_slice(msg);
///
pub type SignatureShare = frost::round2::SignatureShare<R>;
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 crate::frost::{self, *};
use crate::*;
mod 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
/// value is working.
#[test]
fn check_share_generation() {
fn check_share_generation_ristretto255_sha512() {
let mut rng = thread_rng();
let secret = frost::keys::Secret::random(&mut rng);
let _ = RISTRETTO_BASEPOINT_POINT * secret.0;
let secret = frost::keys::Secret::<Ristretto255Sha512>::random(&mut rng);
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!(reconstruct_secret(secret_shares).unwrap(), secret.0)
assert_eq!(
frost::keys::reconstruct_secret::<Ristretto255Sha512>(secret_shares).unwrap(),
secret
)
}
#[test]
@ -76,15 +38,20 @@ fn check_sign_with_test_vectors() {
group_binding_factor_input,
group_binding_factor,
signature_shares,
signature,
signature_bytes,
) = parse_test_vectors();
type R = Ristretto255Sha512;
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////
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();
assert_eq!(
frost::round1::NonceCommitment::from(nonces.hiding),
nonce_commitments.hiding
&frost::round1::NonceCommitment::from(nonces.hiding()),
nonce_commitments.hiding()
);
assert_eq!(
frost::round1::NonceCommitment::from(nonces.binding),
nonce_commitments.binding
&frost::round1::NonceCommitment::from(nonces.binding()),
nonce_commitments.binding()
);
}
@ -112,7 +79,6 @@ fn check_sign_with_test_vectors() {
/////////////////////////////////////////////////////////////////////////////
let signer_commitments_vec = signer_commitments
.clone()
.into_iter()
.map(|(_, signing_commitments)| signing_commitments)
.collect();
@ -121,30 +87,30 @@ fn check_sign_with_test_vectors() {
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);
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
for index in signer_nonces.keys() {
let key_package = key_packages[index];
let nonces = signer_nonces[index];
let key_package = &key_packages[index];
let nonces = &signer_nonces[index];
// 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);
}
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
.into_iter()
.map(|(i, key_package)| (i, key_package.public))
.map(|(i, key_package)| (i, *key_package.public()))
.collect();
let pubkey_package = frost::keys::PublicKeyPackage {
@ -163,7 +129,7 @@ fn check_sign_with_test_vectors() {
&signature_shares
.values()
.cloned()
.collect::<Vec<frost::round2::SignatureShare>>(),
.collect::<Vec<frost::round2::SignatureShare<R>>>(),
&pubkey_package,
);
@ -172,7 +138,7 @@ fn check_sign_with_test_vectors() {
// Check that the generated signature matches the test vector signature
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
let group_signature_result =
@ -183,5 +149,5 @@ fn check_sign_with_test_vectors() {
// Check that the generated signature matches the test vector signature
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 curve25519_dalek::scalar::Scalar;
use hex;
use hex::{self, FromHex};
use lazy_static::lazy_static;
use serde_json::Value;
use crate::{
use frost_core::{
frost::{keys::*, round1::*, round2::*, *},
Signature, VerificationKey,
VerifyingKey,
};
use crate::Ristretto255Sha512;
lazy_static! {
pub static ref RISTRETTO255_SHA512: Value =
serde_json::from_str(include_str!("vectors.json").trim())
@ -18,24 +19,27 @@ lazy_static! {
}
#[allow(clippy::type_complexity)]
pub(super) fn parse_test_vectors() -> (
VerificationKey,
HashMap<u16, KeyPackage>,
#[allow(dead_code)]
pub(crate) fn parse_test_vectors() -> (
VerifyingKey<Ristretto255Sha512>,
HashMap<u16, KeyPackage<Ristretto255Sha512>>,
&'static str,
Vec<u8>,
HashMap<u16, SigningNonces>,
HashMap<u16, SigningCommitments>,
HashMap<u16, SigningNonces<Ristretto255Sha512>>,
HashMap<u16, SigningCommitments<Ristretto255Sha512>>,
Vec<u8>,
Rho,
HashMap<u16, SignatureShare>,
Signature,
Rho<Ristretto255Sha512>,
HashMap<u16, SignatureShare<Ristretto255Sha512>>,
Vec<u8>, // Signature<Ristretto255Sha512>,
) {
type R = Ristretto255Sha512;
let inputs = &RISTRETTO255_SHA512["inputs"];
let message = inputs["message"].as_str().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"]
.as_object()
@ -43,20 +47,20 @@ pub(super) fn parse_test_vectors() -> (
.iter();
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 {
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 key_package = KeyPackage {
let key_package = KeyPackage::<R> {
index: u16::from_str(i).unwrap(),
secret_share: secret,
public: signer_public,
group_public,
};
key_packages.insert(key_package.index, key_package);
key_packages.insert(*key_package.index(), key_package);
}
// Round one outputs
@ -71,22 +75,22 @@ pub(super) fn parse_test_vectors() -> (
.unwrap();
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_commitments: HashMap<u16, SigningCommitments> = HashMap::new();
let mut signer_nonces: HashMap<u16, SigningNonces<R>> = HashMap::new();
let mut signer_commitments: HashMap<u16, SigningCommitments<R>> = HashMap::new();
for (i, signer) in round_one_outputs["signers"].as_object().unwrap().iter() {
let index = u16::from_str(i).unwrap();
let signing_nonces = SigningNonces {
hiding: Nonce::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
binding: Nonce::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
let signing_nonces = SigningNonces::<R> {
hiding: Nonce::<R>::from_hex(signer["hiding_nonce"].as_str().unwrap()).unwrap(),
binding: Nonce::<R>::from_hex(signer["binding_nonce"].as_str().unwrap()).unwrap(),
};
signer_nonces.insert(index, signing_nonces);
let signing_commitments = SigningCommitments {
let signing_commitments = SigningCommitments::<R> {
index,
hiding: NonceCommitment::from_hex(signer["hiding_nonce_commitment"].as_str().unwrap())
.unwrap(),
@ -103,10 +107,10 @@ pub(super) fn parse_test_vectors() -> (
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() {
let signature_share = SignatureShare {
let signature_share = SignatureShare::<R> {
index: u16::from_str(i).unwrap(),
signature: SignatureResponse {
z_share: Scalar::from_canonical_bytes(
@ -126,7 +130,7 @@ pub(super) fn parse_test_vectors() -> (
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,
@ -138,6 +142,6 @@ pub(super) fn parse_test_vectors() -> (
group_binding_factor_input,
group_binding_factor,
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 frost_ristretto255::*;
use rand::thread_rng;
use frost_ristretto255::frost;
#[test]
fn check_sign_with_dealer() {
let mut rng = thread_rng();
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////
let numsigners = 5;
let threshold = 3;
let (shares, pubkeys) =
frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
let (shares, pubkeys) = keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();
// Verifies the secret shares from the dealer
let key_packages: Vec<frost::keys::KeyPackage> = shares
let key_packages: Vec<keys::KeyPackage> = shares
.into_iter()
.map(|share| frost::keys::KeyPackage::try_from(share).unwrap())
.map(|share| keys::KeyPackage::try_from(share).unwrap())
.collect();
let mut nonces: HashMap<u16, Vec<frost::round1::SigningNonces>> = HashMap::new();
let mut commitments: HashMap<u16, Vec<frost::round1::SigningCommitments>> = HashMap::new();
let mut nonces: HashMap<u16, Vec<round1::SigningNonces>> = HashMap::new();
let mut commitments: HashMap<u16, Vec<round1::SigningCommitments>> = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// 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) {
// Generate one (1) nonce and one SigningCommitments instance for each
// 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);
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:
// - decide what message to sign
// - 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 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
@ -51,11 +54,10 @@ fn check_sign_with_dealer() {
.find(|key_package| *participant_index == key_package.index)
.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.
let signature_share =
frost::round2::sign(&signing_package, &nonces_to_use, key_package).unwrap();
let signature_share = round2::sign(&signing_package, nonces_to_use, key_package).unwrap();
signature_shares.push(signature_share);
}
@ -65,7 +67,7 @@ fn check_sign_with_dealer() {
////////////////////////////////////////////////////////////////////////////
// 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());

View File

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