1use crate::{BatchVerifier, Signer, Verifier};
7use blake3::BLOCK_LEN;
8use bytes::Buf;
9use commonware_codec::{varint::UInt, EncodeSize, FixedSize, Read, ReadExt, Write};
10use commonware_math::algebra::Random;
11use commonware_utils::{Array, Span};
12use core::{fmt::Display, ops::Deref};
13use rand_core::{
14 impls::{next_u32_via_fill, next_u64_via_fill},
15 CryptoRng, CryptoRngCore, RngCore,
16};
17use zeroize::ZeroizeOnDrop;
18
19#[derive(ZeroizeOnDrop)]
24struct Rng {
25 inner: blake3::OutputReader,
26 buf: [u8; BLOCK_LEN],
27 start: usize,
28}
29
30impl Rng {
31 const fn new(inner: blake3::OutputReader) -> Self {
32 Self {
33 inner,
34 buf: [0u8; BLOCK_LEN],
35 start: BLOCK_LEN,
36 }
37 }
38}
39
40impl RngCore for Rng {
41 fn next_u32(&mut self) -> u32 {
42 next_u32_via_fill(self)
43 }
44
45 fn next_u64(&mut self) -> u64 {
46 next_u64_via_fill(self)
47 }
48
49 fn fill_bytes(&mut self, dest: &mut [u8]) {
50 let dest_len = dest.len();
51 let remaining = &self.buf[self.start..];
52 if remaining.len() >= dest_len {
53 dest.copy_from_slice(&remaining[..dest_len]);
54 self.start += dest_len;
55 return;
56 }
57
58 let (start, mut dest) = dest.split_at_mut(remaining.len());
59 start.copy_from_slice(remaining);
60 self.start = BLOCK_LEN;
61
62 while dest.len() >= BLOCK_LEN {
63 let (block, rest) = dest.split_at_mut(BLOCK_LEN);
64 self.inner.fill(block);
65 dest = rest;
66 }
67
68 let dest_len = dest.len();
69 if dest_len > 0 {
70 self.inner.fill(&mut self.buf[..]);
71 dest.copy_from_slice(&self.buf[..dest_len]);
72 self.start = dest_len;
73 }
74 }
75
76 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
77 self.fill_bytes(dest);
78 Ok(())
79 }
80}
81
82impl CryptoRng for Rng {}
83
84fn flush(hasher: &mut blake3::Hasher, pending: u64) {
85 let mut pending_bytes = [0u8; 9];
86 let pending = UInt(pending);
87 pending.write(&mut &mut pending_bytes[..]);
88 hasher.update(&pending_bytes[..pending.encode_size()]);
89}
90
91#[repr(u8)]
93enum StartTag {
94 New = 0,
95 Resume = 1,
96 Fork = 2,
97 Noise = 3,
98}
99
100#[derive(ZeroizeOnDrop)]
107pub struct Transcript {
108 hasher: blake3::Hasher,
109 pending: u64,
110}
111
112impl Transcript {
113 fn start(tag: StartTag, summary: Option<Summary>) -> Self {
114 let mut hasher = summary.map_or_else(blake3::Hasher::new, |s| {
119 blake3::Hasher::new_keyed(s.hash.as_bytes())
120 });
121 hasher.update(&[tag as u8]);
122 Self { hasher, pending: 0 }
123 }
124
125 fn flush(&mut self) {
126 flush(&mut self.hasher, self.pending);
127 self.pending = 0;
128 }
129
130 fn do_append(&mut self, data: &[u8]) {
131 self.hasher.update(data);
132 self.pending += data.len() as u64;
133 }
134
135 const fn unflushed(&self) -> bool {
136 self.pending != 0
137 }
138}
139
140impl Transcript {
141 pub fn new(namespace: &[u8]) -> Self {
152 let mut out = Self::start(StartTag::New, None);
153 out.commit(namespace);
154 out
155 }
156
157 pub fn resume(summary: Summary) -> Self {
168 Self::start(StartTag::Resume, Some(summary))
169 }
170
171 pub fn commit(&mut self, data: impl Buf) -> &mut Self {
191 self.append(data);
192 self.flush();
193 self
194 }
195
196 pub fn append(&mut self, mut data: impl Buf) -> &mut Self {
209 while data.has_remaining() {
210 let chunk = data.chunk();
211 self.do_append(chunk);
212 data.advance(chunk.len());
213 }
214 self
215 }
216
217 pub fn fork(&self, label: &'static [u8]) -> Self {
228 let mut out = Self::start(StartTag::Fork, Some(self.summarize()));
229 out.commit(label);
230 out
231 }
232
233 pub fn noise(&self, label: &'static [u8]) -> impl CryptoRngCore {
242 let mut out = Self::start(StartTag::Noise, Some(self.summarize()));
243 out.commit(label);
244 Rng::new(out.hasher.finalize_xof())
245 }
246
247 pub fn summarize(&self) -> Summary {
257 let hash = if self.unflushed() {
258 let mut hasher = self.hasher.clone();
259 flush(&mut hasher, self.pending);
260 hasher.finalize()
261 } else {
262 self.hasher.finalize()
263 };
264 Summary { hash }
265 }
266}
267
268impl Transcript {
270 pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
276 self.summarize().sign(s)
277 }
278
279 pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
281 self.summarize().verify(v, sig)
282 }
283
284 pub fn add_to_batch<B: BatchVerifier>(
286 &self,
287 batch: &mut B,
288 public_key: &B::PublicKey,
289 signature: &<B::PublicKey as Verifier>::Signature,
290 ) -> bool {
291 self.summarize().add_to_batch(batch, public_key, signature)
292 }
293}
294
295impl Summary {
296 pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
298 s.sign(b"", self.as_ref())
301 }
302
303 pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
305 v.verify(b"", self.as_ref(), sig)
308 }
309
310 pub fn add_to_batch<B: BatchVerifier>(
312 &self,
313 batch: &mut B,
314 public_key: &B::PublicKey,
315 signature: &<B::PublicKey as Verifier>::Signature,
316 ) -> bool {
317 batch.add(b"", self.as_ref(), public_key, signature)
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
329pub struct Summary {
330 hash: blake3::Hash,
331}
332
333impl FixedSize for Summary {
334 const SIZE: usize = blake3::OUT_LEN;
335}
336
337impl Write for Summary {
338 fn write(&self, buf: &mut impl bytes::BufMut) {
339 self.hash.as_bytes().write(buf)
340 }
341}
342
343impl Read for Summary {
344 type Cfg = ();
345
346 fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
347 Ok(Self {
348 hash: blake3::Hash::from_bytes(ReadExt::read(buf)?),
349 })
350 }
351}
352
353impl AsRef<[u8]> for Summary {
354 fn as_ref(&self) -> &[u8] {
355 self.hash.as_bytes().as_slice()
356 }
357}
358
359impl Deref for Summary {
360 type Target = [u8];
361
362 fn deref(&self) -> &Self::Target {
363 self.as_ref()
364 }
365}
366
367impl PartialOrd for Summary {
368 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
369 Some(self.cmp(other))
370 }
371}
372
373impl Ord for Summary {
374 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
375 self.as_ref().cmp(other.as_ref())
376 }
377}
378
379impl Display for Summary {
380 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381 write!(f, "{}", commonware_utils::hex(self.as_ref()))
382 }
383}
384
385impl Span for Summary {}
386impl Array for Summary {}
387
388impl crate::Digest for Summary {
389 const EMPTY: Self = Self {
390 hash: blake3::Hash::from_bytes([0u8; blake3::OUT_LEN]),
391 };
392}
393
394impl Random for Summary {
395 fn random(mut rng: impl CryptoRngCore) -> Self {
396 let mut bytes = [0u8; blake3::OUT_LEN];
397 rng.fill_bytes(&mut bytes[..]);
398 Self {
399 hash: blake3::Hash::from_bytes(bytes),
400 }
401 }
402}
403
404#[cfg(feature = "arbitrary")]
405impl arbitrary::Arbitrary<'_> for Summary {
406 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
407 let bytes: [u8; blake3::OUT_LEN] = u.arbitrary()?;
408 Ok(Self {
409 hash: blake3::Hash::from_bytes(bytes),
410 })
411 }
412}
413
414#[cfg(test)]
415mod test {
416 use super::*;
417 use crate::ed25519;
418 use commonware_codec::{DecodeExt as _, Encode};
419 use commonware_utils::test_rng;
420
421 #[test]
422 fn test_namespace_affects_summary() {
423 let s1 = Transcript::new(b"Test-A").summarize();
424 let s2 = Transcript::new(b"Test-B").summarize();
425 assert_ne!(s1, s2);
426 }
427
428 #[test]
429 fn test_namespace_doesnt_leak_into_data() {
430 let s1 = Transcript::new(b"Test-A").summarize();
431 let s2 = Transcript::new(b"Test-").commit(b"".as_slice()).summarize();
432 assert_ne!(s1, s2);
433 }
434
435 #[test]
436 fn test_commit_separates_data() {
437 let s1 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
438 let s2 = Transcript::new(b"")
439 .commit(b"A".as_slice())
440 .commit(b"B".as_slice())
441 .summarize();
442 assert_ne!(s1, s2);
443 }
444
445 #[test]
446 fn test_append_commit_works() {
447 let s1 = Transcript::new(b"")
448 .append(b"A".as_slice())
449 .commit(b"B".as_slice())
450 .summarize();
451 let s2 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
452 assert_eq!(s1, s2);
453 }
454
455 #[test]
456 fn test_fork_returns_different_result() {
457 let t1 = Transcript::new(b"");
458 let t2 = t1.fork(b"");
459 assert_ne!(t1.summarize(), t2.summarize());
460 }
461
462 #[test]
463 fn test_fork_label_matters() {
464 let t1 = Transcript::new(b"");
465 let t2 = t1.fork(b"A");
466 let t3 = t2.fork(b"B");
467 assert_ne!(t2.summarize(), t3.summarize());
468 }
469
470 #[test]
471 fn test_noise_and_summarize_are_different() {
472 let t1 = Transcript::new(b"");
473 let mut s1_bytes = [0u8; 32];
474 t1.noise(b"foo").fill_bytes(&mut s1_bytes[..]);
475 let s1 = Summary {
476 hash: blake3::Hash::from_bytes(s1_bytes),
477 };
478 let s2 = t1.summarize();
479 assert_ne!(s1, s2);
480 }
481
482 #[test]
483 fn test_noise_stream_chunking_doesnt_matter() {
484 let mut s = [0u8; 2 * BLOCK_LEN];
485 Transcript::new(b"test")
486 .noise(b"NOISE")
487 .fill_bytes(&mut s[..]);
488 for i in 0..s.len() {
490 let mut s_prime = [0u8; 2 * BLOCK_LEN];
491 let mut noise = Transcript::new(b"test").noise(b"NOISE");
492 noise.fill_bytes(&mut s_prime[..i]);
493 noise.fill_bytes(&mut s_prime[i..]);
494 assert_eq!(s, s_prime);
495 }
496 }
497
498 #[test]
499 fn test_noise_label_matters() {
500 let mut s1 = [0u8; 32];
501 let mut s2 = [0u8; 32];
502 let t1 = Transcript::new(b"test");
503 t1.noise(b"A").fill_bytes(&mut s1);
504 t1.noise(b"B").fill_bytes(&mut s2);
505 assert_ne!(s1, s2);
506 }
507
508 #[test]
509 fn test_summarize_resume_is_different_than_new() {
510 let s = Transcript::new(b"test").summarize();
511 let s1 = Transcript::new(s.hash.as_bytes()).summarize();
512 let s2 = Transcript::resume(s).summarize();
513 assert_ne!(s1, s2);
514 }
515
516 #[test]
517 fn test_summary_encode_roundtrip() {
518 let s = Transcript::new(b"test").summarize();
519 assert_eq!(&s, &Summary::decode(s.encode()).unwrap());
520 }
521
522 #[test]
523 fn test_summary_sign_verify_matches_transcript() {
524 let sk = ed25519::PrivateKey::from_seed(7);
525 let pk = sk.public_key();
526 let mut transcript = Transcript::new(b"test");
527 transcript.commit(b"DATA".as_slice());
528 let summary = transcript.summarize();
529
530 let sig = summary.sign(&sk);
531 assert_eq!(sig, transcript.sign(&sk));
532 assert!(summary.verify(&pk, &sig));
533 assert!(transcript.verify(&pk, &sig));
534 }
535
536 #[test]
537 fn test_summary_add_to_batch_matches_transcript() {
538 let sk = ed25519::PrivateKey::from_seed(7);
539 let pk = sk.public_key();
540 let mut transcript = Transcript::new(b"test");
541 transcript.commit(b"DATA".as_slice());
542 let summary = transcript.summarize();
543 let sig = transcript.sign(&sk);
544
545 let mut summary_batch = ed25519::Batch::new();
546 assert!(summary.add_to_batch(&mut summary_batch, &pk, &sig));
547 let mut transcript_batch = ed25519::Batch::new();
548 assert!(transcript.add_to_batch(&mut transcript_batch, &pk, &sig));
549
550 assert!(summary_batch.verify(&mut test_rng()));
551 assert!(transcript_batch.verify(&mut test_rng()));
552 }
553
554 #[test]
555 fn test_missing_append() {
556 let s1 = Transcript::new(b"foo").append(b"AB".as_slice()).summarize();
557 let s2 = Transcript::new(b"foo")
558 .append(b"A".as_slice())
559 .commit(b"B".as_slice())
560 .summarize();
561 assert_eq!(s1, s2)
562 }
563
564 #[cfg(feature = "arbitrary")]
565 mod conformance {
566 use super::*;
567 use commonware_codec::conformance::CodecConformance;
568
569 commonware_conformance::conformance_tests! {
570 CodecConformance<Summary>,
571 }
572 }
573}