Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MuSig2 multisignature scheme #80

Merged
merged 10 commits into from
Jan 13, 2022
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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any rationale on why you go this way (by value instead ref?)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XOnly is Copy so I think it's conventional to not return references to Copy things. On top of that There's no reason to leak the implementation that keyPair stores an XOnly (it could store a Point<EvenY> instead).

(that comment needs to change though).

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