Skip to content

Commit 95de2e0

Browse files
committed
Use new BIP340 domain separation scheme
Abandoned the old one (mention by real-or-random) to the one suggested here: sipa/bips#207 (comment)
1 parent c3c5f4f commit 95de2e0

File tree

1 file changed

+48
-5
lines changed

1 file changed

+48
-5
lines changed

schnorr_fun/src/schnorr.rs

Lines changed: 48 additions & 5 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
}
@@ -107,13 +113,24 @@ where
107113
}
108114
.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
}
@@ -213,7 +230,14 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
213230
/// ```
214231
pub fn challenge<S: Secrecy>(&self, R: &XOnly, X: &XOnly, m: Slice<'_, S>) -> Scalar<S, Zero> {
215232
let hash = self.nonce_challenge_bundle.challenge_hash.clone();
216-
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+
217241
challenge
218242
// Since the challenge pre-image is adversarially controlled we
219243
// conservatively allow for it to be zero
@@ -258,6 +282,8 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
258282

259283
#[cfg(test)]
260284
pub mod test {
285+
use fun::nonce::Deterministic;
286+
261287
use super::*;
262288
use crate::fun::TEST_SOUNDNESS;
263289
crate::fun::test_plus_wasm! {
@@ -304,5 +330,22 @@ pub mod test {
304330
assert_ne!(signature_1.R, signature_4.R);
305331
}
306332
}
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+
}
307350
}
308351
}

0 commit comments

Comments
 (0)