1use crate::Hasher;
34use blake3::{
35 CHUNK_LEN, Hash,
36 hazmat::{ChainingValue, HasherExt as _, Mode, merge_subtrees_non_root, merge_subtrees_root},
37};
38use bytes::BufMut;
39use commonware_codec::{Buf, Error as CodecError, FixedArray, FixedSize, Read, ReadExt, Write};
40use commonware_formatting::Hex;
41use commonware_math::algebra::Random;
42use commonware_parallel::Strategy;
43use commonware_utils::{Array, Span, sequence::FixedBytes};
44use core::{
45 cmp::Ordering,
46 fmt::{Debug, Display},
47 ops::Deref,
48};
49use rand_core::CryptoRng;
50use zeroize::Zeroize;
51
52pub type CoreBlake3 = blake3::Hasher;
54
55const DIGEST_LENGTH: usize = blake3::OUT_LEN;
56
57const SUBTREES: usize = 8;
60
61const MIN_SUBTREE_LEN: usize = 16 * CHUNK_LEN;
64
65const MAX_SUBTREE_LEN: usize = 64 * CHUNK_LEN;
67
68const MIN_SPLIT_LEN: usize = SUBTREES * MIN_SUBTREE_LEN;
71
72fn hash_subtrees(parts: &[&[u8]], len: usize, strategy: &impl Strategy) -> Digest {
76 let root = Node {
77 parts,
78 skip: 0,
79 offset: 0,
80 len,
81 task_len: (len / SUBTREES).clamp(MIN_SUBTREE_LEN, MAX_SUBTREE_LEN),
82 };
83 let (left, right) = root.children(strategy);
84 merge_subtrees_root(&left, &right, Mode::Hash).into()
85}
86
87#[derive(Clone, Copy)]
93struct Node<'a> {
94 parts: &'a [&'a [u8]],
95 skip: usize,
96 offset: usize,
97 len: usize,
98 task_len: usize,
99}
100
101impl Node<'_> {
102 fn children(self, strategy: &impl Strategy) -> (ChainingValue, ChainingValue) {
105 let left_len = 1 << (self.len - 1).ilog2();
109 let mut skip = self.skip + left_len;
110 let mut index = 0;
111 while skip >= self.parts[index].len() {
112 skip -= self.parts[index].len();
113 index += 1;
114 }
115 let left = Node {
116 parts: &self.parts[..=index],
117 len: left_len,
118 ..self
119 };
120 let right = Node {
121 parts: &self.parts[index..],
122 skip,
123 offset: self.offset + left_len,
124 len: self.len - left_len,
125 task_len: self.task_len,
126 };
127 strategy.join(
128 || left.chaining_value(strategy),
129 || right.chaining_value(strategy),
130 )
131 }
132
133 fn chaining_value(self, strategy: &impl Strategy) -> ChainingValue {
135 if self.len > self.task_len {
136 let (left, right) = self.children(strategy);
137 return merge_subtrees_non_root(&left, &right, Mode::Hash);
138 }
139
140 let mut hasher = CoreBlake3::new();
142 hasher.set_input_offset(self.offset as u64);
143 let mut skip = self.skip;
144 let mut remaining = self.len;
145 for part in self.parts {
146 let bytes = &part[skip..];
147 let take = bytes.len().min(remaining);
148 hasher.update(&bytes[..take]);
149 remaining -= take;
150 if remaining == 0 {
151 break;
152 }
153 skip = 0;
154 }
155 hasher.finalize_non_root()
156 }
157}
158
159#[derive(Debug, Default)]
161pub struct Blake3 {
162 hasher: CoreBlake3,
163}
164
165impl Hasher for Blake3 {
166 type Digest = Digest;
167
168 fn hash(parts: &[&[u8]]) -> Self::Digest {
169 let mut hasher = CoreBlake3::new();
170 for part in parts {
171 hasher.update(part);
172 }
173 hasher.finalize().into()
174 }
175
176 fn hash_across(parts: &[&[u8]], strategy: &impl Strategy) -> Self::Digest {
177 match parts
181 .iter()
182 .try_fold(0usize, |len, part| len.checked_add(part.len()))
183 {
184 Some(len) if len >= MIN_SPLIT_LEN => strategy.run(
185 len,
186 || Self::hash(parts),
187 || hash_subtrees(parts, len, strategy),
188 ),
189 _ => Self::hash(parts),
190 }
191 }
192
193 fn hash_pair(
194 left: &[&[u8]],
195 right: &[&[u8]],
196 strategy: &impl Strategy,
197 ) -> (Self::Digest, Self::Digest) {
198 (
199 Self::hash_across(left, strategy),
200 Self::hash_across(right, strategy),
201 )
202 }
203
204 fn update(&mut self, message: &[u8]) -> &mut Self {
205 self.hasher.update(message);
206 self
207 }
208
209 fn finalize(mut self) -> (Self, Self::Digest) {
210 let finalized = self.hasher.finalize();
211 self.hasher.reset();
212 let array: [u8; DIGEST_LENGTH] = finalized.into();
213 (self, Self::Digest::from(array))
214 }
215}
216
217#[derive(Clone, Copy, Eq, PartialEq, Hash, FixedArray)]
219#[fixed_array(infallible)]
220#[repr(transparent)]
221pub struct Digest(pub [u8; DIGEST_LENGTH]);
222
223impl Ord for Digest {
224 #[inline]
225 fn cmp(&self, other: &Self) -> Ordering {
226 FixedBytes::new(self.0).cmp(&FixedBytes::new(other.0))
227 }
228}
229
230impl PartialOrd for Digest {
231 #[inline]
232 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
233 Some(self.cmp(other))
234 }
235}
236
237#[cfg(feature = "arbitrary")]
238impl<'a> arbitrary::Arbitrary<'a> for Digest {
239 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
240 let len = u.int_in_range(0..=256)?;
242 let data = u.bytes(len)?;
243 Ok(Blake3::hash(&[data]))
244 }
245}
246
247impl Write for Digest {
248 fn write(&self, buf: &mut impl BufMut) {
249 self.0.write(buf);
250 }
251}
252
253impl Read for Digest {
254 type Cfg = ();
255
256 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
257 let array = <[u8; DIGEST_LENGTH]>::read(buf)?;
258 Ok(Self(array))
259 }
260}
261
262impl FixedSize for Digest {
263 const SIZE: usize = DIGEST_LENGTH;
264}
265
266impl Span for Digest {}
267
268impl Array for Digest {}
269
270impl From<Hash> for Digest {
271 fn from(value: Hash) -> Self {
272 Self(value.into())
273 }
274}
275
276impl AsRef<[u8]> for Digest {
277 fn as_ref(&self) -> &[u8] {
278 &self.0
279 }
280}
281
282impl Deref for Digest {
283 type Target = [u8];
284 fn deref(&self) -> &[u8] {
285 &self.0
286 }
287}
288
289impl Debug for Digest {
290 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
291 write!(f, "{}", Hex(&self.0))
292 }
293}
294
295impl Display for Digest {
296 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
297 write!(f, "{}", Hex(&self.0))
298 }
299}
300
301impl crate::Digest for Digest {
302 const EMPTY: Self = Self([0u8; DIGEST_LENGTH]);
303}
304
305impl Random for Digest {
306 fn random(mut rng: impl CryptoRng) -> Self {
307 let mut array = [0u8; DIGEST_LENGTH];
308 rng.fill_bytes(&mut array);
309 Self(array)
310 }
311}
312
313impl Zeroize for Digest {
314 fn zeroize(&mut self) {
315 self.0.zeroize();
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use commonware_codec::{Copying, DecodeExt, Encode};
323 use commonware_parallel::{
324 Rayon, Sequential,
325 mocks::{Explorer, explore},
326 rayon::{ThreadPoolBuilder, Yield},
327 };
328 use commonware_utils::{NZUsize, TestRng};
329 use rand::Rng as _;
330 use std::sync::{
331 Arc,
332 atomic::{AtomicBool, Ordering::Relaxed},
333 mpsc,
334 };
335
336 const HELLO_DIGEST: [u8; DIGEST_LENGTH] = commonware_formatting::hex!(
337 "d74981efa70a0c880b8d8c1985d075dbcbf679b99a5f9914e5aaf96b831a9e24"
338 );
339
340 fn random(len: usize, seed: u64) -> Vec<u8> {
342 let mut bytes = vec![0; len];
343 TestRng::new(seed).fill_bytes(&mut bytes);
344 bytes
345 }
346
347 #[test]
348 fn test_blake3() {
349 let msg = b"hello world";
350
351 let mut hasher = Blake3::default();
353 hasher.update(msg);
354 let (hasher, digest) = hasher.finalize();
355 assert!(Digest::decode(Copying(&digest)).is_ok());
356 assert_eq!(digest.as_ref(), HELLO_DIGEST);
357
358 let mut hasher = hasher;
360 hasher.update(msg);
361 let (_, digest) = hasher.finalize();
362 assert!(Digest::decode(Copying(&digest)).is_ok());
363 assert_eq!(digest.as_ref(), HELLO_DIGEST);
364
365 let hash = Blake3::hash(&[msg]);
367 assert_eq!(hash.as_ref(), HELLO_DIGEST);
368
369 let hash = Blake3::hash(&[b"hello", b" world"]);
371 assert_eq!(hash.as_ref(), HELLO_DIGEST);
372 }
373
374 #[test]
377 fn test_official_vectors() {
378 const VECTORS: [(usize, [u8; DIGEST_LENGTH]); 3] = [
379 (
380 16384,
381 commonware_formatting::hex!(
382 "f875d6646de28985646f34ee13be9a576fd515f76b5b0a26bb324735041ddde4"
383 ),
384 ),
385 (
386 31744,
387 commonware_formatting::hex!(
388 "62b6960e1a44bcc1eb1a611a8d6235b6b4b78f32e7abc4fb4c6cdcce94895c47"
389 ),
390 ),
391 (
392 102400,
393 commonware_formatting::hex!(
394 "bc3e3d41a1146b069abffad3c0d44860cf664390afce4d9661f7902e7943e085"
395 ),
396 ),
397 ];
398 for (len, expected) in VECTORS {
399 let input: Vec<u8> = (0..len).map(|i| (i % 251) as u8).collect();
401 let mut hasher = Blake3::default();
402 hasher.update(&input);
403 let (_, digest) = hasher.finalize();
404 assert_eq!(digest.as_ref(), expected, "len {len}");
405 }
406 }
407
408 #[test]
413 fn test_hash_long_messages() {
414 let data = random((1 << 20) + 5000, 0);
415 for len in [
416 0,
417 1,
418 CHUNK_LEN,
419 CHUNK_LEN + 1,
420 3 * CHUNK_LEN + 7,
421 MIN_SUBTREE_LEN - 1,
422 MIN_SUBTREE_LEN,
423 MIN_SUBTREE_LEN + 1,
424 MIN_SUBTREE_LEN + CHUNK_LEN,
425 2 * MIN_SUBTREE_LEN - 1,
426 2 * MIN_SUBTREE_LEN,
427 2 * MIN_SUBTREE_LEN + 1,
428 2 * MIN_SUBTREE_LEN + 3 * CHUNK_LEN + 7,
429 3 * MIN_SUBTREE_LEN + 17,
430 MIN_SPLIT_LEN - 1,
431 MIN_SPLIT_LEN,
432 MIN_SPLIT_LEN + MIN_SUBTREE_LEN + 1,
433 3 * MIN_SPLIT_LEN + 17,
434 (1 << 20) + 5000,
435 ] {
436 let message = &data[..len];
437 let expected: Digest = blake3::hash(message).into();
438 let (a, b) = (len / 3, len / 3 + MIN_SUBTREE_LEN.min(len - len / 3));
439 let splits: [Vec<&[u8]>; 4] = [
440 vec![message],
441 vec![&message[..a], &message[a..b], &message[b..]],
442 vec![&[], message, &[]],
443 message.chunks(1000).collect(),
444 ];
445 for parts in &splits {
446 assert_eq!(Blake3::hash(parts), expected, "len={len}");
447 assert_eq!(
448 explore(|strategy| Blake3::hash_across(parts, strategy)),
449 expected,
450 "len={len}"
451 );
452 if len > CHUNK_LEN {
453 assert_eq!(
454 hash_subtrees(parts, len, &Sequential),
455 expected,
456 "len={len}"
457 );
458 }
459 }
460 }
461 }
462
463 #[test]
466 fn test_hash_splits_long_messages() {
467 let pool = Arc::new(ThreadPoolBuilder::new().num_threads(1).build().unwrap());
470 let strategy =
471 Explorer::parallel(&Rayon::with_pool(pool.clone()).with_parallelism(NZUsize!(4)));
472 let data = random(2 * MIN_SPLIT_LEN, 0);
473 for (len, jobs) in [
474 (MIN_SPLIT_LEN - 1, 0),
475 (MIN_SPLIT_LEN, 1),
476 (2 * MIN_SPLIT_LEN, 1),
477 ] {
478 let done = Arc::new(AtomicBool::new(false));
479 let (started, ready) = mpsc::channel();
480 let (sender, receiver) = mpsc::channel();
481 pool.spawn({
482 let done = done.clone();
483 let pool = pool.clone();
484 move || {
485 started.send(()).unwrap();
486 let mut jobs = 0;
487 while !done.load(Relaxed) {
488 if pool.yield_now() == Some(Yield::Executed) {
489 jobs += 1;
490 }
491 }
492 sender.send(jobs).unwrap();
493 }
494 });
495 ready.recv().unwrap();
496
497 let message = &data[..len];
499 let digest = Blake3::hash_across(&[message], &strategy);
500 done.store(true, Relaxed);
501 assert_eq!(digest, blake3::hash(message).into(), "len={len}");
502 assert_eq!(receiver.recv().unwrap(), jobs, "len={len}");
503 }
504 }
505
506 #[test]
507 fn test_blake3_len() {
508 assert_eq!(Digest::SIZE, DIGEST_LENGTH);
509 }
510
511 #[test]
512 fn test_codec() {
513 let msg = b"hello world";
514 let mut hasher = Blake3::default();
515 hasher.update(msg);
516 let (_, digest) = hasher.finalize();
517
518 let encoded = digest.encode();
519 assert_eq!(encoded.len(), DIGEST_LENGTH);
520 assert_eq!(encoded, digest.as_ref());
521
522 let decoded = Digest::decode(encoded).unwrap();
523 assert_eq!(digest, decoded);
524 }
525
526 #[test]
527 fn test_digest_ord() {
528 let a = Digest([0; DIGEST_LENGTH]);
529 let mut b = a;
530 b.0[DIGEST_LENGTH - 1] = 1;
531
532 let mut c = a;
534 c.0[0] = 0x7f;
535 c.0[1..].fill(0xff);
536 let mut d = a;
537 d.0[0] = 0x80;
538 for (a, b, expected) in [
539 (a, a, Ordering::Equal),
540 (a, b, Ordering::Less),
541 (b, a, Ordering::Greater),
542 (c, d, Ordering::Less),
543 (d, c, Ordering::Greater),
544 ] {
545 assert_eq!(a.cmp(&b), expected);
546 assert_eq!(a.partial_cmp(&b), Some(expected));
547 }
548 }
549
550 #[cfg(feature = "arbitrary")]
551 mod conformance {
552 use super::*;
553 use commonware_codec::conformance::CodecConformance;
554
555 commonware_conformance::conformance_tests! {
556 CodecConformance<Digest>,
557 }
558 }
559}