1 // -*- mode: rust; -*-
2 //
3 // This file is part of schnorrkel.
4 // Copyright (c) 2019 Web 3 Foundation
5 // See LICENSE for licensing information.
6 //
7 // Authors:
8 // - Jeffrey Burdges <jeff@web3.foundation>
9
10 //! ### Schnorr signature contexts and configuration, adaptable to most Schnorr signature schemes.
11
12 use core::cell::RefCell;
13
14 use rand_core::{RngCore,CryptoRng};
15
16 use merlin::Transcript;
17
18 use curve25519_dalek::digest::{Update,FixedOutput,ExtendableOutput,XofReader};
19 use curve25519_dalek::digest::generic_array::typenum::{U32,U64};
20
21 use curve25519_dalek::ristretto::CompressedRistretto; // RistrettoPoint
22 use curve25519_dalek::scalar::Scalar;
23
24
25 // === Signing context as transcript === //
26
27 /// Schnorr signing transcript
28 ///
29 /// We envision signatures being on messages, but if a signature occurs
30 /// inside a larger protocol then the signature scheme's internal
31 /// transcript may exist before or persist after signing.
32 ///
33 /// In this trait, we provide an interface for Schnorr signature-like
34 /// constructions that is compatable with `merlin::Transcript`, but
35 /// abstract enough to support conventional hash functions as well.
36 ///
37 /// We warn however that conventional hash functions do not provide
38 /// strong enough domain seperation for usage via `&mut` references.
39 ///
40 /// We fold randomness into witness generation here too, which
41 /// gives every function that takes a `SigningTranscript` a default
42 /// argument `rng: impl Rng = thread_rng()` too.
43 ///
44 /// We also abstract over owned and borrowed `merlin::Transcript`s,
45 /// so that simple use cases do not suffer from our support for.
46 pub trait SigningTranscript {
47 /// Extend transcript with some bytes, shadowed by `merlin::Transcript`.
commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])48 fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]);
49
50 /// Extend transcript with a protocol name
proto_name(&mut self, label: &'static [u8])51 fn proto_name(&mut self, label: &'static [u8]) {
52 self.commit_bytes(b"proto-name", label);
53 }
54
55 /// Extend the transcript with a compressed Ristretto point
commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto)56 fn commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto) {
57 self.commit_bytes(label, compressed.as_bytes());
58 }
59
60 /*
61 fn commit_sorted_points<P,S>(&mut self, label: &'static [u8], set: &mut [P])
62 where P: Borrow<CompressedRistretto>,
63 // S: BorrowMut<[P]>,
64 {
65 // let set = set.borrow_mut();
66 set.sort_unstable_by(
67 |a,b| a.borrow().as_bytes()
68 .cmp(b.borrow().as_bytes())
69 );
70 for p in set.iter() {
71 self.commit_point(label,p.borrow());
72 }
73 }
74 */
75
76 /// Produce some challenge bytes, shadowed by `merlin::Transcript`.
challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])77 fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]);
78
79 /// Produce the public challenge scalar `e`.
challenge_scalar(&mut self, label: &'static [u8]) -> Scalar80 fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar {
81 let mut buf = [0; 64];
82 self.challenge_bytes(label, &mut buf);
83 Scalar::from_bytes_mod_order_wide(&buf)
84 }
85
86 /// Produce a secret witness scalar `k`, aka nonce, from the protocol
87 /// transcript and any "nonce seeds" kept with the secret keys.
witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar88 fn witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar {
89 let mut scalar_bytes = [0u8; 64];
90 self.witness_bytes(label, &mut scalar_bytes, nonce_seeds);
91 Scalar::from_bytes_mod_order_wide(&scalar_bytes)
92 }
93
94 /// Produce secret witness bytes from the protocol transcript
95 /// and any "nonce seeds" kept with the secret keys.
witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])96 fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]]) {
97 self.witness_bytes_rng(label, dest, nonce_seeds, super::rand_hack())
98 }
99
100 /// Produce secret witness bytes from the protocol transcript
101 /// and any "nonce seeds" kept with the secret keys.
witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R) where R: RngCore+CryptoRng102 fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R)
103 where R: RngCore+CryptoRng;
104 }
105
106
107 /// We delegates any mutable reference to its base type, like `&mut Rng`
108 /// or similar to `BorrowMut<..>` do, but doing so here simplifies
109 /// alternative implementations.
110 impl<T> SigningTranscript for &mut T
111 where T: SigningTranscript + ?Sized,
112 {
113 #[inline(always)]
commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])114 fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])
115 { (**self).commit_bytes(label,bytes) }
116 #[inline(always)]
proto_name(&mut self, label: &'static [u8])117 fn proto_name(&mut self, label: &'static [u8])
118 { (**self).proto_name(label) }
119 #[inline(always)]
commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto)120 fn commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto)
121 { (**self).commit_point(label, compressed) }
122 #[inline(always)]
challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])123 fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])
124 { (**self).challenge_bytes(label,dest) }
125 #[inline(always)]
challenge_scalar(&mut self, label: &'static [u8]) -> Scalar126 fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar
127 { (**self).challenge_scalar(label) }
128 #[inline(always)]
witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar129 fn witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar
130 { (**self).witness_scalar(label,nonce_seeds) }
131 #[inline(always)]
witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])132 fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])
133 { (**self).witness_bytes(label,dest,nonce_seeds) }
134 #[inline(always)]
witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R) where R: RngCore+CryptoRng135 fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R)
136 where R: RngCore+CryptoRng
137 { (**self).witness_bytes_rng(label,dest,nonce_seeds,rng) }
138 }
139
140 /// We delegate `SigningTranscript` methods to the corresponding
141 /// inherent methods of `merlin::Transcript` and implement two
142 /// witness methods to avoid overwriting the `merlin::TranscriptRng`
143 /// machinery.
144 impl SigningTranscript for Transcript {
commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])145 fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) {
146 Transcript::append_message(self, label, bytes)
147 }
148
challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])149 fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
150 Transcript::challenge_bytes(self, label, dest)
151 }
152
witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R) where R: RngCore+CryptoRng153 fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R)
154 where R: RngCore+CryptoRng
155 {
156 let mut br = self.build_rng();
157 for ns in nonce_seeds {
158 br = br.rekey_with_witness_bytes(label, ns);
159 }
160 let mut r = br.finalize(&mut rng);
161 r.fill_bytes(dest)
162 }
163 }
164
165
166 /// Schnorr signing context
167 ///
168 /// We expect users to have seperate `SigningContext`s for each role
169 /// that signature play in their protocol. These `SigningContext`s
170 /// may be global `lazy_static!`s, or perhaps constants in future.
171 ///
172 /// To sign a message, apply the appropriate inherent method to create
173 /// a signature transcript.
174 ///
175 /// You should use `merlin::Transcript`s directly if you must do
176 /// anything more complex, like use signatures in larger zero-knoweldge
177 /// protocols or sign several components but only reveal one later.
178 ///
179 /// We declare these methods `#[inline(always)]` because rustc does
180 /// not handle large returns as efficently as one might like.
181 /// https://github.com/rust-random/rand/issues/817
182 #[derive(Clone)] // Debug
183 pub struct SigningContext(Transcript);
184
185 /// Initialize a signing context from a static byte string that
186 /// identifies the signature's role in the larger protocol.
187 #[inline(always)]
signing_context(context : &[u8]) -> SigningContext188 pub fn signing_context(context : &[u8]) -> SigningContext {
189 SigningContext::new(context)
190 }
191
192 impl SigningContext {
193 /// Initialize a signing context from a static byte string that
194 /// identifies the signature's role in the larger protocol.
195 #[inline(always)]
new(context : &[u8]) -> SigningContext196 pub fn new(context : &[u8]) -> SigningContext {
197 let mut t = Transcript::new(b"SigningContext");
198 t.append_message(b"",context);
199 SigningContext(t)
200 }
201
202 /// Initalize an owned signing transcript on a message provided as a byte array.
203 ///
204 /// Avoid this method when processing large slices because it
205 /// calls `merlin::Transcript::append_message` directly and
206 /// `merlin` is designed for domain seperation, not performance.
207 #[inline(always)]
bytes(&self, bytes: &[u8]) -> Transcript208 pub fn bytes(&self, bytes: &[u8]) -> Transcript {
209 let mut t = self.0.clone();
210 t.append_message(b"sign-bytes", bytes);
211 t
212 }
213
214 /// Initalize an owned signing transcript on a message provided
215 /// as a hash function with extensible output mode (XOF) by
216 /// finalizing the hash and extracting 32 bytes from XOF.
217 #[inline(always)]
xof<D: ExtendableOutput>(&self, h: D) -> Transcript218 pub fn xof<D: ExtendableOutput>(&self, h: D) -> Transcript {
219 let mut prehash = [0u8; 32];
220 h.finalize_xof().read(&mut prehash);
221 let mut t = self.0.clone();
222 t.append_message(b"sign-XoF", &prehash);
223 t
224 }
225
226 /// Initalize an owned signing transcript on a message provided as
227 /// a hash function with 256 bit output.
228 #[inline(always)]
hash256<D: FixedOutput<OutputSize=U32>>(&self, h: D) -> Transcript229 pub fn hash256<D: FixedOutput<OutputSize=U32>>(&self, h: D) -> Transcript {
230 let mut prehash = [0u8; 32];
231 prehash.copy_from_slice(h.finalize_fixed().as_slice());
232 let mut t = self.0.clone();
233 t.append_message(b"sign-256", &prehash);
234 t
235 }
236
237 /// Initalize an owned signing transcript on a message provided as
238 /// a hash function with 512 bit output, usually a gross over kill.
239 #[inline(always)]
hash512<D: FixedOutput<OutputSize=U64>>(&self, h: D) -> Transcript240 pub fn hash512<D: FixedOutput<OutputSize=U64>>(&self, h: D) -> Transcript {
241 let mut prehash = [0u8; 64];
242 prehash.copy_from_slice(h.finalize_fixed().as_slice());
243 let mut t = self.0.clone();
244 t.append_message(b"sign-256", &prehash);
245 t
246 }
247 }
248
249
250 /// Very simple transcript construction from a modern hash fucntion.
251 ///
252 /// We provide this transcript type to directly use conventional hash
253 /// functions with an extensible output mode, like Shake128 and
254 /// Blake2x.
255 ///
256 /// We recommend using `merlin::Transcript` instead because merlin
257 /// provides the transcript abstraction natively and might function
258 /// better in low memory enviroments. We therefore do not provide
259 /// conveniences like `signing_context` for this.
260 ///
261 /// We note that merlin already uses Keccak, upon which Shake128 is based,
262 /// and that no rust implementation for Blake2x currently exists.
263 ///
264 /// We caution that our transcript abstractions cannot provide the
265 /// protections against hash collisions that Ed25519 provides via
266 /// double hashing, but that prehashed Ed25519 variants lose.
267 /// As such, any hash function used here must be collision resistant.
268 /// We strongly recommend against building XOFs from weaker hash
269 /// functions like SHA1 with HKDF constructions or similar.
270 ///
271 /// In `XoFTranscript` style, we never expose the hash function `H`
272 /// underlying this type, so that developers cannot circumvent the
273 /// domain separation provided by our methods. We do this to make
274 /// `&mut XoFTranscript : SigningTranscript` safe.
275 pub struct XoFTranscript<H>(H)
276 where H: Update + ExtendableOutput + Clone;
277
input_bytes<H: Update>(h: &mut H, bytes: &[u8])278 fn input_bytes<H: Update>(h: &mut H, bytes: &[u8]) {
279 let l = bytes.len() as u64;
280 h.update(l.to_le_bytes());
281 h.update(bytes);
282 }
283
284 impl<H> XoFTranscript<H>
285 where H: Update + ExtendableOutput + Clone
286 {
287 /// Create a `XoFTranscript` from a conventional hash functions with an extensible output mode.
288 ///
289 /// We intentionally consume and never reexpose the hash function
290 /// provided, so that our domain separation works correctly even
291 /// when using `&mut XoFTranscript : SigningTranscript`.
292 #[inline(always)]
new(h: H) -> XoFTranscript<H>293 pub fn new(h: H) -> XoFTranscript<H> { XoFTranscript(h) }
294 }
295
296 impl<H> From<H> for XoFTranscript<H>
297 where H: Update + ExtendableOutput + Clone
298 {
299 #[inline(always)]
from(h: H) -> XoFTranscript<H>300 fn from(h: H) -> XoFTranscript<H> { XoFTranscript(h) }
301 }
302
303 impl<H> SigningTranscript for XoFTranscript<H>
304 where H: Update + ExtendableOutput + Clone
305 {
commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])306 fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) {
307 self.0.update(b"co");
308 input_bytes(&mut self.0, label);
309 input_bytes(&mut self.0, bytes);
310 }
311
challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])312 fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
313 self.0.update(b"ch");
314 input_bytes(&mut self.0, label);
315 let l = dest.len() as u64;
316 self.0.update(l.to_le_bytes());
317 self.0.clone().chain(b"xof").finalize_xof().read(dest);
318 }
319
witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R) where R: RngCore+CryptoRng320 fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R)
321 where R: RngCore+CryptoRng
322 {
323 let mut h = self.0.clone().chain(b"wb");
324 input_bytes(&mut h, label);
325 for ns in nonce_seeds {
326 input_bytes(&mut h, ns);
327 }
328 let l = dest.len() as u64;
329 h.update(l.to_le_bytes());
330
331 let mut r = [0u8; 32];
332 rng.fill_bytes(&mut r);
333 h.update(&r);
334 h.finalize_xof().read(dest);
335 }
336 }
337
338
339 /// Schnorr signing transcript with the default `ThreadRng` replaced
340 /// by an arbitrary `CryptoRng`.
341 ///
342 /// If `ThreadRng` breaks on your platform, or merely if you're paranoid,
343 /// then you might "upgrade" from `ThreadRng` to `OsRng` by using calls
344 /// like `keypair.sign( attach_rng(t,OSRng::new()) )`.
345 /// However, we recommend instead simply fixing `ThreadRng` for your platform.
346 ///
347 /// There are also derandomization tricks like
348 /// `attach_rng(t,ChaChaRng::from_seed([0u8; 32]))`
349 /// for deterministic signing in tests too. Although derandomization
350 /// produces secure signatures, we recommend against doing this in
351 /// production because we implement protocols like multi-signatures
352 /// which likely become vulnerable when derandomized.
353 pub struct SigningTranscriptWithRng<T,R>
354 where T: SigningTranscript, R: RngCore+CryptoRng
355 {
356 t: T,
357 rng: RefCell<R>,
358 }
359
360 impl<T,R> SigningTranscript for SigningTranscriptWithRng<T,R>
361 where T: SigningTranscript, R: RngCore+CryptoRng
362 {
commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])363 fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])
364 { self.t.commit_bytes(label, bytes) }
365
challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])366 fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])
367 { self.t.challenge_bytes(label, dest) }
368
witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])369 fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])
370 { self.witness_bytes_rng(label, dest, nonce_seeds, &mut *self.rng.borrow_mut()) }
371
witness_bytes_rng<RR>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: RR) where RR: RngCore+CryptoRng372 fn witness_bytes_rng<RR>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: RR)
373 where RR: RngCore+CryptoRng
374 { self.t.witness_bytes_rng(label,dest,nonce_seeds,rng) }
375
376 }
377
378 /// Attach a `CryptoRng` to a `SigningTranscript` to replace the default `ThreadRng`.
379 ///
380 /// There are tricks like `attach_rng(t,ChaChaRng::from_seed([0u8; 32]))`
381 /// for deterministic tests. We warn against doing this in production
382 /// however because, although such derandomization produces secure Schnorr
383 /// signatures, we do implement protocols here like multi-signatures which
384 /// likely become vulnerable when derandomized.
attach_rng<T,R>(t: T, rng: R) -> SigningTranscriptWithRng<T,R> where T: SigningTranscript, R: RngCore+CryptoRng385 pub fn attach_rng<T,R>(t: T, rng: R) -> SigningTranscriptWithRng<T,R>
386 where T: SigningTranscript, R: RngCore+CryptoRng
387 {
388 SigningTranscriptWithRng {
389 t, rng: RefCell::new(rng)
390 }
391 }
392
393 /// Attach a fake `Rng` that returns all zeros, only for use in test vectors.
394 /// You must never deploy this because some protocols like MuSig become insecure.
395 #[cfg(test)]
attach_test_vector_rng<T>(t: T) -> SigningTranscriptWithRng<T,impl RngCore+CryptoRng> where T: SigningTranscript396 pub(crate) fn attach_test_vector_rng<T>(t: T) -> SigningTranscriptWithRng<T,impl RngCore+CryptoRng>
397 where T: SigningTranscript
398 {
399 // Very insecure hack except this fn only exists in tests
400 struct ZeroFakeRng;
401 impl ::rand::RngCore for ZeroFakeRng {
402 fn next_u32(&mut self) -> u32 { panic!() }
403 fn next_u64(&mut self) -> u64 { panic!() }
404 fn fill_bytes(&mut self, dest: &mut [u8]) {
405 for i in dest.iter_mut() { *i = 0; }
406 }
407 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), ::rand_core::Error> {
408 self.fill_bytes(dest);
409 Ok(())
410 }
411 }
412 impl ::rand::CryptoRng for ZeroFakeRng {}
413 attach_rng(t, ZeroFakeRng)
414 }
415
416
417 #[cfg(feature = "rand_chacha")]
418 use rand_chacha::ChaChaRng;
419
420 /// Attach a `ChaChaRng` to a `Transcript` to repalce the default `ThreadRng`
421 #[cfg(feature = "rand_chacha")]
attach_chacharng<T>(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng<T,ChaChaRng> where T: SigningTranscript422 pub fn attach_chacharng<T>(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng<T,ChaChaRng>
423 where T: SigningTranscript
424 {
425 use rand_core::SeedableRng;
426 attach_rng(t,ChaChaRng::from_seed(seed))
427 }
428
429
430
431 /*
432 #[cfg(test)]
433 mod test {
434 use sha3::Shake128;
435 use curve25519_dalek::digest::{Update};
436
437 }
438 */
439