frost/frost-ristretto255/src/frost.rs

312 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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