Skip to content

Commit

Permalink
Merge pull request #80 from LLFourn/musig2
Browse files Browse the repository at this point in the history
Implement MuSig2 multisignature scheme
  • Loading branch information
LLFourn authored Jan 13, 2022
2 parents c121f10 + 1150026 commit 017aaf3
Show file tree
Hide file tree
Showing 18 changed files with 1,590 additions and 156 deletions.
97 changes: 52 additions & 45 deletions schnorr_fun/src/adaptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use crate::{
g,
marker::*,
nonce::NonceGen,
s, Point, Scalar,
s, Point, Scalar, G,
},
KeyPair, Message, Schnorr, Signature,
};
Expand All @@ -78,7 +78,7 @@ pub trait EncryptedSign {
) -> EncryptedSignature;
}

impl<NG, CH, GT> EncryptedSign for Schnorr<CH, NG, GT>
impl<NG, CH> EncryptedSign for Schnorr<CH, NG>
where
CH: Digest<OutputSize = U32> + Clone,
NG: NonceGen,
Expand All @@ -98,7 +98,7 @@ where
public => [X, Y, message]
);

let R = g!(r * { self.G() } + Y)
let R = g!(r * G + Y)
// R_hat = r * G is sampled pseudorandomly for every Y which means R_hat + Y is also
// be pseudoranodm and therefore will not be zero.
// NOTE: Crucially we add Y to the nonce derivation to ensure this is true.
Expand All @@ -110,7 +110,7 @@ where
// key before decrypting it
r.conditional_negate(needs_negation);

let c = self.challenge(&R.to_xonly(), X, message);
let c = self.challenge(R.to_xonly(), X, message);
let s_hat = s!(r + c * x).mark::<Public>();

EncryptedSignature {
Expand Down Expand Up @@ -188,12 +188,12 @@ pub trait Adaptor {
) -> Option<Scalar>;
}

impl<CH, NG, GT> Adaptor for Schnorr<CH, NG, GT>
impl<CH, NG> Adaptor for Schnorr<CH, NG>
where
CH: Digest<OutputSize = U32> + Clone,
{
fn encryption_key_for(&self, decryption_key: &Scalar) -> Point {
g!(decryption_key * { self.G() }).normalize()
g!(decryption_key * G).normalize()
}

#[must_use]
Expand All @@ -216,9 +216,9 @@ where
// !needs_negation => R_hat = R - Y
let R_hat = g!(R + { Y.conditional_negate(!needs_negation) });

let c = self.challenge(&R.to_xonly(), &X.to_xonly(), message);
let c = self.challenge(R.to_xonly(), X.to_xonly(), message);

R_hat == g!(s_hat * { self.G() } - c * X)
R_hat == g!(s_hat * G - c * X)
}

fn decrypt_signature(
Expand Down Expand Up @@ -257,7 +257,7 @@ where

let mut y = s!(s - s_hat);
y.conditional_negate(*needs_negation);
let implied_encryption_key = g!(y * { self.G() });
let implied_encryption_key = g!(y * G);

if implied_encryption_key == *encryption_key {
Some(y.expect_nonzero("unreachable - encryption_key is NonZero and y*G equals it"))
Expand All @@ -271,48 +271,55 @@ where
mod test {

use super::*;
use crate::nonce::{self, Deterministic};
use secp256kfun::TEST_SOUNDNESS;
use crate::nonce::{Deterministic, GlobalRng, Synthetic};
use rand::rngs::ThreadRng;
use secp256kfun::proptest::prelude::*;
use sha2::Sha256;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;

fn test_schnorr<NG: NonceGen>(schnorr: Schnorr<sha2::Sha256, NG>) {
for _ in 0..TEST_SOUNDNESS {
let signing_keypair = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng()));
let verification_key = signing_keypair.verification_key();
let decryption_key = Scalar::random(&mut rand::thread_rng());
let encryption_key = schnorr.encryption_key_for(&decryption_key);
let message = Message::<Public>::plain("test", b"give 100 coins to Bob".as_ref());

let encrypted_signature =
schnorr.encrypted_sign(&signing_keypair, &encryption_key, message);

assert!(schnorr.verify_encrypted_signature(
&signing_keypair.verification_key(),
&encryption_key,
message,
&encrypted_signature,
));
proptest! {
#[test]
fn signing_tests_deterministic(secret_key in any::<Scalar>(), decryption_key in any::<Scalar>()) {
let schnorr = Schnorr::<Sha256, Deterministic<Sha256>>::default();
test_it(schnorr, secret_key, decryption_key);
}

let decryption_key = decryption_key.mark::<Public>();
let signature =
schnorr.decrypt_signature(decryption_key.clone(), encrypted_signature.clone());
assert!(schnorr.verify(&verification_key, message, &signature));
let rec_decryption_key = schnorr
.recover_decryption_key(&encryption_key, &encrypted_signature, &signature)
.expect("recovery works");
assert_eq!(rec_decryption_key, decryption_key);
#[test]
fn signing_tests_synthetic(secret_key in any::<Scalar>(), decryption_key in any::<Scalar>()) {
let schnorr = Schnorr::<Sha256, Synthetic<Sha256, GlobalRng<ThreadRng>>>::default();
test_it(schnorr, secret_key, decryption_key);
}

}

#[test]
fn sign_plain_message() {
use rand::rngs::ThreadRng;
use sha2::Sha256;
test_schnorr(Schnorr::new(Deterministic::<Sha256>::default()));
test_schnorr(Schnorr::new(nonce::Synthetic::<
Sha256,
nonce::GlobalRng<ThreadRng>,
>::default()));
fn test_it<NG: NonceGen>(
schnorr: Schnorr<Sha256, NG>,
secret_key: Scalar,
decryption_key: Scalar,
) {
let signing_keypair = schnorr.new_keypair(secret_key);
let verification_key = signing_keypair.verification_key();
let encryption_key = schnorr.encryption_key_for(&decryption_key);
let message = Message::<Public>::plain("test", b"give 100 coins to Bob".as_ref());

let encrypted_signature =
schnorr.encrypted_sign(&signing_keypair, &encryption_key, message);

assert!(schnorr.verify_encrypted_signature(
&signing_keypair.verification_key(),
&encryption_key,
message,
&encrypted_signature,
));

let decryption_key = decryption_key.mark::<Public>();
let signature =
schnorr.decrypt_signature(decryption_key.clone(), encrypted_signature.clone());
assert!(schnorr.verify(&verification_key, message, &signature));
let rec_decryption_key = schnorr
.recover_decryption_key(&encryption_key, &encrypted_signature, &signature)
.expect("recovery works");
assert_eq!(rec_decryption_key, decryption_key);
}
}
12 changes: 6 additions & 6 deletions schnorr_fun/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use secp256kfun::{marker::*, Point, Scalar, XOnly};
/// ```
///
/// [`Schnorr`]: crate::Schnorr
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct KeyPair {
pub(crate) sk: Scalar,
pub(crate) pk: XOnly,
Expand All @@ -27,9 +27,9 @@ impl KeyPair {
&self.sk
}

/// Returns a reference to the public key.
pub fn public_key(&self) -> &XOnly {
&self.pk
/// The public key
pub fn public_key(&self) -> XOnly {
self.pk
}

/// Gets a reference to the key-pair as a tuple
Expand All @@ -39,8 +39,8 @@ impl KeyPair {
/// # use schnorr_fun::{Schnorr, fun::Scalar};
/// # let keypair = schnorr_fun::test_instance!().new_keypair(Scalar::one());
/// let (secret_key, public_key) = keypair.as_tuple();
pub fn as_tuple(&self) -> (&Scalar, &XOnly) {
(&self.sk, &self.pk)
pub fn as_tuple(&self) -> (&Scalar, XOnly) {
(&self.sk, self.pk)
}

/// Returns the full `Point<EvenY>` for the public key which is used in [`verify`].
Expand Down
12 changes: 11 additions & 1 deletion schnorr_fun/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@
#![no_std]
#![allow(non_snake_case)]
#![doc = include_str!("../README.md")]
#![deny(warnings, missing_docs)]
#![warn(missing_docs)]

#[cfg(all(feature = "alloc", not(feature = "std")))]
#[macro_use]
extern crate alloc;
#[cfg(all(feature = "alloc", not(feature = "std")))]
pub(crate) use alloc::vec::Vec;

#[cfg(feature = "std")]
#[macro_use]
extern crate std;
#[cfg(feature = "std")]
pub(crate) use std::vec::Vec;

#[cfg(feature = "serde")]
extern crate serde_crate as serde;

pub use secp256kfun as fun;
pub use secp256kfun::nonce;

// musig needs vecs
#[cfg(feature = "alloc")]
pub mod musig;

mod signature;
pub use signature::Signature;
pub mod adaptor;
Expand Down
4 changes: 2 additions & 2 deletions schnorr_fun/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl<'a, 'b, S: Secrecy> Message<'a, S> {
///
/// [here]: https://github.com/sipa/bips/issues/207#issuecomment-673681901
pub fn plain(app_tag: &'static str, bytes: &'a [u8]) -> Self {
assert!(app_tag.len() <= 64, "tag must not be 64 bytes or less");
assert!(app_tag.len() <= 64, "tag must be 64 bytes or less");
assert!(!app_tag.is_empty(), "tag must not be empty");
Message {
bytes: bytes.mark::<S>(),
Expand All @@ -39,7 +39,7 @@ impl<'a, 'b, S: Secrecy> Message<'a, S> {
}

impl<S> HashInto for Message<'_, S> {
fn hash_into(&self, hash: &mut impl Digest) {
fn hash_into(self, hash: &mut impl Digest) {
if let Some(prefix) = self.app_tag {
let mut padded_prefix = [0u8; 64];
padded_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
Expand Down
Loading

0 comments on commit 017aaf3

Please sign in to comment.