remove frost module (#568)

This commit is contained in:
Conrado Gouvea 2023-10-31 13:30:05 -03:00 committed by GitHub
parent 71c092532c
commit a0df08e30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 550 additions and 568 deletions

View File

@ -4,8 +4,11 @@ Entries are listed in reverse chronological order.
## Unreleased
## 0.8.0
## 1.0.0-rc.0
* The `frost-core::frost` module contents were merged into `frost-core`, thus
eliminating the `frost` module. You can adapt any calling code with e.g.
changing `use frost_core::frost::*` to `use frost-core::*`.
* Both serde serialization and the default byte-oriented serialization now
include a version field (a u8) at the beginning which is always 0 for now. The
ciphersuite ID field was moved from the last field to the second field, after

View File

@ -2,12 +2,20 @@
Base traits and types in Rust that implement ['Two-Round Threshold Schnorr Signatures with
FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/) generically for
`frost-core::Ciphersuite` implementations.
[`Ciphersuite`] implementations.
For key generation, refer to the [`keys`] module. For round-specific
types and functions, refer to the [`round1`] and [`round2`] modules. This module
contains types and functions not directly related to key generation and the
FROST rounds.
## Status ⚠
The FROST specification is not yet finalized, and this codebase has not yet been audited or
released. The APIs and types in `frost-core` are subject to change.
The FROST specification is not yet finalized, though no significant changes are
expected at this point. This code base has been audited by NCC. The APIs and
types in `frost-core` are subject to change during the release candidate phase,
and will follow SemVer guarantees after 1.0.0.
## Usage

View File

@ -6,7 +6,8 @@ use std::collections::BTreeMap;
use criterion::{BenchmarkId, Criterion, Throughput};
use rand_core::{CryptoRng, RngCore};
use crate::{batch, frost, Ciphersuite, Signature, SigningKey, VerifyingKey};
use crate as frost;
use crate::{batch, Ciphersuite, Signature, SigningKey, VerifyingKey};
struct Item<C: Ciphersuite> {
vk: VerifyingKey<C>,

View File

@ -2,7 +2,7 @@
use thiserror::Error;
use crate::{frost::Identifier, Ciphersuite};
use crate::{Ciphersuite, Identifier};
#[derive(Error, Debug, Clone, Copy, Eq, PartialEq)]
pub struct ParticipantError<C: Ciphersuite>(Identifier<C>);

View File

@ -1,514 +1 @@
//! An implementation of FROST (Flexible Round-Optimized Schnorr Threshold)
//! signatures.
//!
//! For key generation, refer to the [`keys`] module.
//! For round-specific types and functions, refer to the [`round1`] and
//! [`round2`] modules.
//!
//! This module contains types and functions not directly related to key
//! generation and the FROST rounds.
use std::{
collections::{BTreeMap, BTreeSet},
fmt::{self, Debug},
};
use derive_getters::Getters;
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
mod identifier;
pub mod keys;
pub mod round1;
pub mod round2;
use crate::{
scalar_mul::VartimeMultiscalarMul, Ciphersuite, Deserialize, Element, Error, Field, Group,
Header, Scalar, Serialize, Signature, VerifyingKey,
};
pub use self::identifier::Identifier;
/// 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, PartialEq, Eq)]
pub struct BindingFactor<C: Ciphersuite>(Scalar<C>);
impl<C> BindingFactor<C>
where
C: Ciphersuite,
{
/// Deserializes [`BindingFactor`] from bytes.
pub fn deserialize(
bytes: <<C::Group as Group>::Field as Field>::Serialization,
) -> Result<Self, Error<C>> {
<<C::Group as Group>::Field>::deserialize(&bytes)
.map(|scalar| Self(scalar))
.map_err(|e| e.into())
}
/// Serializes [`BindingFactor`] to bytes.
pub fn serialize(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
<<C::Group as Group>::Field>::serialize(&self.0)
}
}
impl<C> Debug for BindingFactor<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("BindingFactor")
.field(&hex::encode(self.serialize()))
.finish()
}
}
/// A list of binding factors and their associated identifiers.
#[derive(Clone)]
pub struct BindingFactorList<C: Ciphersuite>(BTreeMap<Identifier<C>, BindingFactor<C>>);
impl<C> BindingFactorList<C>
where
C: Ciphersuite,
{
/// Create a new [`BindingFactorList`] from a map of identifiers to binding factors.
#[cfg(feature = "internals")]
pub fn new(binding_factors: BTreeMap<Identifier<C>, BindingFactor<C>>) -> Self {
Self(binding_factors)
}
/// Return iterator through all factors.
pub fn iter(&self) -> impl Iterator<Item = (&Identifier<C>, &BindingFactor<C>)> {
self.0.iter()
}
/// Get the [`BindingFactor`] for the given identifier, or None if not found.
pub fn get(&self, key: &Identifier<C>) -> Option<&BindingFactor<C>> {
self.0.get(key)
}
}
/// [`compute_binding_factors`] in the spec
///
/// [`compute_binding_factors`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.4
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn compute_binding_factor_list<C>(
signing_package: &SigningPackage<C>,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> BindingFactorList<C>
where
C: Ciphersuite,
{
let preimages = signing_package.binding_factor_preimages(verifying_key, additional_prefix);
BindingFactorList(
preimages
.iter()
.map(|(identifier, preimage)| {
let binding_factor = C::H1(preimage);
(*identifier, BindingFactor(binding_factor))
})
.collect(),
)
}
#[cfg(any(test, feature = "test-impl"))]
impl<C> FromHex for BindingFactor<C>
where
C: Ciphersuite,
{
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let v: Vec<u8> = FromHex::from_hex(hex).map_err(|_| "invalid hex")?;
match v.try_into() {
Ok(bytes) => Self::deserialize(bytes).map_err(|_| "malformed scalar encoding"),
Err(_) => Err("malformed scalar encoding"),
}
}
}
/// Generates a lagrange coefficient.
///
/// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k
/// is ∑_{i=0}^k y_i._i(x), where _i(x) is the Lagrange basis polynomial:
///
/// _i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j).
///
/// This computes _j(x) for the set of points `xs` and for the j corresponding
/// to the given xj.
///
/// If `x` is None, it uses 0 for it (since Identifiers can't be 0)
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_lagrange_coefficient<C: Ciphersuite>(
x_set: &BTreeSet<Identifier<C>>,
x: Option<Identifier<C>>,
x_i: Identifier<C>,
) -> Result<Scalar<C>, Error<C>> {
if x_set.is_empty() {
return Err(Error::IncorrectNumberOfIdentifiers);
}
let mut num = <<C::Group as Group>::Field>::one();
let mut den = <<C::Group as Group>::Field>::one();
let mut x_i_found = false;
for x_j in x_set.iter() {
if x_i == *x_j {
x_i_found = true;
continue;
}
if let Some(x) = x {
num *= x - *x_j;
den *= x_i - *x_j;
} else {
// Both signs inverted just to avoid requiring Neg (-*xj)
num *= *x_j;
den *= *x_j - x_i;
}
}
if !x_i_found {
return Err(Error::UnknownIdentifier);
}
Ok(
num * <<C::Group as Group>::Field>::invert(&den)
.map_err(|_| Error::DuplicatedIdentifier)?,
)
}
/// Generates the lagrange coefficient for the i'th participant (for `signer_id`).
///
/// Implements [`derive_interpolating_value()`] from the spec.
///
/// [`derive_interpolating_value()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-polynomials
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn derive_interpolating_value<C: Ciphersuite>(
signer_id: &Identifier<C>,
signing_package: &SigningPackage<C>,
) -> Result<Scalar<C>, Error<C>> {
compute_lagrange_coefficient(
&signing_package
.signing_commitments()
.keys()
.cloned()
.collect(),
None,
*signer_id,
)
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct SigningPackage<C: Ciphersuite> {
/// Serialization header
#[getter(skip)]
pub(crate) header: Header<C>,
/// The set of commitments participants published in the first round of the
/// protocol.
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
/// Message which each participant will sign.
///
/// Each signer should perform protocol-specific verification on the
/// message.
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)
)]
message: Vec<u8>,
}
impl<C> SigningPackage<C>
where
C: Ciphersuite,
{
/// Create a new `SigningPackage`
///
/// The `signing_commitments` are sorted by participant `identifier`.
pub fn new(
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
message: &[u8],
) -> SigningPackage<C> {
SigningPackage {
header: Header::default(),
signing_commitments,
message: message.to_vec(),
}
}
/// Get a signing commitment by its participant identifier, or None if not found.
pub fn signing_commitment(
&self,
identifier: &Identifier<C>,
) -> Option<round1::SigningCommitments<C>> {
self.signing_commitments.get(identifier).copied()
}
/// Compute the preimages to H1 to compute the per-signer binding factors
// We separate this out into its own method so it can be tested
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub fn binding_factor_preimages(
&self,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> Vec<(Identifier<C>, Vec<u8>)> {
let mut binding_factor_input_prefix = vec![];
// The length of a serialized verifying key of the same cipersuite does
// not change between runs of the protocol, so we don't need to hash to
// get a fixed length.
binding_factor_input_prefix.extend_from_slice(verifying_key.serialize().as_ref());
// The message is hashed with H4 to force the variable-length message
// into a fixed-length byte string, same for hashing the variable-sized
// (between runs of the protocol) set of group commitments, but with H5.
binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref());
binding_factor_input_prefix.extend_from_slice(
C::H5(&round1::encode_group_commitments(self.signing_commitments())[..]).as_ref(),
);
binding_factor_input_prefix.extend_from_slice(additional_prefix);
self.signing_commitments()
.keys()
.map(|identifier| {
let mut binding_factor_input = vec![];
binding_factor_input.extend_from_slice(&binding_factor_input_prefix);
binding_factor_input.extend_from_slice(identifier.serialize().as_ref());
(*identifier, binding_factor_input)
})
.collect()
}
}
#[cfg(feature = "serialization")]
impl<C> SigningPackage<C>
where
C: Ciphersuite,
{
/// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Serialize::serialize(&self)
}
/// Deserialize the struct from a slice of bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
Deserialize::deserialize(bytes)
}
}
/// The product of all signers' individual commitments, published as part of the
/// final signature.
#[derive(Clone, PartialEq, Eq)]
pub struct GroupCommitment<C: Ciphersuite>(pub(super) Element<C>);
impl<C> GroupCommitment<C>
where
C: Ciphersuite,
{
/// Return the underlying element.
#[cfg(feature = "internals")]
pub fn to_element(self) -> <C::Group as Group>::Element {
self.0
}
}
/// 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-14.html#section-4.5
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_group_commitment<C>(
signing_package: &SigningPackage<C>,
binding_factor_list: &BindingFactorList<C>,
) -> Result<GroupCommitment<C>, Error<C>>
where
C: Ciphersuite,
{
let identity = <C::Group as Group>::identity();
let mut group_commitment = <C::Group as Group>::identity();
// Number of signing participants we are iterating over.
let n = signing_package.signing_commitments().len();
let mut binding_scalars = Vec::with_capacity(n);
let mut binding_elements = Vec::with_capacity(n);
for (commitment_identifier, 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(Error::IdentityCommitment);
}
let binding_factor = binding_factor_list
.get(commitment_identifier)
.ok_or(Error::UnknownIdentifier)?;
// Collect the binding commitments and their binding factors for one big
// multiscalar multiplication at the end.
binding_elements.push(commitment.binding.0);
binding_scalars.push(binding_factor.0);
group_commitment = group_commitment + commitment.hiding.0;
}
let accumulated_binding_commitment: Element<C> =
VartimeMultiscalarMul::<C>::vartime_multiscalar_mul(binding_scalars, binding_elements);
group_commitment = group_commitment + accumulated_binding_commitment;
Ok(GroupCommitment(group_commitment))
}
////////////////////////////////////////////////////////////////////////////////
// Aggregation
////////////////////////////////////////////////////////////////////////////////
/// Aggregates the signature shares to produce a final signature that
/// can be verified with the group public key.
///
/// `signature_shares` maps the identifier of each participant to the
/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping
/// the coordinator has between communication channels and participants, i.e.
/// they must have assurance that the [`round2::SignatureShare`] came from
/// the participant with that identifier.
///
/// 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<C>(
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
pubkeys: &keys::PublicKeyPackage<C>,
) -> Result<Signature<C>, Error<C>>
where
C: Ciphersuite,
{
// Check if signing_package.signing_commitments and signature_shares have
// the same set of identifiers, and if they are all in pubkeys.verifying_shares.
if signing_package.signing_commitments().len() != signature_shares.len() {
return Err(Error::UnknownIdentifier);
}
if !signing_package.signing_commitments().keys().all(|id| {
#[cfg(feature = "cheater-detection")]
return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id);
#[cfg(not(feature = "cheater-detection"))]
return signature_shares.contains_key(id);
}) {
return Err(Error::UnknownIdentifier);
}
// Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
// binding factor.
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[]);
// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
//
// Implements [`aggregate`] from the spec.
//
// [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3
let mut z = <<C::Group as Group>::Field>::zero();
for signature_share in signature_shares.values() {
z = z + signature_share.share;
}
let signature = Signature {
R: group_commitment.0,
z,
};
// Verify the aggregate signature
let verification_result = pubkeys
.verifying_key
.verify(signing_package.message(), &signature);
// Only if the verification of the aggregate signature failed; verify each share to find the cheater.
// This approach is more efficient since we don't need to verify all shares
// if the aggregate signature is valid (which should be the common case).
#[cfg(feature = "cheater-detection")]
if let Err(err) = verification_result {
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);
// Verify the signature shares.
for (signature_share_identifier, signature_share) in signature_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
.verifying_shares
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;
// Compute Lagrange coefficient.
let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?;
let binding_factor = binding_factor_list
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;
// Compute the commitment share.
let R_share = signing_package
.signing_commitment(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?
.to_group_commitment_share(binding_factor);
// Compute relation values to verify this signature share.
signature_share.verify(
*signature_share_identifier,
&R_share,
signer_pubkey,
lambda_i,
&challenge,
)?;
}
// We should never reach here; but we return the verification error to be safe.
return Err(err);
}
#[cfg(not(feature = "cheater-detection"))]
verification_result?;
Ok(signature)
}

View File

@ -17,8 +17,8 @@ use rand_core::{CryptoRng, RngCore};
use zeroize::{DefaultIsZeroes, Zeroize};
use crate::{
frost::Identifier, Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Scalar,
Serialize, SigningKey, VerifyingKey,
Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Identifier, Scalar, Serialize,
SigningKey, VerifyingKey,
};
#[cfg(feature = "serde")]

View File

@ -35,8 +35,8 @@ use std::{collections::BTreeMap, iter};
use rand_core::{CryptoRng, RngCore};
use crate::{
frost::Identifier, Challenge, Ciphersuite, Element, Error, Field, Group, Header, Scalar,
Signature, SigningKey, VerifyingKey,
Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
SigningKey, VerifyingKey,
};
use super::{

View File

@ -7,8 +7,8 @@
use std::collections::{BTreeMap, BTreeSet};
use crate::{
frost::{compute_lagrange_coefficient, Identifier},
Ciphersuite, CryptoRng, Error, Field, Group, Header, RngCore, Scalar,
compute_lagrange_coefficient, Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier,
RngCore, Scalar,
};
use super::{generate_coefficients, SecretShare, SigningShare, VerifiableSecretSharingCommitment};

View File

@ -11,13 +11,18 @@
#![doc = document_features::document_features!()]
use std::{
collections::{BTreeMap, BTreeSet},
default::Default,
fmt::Debug,
fmt::{self, Debug},
marker::PhantomData,
ops::{Add, Mul, Sub},
};
use derive_getters::Getters;
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroize;
// Re-export serde
#[cfg(feature = "serde")]
@ -27,7 +32,10 @@ pub mod batch;
#[cfg(any(test, feature = "test-impl"))]
pub mod benches;
mod error;
pub mod frost;
mod identifier;
pub mod keys;
pub mod round1;
pub mod round2;
mod scalar_mul;
mod signature;
mod signing_key;
@ -35,11 +43,12 @@ mod signing_key;
pub mod tests;
mod verifying_key;
pub use self::identifier::Identifier;
use crate::scalar_mul::VartimeMultiscalarMul;
pub use error::{Error, FieldError, GroupError};
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use verifying_key::VerifyingKey;
use zeroize::Zeroize;
/// A prime order finite field GF(q) over which all scalar values for our prime order group can be
/// multiplied are defined.
@ -560,3 +569,487 @@ impl<T: for<'de> serde::Deserialize<'de>, C: Ciphersuite> Deserialize<C> for T {
postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError)
}
}
/// 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, PartialEq, Eq)]
pub struct BindingFactor<C: Ciphersuite>(Scalar<C>);
impl<C> BindingFactor<C>
where
C: Ciphersuite,
{
/// Deserializes [`BindingFactor`] from bytes.
pub fn deserialize(
bytes: <<C::Group as Group>::Field as Field>::Serialization,
) -> Result<Self, Error<C>> {
<<C::Group as Group>::Field>::deserialize(&bytes)
.map(|scalar| Self(scalar))
.map_err(|e| e.into())
}
/// Serializes [`BindingFactor`] to bytes.
pub fn serialize(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
<<C::Group as Group>::Field>::serialize(&self.0)
}
}
impl<C> Debug for BindingFactor<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("BindingFactor")
.field(&hex::encode(self.serialize()))
.finish()
}
}
/// A list of binding factors and their associated identifiers.
#[derive(Clone)]
pub struct BindingFactorList<C: Ciphersuite>(BTreeMap<Identifier<C>, BindingFactor<C>>);
impl<C> BindingFactorList<C>
where
C: Ciphersuite,
{
/// Create a new [`BindingFactorList`] from a map of identifiers to binding factors.
#[cfg(feature = "internals")]
pub fn new(binding_factors: BTreeMap<Identifier<C>, BindingFactor<C>>) -> Self {
Self(binding_factors)
}
/// Return iterator through all factors.
pub fn iter(&self) -> impl Iterator<Item = (&Identifier<C>, &BindingFactor<C>)> {
self.0.iter()
}
/// Get the [`BindingFactor`] for the given identifier, or None if not found.
pub fn get(&self, key: &Identifier<C>) -> Option<&BindingFactor<C>> {
self.0.get(key)
}
}
/// [`compute_binding_factors`] in the spec
///
/// [`compute_binding_factors`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.4
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn compute_binding_factor_list<C>(
signing_package: &SigningPackage<C>,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> BindingFactorList<C>
where
C: Ciphersuite,
{
let preimages = signing_package.binding_factor_preimages(verifying_key, additional_prefix);
BindingFactorList(
preimages
.iter()
.map(|(identifier, preimage)| {
let binding_factor = C::H1(preimage);
(*identifier, BindingFactor(binding_factor))
})
.collect(),
)
}
#[cfg(any(test, feature = "test-impl"))]
impl<C> FromHex for BindingFactor<C>
where
C: Ciphersuite,
{
type Error = &'static str;
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
let v: Vec<u8> = FromHex::from_hex(hex).map_err(|_| "invalid hex")?;
match v.try_into() {
Ok(bytes) => Self::deserialize(bytes).map_err(|_| "malformed scalar encoding"),
Err(_) => Err("malformed scalar encoding"),
}
}
}
/// Generates a lagrange coefficient.
///
/// The Lagrange polynomial for a set of points (x_j, y_j) for 0 <= j <= k
/// is ∑_{i=0}^k y_i._i(x), where _i(x) is the Lagrange basis polynomial:
///
/// _i(x) = ∏_{0≤j≤k; j≠i} (x - x_j) / (x_i - x_j).
///
/// This computes _j(x) for the set of points `xs` and for the j corresponding
/// to the given xj.
///
/// If `x` is None, it uses 0 for it (since Identifiers can't be 0)
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_lagrange_coefficient<C: Ciphersuite>(
x_set: &BTreeSet<Identifier<C>>,
x: Option<Identifier<C>>,
x_i: Identifier<C>,
) -> Result<Scalar<C>, Error<C>> {
if x_set.is_empty() {
return Err(Error::IncorrectNumberOfIdentifiers);
}
let mut num = <<C::Group as Group>::Field>::one();
let mut den = <<C::Group as Group>::Field>::one();
let mut x_i_found = false;
for x_j in x_set.iter() {
if x_i == *x_j {
x_i_found = true;
continue;
}
if let Some(x) = x {
num *= x - *x_j;
den *= x_i - *x_j;
} else {
// Both signs inverted just to avoid requiring Neg (-*xj)
num *= *x_j;
den *= *x_j - x_i;
}
}
if !x_i_found {
return Err(Error::UnknownIdentifier);
}
Ok(
num * <<C::Group as Group>::Field>::invert(&den)
.map_err(|_| Error::DuplicatedIdentifier)?,
)
}
/// Generates the lagrange coefficient for the i'th participant (for `signer_id`).
///
/// Implements [`derive_interpolating_value()`] from the spec.
///
/// [`derive_interpolating_value()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-polynomials
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn derive_interpolating_value<C: Ciphersuite>(
signer_id: &Identifier<C>,
signing_package: &SigningPackage<C>,
) -> Result<Scalar<C>, Error<C>> {
compute_lagrange_coefficient(
&signing_package
.signing_commitments()
.keys()
.cloned()
.collect(),
None,
*signer_id,
)
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct SigningPackage<C: Ciphersuite> {
/// Serialization header
#[getter(skip)]
pub(crate) header: Header<C>,
/// The set of commitments participants published in the first round of the
/// protocol.
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
/// Message which each participant will sign.
///
/// Each signer should perform protocol-specific verification on the
/// message.
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)
)]
message: Vec<u8>,
}
impl<C> SigningPackage<C>
where
C: Ciphersuite,
{
/// Create a new `SigningPackage`
///
/// The `signing_commitments` are sorted by participant `identifier`.
pub fn new(
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
message: &[u8],
) -> SigningPackage<C> {
SigningPackage {
header: Header::default(),
signing_commitments,
message: message.to_vec(),
}
}
/// Get a signing commitment by its participant identifier, or None if not found.
pub fn signing_commitment(
&self,
identifier: &Identifier<C>,
) -> Option<round1::SigningCommitments<C>> {
self.signing_commitments.get(identifier).copied()
}
/// Compute the preimages to H1 to compute the per-signer binding factors
// We separate this out into its own method so it can be tested
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub fn binding_factor_preimages(
&self,
verifying_key: &VerifyingKey<C>,
additional_prefix: &[u8],
) -> Vec<(Identifier<C>, Vec<u8>)> {
let mut binding_factor_input_prefix = vec![];
// The length of a serialized verifying key of the same cipersuite does
// not change between runs of the protocol, so we don't need to hash to
// get a fixed length.
binding_factor_input_prefix.extend_from_slice(verifying_key.serialize().as_ref());
// The message is hashed with H4 to force the variable-length message
// into a fixed-length byte string, same for hashing the variable-sized
// (between runs of the protocol) set of group commitments, but with H5.
binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref());
binding_factor_input_prefix.extend_from_slice(
C::H5(&round1::encode_group_commitments(self.signing_commitments())[..]).as_ref(),
);
binding_factor_input_prefix.extend_from_slice(additional_prefix);
self.signing_commitments()
.keys()
.map(|identifier| {
let mut binding_factor_input = vec![];
binding_factor_input.extend_from_slice(&binding_factor_input_prefix);
binding_factor_input.extend_from_slice(identifier.serialize().as_ref());
(*identifier, binding_factor_input)
})
.collect()
}
}
#[cfg(feature = "serialization")]
impl<C> SigningPackage<C>
where
C: Ciphersuite,
{
/// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Serialize::serialize(&self)
}
/// Deserialize the struct from a slice of bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
Deserialize::deserialize(bytes)
}
}
/// The product of all signers' individual commitments, published as part of the
/// final signature.
#[derive(Clone, PartialEq, Eq)]
pub struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);
impl<C> GroupCommitment<C>
where
C: Ciphersuite,
{
/// Return the underlying element.
#[cfg(feature = "internals")]
pub fn to_element(self) -> <C::Group as Group>::Element {
self.0
}
}
/// 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-14.html#section-4.5
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_group_commitment<C>(
signing_package: &SigningPackage<C>,
binding_factor_list: &BindingFactorList<C>,
) -> Result<GroupCommitment<C>, Error<C>>
where
C: Ciphersuite,
{
let identity = <C::Group as Group>::identity();
let mut group_commitment = <C::Group as Group>::identity();
// Number of signing participants we are iterating over.
let n = signing_package.signing_commitments().len();
let mut binding_scalars = Vec::with_capacity(n);
let mut binding_elements = Vec::with_capacity(n);
for (commitment_identifier, 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(Error::IdentityCommitment);
}
let binding_factor = binding_factor_list
.get(commitment_identifier)
.ok_or(Error::UnknownIdentifier)?;
// Collect the binding commitments and their binding factors for one big
// multiscalar multiplication at the end.
binding_elements.push(commitment.binding.0);
binding_scalars.push(binding_factor.0);
group_commitment = group_commitment + commitment.hiding.0;
}
let accumulated_binding_commitment: Element<C> =
VartimeMultiscalarMul::<C>::vartime_multiscalar_mul(binding_scalars, binding_elements);
group_commitment = group_commitment + accumulated_binding_commitment;
Ok(GroupCommitment(group_commitment))
}
////////////////////////////////////////////////////////////////////////////////
// Aggregation
////////////////////////////////////////////////////////////////////////////////
/// Aggregates the signature shares to produce a final signature that
/// can be verified with the group public key.
///
/// `signature_shares` maps the identifier of each participant to the
/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping
/// the coordinator has between communication channels and participants, i.e.
/// they must have assurance that the [`round2::SignatureShare`] came from
/// the participant with that identifier.
///
/// 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<C>(
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
pubkeys: &keys::PublicKeyPackage<C>,
) -> Result<Signature<C>, Error<C>>
where
C: Ciphersuite,
{
// Check if signing_package.signing_commitments and signature_shares have
// the same set of identifiers, and if they are all in pubkeys.verifying_shares.
if signing_package.signing_commitments().len() != signature_shares.len() {
return Err(Error::UnknownIdentifier);
}
if !signing_package.signing_commitments().keys().all(|id| {
#[cfg(feature = "cheater-detection")]
return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id);
#[cfg(not(feature = "cheater-detection"))]
return signature_shares.contains_key(id);
}) {
return Err(Error::UnknownIdentifier);
}
// Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
// binding factor.
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[]);
// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
//
// Implements [`aggregate`] from the spec.
//
// [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3
let mut z = <<C::Group as Group>::Field>::zero();
for signature_share in signature_shares.values() {
z = z + signature_share.share;
}
let signature = Signature {
R: group_commitment.0,
z,
};
// Verify the aggregate signature
let verification_result = pubkeys
.verifying_key
.verify(signing_package.message(), &signature);
// Only if the verification of the aggregate signature failed; verify each share to find the cheater.
// This approach is more efficient since we don't need to verify all shares
// if the aggregate signature is valid (which should be the common case).
#[cfg(feature = "cheater-detection")]
if let Err(err) = verification_result {
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);
// Verify the signature shares.
for (signature_share_identifier, signature_share) in signature_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
.verifying_shares
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;
// Compute Lagrange coefficient.
let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?;
let binding_factor = binding_factor_list
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;
// Compute the commitment share.
let R_share = signing_package
.signing_commitment(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?
.to_group_commitment_share(binding_factor);
// Compute relation values to verify this signature share.
signature_share.verify(
*signature_share_identifier,
&R_share,
signer_pubkey,
lambda_i,
&challenge,
)?;
}
// We should never reach here; but we return the verification error to be safe.
return Err(err);
}
#[cfg(not(feature = "cheater-detection"))]
verification_result?;
Ok(signature)
}

View File

@ -12,9 +12,8 @@ use hex::FromHex;
use rand_core::{CryptoRng, RngCore};
use zeroize::Zeroize;
use crate::{
frost, Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Scalar, Serialize,
};
use crate as frost;
use crate::{Ciphersuite, Deserialize, Element, Error, Field, Group, Header, Scalar, Serialize};
#[cfg(feature = "serde")]
use crate::ElementSerialization;

View File

@ -2,10 +2,9 @@
use std::fmt::{self, Debug};
use crate as frost;
use crate::{
challenge,
frost::{self, round1, *},
Challenge, Ciphersuite, Error, Field, Group,
challenge, Challenge, Ciphersuite, Error, Field, Group, {round1, *},
};
#[cfg(feature = "serde")]

View File

@ -3,9 +3,9 @@
use std::{collections::BTreeMap, convert::TryFrom};
use crate as frost;
use crate::{
frost::{self, keys::PublicKeyPackage, Identifier},
Error, Field, Group, Signature, SigningKey, VerifyingKey,
keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, VerifyingKey,
};
use rand_core::{CryptoRng, RngCore};

View File

@ -2,11 +2,8 @@
use std::convert::TryFrom;
use crate::{
frost::{self, keys::CoefficientCommitment},
tests::helpers::generate_element,
Group,
};
use crate as frost;
use crate::{keys::CoefficientCommitment, tests::helpers::generate_element, Group};
use debugless_unwrap::DebuglessUnwrap;
use rand_core::{CryptoRng, RngCore};
use serde_json::Value;

View File

@ -6,16 +6,14 @@ use debugless_unwrap::DebuglessUnwrap;
use rand_core::{CryptoRng, RngCore};
use serde_json::Value;
use crate as frost;
use crate::{
frost::{
self, compute_lagrange_coefficient,
keys::{
repairable::{repair_share_step_1, repair_share_step_2, repair_share_step_3},
PublicKeyPackage, SecretShare, SigningShare,
},
Identifier,
compute_lagrange_coefficient,
keys::{
repairable::{repair_share_step_1, repair_share_step_2, repair_share_step_3},
PublicKeyPackage, SecretShare, SigningShare,
},
Ciphersuite, Error, Field, Group, Scalar,
Ciphersuite, Error, Field, Group, Identifier, Scalar,
};
/// We want to test that recover share matches the original share

View File

@ -5,9 +5,9 @@ use debugless_unwrap::DebuglessUnwrap;
use hex::{self, FromHex};
use serde_json::Value;
use crate as frost;
use crate::{
frost::{self, keys::*, round1::*, round2::*, *},
Ciphersuite, Field, Group, Scalar, SigningKey, VerifyingKey,
keys::*, round1::*, round2::*, Ciphersuite, Field, Group, Scalar, SigningKey, VerifyingKey, *,
};
/// Test vectors for a ciphersuite.

View File

@ -3,7 +3,7 @@
use std::convert::TryFrom;
use crate::{
frost::keys::{CoefficientCommitment, VerifiableSecretSharingCommitment},
keys::{CoefficientCommitment, VerifiableSecretSharingCommitment},
tests::helpers::generate_element,
Group,
};
@ -11,7 +11,7 @@ use debugless_unwrap::DebuglessUnwrap;
use rand_core::{CryptoRng, RngCore};
use serde_json::Value;
use crate::frost::keys::{generate_with_dealer, IdentifierList, PublicKeyPackage};
use crate::keys::{generate_with_dealer, IdentifierList, PublicKeyPackage};
use crate::Ciphersuite;
/// Test serialize VerifiableSecretSharingCommitment

View File

@ -82,7 +82,7 @@ where
/// Computes the group public key given the group commitment.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn from_commitment(
commitment: &crate::frost::keys::VerifiableSecretSharingCommitment<C>,
commitment: &crate::keys::VerifiableSecretSharingCommitment<C>,
) -> Result<VerifyingKey<C>, Error<C>> {
Ok(VerifyingKey {
element: commitment

View File

@ -17,7 +17,7 @@ use frost_rerandomized::RandomizedCiphersuite;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
use frost_core::frost;
use frost_core as frost;
#[cfg(test)]
mod tests;

View File

@ -18,7 +18,7 @@ use sha3::{
Shake256,
};
use frost_core::frost;
use frost_core as frost;
#[cfg(test)]
mod tests;

View File

@ -19,7 +19,7 @@ use p256::{
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use frost_core::frost;
use frost_core as frost;
#[cfg(test)]
mod tests;

View File

@ -20,12 +20,9 @@ use derive_getters::Getters;
pub use frost_core;
use frost_core::{
frost::{
self,
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
SigningPackage,
},
Ciphersuite, Error, Field, Group, Scalar, VerifyingKey,
self as frost,
keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare},
Ciphersuite, Error, Field, Group, Scalar, SigningPackage, VerifyingKey,
};
#[cfg(feature = "serde")]

View File

@ -2,8 +2,8 @@
use std::collections::BTreeMap;
use crate::{frost_core::frost, RandomizedCiphersuite, RandomizedParams, Randomizer};
use frost_core::{frost::SigningPackage, Field, Group, Signature, VerifyingKey};
use crate::{frost_core as frost, RandomizedCiphersuite, RandomizedParams, Randomizer};
use frost_core::{Field, Group, Signature, SigningPackage, VerifyingKey};
use rand_core::{CryptoRng, RngCore};
/// Test re-randomized FROST signing with trusted dealer with a Ciphersuite.

View File

@ -14,7 +14,7 @@ use frost_rerandomized::RandomizedCiphersuite;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha512};
use frost_core::frost;
use frost_core as frost;
#[cfg(test)]
mod tests;

View File

@ -20,7 +20,7 @@ use k256::{
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use frost_core::frost;
use frost_core as frost;
#[cfg(test)]
mod tests;

View File

@ -194,7 +194,7 @@ fn main() -> ExitCode {
// Copy the frost-core repairable docs into ristretto255.
// This will then be copied later down into the other ciphersuites.
let repairable_docs = read_docs("frost-core/src/frost/keys/repairable.rs", &[]);
let repairable_docs = read_docs("frost-core/src/keys/repairable.rs", &[]);
replaced |= write_docs(
&repairable_docs,
"frost-ristretto255/src/keys/repairable.rs",