merlin/
transcript.rs

1use rand_core;
2use zeroize::Zeroize;
3
4use crate::strobe::Strobe128;
5
6fn encode_u64(x: u64) -> [u8; 8] {
7    use byteorder::{ByteOrder, LittleEndian};
8
9    let mut buf = [0; 8];
10    LittleEndian::write_u64(&mut buf, x);
11    buf
12}
13
14fn encode_usize_as_u32(x: usize) -> [u8; 4] {
15    use byteorder::{ByteOrder, LittleEndian};
16
17    assert!(x <= (u32::max_value() as usize));
18
19    let mut buf = [0; 4];
20    LittleEndian::write_u32(&mut buf, x as u32);
21    buf
22}
23
24/// A transcript of a public-coin argument.
25///
26/// The prover's messages are added to the transcript using
27/// [`append_message`](Transcript::append_message), and the verifier's
28/// challenges can be computed using
29/// [`challenge_bytes`](Transcript::challenge_bytes).
30///
31/// # Creating and using a Merlin transcript
32///
33/// To create a Merlin transcript, use [`Transcript::new()`].  This
34/// function takes a domain separation label which should be unique to
35/// the application.
36///
37/// To use the transcript with a Merlin-based proof implementation,
38/// the prover's side creates a Merlin transcript with an
39/// application-specific domain separation label, and passes a `&mut`
40/// reference to the transcript to the proving function(s).
41///
42/// To verify the resulting proof, the verifier creates their own
43/// Merlin transcript using the same domain separation label, then
44/// passes a `&mut` reference to the verifier's transcript to the
45/// verification function.
46///
47/// # Implementing proofs using Merlin
48///
49/// For information on the design of Merlin and how to use it to
50/// implement a proof system, see the documentation at
51/// [merlin.cool](https://merlin.cool), particularly the [Using
52/// Merlin](https://merlin.cool/use/index.html) section.
53#[derive(Clone, Zeroize)]
54pub struct Transcript {
55    strobe: Strobe128,
56}
57
58impl Transcript {
59    /// Initialize a new transcript with the supplied `label`, which
60    /// is used as a domain separator.
61    ///
62    /// # Note
63    ///
64    /// This function should be called by a proof library's API
65    /// consumer (i.e., the application using the proof library), and
66    /// **not by the proof implementation**.  See the [Passing
67    /// Transcripts](https://merlin.cool/use/passing.html) section of
68    /// the Merlin website for more details on why.
69    pub fn new(label: &'static [u8]) -> Transcript {
70        use crate::constants::MERLIN_PROTOCOL_LABEL;
71
72        #[cfg(feature = "debug-transcript")]
73        {
74            use std::str::from_utf8;
75            println!(
76                "Initialize STROBE-128({})\t# b\"{}\"",
77                hex::encode(MERLIN_PROTOCOL_LABEL),
78                from_utf8(MERLIN_PROTOCOL_LABEL).unwrap(),
79            );
80        }
81
82        let mut transcript = Transcript {
83            strobe: Strobe128::new(MERLIN_PROTOCOL_LABEL),
84        };
85        transcript.append_message(b"dom-sep", label);
86
87        transcript
88    }
89
90    /// Append a prover's `message` to the transcript.
91    ///
92    /// The `label` parameter is metadata about the message, and is
93    /// also appended to the transcript.  See the [Transcript
94    /// Protocols](https://merlin.cool/use/protocol.html) section of
95    /// the Merlin website for details on labels.
96    pub fn append_message(&mut self, label: &'static [u8], message: &[u8]) {
97        let data_len = encode_usize_as_u32(message.len());
98        self.strobe.meta_ad(label, false);
99        self.strobe.meta_ad(&data_len, true);
100        self.strobe.ad(message, false);
101
102        #[cfg(feature = "debug-transcript")]
103        {
104            use std::str::from_utf8;
105
106            match from_utf8(label) {
107                Ok(label_str) => {
108                    println!(
109                        "meta-AD : {} || LE32({})\t# b\"{}\"",
110                        hex::encode(label),
111                        message.len(),
112                        label_str
113                    );
114                }
115                Err(_) => {
116                    println!(
117                        "meta-AD : {} || LE32({})",
118                        hex::encode(label),
119                        message.len()
120                    );
121                }
122            }
123            match from_utf8(message) {
124                Ok(message_str) => {
125                    println!("     AD : {}\t# b\"{}\"", hex::encode(message), message_str);
126                }
127                Err(_) => {
128                    println!("     AD : {}", hex::encode(message));
129                }
130            }
131        }
132    }
133
134    /// Deprecated.  This function was renamed to
135    /// [`append_message`](Transcript::append_message).
136    ///
137    /// This is intended to avoid any possible confusion between the
138    /// transcript-level messages and protocol-level commitments.
139    #[deprecated(since = "1.1.0", note = "renamed to append_message for clarity.")]
140    pub fn commit_bytes(&mut self, label: &'static [u8], message: &[u8]) {
141        self.append_message(label, message);
142    }
143
144    /// Convenience method for appending a `u64` to the transcript.
145    ///
146    /// The `label` parameter is metadata about the message, and is
147    /// also appended to the transcript.  See the [Transcript
148    /// Protocols](https://merlin.cool/use/protocol.html) section of
149    /// the Merlin website for details on labels.
150    ///
151    /// # Implementation
152    ///
153    /// Calls `append_message` with the 8-byte little-endian encoding
154    /// of `x`.
155    pub fn append_u64(&mut self, label: &'static [u8], x: u64) {
156        self.append_message(label, &encode_u64(x));
157    }
158
159    /// Deprecated.  This function was renamed to
160    /// [`append_u64`](Transcript::append_u64).
161    ///
162    /// This is intended to avoid any possible confusion between the
163    /// transcript-level messages and protocol-level commitments.
164    #[deprecated(since = "1.1.0", note = "renamed to append_u64 for clarity.")]
165    pub fn commit_u64(&mut self, label: &'static [u8], x: u64) {
166        self.append_u64(label, x);
167    }
168
169    /// Fill the supplied buffer with the verifier's challenge bytes.
170    ///
171    /// The `label` parameter is metadata about the challenge, and is
172    /// also appended to the transcript.  See the [Transcript
173    /// Protocols](https://merlin.cool/use/protocol.html) section of
174    /// the Merlin website for details on labels.
175    pub fn challenge_bytes(&mut self, label: &'static [u8], dest: &mut [u8]) {
176        let data_len = encode_usize_as_u32(dest.len());
177        self.strobe.meta_ad(label, false);
178        self.strobe.meta_ad(&data_len, true);
179        self.strobe.prf(dest, false);
180
181        #[cfg(feature = "debug-transcript")]
182        {
183            use std::str::from_utf8;
184
185            match from_utf8(label) {
186                Ok(label_str) => {
187                    println!(
188                        "meta-AD : {} || LE32({})\t# b\"{}\"",
189                        hex::encode(label),
190                        dest.len(),
191                        label_str
192                    );
193                }
194                Err(_) => {
195                    println!("meta-AD : {} || LE32({})", hex::encode(label), dest.len());
196                }
197            }
198            println!("     PRF: {}", hex::encode(dest));
199        }
200    }
201
202    /// Fork the current [`Transcript`] to construct an RNG whose output is bound
203    /// to the current transcript state as well as prover's secrets.
204    ///
205    /// See the [`TranscriptRngBuilder`] documentation for more details.
206    pub fn build_rng(&self) -> TranscriptRngBuilder {
207        TranscriptRngBuilder {
208            strobe: self.strobe.clone(),
209        }
210    }
211}
212
213/// Constructs a [`TranscriptRng`] by rekeying the [`Transcript`] with
214/// prover secrets and an external RNG.
215///
216/// The prover uses a [`TranscriptRngBuilder`] to rekey with its
217/// witness data, before using an external RNG to finalize to a
218/// [`TranscriptRng`].  The resulting [`TranscriptRng`] will be a PRF
219/// of all of the entire public transcript, the prover's secret
220/// witness data, and randomness from the external RNG.
221///
222/// # Usage
223///
224/// To construct a [`TranscriptRng`], a prover calls
225/// [`Transcript::build_rng()`] to clone the transcript state, then
226/// uses [`rekey_with_witness_bytes()`][rekey_with_witness_bytes] to rekey the
227/// transcript with the prover's secrets, before finally calling
228/// [`finalize()`][finalize].  This rekeys the transcript with the
229/// output of an external [`rand_core::RngCore`] instance and returns
230/// a finalized [`TranscriptRng`].
231///
232/// These methods are intended to be chained, passing from a borrowed
233/// [`Transcript`] to an owned [`TranscriptRng`] as follows:
234/// ```
235/// # extern crate merlin;
236/// # extern crate rand_core;
237/// # use merlin::Transcript;
238/// # fn main() {
239/// # let mut transcript = Transcript::new(b"TranscriptRng doctest");
240/// # let public_data = b"public data";
241/// # let witness_data = b"witness data";
242/// # let more_witness_data = b"witness data";
243/// transcript.append_message(b"public", public_data);
244///
245/// let mut rng = transcript
246///     .build_rng()
247///     .rekey_with_witness_bytes(b"witness1", witness_data)
248///     .rekey_with_witness_bytes(b"witness2", more_witness_data)
249///     .finalize(&mut rand_core::OsRng);
250/// # }
251/// ```
252/// In this example, the final `rng` is a PRF of `public_data`
253/// (as well as all previous `transcript` state), and of the prover's
254/// secret `witness_data` and `more_witness_data`, and finally, of the
255/// output of the thread-local RNG.
256/// Note that because the [`TranscriptRng`] is produced from
257/// [`finalize()`][finalize], it's impossible to forget
258/// to rekey the transcript with external randomness.
259///
260/// # Note
261///
262/// Protocols that require randomness in multiple places (e.g., to
263/// choose blinding factors for a multi-round protocol) should create
264/// a fresh [`TranscriptRng`] **each time they need randomness**,
265/// rather than reusing a single instance.  This ensures that the
266/// randomness in each round is bound to the latest transcript state,
267/// rather than just the state of the transcript when randomness was
268/// first required.
269///
270/// # Typed Witness Data
271///
272/// Like the [`Transcript`], the [`TranscriptRngBuilder`] provides a
273/// minimal, byte-oriented API, and like the [`Transcript`], this API
274/// can be extended to allow rekeying with protocol-specific types
275/// using an extension trait.  See the [Transcript
276/// Protocols](https://merlin.cool/use/protocol.html) section of the
277/// Merlin website for more details.
278///
279/// [rekey_with_witness_bytes]: TranscriptRngBuilder::rekey_with_witness_bytes
280/// [finalize]: TranscriptRngBuilder::finalize
281pub struct TranscriptRngBuilder {
282    strobe: Strobe128,
283}
284
285impl TranscriptRngBuilder {
286    /// Rekey the transcript using the provided witness data.
287    ///
288    /// The `label` parameter is metadata about `witness`.
289    pub fn rekey_with_witness_bytes(
290        mut self,
291        label: &'static [u8],
292        witness: &[u8],
293    ) -> TranscriptRngBuilder {
294        let witness_len = encode_usize_as_u32(witness.len());
295        self.strobe.meta_ad(label, false);
296        self.strobe.meta_ad(&witness_len, true);
297        self.strobe.key(witness, false);
298
299        self
300    }
301
302    /// Deprecated.  This function was renamed to
303    /// [`rekey_with_witness_bytes`](Transcript::rekey_with_witness_bytes).
304    ///
305    /// This is intended to avoid any possible confusion between the
306    /// transcript-level messages and protocol-level commitments.
307    #[deprecated(
308        since = "1.1.0",
309        note = "renamed to rekey_with_witness_bytes for clarity."
310    )]
311    pub fn commit_witness_bytes(
312        self,
313        label: &'static [u8],
314        witness: &[u8],
315    ) -> TranscriptRngBuilder {
316        self.rekey_with_witness_bytes(label, witness)
317    }
318
319    /// Use the supplied external `rng` to rekey the transcript, so
320    /// that the finalized [`TranscriptRng`] is a PRF bound to
321    /// randomness from the external RNG, as well as all other
322    /// transcript data.
323    pub fn finalize<R>(mut self, rng: &mut R) -> TranscriptRng
324    where
325        R: rand_core::RngCore + rand_core::CryptoRng,
326    {
327        let random_bytes = {
328            let mut bytes = [0u8; 32];
329            rng.fill_bytes(&mut bytes);
330            bytes
331        };
332
333        self.strobe.meta_ad(b"rng", false);
334        self.strobe.key(&random_bytes, false);
335
336        TranscriptRng {
337            strobe: self.strobe,
338        }
339    }
340}
341
342/// An RNG providing synthetic randomness to the prover.
343///
344/// A [`TranscriptRng`] is constructed from a [`Transcript`] using a
345/// [`TranscriptRngBuilder`]; see its documentation for details on
346/// how to construct one.
347///
348/// The transcript RNG construction is described in the [Generating
349/// Randomness](https://merlin.cool/transcript/rng.html) section of
350/// the Merlin website.
351pub struct TranscriptRng {
352    strobe: Strobe128,
353}
354
355impl rand_core::RngCore for TranscriptRng {
356    fn next_u32(&mut self) -> u32 {
357        rand_core::impls::next_u32_via_fill(self)
358    }
359
360    fn next_u64(&mut self) -> u64 {
361        rand_core::impls::next_u64_via_fill(self)
362    }
363
364    fn fill_bytes(&mut self, dest: &mut [u8]) {
365        let dest_len = encode_usize_as_u32(dest.len());
366        self.strobe.meta_ad(&dest_len, false);
367        self.strobe.prf(dest, false);
368    }
369
370    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
371        self.fill_bytes(dest);
372        Ok(())
373    }
374}
375
376impl rand_core::CryptoRng for TranscriptRng {}
377
378#[cfg(test)]
379mod tests {
380    use strobe_rs::SecParam;
381    use strobe_rs::Strobe;
382
383    use super::*;
384
385    /// Test against a full strobe implementation to ensure we match the few
386    /// operations we're interested in.
387    struct TestTranscript {
388        state: Strobe,
389    }
390
391    impl TestTranscript {
392        /// Strobe init; meta-AD(label)
393        pub fn new(label: &[u8]) -> TestTranscript {
394            use crate::constants::MERLIN_PROTOCOL_LABEL;
395
396            let mut tt = TestTranscript {
397                state: Strobe::new(MERLIN_PROTOCOL_LABEL, SecParam::B128),
398            };
399            tt.append_message(b"dom-sep", label);
400
401            tt
402        }
403
404        /// Strobe op: meta-AD(label || len(message)); AD(message)
405        pub fn append_message(&mut self, label: &[u8], message: &[u8]) {
406            // metadata = label || len(message);
407            let mut metadata: Vec<u8> = Vec::with_capacity(label.len() + 4);
408            metadata.extend_from_slice(label);
409            metadata.extend_from_slice(&encode_usize_as_u32(message.len()));
410
411            self.state.meta_ad(&metadata, false);
412            self.state.ad(&message, false);
413        }
414
415        /// Strobe op: meta-AD(label || len(dest)); PRF into challenge_bytes
416        pub fn challenge_bytes(&mut self, label: &[u8], dest: &mut [u8]) {
417            let prf_len = dest.len();
418
419            // metadata = label || len(challenge_bytes);
420            let mut metadata: Vec<u8> = Vec::with_capacity(label.len() + 4);
421            metadata.extend_from_slice(label);
422            metadata.extend_from_slice(&encode_usize_as_u32(prf_len));
423
424            self.state.meta_ad(&metadata, false);
425            self.state.prf(dest, false);
426        }
427    }
428
429    /// Test a simple protocol with one message and one challenge
430    #[test]
431    fn equivalence_simple() {
432        let mut real_transcript = Transcript::new(b"test protocol");
433        let mut test_transcript = TestTranscript::new(b"test protocol");
434
435        real_transcript.append_message(b"some label", b"some data");
436        test_transcript.append_message(b"some label", b"some data");
437
438        let mut real_challenge = [0u8; 32];
439        let mut test_challenge = [0u8; 32];
440
441        real_transcript.challenge_bytes(b"challenge", &mut real_challenge);
442        test_transcript.challenge_bytes(b"challenge", &mut test_challenge);
443
444        assert_eq!(real_challenge, test_challenge);
445    }
446
447    /// Test a complex protocol with multiple messages and challenges,
448    /// with messages long enough to wrap around the sponge state, and
449    /// with multiple rounds of messages and challenges.
450    #[test]
451    fn equivalence_complex() {
452        let mut real_transcript = Transcript::new(b"test protocol");
453        let mut test_transcript = TestTranscript::new(b"test protocol");
454
455        let data = vec![99; 1024];
456
457        real_transcript.append_message(b"step1", b"some data");
458        test_transcript.append_message(b"step1", b"some data");
459
460        let mut real_challenge = [0u8; 32];
461        let mut test_challenge = [0u8; 32];
462
463        for _ in 0..32 {
464            real_transcript.challenge_bytes(b"challenge", &mut real_challenge);
465            test_transcript.challenge_bytes(b"challenge", &mut test_challenge);
466
467            assert_eq!(real_challenge, test_challenge);
468
469            real_transcript.append_message(b"bigdata", &data);
470            test_transcript.append_message(b"bigdata", &data);
471
472            real_transcript.append_message(b"challengedata", &real_challenge);
473            test_transcript.append_message(b"challengedata", &test_challenge);
474        }
475    }
476
477    #[test]
478    fn transcript_rng_is_bound_to_transcript_and_witnesses() {
479        use curve25519_dalek::scalar::Scalar;
480        use rand_chacha::ChaChaRng;
481        use rand_core::SeedableRng;
482
483        // Check that the TranscriptRng is bound to the transcript and
484        // the witnesses.  This is done by producing a sequence of
485        // transcripts that diverge at different points and checking
486        // that they produce different challenges.
487
488        let protocol_label = b"test TranscriptRng collisions";
489        let commitment1 = b"commitment data 1";
490        let commitment2 = b"commitment data 2";
491        let witness1 = b"witness data 1";
492        let witness2 = b"witness data 2";
493
494        let mut t1 = Transcript::new(protocol_label);
495        let mut t2 = Transcript::new(protocol_label);
496        let mut t3 = Transcript::new(protocol_label);
497        let mut t4 = Transcript::new(protocol_label);
498
499        t1.append_message(b"com", commitment1);
500        t2.append_message(b"com", commitment2);
501        t3.append_message(b"com", commitment2);
502        t4.append_message(b"com", commitment2);
503
504        let mut r1 = t1
505            .build_rng()
506            .rekey_with_witness_bytes(b"witness", witness1)
507            .finalize(&mut ChaChaRng::from_seed([0; 32]));
508
509        let mut r2 = t2
510            .build_rng()
511            .rekey_with_witness_bytes(b"witness", witness1)
512            .finalize(&mut ChaChaRng::from_seed([0; 32]));
513
514        let mut r3 = t3
515            .build_rng()
516            .rekey_with_witness_bytes(b"witness", witness2)
517            .finalize(&mut ChaChaRng::from_seed([0; 32]));
518
519        let mut r4 = t4
520            .build_rng()
521            .rekey_with_witness_bytes(b"witness", witness2)
522            .finalize(&mut ChaChaRng::from_seed([0; 32]));
523
524        let s1 = Scalar::random(&mut r1);
525        let s2 = Scalar::random(&mut r2);
526        let s3 = Scalar::random(&mut r3);
527        let s4 = Scalar::random(&mut r4);
528
529        // Transcript t1 has different commitments than t2, t3, t4, so
530        // it should produce distinct challenges from all of them.
531        assert_ne!(s1, s2);
532        assert_ne!(s1, s3);
533        assert_ne!(s1, s4);
534
535        // Transcript t2 has different witness variables from t3, t4,
536        // so it should produce distinct challenges from all of them.
537        assert_ne!(s2, s3);
538        assert_ne!(s2, s4);
539
540        // Transcripts t3 and t4 have the same commitments and
541        // witnesses, so they should give different challenges only
542        // based on the RNG. Checking that they're equal in the
543        // presence of a bad RNG checks that the different challenges
544        // above aren't because the RNG is accidentally different.
545        assert_eq!(s3, s4);
546    }
547}