schnorrkel/
context.rs

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
12use core::cell::RefCell;
13
14use rand_core::{RngCore,CryptoRng};
15
16use merlin::Transcript;
17
18use curve25519_dalek::digest::{Update,FixedOutput,ExtendableOutput,XofReader};
19use curve25519_dalek::digest::generic_array::typenum::{U32,U64};
20
21use curve25519_dalek::ristretto::CompressedRistretto; // RistrettoPoint
22use 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.
46pub trait SigningTranscript {
47    /// Extend transcript with some bytes, shadowed by `merlin::Transcript`.
48    fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]);
49
50    /// Extend transcript with a protocol name
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
56    fn commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto) {
57        self.commit_bytes(label, compressed.as_bytes());
58    }
59
60    /// Produce some challenge bytes, shadowed by `merlin::Transcript`.
61    fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]);
62
63    /// Produce the public challenge scalar `e`.
64    fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar {
65        let mut buf = [0; 64];
66        self.challenge_bytes(label, &mut buf);
67        Scalar::from_bytes_mod_order_wide(&buf)
68    }
69
70    /// Produce a secret witness scalar `k`, aka nonce, from the protocol
71    /// transcript and any "nonce seeds" kept with the secret keys.
72    fn witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar {
73        let mut scalar_bytes = [0u8; 64];
74        self.witness_bytes(label, &mut scalar_bytes, nonce_seeds);
75        Scalar::from_bytes_mod_order_wide(&scalar_bytes)
76    }
77
78    /// Produce secret witness bytes from the protocol transcript
79    /// and any "nonce seeds" kept with the secret keys.
80    fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]]) {
81        self.witness_bytes_rng(label, dest, nonce_seeds, super::getrandom_or_panic())
82    }
83
84    /// Produce secret witness bytes from the protocol transcript
85    /// and any "nonce seeds" kept with the secret keys.
86    fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R)
87    where R: RngCore+CryptoRng;
88}
89
90
91/// We delegates any mutable reference to its base type, like `&mut Rng`
92/// or similar to `BorrowMut<..>` do, but doing so here simplifies
93/// alternative implementations.
94impl<T> SigningTranscript for &mut T
95where T: SigningTranscript + ?Sized,
96{
97    #[inline(always)]
98    fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])
99        {  (**self).commit_bytes(label,bytes)  }
100    #[inline(always)]
101    fn proto_name(&mut self, label: &'static [u8])
102        {  (**self).proto_name(label)  }
103    #[inline(always)]
104    fn commit_point(&mut self, label: &'static [u8], compressed: &CompressedRistretto)
105        {  (**self).commit_point(label, compressed)  }
106    #[inline(always)]
107    fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])
108        {  (**self).challenge_bytes(label,dest)  }
109    #[inline(always)]
110    fn challenge_scalar(&mut self, label: &'static [u8]) -> Scalar
111        {  (**self).challenge_scalar(label)  }
112    #[inline(always)]
113    fn witness_scalar(&self, label: &'static [u8], nonce_seeds: &[&[u8]]) -> Scalar
114        {  (**self).witness_scalar(label,nonce_seeds)  }
115    #[inline(always)]
116    fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])
117        {  (**self).witness_bytes(label,dest,nonce_seeds)  }
118    #[inline(always)]
119    fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: R)
120    where R: RngCore+CryptoRng
121        {  (**self).witness_bytes_rng(label,dest,nonce_seeds,rng)  }
122}
123
124/// We delegate `SigningTranscript` methods to the corresponding
125/// inherent methods of `merlin::Transcript` and implement two
126/// witness methods to avoid overwriting the `merlin::TranscriptRng`
127/// machinery.
128impl SigningTranscript for Transcript {
129    fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) {
130        Transcript::append_message(self, label, bytes)
131    }
132
133    fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
134        Transcript::challenge_bytes(self, label, dest)
135    }
136
137    fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R)
138    where R: RngCore+CryptoRng
139    {
140        let mut br = self.build_rng();
141        for ns in nonce_seeds {
142            br = br.rekey_with_witness_bytes(label, ns);
143        }
144        let mut r = br.finalize(&mut rng);
145        r.fill_bytes(dest)
146    }
147}
148
149
150/// Schnorr signing context
151///
152/// We expect users to have separate `SigningContext`s for each role
153/// that signature play in their protocol.  These `SigningContext`s
154/// may be global `lazy_static!`s, or perhaps constants in future.
155///
156/// To sign a message, apply the appropriate inherent method to create
157/// a signature transcript.
158///
159/// You should use `merlin::Transcript`s directly if you must do
160/// anything more complex, like use signatures in larger zero-knowledge
161/// protocols or sign several components but only reveal one later.
162///
163/// We declare these methods `#[inline(always)]` because rustc does
164/// not handle large returns as efficiently as one might like.
165/// https://github.com/rust-random/rand/issues/817
166#[derive(Clone)] // Debug
167pub struct SigningContext(Transcript);
168
169/// Initialize a signing context from a static byte string that
170/// identifies the signature's role in the larger protocol.
171#[inline(always)]
172pub fn signing_context(context : &[u8]) -> SigningContext {
173    SigningContext::new(context)
174}
175
176impl SigningContext {
177    /// Initialize a signing context from a static byte string that
178    /// identifies the signature's role in the larger protocol.
179    #[inline(always)]
180    pub fn new(context : &[u8]) -> SigningContext {
181        let mut t = Transcript::new(b"SigningContext");
182        t.append_message(b"",context);
183        SigningContext(t)
184    }
185
186    /// Initialize an owned signing transcript on a message provided as a byte array.
187    ///
188    /// Avoid this method when processing large slices because it
189    /// calls `merlin::Transcript::append_message` directly and
190    /// `merlin` is designed for domain seperation, not performance.
191    #[inline(always)]
192    pub fn bytes(&self, bytes: &[u8]) -> Transcript {
193        let mut t = self.0.clone();
194        t.append_message(b"sign-bytes", bytes);
195        t
196    }
197
198    /// Initialize an owned signing transcript on a message provided
199    /// as a hash function with extensible output mode (XOF) by
200    /// finalizing the hash and extracting 32 bytes from XOF.
201    #[inline(always)]
202    pub fn xof<D: ExtendableOutput>(&self, h: D) -> Transcript {
203        let mut prehash = [0u8; 32];
204        h.finalize_xof().read(&mut prehash);
205        let mut t = self.0.clone();
206        t.append_message(b"sign-XoF", &prehash);
207        t
208    }
209
210    /// Initialize an owned signing transcript on a message provided as
211    /// a hash function with 256 bit output.
212    #[inline(always)]
213    pub fn hash256<D: FixedOutput<OutputSize=U32>>(&self, h: D) -> Transcript {
214        let mut prehash = [0u8; 32];
215        prehash.copy_from_slice(h.finalize_fixed().as_slice());
216        let mut t = self.0.clone();
217        t.append_message(b"sign-256", &prehash);
218        t
219    }
220
221    /// Initialize an owned signing transcript on a message provided as
222    /// a hash function with 512 bit output, usually a gross over kill.
223    #[inline(always)]
224    pub fn hash512<D: FixedOutput<OutputSize=U64>>(&self, h: D) -> Transcript {
225        let mut prehash = [0u8; 64];
226        prehash.copy_from_slice(h.finalize_fixed().as_slice());
227        let mut t = self.0.clone();
228        t.append_message(b"sign-512", &prehash);
229        t
230    }
231}
232
233
234/// Very simple transcript construction from a modern hash function.
235///
236/// We provide this transcript type to directly use conventional hash
237/// functions with an extensible output mode, like Shake128 and
238/// Blake2x.
239///
240/// We recommend using `merlin::Transcript` instead because merlin
241/// provides the transcript abstraction natively and might function
242/// better in low memory environments.  We therefore do not provide
243/// conveniences like `signing_context` for this.
244///
245/// We note that merlin already uses Keccak, upon which Shake128 is based,
246/// and that no rust implementation for Blake2x currently exists.
247///
248/// We caution that our transcript abstractions cannot provide the
249/// protections against hash collisions that Ed25519 provides via
250/// double hashing, but that prehashed Ed25519 variants lose.
251/// As such, any hash function used here must be collision resistant.
252/// We strongly recommend against building XOFs from weaker hash
253/// functions like SHA1 with HKDF constructions or similar.
254///
255/// In `XoFTranscript` style, we never expose the hash function `H`
256/// underlying this type, so that developers cannot circumvent the
257/// domain separation provided by our methods.  We do this to make
258/// `&mut XoFTranscript : SigningTranscript` safe.
259pub struct XoFTranscript<H>(H)
260where H: Update + ExtendableOutput + Clone;
261
262fn input_bytes<H: Update>(h: &mut H, bytes: &[u8]) {
263    let l = bytes.len() as u64;
264    h.update(&l.to_le_bytes());
265    h.update(bytes);
266}
267
268impl<H> XoFTranscript<H>
269where H: Update + ExtendableOutput + Clone
270{
271    /// Create a `XoFTranscript` from a conventional hash functions with an extensible output mode.
272    ///
273    /// We intentionally consume and never reexpose the hash function
274    /// provided, so that our domain separation works correctly even
275    /// when using `&mut XoFTranscript : SigningTranscript`.
276    #[inline(always)]
277    pub fn new(h: H) -> XoFTranscript<H> { XoFTranscript(h) }
278}
279
280impl<H> From<H> for XoFTranscript<H>
281where H: Update + ExtendableOutput + Clone
282{
283    #[inline(always)]
284    fn from(h: H) -> XoFTranscript<H> { XoFTranscript(h) }
285}
286
287impl<H> SigningTranscript for XoFTranscript<H>
288where H: Update + ExtendableOutput + Clone
289{
290    fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8]) {
291        self.0.update(b"co");
292        input_bytes(&mut self.0, label);
293        input_bytes(&mut self.0, bytes);
294    }
295
296    fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
297        self.0.update(b"ch");
298        input_bytes(&mut self.0, label);
299        let l = dest.len() as u64;
300        self.0.update(&l.to_le_bytes());
301        self.0.clone().chain(b"xof").finalize_xof().read(dest);
302    }
303
304    fn witness_bytes_rng<R>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], mut rng: R)
305    where R: RngCore+CryptoRng
306    {
307        let mut h = self.0.clone().chain(b"wb");
308        input_bytes(&mut h, label);
309        for ns in nonce_seeds {
310            input_bytes(&mut h, ns);
311        }
312        let l = dest.len() as u64;
313        h.update(&l.to_le_bytes());
314
315        let mut r = [0u8; 32];
316        rng.fill_bytes(&mut r);
317        h.update(&r);
318        h.finalize_xof().read(dest);
319    }
320}
321
322
323/// Schnorr signing transcript with the default `ThreadRng` replaced
324/// by an arbitrary `CryptoRng`.
325///
326/// If `ThreadRng` breaks on your platform, or merely if you're paranoid,
327/// then you might "upgrade" from `ThreadRng` to `OsRng` by using calls
328/// like `keypair.sign( attach_rng(t,OSRng::new()) )`.
329/// However, we recommend instead simply fixing `ThreadRng` for your platform.
330///
331/// There are also derandomization tricks like
332/// `attach_rng(t,ChaChaRng::from_seed([0u8; 32]))`
333/// for deterministic signing in tests too.  Although derandomization
334/// produces secure signatures, we recommend against doing this in
335/// production because we implement protocols like multi-signatures
336/// which likely become vulnerable when derandomized.
337pub struct SigningTranscriptWithRng<T,R>
338where T: SigningTranscript, R: RngCore+CryptoRng
339{
340    t: T,
341    rng: RefCell<R>,
342}
343
344impl<T,R> SigningTranscript for SigningTranscriptWithRng<T,R>
345where T: SigningTranscript, R: RngCore+CryptoRng
346{
347    fn commit_bytes(&mut self, label: &'static [u8], bytes: &[u8])
348        {  self.t.commit_bytes(label, bytes)  }
349
350    fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8])
351        {  self.t.challenge_bytes(label, dest)  }
352
353    fn witness_bytes(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]])
354       {  self.witness_bytes_rng(label, dest, nonce_seeds, &mut *self.rng.borrow_mut())  }
355
356    fn witness_bytes_rng<RR>(&self, label: &'static [u8], dest: &mut [u8], nonce_seeds: &[&[u8]], rng: RR)
357    where RR: RngCore+CryptoRng
358       {  self.t.witness_bytes_rng(label,dest,nonce_seeds,rng)  }
359
360}
361
362/// Attach a `CryptoRng` to a `SigningTranscript` to replace the default `ThreadRng`.
363///
364/// There are tricks like `attach_rng(t,ChaChaRng::from_seed([0u8; 32]))`
365/// for deterministic tests.  We warn against doing this in production
366/// however because, although such derandomization produces secure Schnorr
367/// signatures, we do implement protocols here like multi-signatures which
368/// likely become vulnerable when derandomized.
369pub fn attach_rng<T,R>(t: T, rng: R) -> SigningTranscriptWithRng<T,R>
370where T: SigningTranscript, R: RngCore+CryptoRng
371{
372    SigningTranscriptWithRng {
373        t, rng: RefCell::new(rng)
374    }
375}
376
377#[cfg(feature = "rand_chacha")]
378use rand_chacha::ChaChaRng;
379
380/// Attach a `ChaChaRng` to a `Transcript` to repalce the default `ThreadRng`
381#[cfg(feature = "rand_chacha")]
382pub fn attach_chacharng<T>(t: T, seed: [u8; 32]) -> SigningTranscriptWithRng<T,ChaChaRng>
383where T: SigningTranscript
384{
385    use rand_core::SeedableRng;
386    attach_rng(t,ChaChaRng::from_seed(seed))
387}