add Ed448 support (#187)

* add Ed448 support

* use zero() instead of one() when encoding a signature

* point to ed448-goldilocks main branch
This commit is contained in:
Conrado Gouvea 2022-12-02 16:47:20 -03:00 committed by GitHub
parent 20d6d2a9b0
commit f79648dfa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 896 additions and 2 deletions

View File

@ -4,6 +4,7 @@ members = [
"frost-core",
#"frost-redjubjub",
"frost-ed25519",
"frost-ed448",
"frost-p256",
"frost-ristretto255",
"frost-secp256k1",

View File

@ -47,7 +47,7 @@ pub trait Field: Copy + Clone {
+ Sub<Output = Self::Scalar>;
/// A unique byte array buf of fixed length N.
type Serialization: AsRef<[u8]> + Debug + Default + TryFrom<Vec<u8>>;
type Serialization: AsRef<[u8]> + Debug + TryFrom<Vec<u8>>;
/// Returns the zero element of the field, the additive identity.
fn zero() -> Self::Scalar;

View File

@ -34,8 +34,9 @@ where
let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?;
let one = <<C::Group as Group>::Field as Field>::zero();
let mut z_bytes =
Vec::from(<<C::Group as Group>::Field as Field>::Serialization::default().as_ref());
Vec::from(<<C::Group as Group>::Field as Field>::serialize(&one).as_ref());
let z_bytes_len = z_bytes.len();

47
frost-ed448/Cargo.toml Normal file
View File

@ -0,0 +1,47 @@
[package]
name = "frost-ed448"
edition = "2021"
# When releasing to crates.io:
# - Update html_root_url
# - Update CHANGELOG.md
# - Create git tag.
version = "0.1.0"
authors = ["Deirdre Connolly <durumcrustulum@gmail.com>", "Chelsea Komlo <me@chelseakomlo.com>",
"Conrado Gouvea <conradoplg@gmail.com>"]
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ZcashFoundation/frost"
categories = ["cryptography"]
keywords = ["cryptography", "crypto", "ristretto", "threshold", "signature"]
description = "A Schnorr signature scheme over the prime-order Ristretto group that supports FROST ."
[package.metadata.docs.rs]
features = ["nightly"]
[dependencies]
byteorder = "1.4"
# Pointing to a commit after 0.8.3 with the required functions for FROST.
# TODO: update after a release is made
ed448-goldilocks = { git = "https://github.com/crate-crypto/Ed448-Goldilocks.git", rev = "54ffc6b8f14030688996da50a1bfb7bb713eb172" }
digest = "0.10"
frost-core = { path = "../frost-core", features = ["test-impl"] }
hex = { version = "0.4.3", features = ["serde"] }
rand_core = "0.6"
serde = { version = "1", optional = true, features = ["derive"] }
sha3 = "0.10.6"
thiserror = "1.0"
zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] }
[dev-dependencies]
bincode = "1"
criterion = "0.4"
lazy_static = "1.4"
proptest = "1.0"
proptest-derive = "0.3"
rand = "0.8"
rand_chacha = "0.3"
serde_json = "1.0"
[features]
nightly = []
default = ["serde"]

98
frost-ed448/README.md Normal file
View File

@ -0,0 +1,98 @@
An implementation of Schnorr signatures on the Ed448 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 frost_ed448 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 store them in a HashMap.
// In practice, the KeyPackages must be sent to its respective participants
// 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 commitment 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 authenticate 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
frost-ed448/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 an 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 frost_ed448 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>(())
```

405
frost-ed448/src/lib.rs Normal file
View File

@ -0,0 +1,405 @@
#![allow(non_snake_case)]
#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
use ed448_goldilocks::{
curve::{edwards::CompressedEdwardsY, ExtendedPoint},
Scalar,
};
use rand_core::{CryptoRng, RngCore};
use sha3::{
digest::{ExtendableOutput, Update, XofReader},
Shake256,
};
use frost_core::{frost, Ciphersuite, Field, Group};
#[cfg(test)]
mod tests;
pub use frost_core::Error;
#[derive(Clone, Copy)]
/// An implementation of the FROST(Ed448, SHAKE256) ciphersuite scalar field.
pub struct Ed448ScalarField;
impl Field for Ed448ScalarField {
type Scalar = Scalar;
type Serialization = [u8; 57];
fn zero() -> Self::Scalar {
Scalar::zero()
}
fn one() -> Self::Scalar {
Scalar::one()
}
fn invert(scalar: &Self::Scalar) -> Result<Self::Scalar, Error> {
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 serialize(scalar: &Self::Scalar) -> Self::Serialization {
let bytes = scalar.to_bytes();
std::array::from_fn(|i| if i < 56 { bytes[i] } else { 0 })
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, Error> {
match Scalar::from_canonical_bytes(*buf) {
Some(s) => Ok(s),
None => Err(Error::MalformedScalar),
}
}
fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
Self::serialize(scalar)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
/// An implementation of the FROST(Ed448, SHAKE256) ciphersuite group.
pub struct Ed448Group;
impl Group for Ed448Group {
type Field = Ed448ScalarField;
type Element = ExtendedPoint;
type Serialization = [u8; 57];
fn cofactor() -> <Self::Field as Field>::Scalar {
Scalar::one()
}
fn identity() -> Self::Element {
Self::Element::identity()
}
fn generator() -> Self::Element {
Self::Element::generator()
}
fn serialize(element: &Self::Element) -> Self::Serialization {
element.compress().0
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, Error> {
match CompressedEdwardsY(*buf).decompress() {
Some(point) => {
if point == Self::identity() {
Err(Error::InvalidIdentityElement)
} else if point.is_torsion_free() {
Ok(point)
} else {
Err(Error::InvalidNonPrimeOrderElement)
}
}
None => Err(Error::MalformedElement),
}
}
}
fn hash_to_array(inputs: &[&[u8]]) -> [u8; 114] {
let mut h = Shake256::default();
for i in inputs {
h.update(i);
}
let mut reader = h.finalize_xof();
let mut output = [0u8; 114];
reader.read(&mut output);
output
}
fn hash_to_scalar(inputs: &[&[u8]]) -> Scalar {
let output = hash_to_array(inputs);
Scalar::from_bytes_mod_order_wide(&output)
}
/// Context string from the ciphersuite in the [spec]
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.3-1
const CONTEXT_STRING: &str = "FROST-ED448-SHAKE256-v11";
#[derive(Clone, Copy, PartialEq, Eq)]
/// An implementation of the FROST(Ed448, SHAKE256) ciphersuite.
pub struct Ed448Shake256;
impl Ciphersuite for Ed448Shake256 {
type Group = Ed448Group;
type HashOutput = [u8; 114];
type SignatureSerialization = [u8; 114];
/// H1 for FROST(Ed448, SHAKE256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.3-2.2.2.1
fn H1(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho", m])
}
/// H2 for FROST(Ed448, SHAKE256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.3-2.2.2.2
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
hash_to_scalar(&[b"SigEd448\0\0", m])
}
/// H3 for FROST(Ed448, SHAKE256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.3-2.2.2.3
fn H3(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce", m])
}
/// H4 for FROST(Ed448, SHAKE256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.3-2.2.2.4
fn H4(m: &[u8]) -> Self::HashOutput {
hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m])
}
/// H5 for FROST(Ed448, SHAKE256)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-11.html#section-6.3-2.2.2.5
fn H5(m: &[u8]) -> Self::HashOutput {
hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m])
}
/// HDKG for FROST(Ed448, SHAKE256)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg", m]))
}
}
type E = Ed448Shake256;
/// A FROST(Ed448, SHAKE256) participant identifier.
pub type Identifier = frost::Identifier<E>;
/// FROST(Ed448, SHAKE256) keys, key generation, key shares.
pub mod keys {
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(Ed448, SHAKE256) keypair, the receiver of the [`SecretShare`] *must* call
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<E>;
/// A FROST(Ed448, SHAKE256) 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<E>;
/// 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<E>;
pub mod dkg {
#![doc = include_str!("../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<E>;
/// 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<E>;
/// 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<E>;
/// 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<E>;
/// 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(Ed448, SHAKE256) Round 1 functionality and types.
pub mod round1 {
use frost_core::frost::keys::SigningShare;
use super::*;
/// Comprised of FROST(Ed448, SHAKE256) 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<E>;
/// 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<E>;
/// 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<E>,
secret: &SigningShare<E>,
rng: &mut RNG,
) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<E, RNG>(participant_identifier, secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<E>;
/// FROST(Ed448, SHAKE256) Round 2 functionality and types, for signature share generation.
pub mod round2 {
use super::*;
/// A FROST(Ed448, SHAKE256) 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<E>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<E>;
/// 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,
) -> Result<SignatureShare, Error> {
frost::round2::sign(signing_package, signer_nonces, key_package)
}
}
/// A Schnorr signature on FROST(Ed448, SHAKE256).
pub type Signature = frost_core::Signature<E>;
/// Verifies each FROST(Ed448, SHAKE256) 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,
) -> Result<Signature, Error> {
frost::aggregate(signing_package, signature_shares, pubkeys)
}
/// A signing key for a Schnorr signature on FROST(Ed448, SHAKE256).
pub type SigningKey = frost_core::SigningKey<E>;
/// A valid verifying key for Schnorr signatures on FROST(Ed448, SHAKE256).
pub type VerifyingKey = frost_core::VerifyingKey<E>;

24
frost-ed448/src/tests.rs Normal file
View File

@ -0,0 +1,24 @@
use lazy_static::lazy_static;
use rand::thread_rng;
use serde_json::Value;
use crate::*;
lazy_static! {
pub static ref ED448_SHAKE256: Value =
serde_json::from_str(include_str!("tests/vectors.json").trim())
.expect("Test vector is valid JSON");
}
/// This is testing that Shamir's secret sharing to compute and arbitrary
/// value is working.
#[test]
fn check_share_generation_ed448_shake256() {
let rng = thread_rng();
frost_core::tests::check_share_generation::<Ed448Shake256, _>(rng);
}
#[test]
fn check_sign_with_test_vectors() {
frost_core::tests::vectors::check_sign_with_test_vectors::<Ed448Shake256>(&ED448_SHAKE256)
}

View File

@ -0,0 +1,68 @@
{
"config": {
"MAX_PARTICIPANTS": "3",
"NUM_PARTICIPANTS": "2",
"MIN_PARTICIPANTS": "2",
"name": "FROST(Ed448, SHAKE256)",
"group": "ed448",
"hash": "SHAKE256"
},
"inputs": {
"group_secret_key": "6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b404158a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c00",
"group_public_key": "3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d40801ba265632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b000",
"message": "74657374",
"share_polynomial_coefficients": [
"dbd7a514f7a731976620f0436bd135fe8dddc3fadd6e0d13dbd58a1981e587d377d48e0b7ce4e0092967c5e85884d0275a7a740b6abdcd0500"
],
"participants": {
"1": {
"participant_share": "4a2b2f5858a932ad3d3b18bd16e76ced3070d72fd79ae4402df201f525e754716a1bc1b87a502297f2a99d89ea054e0018eb55d39562fd0100"
},
"2": {
"participant_share": "2503d56c4f516444a45b080182b8a2ebbe4d9b2ab509f25308c88c0ea7ccdc44e2ef4fc4f63403a11b116372438a1e287265cadeff1fcb0700"
},
"3": {
"participant_share": "00db7a8146f995db0a7cf844ed89d8e94c2b5f259378ff66e39d172828b264185ac4decf7219e4aa4478285b9c0eef4fccdf3eea69dd980d00"
}
}
},
"round_one_outputs": {
"participant_list": "1,3",
"participants": {
"1": {
"hiding_nonce_randomness": "89bf16040081ff2990336b200613787937ebe1f024b8cdff90eb6f1c741d91c1",
"binding_nonce_randomness": "cd646348bb98fd2a4b2f27fb7d6da18201c161847352576b4bf125190e965483",
"hiding_nonce": "67a6f023e77361707c6e894c625e809e80f33fdb310810053ae29e28e7011f3193b9020e73c183a98cc3a519160ed759376dd92c9483162200",
"binding_nonce": "4812e8d7c8b7a50ced80b507902d074ef8647bc1146979683da8d0fecd93fa3c8230cade2fb4344600aa04bd4b7a21d046c5b63ee865b12a00",
"hiding_nonce_commitment": "649c6a53b109897d962d033f23d01fd4e1053dddf3746d2ddce9bd66aea38ccfc3df061df03ca399eb806312ab3037c0c31523142956ada780",
"binding_nonce_commitment": "0064cc729a8e2fcf417e43788ecec37b10e9e1dcb3ae90854efbfaad00a0ef3cdd52e18d56f073c8ff0947cb71ff0bb17c3d45d096409ddb00",
"binding_factor_input": "106dadce87ca867018702d69a02effd165e1ac1a511c957cff1897ceff2e34ca212fe798d84f0bde6054bf0fa77fd4cd4bc4853d6dc8dbd19d340923f0ebbbb35172df4ab865a45d55af31fa0e6606ea97cf8513022b2b133d0f9f6b8d3be184221fc4592bf12bd7fb4127bb67e51a6dc9e5f1ed5243362fb46a6da552418ca967d43d9bc811a21917a3018de58f11c25f6b9ad8bec3699e06b87dd3ab67a7326c30878c7c55ec1a45802af65da193ce99634158539e38c232a627895c5f14e2e20d487382ccc9c99cd0a0df266a292f283bb9b6854e344ecc32d5e1852fdde5fde77798010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"binding_factor": "3412ac894a91a6bc0e3e7c790f3e8ef5d1288e54de780aba384cbb3081b602dd188010e5b0c9ac2b5dca0aae54cfd0f5c391cece8092131d00"
},
"3": {
"hiding_nonce_randomness": "3718dabb4fd3d7dd9adad4878c6de8b33c8841cfe7cc95a85592952a2c9c554d",
"binding_nonce_randomness": "3becbc90798211a0f52543dd1f24869a143fdf743409581af4db30f045773d64",
"hiding_nonce": "4f2666770317d14ec9f7fd6690c075c34b4cde7f6d9bceda9e9433ec8c0f2dc983ff1622c3a54916ce7c161381d263fad62539cddab2101600",
"binding_nonce": "88f66df8bb66389932721a40de4aa5754f632cac114abc1052688104d19f3b1a010880ebcd0c4c0f8cf567d887e5b0c3c0dc78821166550f00",
"hiding_nonce_commitment": "8dcf049167e28d5f53fa7ebbbd136abcaf2be9f2c02448c8979002f92577b22027640def7ddd5b98f9540c2280f36a92d4747bbade0b0c4280",
"binding_nonce_commitment": "12e837b89a2c085481fcf0ca640a17a24b6fc96b032d40e4301c78e7232a9f49ffdcad2c21acbc992e79dfc3c6c07cb94e4680b3dcc9935580",
"binding_factor_input": "106dadce87ca867018702d69a02effd165e1ac1a511c957cff1897ceff2e34ca212fe798d84f0bde6054bf0fa77fd4cd4bc4853d6dc8dbd19d340923f0ebbbb35172df4ab865a45d55af31fa0e6606ea97cf8513022b2b133d0f9f6b8d3be184221fc4592bf12bd7fb4127bb67e51a6dc9e5f1ed5243362fb46a6da552418ca967d43d9bc811a21917a3018de58f11c25f6b9ad8bec3699e06b87dd3ab67a7326c30878c7c55ec1a45802af65da193ce99634158539e38c232a627895c5f14e2e20d487382ccc9c99cd0a0df266a292f283bb9b6854e344ecc32d5e1852fdde5fde77798030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"binding_factor": "6aa48a3635d7b962489283ee1ccda8ea66e5677b1e17f2f475eb565e3ae8ea73360f24c04e3775dadd1f2923adcda3d105536ad28c3c561100"
}
}
},
"round_two_outputs": {
"participant_list": "1,3",
"participants": {
"1": {
"sig_share": "c5057c80d13e565545dac6f3aa333065c809a14a94fea3c8e4e87e386a9cb89602de7355c5d19ebb09d553b100ef1858104fc7c43992d83400"
},
"3": {
"sig_share": "2b490ea08411f78c620c668fff8ba70b25b7c89436f20cc45419213de70f93fb6c9094c79293697d72e741b68d2e493446005145d0b7fc3500"
}
}
},
"final_output": {
"sig": "83ac141d289a5171bc894b058aee2890316280719a870fc5c1608b77403023155d7a9dc15a2b7920bb5826dd540bf76336be99536cebe36280fd093275c38dd4be525767f537fd6a4f5d8a9330811562c84fded5f851ac4b926f6e081d586508397cbc95678e1d628c564f180a0a4ad52a00"
}
}

View File

@ -0,0 +1,44 @@
use ed448_goldilocks::curve::ExtendedPoint;
use frost_core::{Ciphersuite, Group};
use frost_ed448::*;
use rand::thread_rng;
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
frost_core::tests::check_sign_with_dealer::<Ed448Shake256, _>(rng);
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::check_sign_with_dkg::<Ed448Shake256, _>(rng);
}
// TODO: make batching work for larger scalars
// #[test]
#[allow(unused)]
fn check_batch_verify() {
let rng = thread_rng();
frost_core::tests::batch::batch_verify::<Ed448Shake256, _>(rng);
}
// TODO: make batching work for larger scalars
// #[test]
#[allow(unused)]
fn check_bad_batch_verify() {
let rng = thread_rng();
frost_core::tests::batch::bad_batch_verify::<Ed448Shake256, _>(rng);
}
#[test]
fn check_deserialize_identity() {
let encoded_identity = ExtendedPoint::identity().compress().0;
let r = <<Ed448Shake256 as Ciphersuite>::Group as Group>::deserialize(&encoded_identity);
assert_eq!(r, Err(Error::InvalidIdentityElement));
}

View File

@ -0,0 +1,33 @@
use frost_core::tests::proptests::{tweak_strategy, SignatureCase};
use frost_ed448::*;
use proptest::prelude::*;
use rand_chacha::ChaChaRng;
use rand_core::SeedableRng;
proptest! {
#[test]
fn tweak_signature(
tweaks in prop::collection::vec(tweak_strategy(), (0,5)),
rng_seed in prop::array::uniform32(any::<u8>()),
) {
// Use a deterministic RNG so that test failures can be reproduced.
// Seeding with 64 bits of entropy is INSECURE and this code should
// not be copied outside of this test!
let mut rng = ChaChaRng::from_seed(rng_seed);
// Create a test case for each signature type.
let msg = b"test message for proptests";
let mut sig = SignatureCase::<Ed448Shake256>::new(&mut rng, msg.to_vec());
// Apply tweaks to each case.
for t in &tweaks {
sig.apply_tweak(t);
}
assert!(sig.check());
}
}

View File

@ -178,6 +178,22 @@ fn main() -> ExitCode {
);
}
replaced |= write_docs(
&docs,
"frost-ed448/src/lib.rs",
&["Ed448Shake256", "Ed448", "<E>"],
old_suite_names_doc,
&["FROST(Ed448, SHAKE256)"],
);
for filename in ["README.md", "dkg.md"] {
replaced |= copy_and_replace(
format!("{}/{}", original_basename, filename).as_str(),
format!("frost-ed448/{}", filename).as_str(),
original_strings,
&["frost_ed448", "Ed448 curve"],
);
}
replaced |= write_docs(
&docs,
"frost-secp256k1/src/lib.rs",