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) #584

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b380fd5
add frost-secp256k1-tr crate (BIP340/BIP341)
zebra-lucky Nov 20, 2023
ab6b0d0
run cargo fmt on frost-secp256k1-tr
zebra-lucky Dec 25, 2023
8204166
fix use of tweaked public key
mimoo Dec 27, 2023
a307130
additional fixes for use of tweaked pubkey
zebra-lucky Jan 10, 2024
6d8be7c
give more consistent names to taproot functions
zebra-lucky Dec 26, 2023
20da59a
add DKG vector test for frost-secp256k1-tr
zebra-lucky Jan 11, 2024
00cdfe5
cargo fmt
conradoplg Feb 5, 2024
bdc8fb4
fix gencode-related issues
conradoplg Feb 6, 2024
a66b9a2
clippy fixes
conradoplg Feb 6, 2024
142556f
Refactor Ciphersuite taproot methods for universal applicability (#2)
conduition Feb 21, 2024
0ed163f
fix docstrings in frost-core/src/traits.rs
zebra-lucky Feb 22, 2024
c63a3ca
update frost-secp256-tr code to changes from 1.0.0
zebra-lucky Feb 24, 2024
e5b3f5d
encapsulate BIP341 tapscript commitment in new SigningTarget type
conduition Mar 2, 2024
155dfa6
add effective_key method to VerifyingKey
conduition Mar 2, 2024
1268f5c
Fix typo for VerifyingKey.effective_key
zebra-lucky Mar 5, 2024
1c085ba
remove debugging assert_eq from tests/vectors.rs
zebra-lucky Mar 6, 2024
c1b8663
fix reference to SigningTarget.message instead of cloned signing target
conduition Mar 16, 2024
8f52646
remove unneeded Into invocation
conduition Mar 16, 2024
5d2d683
fix reference to internal function
conduition Mar 16, 2024
20c2c98
add integration tests to cover taproot-tweaked signing
conduition Mar 16, 2024
15688ab
ensure taproot signatures always use even nonce points
conduition Apr 24, 2024
d580241
serialize taproot signatures as 64 bytes with x-only nonce
conduition Apr 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Cargo.lock
*~
**/.DS_Store
.vscode/*
*.swp
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"
]
19 changes: 14 additions & 5 deletions frost-core/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *};
pub struct Item<C: Ciphersuite> {
vk: VerifyingKey<C>,
sig: Signature<C>,
sig_params: C::SigningParameters,
c: Challenge<C>,
}

Expand All @@ -32,9 +33,15 @@ where
{
fn from((vk, sig, msg): (VerifyingKey<C>, Signature<C>, &'msg M)) -> Self {
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::challenge(&sig.R, &vk, msg.as_ref());

Self { vk, sig, c }
let sig_target = SigningTarget::from_message(msg);
let c = <C>::challenge(&sig.R, &vk, &sig_target);

Self {
vk,
sig,
sig_params: sig_target.sig_params,
c,
}
}
}

Expand All @@ -50,7 +57,8 @@ where
/// requires borrowing the message data, the `Item` type is unlinked
/// from the lifetime of the message.
pub fn verify_single(self) -> Result<(), Error<C>> {
self.vk.verify_prehashed(self.c, &self.sig)
self.vk
.verify_prehashed(self.c, &self.sig, &self.sig_params)
}
}

Expand Down Expand Up @@ -119,6 +127,7 @@ where
for item in self.signatures.iter() {
let z = item.sig.z;
let R = item.sig.R;
let vk = <C>::effective_pubkey_element(&item.vk, &item.sig_params);

let blind = <<C::Group as Group>::Field>::random(&mut rng);

Expand All @@ -129,7 +138,7 @@ where
Rs.push(R);

VK_coeffs.push(<<C::Group as Group>::Field>::zero() + (blind * item.c.0));
VKs.push(item.vk.element);
VKs.push(vk);
}

let scalars = once(&P_coeff_acc)
Expand Down
10 changes: 10 additions & 0 deletions frost-core/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ where
pub(crate) fn from_coefficients(coefficients: &[Scalar<C>], peer: Identifier<C>) -> Self {
Self(evaluate_polynomial(peer, coefficients))
}

/// Returns negated SigningShare
pub fn negate(&mut self) {
self.0 = <<C::Group as Group>::Field>::negate(&self.0);
}
}

impl<C> Debug for SigningShare<C>
Expand Down Expand Up @@ -686,6 +691,11 @@ where
min_signers,
}
}

/// Negate `SigningShare`.
pub fn negate_signing_share(&mut self) {
self.signing_share.negate();
}
}

#[cfg(feature = "serialization")]
Expand Down
32 changes: 22 additions & 10 deletions frost-core/src/keys/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use rand_core::{CryptoRng, RngCore};

use crate::{
Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
SigningKey, VerifyingKey,
SigningKey,
};

use super::{
Expand Down Expand Up @@ -314,7 +314,7 @@ pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
/// Generates the challenge for the proof of knowledge to a secret for the DKG.
fn challenge<C>(
identifier: Identifier<C>,
verifying_key: &VerifyingKey<C>,
verifying_key: &Element<C>,
R: &Element<C>,
) -> Option<Challenge<C>>
where
Expand All @@ -323,7 +323,7 @@ where
let mut preimage = vec![];

preimage.extend_from_slice(identifier.serialize().as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.element).as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key).as_ref());
preimage.extend_from_slice(<C::Group>::serialize(R).as_ref());

Some(Challenge(C::HDKG(&preimage[..])?))
Expand All @@ -344,14 +344,23 @@ 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 c_i = challenge::<C>(identifier, &commitment.verifying_key()?, &R_i)
.ok_or(Error::DKGNotSupported)?;
let mut k = <<C::Group as Group>::Field>::random(&mut rng);
let mut R_i = <C::Group>::generator() * k;
k = <C>::effective_nonce_secret(k, &R_i);
R_i = <C>::effective_nonce_element(R_i);

let verifying_key = commitment.verifying_key()?;
let sig_params = Default::default();

let phi_ell0 = <C>::effective_pubkey_element(&verifying_key, &sig_params);

let c_i = challenge::<C>(identifier, &phi_ell0, &R_i).ok_or(Error::DKGNotSupported)?;
let a_i0 = *coefficients
.first()
.expect("coefficients must have at least one element");
let mu_i = k + a_i0 * c_i.0;
let a_i0_effective = <C>::effective_secret_key(a_i0, &verifying_key, &sig_params);

let mu_i = k + a_i0_effective * c_i.0;
Ok(Signature { R: R_i, z: mu_i })
}

Expand All @@ -371,9 +380,12 @@ pub(crate) fn verify_proof_of_knowledge<C: Ciphersuite>(
let ell = identifier;
let R_ell = proof_of_knowledge.R;
let mu_ell = proof_of_knowledge.z;
let phi_ell0 = commitment.verifying_key()?;

let verifying_key = commitment.verifying_key()?;
let phi_ell0 = <C>::effective_pubkey_element(&verifying_key, &Default::default());
let c_ell = challenge::<C>(ell, &phi_ell0, &R_ell).ok_or(Error::DKGNotSupported)?;
if R_ell != <C::Group>::generator() * mu_ell - phi_ell0.element * c_ell.0 {

if R_ell != <C::Group>::generator() * mu_ell - phi_ell0 * c_ell.0 {
return Err(Error::InvalidProofOfKnowledge { culprit: ell });
}
Ok(())
Expand Down
105 changes: 77 additions & 28 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ 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, Element, Field, Group, Scalar, SigningParameters};
pub use verifying_key::VerifyingKey;

/// A type refinement for the scalar field element representing the per-message _[challenge]_.
Expand All @@ -69,15 +69,13 @@ where
C: Ciphersuite,
{
/// Creates a challenge from a scalar.
#[cfg(feature = "internals")]
pub fn from_scalar(
scalar: <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
) -> Self {
Self(scalar)
}

/// Return the underlying scalar.
#[cfg(feature = "internals")]
pub fn to_scalar(self) -> <<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
self.0
}
Expand Down Expand Up @@ -342,6 +340,53 @@ fn derive_interpolating_value<C: Ciphersuite>(
)
}

/// The data which the group's signature should commit to. Includes
/// a message byte vector, and a set of ciphersuite-specific parameters.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
pub struct SigningTarget<C: Ciphersuite> {
#[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>,
#[cfg_attr(feature = "serde", serde(default))]
sig_params: C::SigningParameters,
}

impl<C: Ciphersuite> SigningTarget<C> {
/// Construct a signing target from a message and additional signing parameters.
pub fn new<T: AsRef<[u8]>, P: Into<C::SigningParameters>>(
message: T,
sig_params: P,
) -> SigningTarget<C> {
SigningTarget {
message: message.as_ref().to_vec(),
sig_params: sig_params.into(),
}
}

/// Constructs a signing target from an arbitrary message.
/// This populates [the `sig_params` field][SigningTarget::sig_params] with
/// the [`Default`] instance of the [`Ciphersuite::SigningParameters`].
pub fn from_message<T: AsRef<[u8]>>(message: T) -> SigningTarget<C> {
SigningTarget {
message: message.as_ref().to_vec(),
sig_params: C::SigningParameters::default(),
}
}
}

impl<C: Ciphersuite, T: AsRef<[u8]>> From<T> for SigningTarget<C> {
fn from(message: T) -> Self {
Self::from_message(message)
}
}

/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
Expand All @@ -355,18 +400,9 @@ pub struct SigningPackage<C: Ciphersuite> {
/// 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>,
/// The message and parameters which each participant will use to sign.
/// Each signer should perform protocol-specific verification on the signing target.
sig_target: SigningTarget<C>,
}

impl<C> SigningPackage<C>
Expand All @@ -376,14 +412,19 @@ where
/// Create a new `SigningPackage`
///
/// The `signing_commitments` are sorted by participant `identifier`.
///
/// The `sig_target` can be any bytes-like type that implements `AsRef<[u8]>`.
/// Some ciphersuites like `frost-secp256k1-tr` allow customization of the signing
/// process by embedding additional parameters into a [`SigningTarget`], but this
/// is optional and not required by most ciphersuites.
pub fn new(
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
message: &[u8],
sig_target: impl Into<SigningTarget<C>>,
) -> SigningPackage<C> {
SigningPackage {
header: Header::default(),
signing_commitments,
message: message.to_vec(),
sig_target: sig_target.into(),
}
}

Expand All @@ -395,6 +436,11 @@ where
self.signing_commitments.get(identifier).copied()
}

/// Returns the message to be signed.
pub fn message(&self) -> &[u8] {
&self.sig_target.message
}

/// 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))]
Expand All @@ -414,7 +460,7 @@ where
// 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::H4(self.message()).as_ref());
binding_factor_input_prefix.extend_from_slice(
C::H5(&round1::encode_group_commitments(self.signing_commitments())[..]).as_ref(),
);
Expand Down Expand Up @@ -465,6 +511,11 @@ where
pub fn to_element(self) -> <C::Group as Group>::Element {
self.0
}

/// Check if group commitment is odd
pub fn y_is_odd(&self) -> bool {
<C::Group as Group>::y_is_odd(&self.0)
}
}

/// Generates the group commitment which is published as part of the joint
Expand Down Expand Up @@ -572,6 +623,7 @@ where

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

// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
Expand All @@ -585,27 +637,21 @@ where
z = z + signature_share.share;
}

let signature = Signature {
R: group_commitment.0,
z,
};
let signature: Signature<C> =
<C>::aggregate_sig_finalize(z, R, &pubkeys.verifying_key, &signing_package.sig_target);

// Verify the aggregate signature
let verification_result = pubkeys
.verifying_key
.verify(signing_package.message(), &signature);
.verify(signing_package.sig_target.clone(), &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(),
);
let challenge = <C>::challenge(&R, &pubkeys.verifying_key, &signing_package.sig_target);

// Verify the signature shares.
for (signature_share_identifier, signature_share) in signature_shares {
Expand Down Expand Up @@ -636,6 +682,9 @@ where
signer_pubkey,
lambda_i,
&challenge,
&group_commitment,
&pubkeys.verifying_key,
&signing_package.sig_target.sig_params,
)?;
}

Expand Down
Loading