Refactor Ciphersuite taproot methods for universal applicability (#2)

The taproot compatibility methods added to Ciphersuite were
very specific to taproot. I renamed them, and tidied up their
usage to remove unnecessary if/else branches. This should make
them applicable to any ciphersuite which needs to modify such
internal processes of the FROST algorithm.

This change should be a pure refactor with no logical changes.
This commit is contained in:
conduition 2024-02-21 22:57:48 +00:00 committed by zebra-lucky
parent a66b9a2e7c
commit 142556fabd
8 changed files with 225 additions and 224 deletions

View File

@ -118,12 +118,8 @@ where
for item in self.signatures.iter() {
let z = item.sig.z;
let mut R = item.sig.R;
let mut vk = item.vk.element;
if <C>::is_taproot_compat() {
R = <C>::taproot_compat_R(&item.sig.R);
vk = <C>::tweaked_public_key(&item.vk.element);
}
let R = <C>::effective_nonce_element(item.sig.R);
let vk = <C>::effective_pubkey_element(&item.vk);
let blind = <<C::Group as Group>::Field>::random(&mut rng);

View File

@ -588,19 +588,12 @@ where
z = z + signature_share.share;
}
if <C>::is_taproot_compat() {
let challenge = <C>::challenge(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);
z = <C>::aggregate_tweak_z(z, &challenge, &pubkeys.verifying_key.element);
}
let signature = Signature {
R: group_commitment.0,
let signature: Signature<C> = <C>::aggregate_sig_finalize(
z,
};
group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);
// Verify the aggregate signature
let verification_result = pubkeys

View File

@ -404,6 +404,14 @@ where
#[derive(Clone, Copy, PartialEq)]
pub struct GroupCommitmentShare<C: Ciphersuite>(pub(super) Element<C>);
impl<C: Ciphersuite> GroupCommitmentShare<C> {
/// 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.

View File

@ -94,15 +94,10 @@ where
group_commitment: &frost::GroupCommitment<C>,
verifying_key: &frost::VerifyingKey<C>,
) -> Result<(), Error<C>> {
let mut commitment_share = group_commitment_share.0;
let mut vsh = verifying_share.0;
if <C>::is_taproot_compat() {
commitment_share = <C>::taproot_compat_commitment_share(
&group_commitment_share.0,
&group_commitment.0,
);
vsh = <C>::taproot_compat_verifying_share(&verifying_share.0, &verifying_key.element);
}
let commitment_share =
<C>::effective_commitment_share(group_commitment_share.clone(), &group_commitment);
let vsh = <C>::effective_verifying_share(&verifying_share, &verifying_key);
if (<C::Group>::generator() * self.share)
!= (commitment_share + (vsh * challenge.0 * lambda_i))
{
@ -231,26 +226,14 @@ pub fn sign<C: Ciphersuite>(
);
// Compute the Schnorr signature share.
if <C>::is_taproot_compat() {
let signature_share = <C>::compute_taproot_compat_signature_share(
signer_nonces,
binding_factor,
group_commitment,
lambda_i,
key_package,
challenge,
);
let signature_share = <C>::compute_signature_share(
signer_nonces,
binding_factor,
group_commitment,
lambda_i,
key_package,
challenge,
);
Ok(signature_share)
} else {
let signature_share = compute_signature_share(
signer_nonces,
binding_factor,
lambda_i,
key_package,
challenge,
);
Ok(signature_share)
}
Ok(signature_share)
}

View File

@ -48,26 +48,16 @@ where
/// Create a signature `msg` using this `SigningKey`.
pub fn sign<R: RngCore + CryptoRng>(&self, mut rng: R, msg: &[u8]) -> Signature<C> {
let public = VerifyingKey::<C>::from(*self);
let mut secret = self.scalar;
if <C>::is_taproot_compat() {
secret = <C>::tweaked_secret_key(secret, &public.element);
}
let secret = <C>::effective_secret_key(self.scalar, &public);
let mut k = random_nonzero::<C, R>(&mut rng);
let R = <C::Group>::generator() * k;
if <C>::is_taproot_compat() {
k = <C>::taproot_compat_nonce(k, &R);
}
k = <C>::effective_nonce_secret(k, &R);
// Generate Schnorr challenge
let c: Challenge<C> = <C>::challenge(&R, &public, msg);
if <C>::is_taproot_compat() {
let z = <C>::tweaked_z(k, secret, c.0, &public.element);
Signature { R, z }
} else {
let z = k + (c.0 * secret);
Signature { R, z }
}
<C>::single_sig_finalize(k, R, secret, &c, &public)
}
/// Creates a SigningKey from a scalar.

View File

@ -7,7 +7,12 @@ use std::{
use rand_core::{CryptoRng, RngCore};
use crate::{challenge, Challenge, Error, FieldError, GroupError, Signature, VerifyingKey};
use crate::{
challenge,
keys::{KeyPackage, VerifyingShare},
round1, round2, BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError,
Signature, VerifyingKey,
};
/// A prime order finite field GF(q) over which all scalar values for our prime order group can be
/// multiplied are defined.
@ -258,94 +263,115 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug {
challenge(R, verifying_key, msg)
}
/// determine code is taproot compatible (used in frost-sepc256k1-tr)
fn is_taproot_compat() -> bool {
false
}
/// aggregate tweak z (used in frost-sepc256k1-tr)
#[allow(unused)]
fn aggregate_tweak_z(
/// Finalize an aggregated group signature. This is used by frost-sepc256k1-tr
/// to ensure the signature is valid under BIP340; for all other ciphersuites
/// this simply returns a [`Signature`] wrapping `R` and `z`.
fn aggregate_sig_finalize(
z: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: &Challenge<Self>,
verifying_key: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
panic!("Not implemented");
R: Element<Self>,
_verifying_key: &VerifyingKey<Self>,
_msg: &[u8],
) -> Signature<Self> {
Signature { R, z }
}
/// tweaked z for SigningKey sign (used in frost-sepc256k1-tr)
#[allow(unused)]
fn tweaked_z(
/// Finalize and output a single-signer Schnorr signature.
fn single_sig_finalize(
k: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
secret: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: <<Self::Group as Group>::Field as Field>::Scalar,
verifying_key: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
panic!("Not implemented");
challenge: &Challenge<Self>,
_verifying_key: &VerifyingKey<Self>,
) -> Signature<Self> {
let z = k + (challenge.0 * secret);
Signature { R, z }
}
/// signature_share compatible with taproot (used in frost-sepc256k1-tr)
#[allow(unused)]
fn compute_taproot_compat_signature_share(
signer_nonces: &crate::round1::SigningNonces<Self>,
binding_factor: crate::BindingFactor<Self>,
group_commitment: crate::GroupCommitment<Self>,
/// Compute the signature share for a particular signer on a given challenge.
fn compute_signature_share(
signer_nonces: &round1::SigningNonces<Self>,
binding_factor: BindingFactor<Self>,
_group_commitment: GroupCommitment<Self>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &crate::keys::KeyPackage<Self>,
key_package: &KeyPackage<Self>,
challenge: Challenge<Self>,
) -> crate::round2::SignatureShare<Self> {
panic!("Not implemented");
) -> round2::SignatureShare<Self> {
round2::compute_signature_share(
signer_nonces,
binding_factor,
lambda_i,
key_package,
challenge,
)
}
/// calculate tweaked public key (used in frost-sepc256k1-tr)
#[allow(unused)]
fn tweaked_public_key(
public_key: &<Self::Group as Group>::Element,
/// Compute the effective group element which should be used for signature operations
/// for the given verifying key.
///
/// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes.
/// For all other ciphersuites, this simply returns `verifying_key.to_element()`
fn effective_pubkey_element(
verifying_key: &VerifyingKey<Self>,
) -> <Self::Group as Group>::Element {
panic!("Not implemented");
verifying_key.to_element()
}
/// calculate taproot compatible R (used in frost-sepc256k1-tr)
#[allow(unused)]
fn taproot_compat_R(
public_key: &<Self::Group as Group>::Element,
/// Compute the effective nonce element which should be used for signature operations.
///
/// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity.
/// For all other ciphersuites, this simply returns the input `R`.
fn effective_nonce_element(
R: <Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
panic!("Not implemented");
R
}
/// tweaked secret (used in frost-sepc256k1-tr)
#[allow(unused)]
fn tweaked_secret_key(
/// Compute the effective secret key which should be used for signature operations
/// for the given verifying key.
///
/// In frost-sepc256k1-tr, this is used to commit the key to taptree merkle root hashes.
/// For all other ciphersuites, this simply returns `secret` unchanged.
fn effective_secret_key(
secret: <<Self::Group as Group>::Field as Field>::Scalar,
public: &Element<Self>,
_public: &VerifyingKey<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
panic!("Not implemented");
secret
}
/// calculate taproot compatible nonce (used in frost-sepc256k1-tr)
#[allow(unused)]
fn taproot_compat_nonce(
/// Compute the effective nonce secret which should be used for signature operations.
///
/// In frost-sepc256k1-tr, this negates the nonce if it has an odd parity.
/// For all other ciphersuites, this simply returns the input `nonce`.
fn effective_nonce_secret(
nonce: <<Self::Group as Group>::Field as Field>::Scalar,
R: &Element<Self>,
_R: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
panic!("Not implemented");
nonce
}
/// calculate taproot compatible commitment share (used in frost-sepc256k1-tr)
#[allow(unused)]
fn taproot_compat_commitment_share(
group_commitment_share: &<Self::Group as Group>::Element,
group_commitment: &<Self::Group as Group>::Element,
/// Compute the effective nonce commitment share which should be used for
/// FROST signing.
///
/// In frost-sepc256k1-tr, this negates the commitment share if the group's final
/// commitment has an odd parity. For all other ciphersuites, this simply returns
/// `group_commitment_share.to_element()`
fn effective_commitment_share(
group_commitment_share: round1::GroupCommitmentShare<Self>,
_group_commitment: &GroupCommitment<Self>,
) -> <Self::Group as Group>::Element {
panic!("Not implemented");
group_commitment_share.to_element()
}
/// calculate taproot compatible verifying share (used in frost-sepc256k1-tr)
#[allow(unused)]
fn taproot_compat_verifying_share(
verifying_share: &<Self::Group as Group>::Element,
verifying_key: &<Self::Group as Group>::Element,
/// Compute the effective verifying share which should be used for FROST
/// partial signature verification.
///
/// In frost-sepc256k1-tr, this negates the verifying share if the group's final
/// verifying key has an odd parity. For all other ciphersuites, this simply returns
/// `verifying_share.to_element()`
fn effective_verifying_share(
verifying_share: &VerifyingShare<Self>,
_verifying_key: &VerifyingKey<Self>,
) -> <Self::Group as Group>::Element {
panic!("Not implemented");
verifying_share.to_element()
}
}

View File

@ -68,12 +68,9 @@ where
// h * ( z * B - c * A - R) == 0
//
// where h is the cofactor
let mut R = signature.R;
let mut vk = self.element;
if <C>::is_taproot_compat() {
R = <C>::taproot_compat_R(&signature.R);
vk = <C>::tweaked_public_key(&self.element);
}
let R = C::effective_nonce_element(signature.R);
let vk = C::effective_pubkey_element(&self);
let zB = C::Group::generator() * signature.z;
let cA = vk * challenge.0;
let check = (zB - cA - R) * C::Group::cofactor();

View File

@ -13,7 +13,7 @@ use k256::{
bigint::U256,
group::prime::PrimeCurveAffine,
hash2curve::{hash_to_field, ExpandMsgXmd},
point::{AffineCoordinates, DecompactPoint},
point::AffineCoordinates,
sec1::{FromEncodedPoint, ToEncodedPoint},
Field as FFField, PrimeField, ScalarPrimitive,
},
@ -29,11 +29,15 @@ mod tests;
// Re-exports in our public API
pub use frost_core::{
serde, Challenge, Ciphersuite, Element, Field, FieldError, Group, GroupError,
serde, Challenge, Ciphersuite, Element, Field, FieldError, Group, GroupCommitment, GroupError,
};
pub use rand_core;
/// The tapscript path is provably unspendable by committing to an empty merkle root.
/// Perhaps we can support taptree commitments in the future.
const UNSPENDABLE_MERKLE_ROOT: [u8; 0] = [];
/// An error.
pub type Error = frost_core::Error<Secp256K1Sha256>;
@ -218,40 +222,16 @@ fn tweak(
/// Create a BIP341 compliant tweaked public key
fn tweaked_public_key(
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
public_key: &VerifyingKey,
merkle_root: &[u8],
) -> <<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element {
let mut pk = *public_key;
if public_key.to_affine().y_is_odd().into() {
let mut pk = public_key.to_element();
if pk.to_affine().y_is_odd().into() {
pk = -pk;
}
ProjectivePoint::GENERATOR * tweak(&pk, merkle_root) + pk
}
/// Creates a real BIP341 tweaked public key by assuming an even y-coordinate.
fn real_tweaked_pubkey(
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
merkle_root: &[u8],
) -> <<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element {
let tweaked_pubkey = tweaked_public_key(public_key, merkle_root);
AffinePoint::decompact(&tweaked_pubkey.to_affine().x())
.unwrap()
.into()
}
/// Create a BIP341 compliant tweaked secret key
fn tweaked_secret_key(
secret: <<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Field as Field>::Scalar,
public_key: &<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Element,
merkle_root: &[u8],
) -> <<<Secp256K1Sha256 as Ciphersuite>::Group as Group>::Field as Field>::Scalar {
if public_key.to_affine().y_is_odd().into() {
-secret + tweak(public_key, merkle_root)
} else {
secret + tweak(public_key, merkle_root)
}
}
impl Ciphersuite for Secp256K1Sha256 {
const ID: &'static str = CONTEXT_STRING;
@ -317,54 +297,58 @@ impl Ciphersuite for Secp256K1Sha256 {
/// Generates the challenge as is required for Schnorr signatures.
fn challenge(R: &Element<S>, verifying_key: &VerifyingKey, msg: &[u8]) -> Challenge<S> {
let mut preimage = vec![];
let tweaked_public_key = tweaked_public_key(&verifying_key.to_element(), &[]);
let tweaked_pk = tweaked_public_key(&verifying_key, &UNSPENDABLE_MERKLE_ROOT);
preimage.extend_from_slice(&R.to_affine().x());
preimage.extend_from_slice(&tweaked_public_key.to_affine().x());
preimage.extend_from_slice(&tweaked_pk.to_affine().x());
preimage.extend_from_slice(msg);
Challenge::from_scalar(S::H2(&preimage[..]))
}
/// determine code is taproot compatible
fn is_taproot_compat() -> bool {
true
}
/// Finalizes the signature by negating it depending on whether
/// the group [`VerifyingKey`] is even or odd parity.
fn aggregate_sig_finalize(
z_raw: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
verifying_key: &VerifyingKey,
msg: &[u8],
) -> Signature {
let challenge = Self::challenge(&R, verifying_key, msg);
/// aggregate tweak z
fn aggregate_tweak_z(
z: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: &Challenge<S>,
verifying_key: &Element<S>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
let t = tweak(verifying_key, &[]);
let t = tweak(verifying_key.element(), &UNSPENDABLE_MERKLE_ROOT);
let tc = t * challenge.clone().to_scalar();
let tweaked_pubkey = tweaked_public_key(verifying_key, &[]);
if tweaked_pubkey.to_affine().y_is_odd().into() {
z - tc
let tweaked_pubkey = tweaked_public_key(verifying_key, &UNSPENDABLE_MERKLE_ROOT);
let z_tweaked = if tweaked_pubkey.to_affine().y_is_odd().into() {
z_raw - tc
} else {
z + tc
}
z_raw + tc
};
Signature::new(R, z_tweaked)
}
/// tweaked z for SigningKey sign
fn tweaked_z(
/// Finalize a single-signer BIP340 Schnorr signature.
fn single_sig_finalize(
k: <<Self::Group as Group>::Field as Field>::Scalar,
R: Element<Self>,
secret: <<Self::Group as Group>::Field as Field>::Scalar,
challenge: <<Self::Group as Group>::Field as Field>::Scalar,
verifying_key: &Element<S>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
let tweaked_pubkey = tweaked_public_key(verifying_key, &[]);
if tweaked_pubkey.to_affine().y_is_odd().into() {
k - (challenge * secret)
challenge: &Challenge<S>,
verifying_key: &VerifyingKey,
) -> Signature {
let tweaked_pubkey = tweaked_public_key(verifying_key, &UNSPENDABLE_MERKLE_ROOT);
let c = challenge.clone().to_scalar();
let z = if tweaked_pubkey.to_affine().y_is_odd().into() {
k - (c * secret)
} else {
k + (challenge * secret)
}
k + (c * secret)
};
Signature::new(R, z)
}
/// signature_share compatible with taproot
fn compute_taproot_compat_signature_share(
/// Compute a signature share, negating if required by BIP340.
fn compute_signature_share(
signer_nonces: &round1::SigningNonces,
binding_factor: frost::BindingFactor<S>,
group_commitment: frost_core::GroupCommitment<S>,
group_commitment: GroupCommitment<S>,
lambda_i: <<Self::Group as Group>::Field as Field>::Scalar,
key_package: &frost::keys::KeyPackage<S>,
challenge: Challenge<S>,
@ -377,7 +361,7 @@ impl Ciphersuite for Secp256K1Sha256 {
let mut kp = key_package.clone();
let public_key = key_package.verifying_key();
let pubkey_is_odd: bool = public_key.y_is_odd();
let tweaked_pubkey_is_odd: bool = tweaked_public_key(public_key.element(), &[])
let tweaked_pubkey_is_odd: bool = tweaked_public_key(public_key, &UNSPENDABLE_MERKLE_ROOT)
.to_affine()
.y_is_odd()
.into();
@ -388,28 +372,42 @@ impl Ciphersuite for Secp256K1Sha256 {
frost::round2::compute_signature_share(&sn, binding_factor, lambda_i, &kp, challenge)
}
/// calculate tweaked public key
fn tweaked_public_key(
public_key: &<Self::Group as Group>::Element,
/// Computes the effective pubkey point by tweaking the verifying key with a
/// provably unspendable taproot tweak.
fn effective_pubkey_element(public_key: &VerifyingKey) -> <Self::Group as Group>::Element {
let tweaked_pubkey = tweaked_public_key(public_key, &UNSPENDABLE_MERKLE_ROOT);
if Self::Group::y_is_odd(&tweaked_pubkey) {
-tweaked_pubkey
} else {
tweaked_pubkey
}
}
/// Ensures the nonce has an even Y coordinate.
fn effective_nonce_element(
R: <Self::Group as Group>::Element,
) -> <Self::Group as Group>::Element {
real_tweaked_pubkey(public_key, &[])
if Self::Group::y_is_odd(&R) {
-R
} else {
R
}
}
/// calculate taproot compatible R
fn taproot_compat_R(R: &<Self::Group as Group>::Element) -> <Self::Group as Group>::Element {
AffinePoint::decompact(&R.to_affine().x()).unwrap().into()
}
/// tweaked secret
fn tweaked_secret_key(
/// Ensures the secret key is negated if the public key has odd parity.
fn effective_secret_key(
secret: <<Self::Group as Group>::Field as Field>::Scalar,
public: &Element<Self>,
public_key: &VerifyingKey,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
tweaked_secret_key(secret, public, &[])
if Self::Group::y_is_odd(public_key.element()) {
-secret + tweak(public_key.element(), &UNSPENDABLE_MERKLE_ROOT)
} else {
secret + tweak(public_key.element(), &UNSPENDABLE_MERKLE_ROOT)
}
}
/// calculate taproot compatible nonce
fn taproot_compat_nonce(
/// Ensures the nonce secret is negated if the public nonce point has odd parity.
fn effective_nonce_secret(
nonce: <<Self::Group as Group>::Field as Field>::Scalar,
R: &Element<Self>,
) -> <<Self::Group as Group>::Field as Field>::Scalar {
@ -420,33 +418,43 @@ impl Ciphersuite for Secp256K1Sha256 {
}
}
/// calculate taproot compatible commitment share
fn taproot_compat_commitment_share(
group_commitment_share: &Element<Self>,
group_commitment: &Element<Self>,
/// Ensures the commitment share is negated if the group's commitment has odd parity.
fn effective_commitment_share(
group_commitment_share: frost::round1::GroupCommitmentShare<Self>,
group_commitment: &GroupCommitment<Self>,
) -> Element<Self> {
if group_commitment.to_affine().y_is_odd().into() {
-group_commitment_share
if group_commitment
.clone()
.to_element()
.to_affine()
.y_is_odd()
.into()
{
-group_commitment_share.to_element()
} else {
*group_commitment_share
group_commitment_share.to_element()
}
}
/// calculate taproot compatible verifying share
fn taproot_compat_verifying_share(
verifying_share: &<Self::Group as Group>::Element,
verifying_key: &<Self::Group as Group>::Element,
/// Calculate a verifying share compatible with taproot, depending on the parity
/// of the tweaked vs untweaked verifying key.
fn effective_verifying_share(
verifying_share: &keys::VerifyingShare,
verifying_key: &VerifyingKey,
) -> <Self::Group as Group>::Element {
let mut vs = *verifying_share;
let pubkey_is_odd: bool = verifying_key.to_affine().y_is_odd().into();
let tweaked_pubkey_is_odd: bool = tweaked_public_key(verifying_key, &[])
.to_affine()
.y_is_odd()
.into();
let pubkey_is_odd: bool = verifying_key.to_element().to_affine().y_is_odd().into();
let tweaked_pubkey_is_odd: bool =
tweaked_public_key(verifying_key, &UNSPENDABLE_MERKLE_ROOT)
.to_affine()
.y_is_odd()
.into();
let vs = verifying_share.to_element();
if pubkey_is_odd != tweaked_pubkey_is_odd {
vs = -vs;
-vs
} else {
vs
}
vs
}
}