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
4 changes: 2 additions & 2 deletions schnorr_fun/src/adaptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -216,7 +216,7 @@ 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)
}
Expand Down
8 changes: 4 additions & 4 deletions schnorr_fun/src/keypair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ impl KeyPair {
}

/// Returns a reference to the public key.
pub fn public_key(&self) -> &XOnly {
&self.pk
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
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
17 changes: 6 additions & 11 deletions schnorr_fun/src/schnorr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ where
);

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

Signature { R, s }
Expand Down Expand Up @@ -168,20 +168,15 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
/// let keypair = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng()));
/// let mut r = Scalar::random(&mut rand::thread_rng());
/// let R = XOnly::from_scalar_mul(schnorr.G(), &mut r);
/// let challenge = schnorr.challenge(&R, keypair.public_key(), message);
/// let challenge = schnorr.challenge(R, keypair.public_key(), message);
/// let s = s!(r + challenge * { keypair.secret_key() });
/// let signature = Signature { R, s };
/// assert!(schnorr.verify(&keypair.verification_key(), message, &signature));
/// ```
///
/// [BIP-340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki
/// [`Secrecy`]: secp256kfun::marker::Secrecy
pub fn challenge<S: Secrecy>(
&self,
R: &XOnly,
X: &XOnly,
m: Message<'_, S>,
) -> Scalar<S, Zero> {
pub fn challenge<S: Secrecy>(&self, R: XOnly, X: XOnly, m: Message<'_, S>) -> Scalar<S, Zero> {
let hash = self.challenge_hash.clone();
let challenge = Scalar::from_hash(hash.add(R).add(X).add(&m));

Expand Down Expand Up @@ -228,9 +223,9 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
) -> bool {
let X = public_key;
let (R, s) = signature.as_tuple();
let c = self.challenge(R, &X.to_xonly(), message);
let c = self.challenge(R, X.to_xonly(), message);
let R_implied = g!(s * self.G - c * X).mark::<Normal>();
R_implied == *R
R_implied == R
}

/// _Anticipates_ a Schnorr signature given the nonce `R` that will be used ahead of time.
Expand All @@ -242,7 +237,7 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
R: &Point<EvenY, impl Secrecy>,
m: Message<'_, impl Secrecy>,
) -> Point<Jacobian, Public, Zero> {
let c = self.challenge(&R.to_xonly(), &X.to_xonly(), m);
let c = self.challenge(R.to_xonly(), X.to_xonly(), m);
g!(R + c * X)
}
}
Expand Down
4 changes: 2 additions & 2 deletions schnorr_fun/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ impl<S> Signature<S> {
/// # let signature = schnorr_fun::Signature::random(&mut rand::thread_rng());
/// let (R, s) = signature.as_tuple();
/// ```
pub fn as_tuple(&self) -> (&XOnly, &Scalar<S, Zero>) {
(&self.R, &self.s)
pub fn as_tuple(&self) -> (XOnly, &Scalar<S, Zero>) {
(self.R, &self.s)
}

/// Marks the signature with a [`Secrecy`]. If it is marked as `Secret` the
Expand Down
2 changes: 1 addition & 1 deletion schnorr_fun/tests/bip340.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fn signing_test_vectors() {
let secret_key = Scalar::<Secret, NonZero>::from_str(line[1]).unwrap();
let expected_public_key = XOnly::from_str(line[2]).unwrap();
let keypair = bip340.new_keypair(secret_key);
assert_eq!(keypair.public_key(), &expected_public_key);
assert_eq!(keypair.public_key(), expected_public_key);
let message = hex::decode(line[4]).unwrap();
let signature = bip340.sign(&keypair, Message::<Public>::raw(&message));
let expected_signature = Signature::<Public>::from_str(line[5]).unwrap();
Expand Down
6 changes: 3 additions & 3 deletions secp256kfun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ digest = "0.9"
subtle = { package = "subtle-ng", version = "2" }
rand_core = { version = "0.6" }
serde_crate = { package = "serde", version = "1.0", optional = true, default-features = false, features = ["derive"] }
# secp256kfun_k256_backend = { path = "../../k256_backend/k256", features = ["expose-field"] }
secp256kfun_k256_backend = { version = "1.0.0", features = ["expose-field"] }
# secp256kfun_k256_backend = { path = "../../k256_backend/k256" }
secp256kfun_k256_backend = { version = "2.0.0" }
secp256kfun_parity_backend = { version = "0.1.5", optional = true }
secp256k1 = { version = "0.20", optional = true, default-features = false }
proptest = { version = "1", optional = true }
Expand All @@ -48,7 +48,7 @@ wasm-bindgen-test = "0.3"
[features]
default = ["std"]
all = ["std", "serde", "libsecp_compat", "nightly"]
alloc = ["serde_crate/alloc"]
alloc = ["serde_crate/alloc", "secp256kfun_k256_backend/alloc"]
std = ["alloc"]
libsecp_compat = ["secp256k1"]
serde = [ "serde_crate" ]
Expand Down
15 changes: 15 additions & 0 deletions secp256kfun/src/backend/k256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,13 @@ impl TimeSensitive for ConstantTime {
fn xonly_eq(lhs: &XOnly, rhs: &XOnly) -> bool {
lhs.0.ct_eq(&rhs.0).into()
}

fn lincomb_iter<'a, 'b, A: Iterator<Item = &'a Point>, B: Iterator<Item = &'b Scalar>>(
points: A,
scalars: B,
) -> Point {
secp256kfun_k256_backend::lincomb_iter(points, scalars)
}
}

pub struct VariableTime;
Expand Down Expand Up @@ -439,6 +446,14 @@ impl TimeSensitive for VariableTime {
fn point_double_mul(x: &Scalar, A: &Point, y: &Scalar, B: &Point) -> Point {
ConstantTime::point_double_mul(x, A, y, B)
}

#[cfg(feature = "alloc")]
fn lincomb_iter<'a, 'b, A: Iterator<Item = &'a Point>, B: Iterator<Item = &'b Scalar>>(
points: A,
scalars: B,
) -> Point {
ConstantTime::lincomb_iter(points, scalars)
}
}

impl VariableTime {
Expand Down
8 changes: 8 additions & 0 deletions secp256kfun/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,12 @@ pub trait TimeSensitive {
fn scalar_invert(scalar: &Scalar) -> Scalar;
fn scalar_mul_basepoint(scalar: &Scalar, base: &BasePoint) -> Point;
fn xonly_eq(lhs: &XOnly, rhs: &XOnly) -> bool;
fn lincomb_iter<'a, 'b, A: Iterator<Item = &'a Point>, B: Iterator<Item = &'b Scalar>>(
points: A,
scalars: B,
) -> Point {
points.zip(scalars).fold(Point::zero(), |acc, (X, k)| {
Self::point_add_point(&acc, &Self::scalar_mul_point(k, X))
})
}
}
46 changes: 36 additions & 10 deletions secp256kfun/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,42 +56,68 @@ where
/// ```
/// use digest::Digest;
/// use secp256kfun::hash::{HashAdd, HashInto};
/// #[derive(Clone, Copy)]
/// struct CryptoData([u8; 42]);
///
/// impl HashInto for CryptoData {
/// fn hash_into(&self, hash: &mut impl digest::Digest) {
/// fn hash_into(self, hash: &mut impl digest::Digest) {
/// hash.update(&self.0[..])
/// }
/// }
///
/// let cryptodata = CryptoData([42u8; 42]);
/// let hash = sha2::Sha256::default().add(&cryptodata).finalize();
/// let hash = sha2::Sha256::default().add(cryptodata).finalize();
/// ```
pub trait HashInto {
/// Asks the item to convert itself to bytes and add itself to `hash`.
fn hash_into(&self, hash: &mut impl digest::Digest);
fn hash_into(self, hash: &mut impl digest::Digest);
}

impl HashInto for [u8] {
fn hash_into(&self, hash: &mut impl digest::Digest) {
hash.update(self)
impl HashInto for u8 {
fn hash_into(self, hash: &mut impl digest::Digest) {
hash.update(&[self])
}
}

impl HashInto for str {
fn hash_into(&self, hash: &mut impl digest::Digest) {
impl<'a, T: HashInto + Clone> HashInto for &'a T {
fn hash_into(self, hash: &mut impl digest::Digest) {
self.clone().hash_into(hash)
}
}

impl<'a, T> HashInto for &'a [T]
where
&'a T: HashInto,
{
fn hash_into(self, hash: &mut impl digest::Digest) {
for item in self {
item.hash_into(hash)
}
}
}

impl HashInto for &str {
fn hash_into(self, hash: &mut impl digest::Digest) {
hash.update(self.as_bytes())
}
}

impl<T: HashInto, const N: usize> HashInto for [T; N] {
fn hash_into(self, hash: &mut impl digest::Digest) {
for item in self {
item.hash_into(hash)
}
}
}

/// Extension trait for [`digest::Digest`] to make adding things to the hash convenient.
pub trait HashAdd {
/// Converts something that implements [`HashInto`] to bytes and then incorporate the result into the digest (`self`).
fn add<HI: HashInto + ?Sized>(self, data: &HI) -> Self;
fn add<HI: HashInto>(self, data: HI) -> Self;
}

impl<D: Digest> HashAdd for D {
fn add<HI: HashInto + ?Sized>(mut self, data: &HI) -> Self {
fn add<HI: HashInto>(mut self, data: HI) -> Self {
data.hash_into(&mut self);
self
}
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ macro_rules! derive_nonce {
use core::borrow::Borrow;
use $crate::nonce::NonceGen;
Scalar::from_hash(
$nonce_gen.begin_derivation($secret.borrow())$(.add($public.borrow()))+
$nonce_gen.begin_derivation($secret.borrow())$(.add($public))+
)
}}
}
Expand Down
32 changes: 32 additions & 0 deletions secp256kfun/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ pub fn point_add<Z1, Z2, S1, S2, T1, T2>(
Point::from_inner(PointBinary::add(A, B), Jacobian)
}

/// Does a linear combination of points
pub fn lincomb<'a, T1: 'a, S1: 'a, Z1: 'a, S2: 'a, Z2: 'a>(
scalars: impl IntoIterator<Item = &'a Scalar<S2, Z2>>,
points: impl IntoIterator<Item = &'a Point<T1, S1, Z1>>,
) -> Point<Jacobian, Public, Zero> {
Point::from_inner(
ConstantTime::lincomb_iter(
points.into_iter().map(|p| &p.0),
scalars.into_iter().map(|s| &s.0),
),
Jacobian,
)
}

pub(crate) trait PointBinary<T2, S2, Z2> {
fn add(&self, rhs: &Point<T2, S2, Z2>) -> backend::Point;
fn sub(&self, rhs: &Point<T2, S2, Z2>) -> backend::Point;
Expand Down Expand Up @@ -630,4 +644,22 @@ mod test {
.unwrap();
assert_eq!(R_implied, R_expected);
}

use proptest::prelude::*;

proptest! {

#[test]
fn lincomb_against_mul(a in any::<Scalar>(),
b in any::<Scalar>(),
c in any::<Scalar>(),
A in any::<Point>(),
B in any::<Point>(),
C in any::<Point>()
) {
use crate::op::*;
assert_eq!(lincomb([&a,&b,&c], [&A,&B,&C]),
point_add(&scalar_mul_point(&a, &A), &point_add(&scalar_mul_point(&b, &B), &scalar_mul_point(&c, &C))))
}
}
}
2 changes: 1 addition & 1 deletion secp256kfun/src/point.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ impl<S, T: Normalized> Point<T, S, NonZero> {
}

impl<T: Normalized, S> HashInto for Point<T, S, NonZero> {
fn hash_into(&self, hash: &mut impl digest::Digest) {
fn hash_into(self, hash: &mut impl digest::Digest) {
hash.update(self.to_bytes().as_ref())
}
}
Expand Down
6 changes: 4 additions & 2 deletions secp256kfun/src/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ use rand_core::{CryptoRng, RngCore};
#[derive(Clone, Eq)]
pub struct Scalar<S = Secret, Z = NonZero>(pub(crate) backend::Scalar, PhantomData<(Z, S)>);

impl<Z: Clone> Copy for Scalar<Public, Z> {}

impl<Z> core::hash::Hash for Scalar<Public, Z> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state)
Expand Down Expand Up @@ -314,8 +316,8 @@ impl<S, Z> core::ops::Neg for &Scalar<S, Z> {
}
}

impl HashInto for Scalar {
fn hash_into(&self, hash: &mut impl digest::Digest) {
impl<S, Z> HashInto for Scalar<S, Z> {
fn hash_into(self, hash: &mut impl digest::Digest) {
hash.update(&self.to_bytes())
}
}
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl<'a, S> From<Slice<'a, S>> for &'a [u8] {
}

impl<'a, S> HashInto for Slice<'a, S> {
fn hash_into(&self, hash: &mut impl digest::Digest) {
fn hash_into(self, hash: &mut impl digest::Digest) {
hash.update(self.inner)
}
}
Expand Down
2 changes: 1 addition & 1 deletion secp256kfun/src/xonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl XOnly {
}

impl HashInto for XOnly {
fn hash_into(&self, hash: &mut impl digest::Digest) {
fn hash_into(self, hash: &mut impl digest::Digest) {
hash.update(self.as_bytes())
}
}
Expand Down