frost/frost-ed25519/src/lib.rs

420 lines
15 KiB
Rust

#![allow(non_snake_case)]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc = document_features::document_features!()]
use std::collections::BTreeMap;
use curve25519_dalek::{
constants::ED25519_BASEPOINT_POINT,
edwards::{CompressedEdwardsY, EdwardsPoint},
scalar::Scalar,
traits::Identity,
};
use frost_rerandomized::RandomizedCiphersuite;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
use frost_core as frost;
#[cfg(test)]
mod tests;
// Re-exports in our public API
pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError};
pub use rand_core;
/// An error.
pub type Error = frost_core::Error<Ed25519Sha512>;
/// An implementation of the FROST(Ed25519, SHA-512) ciphersuite scalar field.
#[derive(Clone, Copy)]
pub struct Ed25519ScalarField;
impl Field for Ed25519ScalarField {
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, FieldError> {
// [`curve25519_dalek::scalar::Scalar`]'s Eq/PartialEq does a constant-time comparison using
// `ConstantTimeEq`
if *scalar == <Self as Field>::zero() {
Err(FieldError::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 {
scalar.to_bytes()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Scalar, FieldError> {
match Scalar::from_canonical_bytes(*buf).into() {
Some(s) => Ok(s),
None => Err(FieldError::MalformedScalar),
}
}
fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization {
Self::serialize(scalar)
}
}
/// An implementation of the FROST(Ed25519, SHA-512) ciphersuite group.
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Ed25519Group;
impl Group for Ed25519Group {
type Field = Ed25519ScalarField;
type Element = EdwardsPoint;
type Serialization = [u8; 32];
fn cofactor() -> <Self::Field as Field>::Scalar {
Scalar::ONE
}
fn identity() -> Self::Element {
EdwardsPoint::identity()
}
fn generator() -> Self::Element {
ED25519_BASEPOINT_POINT
}
fn serialize(element: &Self::Element) -> Self::Serialization {
element.compress().to_bytes()
}
fn deserialize(buf: &Self::Serialization) -> Result<Self::Element, GroupError> {
match CompressedEdwardsY::from_slice(buf.as_ref())
.map_err(|_| GroupError::MalformedElement)?
.decompress()
{
Some(point) => {
if point == Self::identity() {
Err(GroupError::InvalidIdentityElement)
} else if point.is_torsion_free() {
// At this point we should reject points which were not
// encoded canonically (i.e. Y coordinate >= p).
// However, we don't allow non-prime order elements,
// and that suffices to also reject non-canonical encodings
// per https://eprint.iacr.org/2020/1244.pdf:
//
// > There are 19 elliptic curve points that can be encoded in a non-canonical form.
// > (...) Among these points there are 2 points of small order and from the
// > remaining 17 y-coordinates only 10 decode to valid curve points all of mixed order.
Ok(point)
} else {
Err(GroupError::InvalidNonPrimeOrderElement)
}
}
None => Err(GroupError::MalformedElement),
}
}
}
fn hash_to_array(inputs: &[&[u8]]) -> [u8; 64] {
let mut h = Sha512::new();
for i in inputs {
h.update(i);
}
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
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-14.html#section-6.1-1
const CONTEXT_STRING: &str = "FROST-ED25519-SHA512-v1";
/// An implementation of the FROST(Ed25519, SHA-512) ciphersuite.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Ed25519Sha512;
impl Ciphersuite for Ed25519Sha512 {
const ID: &'static str = CONTEXT_STRING;
type Group = Ed25519Group;
type HashOutput = [u8; 64];
type SignatureSerialization = [u8; 64];
/// H1 for FROST(Ed25519, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-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(Ed25519, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.2
fn H2(m: &[u8]) -> <<Self::Group as Group>::Field as Field>::Scalar {
hash_to_scalar(&[m])
}
/// H3 for FROST(Ed25519, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-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(Ed25519, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.4
fn H4(m: &[u8]) -> Self::HashOutput {
hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m])
}
/// H5 for FROST(Ed25519, SHA-512)
///
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.5
fn H5(m: &[u8]) -> Self::HashOutput {
hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m])
}
/// HDKG for FROST(Ed25519, SHA-512)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg", m]))
}
/// HID for FROST(Ed25519, SHA-512)
fn HID(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id", m]))
}
}
impl RandomizedCiphersuite for Ed25519Sha512 {
fn hash_randomizer(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
Some(hash_to_scalar(&[
CONTEXT_STRING.as_bytes(),
b"randomizer",
m,
]))
}
}
type E = Ed25519Sha512;
/// A FROST(Ed25519, SHA-512) participant identifier.
pub type Identifier = frost::Identifier<E>;
/// FROST(Ed25519, SHA-512) keys, key generation, key shares.
pub mod keys {
use std::collections::BTreeMap;
use super::*;
/// The identifier list to use when generating key shares.
pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, E>;
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
pub fn generate_with_dealer<RNG: RngCore + CryptoRng>(
max_signers: u16,
min_signers: u16,
identifiers: IdentifierList,
mut rng: RNG,
) -> Result<(BTreeMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng)
}
/// Splits an existing key into FROST shares.
///
/// This is identical to [`generate_with_dealer`] but receives an existing key
/// instead of generating a fresh one. This is useful in scenarios where
/// the key needs to be generated externally or must be derived from e.g. a
/// seed phrase.
pub fn split<R: RngCore + CryptoRng>(
secret: &SigningKey,
max_signers: u16,
min_signers: u16,
identifiers: IdentifierList,
rng: &mut R,
) -> Result<(BTreeMap<Identifier, SecretShare>, PublicKeyPackage), Error> {
frost::keys::split(secret, max_signers, min_signers, identifiers, rng)
}
/// Recompute the secret from t-of-n secret shares using Lagrange interpolation.
///
/// This can be used if for some reason the original key must be restored; e.g.
/// if threshold signing is not required anymore.
///
/// This is NOT required to sign with FROST; the whole point of FROST is being
/// able to generate signatures only using the shares, without having to
/// reconstruct the original key.
///
/// The caller is responsible for providing at least `min_signers` shares;
/// if less than that is provided, a different key will be returned.
pub fn reconstruct(secret_shares: &[KeyPackage]) -> Result<SigningKey, Error> {
frost::keys::reconstruct(secret_shares)
}
/// Secret and public key material generated by a dealer performing
/// [`generate_with_dealer`].
///
/// # Security
///
/// To derive a FROST(Ed25519, SHA-512) keypair, the receiver of the [`SecretShare`] *must* call
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<E>;
/// A secret scalar value representing a signer's share of the group secret.
pub type SigningShare = frost::keys::SigningShare<E>;
/// A public group element that represents a single signer's public verification share.
pub type VerifyingShare = frost::keys::VerifyingShare<E>;
/// A FROST(Ed25519, SHA-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<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>;
/// 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.
pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment<E>;
pub mod dkg;
pub mod repairable;
}
/// FROST(Ed25519, SHA-512) Round 1 functionality and types.
pub mod round1 {
use crate::keys::SigningShare;
use super::*;
/// Comprised of FROST(Ed25519, SHA-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<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>;
/// A commitment to a signing nonce share.
pub type NonceCommitment = frost::round1::NonceCommitment<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>(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<E, RNG>(secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<E>;
/// FROST(Ed25519, SHA-512) Round 2 functionality and types, for signature share generation.
pub mod round2 {
use super::*;
/// A FROST(Ed25519, SHA-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<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(Ed25519, SHA-512).
pub type Signature = frost_core::Signature<E>;
/// Verifies each FROST(Ed25519, SHA-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: &SigningPackage,
signature_shares: &BTreeMap<Identifier, round2::SignatureShare>,
pubkeys: &keys::PublicKeyPackage,
) -> Result<Signature, Error> {
frost::aggregate(signing_package, signature_shares, pubkeys)
}
/// A signing key for a Schnorr signature on FROST(Ed25519, SHA-512).
pub type SigningKey = frost_core::SigningKey<E>;
/// A valid verifying key for Schnorr signatures on FROST(Ed25519, SHA-512).
pub type VerifyingKey = frost_core::VerifyingKey<E>;