1use 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#[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#[repr(u8)]
97enum StartTag {
98 New = 0,
99 Resume = 1,
100 Fork = 2,
101 Noise = 3,
102}
103
104#[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 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 pub fn new(namespace: &[u8]) -> Self {
156 let mut out = Self::start(StartTag::New, None);
157 out.commit(namespace);
158 out
159 }
160
161 pub fn resume(summary: Summary) -> Self {
172 Self::start(StartTag::Resume, Some(summary))
173 }
174
175 pub fn commit(&mut self, data: impl Buf) -> &mut Self {
195 self.append(data);
196 self.flush();
197 self
198 }
199
200 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 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 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 #[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 #[commonware_macros::stability(ALPHA)]
288 pub fn sample(&self, label: &'static [u8], bound: NonZeroU64) -> u64 {
289 sample(self.noise(label), bound)
290 }
291
292 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#[commonware_macros::stability(ALPHA)]
315fn sample(mut rng: impl CryptoRng, bound: NonZeroU64) -> u64 {
316 let bound = bound.get();
317
318 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
329impl Transcript {
331 pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
337 self.summarize().sign(s)
338 }
339
340 pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
342 self.summarize().verify(v, sig)
343 }
344
345 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 pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
359 s.sign(b"", self.as_ref())
362 }
363
364 pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
366 v.verify(b"", self.as_ref(), sig)
369 }
370
371 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 batch.add(b"", self.as_ref(), public_key, signature)
381 }
382}
383
384#[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 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}