1use std::mem::MaybeUninit;
7
8use binius_utils::{
9 FixedSizeSerializeBytes, SerializeBytes,
10 rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator},
11};
12use bytemuck::{bytes_of_mut, must_cast};
13use digest::Digest;
14use sha2::{Sha256, block_api::compress256, digest::Output};
15
16use super::{
17 binary_merkle_tree::HashSuite,
18 compress::CompressionFunction,
19 parallel_compression::ParallelPseudoCompression,
20 parallel_digest::{ParallelDigest, ParallelDigestAdapter},
21 sha256_x4::compress256_x4,
22};
23
24#[cfg(all(target_arch = "aarch64", target_feature = "sha2"))]
32fn digest_with_const_len_x4<I: IntoIterator<Item: FixedSizeSerializeBytes>>(
33 n_items_per_input: usize,
34 source: impl IndexedParallelIterator<Item = I>,
35 out: &mut [MaybeUninit<Output<Sha256>>],
36) {
37 use binius_utils::rayon::slice::{ParallelSlice, ParallelSliceMut};
38
39 let leaf_len = n_items_per_input * <I::Item as FixedSizeSerializeBytes>::BYTE_SIZE;
40
41 let mut leaf_bytes = vec![0u8; out.len() * leaf_len];
43 source
44 .zip(leaf_bytes.par_chunks_mut(leaf_len))
45 .for_each(|(items, dst)| {
46 let mut cursor = &mut dst[..];
47 for item in items {
48 item.serialize(&mut cursor)
49 .expect("pre-condition: items serialize without error");
50 }
51 debug_assert!(cursor.is_empty(), "pre-condition: each leaf serializes to leaf_len");
52 });
53
54 out.par_chunks_mut(4)
56 .zip(leaf_bytes.par_chunks(4 * leaf_len))
57 .for_each(|(out4, bytes4)| {
58 let inputs: [&[u8]; 4] =
59 std::array::from_fn(|i| &bytes4[i * leaf_len..(i + 1) * leaf_len]);
60 let digests = crate::sha256_x4::sha256_x4(inputs);
61 for (slot, digest) in out4.iter_mut().zip(digests) {
62 let mut hash = Output::<Sha256>::default();
63 hash.copy_from_slice(&digest);
64 slot.write(hash);
65 }
66 });
67}
68
69const SHA256_IV: [u32; 8] = [
71 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
72];
73
74const SINGLE_BLOCK_MAX_LEN: usize = 64 - 1 - 8;
77
78#[derive(Debug, Clone)]
80pub struct Sha256Compression {
81 initial_state: [u32; 8],
82}
83
84impl Default for Sha256Compression {
85 fn default() -> Self {
86 let initial_state_bytes = Sha256::digest(b"BINIUS SHA-256 COMPRESS");
87 let mut initial_state = [0u32; 8];
88 bytes_of_mut(&mut initial_state).copy_from_slice(&initial_state_bytes);
89 Self { initial_state }
90 }
91}
92
93impl CompressionFunction<Output<Sha256>, 2> for Sha256Compression {
94 fn compress(&self, input: [Output<Sha256>; 2]) -> Output<Sha256> {
95 let mut ret = self.initial_state;
96 let mut block = [0u8; 64];
97 block[..32].copy_from_slice(input[0].as_slice());
98 block[32..].copy_from_slice(input[1].as_slice());
99 compress256(&mut ret, &[block]);
100 must_cast::<[u32; 8], [u8; 32]>(ret).into()
101 }
102}
103
104#[derive(Debug, Clone, Default)]
106pub struct Sha256HashSuite;
107
108impl HashSuite for Sha256HashSuite {
109 type LeafHash = Sha256;
110 type Compression = Sha256Compression;
111 type ParLeafHash = ParallelSha256Digest;
112 type ParCompression = ParallelSha256Compression;
113}
114
115#[derive(Debug, Clone, Default)]
121pub struct ParallelSha256Compression {
122 compression: Sha256Compression,
124}
125
126impl ParallelPseudoCompression<Output<Sha256>, 2> for ParallelSha256Compression {
127 type Compression = Sha256Compression;
128
129 fn compression(&self) -> &Self::Compression {
130 &self.compression
131 }
132
133 fn parallel_compress(
134 &self,
135 inputs: &[Output<Sha256>],
136 out: &mut [MaybeUninit<Output<Sha256>>],
137 ) {
138 use binius_utils::rayon::slice::{ParallelSlice, ParallelSliceMut};
139
140 assert_eq!(inputs.len(), 2 * out.len(), "Input length must be N * output length");
141
142 let n_groups = out.len() / 4;
147 let (input_groups, input_tail) = inputs.split_at(8 * n_groups);
148 let (out_groups, out_tail) = out.split_at_mut(4 * n_groups);
149
150 input_groups
153 .par_chunks_exact(8)
154 .zip(out_groups.par_chunks_exact_mut(4))
155 .for_each(|(pairs, out4)| {
156 let mut blocks = [[0u8; 64]; 4];
158 for (block, pair) in blocks.iter_mut().zip(pairs.chunks_exact(2)) {
159 block[..32].copy_from_slice(&pair[0]);
160 block[32..].copy_from_slice(&pair[1]);
161 }
162
163 let mut states = [self.compression.initial_state; 4];
165 compress256_x4(&mut states, [&blocks[0], &blocks[1], &blocks[2], &blocks[3]]);
166
167 for (slot, state) in out4.iter_mut().zip(states) {
169 slot.write(must_cast::<[u32; 8], [u8; 32]>(state).into());
170 }
171 });
172
173 for (slot, pair) in out_tail.iter_mut().zip(input_tail.chunks_exact(2)) {
175 slot.write(self.compression.compress([pair[0], pair[1]]));
176 }
177 }
178}
179
180#[derive(Debug, Clone, Default)]
191pub struct ParallelSha256Digest;
192
193impl ParallelDigest for ParallelSha256Digest {
194 type Digest = Sha256;
195
196 fn new() -> Self {
197 Self
198 }
199
200 fn digest<I: IntoIterator<Item: SerializeBytes>>(
201 &self,
202 source: impl IndexedParallelIterator<Item = I>,
203 out: &mut [MaybeUninit<Output<Sha256>>],
204 ) {
205 ParallelDigestAdapter::<Sha256>::new().digest(source, out);
206 }
207
208 fn digest_with_const_len<I: IntoIterator<Item: FixedSizeSerializeBytes>>(
209 &self,
210 n_items_per_input: usize,
211 source: impl IndexedParallelIterator<Item = I>,
212 out: &mut [MaybeUninit<Output<Sha256>>],
213 ) {
214 #[cfg(all(target_arch = "aarch64", target_feature = "sha2"))]
217 if out.len() >= 4 && out.len().is_multiple_of(4) {
218 digest_with_const_len_x4(n_items_per_input, source, out);
219 return;
220 }
221
222 let leaf_len = n_items_per_input * <I::Item as FixedSizeSerializeBytes>::BYTE_SIZE;
223 if leaf_len > SINGLE_BLOCK_MAX_LEN {
224 self.digest(source, out);
225 return;
226 }
227
228 let mut block_template = [0u8; 64];
233 block_template[leaf_len] = 0x80;
234 block_template[56..64].copy_from_slice(&((leaf_len as u64) * 8).to_be_bytes());
235
236 source
237 .zip(out.par_iter_mut())
238 .for_each_with(block_template, |block, (items, out)| {
239 let mut cursor = &mut block[..leaf_len];
241 let mut n_items = 0;
242 for item in items {
243 item.serialize(&mut cursor)
244 .expect("pre-condition: items must serialize without error");
245 n_items += 1;
246 }
247 debug_assert_eq!(n_items, n_items_per_input);
248 debug_assert!(cursor.is_empty(), "pre-condition: each leaf serializes to leaf_len");
249
250 let mut state = SHA256_IV;
251 compress256(&mut state, std::slice::from_ref(&*block));
252
253 let mut digest = Output::<Sha256>::default();
255 for (chunk, word) in digest.chunks_exact_mut(4).zip(state) {
256 chunk.copy_from_slice(&word.to_be_bytes());
257 }
258 out.write(digest);
259 });
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use std::iter::repeat_with;
266
267 use binius_utils::rayon::iter::{IntoParallelRefIterator, ParallelIterator};
268 use rand::{Rng, RngExt, SeedableRng, rngs::StdRng};
269
270 use super::*;
271 use crate::parallel_compression::ParallelCompressionAdaptor;
272
273 #[test]
274 fn test_parallel_sha256_compression_matches_adaptor() {
275 let mut rng = StdRng::seed_from_u64(0);
276
277 for n_nodes in [1usize, 2, 3, 4, 5, 7, 8, 64] {
286 let inputs: Vec<Output<Sha256>> = repeat_with(|| {
288 let mut digest = Output::<Sha256>::default();
289 rng.fill_bytes(&mut digest);
290 digest
291 })
292 .take(2 * n_nodes)
293 .collect();
294
295 let grouped = ParallelSha256Compression::default();
297 let mut got = repeat_with(MaybeUninit::<Output<Sha256>>::uninit)
298 .take(n_nodes)
299 .collect::<Vec<_>>();
300 grouped.parallel_compress(&inputs, &mut got);
301
302 let adaptor = ParallelCompressionAdaptor::new(Sha256Compression::default());
304 let mut want = repeat_with(MaybeUninit::<Output<Sha256>>::uninit)
305 .take(n_nodes)
306 .collect::<Vec<_>>();
307 adaptor.parallel_compress(&inputs, &mut want);
308
309 for (i, (got_i, want_i)) in got.iter().zip(&want).enumerate() {
310 let (got_i, want_i) =
312 unsafe { (got_i.assume_init_ref(), want_i.assume_init_ref()) };
313 assert_eq!(got_i, want_i, "mismatch at node {i} of {n_nodes}");
314 }
315 }
316 }
317
318 #[test]
321 fn test_parallel_sha256_matches_serial() {
322 let mut rng = StdRng::seed_from_u64(0);
323 for n_items_per_input in [1, 2, 3, 4] {
326 let n_leaves = 50;
327 let leaves: Vec<Vec<u128>> = (0..n_leaves)
328 .map(|_| {
329 (0..n_items_per_input)
330 .map(|_| rng.random::<u128>())
331 .collect()
332 })
333 .collect();
334
335 let digest = ParallelSha256Digest::new();
336 let mut results = repeat_with(MaybeUninit::<Output<Sha256>>::uninit)
337 .take(n_leaves)
338 .collect::<Vec<_>>();
339 digest.digest_with_const_len(
340 n_items_per_input,
341 leaves.par_iter().map(|leaf| leaf.iter().copied()),
342 &mut results,
343 );
344
345 for (result, leaf) in results.into_iter().zip(&leaves) {
346 let mut bytes = Vec::new();
347 for &item in leaf {
348 bytes.extend_from_slice(&item.to_le_bytes());
349 }
350 assert_eq!(unsafe { result.assume_init() }, <Sha256 as Digest>::digest(&bytes));
351 }
352 }
353 }
354}