Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add frost-secp256k1-tr crate (BIP340/BIP341) [moved] #730

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"frost-p256",
"frost-ristretto255",
"frost-secp256k1",
"frost-secp256k1-tr",
"frost-rerandomized",
"gencode"
]
12 changes: 8 additions & 4 deletions frost-core/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ where
where
M: AsRef<[u8]>,
{
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::challenge(&sig.R, &vk, msg.as_ref())?;

Ok(Self { vk, sig, c })
let (msg, sig, vk) = <C>::pre_verify(msg.as_ref(), &sig, &vk)?;
let c = <C>::challenge(&sig.R, &vk, &msg)?;

Ok(Self {
vk: *vk,
sig: *sig,
c,
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion frost-core/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ where
}

/// Returns VerifiableSecretSharingCommitment from a iterator of serialized
/// CoefficientCommitments (e.g. a Vec<Vec<u8>>).
/// CoefficientCommitments (e.g. a `Vec<Vec<u8>>`).
pub fn deserialize<I, V>(serialized_coefficient_commitments: I) -> Result<Self, Error<C>>
where
I: IntoIterator<Item = V>,
Expand Down
5 changes: 2 additions & 3 deletions frost-core/src/keys/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,7 @@ pub(crate) fn compute_proof_of_knowledge<C: Ciphersuite, R: RngCore + CryptoRng>
// > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k,
// > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being
// > a context string to prevent replay attacks.
let k = <<C::Group as Group>::Field>::random(&mut rng);
let R_i = <C::Group>::generator() * k;
let (k, R_i) = <C>::generate_nonce(&mut rng);
let c_i = challenge::<C>(identifier, &commitment.verifying_key()?, &R_i)?;
let a_i0 = *coefficients
.first()
Expand Down Expand Up @@ -560,5 +559,5 @@ pub fn part3<C: Ciphersuite>(
min_signers: round2_secret_package.min_signers,
};

Ok((key_package, public_key_package))
C::post_dkg(key_package, public_key_package)
}
55 changes: 32 additions & 23 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,14 @@ use scalar_mul::VartimeMultiscalarMul;
pub use serde;
pub use signature::Signature;
pub use signing_key::SigningKey;
pub use traits::{Ciphersuite, Element, Field, Group, Scalar};
pub use traits::{Ciphersuite, Context, Element, Field, Group, Scalar};
pub use verifying_key::VerifyingKey;

/// A type refinement for the scalar field element representing the per-message _[challenge]_.
///
/// [challenge]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-challenge-computa
#[derive(Copy, Clone)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct Challenge<C: Ciphersuite>(
pub(crate) <<C::Group as Group>::Field as Field>::Scalar,
);
pub struct Challenge<C: Ciphersuite>(pub(crate) <<C::Group as Group>::Field as Field>::Scalar);

impl<C> Challenge<C>
where
Expand Down Expand Up @@ -138,6 +134,8 @@ where
/// Generates a random nonzero scalar.
///
/// It assumes that the Scalar Eq/PartialEq implementation is constant-time.
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn random_nonzero<C: Ciphersuite, R: RngCore + CryptoRng>(rng: &mut R) -> Scalar<C> {
loop {
let scalar = <<C::Group as Group>::Field>::random(rng);
Expand Down Expand Up @@ -192,9 +190,7 @@ where
///
/// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md>
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct BindingFactor<C: Ciphersuite>(Scalar<C>);
pub struct BindingFactor<C: Ciphersuite>(Scalar<C>);

impl<C> BindingFactor<C>
where
Expand Down Expand Up @@ -469,9 +465,7 @@ where
/// The product of all signers' individual commitments, published as part of the
/// final signature.
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);
pub struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);

impl<C> GroupCommitment<C>
where
Expand All @@ -482,6 +476,12 @@ where
pub fn to_element(self) -> <C::Group as Group>::Element {
self.0
}

/// Return the underlying element.
#[cfg(feature = "internals")]
pub fn from_element(element: Element<C>) -> Self {
Self(element)
}
}

/// Generates the group commitment which is published as part of the joint
Expand Down Expand Up @@ -583,12 +583,18 @@ where
return Err(Error::UnknownIdentifier);
}

let mut ctx = C::Context::default();

let (signing_package, signature_shares, pubkeys) =
<C>::pre_aggregate(&mut ctx, signing_package, signature_shares, pubkeys)?;

// 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_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)?;
let group_commitment =
<C>::compute_group_commitment(&mut ctx, &signing_package, &binding_factor_list)?;

// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
Expand Down Expand Up @@ -618,10 +624,11 @@ where
#[cfg(feature = "cheater-detection")]
if verification_result.is_err() {
detect_cheater(
group_commitment,
pubkeys,
signing_package,
signature_shares,
&mut ctx,
&group_commitment,
&pubkeys,
&signing_package,
&signature_shares,
&binding_factor_list,
)?;
}
Expand All @@ -631,21 +638,21 @@ where

Ok(signature)
}

/// Optional cheater detection feature
/// Each share is verified to find the cheater
fn detect_cheater<C: Ciphersuite>(
group_commitment: GroupCommitment<C>,
ctx: &mut C::Context,
group_commitment: &GroupCommitment<C>,
pubkeys: &keys::PublicKeyPackage<C>,
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
binding_factor_list: &BindingFactorList<C>,
) -> Result<(), Error<C>> {
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
let challenge = <C>::challenge(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
signing_package.message(),
)?;

// Verify the signature shares.
Expand All @@ -671,7 +678,9 @@ fn detect_cheater<C: Ciphersuite>(
.to_group_commitment_share(binding_factor);

// Compute relation values to verify this signature share.
signature_share.verify(
<C>::verify_share(
ctx,
signature_share,
*signature_share_identifier,
&R_share,
signer_pubkey,
Expand Down
20 changes: 20 additions & 0 deletions frost-core/src/round1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,16 @@ where
Self::nonce_generate_from_random_bytes(secret, random_bytes)
}

/// Create a nonce from a scalar.
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn from_scalar(scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar) -> Self {
Self(SerializableScalar(scalar))
}

/// Convert a nonce into a scalar.
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
pub(crate) fn to_scalar(
self,
) -> <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
Expand Down Expand Up @@ -358,6 +364,20 @@ where
#[derive(Clone, Copy, PartialEq)]
pub struct GroupCommitmentShare<C: Ciphersuite>(pub(super) Element<C>);

impl<C: Ciphersuite> GroupCommitmentShare<C> {
/// Create from an element.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn from_element(element: Element<C>) -> Self {
Self(element)
}

/// Return the underlying element.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn to_element(self) -> Element<C> {
self.0
}
}

/// Encode the list of group signing commitments.
///
/// Implements [`encode_group_commitment_list()`] from the spec.
Expand Down
30 changes: 19 additions & 11 deletions frost-core/src/round2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::fmt::{self, Debug};

use crate as frost;
use crate::{
challenge, Challenge, Ciphersuite, Error, Field, Group, {round1, *},
Challenge, Ciphersuite, Error, Field, Group, {round1, *},
};

/// A participant's signature share, which the coordinator will aggregate with all other signer's
Expand Down Expand Up @@ -71,7 +71,8 @@ where
challenge: &Challenge<C>,
) -> Result<(), Error<C>> {
if (<C::Group>::generator() * self.to_scalar())
!= (group_commitment_share.0 + (verifying_share.to_element() * challenge.0 * lambda_i))
!= (group_commitment_share.to_element()
+ (verifying_share.to_element() * challenge.0 * lambda_i))
Comment on lines +74 to +75
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notes:

  • The group commitment is negated in the ciphersuite verify_share method
  • The verifying share is negated in the aggregate function, via the ciphersuite pre_aggregate method

{
return Err(Error::InvalidSignatureShare {
culprit: identifier,
Expand All @@ -96,7 +97,7 @@ where
/// Compute the signature share for a signing operation.
#[cfg_attr(feature = "internals", visibility::make(pub))]
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
fn compute_signature_share<C: Ciphersuite>(
pub(super) fn compute_signature_share<C: Ciphersuite>(
signer_nonces: &round1::SigningNonces<C>,
binding_factor: BindingFactor<C>,
lambda_i: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
Expand Down Expand Up @@ -142,34 +143,41 @@ pub fn sign<C: Ciphersuite>(
return Err(Error::IncorrectCommitment);
}

let mut ctx = C::Context::default();

let (signing_package, signer_nonces, key_package) =
<C>::pre_sign(&mut ctx, signing_package, signer_nonces, key_package)?;

// 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, &key_package.verifying_key, &[])?;
compute_binding_factor_list(&signing_package, &key_package.verifying_key, &[])?;
let binding_factor: frost::BindingFactor<C> = binding_factor_list
.get(&key_package.identifier)
.ok_or(Error::UnknownIdentifier)?
.clone();

// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
let group_commitment =
<C>::compute_group_commitment(&mut ctx, &signing_package, &binding_factor_list)?;

// Compute Lagrange coefficient.
let lambda_i = frost::derive_interpolating_value(key_package.identifier(), signing_package)?;
let lambda_i = frost::derive_interpolating_value(key_package.identifier(), &signing_package)?;

// Compute the per-message challenge.
let challenge = challenge::<C>(
let challenge = <C>::challenge(
&group_commitment.0,
&key_package.verifying_key,
signing_package.message.as_slice(),
signing_package.message(),
)?;

// Compute the Schnorr signature share.
let signature_share = compute_signature_share(
signer_nonces,
let signature_share = <C>::compute_signature_share(
&mut ctx,
&signer_nonces,
binding_factor,
lambda_i,
key_package,
&key_package,
challenge,
);

Expand Down
24 changes: 19 additions & 5 deletions frost-core/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Schnorr signatures over prime order groups (or subgroups)

use alloc::{string::ToString, vec::Vec};
use derive_getters::Getters;

use crate::{Ciphersuite, Element, Error, Field, Group, Scalar};

/// A Schnorr signature over some prime order group (or subgroup).
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, PartialEq, Getters)]
pub struct Signature<C: Ciphersuite> {
/// The commitment `R` to the signature nonce.
pub(crate) R: Element<C>,
Expand All @@ -29,8 +30,10 @@ where
Self { R, z }
}

/// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature<C>`.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
/// Converts default-encoded bytes as
/// [`Ciphersuite::SignatureSerialization`] into a `Signature<C>`.
#[cfg(feature = "internals")]
pub fn default_deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
// To compute the expected length of the encoded point, encode the generator
// and get its length. Note that we can't use the identity because it can be encoded
// shorter in some cases (e.g. P-256, which uses SEC1 encoding).
Expand Down Expand Up @@ -66,15 +69,26 @@ where
})
}

/// Converts this signature to its byte serialization.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
/// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature<C>`.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
C::deserialize_signature(bytes)
}

/// Converts this signature to its default byte serialization.
#[cfg(feature = "internals")]
pub fn default_serialize(&self) -> Result<Vec<u8>, Error<C>> {
let mut bytes = Vec::<u8>::new();

bytes.extend(<C::Group>::serialize(&self.R)?.as_ref());
bytes.extend(<<C::Group as Group>::Field>::serialize(&self.z).as_ref());

Ok(bytes)
}

/// Converts this signature to its byte serialization.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
<C>::serialize_signature(self)
}
}

#[cfg(feature = "serde")]
Expand Down
19 changes: 13 additions & 6 deletions frost-core/src/signing_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use alloc::vec::Vec;
use rand_core::{CryptoRng, RngCore};

use crate::{
random_nonzero, serialization::SerializableScalar, Ciphersuite, Error, Field, Group, Scalar,
Signature, VerifyingKey,
random_nonzero, serialization::SerializableScalar, Challenge, Ciphersuite, Error, Field, Group,
Scalar, Signature, VerifyingKey,
};

/// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`].
Expand Down Expand Up @@ -40,13 +40,20 @@ where
}

/// Create a signature `msg` using this `SigningKey`.
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<C> {
let k = random_nonzero::<C, R>(&mut rng);
pub fn sign<R: RngCore + CryptoRng>(&self, rng: R, message: &[u8]) -> Signature<C> {
<C>::single_sign(self, rng, message)
}

/// Create a signature `msg` using this `SigningKey` using the default
/// signing.
#[cfg(feature = "internals")]
pub fn default_sign<R: RngCore + CryptoRng>(&self, mut rng: R, message: &[u8]) -> Signature<C> {
let public = VerifyingKey::<C>::from(*self);

let R = <C::Group>::generator() * k;
let (k, R) = <C>::generate_nonce(&mut rng);

// Generate Schnorr challenge
let c = crate::challenge::<C>(&R, &VerifyingKey::<C>::from(*self), msg).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero.");
let c: Challenge<C> = <C>::challenge(&R, &public, message).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero.");

let z = k + (c.0 * self.scalar);

Expand Down
Loading