commonware_cryptography/
transcript.rs1use blake3::BLOCK_LEN;
8use bytes::Buf;
9use commonware_codec::{varint::UInt, EncodeSize, Write};
10use rand_core::{
11 impls::{next_u32_via_fill, next_u64_via_fill},
12 CryptoRng, CryptoRngCore, RngCore,
13};
14use zeroize::ZeroizeOnDrop;
15
16#[derive(ZeroizeOnDrop)]
21struct Rng {
22 inner: blake3::OutputReader,
23 buf: [u8; BLOCK_LEN],
24 start: usize,
25}
26
27impl Rng {
28 fn new(inner: blake3::OutputReader) -> Self {
29 Self {
30 inner,
31 buf: [0u8; BLOCK_LEN],
32 start: BLOCK_LEN,
33 }
34 }
35}
36
37impl RngCore for Rng {
38 fn next_u32(&mut self) -> u32 {
39 next_u32_via_fill(self)
40 }
41
42 fn next_u64(&mut self) -> u64 {
43 next_u64_via_fill(self)
44 }
45
46 fn fill_bytes(&mut self, dest: &mut [u8]) {
47 let dest_len = dest.len();
48 let remaining = &self.buf[self.start..];
49 if remaining.len() >= dest_len {
50 dest.copy_from_slice(&remaining[..dest_len]);
51 self.start += dest_len;
52 return;
53 }
54
55 let (start, mut dest) = dest.split_at_mut(remaining.len());
56 start.copy_from_slice(remaining);
57 self.start = BLOCK_LEN;
58
59 while dest.len() >= BLOCK_LEN {
60 let (block, rest) = dest.split_at_mut(BLOCK_LEN);
61 self.inner.fill(block);
62 dest = rest;
63 }
64
65 let dest_len = dest.len();
66 if dest_len > 0 {
67 self.inner.fill(&mut self.buf[..]);
68 dest.copy_from_slice(&self.buf[..dest_len]);
69 self.start = dest_len;
70 }
71 }
72
73 fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
74 self.fill_bytes(dest);
75 Ok(())
76 }
77}
78
79impl CryptoRng for Rng {}
80
81#[repr(u8)]
83enum StartTag {
84 New = 0,
85 Resume = 1,
86 Fork = 2,
87 Noise = 3,
88}
89
90#[derive(ZeroizeOnDrop)]
97pub struct Transcript {
98 hasher: blake3::Hasher,
99 pending: u64,
100}
101
102impl Transcript {
103 fn start(tag: StartTag, summary: Option<Summary>) -> Self {
104 let mut hasher = match summary {
109 Some(s) => blake3::Hasher::new_keyed(s.hash.as_bytes()),
110 None => blake3::Hasher::new(),
111 };
112 hasher.update(&[tag as u8]);
113 Self { hasher, pending: 0 }
114 }
115
116 fn flush(&mut self) {
117 let mut pending_bytes = [0u8; 9];
118 let pending = UInt(self.pending);
119 pending.write(&mut &mut pending_bytes[..]);
120 self.hasher.update(&pending_bytes[..pending.encode_size()]);
121 self.pending = 0;
122 }
123
124 fn do_append(&mut self, data: &[u8]) {
125 self.hasher.update(data);
126 self.pending += data.len() as u64;
127 }
128
129 fn assert_committed(&self) {
130 assert!(self.pending == 0, "transcript had uncommitted data");
131 }
132}
133
134impl Transcript {
135 pub fn new(namespace: &[u8]) -> Self {
146 let mut out = Self::start(StartTag::New, None);
147 out.commit(namespace);
148 out
149 }
150
151 pub fn resume(summary: Summary) -> Self {
162 Self::start(StartTag::Resume, Some(summary))
163 }
164
165 pub fn commit(&mut self, data: impl Buf) -> &mut Self {
185 self.append(data);
186 self.flush();
187 self
188 }
189
190 pub fn append(&mut self, mut data: impl Buf) -> &mut Self {
203 while data.has_remaining() {
204 let chunk = data.chunk();
205 self.do_append(chunk);
206 data.advance(chunk.len());
207 }
208 self
209 }
210
211 pub fn fork(&self, label: &'static [u8]) -> Self {
222 let mut out = Self::start(StartTag::Fork, Some(self.summarize()));
223 out.commit(label);
224 out
225 }
226
227 pub fn noise(&self, label: &'static [u8]) -> impl CryptoRngCore {
236 let mut out = Self::start(StartTag::Noise, Some(self.summarize()));
237 out.commit(label);
238 Rng::new(out.hasher.finalize_xof())
239 }
240
241 pub fn summarize(&self) -> Summary {
251 self.assert_committed();
252 Summary {
253 hash: self.hasher.finalize(),
254 }
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)]
264pub struct Summary {
265 hash: blake3::Hash,
266}
267
268#[cfg(test)]
269mod test {
270 use super::*;
271
272 #[test]
273 fn test_namespace_affects_summary() {
274 let s1 = Transcript::new(b"Test-A").summarize();
275 let s2 = Transcript::new(b"Test-B").summarize();
276 assert_ne!(s1, s2);
277 }
278
279 #[test]
280 fn test_namespace_doesnt_leak_into_data() {
281 let s1 = Transcript::new(b"Test-A").summarize();
282 let s2 = Transcript::new(b"Test-").commit(b"".as_slice()).summarize();
283 assert_ne!(s1, s2);
284 }
285
286 #[test]
287 fn test_commit_separates_data() {
288 let s1 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
289 let s2 = Transcript::new(b"")
290 .commit(b"A".as_slice())
291 .commit(b"B".as_slice())
292 .summarize();
293 assert_ne!(s1, s2);
294 }
295
296 #[test]
297 fn test_append_commit_works() {
298 let s1 = Transcript::new(b"")
299 .append(b"A".as_slice())
300 .commit(b"B".as_slice())
301 .summarize();
302 let s2 = Transcript::new(b"").commit(b"AB".as_slice()).summarize();
303 assert_eq!(s1, s2);
304 }
305
306 #[test]
307 fn test_fork_returns_different_result() {
308 let t1 = Transcript::new(b"");
309 let t2 = t1.fork(b"");
310 assert_ne!(t1.summarize(), t2.summarize());
311 }
312
313 #[test]
314 fn test_fork_label_matters() {
315 let t1 = Transcript::new(b"");
316 let t2 = t1.fork(b"A");
317 let t3 = t2.fork(b"B");
318 assert_ne!(t2.summarize(), t3.summarize());
319 }
320
321 #[test]
322 fn test_noise_and_summarize_are_different() {
323 let t1 = Transcript::new(b"");
324 let mut s1_bytes = [0u8; 32];
325 t1.noise(b"foo").fill_bytes(&mut s1_bytes[..]);
326 let s1 = Summary {
327 hash: blake3::Hash::from_bytes(s1_bytes),
328 };
329 let s2 = t1.summarize();
330 assert_ne!(s1, s2);
331 }
332
333 #[test]
334 fn test_noise_stream_chunking_doesnt_matter() {
335 let mut s = [0u8; 2 * BLOCK_LEN];
336 Transcript::new(b"test")
337 .noise(b"NOISE")
338 .fill_bytes(&mut s[..]);
339 for i in 0..s.len() {
341 let mut s_prime = [0u8; 2 * BLOCK_LEN];
342 let mut noise = Transcript::new(b"test").noise(b"NOISE");
343 noise.fill_bytes(&mut s_prime[..i]);
344 noise.fill_bytes(&mut s_prime[i..]);
345 dbg!(i);
346 assert_eq!(s, s_prime);
347 }
348 }
349
350 #[test]
351 fn test_noise_label_matters() {
352 let mut s1 = [0u8; 32];
353 let mut s2 = [0u8; 32];
354 let t1 = Transcript::new(b"test");
355 t1.noise(b"A").fill_bytes(&mut s1);
356 t1.noise(b"B").fill_bytes(&mut s2);
357 assert_ne!(s1, s2);
358 }
359
360 #[test]
361 fn test_summarize_resume_is_different_than_new() {
362 let s = Transcript::new(b"test").summarize();
363 let s1 = Transcript::new(s.hash.as_bytes()).summarize();
364 let s2 = Transcript::resume(s).summarize();
365 assert_ne!(s1, s2);
366 }
367}