@@ -30,6 +30,7 @@ pub struct Schnorr<CH, NG = (), GT = BasePoint> {
30
30
///
31
31
/// [`NonceGen`]: crate::nonce::NonceGen
32
32
nonce_challenge_bundle : NonceChallengeBundle < CH , NG > ,
33
+ application_tag : Option < [ u8 ; 64 ] > ,
33
34
}
34
35
35
36
/// Describes the kind of messages that will be signed with a [`Schnorr`] instance.
@@ -48,7 +49,12 @@ pub enum MessageKind {
48
49
Plain {
49
50
/// You must provide a tag to separate signatures from your application
50
51
/// 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
52
58
tag : & ' static str ,
53
59
} ,
54
60
}
@@ -107,13 +113,24 @@ where
107
113
}
108
114
. add_protocol_tag ( "BIP0340" ) ;
109
115
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
+ } ;
113
129
114
130
Self {
115
131
G : fun:: G . clone ( ) ,
116
132
nonce_challenge_bundle,
133
+ application_tag,
117
134
}
118
135
}
119
136
}
@@ -213,7 +230,14 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
213
230
/// ```
214
231
pub fn challenge < S : Secrecy > ( & self , R : & XOnly , X : & XOnly , m : Slice < ' _ , S > ) -> Scalar < S , Zero > {
215
232
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
+
217
241
challenge
218
242
// Since the challenge pre-image is adversarially controlled we
219
243
// conservatively allow for it to be zero
@@ -258,6 +282,8 @@ impl<NG, CH: Digest<OutputSize = U32> + Clone, GT> Schnorr<CH, NG, GT> {
258
282
259
283
#[ cfg( test) ]
260
284
pub mod test {
285
+ use fun:: nonce:: Deterministic ;
286
+
261
287
use super :: * ;
262
288
use crate :: fun:: TEST_SOUNDNESS ;
263
289
crate :: fun:: test_plus_wasm! {
@@ -304,5 +330,22 @@ pub mod test {
304
330
assert_ne!( signature_1. R , signature_4. R ) ;
305
331
}
306
332
}
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
+ }
307
350
}
308
351
}
0 commit comments