commonware_cryptography/
transcript.rs1use crate::{Signer, Verifier};
7use blake3::BLOCK_LEN;
8use bytes::Buf;
9use commonware_codec::{varint::UInt, EncodeSize, FixedSize, Read, ReadExt, Write};
10use commonware_utils::{Array, Span};
11use core::{fmt::Display, ops::Deref};
12use rand_core::{
13 impls::{next_u32_via_fill, next_u64_via_fill},
14 CryptoRng, CryptoRngCore, RngCore,
15};
16use zeroize::ZeroizeOnDrop;
17
18#[derive(ZeroizeOnDrop)]
23struct Rng {
24 inner: blake3::OutputReader,
25 buf: [u8; BLOCK_LEN],
26 start: usize,
27}
28
29impl Rng {
30 fn new(inner: blake3::OutputReader) -> Self {
31 Self {
32 inner,
33 buf: [0u8; BLOCK_LEN],
34 start: BLOCK_LEN,
35 }
36 }
37}
38
39impl RngCore for Rng {
40 fn next_u32(&mut self) -> u32 {
41 next_u32_via_fill(self)
42 }
43
44 fn next_u64(&mut self) -> u64 {
45 next_u64_via_fill(self)
46 }
47
48 fn fill_bytes(&mut self, dest: &mut [u8]) {
49 let dest_len = dest.len();
50 let remaining = &self.buf[self.start..];
51 if remaining.len() >= dest_len {
52 dest.copy_from_slice(&remaining[..dest_len]);
53 self.start += dest_len;
54 return;
55 }
56
57 let (start, mut dest) = dest.split_at_mut(remaining.len());
58 start.copy_from_slice(remaining);
59 self.start = BLOCK_LEN;
60
61 while dest.len() >= BLOCK_LEN {
62 let (block, rest) = dest.split_at_mut(BLOCK_LEN);
63 self.inner.fill(block);
64 dest = rest;
65 }
66
67 let dest_len = dest.len();
68 if dest_len > 0 {
69 self.inner.fill(&mut self.buf[..]);
70 dest.copy_from_slice(&self.buf[..dest_len]);
71 self.start = dest_len;
72 }
73 }
74
75 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
76 self.fill_bytes(dest);
77 Ok(())
78 }
79}
80
81impl CryptoRng for Rng {}
82
83#[repr(u8)]
85enum StartTag {
86 New = 0,
87 Resume = 1,
88 Fork = 2,
89 Noise = 3,
90}
91
92#[derive(ZeroizeOnDrop)]
99pub struct Transcript {
100 hasher: blake3::Hasher,
101 pending: u64,
102}
103
104impl Transcript {
105 fn start(tag: StartTag, summary: Option<Summary>) -> Self {
106 let mut hasher = match summary {
111 Some(s) => blake3::Hasher::new_keyed(s.hash.as_bytes()),
112 None => blake3::Hasher::new(),
113 };
114 hasher.update(&[tag as u8]);
115 Self { hasher, pending: 0 }
116 }
117
118 fn flush(&mut self) {
119 let mut pending_bytes = [0u8; 9];
120 let pending = UInt(self.pending);
121 pending.write(&mut &mut pending_bytes[..]);
122 self.hasher.update(&pending_bytes[..pending.encode_size()]);
123 self.pending = 0;
124 }
125
126 fn do_append(&mut self, data: &[u8]) {
127 self.hasher.update(data);
128 self.pending += data.len() as u64;
129 }
130
131 fn assert_committed(&self) {
132 assert!(self.pending == 0, "transcript had uncommitted data");
133 }
134}
135
136impl Transcript {
137 pub fn new(namespace: &[u8]) -> Self {
148 let mut out = Self::start(StartTag::New, None);
149 out.commit(namespace);
150 out
151 }
152
153 pub fn resume(summary: Summary) -> Self {
164 Self::start(StartTag::Resume, Some(summary))
165 }
166
167 pub fn commit(&mut self, data: impl Buf) -> &mut Self {
187 self.append(data);
188 self.flush();
189 self
190 }
191
192 pub fn append(&mut self, mut data: impl Buf) -> &mut Self {
205 while data.has_remaining() {
206 let chunk = data.chunk();
207 self.do_append(chunk);
208 data.advance(chunk.len());
209 }
210 self
211 }
212
213 pub fn fork(&self, label: &'static [u8]) -> Self {
224 let mut out = Self::start(StartTag::Fork, Some(self.summarize()));
225 out.commit(label);
226 out
227 }
228
229 pub fn noise(&self, label: &'static [u8]) -> impl CryptoRngCore {
238 let mut out = Self::start(StartTag::Noise, Some(self.summarize()));
239 out.commit(label);
240 Rng::new(out.hasher.finalize_xof())
241 }
242
243 pub fn summarize(&self) -> Summary {
253 self.assert_committed();
254 Summary {
255 hash: self.hasher.finalize(),
256 }
257 }
258}
259
260impl Transcript {
262 pub fn sign<S: Signer>(&self, s: &S) -> <S as Signer>::Signature {
268 s.sign(None, self.summarize().hash.as_bytes())
269 }
270
271 pub fn verify<V: Verifier>(&self, v: &V, sig: &<V as Verifier>::Signature) -> bool {
273 v.verify(None, self.summarize().hash.as_bytes(), sig)
274 }
275}
276
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
283pub struct Summary {
284 hash: blake3::Hash,
285}
286
287impl FixedSize for Summary {
288 const SIZE: usize = blake3::OUT_LEN;
289}
290
291impl Write for Summary {
292 fn write(&self, buf: &mut impl bytes::BufMut) {
293 self.hash.as_bytes().write(buf)
294 }
295}
296
297impl Read for Summary {
298 type Cfg = ();
299
300 fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, commonware_codec::Error> {
301 Ok(Self {
302 hash: blake3::Hash::from_bytes(ReadExt::read(buf)?),
303 })
304 }
305}
306
307impl AsRef<[u8]> for Summary {
308 fn as_ref(&self) -> &[u8] {
309 self.hash.as_bytes().as_slice()
310 }
311}
312
313impl Deref for Summary {
314 type Target = [u8];
315
316 fn deref(&self) -> &Self::Target {
317 self.as_ref()
318 }
319}
320
321impl PartialOrd for Summary {
322 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
323 Some(self.cmp(other))
324 }
325}
326
327impl Ord for Summary {
328 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
329 self.as_ref().cmp(other.as_ref())
330 }
331}
332
333impl Display for Summary {
334 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
335 write!(f, "{}", commonware_utils::hex(self.as_ref()))
336 }
337}
338
339impl Span for Summary {}
340impl Array for Summary {}
341
342impl crate::Digest for Summary {
343 fn random<R: CryptoRngCore>(rng: &mut R) -> Self {
344 let mut bytes = [0u8; blake3::OUT_LEN];
345 rng.fill_bytes(&mut bytes[..]);
346 Self {
347 hash: blake3::Hash::from_bytes(bytes),
348 }
349 }
350}
351
352#[cfg(test)]
353mod test {
354 use super::*;
355 use commonware_codec::{DecodeExt as _, Encode};
356
357 #[test]
358 fn test_namespace_affects_summary() {
359 let s1 = Transcript::new(b"Test-A").summarize();
360 let s2 = Transcript::new(b"Test-B").summarize();
361 assert_ne!(s1, s2);
362 }
363
364 #[test]
365 fn test_namespace_doesnt_leak_into_data() {
366 let s1 = Transcript::new(b"Test-A").summarize();
367 let s2 = Transcript::new(b"Test-").commit(b"".as_slice()).summarize();
368 assert_ne!(s1, s2);
369 }
370
371 #[test]
372 fn test_commit_separates_data() {
373 let s1 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
374 let s2 = Transcript::new(b"")
375 .commit(b"A".as_slice())
376 .commit(b"B".as_slice())
377 .summarize();
378 assert_ne!(s1, s2);
379 }
380
381 #[test]
382 fn test_append_commit_works() {
383 let s1 = Transcript::new(b"")
384 .append(b"A".as_slice())
385 .commit(b"B".as_slice())
386 .summarize();
387 let s2 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
388 assert_eq!(s1, s2);
389 }
390
391 #[test]
392 fn test_fork_returns_different_result() {
393 let t1 = Transcript::new(b"");
394 let t2 = t1.fork(b"");
395 assert_ne!(t1.summarize(), t2.summarize());
396 }
397
398 #[test]
399 fn test_fork_label_matters() {
400 let t1 = Transcript::new(b"");
401 let t2 = t1.fork(b"A");
402 let t3 = t2.fork(b"B");
403 assert_ne!(t2.summarize(), t3.summarize());
404 }
405
406 #[test]
407 fn test_noise_and_summarize_are_different() {
408 let t1 = Transcript::new(b"");
409 let mut s1_bytes = [0u8; 32];
410 t1.noise(b"foo").fill_bytes(&mut s1_bytes[..]);
411 let s1 = Summary {
412 hash: blake3::Hash::from_bytes(s1_bytes),
413 };
414 let s2 = t1.summarize();
415 assert_ne!(s1, s2);
416 }
417
418 #[test]
419 fn test_noise_stream_chunking_doesnt_matter() {
420 let mut s = [0u8; 2 * BLOCK_LEN];
421 Transcript::new(b"test")
422 .noise(b"NOISE")
423 .fill_bytes(&mut s[..]);
424 for i in 0..s.len() {
426 let mut s_prime = [0u8; 2 * BLOCK_LEN];
427 let mut noise = Transcript::new(b"test").noise(b"NOISE");
428 noise.fill_bytes(&mut s_prime[..i]);
429 noise.fill_bytes(&mut s_prime[i..]);
430 assert_eq!(s, s_prime);
431 }
432 }
433
434 #[test]
435 fn test_noise_label_matters() {
436 let mut s1 = [0u8; 32];
437 let mut s2 = [0u8; 32];
438 let t1 = Transcript::new(b"test");
439 t1.noise(b"A").fill_bytes(&mut s1);
440 t1.noise(b"B").fill_bytes(&mut s2);
441 assert_ne!(s1, s2);
442 }
443
444 #[test]
445 fn test_summarize_resume_is_different_than_new() {
446 let s = Transcript::new(b"test").summarize();
447 let s1 = Transcript::new(s.hash.as_bytes()).summarize();
448 let s2 = Transcript::resume(s).summarize();
449 assert_ne!(s1, s2);
450 }
451
452 #[test]
453 fn test_summary_encode_roundtrip() {
454 let s = Transcript::new(b"test").summarize();
455 assert_eq!(&s, &Summary::decode(s.encode()).unwrap());
456 }
457}