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