use crate::{
Message, Schnorr, Signature,
adaptor::EncryptedSignature,
binonce::{self, SecretNonce},
};
use alloc::vec::Vec;
use secp256kfun::{
KeyPair,
hash::{Hash32, HashAdd, Tag},
nonce::{self, NoNonces, NonceGen},
prelude::*,
rand_core::{RngCore, SeedableRng},
};
pub struct MuSig<H, NG> {
pk_hash: H,
coeff_hash: H,
nonce_coeff_hash: H,
pub schnorr: Schnorr<H, NG>,
nonce_gen: NG,
}
impl<H, NG> MuSig<H, NG> {
pub fn new_keypair(&self, secret_key: Scalar) -> KeyPair {
KeyPair::<Normal>::new(secret_key)
}
pub fn nonce_gen(&self) -> &NG {
&self.nonce_gen
}
pub fn gen_nonce<R: RngCore>(&self, nonce_rng: &mut R) -> binonce::NonceKeyPair {
binonce::NonceKeyPair::random(nonce_rng)
}
}
impl<H, NG> MuSig<H, NG>
where
H: Tag + Default,
NG: Tag + Clone,
{
pub fn new(schnorr: Schnorr<H, NG>) -> Self {
Self {
pk_hash: H::default().tag(b"KeyAgg list"),
coeff_hash: H::default().tag(b"KeyAgg coefficient"),
nonce_coeff_hash: H::default().tag(b"MuSig/noncecoef"),
nonce_gen: schnorr.nonce_gen().clone().tag(b"MuSig"),
schnorr,
}
}
}
impl<H, NG> Default for MuSig<H, NG>
where
H: Tag + Default,
NG: Default + Clone + Tag,
{
fn default() -> Self {
MuSig::new(Schnorr::<H, NG>::default())
}
}
#[derive(Debug, Clone)]
pub struct AggKey<T> {
keys: Vec<Point>,
coefs: Vec<Scalar<Public>>,
needs_negation: bool,
agg_key: Point<T>,
tweak: Scalar<Public, Zero>,
}
impl<T: Copy> AggKey<T> {
pub fn agg_public_key(&self) -> Point<T> {
self.agg_key
}
pub fn keys(&self) -> impl Iterator<Item = Point> + '_ {
self.keys.iter().copied()
}
}
impl AggKey<Normal> {
pub fn into_xonly_key(self) -> AggKey<EvenY> {
let (agg_key, needs_negation) = self.agg_key.into_point_with_even_y();
let mut tweak = self.tweak;
tweak.conditional_negate(needs_negation);
AggKey {
keys: self.keys,
coefs: self.coefs,
needs_negation,
tweak,
agg_key,
}
}
pub fn tweak(self, tweak: Scalar<impl Secrecy, impl ZeroChoice>) -> Option<Self> {
let agg_key = g!(self.agg_key + tweak * G).normalize().non_zero()?;
let tweak = s!(self.tweak + tweak).public();
Some(AggKey {
keys: self.keys,
coefs: self.coefs,
needs_negation: false,
agg_key,
tweak,
})
}
}
impl AggKey<EvenY> {
pub fn tweak(self, tweak: Scalar<impl Secrecy, impl ZeroChoice>) -> Option<Self> {
let (new_agg_key, needs_negation) = g!(self.agg_key + tweak * G)
.normalize()
.non_zero()?
.into_point_with_even_y();
let mut new_tweak = s!(self.tweak + tweak).public();
new_tweak.conditional_negate(needs_negation);
let needs_negation = self.needs_negation ^ needs_negation;
Some(Self {
keys: self.keys,
coefs: self.coefs,
needs_negation,
tweak: new_tweak,
agg_key: new_agg_key,
})
}
}
impl<H: Hash32, NG> MuSig<H, NG> {
pub fn new_agg_key(&self, keys: Vec<Point>) -> AggKey<Normal> {
let coeff_hash = {
let L = self.pk_hash.clone().add(&keys[..]).finalize_fixed();
self.coeff_hash.clone().add(L.as_slice())
};
let mut second = None;
let coefs = keys
.iter()
.map(|key| {
if second.is_none() && key != &keys[0] {
second = Some(key);
}
if second != Some(key) {
Scalar::from_hash(coeff_hash.clone().add(key))
} else {
Scalar::one()
}
.public()
})
.collect::<Vec<_>>();
let agg_key = g!(&coefs .* &keys)
.non_zero().expect("computationally unreachable: linear combination of hash randomised points cannot add to zero");
AggKey {
keys,
coefs,
agg_key: agg_key.normalize(),
tweak: Scalar::zero(),
needs_negation: false,
}
}
}
impl<H, NG> MuSig<H, NG>
where
H: Hash32,
NG: NonceGen,
{
pub fn seed_nonce_rng<R: SeedableRng<Seed = [u8; 32]>>(
&self,
agg_key: &AggKey<impl Normalized>,
secret: &Scalar,
session_id: &[u8],
) -> R {
let sid_len = (session_id.len() as u64).to_be_bytes();
let pk_bytes = agg_key.agg_public_key().to_xonly_bytes();
let rng: R = secp256kfun::derive_nonce_rng!(
nonce_gen => self.nonce_gen(),
secret => &secret,
public => [pk_bytes, sid_len, session_id],
seedable_rng => R
);
rng
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(crate::fun::serde::Deserialize, crate::fun::serde::Serialize),
serde(crate = "crate::fun::serde")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct Ordinary;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(crate::fun::serde::Deserialize, crate::fun::serde::Serialize),
serde(crate = "crate::fun::serde")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct Adaptor {
y_needs_negation: bool,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(crate::fun::serde::Deserialize, crate::fun::serde::Serialize),
serde(crate = "crate::fun::serde")
)]
#[cfg_attr(feature = "bincode", derive(bincode::Encode, bincode::Decode))]
pub struct SignSession<T = Ordinary> {
b: Scalar<Public>,
c: Scalar<Public, Zero>,
public_nonces: Vec<binonce::Nonce>,
R: Point<EvenY>,
nonce_needs_negation: bool,
signing_type: T,
}
impl<H: Hash32, NG> MuSig<H, NG> {
pub fn start_sign_session(
&self,
agg_key: &AggKey<EvenY>,
nonces: Vec<binonce::Nonce>,
message: Message<'_>,
) -> SignSession {
let (b, c, public_nonces, R, nonce_needs_negation) =
self._start_sign_session(agg_key, nonces, message, Point::<Normal, Public, _>::zero());
SignSession {
b,
c,
public_nonces,
R,
nonce_needs_negation,
signing_type: Ordinary,
}
}
pub fn start_encrypted_sign_session(
&self,
agg_key: &AggKey<EvenY>,
nonces: Vec<binonce::Nonce>,
message: Message<'_>,
encryption_key: Point<impl PointType, impl Secrecy, impl ZeroChoice>,
) -> Option<SignSession<Adaptor>> {
let (b, c, public_nonces, R, nonce_needs_negation) =
self._start_sign_session(agg_key, nonces, message, encryption_key);
Some(SignSession {
b,
c,
public_nonces,
R,
nonce_needs_negation,
signing_type: Adaptor {
y_needs_negation: nonce_needs_negation,
},
})
}
#[allow(clippy::type_complexity)]
fn _start_sign_session(
&self,
agg_key: &AggKey<EvenY>,
mut nonces: Vec<binonce::Nonce>,
message: Message<'_>,
encryption_key: Point<impl PointType, impl Secrecy, impl ZeroChoice>,
) -> (
Scalar<Public>,
Scalar<Public, Zero>,
Vec<binonce::Nonce>,
Point<EvenY>,
bool,
) {
let agg_binonce = binonce::Nonce::aggregate_and_add(nonces.iter().cloned(), encryption_key);
let binding_coeff = {
let H = self.nonce_coeff_hash.clone();
Scalar::from_hash(
H.add(agg_binonce)
.add(agg_key.agg_public_key())
.add(message),
)
}
.public();
let (R, nonces_need_negation) = agg_binonce.bind(binding_coeff);
let c = self
.schnorr
.challenge(&R, &agg_key.agg_public_key(), message);
for nonce in &mut nonces {
nonce.conditional_negate(nonces_need_negation);
}
(binding_coeff, c, nonces, R, nonces_need_negation)
}
pub fn sign<T>(
&self,
agg_key: &AggKey<EvenY>,
session: &SignSession<T>,
my_index: usize,
keypair: &KeyPair,
local_secret_nonce: impl AsRef<SecretNonce>,
) -> Scalar<Public, Zero> {
assert_eq!(
keypair.public_key(),
agg_key.keys().nth(my_index).unwrap(),
"key at index {my_index} didn't match",
);
let c = session.c;
let b = session.b;
let x_i = keypair.secret_key();
let mut a = agg_key.coefs[my_index];
a.conditional_negate(agg_key.needs_negation);
let [mut r1, mut r2] = local_secret_nonce.as_ref().0;
r1.conditional_negate(session.nonce_needs_negation);
r2.conditional_negate(session.nonce_needs_negation);
s!(c * a * x_i + r1 + b * r2).public()
}
#[must_use]
pub fn verify_partial_signature<T>(
&self,
agg_key: &AggKey<EvenY>,
session: &SignSession<T>,
index: usize,
partial_sig: Scalar<Public, Zero>,
) -> bool {
let c = session.c;
let b = session.b;
let s_i = &partial_sig;
let a = agg_key.coefs[index];
let X_i = agg_key
.keys()
.nth(index)
.unwrap()
.conditional_negate(agg_key.needs_negation);
let [R1, R2] = &session.public_nonces[index].0;
g!((c * a) * X_i + R1 + b * R2 - s_i * G).is_zero()
}
pub fn combine_partial_signatures(
&self,
agg_key: &AggKey<EvenY>,
session: &SignSession<Ordinary>,
partial_sigs: impl IntoIterator<Item = Scalar<Public, Zero>>,
) -> Signature {
let (R, s) = self._combine_partial_signatures(agg_key, session, partial_sigs);
Signature { R, s }
}
pub fn combine_partial_encrypted_signatures(
&self,
agg_key: &AggKey<EvenY>,
session: &SignSession<Adaptor>,
partial_encrypted_sigs: impl IntoIterator<Item = Scalar<Public, Zero>>,
) -> EncryptedSignature {
let (R, s_hat) = self._combine_partial_signatures(agg_key, session, partial_encrypted_sigs);
EncryptedSignature {
R,
s_hat,
needs_negation: session.signing_type.y_needs_negation,
}
}
fn _combine_partial_signatures<T>(
&self,
agg_key: &AggKey<EvenY>,
session: &SignSession<T>,
partial_sigs: impl IntoIterator<Item = Scalar<Public, Zero>>,
) -> (Point<EvenY>, Scalar<Public, Zero>) {
let sum_s = partial_sigs
.into_iter()
.reduce(|acc, s| s!(acc + s).public())
.unwrap_or(Scalar::zero());
let s = s!(sum_s + agg_key.tweak * session.c).public();
(session.R, s)
}
}
pub fn new_with_deterministic_nonces<H>() -> MuSig<H, nonce::Deterministic<H>>
where
H: Hash32,
{
MuSig::default()
}
pub fn new_with_synthetic_nonces<H, R>() -> MuSig<H, nonce::Synthetic<H, nonce::GlobalRng<R>>>
where
H: Hash32,
R: RngCore + Default + Clone,
{
MuSig::default()
}
pub fn new_without_nonce_generation<H>() -> MuSig<H, NoNonces>
where
H: Hash32,
{
MuSig::default()
}
#[cfg(test)]
mod test {
use crate::adaptor::Adaptor;
use super::*;
use rand_chacha::ChaCha20Rng;
use secp256kfun::proptest::{option, prelude::*};
use sha2::Sha256;
proptest! {
#[test]
fn proptest_sign_verify(sk1 in any::<Scalar>(),
sk2 in any::<Scalar>(),
sk3 in any::<Scalar>(),
pre_tweak1 in option::of(any::<Scalar<Public, Zero>>()),
pre_tweak2 in option::of(any::<Scalar<Public, Zero>>()),
tweak1 in option::of(any::<Scalar<Public, Zero>>()),
tweak2 in option::of(any::<Scalar<Public, Zero>>()),
) {
let schnorr = Schnorr::<Sha256, nonce::Deterministic<Sha256>>::default();
let musig = MuSig::new(schnorr);
let keypair1 = musig
.new_keypair(sk1);
let keypair2 = musig
.new_keypair(sk2);
let keypair3 = musig
.new_keypair(sk3);
let mut agg_key1 = musig.new_agg_key(vec![
keypair1.public_key(),
keypair2.public_key(),
keypair3.public_key(),
]);
let mut agg_key2 = musig.new_agg_key(vec![
keypair1.public_key(),
keypair2.public_key(),
keypair3.public_key(),
]);
let mut agg_key3 = musig.new_agg_key(vec![
keypair1.public_key(),
keypair2.public_key(),
keypair3.public_key(),
]);
for tweak in [pre_tweak1, pre_tweak2].into_iter().flatten() {
agg_key1 = agg_key1.tweak(tweak).unwrap();
agg_key2 = agg_key2.tweak(tweak).unwrap();
agg_key3 = agg_key3.tweak(tweak).unwrap();
}
let mut agg_key1 = agg_key1.into_xonly_key();
let mut agg_key2 = agg_key2.into_xonly_key();
let mut agg_key3 = agg_key3.into_xonly_key();
for tweak in [tweak1, tweak2].into_iter().flatten() {
agg_key1 = agg_key1.tweak(tweak).unwrap();
agg_key2 = agg_key2.tweak(tweak).unwrap();
agg_key3 = agg_key3.tweak(tweak).unwrap();
}
assert_eq!(agg_key1.agg_public_key(), agg_key2.agg_public_key());
assert_eq!(agg_key1.agg_public_key(), agg_key3.agg_public_key());
let message =
Message::new("test", b"Chancellor on brink of second bailout for banks");
let session_id = message.bytes;
let mut nonce_rng: ChaCha20Rng = musig.seed_nonce_rng(&agg_key1, keypair1.secret_key(), session_id);
let p1_nonce = musig.gen_nonce(&mut nonce_rng);
let p2_nonce = musig.gen_nonce(&mut nonce_rng);
let p3_nonce = musig.gen_nonce(&mut nonce_rng);
let nonces = vec![p1_nonce.public, p2_nonce.public, p3_nonce.public];
let p1_session = musig
.start_sign_session(
&agg_key1,
nonces.clone(),
message,
);
let p2_session = musig
.start_sign_session(
&agg_key2,
nonces.clone(),
message,
);
let p3_session = musig
.start_sign_session(
&agg_key3,
nonces,
message,
);
let p1_sig = musig.sign(&agg_key1, &p1_session, 0, &keypair1, p1_nonce);
assert!(musig.verify_partial_signature(&agg_key1, &p1_session, 0, p1_sig));
assert_eq!(p1_session, p2_session);
assert!(musig.verify_partial_signature(&agg_key1, &p2_session, 0, p1_sig));
assert!(musig.verify_partial_signature(&agg_key1, &p3_session, 0, p1_sig));
let p2_sig = musig.sign(&agg_key1, &p2_session, 1, &keypair2, p2_nonce);
assert!(musig.verify_partial_signature(&agg_key1, &p1_session, 1, p2_sig));
let p3_sig = musig.sign(&agg_key1, &p3_session, 2, &keypair3, p3_nonce);
assert!(musig.verify_partial_signature(&agg_key1, &p1_session, 2, p3_sig));
let partial_sigs = [p1_sig, p2_sig, p3_sig];
let sig_p1 = musig.combine_partial_signatures(&agg_key1, &p1_session, partial_sigs);
let sig_p2 = musig.combine_partial_signatures(&agg_key1, &p2_session, partial_sigs);
let sig_p3 = musig.combine_partial_signatures(&agg_key1, &p3_session, partial_sigs);
assert_eq!(sig_p1, sig_p2);
assert_eq!(sig_p1, sig_p3);
assert!(musig
.schnorr
.verify(&agg_key1.agg_public_key(), message, &sig_p1));
assert!(musig
.schnorr
.verify(&agg_key1.agg_public_key(), message, &sig_p2));
assert!(musig
.schnorr
.verify(&agg_key1.agg_public_key(), message, &sig_p3));
}
#[test]
fn test_musig_adaptor(
sk1 in any::<Scalar>(),
sk2 in any::<Scalar>(),
sk3 in any::<Scalar>(),
y in any::<Scalar>()
) {
let schnorr = Schnorr::<Sha256, nonce::Deterministic<Sha256>>::default();
let musig = MuSig::new(schnorr);
let keypair1 = musig
.new_keypair(sk1);
let keypair2 = musig
.new_keypair(sk2);
let keypair3 = musig
.new_keypair(sk3);
let encryption_key = musig.schnorr.encryption_key_for(&y);
let agg_key1 = musig.new_agg_key(vec![
keypair1.public_key(),
keypair2.public_key(),
keypair3.public_key(),
]).into_xonly_key();
let agg_key2 = musig.new_agg_key(vec![
keypair1.public_key(),
keypair2.public_key(),
keypair3.public_key(),
]).into_xonly_key();
let agg_key3 = musig.new_agg_key(vec![
keypair1.public_key(),
keypair2.public_key(),
keypair3.public_key(),
]).into_xonly_key();
let message =
Message::new("test", b"Chancellor on brink of second bailout for banks");
let session_id = message.bytes;
let mut nonce_rng: ChaCha20Rng = musig.seed_nonce_rng(&agg_key1, keypair1.secret_key(), session_id);
let p1_nonce = musig.gen_nonce(&mut nonce_rng);
let p2_nonce = musig.gen_nonce(&mut nonce_rng);
let p3_nonce = musig.gen_nonce(&mut nonce_rng);
let nonces = vec![p1_nonce.public, p2_nonce.public, p3_nonce.public];
let p1_session = musig
.start_encrypted_sign_session(
&agg_key1,
nonces.clone(),
message,
encryption_key
)
.unwrap();
let p2_session = musig
.start_encrypted_sign_session(
&agg_key2,
nonces.clone(),
message,
encryption_key
)
.unwrap();
let p3_session = musig
.start_encrypted_sign_session(
&agg_key3,
nonces,
message,
encryption_key
)
.unwrap();
let p1_sig = musig.sign(&agg_key1, &p1_session, 0, &keypair1, p1_nonce);
let p2_sig = musig.sign(&agg_key1, &p2_session, 1, &keypair2, p2_nonce);
let p3_sig = musig.sign(&agg_key1, &p3_session, 2, &keypair3, p3_nonce);
assert!(musig.verify_partial_signature(&agg_key2, &p2_session, 0, p1_sig));
assert!(musig.verify_partial_signature(&agg_key1, &p1_session, 0, p1_sig));
let partial_sigs = vec![p1_sig, p2_sig, p3_sig];
let combined_sig_p1 = musig.combine_partial_encrypted_signatures(&agg_key1, &p1_session, partial_sigs.clone());
let combined_sig_p2 = musig.combine_partial_encrypted_signatures(&agg_key2, &p2_session, partial_sigs.clone());
let combined_sig_p3 = musig.combine_partial_encrypted_signatures(&agg_key3, &p3_session, partial_sigs);
assert_eq!(combined_sig_p1, combined_sig_p2);
assert_eq!(combined_sig_p1, combined_sig_p3);
assert!(musig
.schnorr
.verify_encrypted_signature(&agg_key1.agg_public_key(), &encryption_key, message, &combined_sig_p1));
assert!(musig
.schnorr
.verify_encrypted_signature(&agg_key2.agg_public_key(), &encryption_key, message, &combined_sig_p2));
assert!(musig
.schnorr
.verify_encrypted_signature(&agg_key2.agg_public_key(), &encryption_key, message, &combined_sig_p3));
}
}
}