Skip to content

Commit 29635d2

Browse files
authored
Merge pull request #28 from LLFourn/xonly_is_even
Update for new BIP340: XOnly is always even
2 parents d83319b + 0e45abc commit 29635d2

File tree

23 files changed

+385
-540
lines changed

23 files changed

+385
-540
lines changed

schnorr_fun/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ serde = { version = "1.0", default-features = false, optional = true, features =
2121

2222
[dev-dependencies]
2323
rand = { version = "0.7", features = ["wasm-bindgen"] }
24-
hex-literal = "0.2"
2524
lazy_static = "1.4"
2625
bincode = "1.0"
2726
sha2 = "0.9"
27+
secp256kfun = { path = "../secp256kfun", version = "^0.2.5-alpha.0", features = ["alloc"] }
2828

2929

3030
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]

schnorr_fun/src/adaptor/encrypted_signature.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use secp256kfun::{marker::*, Point, Scalar};
1111
)]
1212
pub struct EncryptedSignature<S = Public> {
1313
/// The `R` point in the signature
14-
pub R: Point<SquareY, Public>,
14+
pub R: Point<EvenY, Public>,
1515
/// The _one-time encrypted_ `s` value of the signature.
1616
pub s_hat: Scalar<S, Zero>,
1717
/// Whether the decryptor should negate their decryption key prior to decryption.

schnorr_fun/src/adaptor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ where
105105
.mark::<NonZero>()
106106
.expect("computationally unreachable");
107107

108-
let (R, needs_negation) = R.into_point_with_y_choice::<SquareY>();
108+
let (R, needs_negation) = R.into_point_with_even_y();
109109
// We correct r here but we can't correct the decryption key (y) so we
110110
// store in "needs_negation" whether the decryptor needs to negate their
111111
// key before decrypting it

schnorr_fun/src/keypair.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use secp256kfun::{marker::*, Point, Scalar, XOnly};
1818
#[derive(Clone, Debug)]
1919
pub struct KeyPair {
2020
pub(crate) sk: Scalar,
21-
pub(crate) pk: XOnly<EvenY>,
21+
pub(crate) pk: XOnly,
2222
}
2323

2424
impl KeyPair {
@@ -28,7 +28,7 @@ impl KeyPair {
2828
}
2929

3030
/// Returns a reference to the public key.
31-
pub fn public_key(&self) -> &XOnly<EvenY> {
31+
pub fn public_key(&self) -> &XOnly {
3232
&self.pk
3333
}
3434

@@ -39,7 +39,7 @@ impl KeyPair {
3939
/// # use schnorr_fun::{Schnorr, fun::Scalar};
4040
/// # let keypair = schnorr_fun::test_instance!().new_keypair(Scalar::one());
4141
/// let (secret_key, public_key) = keypair.as_tuple();
42-
pub fn as_tuple(&self) -> (&Scalar, &XOnly<EvenY>) {
42+
pub fn as_tuple(&self) -> (&Scalar, &XOnly) {
4343
(&self.sk, &self.pk)
4444
}
4545

@@ -59,8 +59,14 @@ impl KeyPair {
5959
}
6060
}
6161

62-
impl From<KeyPair> for (Scalar, XOnly<EvenY>) {
62+
impl From<KeyPair> for (Scalar, XOnly) {
6363
fn from(kp: KeyPair) -> Self {
6464
(kp.sk, kp.pk)
6565
}
6666
}
67+
68+
impl AsRef<XOnly> for KeyPair {
69+
fn as_ref(&self) -> &XOnly {
70+
&self.pk
71+
}
72+
}

schnorr_fun/src/schnorr.rs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub struct Schnorr<CH, NG = (), GT = BasePoint> {
3030
///
3131
/// [`NonceGen`]: crate::nonce::NonceGen
3232
nonce_challenge_bundle: NonceChallengeBundle<CH, NG>,
33+
application_tag: Option<[u8; 64]>,
3334
}
3435

3536
/// Describes the kind of messages that will be signed with a [`Schnorr`] instance.
@@ -48,7 +49,12 @@ pub enum MessageKind {
4849
Plain {
4950
/// You must provide a tag to separate signatures from your application
5051
/// from other applications. If two [`Schnorr`] instances are created
51-
/// with a different `tag` then a signature valid for one will never be valid for the other.
52+
/// with a different `tag` then a signature valid for one will never be
53+
/// valid for the other. It will also never be valid for `Prehashed`
54+
/// instances. The specific method of domain separation used is
55+
/// described [here].
56+
///
57+
/// [here]: https://github.com/sipa/bips/issues/207#issuecomment-673681901
5258
tag: &'static str,
5359
},
5460
}
@@ -105,15 +111,26 @@ where
105111
challenge_hash: CH::default(),
106112
nonce_gen,
107113
}
108-
.add_protocol_tag("BIP340");
114+
.add_protocol_tag("BIP0340");
109115

110-
if let MessageKind::Plain { tag } = msgkind {
111-
nonce_challenge_bundle = nonce_challenge_bundle.add_application_tag(tag);
112-
}
116+
let application_tag = match msgkind {
117+
MessageKind::Prehashed => None,
118+
MessageKind::Plain { tag } => {
119+
assert!(tag.len() <= 64);
120+
// Only add the application tag to nonce gen since we will be
121+
// separately adding the tag to the challenge hash in self.challenge()
122+
nonce_challenge_bundle.nonce_gen =
123+
nonce_challenge_bundle.nonce_gen.add_application_tag(tag);
124+
let mut application_tag = [0u8; 64];
125+
application_tag[..tag.len()].copy_from_slice(tag.as_bytes());
126+
Some(application_tag)
127+
}
128+
};
113129

114130
Self {
115131
G: fun::G.clone(),
116132
nonce_challenge_bundle,
133+
application_tag,
117134
}
118135
}
119136
}
@@ -149,7 +166,7 @@ where
149166
public => [X, message]
150167
);
151168

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

@@ -205,20 +222,22 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
205222
/// let message = b"we rolled our own sign!".as_ref().mark::<Public>();
206223
/// let keypair = schnorr.new_keypair(Scalar::random(&mut rand::thread_rng()));
207224
/// let mut r = Scalar::random(&mut rand::thread_rng());
208-
/// let R = XOnly::<SquareY>::from_scalar_mul(schnorr.G(), &mut r);
225+
/// let R = XOnly::from_scalar_mul(schnorr.G(), &mut r);
209226
/// let challenge = schnorr.challenge(&R, keypair.public_key(), message);
210227
/// let s = s!(r + challenge * { keypair.secret_key() });
211228
/// let signature = Signature { R, s };
212229
/// assert!(schnorr.verify(&keypair.verification_key(), message, &signature));
213230
/// ```
214-
pub fn challenge<S: Secrecy>(
215-
&self,
216-
R: &XOnly<SquareY>,
217-
X: &XOnly<EvenY>,
218-
m: Slice<'_, S>,
219-
) -> Scalar<S, Zero> {
231+
pub fn challenge<S: Secrecy>(&self, R: &XOnly, X: &XOnly, m: Slice<'_, S>) -> Scalar<S, Zero> {
220232
let hash = self.nonce_challenge_bundle.challenge_hash.clone();
221-
let challenge = Scalar::from_hash(hash.add(R).add(X).add(&m));
233+
let mut hash = hash.add(R).add(X);
234+
235+
if let Some(tag) = self.application_tag {
236+
hash.update(&tag[..]);
237+
}
238+
239+
let challenge = Scalar::from_hash(hash.add(&m));
240+
222241
challenge
223242
// Since the challenge pre-image is adversarially controlled we
224243
// conservatively allow for it to be zero
@@ -228,8 +247,8 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
228247
}
229248

230249
/// Verifies a signature on a message under a given public key. Note that a full
231-
/// `Point<EvenY,..>` is passed in rather than a `XOnly<EvenY,..>` because it's more efficient
232-
/// for repeated verification (where as `XOnly<EvenY,..>` is more efficient for repeated
250+
/// `Point<EvenY,..>` is passed in rather than a `XOnly` because it's more efficient
251+
/// for repeated verification (where as `XOnly` is more efficient for repeated
233252
/// signing).
234253
///
235254
/// For an example see the [Synopsis](crate#synopsis)
@@ -243,7 +262,8 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
243262
let X = public_key;
244263
let (R, s) = signature.as_tuple();
245264
let c = self.challenge(R, &X.to_xonly(), message);
246-
g!(s * self.G - c * X) == *R
265+
let R_tmp = g!(s * self.G - c * X).mark::<Normal>();
266+
R_tmp == *R
247267
}
248268

249269
/// _Anticipates_ a Schnorr signature given the nonce `R` that will be used ahead of time.
@@ -252,7 +272,7 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
252272
pub fn anticipate_signature(
253273
&self,
254274
X: &Point<EvenY, impl Secrecy>,
255-
R: &Point<SquareY, impl Secrecy>,
275+
R: &Point<EvenY, impl Secrecy>,
256276
m: Slice<'_, impl Secrecy>,
257277
) -> Point<Jacobian, Public, Zero> {
258278
let c = self.challenge(&R.to_xonly(), &X.to_xonly(), m);
@@ -262,6 +282,8 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
262282

263283
#[cfg(test)]
264284
pub mod test {
285+
use fun::nonce::Deterministic;
286+
265287
use super::*;
266288
use crate::fun::TEST_SOUNDNESS;
267289
crate::fun::test_plus_wasm! {
@@ -308,5 +330,22 @@ pub mod test {
308330
assert_ne!(signature_1.R, signature_4.R);
309331
}
310332
}
333+
334+
fn deterministic_nonces_for_different_message_kinds() {
335+
use sha2::Sha256;
336+
let schnorr_1 = Schnorr::<Sha256,_>::new(Deterministic::<Sha256>::default(), MessageKind::Prehashed);
337+
let schnorr_2 = Schnorr::<Sha256,_>::new(Deterministic::<Sha256>::default(), MessageKind::Plain { tag: "two" });
338+
let schnorr_3 = Schnorr::<Sha256,_>::new(Deterministic::<Sha256>::default(), MessageKind::Plain { tag: "three" });
339+
let x = Scalar::from_str("18451f9e08af9530814243e202a4a977130e672079f5c14dcf15bd4dee723072").unwrap();
340+
let keypair = schnorr_1.new_keypair(x);
341+
let message = b"foo".as_ref().mark::<Public>();
342+
assert_ne!(schnorr_1.sign(&keypair, message).R, schnorr_2.sign(&keypair, message).R);
343+
assert_ne!(schnorr_1.sign(&keypair, message).R, schnorr_3.sign(&keypair, message).R);
344+
345+
// make sure deterministic signatures don't change
346+
use core::str::FromStr;
347+
assert_eq!(schnorr_1.sign(&keypair, message), Signature::<Public>::from_str("fe9e5d0319d5d221988d6fd7fe1c4bedd2fb4465f592f1002f461503332a266977bb4a0b00c00d07072c796212cbea0957ebaaa5139143761c45d997ebe36cbe").unwrap());
348+
assert_eq!(schnorr_2.sign(&keypair, message), Signature::<Public>::from_str("6cad3863f4d01494ce4c40f3422e4916c616356d730bc4ffe33e386f038b328ba1dc9621e626992c2612f33cdb35f4be4badc464c1f4bf3de15517e7aedcf615").unwrap());
349+
}
311350
}
312351
}

schnorr_fun/src/signature.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub struct Signature<S = Public> {
1111
/// interpreted as the [`Point`].
1212
///
1313
/// [`Point`]: secp256kfun::Point
14-
pub R: XOnly<SquareY>,
14+
pub R: XOnly,
1515
/// The challenge _response_ part of the signature.
1616
pub s: Scalar<S, Zero>,
1717
}
@@ -47,7 +47,7 @@ impl<S> Signature<S> {
4747
/// # let signature = schnorr_fun::Signature::random(&mut rand::thread_rng());
4848
/// let (R, s) = signature.as_tuple();
4949
/// ```
50-
pub fn as_tuple(&self) -> (&XOnly<SquareY>, &Scalar<S, Zero>) {
50+
pub fn as_tuple(&self) -> (&XOnly, &Scalar<S, Zero>) {
5151
(&self.R, &self.s)
5252
}
5353

@@ -84,7 +84,7 @@ impl Signature<Public> {
8484
/// let random_signature = Signature::random(&mut rand::thread_rng());
8585
pub fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
8686
Signature {
87-
R: XOnly::<SquareY>::random(rng),
87+
R: XOnly::random(rng),
8888
s: Scalar::random(rng).mark::<(Zero, Public)>(),
8989
}
9090
}

0 commit comments

Comments
 (0)