Skip to content

Commit

Permalink
[frost] Add multiplicative tweaking by demand
Browse files Browse the repository at this point in the history
See: #189
  • Loading branch information
LLFourn committed Jul 26, 2024
1 parent bbaf252 commit 0e4ba72
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 17 deletions.
31 changes: 30 additions & 1 deletion schnorr_fun/src/frost/frost_poly.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use core::marker::PhantomData;
use core::{marker::PhantomData, ops::Deref};

use alloc::vec::Vec;
use secp256kfun::{poly, prelude::*};
Expand Down Expand Up @@ -125,6 +125,16 @@ impl FrostPoly<Normal> {
pub fn homomorphic_negate(&mut self) {
poly::point::negate(&mut self.point_polynomial)
}

/// Multiplies the shared key by a scalar.
///
/// In otder for a [`PairedSecretShare`] to be valid against the new key they will have to apply
/// [the same operation](PairedSecretShare::xonly_homomorphic_mul).
pub fn homomorphic_mul(&mut self, tweak: Scalar<impl Secrecy>) {
for coeff in &mut self.point_polynomial {
*coeff = g!(tweak * coeff.deref()).normalize();
}
}
}

impl FrostPoly<EvenY> {
Expand Down Expand Up @@ -154,6 +164,25 @@ impl FrostPoly<EvenY> {
Some(self)
}

/// Multiplies the shared key by a scalar.
///
/// In otder for a `PairedSecretShare` to be valid against the new key they will have to apply
/// [the same operation](PairedSecretShare::xonly_homomorphic_mul).
pub fn xonly_homomorphic_mul(&mut self, mut tweak: Scalar<impl Secrecy>) {
self.point_polynomial[0] = g!(tweak * self.point_polynomial[0]).normalize();
let needs_negation = !self.point_polynomial[0]
.non_zero()
.expect("multiplication cannot be zero")
.is_y_even();

self.point_polynomial[0] = self.point_polynomial[0].conditional_negate(needs_negation);
tweak.conditional_negate(needs_negation);

for coeff in &mut self.point_polynomial[1..] {
*coeff = g!(tweak * coeff.deref()).normalize();
}
}

/// The public key that would have signatures verified against for this shared key.
pub fn shared_key(&self) -> Point<EvenY> {
let (even_y_point, _needs_negation) = self.point_polynomial[0]
Expand Down
43 changes: 32 additions & 11 deletions schnorr_fun/src/frost/share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,24 @@ impl PairedSecretShare<Normal> {
}

/// Add a `Scalar` to both the secret share and the paired shared key.
pub fn homomorphic_add(
mut self,
scalar: Scalar<impl Secrecy, impl ZeroChoice>,
) -> Option<Self> {
self.shared_key = g!(self.shared_key + scalar * G).normalize().non_zero()?;
self.secret_share.share = s!(self.secret_share.share + scalar);
/// If the share is a share of `secret` it becomes the share of the `secret + tweak`.
///
/// This is useful for for deriving unhardened child frost keys from a root frost public key
/// using [BIP32]
///
/// [BIP32]: https://bips.xyz/32
pub fn homomorphic_add(mut self, tweak: Scalar<impl Secrecy, impl ZeroChoice>) -> Option<Self> {
self.shared_key = g!(self.shared_key + tweak * G).normalize().non_zero()?;
self.secret_share.share = s!(self.secret_share.share + tweak);
Some(self)
}

/// Multiply the secret share by `scalar`.
pub fn homomorphic_mul(&mut self, tweak: Scalar<impl Secrecy>) {
self.shared_key = g!(tweak * self.shared_key).normalize();
self.secret_share.share = s!(tweak * self.secret_share.share);
}

/// Create an XOnly secert share where the paired image is always an `EvenY` point.
pub fn into_xonly(mut self) -> PairedSecretShare<EvenY> {
let (shared_key, needs_negation) = self.shared_key.into_point_with_even_y();
Expand All @@ -200,26 +209,38 @@ impl PairedSecretShare<Normal> {
}

impl PairedSecretShare<EvenY> {
/// Add a scalar to the share such that share becomes a share of the previous secret plus
/// `scalar`.
/// Adds an "XOnly" tweak to the FROST public key. If the share is a share of `secret` it
/// becomes the share of the `secret + tweak` however the even-y coordinate of the shared key is
/// maintained by negating if need be.
///
/// If the secret image were to become zero it returns `None` since this desinged to be
/// [`BIP340`] secret share which does not allow 0.
///
/// [`BIP340`]: https://bips.xyz/bip340
pub fn xonly_homomorphic_add(
mut self,
scalar: Scalar<impl Secrecy, impl ZeroChoice>,
tweak: Scalar<impl Secrecy, impl ZeroChoice>,
) -> Option<Self> {
let (shared_key, needs_negation) = g!(self.shared_key + scalar * G)
let (shared_key, needs_negation) = g!(self.shared_key + tweak * G)
.normalize()
.non_zero()?
.into_point_with_even_y();
self.shared_key = shared_key;
self.secret_share.share = s!(self.secret_share.share + scalar);
self.secret_share.share = s!(self.secret_share.share + tweak);
self.secret_share.share.conditional_negate(needs_negation);
Some(self)
}

/// Multiply the secret share and paired key by `scalar` maintaing the paired key's even y
/// coordinate by negating them both (if need be).
pub fn xonly_homomorphic_mul(&mut self, mut tweak: Scalar<impl Secrecy>) {
let (shared_key, needs_negation) = g!(tweak * self.shared_key)
.normalize()
.into_point_with_even_y();
self.shared_key = shared_key;
tweak.conditional_negate(needs_negation);
self.secret_share.share *= tweak;
}
}

#[cfg(feature = "share_backup")]
Expand Down
26 changes: 21 additions & 5 deletions schnorr_fun/tests/frost_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ proptest! {
#[test]
fn frost_prop_test(
(n_parties, threshold) in (2usize..=4).prop_flat_map(|n| (Just(n), 2usize..=n)),
plain_tweak in option::of(any::<Scalar<Public, Zero>>()),
xonly_tweak in option::of(any::<Scalar<Public, Zero>>())
add_tweak in option::of(any::<Scalar<Public, Zero>>()),
xonly_add_tweak in option::of(any::<Scalar<Public, Zero>>()),
mul_tweak in option::of(any::<Scalar<Public>>()),
xonly_mul_tweak in option::of(any::<Scalar<Public>>())
) {
let proto = new_with_deterministic_nonces::<Sha256>();
assert!(threshold <= n_parties);
Expand All @@ -30,25 +32,39 @@ proptest! {
let mut rng = TestRng::deterministic_rng(RngAlgorithm::ChaCha);
let (mut frost_poly, mut secret_shares) = proto.simulate_keygen(threshold, n_parties, &mut rng);

if let Some(tweak) = plain_tweak {
if let Some(tweak) = add_tweak {
for secret_share in &mut secret_shares {
*secret_share = secret_share.homomorphic_add(tweak).unwrap();
}
frost_poly = frost_poly.homomorphic_add(tweak).unwrap();
}

if let Some(mul_tweak) = mul_tweak {
frost_poly.homomorphic_mul(mul_tweak);
for secret_share in &mut secret_shares {
secret_share.homomorphic_mul(mul_tweak);
}
}

let mut xonly_poly = frost_poly.into_xonly();
let mut xonly_secret_shares = secret_shares.into_iter().map(|secret_share| secret_share.into_xonly()).collect::<Vec<_>>();

if let Some(tweak) = xonly_tweak {
if let Some(tweak) = xonly_add_tweak {
xonly_poly = xonly_poly.xonly_homomorphic_add(tweak).unwrap();
for secret_share in &mut xonly_secret_shares {
*secret_share = secret_share.xonly_homomorphic_add(tweak).unwrap();
}
}

if let Some(xonly_mul_tweak) = xonly_mul_tweak {
xonly_poly.xonly_homomorphic_mul(xonly_mul_tweak);
for secret_share in &mut xonly_secret_shares {
secret_share.xonly_homomorphic_mul(xonly_mul_tweak);
}
}

for secret_share in &xonly_secret_shares {
assert_eq!(secret_share.shared_key(), xonly_poly.shared_key());
assert_eq!(secret_share.shared_key(), xonly_poly.shared_key(), "shared key doesn't match");
}

// use a boolean mask for which t participants are signers
Expand Down

0 comments on commit 0e4ba72

Please sign in to comment.