Skip to content

Commit 4cc535c

Browse files
committed
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
1 parent 30977f6 commit 4cc535c

File tree

20 files changed

+560
-140
lines changed

20 files changed

+560
-140
lines changed

frost-core/src/batch.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *};
2020
pub struct Item<C: Ciphersuite> {
2121
vk: VerifyingKey<C>,
2222
sig: Signature<C>,
23+
sig_params: C::SigningParameters,
2324
c: Challenge<C>,
2425
}
2526

@@ -33,10 +34,15 @@ where
3334
where
3435
M: AsRef<[u8]>,
3536
{
36-
// Compute c now to avoid dependency on the msg lifetime.
37-
let c = crate::challenge(&sig.R, &vk, msg.as_ref())?;
38-
39-
Ok(Self { vk, sig, c })
37+
let sig_target = SigningTarget::from_message(msg);
38+
let c = <C>::challenge(&sig.R, &vk, &sig_target)?;
39+
40+
Ok(Self {
41+
vk,
42+
sig,
43+
sig_params: sig_target.sig_params,
44+
c,
45+
})
4046
}
4147
}
4248

@@ -52,7 +58,8 @@ where
5258
/// requires borrowing the message data, the `Item` type is unlinked
5359
/// from the lifetime of the message.
5460
pub fn verify_single(self) -> Result<(), Error<C>> {
55-
self.vk.verify_prehashed(self.c, &self.sig)
61+
self.vk
62+
.verify_prehashed(self.c, &self.sig, &self.sig_params)
5663
}
5764
}
5865

@@ -121,6 +128,7 @@ where
121128
for item in self.signatures.iter() {
122129
let z = item.sig.z;
123130
let R = item.sig.R;
131+
let vk = <C>::effective_pubkey_element(&item.vk, &item.sig_params);
124132

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

@@ -131,7 +139,7 @@ where
131139
Rs.push(R);
132140

133141
VK_coeffs.push(<<C::Group as Group>::Field>::zero() + (blind * item.c.0));
134-
VKs.push(item.vk.to_element());
142+
VKs.push(vk);
135143
}
136144

137145
let scalars = core::iter::once(&P_coeff_acc)

frost-core/src/keys.rs

+10
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ where
119119
pub(crate) fn from_coefficients(coefficients: &[Scalar<C>], peer: Identifier<C>) -> Self {
120120
Self::new(evaluate_polynomial(peer, coefficients))
121121
}
122+
123+
/// Returns negated SigningShare
124+
pub fn negate(&mut self) {
125+
self.0 .0 = <<C::Group as Group>::Field>::negate(&self.to_scalar());
126+
}
122127
}
123128

124129
impl<C> Debug for SigningShare<C>
@@ -630,6 +635,11 @@ where
630635
min_signers,
631636
}
632637
}
638+
639+
/// Negate `SigningShare`.
640+
pub fn negate_signing_share(&mut self) {
641+
self.signing_share.negate();
642+
}
633643
}
634644

635645
#[cfg(feature = "serialization")]

frost-core/src/keys/dkg.rs

+22-9
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use rand_core::{CryptoRng, RngCore};
3838

3939
use crate::{
4040
Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
41-
SigningKey, VerifyingKey,
41+
SigningKey,
4242
};
4343

4444
#[cfg(feature = "serialization")]
@@ -322,7 +322,7 @@ pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
322322
/// Generates the challenge for the proof of knowledge to a secret for the DKG.
323323
fn challenge<C>(
324324
identifier: Identifier<C>,
325-
verifying_key: &VerifyingKey<C>,
325+
verifying_key: &Element<C>,
326326
R: &Element<C>,
327327
) -> Result<Challenge<C>, Error<C>>
328328
where
@@ -331,7 +331,7 @@ where
331331
let mut preimage = vec![];
332332

333333
preimage.extend_from_slice(identifier.serialize().as_ref());
334-
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.to_element())?.as_ref());
334+
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key)?.as_ref());
335335
preimage.extend_from_slice(<C::Group>::serialize(R)?.as_ref());
336336

337337
Ok(Challenge(
@@ -354,13 +354,23 @@ pub(crate) fn compute_proof_of_knowledge<C: Ciphersuite, R: RngCore + CryptoRng>
354354
// > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k,
355355
// > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being
356356
// > a context string to prevent replay attacks.
357-
let k = <<C::Group as Group>::Field>::random(&mut rng);
358-
let R_i = <C::Group>::generator() * k;
359-
let c_i = challenge::<C>(identifier, &commitment.verifying_key()?, &R_i)?;
357+
let mut k = <<C::Group as Group>::Field>::random(&mut rng);
358+
let mut R_i = <C::Group>::generator() * k;
359+
k = <C>::effective_nonce_secret(k, &R_i);
360+
R_i = <C>::effective_nonce_element(R_i);
361+
362+
let verifying_key = commitment.verifying_key()?;
363+
let sig_params = Default::default();
364+
365+
let phi_ell0 = <C>::effective_pubkey_element(&verifying_key, &sig_params);
366+
367+
let c_i = challenge::<C>(identifier, &phi_ell0, &R_i)?;
360368
let a_i0 = *coefficients
361369
.first()
362370
.expect("coefficients must have at least one element");
363-
let mu_i = k + a_i0 * c_i.0;
371+
let a_i0_effective = <C>::effective_secret_key(a_i0, &verifying_key, &sig_params);
372+
373+
let mu_i = k + a_i0_effective * c_i.0;
364374
Ok(Signature { R: R_i, z: mu_i })
365375
}
366376

@@ -380,9 +390,12 @@ pub(crate) fn verify_proof_of_knowledge<C: Ciphersuite>(
380390
let ell = identifier;
381391
let R_ell = proof_of_knowledge.R;
382392
let mu_ell = proof_of_knowledge.z;
383-
let phi_ell0 = commitment.verifying_key()?;
393+
394+
let verifying_key = commitment.verifying_key()?;
395+
let phi_ell0 = <C>::effective_pubkey_element(&verifying_key, &Default::default());
384396
let c_ell = challenge::<C>(ell, &phi_ell0, &R_ell)?;
385-
if R_ell != <C::Group>::generator() * mu_ell - phi_ell0.to_element() * c_ell.0 {
397+
398+
if R_ell != <C::Group>::generator() * mu_ell - phi_ell0 * c_ell.0 {
386399
return Err(Error::InvalidProofOfKnowledge { culprit: ell });
387400
}
388401
Ok(())

frost-core/src/lib.rs

+86-36
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,14 @@ use scalar_mul::VartimeMultiscalarMul;
5757
pub use serde;
5858
pub use signature::Signature;
5959
pub use signing_key::SigningKey;
60-
pub use traits::{Ciphersuite, Element, Field, Group, Scalar};
60+
pub use traits::{Ciphersuite, Element, Field, Group, Scalar, SigningParameters};
6161
pub use verifying_key::VerifyingKey;
6262

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

7369
impl<C> Challenge<C>
7470
where
@@ -192,9 +188,7 @@ where
192188
///
193189
/// <https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md>
194190
#[derive(Clone, PartialEq, Eq)]
195-
#[cfg_attr(feature = "internals", visibility::make(pub))]
196-
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
197-
pub(crate) struct BindingFactor<C: Ciphersuite>(Scalar<C>);
191+
pub struct BindingFactor<C: Ciphersuite>(Scalar<C>);
198192

199193
impl<C> BindingFactor<C>
200194
where
@@ -357,6 +351,54 @@ fn derive_interpolating_value<C: Ciphersuite>(
357351
)
358352
}
359353

354+
/// The data which the group's signature should commit to. Includes
355+
/// a message byte vector, and a set of ciphersuite-specific parameters.
356+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357+
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
358+
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
359+
pub struct SigningTarget<C: Ciphersuite> {
360+
#[cfg_attr(
361+
feature = "serde",
362+
serde(
363+
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
364+
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
365+
)
366+
)]
367+
message: Vec<u8>,
368+
369+
#[cfg_attr(feature = "serde", serde(default))]
370+
sig_params: C::SigningParameters,
371+
}
372+
373+
impl<C: Ciphersuite> SigningTarget<C> {
374+
/// Construct a signing target from a message and additional signing parameters.
375+
pub fn new<T: AsRef<[u8]>, P: Into<C::SigningParameters>>(
376+
message: T,
377+
sig_params: P,
378+
) -> SigningTarget<C> {
379+
SigningTarget {
380+
message: message.as_ref().to_vec(),
381+
sig_params: sig_params.into(),
382+
}
383+
}
384+
385+
/// Constructs a signing target from an arbitrary message.
386+
/// This populates [the `sig_params` field][SigningTarget::sig_params] with
387+
/// the [`Default`] instance of the [`Ciphersuite::SigningParameters`].
388+
pub fn from_message<T: AsRef<[u8]>>(message: T) -> SigningTarget<C> {
389+
SigningTarget {
390+
message: message.as_ref().to_vec(),
391+
sig_params: C::SigningParameters::default(),
392+
}
393+
}
394+
}
395+
396+
impl<C: Ciphersuite, T: AsRef<[u8]>> From<T> for SigningTarget<C> {
397+
fn from(message: T) -> Self {
398+
Self::from_message(message)
399+
}
400+
}
401+
360402
/// Generated by the coordinator of the signing operation and distributed to
361403
/// each signing party
362404
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
@@ -370,18 +412,9 @@ pub struct SigningPackage<C: Ciphersuite> {
370412
/// The set of commitments participants published in the first round of the
371413
/// protocol.
372414
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
373-
/// Message which each participant will sign.
374-
///
375-
/// Each signer should perform protocol-specific verification on the
376-
/// message.
377-
#[cfg_attr(
378-
feature = "serde",
379-
serde(
380-
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
381-
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
382-
)
383-
)]
384-
message: Vec<u8>,
415+
/// The message and parameters which each participant will use to sign.
416+
/// Each signer should perform protocol-specific verification on the signing target.
417+
sig_target: SigningTarget<C>,
385418
}
386419

387420
impl<C> SigningPackage<C>
@@ -391,14 +424,19 @@ where
391424
/// Create a new `SigningPackage`
392425
///
393426
/// The `signing_commitments` are sorted by participant `identifier`.
427+
///
428+
/// The `sig_target` can be any bytes-like type that implements `AsRef<[u8]>`.
429+
/// Some ciphersuites like `frost-secp256k1-tr` allow customization of the signing
430+
/// process by embedding additional parameters into a [`SigningTarget`], but this
431+
/// is optional and not required by most ciphersuites.
394432
pub fn new(
395433
signing_commitments: BTreeMap<Identifier<C>, round1::SigningCommitments<C>>,
396-
message: &[u8],
434+
sig_target: impl Into<SigningTarget<C>>,
397435
) -> SigningPackage<C> {
398436
SigningPackage {
399437
header: Header::default(),
400438
signing_commitments,
401-
message: message.to_vec(),
439+
sig_target: sig_target.into(),
402440
}
403441
}
404442

@@ -410,6 +448,11 @@ where
410448
self.signing_commitments.get(identifier).copied()
411449
}
412450

451+
/// Returns the message to be signed.
452+
pub fn message(&self) -> &[u8] {
453+
&self.sig_target.message
454+
}
455+
413456
/// Compute the preimages to H1 to compute the per-signer binding factors
414457
// We separate this out into its own method so it can be tested
415458
#[cfg_attr(feature = "internals", visibility::make(pub))]
@@ -430,7 +473,7 @@ where
430473
// The message is hashed with H4 to force the variable-length message
431474
// into a fixed-length byte string, same for hashing the variable-sized
432475
// (between runs of the protocol) set of group commitments, but with H5.
433-
binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref());
476+
binding_factor_input_prefix.extend_from_slice(C::H4(self.message()).as_ref());
434477
binding_factor_input_prefix.extend_from_slice(
435478
C::H5(&round1::encode_group_commitments(self.signing_commitments())?[..]).as_ref(),
436479
);
@@ -469,9 +512,7 @@ where
469512
/// The product of all signers' individual commitments, published as part of the
470513
/// final signature.
471514
#[derive(Clone, PartialEq, Eq)]
472-
#[cfg_attr(feature = "internals", visibility::make(pub))]
473-
#[cfg_attr(docsrs, doc(cfg(feature = "internals")))]
474-
pub(crate) struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);
515+
pub struct GroupCommitment<C: Ciphersuite>(pub(crate) Element<C>);
475516

476517
impl<C> GroupCommitment<C>
477518
where
@@ -482,6 +523,12 @@ where
482523
pub fn to_element(self) -> <C::Group as Group>::Element {
483524
self.0
484525
}
526+
527+
/// Check if group commitment is odd
528+
#[cfg(feature = "internals")]
529+
pub fn y_is_odd(&self) -> bool {
530+
<C::Group as Group>::y_is_odd(&self.0)
531+
}
485532
}
486533

487534
/// Generates the group commitment which is published as part of the joint
@@ -589,6 +636,7 @@ where
589636
compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?;
590637
// Compute the group commitment from signing commitments produced in round one.
591638
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;
639+
let R = <C>::effective_nonce_element(group_commitment.0);
592640

593641
// The aggregation of the signature shares by summing them up, resulting in
594642
// a plain Schnorr signature.
@@ -602,15 +650,13 @@ where
602650
z = z + signature_share.to_scalar();
603651
}
604652

605-
let signature = Signature {
606-
R: group_commitment.0,
607-
z,
608-
};
653+
let signature: Signature<C> =
654+
<C>::aggregate_sig_finalize(z, R, &pubkeys.verifying_key, &signing_package.sig_target)?;
609655

610656
// Verify the aggregate signature
611657
let verification_result = pubkeys
612658
.verifying_key
613-
.verify(signing_package.message(), &signature);
659+
.verify(signing_package.sig_target.clone(), &signature);
614660

615661
// Only if the verification of the aggregate signature failed; verify each share to find the cheater.
616662
// This approach is more efficient since we don't need to verify all shares
@@ -619,6 +665,7 @@ where
619665
if verification_result.is_err() {
620666
detect_cheater(
621667
group_commitment,
668+
&R,
622669
pubkeys,
623670
signing_package,
624671
signature_shares,
@@ -631,21 +678,21 @@ where
631678

632679
Ok(signature)
633680
}
634-
635681
/// Optional cheater detection feature
636682
/// Each share is verified to find the cheater
637683
fn detect_cheater<C: Ciphersuite>(
638684
group_commitment: GroupCommitment<C>,
685+
effective_group_commitment: &Element<C>,
639686
pubkeys: &keys::PublicKeyPackage<C>,
640687
signing_package: &SigningPackage<C>,
641688
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
642689
binding_factor_list: &BindingFactorList<C>,
643690
) -> Result<(), Error<C>> {
644691
// Compute the per-message challenge.
645-
let challenge = crate::challenge::<C>(
646-
&group_commitment.0,
692+
let challenge = <C>::challenge(
693+
effective_group_commitment,
647694
&pubkeys.verifying_key,
648-
signing_package.message().as_slice(),
695+
&signing_package.sig_target,
649696
)?;
650697

651698
// Verify the signature shares.
@@ -677,6 +724,9 @@ fn detect_cheater<C: Ciphersuite>(
677724
signer_pubkey,
678725
lambda_i,
679726
&challenge,
727+
&group_commitment,
728+
&pubkeys.verifying_key,
729+
&signing_package.sig_target.sig_params,
680730
)?;
681731
}
682732

0 commit comments

Comments
 (0)