Skip to content

Commit

Permalink
Add frost-secp256k1-tr crate (BIP340/BIP341) [moved] (#730)
Browse files Browse the repository at this point in the history
* modify frost-core traits to enable taproot compatibility

This commit contains changes to the frost-core crate which
allow ciphersuites to better customize how signatures are computed.
This will enable taproot support without requiring major changes
to existing frost ciphersuites.

Co-authored by @zebra-lucky and @mimoo

This work sponsored by dlcbtc.com and lightspark.com

* add frost-secp256k1-tr crate and ciphersuite

Co-authored by @zebra-lucky and @mimoo

This work sponsored by dlcbtc.com and lightspark.com

* test coverage for taproot crate

Co-authored by @zebra-lucky and @mimoo

This work sponsored by dlcbtc.com and lightspark.com

* clippy fixes

* tweak DKG output to avoid rogue taproot tweaks

* add interoperability tests

* cleanup taproot implementation to minimize impact in frost_core

* Update PoK test vector to use nonce which generates an even-parity point

Uses r = e99ae2676eab512a3572c7b7655d633642a717250af57a7e0ccd5f9618b69f3f

* BIP341 key package tweaks shouldn't cause key negation

* prune the Context type, instead negate signature.R before verifying

With a couple of small adjustments to the code, we can remove the
need for this extra associated type on the Ciphersuite crate. Accepting
signature with odd-parity nonce values is OK, because BIP340 discard
the nonce parity bit anyway.

* proper TapTweak point-addition operates on even internal key representation

Thanks to @conradoplg for spotting this. The internal key is supposed
to be represented as an even-parity point when adding the TapTweak
point t*G. I added a regression test to ensure the tweaked verifying
key and its parity match the BIP341 spec.

* clippy test fixes

* fix no-std issues and warnings

---------

Co-authored-by: Conrado Gouvea <[email protected]>
  • Loading branch information
conduition and conradoplg authored Nov 14, 2024
1 parent 958fde3 commit c88fadd
Show file tree
Hide file tree
Showing 61 changed files with 4,060 additions and 125 deletions.
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"
]
Expand Down
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
5 changes: 2 additions & 3 deletions frost-core/src/keys/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,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 @@ -570,5 +569,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)
}
81 changes: 55 additions & 26 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use alloc::{
use derive_getters::Getters;
#[cfg(any(test, feature = "test-impl"))]
use hex::FromHex;
use keys::PublicKeyPackage;
use rand_core::{CryptoRng, RngCore};
use serialization::SerializableScalar;
use zeroize::Zeroize;
Expand Down Expand Up @@ -64,11 +65,7 @@ pub use verifying_key::VerifyingKey;
///
/// [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 +135,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 +191,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 +466,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 @@ -483,6 +478,12 @@ where
pub(crate) 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 @@ -584,12 +585,15 @@ where
return Err(Error::UnknownIdentifier);
}

let (signing_package, signature_shares, pubkeys) =
<C>::pre_aggregate(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 = compute_group_commitment(&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 @@ -619,10 +623,10 @@ where
#[cfg(feature = "cheater-detection")]
if verification_result.is_err() {
detect_cheater(
group_commitment,
pubkeys,
signing_package,
signature_shares,
&group_commitment,
&pubkeys,
&signing_package,
&signature_shares,
&binding_factor_list,
)?;
}
Expand All @@ -637,17 +641,17 @@ where
/// Each share is verified to find the cheater
#[cfg(feature = "cheater-detection")]
fn detect_cheater<C: Ciphersuite>(
group_commitment: GroupCommitment<C>,
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 @@ -663,6 +667,7 @@ fn detect_cheater<C: Ciphersuite>(
*identifier,
signing_package,
binding_factor_list,
group_commitment,
signature_share,
verifying_share,
challenge,
Expand All @@ -688,24 +693,44 @@ pub fn verify_signature_share<C: Ciphersuite>(
signing_package: &SigningPackage<C>,
verifying_key: &VerifyingKey<C>,
) -> Result<(), Error<C>> {
// In order to reuse `pre_aggregate()`, we need to create some "dummy" containers
let signature_shares = BTreeMap::from([(identifier, *signature_share)]);
let verifying_shares = BTreeMap::from([(identifier, *verifying_share)]);
let public_key_package = PublicKeyPackage::new(verifying_shares, *verifying_key);

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

// Extract the processed values back from the "dummy" containers
let verifying_share = pubkeys
.verifying_shares()
.get(&identifier)
.expect("pre_aggregate() must keep the identifiers");
let verifying_key = pubkeys.verifying_key();
let signature_share = signature_shares
.get(&identifier)
.expect("pre_aggregate() must keep the identifiers");

// 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, verifying_key, &[])?;
compute_binding_factor_list(&signing_package, 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 = compute_group_commitment(&signing_package, &binding_factor_list)?;

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

verify_signature_share_precomputed(
identifier,
signing_package,
&signing_package,
&binding_factor_list,
&group_commitment,
signature_share,
verifying_share,
challenge,
Expand All @@ -720,6 +745,7 @@ fn verify_signature_share_precomputed<C: Ciphersuite>(
signature_share_identifier: Identifier<C>,
signing_package: &SigningPackage<C>,
binding_factor_list: &BindingFactorList<C>,
group_commitment: &GroupCommitment<C>,
signature_share: &round2::SignatureShare<C>,
verifying_share: &keys::VerifyingShare<C>,
challenge: Challenge<C>,
Expand All @@ -735,7 +761,10 @@ fn verify_signature_share_precomputed<C: Ciphersuite>(
.ok_or(Error::UnknownIdentifier)?
.to_group_commitment_share(binding_factor);

signature_share.verify(
// Compute relation values to verify this signature share.
<C>::verify_share(
group_commitment,
signature_share,
signature_share_identifier,
&R_share,
verifying_share,
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
27 changes: 16 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))
{
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,38 @@ pub fn sign<C: Ciphersuite>(
return Err(Error::IncorrectCommitment);
}

let (signing_package, signer_nonces, key_package) =
<C>::pre_sign(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 = compute_group_commitment(&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(
&group_commitment,
&signer_nonces,
binding_factor,
lambda_i,
key_package,
&key_package,
challenge,
);

Expand Down
Loading

0 comments on commit c88fadd

Please sign in to comment.