add Pallas and Jubjub ciphersuites and FROST support (#33)

* clippy fixes; remove old FROST code

* add RedPallas ciphersuite and FROST support

* organized code

* simplified version

* remove randomized_frost; point to frost-randomized crate

* move rerandomized test to frost-rerandomized; clean up dependencies; add 'frost' feature

* remove stale comment

* add Jubjub support

* add torsion and identity checks where needed; tests

* Apply suggestions from code review

Co-authored-by: Marek <mail@marek.onl>

* Apply suggestions from code review

Co-authored-by: Marek <mail@marek.onl>

* change Jubjub serialize() to use to_bytes(); add comment to Pallas serialize()

* update frost-rerandomized version

* unpin nightly Rust in coverage.yaml

* fix conditional hex dependency

* move FROST code inside frost folder

* Apply suggestions from code review

Co-authored-by: Marek <mail@marek.onl>

---------

Co-authored-by: Marek <mail@marek.onl>
Co-authored-by: Deirdre Connolly <deirdre@zfnd.org>
This commit is contained in:
Conrado Gouvea 2023-02-27 20:01:50 -03:00 committed by GitHub
parent 507dcdf695
commit 08bb408846
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1509 additions and 3 deletions

View File

@ -31,6 +31,7 @@ pasta_curves = { version = "0.4", default-features = false }
rand_core = { version = "0.6", default-features = false }
serde = { version = "1", optional = true, features = ["derive"] }
thiserror = { version = "1.0", optional = true }
frost-rerandomized = { git = "https://github.com/ZcashFoundation/frost.git", rev = "ffe5c57a1729c933b3ec8766ec96d2e6976a7ece", optional = true }
[dependencies.zeroize]
version = "1"
@ -40,12 +41,14 @@ optional = true
[dev-dependencies]
bincode = "1"
criterion = "0.4"
hex = "0.4.3"
proptest-derive = "0.3"
lazy_static = "1.4"
proptest = "1.0"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"
frost-rerandomized = { git = "https://github.com/ZcashFoundation/frost.git", rev = "ffe5c57a1729c933b3ec8766ec96d2e6976a7ece", features=["test-impl"] }
# `alloc` is only used in test code
[dev-dependencies.pasta_curves]
@ -53,9 +56,10 @@ features = ["alloc"]
[features]
std = ["blake2b_simd/std", "thiserror", "zeroize", "alloc",
"serde"] # conditional compilation for serde not complete (issue #9)
"serde", "frost-rerandomized"] # conditional compilation for serde not complete (issue #9)
alloc = ["hex"]
nightly = []
frost = ["frost-rerandomized"]
default = ["std"]
[[bench]]

4
src/frost.rs Normal file
View File

@ -0,0 +1,4 @@
//! RedDSA with FROST.
pub mod redjubjub;
pub mod redpallas;

406
src/frost/redjubjub.rs Normal file
View File

@ -0,0 +1,406 @@
//! Rerandomized FROST with Jubjub curve.
#![allow(non_snake_case)]
#![deny(missing_docs)]
use group::GroupEncoding;
#[cfg(feature = "alloc")]
use group::{ff::Field as FFField, ff::PrimeField};
use frost_rerandomized::{
frost_core::{frost, Ciphersuite, Field, FieldError, Group, GroupError},
RandomizedParams,
};
use rand_core::{CryptoRng, RngCore};
use crate::{hash::HStar, private::Sealed, sapling};
/// An error type for the FROST(Jubjub, BLAKE2b-512) ciphersuite.
pub type Error = frost_rerandomized::frost_core::Error<JubjubBlake2b512>;
/// An implementation of the FROST(Jubjub, BLAKE2b-512) ciphersuite scalar field.
#[derive(Clone, Copy)]
pub struct JubjubScalarField;
impl Field for JubjubScalarField {
type Scalar = jubjub::Scalar;
type Serialization = [u8; 32];
fn zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn one() -> Self::Scalar {
Self::Scalar::one()
}
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, FieldError> {
// [`Jubjub::Scalar`]'s Eq/PartialEq does a constant-time comparison using
// `ConstantTimeEq`
if *scalar == <Self as Field>::zero() {
Err(FieldError::InvalidZeroScalar)
} else {
Ok(Self::Scalar::invert(scalar).unwrap())
}
}
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Self::Scalar::random(rng)
}
fn serialize(scalar: &Self::Scalar) -> Self::Serialization {
scalar.to_bytes()
}
fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
Self::serialize(scalar)
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError> {
match Self::Scalar::from_repr(*buf).into() {
Some(s) => Ok(s),
None => Err(FieldError::MalformedScalar),
}
}
}
/// An implementation of the FROST(Jubjub, BLAKE2b-512) ciphersuite group.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct JubjubGroup;
impl Group for JubjubGroup {
type Field = JubjubScalarField;
type Element = jubjub::ExtendedPoint;
type Serialization = [u8; 32];
fn cofactor() -> <Self::Field as Field>::Scalar {
Self::Field::one()
}
fn identity() -> Self::Element {
Self::Element::identity()
}
fn generator() -> Self::Element {
sapling::SpendAuth::basepoint()
}
fn serialize(element: &Self::Element) -> Self::Serialization {
element.to_bytes()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError> {
let point = Self::Element::from_bytes(buf);
match Option::<Self::Element>::from(point) {
Some(point) => {
if point == Self::identity() {
Err(GroupError::InvalidIdentityElement)
} else if point.is_torsion_free().into() {
Ok(point)
} else {
Err(GroupError::InvalidNonPrimeOrderElement)
}
}
None => Err(GroupError::MalformedElement),
}
}
}
/// An implementation of the FROST(Jubjub, BLAKE2b-512) ciphersuite.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct JubjubBlake2b512;
impl Ciphersuite for JubjubBlake2b512 {
type Group = JubjubGroup;
type HashOutput = [u8; 64];
type SignatureSerialization = [u8; 64];
/// H1 for FROST(Jubjub, BLAKE2b-512)
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
HStar::<sapling::SpendAuth>::new(b"FROST_RedJubjubR")
.update(m)
.finalize()
}
/// H2 for FROST(Jubjub, BLAKE2b-512)
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
HStar::<sapling::SpendAuth>::default().update(m).finalize()
}
/// H3 for FROST(Jubjub, BLAKE2b-512)
fn H3(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
HStar::<sapling::SpendAuth>::new(b"FROST_RedJubjubN")
.update(m)
.finalize()
}
/// H4 for FROST(Jubjub, BLAKE2b-512)
fn H4(m: &[u8]) -> Self::HashOutput {
let mut state = blake2b_simd::Params::new()
.hash_length(64)
.personal(b"FROST_RedJubjubM")
.to_state();
*state.update(m).finalize().as_array()
}
/// H5 for FROST(Jubjub, BLAKE2b-512)
fn H5(m: &[u8]) -> Self::HashOutput {
let mut state = blake2b_simd::Params::new()
.hash_length(64)
.personal(b"FROST_RedJubjubC")
.to_state();
*state.update(m).finalize().as_array()
}
/// HDKG for FROST(Jubjub, BLAKE2b-512)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(
HStar::<sapling::SpendAuth>::new(b"FROST_RedJubjubD")
.update(m)
.finalize(),
)
}
}
// Shorthand alias for the ciphersuite
type J = JubjubBlake2b512;
/// A FROST(Jubjub, BLAKE2b-512) participant identifier.
pub type Identifier = frost::Identifier<J>;
/// FROST(Jubjub, BLAKE2b-512) keys, key generation, key shares.
pub mod keys {
use alloc::vec::Vec;
use super::*;
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
pub fn keygen_with_dealer<RNG: RngCore + CryptoRng>(
max_signers: u16,
min_signers: u16,
mut rng: RNG,
) -> Result<(Vec<SecretShare>, PublicKeyPackage), Error> {
frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
///
/// # Security
///
/// To derive a FROST(Jubjub, BLAKE2b-512) keypair, the receiver of the [`SecretShare`] *must* call
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<J>;
/// A FROST(Jubjub, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
/// When using a central dealer, [`SecretShare`]s are distributed to
/// participants, who then perform verification, before deriving
/// [`KeyPackage`]s, which they store to later use during signing.
pub type KeyPackage = frost::keys::KeyPackage<J>;
/// 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 type PublicKeyPackage = frost::keys::PublicKeyPackage<J>;
pub mod dkg {
#![doc = include_str!("./redjubjub/dkg.md")]
use super::*;
/// The secret package that must be kept in memory by the participant
/// between the first and second parts of the DKG protocol (round 1).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type Round1SecretPackage = frost::keys::dkg::Round1SecretPackage<J>;
/// The package that must be broadcast by each participant to all other participants
/// between the first and second parts of the DKG protocol (round 1).
pub type Round1Package = frost::keys::dkg::Round1Package<J>;
/// The secret package that must be kept in memory by the participant
/// between the second and third parts of the DKG protocol (round 2).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type Round2SecretPackage = frost::keys::dkg::Round2SecretPackage<J>;
/// A package that must be sent by each participant to some other participants
/// in Round 2 of the DKG protocol. Note that there is one specific package
/// for each specific recipient, in contrast to Round 1.
///
/// # Security
///
/// The package must be sent on an *confidential* and *authenticated* channel.
pub type Round2Package = frost::keys::dkg::Round2Package<J>;
/// Performs the first part of the distributed key generation protocol
/// for the given participant.
///
/// It returns the [`Round1SecretPackage`] that must be kept in memory
/// by the participant for the other steps, and the [`Round1Package`] that
/// must be sent to other participants.
pub fn keygen_part1<R: RngCore + CryptoRng>(
identifier: Identifier,
max_signers: u16,
min_signers: u16,
mut rng: R,
) -> Result<(Round1SecretPackage, Round1Package), Error> {
frost::keys::dkg::keygen_part1(identifier, max_signers, min_signers, &mut rng)
}
/// Performs the second part of the distributed key generation protocol
/// for the participant holding the given [`Round1SecretPackage`],
/// given the received [`Round1Package`]s received from the other participants.
///
/// It returns the [`Round2SecretPackage`] that must be kept in memory
/// by the participant for the final step, and the [`Round2Package`]s that
/// must be sent to other participants.
pub fn keygen_part2(
secret_package: Round1SecretPackage,
round1_packages: &[Round1Package],
) -> Result<(Round2SecretPackage, Vec<Round2Package>), Error> {
frost::keys::dkg::keygen_part2(secret_package, round1_packages)
}
/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`Round2SecretPackage`],
/// given the received [`Round1Package`]s and [`Round2Package`]s received from
/// the other participants.
///
/// It returns the [`KeyPackage`] that has the long-lived key share for the
/// participant, and the [`PublicKeyPackage`]s that has public information
/// about all participants; both of which are required to compute FROST
/// signatures.
pub fn keygen_part3(
round2_secret_package: &Round2SecretPackage,
round1_packages: &[Round1Package],
round2_packages: &[Round2Package],
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::keygen_part3(round2_secret_package, round1_packages, round2_packages)
}
}
}
/// FROST(Jubjub, BLAKE2b-512) Round 1 functionality and types.
pub mod round1 {
use frost_rerandomized::frost_core::frost::keys::SigningShare;
use super::*;
/// Comprised of FROST(Jubjub, BLAKE2b-512) 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.
pub type SigningNonces = frost::round1::SigningNonces<J>;
/// 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.
pub type SigningCommitments = frost::round1::SigningCommitments<J>;
/// Performed once by each participant selected for the signing operation.
///
/// Generates the signing nonces and commitments to be used in the signing
/// operation.
pub fn commit<RNG>(
participant_identifier: frost::Identifier<J>,
secret: &SigningShare<J>,
rng: &mut RNG,
) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<J, RNG>(participant_identifier, secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<J>;
/// FROST(Jubjub, BLAKE2b-512) Round 2 functionality and types, for signature share generation.
pub mod round2 {
use super::*;
/// A FROST(Jubjub, BLAKE2b-512) participant's signature share, which the Coordinator will aggregate with all other signer's
/// shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<J>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<J>;
/// Performed once by each participant selected for the signing operation.
///
/// 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.
pub fn sign(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
randomizer_point: &<<J as Ciphersuite>::Group as Group>::Element,
) -> Result<SignatureShare, Error> {
frost_rerandomized::sign(
signing_package,
signer_nonces,
key_package,
randomizer_point,
)
}
}
/// A Schnorr signature on FROST(Jubjub, BLAKE2b-512).
pub type Signature = frost_rerandomized::frost_core::Signature<J>;
/// Verifies each FROST(Jubjub, BLAKE2b-512) 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 Schnorr
/// 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: &round2::SigningPackage,
signature_shares: &[round2::SignatureShare],
pubkeys: &keys::PublicKeyPackage,
randomized_params: &RandomizedParams<J>,
) -> Result<Signature, Error> {
frost_rerandomized::aggregate(
signing_package,
signature_shares,
pubkeys,
randomized_params,
)
}
/// A signing key for a Schnorr signature on FROST(Jubjub, BLAKE2b-512).
pub type SigningKey = frost_rerandomized::frost_core::SigningKey<J>;
/// A valid verifying key for Schnorr signatures on FROST(Jubjub, BLAKE2b-512).
pub type VerifyingKey = frost_rerandomized::frost_core::VerifyingKey<J>;

View File

@ -0,0 +1,98 @@
An implementation of Schnorr signatures on the Jubjub curve for both single and threshold numbers
of signers (FROST).
## Example: key generation with trusted dealer and FROST signing
Creating a key with a trusted dealer and splitting into shares; then signing a message
and aggregating the signature. Note that the example just simulates a distributed
scenario in a single thread and it abstracts away any communication between peers.
```rust
use reddsa::frost::redjubjub as frost;
use rand::thread_rng;
use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?;
// Verifies the secret shares from the dealer and stores them in a HashMap.
// In practice, each KeyPackage must be sent to its respective participant
// through a confidential and authenticated channel.
let key_packages: HashMap<_, _> = shares
.into_iter()
.map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?)))
.collect::<Result<_, _>>()?;
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
&mut rng,
);
// In practice, the nonces and commitments must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
}
// 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::new();
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticated channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
signature_shares.push(signature_share);
}
////////////////////////////////////////////////////////////////////////////
// Aggregation: collects the signing shares from all participants,
// generates the final signature.
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
.group_public
.verify(message, &group_signature)
.is_ok());
# Ok::<(), frost::Error>(())
```

157
src/frost/redjubjub/dkg.md Normal file
View File

@ -0,0 +1,157 @@
# Distributed Key Generation (DKG)
The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.
Before starting, each participant needs a unique identifier, which can be built from
a `u16`. The process in which these identifiers are allocated is up to the application.
The distributed key generation process has 3 parts, with 2 communication rounds
between them, in which each participant needs to send a "package" to every other
participant. In the first round, each participant sends the same package
(a [`Round1Package`]) to every other. In the second round, each receiver gets
their own package (a [`Round2Package`]).
Between part 1 and 2, each participant needs to hold onto a [`Round1SecretPackage`]
that MUST be kept secret. Between part 2 and 3, each participant needs to hold
onto a [`Round2SecretPackage`].
After the third part, each participant will get a [`KeyPackage`] with their
long-term secret share that must be kept secret, and a [`PublicKeyPackage`]
that is public (and will be the same between all participants). With those
they can proceed to sign messages with FROST.
## Example
```rust
use rand::thread_rng;
use std::collections::HashMap;
use reddsa::frost::redjubjub as frost;
let mut rng = thread_rng();
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round1_secret_packages = HashMap::new();
// Keep track of all round 1 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round1_packages = HashMap::new();
// For each participant, perform the first part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::keygen_part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
// sent through some communication channel.
for receiver_participant_index in 1..=max_signers {
if receiver_participant_index == participant_index {
continue;
}
let receiver_participant_identifier: frost::Identifier = receiver_participant_index
.try_into()
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(Vec::new)
.push(round1_package.clone());
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 2
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's round 2 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round2_secret_packages = HashMap::new();
// Keep track of all round 2 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round2_packages = HashMap::new();
// For each participant, perform the second part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::keygen_part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round2_secret_packages.insert(participant_identifier, round2_secret_package);
// "Send" the round 2 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for round2_package in round2_packages {
received_round2_packages
.entry(round2_package.receiver_identifier)
.or_insert_with(Vec::new)
.push(round2_package);
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, final computation
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's long-lived key package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut key_packages = HashMap::new();
// Keep track of each participant's public key package.
// In practice, if there is a Coordinator, only they need to store the set.
// If there is not, then all candidates must store their own sets.
// All participants will have the same exact public key package.
let mut pubkey_packages = HashMap::new();
// For each participant, perform the third part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::keygen_part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
)?;
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
}
// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```

408
src/frost/redpallas.rs Normal file
View File

@ -0,0 +1,408 @@
//! Rerandomized FROST with Pallas curve.
#![allow(non_snake_case)]
#![deny(missing_docs)]
use group::GroupEncoding;
#[cfg(feature = "alloc")]
use group::{ff::Field as FFField, ff::PrimeField, Group as FFGroup};
use pasta_curves::pallas;
use frost_rerandomized::{
frost_core::{frost, Ciphersuite, Field, FieldError, Group, GroupError},
RandomizedParams,
};
use rand_core::{CryptoRng, RngCore};
use crate::{hash::HStar, orchard, private::Sealed};
/// An error type for the FROST(Pallas, BLAKE2b-512) ciphersuite.
pub type Error = frost_rerandomized::frost_core::Error<PallasBlake2b512>;
/// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite scalar field.
#[derive(Clone, Copy)]
pub struct PallasScalarField;
impl Field for PallasScalarField {
type Scalar = pallas::Scalar;
type Serialization = [u8; 32];
fn zero() -> Self::Scalar {
Self::Scalar::zero()
}
fn one() -> Self::Scalar {
Self::Scalar::one()
}
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, FieldError> {
// [`pallas::Scalar`]'s Eq/PartialEq does a constant-time comparison using
// `ConstantTimeEq`
if *scalar == <Self as Field>::zero() {
Err(FieldError::InvalidZeroScalar)
} else {
Ok(Self::Scalar::invert(scalar).unwrap())
}
}
fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Self::Scalar::random(rng)
}
fn serialize(scalar: &Self::Scalar) -> Self::Serialization {
// to_repr() endianess is implementation-specific, but this is OK since
// it is specific to [`pallas::Scalar`] which uses little-endian and that
// is what we want.
scalar.to_repr()
}
fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
Self::serialize(scalar)
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError> {
match pallas::Scalar::from_repr(*buf).into() {
Some(s) => Ok(s),
None => Err(FieldError::MalformedScalar),
}
}
}
/// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite group.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PallasGroup;
impl Group for PallasGroup {
type Field = PallasScalarField;
type Element = pallas::Point;
type Serialization = [u8; 32];
fn cofactor() -> <Self::Field as Field>::Scalar {
Self::Field::one()
}
fn identity() -> Self::Element {
Self::Element::identity()
}
fn generator() -> Self::Element {
orchard::SpendAuth::basepoint()
}
fn serialize(element: &Self::Element) -> Self::Serialization {
element.to_bytes()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError> {
let point = Self::Element::from_bytes(buf);
match Option::<Self::Element>::from(point) {
Some(point) => {
if point == Self::identity() {
Err(GroupError::InvalidIdentityElement)
} else {
Ok(point)
}
}
None => Err(GroupError::MalformedElement),
}
}
}
/// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct PallasBlake2b512;
impl Ciphersuite for PallasBlake2b512 {
type Group = PallasGroup;
type HashOutput = [u8; 64];
type SignatureSerialization = [u8; 64];
/// H1 for FROST(Pallas, BLAKE2b-512)
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
HStar::<orchard::SpendAuth>::new(b"FROST_RedPallasR")
.update(m)
.finalize()
}
/// H2 for FROST(Pallas, BLAKE2b-512)
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
HStar::<orchard::SpendAuth>::default().update(m).finalize()
}
/// H3 for FROST(Pallas, BLAKE2b-512)
fn H3(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
HStar::<orchard::SpendAuth>::new(b"FROST_RedPallasN")
.update(m)
.finalize()
}
/// H4 for FROST(Pallas, BLAKE2b-512)
fn H4(m: &[u8]) -> Self::HashOutput {
let mut state = blake2b_simd::Params::new()
.hash_length(64)
.personal(b"FROST_RedPallasM")
.to_state();
*state.update(m).finalize().as_array()
}
/// H5 for FROST(Pallas, BLAKE2b-512)
fn H5(m: &[u8]) -> Self::HashOutput {
let mut state = blake2b_simd::Params::new()
.hash_length(64)
.personal(b"FROST_RedPallasC")
.to_state();
*state.update(m).finalize().as_array()
}
/// HDKG for FROST(Pallas, BLAKE2b-512)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(
HStar::<orchard::SpendAuth>::new(b"FROST_RedPallasD")
.update(m)
.finalize(),
)
}
}
// Shorthand alias for the ciphersuite
type P = PallasBlake2b512;
/// A FROST(Pallas, BLAKE2b-512) participant identifier.
pub type Identifier = frost::Identifier<P>;
/// FROST(Pallas, BLAKE2b-512) keys, key generation, key shares.
pub mod keys {
use alloc::vec::Vec;
use super::*;
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
pub fn keygen_with_dealer<RNG: RngCore + CryptoRng>(
max_signers: u16,
min_signers: u16,
mut rng: RNG,
) -> Result<(Vec<SecretShare>, PublicKeyPackage), Error> {
frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
///
/// # Security
///
/// To derive a FROST(Pallas, BLAKE2b-512) keypair, the receiver of the [`SecretShare`] *must* call
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<P>;
/// A FROST(Pallas, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
/// When using a central dealer, [`SecretShare`]s are distributed to
/// participants, who then perform verification, before deriving
/// [`KeyPackage`]s, which they store to later use during signing.
pub type KeyPackage = frost::keys::KeyPackage<P>;
/// 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 type PublicKeyPackage = frost::keys::PublicKeyPackage<P>;
pub mod dkg {
#![doc = include_str!("./redpallas/dkg.md")]
use super::*;
/// The secret package that must be kept in memory by the participant
/// between the first and second parts of the DKG protocol (round 1).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type Round1SecretPackage = frost::keys::dkg::Round1SecretPackage<P>;
/// The package that must be broadcast by each participant to all other participants
/// between the first and second parts of the DKG protocol (round 1).
pub type Round1Package = frost::keys::dkg::Round1Package<P>;
/// The secret package that must be kept in memory by the participant
/// between the second and third parts of the DKG protocol (round 2).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type Round2SecretPackage = frost::keys::dkg::Round2SecretPackage<P>;
/// A package that must be sent by each participant to some other participants
/// in Round 2 of the DKG protocol. Note that there is one specific package
/// for each specific recipient, in contrast to Round 1.
///
/// # Security
///
/// The package must be sent on an *confidential* and *authenticated* channel.
pub type Round2Package = frost::keys::dkg::Round2Package<P>;
/// Performs the first part of the distributed key generation protocol
/// for the given participant.
///
/// It returns the [`Round1SecretPackage`] that must be kept in memory
/// by the participant for the other steps, and the [`Round1Package`] that
/// must be sent to other participants.
pub fn keygen_part1<R: RngCore + CryptoRng>(
identifier: Identifier,
max_signers: u16,
min_signers: u16,
mut rng: R,
) -> Result<(Round1SecretPackage, Round1Package), Error> {
frost::keys::dkg::keygen_part1(identifier, max_signers, min_signers, &mut rng)
}
/// Performs the second part of the distributed key generation protocol
/// for the participant holding the given [`Round1SecretPackage`],
/// given the received [`Round1Package`]s received from the other participants.
///
/// It returns the [`Round2SecretPackage`] that must be kept in memory
/// by the participant for the final step, and the [`Round2Package`]s that
/// must be sent to other participants.
pub fn keygen_part2(
secret_package: Round1SecretPackage,
round1_packages: &[Round1Package],
) -> Result<(Round2SecretPackage, Vec<Round2Package>), Error> {
frost::keys::dkg::keygen_part2(secret_package, round1_packages)
}
/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`Round2SecretPackage`],
/// given the received [`Round1Package`]s and [`Round2Package`]s received from
/// the other participants.
///
/// It returns the [`KeyPackage`] that has the long-lived key share for the
/// participant, and the [`PublicKeyPackage`]s that has public information
/// about all participants; both of which are required to compute FROST
/// signatures.
pub fn keygen_part3(
round2_secret_package: &Round2SecretPackage,
round1_packages: &[Round1Package],
round2_packages: &[Round2Package],
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::keygen_part3(round2_secret_package, round1_packages, round2_packages)
}
}
}
/// FROST(Pallas, BLAKE2b-512) Round 1 functionality and types.
pub mod round1 {
use frost_rerandomized::frost_core::frost::keys::SigningShare;
use super::*;
/// Comprised of FROST(Pallas, BLAKE2b-512) 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.
pub type SigningNonces = frost::round1::SigningNonces<P>;
/// 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.
pub type SigningCommitments = frost::round1::SigningCommitments<P>;
/// Performed once by each participant selected for the signing operation.
///
/// Generates the signing nonces and commitments to be used in the signing
/// operation.
pub fn commit<RNG>(
participant_identifier: frost::Identifier<P>,
secret: &SigningShare<P>,
rng: &mut RNG,
) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<P, RNG>(participant_identifier, secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<P>;
/// FROST(Pallas, BLAKE2b-512) Round 2 functionality and types, for signature share generation.
pub mod round2 {
use super::*;
/// A FROST(Pallas, BLAKE2b-512) participant's signature share, which the Coordinator will aggregate with all other signer's
/// shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<P>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<P>;
/// Performed once by each participant selected for the signing operation.
///
/// 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.
pub fn sign(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
key_package: &keys::KeyPackage,
randomizer_point: &<<P as Ciphersuite>::Group as Group>::Element,
) -> Result<SignatureShare, Error> {
frost_rerandomized::sign(
signing_package,
signer_nonces,
key_package,
randomizer_point,
)
}
}
/// A Schnorr signature on FROST(Pallas, BLAKE2b-512).
pub type Signature = frost_rerandomized::frost_core::Signature<P>;
/// Verifies each FROST(Pallas, BLAKE2b-512) 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 Schnorr
/// 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: &round2::SigningPackage,
signature_shares: &[round2::SignatureShare],
pubkeys: &keys::PublicKeyPackage,
randomized_params: &RandomizedParams<P>,
) -> Result<Signature, Error> {
frost_rerandomized::aggregate(
signing_package,
signature_shares,
pubkeys,
randomized_params,
)
}
/// A signing key for a Schnorr signature on FROST(Pallas, BLAKE2b-512).
pub type SigningKey = frost_rerandomized::frost_core::SigningKey<P>;
/// A valid verifying key for Schnorr signatures on FROST(Pallas, BLAKE2b-512).
pub type VerifyingKey = frost_rerandomized::frost_core::VerifyingKey<P>;

View File

@ -0,0 +1,98 @@
An implementation of Schnorr signatures on the Pallas curve for both single and threshold numbers
of signers (FROST).
## Example: key generation with trusted dealer and FROST signing
Creating a key with a trusted dealer and splitting into shares; then signing a message
and aggregating the signature. Note that the example just simulates a distributed
scenario in a single thread and it abstracts away any communication between peers.
```rust
use reddsa::frost::redpallas as frost;
use rand::thread_rng;
use std::collections::HashMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?;
// Verifies the secret shares from the dealer and stores them in a HashMap.
// In practice, each KeyPackage must be sent to its respective participant
// through a confidential and authenticated channel.
let key_packages: HashMap<_, _> = shares
.into_iter()
.map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?)))
.collect::<Result<_, _>>()?;
let mut nonces = HashMap::new();
let mut commitments = HashMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..(min_signers as u16 + 1) {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonce, commitment) = frost::round1::commit(
participant_identifier,
key_packages[&participant_identifier].secret_share(),
&mut rng,
);
// In practice, the nonces and commitments must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces.insert(participant_identifier, nonce);
commitments.insert(participant_identifier, commitment);
}
// 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::new();
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
// In practice, the SigningPackage must be sent to all participants
// involved in the current signing (at least min_signers participants),
// using an authenticated channel (and confidential if the message is secret).
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
////////////////////////////////////////////////////////////////////////////
// Round 2: each participant generates their signature share
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_identifier in nonces.keys() {
let key_package = &key_packages[participant_identifier];
let nonces_to_use = &nonces[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?;
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
signature_shares.push(signature_share);
}
////////////////////////////////////////////////////////////////////////////
// Aggregation: collects the signing shares from all participants,
// generates the final signature.
////////////////////////////////////////////////////////////////////////////
// Aggregate (also verifies the signature shares)
let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?;
// Check that the threshold signature can be verified by the group public
// key (the verification key).
assert!(pubkeys
.group_public
.verify(message, &group_signature)
.is_ok());
# Ok::<(), frost::Error>(())
```

157
src/frost/redpallas/dkg.md Normal file
View File

@ -0,0 +1,157 @@
# Distributed Key Generation (DKG)
The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.
Before starting, each participant needs a unique identifier, which can be built from
a `u16`. The process in which these identifiers are allocated is up to the application.
The distributed key generation process has 3 parts, with 2 communication rounds
between them, in which each participant needs to send a "package" to every other
participant. In the first round, each participant sends the same package
(a [`Round1Package`]) to every other. In the second round, each receiver gets
their own package (a [`Round2Package`]).
Between part 1 and 2, each participant needs to hold onto a [`Round1SecretPackage`]
that MUST be kept secret. Between part 2 and 3, each participant needs to hold
onto a [`Round2SecretPackage`].
After the third part, each participant will get a [`KeyPackage`] with their
long-term secret share that must be kept secret, and a [`PublicKeyPackage`]
that is public (and will be the same between all participants). With those
they can proceed to sign messages with FROST.
## Example
```rust
use rand::thread_rng;
use std::collections::HashMap;
use reddsa::frost::redpallas as frost;
let mut rng = thread_rng();
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round1_secret_packages = HashMap::new();
// Keep track of all round 1 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round1_packages = HashMap::new();
// For each participant, perform the first part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::keygen_part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
// sent through some communication channel.
for receiver_participant_index in 1..=max_signers {
if receiver_participant_index == participant_index {
continue;
}
let receiver_participant_identifier: frost::Identifier = receiver_participant_index
.try_into()
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(Vec::new)
.push(round1_package.clone());
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 2
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's round 2 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round2_secret_packages = HashMap::new();
// Keep track of all round 2 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round2_packages = HashMap::new();
// For each participant, perform the second part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::keygen_part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round2_secret_packages.insert(participant_identifier, round2_secret_package);
// "Send" the round 2 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for round2_package in round2_packages {
received_round2_packages
.entry(round2_package.receiver_identifier)
.or_insert_with(Vec::new)
.push(round2_package);
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, final computation
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's long-lived key package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut key_packages = HashMap::new();
// Keep track of each participant's public key package.
// In practice, if there is a Coordinator, only they need to store the set.
// If there is not, then all candidates must store their own sets.
// All participants will have the same exact public key package.
let mut pubkey_packages = HashMap::new();
// For each participant, perform the third part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::keygen_part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
)?;
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
}
// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```

View File

@ -34,6 +34,19 @@ impl<T: SigType> Default for HStar<T> {
}
impl<T: SigType> HStar<T> {
// Only used by FROST code
#[allow(unused)]
pub(crate) fn new(personalization_string: &[u8]) -> Self {
let state = Params::new()
.hash_length(64)
.personal(personalization_string)
.to_state();
Self {
state,
_marker: PhantomData::default(),
}
}
/// Add `data` to the hash, and return `Self` for chaining.
pub fn update(&mut self, data: impl AsRef<[u8]>) -> &mut Self {
self.state.update(data.as_ref());

View File

@ -24,6 +24,8 @@ extern crate std;
pub mod batch;
mod constants;
mod error;
#[cfg(feature = "frost")]
pub mod frost;
mod hash;
pub mod orchard;
pub mod sapling;

View File

@ -31,8 +31,8 @@ pub struct SigningKey<T: SigType> {
pk: VerificationKey<T>,
}
impl<'a, T: SigType> From<&'a SigningKey<T>> for VerificationKey<T> {
fn from(sk: &'a SigningKey<T>) -> VerificationKey<T> {
impl<T: SigType> From<&SigningKey<T>> for VerificationKey<T> {
fn from(sk: &SigningKey<T>) -> VerificationKey<T> {
sk.pk
}
}

85
tests/frost_redjubjub.rs Normal file
View File

@ -0,0 +1,85 @@
#![cfg(feature = "frost")]
use frost_rerandomized::frost_core::{Ciphersuite, Group, GroupError};
use rand::thread_rng;
use reddsa::{frost::redjubjub::JubjubBlake2b512, sapling};
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_rerandomized::frost_core::tests::check_sign_with_dealer::<JubjubBlake2b512, _>(rng);
}
#[test]
fn check_randomized_sign_with_dealer() {
let rng = thread_rng();
let (msg, group_signature, group_pubkey) =
frost_rerandomized::tests::check_randomized_sign_with_dealer::<JubjubBlake2b512, _>(rng);
// Check that the threshold signature can be verified by the `reddsa` crate
// public key (interoperability test)
let sig = {
let bytes: [u8; 64] = group_signature.to_bytes().as_ref().try_into().unwrap();
reddsa::Signature::<sapling::SpendAuth>::from(bytes)
};
let pk_bytes = {
let bytes: [u8; 32] = group_pubkey.to_bytes().as_ref().try_into().unwrap();
reddsa::VerificationKeyBytes::<sapling::SpendAuth>::from(bytes)
};
// Check that the verification key is a valid RedDSA verification key.
let pub_key = reddsa::VerificationKey::try_from(pk_bytes)
.expect("The test verification key to be well-formed.");
// Check that signature validation has the expected result.
assert!(pub_key.verify(&msg, &sig).is_ok());
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_rerandomized::frost_core::tests::check_sign_with_dkg::<JubjubBlake2b512, _>(rng);
}
#[test]
fn check_deserialize_identity() {
let encoded_identity = <JubjubBlake2b512 as Ciphersuite>::Group::serialize(
&<JubjubBlake2b512 as Ciphersuite>::Group::identity(),
);
let r = <JubjubBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::InvalidIdentityElement));
}
#[test]
fn check_deserialize_non_canonical() {
let encoded_generator = <JubjubBlake2b512 as Ciphersuite>::Group::serialize(
&<JubjubBlake2b512 as Ciphersuite>::Group::generator(),
);
let r = <JubjubBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert!(r.is_ok());
// This is x = p + 3 which is non-canonical and maps to a valid point.
let encoded_point =
hex::decode("04000000fffffffffe5bfeff02a4bd5305d8a10908d83933487d9d2953a7ed73")
.unwrap()
.try_into()
.unwrap();
let r = <JubjubBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::MalformedElement));
}
#[test]
fn check_deserialize_non_prime_order() {
let encoded_point =
hex::decode("0300000000000000000000000000000000000000000000000000000000000000")
.unwrap()
.try_into()
.unwrap();
let r = <JubjubBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::InvalidNonPrimeOrderElement));
}

74
tests/frost_redpallas.rs Normal file
View File

@ -0,0 +1,74 @@
#![cfg(feature = "frost")]
use frost_rerandomized::frost_core::{Ciphersuite, Group, GroupError};
use rand::thread_rng;
use reddsa::{frost::redpallas::PallasBlake2b512, orchard};
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_rerandomized::frost_core::tests::check_sign_with_dealer::<PallasBlake2b512, _>(rng);
}
#[test]
fn check_randomized_sign_with_dealer() {
let rng = thread_rng();
let (msg, group_signature, group_pubkey) =
frost_rerandomized::tests::check_randomized_sign_with_dealer::<PallasBlake2b512, _>(rng);
// Check that the threshold signature can be verified by the `reddsa` crate
// public key (interoperability test)
let sig = {
let bytes: [u8; 64] = group_signature.to_bytes().as_ref().try_into().unwrap();
reddsa::Signature::<orchard::SpendAuth>::from(bytes)
};
let pk_bytes = {
let bytes: [u8; 32] = group_pubkey.to_bytes().as_ref().try_into().unwrap();
reddsa::VerificationKeyBytes::<orchard::SpendAuth>::from(bytes)
};
// Check that the verification key is a valid RedDSA verification key.
let pub_key = reddsa::VerificationKey::try_from(pk_bytes)
.expect("The test verification key to be well-formed.");
// Check that signature validation has the expected result.
assert!(pub_key.verify(&msg, &sig).is_ok());
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_rerandomized::frost_core::tests::check_sign_with_dkg::<PallasBlake2b512, _>(rng);
}
#[test]
fn check_deserialize_identity() {
let encoded_identity = <PallasBlake2b512 as Ciphersuite>::Group::serialize(
&<PallasBlake2b512 as Ciphersuite>::Group::identity(),
);
let r = <PallasBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_identity);
assert_eq!(r, Err(GroupError::InvalidIdentityElement));
}
#[test]
fn check_deserialize_non_canonical() {
let encoded_generator = <PallasBlake2b512 as Ciphersuite>::Group::serialize(
&<PallasBlake2b512 as Ciphersuite>::Group::generator(),
);
let r = <PallasBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_generator);
assert!(r.is_ok());
// This is x = p + 3 which is non-canonical and maps to a valid point.
let encoded_point =
hex::decode("04000000ed302d991bf94c09fc98462200000000000000000000000000000040")
.unwrap()
.try_into()
.unwrap();
let r = <PallasBlake2b512 as Ciphersuite>::Group::deserialize(&encoded_point);
assert_eq!(r, Err(GroupError::MalformedElement));
}