1use digest::Output;
6
7use super::{
8 binary_merkle_tree::HashSuite,
9 blake3_portable::{PortableBlake3ParallelCompression, PortableBlake3ParallelDigest},
10 compress::CompressionFunction,
11};
12
13#[derive(Debug, Clone, Default)]
15pub struct Blake3Compression;
16
17impl CompressionFunction<Output<blake3::Hasher>, 2> for Blake3Compression {
18 fn compress(&self, input: [Output<blake3::Hasher>; 2]) -> Output<blake3::Hasher> {
19 let mut hasher = blake3::Hasher::new();
20 hasher.update(input[0].as_slice());
21 hasher.update(input[1].as_slice());
22 (*hasher.finalize().as_bytes()).into()
23 }
24}
25
26#[derive(Debug, Clone, Default)]
38pub struct Blake3HashSuite;
39
40impl HashSuite for Blake3HashSuite {
41 type LeafHash = blake3::Hasher;
42 type Compression = Blake3Compression;
43 type ParLeafHash = PortableBlake3ParallelDigest<16>;
44 type ParCompression = PortableBlake3ParallelCompression<16>;
45}
46
47#[cfg(test)]
48mod tests {
49 use std::{iter::repeat_with, mem::MaybeUninit};
50
51 use binius_utils::rayon::iter::{IntoParallelRefIterator, ParallelIterator};
52 use rand::{RngExt, SeedableRng, rngs::StdRng};
53
54 use super::*;
55 use crate::{ParallelDigest, parallel_digest::ParallelDigestAdapter};
56
57 #[test]
59 fn test_blake3_compression_matches_reference() {
60 let mut rng = StdRng::seed_from_u64(0);
61 let left: [u8; 32] = rng.random();
62 let right: [u8; 32] = rng.random();
63
64 let compressed = Blake3Compression.compress([left.into(), right.into()]);
65
66 let mut concatenated = [0u8; 64];
67 concatenated[..32].copy_from_slice(&left);
68 concatenated[32..].copy_from_slice(&right);
69 let expected = blake3::hash(&concatenated);
70
71 assert_eq!(compressed.as_slice(), expected.as_bytes());
72 }
73
74 #[test]
76 fn test_parallel_blake3_matches_serial() {
77 let mut rng = StdRng::seed_from_u64(0);
78 let n_leaves = 50;
79 let leaves: Vec<Vec<u128>> = (0..n_leaves)
81 .map(|_| (0..4).map(|_| rng.random::<u128>()).collect())
82 .collect();
83
84 let digest = <ParallelDigestAdapter<blake3::Hasher> as ParallelDigest>::new();
85 let mut results = repeat_with(MaybeUninit::<Output<blake3::Hasher>>::uninit)
86 .take(n_leaves)
87 .collect::<Vec<_>>();
88 digest.digest(leaves.par_iter().map(|leaf| leaf.iter().copied()), &mut results);
89
90 for (result, leaf) in results.into_iter().zip(&leaves) {
91 let mut bytes = Vec::new();
92 for &item in leaf {
93 bytes.extend_from_slice(&item.to_le_bytes());
94 }
95 let expected = blake3::hash(&bytes);
96 assert_eq!(unsafe { result.assume_init() }.as_slice(), expected.as_bytes());
97 }
98 }
99
100 #[test]
101 fn test_portable_leaf_hash_matches_scalar_reference() {
102 let mut rng = StdRng::seed_from_u64(1);
106 let n_leaves = 50;
107
108 let mut check = |leaf_len: usize| {
110 let leaves: Vec<Vec<u8>> = (0..n_leaves)
111 .map(|_| (0..leaf_len).map(|_| rng.random::<u8>()).collect())
112 .collect();
113
114 let mut scalar = repeat_with(MaybeUninit::<Output<blake3::Hasher>>::uninit)
116 .take(n_leaves)
117 .collect::<Vec<_>>();
118 ParallelDigestAdapter::<blake3::Hasher>::default().digest_with_const_len(
119 leaf_len,
120 leaves.par_iter().map(|leaf| leaf.iter().copied()),
121 &mut scalar,
122 );
123
124 let mut portable = repeat_with(MaybeUninit::<Output<blake3::Hasher>>::uninit)
126 .take(n_leaves)
127 .collect::<Vec<_>>();
128 <Blake3HashSuite as HashSuite>::ParLeafHash::default().digest_with_const_len(
129 leaf_len,
130 leaves.par_iter().map(|leaf| leaf.iter().copied()),
131 &mut portable,
132 );
133
134 for ((s, p), leaf) in scalar.into_iter().zip(portable).zip(&leaves) {
136 let expected = blake3::hash(leaf);
137 let (s, p) = unsafe { (s.assume_init(), p.assume_init()) };
138 assert_eq!(s.as_slice(), expected.as_bytes(), "scalar, leaf_len {leaf_len}");
139 assert_eq!(p.as_slice(), expected.as_bytes(), "portable, leaf_len {leaf_len}");
140 }
141 };
142
143 for leaf_len in [0, 1, 63, 100, 1000, 1024, 1025, 4096] {
147 check(leaf_len);
148 }
149 }
150}