1use alloc::vec::Vec;
2
3use p3_field::Field;
4
5use crate::CryptographicHasher;
6
7const ROW_BYTES_SCRATCH: usize = 8 * 1024;
16
17#[derive(Copy, Clone, Debug)]
24pub struct SerializingHasher<Inner> {
25 inner: Inner,
26}
27
28impl<Inner> SerializingHasher<Inner> {
29 pub const fn new(inner: Inner) -> Self {
30 Self { inner }
31 }
32}
33
34impl<F, Inner, const N: usize> CryptographicHasher<F, [u8; N]> for SerializingHasher<Inner>
35where
36 F: Field,
37 Inner: CryptographicHasher<u8, [u8; N]>,
38{
39 const LANES: usize = <Inner as CryptographicHasher<u8, [u8; N]>>::LANES;
46
47 fn hash_iter<I>(&self, input: I) -> [u8; N]
48 where
49 I: IntoIterator<Item = F>,
50 {
51 self.inner.hash_iter(F::into_byte_stream(input))
52 }
53
54 fn hash_many(&self, input: &[F], out: &mut [[u8; N]]) {
55 if out.is_empty() {
57 return;
58 }
59
60 assert!(
62 input.len().is_multiple_of(out.len()),
63 "input length ({}) must be a whole multiple of the digest count ({})",
64 input.len(),
65 out.len()
66 );
67 let row_len = input.len() / out.len();
68 let row_bytes = row_len * F::NUM_BYTES;
69
70 if row_len == 0 {
72 for digest in out.iter_mut() {
73 *digest = self.hash_iter(core::iter::empty::<F>());
74 }
75 return;
76 }
77
78 let lanes = Inner::LANES.max(1);
91 let rows_per_group = (ROW_BYTES_SCRATCH / row_bytes / lanes).max(1) * lanes;
92
93 let mut scratch: Vec<u8> = Vec::with_capacity(rows_per_group.min(out.len()) * row_bytes);
96
97 for (rows, digests) in input
98 .chunks(rows_per_group * row_len)
99 .zip(out.chunks_mut(rows_per_group))
100 {
101 scratch.clear();
107 for (index, row) in rows.chunks(row_len).enumerate() {
108 scratch.extend(F::into_byte_stream(row.iter().copied()));
109 assert_eq!(
110 scratch.len(),
111 (index + 1) * row_bytes,
112 "field serialization must be its declared width of {} bytes per element",
113 F::NUM_BYTES
114 );
115 }
116
117 self.inner.hash_many(&scratch, digests);
118 }
119 }
120}
121
122impl<F, Inner, const N: usize> CryptographicHasher<F, [u32; N]> for SerializingHasher<Inner>
123where
124 F: Field,
125 Inner: CryptographicHasher<u32, [u32; N]>,
126{
127 fn hash_iter<I>(&self, input: I) -> [u32; N]
128 where
129 I: IntoIterator<Item = F>,
130 {
131 self.inner.hash_iter(F::into_u32_stream(input))
132 }
133}
134
135impl<F, Inner, const N: usize> CryptographicHasher<F, [u64; N]> for SerializingHasher<Inner>
136where
137 F: Field,
138 Inner: CryptographicHasher<u64, [u64; N]>,
139{
140 fn hash_iter<I>(&self, input: I) -> [u64; N]
141 where
142 I: IntoIterator<Item = F>,
143 {
144 self.inner.hash_iter(F::into_u64_stream(input))
145 }
146}
147
148impl<F, Inner, const N: usize, const M: usize> CryptographicHasher<[F; M], [[u8; M]; N]>
149 for SerializingHasher<Inner>
150where
151 F: Field,
152 Inner: CryptographicHasher<[u8; M], [[u8; M]; N]>,
153{
154 fn hash_iter<I>(&self, input: I) -> [[u8; M]; N]
155 where
156 I: IntoIterator<Item = [F; M]>,
157 {
158 self.inner.hash_iter(F::into_parallel_byte_streams(input))
159 }
160}
161
162impl<F, Inner, const N: usize, const M: usize> CryptographicHasher<[F; M], [[u32; M]; N]>
163 for SerializingHasher<Inner>
164where
165 F: Field,
166 Inner: CryptographicHasher<[u32; M], [[u32; M]; N]>,
167{
168 fn hash_iter<I>(&self, input: I) -> [[u32; M]; N]
169 where
170 I: IntoIterator<Item = [F; M]>,
171 {
172 self.inner.hash_iter(F::into_parallel_u32_streams(input))
173 }
174}
175
176impl<F, Inner, const N: usize, const M: usize> CryptographicHasher<[F; M], [[u64; M]; N]>
177 for SerializingHasher<Inner>
178where
179 F: Field,
180 Inner: CryptographicHasher<[u64; M], [[u64; M]; N]>,
181{
182 const PREFER_CONTIGUOUS_INPUT: bool = M > 1 && F::NUM_BYTES == 4;
185
186 fn hash_iter<I>(&self, input: I) -> [[u64; M]; N]
187 where
188 I: IntoIterator<Item = [F; M]>,
189 {
190 self.inner.hash_iter(F::into_parallel_u64_streams(input))
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use alloc::rc::Rc;
197 use alloc::vec;
198 use alloc::vec::Vec;
199 use core::array;
200 use core::cell::RefCell;
201
202 use p3_field::PrimeCharacteristicRing;
203 use p3_koala_bear::KoalaBear;
204
205 use crate::{CryptographicHasher, SerializingHasher};
206
207 #[derive(Clone)]
208 struct MockHasher;
209
210 impl CryptographicHasher<u8, [u8; 4]> for MockHasher {
211 fn hash_iter<I: IntoIterator<Item = u8>>(&self, iter: I) -> [u8; 4] {
212 let sum: u8 = iter.into_iter().fold(0, |acc, x| acc.wrapping_add(x));
213 [sum; 4]
215 }
216 }
217
218 impl CryptographicHasher<[u8; 4], [[u8; 4]; 4]> for MockHasher {
219 fn hash_iter<I: IntoIterator<Item = [u8; 4]>>(&self, iter: I) -> [[u8; 4]; 4] {
220 let sum: [u8; 4] = iter.into_iter().fold([0, 0, 0, 0], |acc, x| {
221 [
222 acc[0].wrapping_add(x[0]),
223 acc[1].wrapping_add(x[1]),
224 acc[2].wrapping_add(x[2]),
225 acc[3].wrapping_add(x[3]),
226 ]
227 });
228 [sum; 4]
230 }
231 }
232
233 impl CryptographicHasher<u32, [u32; 4]> for MockHasher {
234 fn hash_iter<I: IntoIterator<Item = u32>>(&self, iter: I) -> [u32; 4] {
235 let sum: u32 = iter.into_iter().fold(0, |acc, x| acc.wrapping_add(x));
236 [sum; 4]
238 }
239 }
240
241 impl CryptographicHasher<[u32; 4], [[u32; 4]; 4]> for MockHasher {
242 fn hash_iter<I: IntoIterator<Item = [u32; 4]>>(&self, iter: I) -> [[u32; 4]; 4] {
243 let sum: [u32; 4] = iter.into_iter().fold([0, 0, 0, 0], |acc, x| {
244 [
245 acc[0].wrapping_add(x[0]),
246 acc[1].wrapping_add(x[1]),
247 acc[2].wrapping_add(x[2]),
248 acc[3].wrapping_add(x[3]),
249 ]
250 });
251 [sum; 4]
253 }
254 }
255
256 impl CryptographicHasher<u64, [u64; 4]> for MockHasher {
257 fn hash_iter<I: IntoIterator<Item = u64>>(&self, iter: I) -> [u64; 4] {
258 let sum: u64 = iter.into_iter().fold(0, |acc, x| acc.wrapping_add(x));
259 [sum; 4]
261 }
262 }
263
264 impl CryptographicHasher<[u64; 4], [[u64; 4]; 4]> for MockHasher {
265 fn hash_iter<I: IntoIterator<Item = [u64; 4]>>(&self, iter: I) -> [[u64; 4]; 4] {
266 let sum: [u64; 4] = iter.into_iter().fold([0, 0, 0, 0], |acc, x| {
267 [
268 acc[0].wrapping_add(x[0]),
269 acc[1].wrapping_add(x[1]),
270 acc[2].wrapping_add(x[2]),
271 acc[3].wrapping_add(x[3]),
272 ]
273 });
274 [sum; 4]
276 }
277 }
278
279 #[test]
280 fn test_parallel_hashers() {
281 let mock_hash = MockHasher {};
282 let hasher = SerializingHasher::new(mock_hash);
283 let input: [KoalaBear; 256] = KoalaBear::new_array(array::from_fn(|x| x as u32));
284
285 let parallel_input: [[KoalaBear; 4]; 64] = unsafe { core::mem::transmute(input) };
286 let unzipped_input: [[KoalaBear; 64]; 4] = array::from_fn(|i| parallel_input.map(|x| x[i]));
287
288 let u8_output_parallel: [[u8; 4]; 4] = hasher.hash_iter(parallel_input);
289 let u8_output_individual: [[u8; 4]; 4] = unzipped_input.map(|x| hasher.hash_iter(x));
290 let u8_output_individual_transposed =
291 array::from_fn(|i| u8_output_individual.map(|x| x[i]));
292
293 let u32_output_parallel: [[u32; 4]; 4] = hasher.hash_iter(parallel_input);
294 let u32_output_individual: [[u32; 4]; 4] = unzipped_input.map(|x| hasher.hash_iter(x));
295 let u32_output_individual_transposed =
296 array::from_fn(|i| u32_output_individual.map(|x| x[i]));
297
298 let u64_output_parallel: [[u64; 4]; 4] = hasher.hash_iter(parallel_input);
299 let u64_output_individual: [[u64; 4]; 4] = unzipped_input.map(|x| hasher.hash_iter(x));
300 let u64_output_individual_transposed =
301 array::from_fn(|i| u64_output_individual.map(|x| x[i]));
302
303 assert_eq!(u8_output_parallel, u8_output_individual_transposed);
304 assert_eq!(u32_output_parallel, u32_output_individual_transposed);
305 assert_eq!(u64_output_parallel, u64_output_individual_transposed);
306 }
307
308 #[derive(Clone)]
313 struct LaneRecorder {
314 calls: Rc<RefCell<Vec<usize>>>,
315 }
316
317 impl LaneRecorder {
318 fn new() -> (Self, Rc<RefCell<Vec<usize>>>) {
319 let calls = Rc::new(RefCell::new(Vec::new()));
320 (
321 Self {
322 calls: Rc::clone(&calls),
323 },
324 calls,
325 )
326 }
327 }
328
329 impl CryptographicHasher<u8, [u8; 4]> for LaneRecorder {
330 const LANES: usize = 3;
331
332 fn hash_iter<I: IntoIterator<Item = u8>>(&self, iter: I) -> [u8; 4] {
333 MockHasher.hash_iter(iter)
334 }
335
336 fn hash_many(&self, input: &[u8], out: &mut [[u8; 4]]) {
337 if out.is_empty() {
338 return;
339 }
340 self.calls.borrow_mut().push(out.len());
341
342 let len = input.len() / out.len();
343 for (digest, message) in out.iter_mut().zip(input.chunks_exact(len)) {
344 *digest = self.hash_iter(message.iter().copied());
345 }
346 }
347 }
348
349 fn rows_of(rows: usize, row_len: usize) -> Vec<KoalaBear> {
351 (0..rows * row_len)
352 .map(|i| KoalaBear::from_u32(i as u32))
353 .collect()
354 }
355
356 fn digests_one_by_one<H>(
358 hasher: &SerializingHasher<H>,
359 input: &[KoalaBear],
360 row_len: usize,
361 ) -> Vec<[u8; 4]>
362 where
363 H: CryptographicHasher<u8, [u8; 4]>,
364 {
365 input
366 .chunks(row_len)
367 .map(|row| hasher.hash_iter(row.iter().copied()))
368 .collect()
369 }
370
371 #[test]
372 fn hash_many_matches_the_unbatched_digests() {
373 let hasher = SerializingHasher::new(MockHasher);
374
375 for row_len in [1, 3, 7, 100, 2048, 2049, 2100, 5000] {
378 for rows in [1, 2, 3, 4, 7] {
379 let input = rows_of(rows, row_len);
380 let mut batched = vec![[0u8; 4]; rows];
381 hasher.hash_many(&input, &mut batched);
382
383 assert_eq!(
384 batched,
385 digests_one_by_one(&hasher, &input, row_len),
386 "rows {rows} of width {row_len}"
387 );
388 }
389 }
390 }
391
392 #[test]
393 fn hash_many_hands_the_inner_hasher_whole_lane_groups() {
394 let (inner, calls) = LaneRecorder::new();
395 let hasher = SerializingHasher::new(inner);
396
397 let input = rows_of(40, 100);
400 let mut out = vec![[0u8; 4]; 40];
401 hasher.hash_many(&input, &mut out);
402
403 assert_eq!(*calls.borrow(), vec![18, 18, 4]);
405 assert_eq!(out, digests_one_by_one(&hasher, &input, 100));
406 }
407
408 #[test]
409 fn hash_many_still_fills_the_lanes_when_a_lane_group_overshoots_the_target() {
410 let (inner, calls) = LaneRecorder::new();
411 let hasher = SerializingHasher::new(inner);
412
413 let input = rows_of(6, 700);
417 let mut out = vec![[0u8; 4]; 6];
418 hasher.hash_many(&input, &mut out);
419
420 assert_eq!(*calls.borrow(), vec![3, 3]);
421 assert_eq!(out, digests_one_by_one(&hasher, &input, 700));
422 }
423
424 #[test]
425 fn hash_many_still_groups_by_one_lane_group_past_the_row_budget() {
426 let (inner, calls) = LaneRecorder::new();
427 let hasher = SerializingHasher::new(inner);
428
429 let input = rows_of(4, 2049);
432 let mut out = vec![[0u8; 4]; 4];
433 hasher.hash_many(&input, &mut out);
434
435 assert_eq!(*calls.borrow(), vec![3, 1]);
436 assert_eq!(out, digests_one_by_one(&hasher, &input, 2049));
437 }
438
439 #[test]
440 fn hash_many_still_groups_by_one_lane_group_far_above_the_row_budget() {
441 let (inner, calls) = LaneRecorder::new();
442 let hasher = SerializingHasher::new(inner);
443
444 let input = rows_of(7, 5000);
447 let mut out = vec![[0u8; 4]; 7];
448 hasher.hash_many(&input, &mut out);
449
450 assert_eq!(*calls.borrow(), vec![3, 3, 1]);
451 assert_eq!(out, digests_one_by_one(&hasher, &input, 5000));
452 }
453
454 #[test]
455 fn hash_many_of_width_zero_rows_hashes_the_empty_message() {
456 let hasher = SerializingHasher::new(MockHasher);
457
458 let mut out = vec![[1u8; 4]; 3];
460 hasher.hash_many(&[] as &[KoalaBear], &mut out);
461
462 let empty: [u8; 4] = hasher.hash_iter(core::iter::empty::<KoalaBear>());
463 assert_eq!(out, vec![empty; 3]);
464 }
465
466 #[test]
467 fn hash_many_reads_nothing_when_no_digests_are_requested() {
468 let hasher = SerializingHasher::new(MockHasher);
469
470 let mut out: [[u8; 4]; 0] = [];
473 hasher.hash_many(&rows_of(3, 5), &mut out);
474 }
475
476 #[test]
477 #[should_panic(expected = "must be a whole multiple")]
478 fn hash_many_rejects_ragged_input() {
479 let hasher = SerializingHasher::new(MockHasher);
480
481 let mut out = [[0u8; 4]; 2];
483 hasher.hash_many(&rows_of(5, 1), &mut out);
484 }
485}