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}