Skip to main content

commonware_cryptography/
transcript.rs

1//! This module provides a [Transcript] abstraction.
2//!
3//! This is useful for hashing data, committing to it, and extracting secure
4//! randomness from it. The API evades common footguns when doing these things
5//! in an ad hoc way.
6use crate::{BatchVerifier, Signer, Verifier};
7use blake3::BLOCK_LEN;
8use bytes::Buf;
9use commonware_codec::{varint::UInt, EncodeSize, FixedArray, FixedSize, Read, ReadExt, Write};
10use commonware_math::algebra::Random;
11#[commonware_macros::stability(ALPHA)]
12use commonware_utils::NZU64;
13use commonware_utils::{Array, Span};
14#[commonware_macros::stability(ALPHA)]
15use core::num::NonZeroU64;
16use core::{convert::Infallible, fmt::Display, ops::Deref};
17use rand_core::{CryptoRng, TryCryptoRng, TryRng};
18use zeroize::ZeroizeOnDrop;
19
20/// Provides an implementation of [CryptoRng].
21///
22/// We intentionally don't expose this struct, to make the impl returned by
23/// [Transcript::noise] completely opaque.
24#[derive(ZeroizeOnDrop)]
25struct Rng {
26    inner: blake3::OutputReader,
27    buf: [u8; BLOCK_LEN],
28    start: usize,
29}
30
31impl Rng {
32    const fn new(inner: blake3::OutputReader) -> Self {
33        Self {
34            inner,
35            buf: [0u8; BLOCK_LEN],
36            start: BLOCK_LEN,
37        }
38    }
39}
40
41impl TryRng for Rng {
42    type Error = Infallible;
43
44    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
45        let mut bytes = [0u8; 4];
46        self.try_fill_bytes(&mut bytes)?;
47        Ok(u32::from_le_bytes(bytes))
48    }
49
50    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
51        let mut bytes = [0u8; 8];
52        self.try_fill_bytes(&mut bytes)?;
53        Ok(u64::from_le_bytes(bytes))
54    }
55
56    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
57        let dest_len = dest.len();
58        let remaining = &self.buf[self.start..];
59        if remaining.len() >= dest_len {
60            dest.copy_from_slice(&remaining[..dest_len]);
61            self.start += dest_len;
62            return Ok(());
63        }
64
65        let (start, mut dest) = dest.split_at_mut(remaining.len());
66        start.copy_from_slice(remaining);
67        self.start = BLOCK_LEN;
68
69        while dest.len() >= BLOCK_LEN {
70            let (block, rest) = dest.split_at_mut(BLOCK_LEN);
71            self.inner.fill(block);
72            dest = rest;
73        }
74
75        let dest_len = dest.len();
76        if dest_len > 0 {
77            self.inner.fill(&mut self.buf[..]);
78            dest.copy_from_slice(&self.buf[..dest_len]);
79            self.start = dest_len;
80        }
81
82        Ok(())
83    }
84}
85
86impl TryCryptoRng for Rng {}
87
88fn flush(hasher: &mut blake3::Hasher, pending: u64) {
89    let mut pending_bytes = [0u8; 9];
90    let pending = UInt(pending);
91    pending.write(&mut &mut pending_bytes[..]);
92    hasher.update(&pending_bytes[..pending.encode_size()]);
93}
94
95/// Ensures different [Transcript] initializations are unique.
96#[repr(u8)]
97enum StartTag {
98    New = 0,
99    Resume = 1,
100    Fork = 2,
101    Noise = 3,
102}
103
104/// Provides a convenient abstraction over hashing data and deriving randomness.
105///
106/// It automatically takes care of details like:
107/// - correctly segmenting packets of data,
108/// - domain separating different uses of tags and randomness,
109/// - making sure that secret state is zeroized as necessary.
110#[derive(ZeroizeOnDrop)]
111pub struct Transcript {
112    hasher: blake3::Hasher,
113    pending: u64,
114}
115
116impl Transcript {
117    fn start(tag: StartTag, summary: Option<Summary>) -> Self {
118        // By starting with an optional key, we basically get to hash in 32 bytes
119        // for free, since they won't affect the number of bytes we can process without
120        // a call to the compression function. So, in many cases where we want to
121        // link a new transcript to a previous history, we take an optional summary.
122        let mut hasher = summary.map_or_else(blake3::Hasher::new, |s| {
123            blake3::Hasher::new_keyed(s.hash.as_bytes())
124        });
125        hasher.update(&[tag as u8]);
126        Self { hasher, pending: 0 }
127    }
128
129    fn flush(&mut self) {
130        flush(&mut self.hasher, self.pending);
131        self.pending = 0;
132    }
133
134    fn do_append(&mut self, data: &[u8]) {
135        self.hasher.update(data);
136        self.pending += data.len() as u64;
137    }
138
139    const fn unflushed(&self) -> bool {
140        self.pending != 0
141    }
142}
143
144impl Transcript {
145    /// Create a new transcript.
146    ///
147    /// The namespace serves to disambiguate two transcripts, so that even if they record
148    /// the same information, the results will be different:
149    /// ```
150    /// # use commonware_cryptography::transcript::Transcript;
151    /// let s1 = Transcript::new(b"n1").commit(b"A".as_slice()).summarize();
152    /// let s2 = Transcript::new(b"n2").commit(b"A".as_slice()).summarize();
153    /// assert_ne!(s1, s2);
154    /// ```
155    pub fn new(namespace: &[u8]) -> Self {
156        let mut out = Self::start(StartTag::New, None);
157        out.commit(namespace);
158        out
159    }
160
161    /// Start a transcript from a summary.
162    ///
163    /// Note that this will not produce the same result as if the transcript
164    /// were never summarized to begin with.
165    /// ```
166    /// # use commonware_cryptography::transcript::Transcript;
167    /// let s1 = Transcript::new(b"test").commit(b"A".as_slice()).summarize();
168    /// let s2 = Transcript::resume(s1.clone()).summarize();
169    /// assert_ne!(s1, s2);
170    /// ```
171    pub fn resume(summary: Summary) -> Self {
172        Self::start(StartTag::Resume, Some(summary))
173    }
174
175    /// Record data in this transcript.
176    ///
177    /// Calls to record automatically separate out data:
178    /// ```
179    /// # use commonware_cryptography::transcript::Transcript;
180    /// let s1 = Transcript::new(b"test").commit(b"A".as_slice()).commit(b"B".as_slice()).summarize();
181    /// let s2 = Transcript::new(b"test").commit(b"AB".as_slice()).summarize();
182    /// assert_ne!(s1, s2);
183    /// ```
184    ///
185    /// In particular, even a call with an empty string matters:
186    /// ```
187    /// # use commonware_cryptography::transcript::Transcript;
188    /// let s1 = Transcript::new(b"test").summarize();
189    /// let s2 = Transcript::new(b"testt").commit(b"".as_slice()).summarize();
190    /// assert_ne!(s1, s2);
191    /// ```
192    ///
193    /// If you want to provide data incrementally, use [Self::append].
194    pub fn commit(&mut self, data: impl Buf) -> &mut Self {
195        self.append(data);
196        self.flush();
197        self
198    }
199
200    /// Like [Self::commit], except that subsequent calls to [Self::append] or [Self::commit] are
201    /// considered part of the same message.
202    ///
203    /// [Self::commit] needs to be called before calling any other method, besides [Self::append],
204    /// in order to avoid having uncommitted data.
205    ///
206    /// ```
207    /// # use commonware_cryptography::transcript::Transcript;
208    /// let s1 = Transcript::new(b"test").append(b"A".as_slice()).commit(b"B".as_slice()).summarize();
209    /// let s2 = Transcript::new(b"test").commit(b"AB".as_slice()).summarize();
210    /// assert_eq!(s1, s2);
211    /// ```
212    pub fn append(&mut self, mut data: impl Buf) -> &mut Self {
213        while data.has_remaining() {
214            let chunk = data.chunk();
215            self.do_append(chunk);
216            data.advance(chunk.len());
217        }
218        self
219    }
220
221    /// Create a new instance sharing the same history.
222    ///
223    /// This instance will commit to the same data, but it will produce a different
224    /// summary and noise:
225    /// ```
226    /// # use commonware_cryptography::transcript::Transcript;
227    /// let t = Transcript::new(b"test");
228    /// assert_ne!(t.summarize(), t.fork(b"A").summarize());
229    /// assert_ne!(t.fork(b"A").summarize(), t.fork(b"B").summarize());
230    /// ```
231    pub fn fork(&self, label: &'static [u8]) -> Self {
232        let mut out = Self::start(StartTag::Fork, Some(self.summarize()));
233        out.commit(label);
234        out
235    }
236
237    /// Pull out some noise from this transript.
238    ///
239    /// This noise will depend on all of the messages committed to the transcript
240    /// so far, and can be used as a secure source of randomness, for generating
241    /// keys, and other things.
242    ///
243    /// The label will also affect the noise. Changing the label will change
244    /// the stream of bytes generated.
245    pub fn noise(&self, label: &'static [u8]) -> impl CryptoRng {
246        let mut out = Self::start(StartTag::Noise, Some(self.summarize()));
247        out.commit(label);
248        Rng::new(out.hasher.finalize_xof())
249    }
250
251    /// Shuffle a slice deterministically, based on this transcript.
252    ///
253    /// The permutation will depend on all of the messages committed to the
254    /// transcript so far. This is a Fisher-Yates shuffle over [Transcript::noise].
255    ///
256    /// The label will also affect the permutation. Changing the label will
257    /// change the resulting order:
258    /// ```
259    /// # use commonware_cryptography::transcript::Transcript;
260    /// let t = Transcript::new(b"test");
261    /// let mut a = [0u32, 1, 2, 3, 4, 5, 6, 7];
262    /// let mut b = a;
263    /// t.shuffle(b"A", &mut a);
264    /// t.shuffle(b"B", &mut b);
265    /// assert_ne!(a, b);
266    /// ```
267    #[commonware_macros::stability(ALPHA)]
268    pub fn shuffle<T>(&self, label: &'static [u8], items: &mut [T]) {
269        let mut rng = self.noise(label);
270        for i in (1..items.len()).rev() {
271            let j = sample(&mut rng, NZU64!(i as u64 + 1));
272            items.swap(i, j as usize);
273        }
274    }
275
276    /// Sample a uniform value in `0..bound`, based on this transcript.
277    ///
278    /// The value is unbiased, and will depend on all of the messages committed
279    /// to the transcript so far. The label will also affect the value:
280    /// ```
281    /// # use commonware_cryptography::transcript::Transcript;
282    /// # use commonware_utils::NZU64;
283    /// let t = Transcript::new(b"test");
284    /// assert_eq!(t.sample(b"A", NZU64!(100)), t.sample(b"A", NZU64!(100)));
285    /// assert!(t.sample(b"A", NZU64!(100)) < 100);
286    /// ```
287    #[commonware_macros::stability(ALPHA)]
288    pub fn sample(&self, label: &'static [u8], bound: NonZeroU64) -> u64 {
289        sample(self.noise(label), bound)
290    }
291
292    /// Extract a compact summary from this transcript.
293    ///
294    /// This can be used to compare transcripts for equality:
295    /// ```
296    /// # use commonware_cryptography::transcript::Transcript;
297    /// let s1 = Transcript::new(b"test").commit(b"DATA".as_slice()).summarize();
298    /// let s2 = Transcript::new(b"test").commit(b"DATA".as_slice()).summarize();
299    /// assert_eq!(s1, s2);
300    /// ```
301    pub fn summarize(&self) -> Summary {
302        let hash = if self.unflushed() {
303            let mut hasher = self.hasher.clone();
304            flush(&mut hasher, self.pending);
305            hasher.finalize()
306        } else {
307            self.hasher.finalize()
308        };
309        Summary { hash }
310    }
311}
312
313/// Sample a uniform value in `0..bound` from an infallible RNG.
314#[commonware_macros::stability(ALPHA)]
315fn sample(mut rng: impl CryptoRng, bound: NonZeroU64) -> u64 {
316    let bound = bound.get();
317
318    // Accept only draws below the largest multiple of `bound`, so that the
319    // modulo is unbiased. Fewer than two draws are needed on average.
320    let zone = bound * (u64::MAX / bound);
321    loop {
322        let v = rng.next_u64();
323        if v < zone {
324            return v % bound;
325        }
326    }
327}
328
329// Utility methods which can be created using the other methods.
330impl Transcript {
331    /// Use a signer to create a signature over this transcript.
332    ///
333    /// Conceptually, this is the same as:
334    /// - signing the operations that have been performed on the transcript,
335    /// - or, equivalently, signing randomness or a summary extracted from the transcript.
336    pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
337        self.summarize().sign(s)
338    }
339
340    /// Verify a signature produced by [Transcript::sign].
341    pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
342        self.summarize().verify(v, sig)
343    }
344
345    /// Append a signature produced by [Transcript::sign] to a batch verifier.
346    pub fn add_to_batch<B: BatchVerifier>(
347        &self,
348        batch: &mut B,
349        public_key: &B::PublicKey,
350        signature: &<B::PublicKey as Verifier>::Signature,
351    ) -> bool {
352        self.summarize().add_to_batch(batch, public_key, signature)
353    }
354}
355
356impl Summary {
357    /// Use a signer to create a signature over this summary.
358    pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
359        // Note: We pass an empty namespace here, since the namespace may be included
360        // within the transcript summary already via `Transcript::new`.
361        s.sign(b"", self.as_ref())
362    }
363
364    /// Verify a signature produced by [Summary::sign].
365    pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
366        // Note: We pass an empty namespace here, since the namespace may be included
367        // within the transcript summary already via `Transcript::new`.
368        v.verify(b"", self.as_ref(), sig)
369    }
370
371    /// Append a signature produced by [Summary::sign] to a batch verifier.
372    pub fn add_to_batch<B: BatchVerifier>(
373        &self,
374        batch: &mut B,
375        public_key: &B::PublicKey,
376        signature: &<B::PublicKey as Verifier>::Signature,
377    ) -> bool {
378        // Note: We pass an empty namespace here, since the namespace may be included
379        // within the transcript summary already via `Transcript::new`.
380        batch.add(b"", self.as_ref(), public_key, signature)
381    }
382}
383
384/// Represents a summary of a transcript.
385///
386/// This is the primary way to compare two transcripts for equality.
387/// You can think of this as a hash over the transcript, providing a commitment
388/// to the data it recorded.
389#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FixedArray)]
390pub struct Summary {
391    hash: blake3::Hash,
392}
393
394impl FixedSize for Summary {
395    const SIZE: usize = blake3::OUT_LEN;
396}
397
398impl Write for Summary {
399    fn write(&self, buf: &mut impl bytes::BufMut) {
400        self.hash.as_bytes().write(buf)
401    }
402}
403
404impl Read for Summary {
405    type Cfg = ();
406
407    fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
408        Ok(Self {
409            hash: blake3::Hash::from_bytes(ReadExt::read(buf)?),
410        })
411    }
412}
413
414impl AsRef<[u8]> for Summary {
415    fn as_ref(&self) -> &[u8] {
416        self.hash.as_bytes().as_slice()
417    }
418}
419
420impl Deref for Summary {
421    type Target = [u8];
422
423    fn deref(&self) -> &Self::Target {
424        self.as_ref()
425    }
426}
427
428impl PartialOrd for Summary {
429    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
430        Some(self.cmp(other))
431    }
432}
433
434impl Ord for Summary {
435    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
436        self.as_ref().cmp(other.as_ref())
437    }
438}
439
440impl Display for Summary {
441    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
442        write!(f, "{}", commonware_formatting::Hex(self.as_ref()))
443    }
444}
445
446impl Span for Summary {}
447
448impl Array for Summary {}
449
450impl crate::Digest for Summary {
451    const EMPTY: Self = Self {
452        hash: blake3::Hash::from_bytes([0u8; blake3::OUT_LEN]),
453    };
454}
455
456impl Random for Summary {
457    fn random(mut rng: impl CryptoRng) -> Self {
458        let mut bytes = [0u8; blake3::OUT_LEN];
459        rng.fill_bytes(&mut bytes[..]);
460        Self {
461            hash: blake3::Hash::from_bytes(bytes),
462        }
463    }
464}
465
466#[cfg(any(test, feature = "arbitrary"))]
467impl arbitrary::Arbitrary<'_> for Summary {
468    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
469        let bytes: [u8; blake3::OUT_LEN] = u.arbitrary()?;
470        Ok(Self {
471            hash: blake3::Hash::from_bytes(bytes),
472        })
473    }
474}
475
476#[cfg(test)]
477mod test {
478    use super::*;
479    use crate::ed25519;
480    use commonware_codec::{DecodeExt as _, Encode};
481    use commonware_parallel::Sequential;
482    use commonware_utils::test_rng;
483    use rand_core::Rng;
484
485    #[test]
486    fn test_namespace_affects_summary() {
487        let s1 = Transcript::new(b"Test-A").summarize();
488        let s2 = Transcript::new(b"Test-B").summarize();
489        assert_ne!(s1, s2);
490    }
491
492    #[test]
493    fn test_namespace_doesnt_leak_into_data() {
494        let s1 = Transcript::new(b"Test-A").summarize();
495        let s2 = Transcript::new(b"Test-").commit(b"".as_slice()).summarize();
496        assert_ne!(s1, s2);
497    }
498
499    #[test]
500    fn test_commit_separates_data() {
501        let s1 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
502        let s2 = Transcript::new(b"")
503            .commit(b"A".as_slice())
504            .commit(b"B".as_slice())
505            .summarize();
506        assert_ne!(s1, s2);
507    }
508
509    #[test]
510    fn test_append_commit_works() {
511        let s1 = Transcript::new(b"")
512            .append(b"A".as_slice())
513            .commit(b"B".as_slice())
514            .summarize();
515        let s2 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
516        assert_eq!(s1, s2);
517    }
518
519    #[test]
520    fn test_fork_returns_different_result() {
521        let t1 = Transcript::new(b"");
522        let t2 = t1.fork(b"");
523        assert_ne!(t1.summarize(), t2.summarize());
524    }
525
526    #[test]
527    fn test_fork_label_matters() {
528        let t1 = Transcript::new(b"");
529        let t2 = t1.fork(b"A");
530        let t3 = t2.fork(b"B");
531        assert_ne!(t2.summarize(), t3.summarize());
532    }
533
534    #[test]
535    fn test_noise_and_summarize_are_different() {
536        let t1 = Transcript::new(b"");
537        let mut s1_bytes = [0u8; 32];
538        t1.noise(b"foo").fill_bytes(&mut s1_bytes[..]);
539        let s1 = Summary {
540            hash: blake3::Hash::from_bytes(s1_bytes),
541        };
542        let s2 = t1.summarize();
543        assert_ne!(s1, s2);
544    }
545
546    #[test]
547    fn test_noise_stream_chunking_doesnt_matter() {
548        let mut s = [0u8; 2 * BLOCK_LEN];
549        Transcript::new(b"test")
550            .noise(b"NOISE")
551            .fill_bytes(&mut s[..]);
552        // Split up the bytes into two chunks
553        for i in 0..s.len() {
554            let mut s_prime = [0u8; 2 * BLOCK_LEN];
555            let mut noise = Transcript::new(b"test").noise(b"NOISE");
556            noise.fill_bytes(&mut s_prime[..i]);
557            noise.fill_bytes(&mut s_prime[i..]);
558            assert_eq!(s, s_prime);
559        }
560    }
561
562    #[test]
563    fn test_noise_label_matters() {
564        let mut s1 = [0u8; 32];
565        let mut s2 = [0u8; 32];
566        let t1 = Transcript::new(b"test");
567        t1.noise(b"A").fill_bytes(&mut s1);
568        t1.noise(b"B").fill_bytes(&mut s2);
569        assert_ne!(s1, s2);
570    }
571
572    #[test]
573    fn test_summarize_resume_is_different_than_new() {
574        let s = Transcript::new(b"test").summarize();
575        let s1 = Transcript::new(s.hash.as_bytes()).summarize();
576        let s2 = Transcript::resume(s).summarize();
577        assert_ne!(s1, s2);
578    }
579
580    #[test]
581    fn test_summary_encode_roundtrip() {
582        let s = Transcript::new(b"test").summarize();
583        assert_eq!(&s, &Summary::decode(s.encode()).unwrap());
584    }
585
586    #[test]
587    fn test_summary_sign_verify_matches_transcript() {
588        let sk = ed25519::PrivateKey::from_seed(7);
589        let pk = sk.public_key();
590        let mut transcript = Transcript::new(b"test");
591        transcript.commit(b"DATA".as_slice());
592        let summary = transcript.summarize();
593
594        let sig = summary.sign(&sk);
595        assert_eq!(sig, transcript.sign(&sk));
596        assert!(summary.verify(&pk, &sig));
597        assert!(transcript.verify(&pk, &sig));
598    }
599
600    #[test]
601    fn test_summary_add_to_batch_matches_transcript() {
602        let sk = ed25519::PrivateKey::from_seed(7);
603        let pk = sk.public_key();
604        let mut transcript = Transcript::new(b"test");
605        transcript.commit(b"DATA".as_slice());
606        let summary = transcript.summarize();
607        let sig = transcript.sign(&sk);
608
609        let mut summary_batch = ed25519::Batch::new(1);
610        assert!(summary.add_to_batch(&mut summary_batch, &pk, &sig));
611        let mut transcript_batch = ed25519::Batch::new(1);
612        assert!(transcript.add_to_batch(&mut transcript_batch, &pk, &sig));
613
614        assert!(summary_batch.verify(&mut test_rng(), &Sequential));
615        assert!(transcript_batch.verify(&mut test_rng(), &Sequential));
616    }
617
618    #[test]
619    fn test_shuffle_is_permutation() {
620        let t = Transcript::new(b"test");
621        let mut items: Vec<u32> = (0..1000).collect();
622        t.shuffle(b"shuffle", &mut items);
623        assert_ne!(items, (0..1000).collect::<Vec<_>>());
624        items.sort_unstable();
625        assert_eq!(items, (0..1000).collect::<Vec<_>>());
626    }
627
628    #[test]
629    fn test_shuffle_is_deterministic() {
630        let mut t = Transcript::new(b"test");
631        t.commit(b"DATA".as_slice());
632        let mut s1: Vec<u32> = (0..100).collect();
633        let mut s2 = s1.clone();
634        t.shuffle(b"shuffle", &mut s1);
635        t.shuffle(b"shuffle", &mut s2);
636        assert_eq!(s1, s2);
637    }
638
639    #[test]
640    fn test_shuffle_label_and_history_matter() {
641        let t1 = Transcript::new(b"test");
642        let mut t2 = Transcript::new(b"test");
643        t2.commit(b"DATA".as_slice());
644        let mut base: Vec<u32> = (0..100).collect();
645        let (mut a, mut b, mut c) = (base.clone(), base.clone(), base.clone());
646        t1.shuffle(b"A", &mut a);
647        t1.shuffle(b"B", &mut b);
648        t2.shuffle(b"A", &mut c);
649        base.clear();
650        assert_ne!(a, b);
651        assert_ne!(a, c);
652    }
653
654    #[test]
655    fn test_sample_within_bound() {
656        let t = Transcript::new(b"test");
657        let mut rng = t.noise(b"sample");
658        for bound in [1, 2, 3, 7, 100, 1 << 40, u64::MAX] {
659            assert!(sample(&mut rng, NZU64!(bound)) < bound);
660        }
661        assert_eq!(t.sample(b"sample", NZU64!(1)), 0);
662        assert_eq!(
663            t.sample(b"one shot", NZU64!(1000)),
664            sample(t.noise(b"one shot"), NZU64!(1000))
665        );
666    }
667
668    #[test]
669    fn test_missing_append() {
670        let s1 = Transcript::new(b"foo").append(b"AB".as_slice()).summarize();
671        let s2 = Transcript::new(b"foo")
672            .append(b"A".as_slice())
673            .commit(b"B".as_slice())
674            .summarize();
675        assert_eq!(s1, s2)
676    }
677
678    #[cfg(feature = "arbitrary")]
679    mod conformance {
680        use super::*;
681        use commonware_codec::conformance::CodecConformance;
682        use commonware_conformance::Conformance;
683
684        struct TranscriptOps;
685
686        impl Conformance for TranscriptOps {
687            async fn commit(seed: u64) -> Vec<u8> {
688                let seed_bytes = seed.to_le_bytes();
689                let namespace = seed_bytes[..(seed as usize % seed_bytes.len()) + 1].to_vec();
690                let data: Vec<_> = (0..seed as usize % 256)
691                    .map(|i| (seed as u8).wrapping_add((3 * i) as u8))
692                    .collect();
693                let split = data.len() / 2;
694
695                let mut transcript = Transcript::new(&namespace);
696                transcript.append(&data[..split]);
697                transcript.commit(&data[split..]);
698
699                let mut log = transcript.summarize().encode().to_vec();
700                log.extend(
701                    Transcript::new(&namespace)
702                        .commit(&data[..split])
703                        .commit(&data[split..])
704                        .summarize()
705                        .encode(),
706                );
707                log.extend(
708                    Transcript::new(&namespace)
709                        .append(data.as_slice())
710                        .commit([].as_slice())
711                        .summarize()
712                        .encode(),
713                );
714                let resumed = Transcript::resume(transcript.summarize());
715                log.extend(resumed.summarize().encode());
716                log.extend(transcript.fork(b"left").summarize().encode());
717                log.extend(transcript.fork(b"right").summarize().encode());
718
719                let mut noise = [0u8; 80];
720                let mut rng = transcript.noise(b"noise");
721                log.extend(rng.next_u32().encode());
722                log.extend(rng.next_u64().encode());
723                rng.fill_bytes(&mut noise[..31]);
724                rng.fill_bytes(&mut noise[31..]);
725                log.extend(noise);
726
727                let mut indices: Vec<u32> = (0..(seed % 100) as u32).collect();
728                transcript.shuffle(b"shuffle", &mut indices);
729                for index in &indices {
730                    log.extend(index.encode());
731                }
732                log.extend(transcript.sample(b"sample", NZU64!(seed | 1)).encode());
733
734                let private_key = ed25519::PrivateKey::from_seed(seed);
735                let public_key = private_key.public_key();
736                let summary = transcript.summarize();
737                let summary_sig = summary.sign(&private_key);
738                let transcript_sig = transcript.sign(&private_key);
739                log.extend(summary_sig.encode());
740                log.extend(transcript_sig.encode());
741                log.extend(summary.verify(&public_key, &summary_sig).encode());
742                log.extend(transcript.verify(&public_key, &transcript_sig).encode());
743
744                let mut summary_batch = ed25519::Batch::new(1);
745                log.extend(
746                    summary
747                        .add_to_batch(&mut summary_batch, &public_key, &summary_sig)
748                        .encode(),
749                );
750                log.extend(
751                    summary_batch
752                        .verify(&mut transcript.noise(b"summary batch"), &Sequential)
753                        .encode(),
754                );
755
756                let mut transcript_batch = ed25519::Batch::new(1);
757                log.extend(
758                    transcript
759                        .add_to_batch(&mut transcript_batch, &public_key, &transcript_sig)
760                        .encode(),
761                );
762                log.extend(
763                    transcript_batch
764                        .verify(&mut transcript.noise(b"transcript batch"), &Sequential)
765                        .encode(),
766                );
767
768                let mut pending = Transcript::new(&namespace);
769                pending.append(data.as_slice());
770                let pending_summary = pending.summarize();
771                log.extend(pending_summary.encode());
772                log.extend(pending.fork(b"pending fork").summarize().encode());
773
774                let mut pending_noise = [0u8; 37];
775                pending
776                    .noise(b"pending noise")
777                    .fill_bytes(&mut pending_noise);
778                log.extend(pending_noise);
779
780                let pending_sig = pending.sign(&private_key);
781                log.extend(pending_sig.encode());
782                log.extend(pending.verify(&public_key, &pending_sig).encode());
783
784                log
785            }
786        }
787
788        commonware_conformance::conformance_tests! {
789            TranscriptOps => 4096,
790            CodecConformance<Summary>,
791        }
792    }
793}