1use crate::hasher::CryptographicHasher;
2use crate::permutation::CryptographicPermutation;
3
4pub trait PseudoCompressionFunction<T, const N: usize>: Clone {
10 const LANES: usize = 1;
17
18 fn compress(&self, input: [T; N]) -> T;
19
20 fn compress_many(&self, inputs: &[[T; N]], out: &mut [T])
34 where
35 T: Clone,
36 {
37 assert_eq!(
39 inputs.len(),
40 out.len(),
41 "group count ({}) must equal the output count ({})",
42 inputs.len(),
43 out.len()
44 );
45
46 for (output, group) in out.iter_mut().zip(inputs) {
48 *output = self.compress(group.clone());
49 }
50 }
51}
52
53pub trait CompressionFunction<T, const N: usize>: PseudoCompressionFunction<T, N> {}
55
56#[derive(Clone, Debug)]
57pub struct TruncatedPermutation<InnerP, const N: usize, const CHUNK: usize, const WIDTH: usize> {
58 inner_permutation: InnerP,
59}
60
61impl<InnerP, const N: usize, const CHUNK: usize, const WIDTH: usize>
62 TruncatedPermutation<InnerP, N, CHUNK, WIDTH>
63{
64 pub const fn new(inner_permutation: InnerP) -> Self {
65 const {
66 assert!(N > 0);
67 assert!(CHUNK > 0);
68 assert!(CHUNK * N <= WIDTH);
69 }
70 Self { inner_permutation }
71 }
72}
73
74impl<T, InnerP, const N: usize, const CHUNK: usize, const WIDTH: usize>
75 PseudoCompressionFunction<[T; CHUNK], N> for TruncatedPermutation<InnerP, N, CHUNK, WIDTH>
76where
77 T: Copy + Default,
78 InnerP: CryptographicPermutation<[T; WIDTH]>,
79{
80 fn compress(&self, input: [[T; CHUNK]; N]) -> [T; CHUNK] {
81 let mut pre = [T::default(); WIDTH];
82 for i in 0..N {
83 pre[i * CHUNK..(i + 1) * CHUNK].copy_from_slice(&input[i]);
84 }
85 let post = self.inner_permutation.permute(pre);
86 post[..CHUNK].try_into().unwrap()
87 }
88}
89
90#[derive(Clone, Debug)]
91pub struct CompressionFunctionFromHasher<H, const N: usize, const CHUNK: usize> {
92 hasher: H,
93}
94
95impl<H, const N: usize, const CHUNK: usize> CompressionFunctionFromHasher<H, N, CHUNK> {
96 pub const fn new(hasher: H) -> Self {
97 Self { hasher }
98 }
99}
100
101impl<T, H, const N: usize, const CHUNK: usize> PseudoCompressionFunction<[T; CHUNK], N>
102 for CompressionFunctionFromHasher<H, N, CHUNK>
103where
104 T: Clone,
105 H: CryptographicHasher<T, [T; CHUNK]>,
106{
107 const LANES: usize = <H as CryptographicHasher<T, [T; CHUNK]>>::LANES;
108
109 fn compress(&self, input: [[T; CHUNK]; N]) -> [T; CHUNK] {
110 self.hasher.hash_iter(input.into_iter().flatten())
111 }
112
113 fn compress_many(&self, inputs: &[[[T; CHUNK]; N]], out: &mut [[T; CHUNK]]) {
114 assert_eq!(
116 inputs.len(),
117 out.len(),
118 "group count ({}) must equal the output count ({})",
119 inputs.len(),
120 out.len()
121 );
122
123 let messages = inputs.as_flattened().as_flattened();
131
132 self.hasher.hash_many(messages, out);
135 }
136}
137
138impl<T, H, const N: usize, const CHUNK: usize> CompressionFunction<[T; CHUNK], N>
139 for CompressionFunctionFromHasher<H, N, CHUNK>
140where
141 T: Clone,
142 H: CryptographicHasher<T, [T; CHUNK]>,
143{
144}
145
146#[cfg(test)]
147mod tests {
148 use alloc::vec;
149 use alloc::vec::Vec;
150 use core::array;
151
152 use super::*;
153 use crate::Permutation;
154
155 #[derive(Clone)]
156 struct MockPermutation;
157
158 impl<T, const WIDTH: usize> Permutation<[T; WIDTH]> for MockPermutation
159 where
160 T: Copy + core::ops::Add<Output = T> + Default,
161 {
162 fn permute_mut(&self, input: &mut [T; WIDTH]) {
163 let sum: T = input.iter().copied().fold(T::default(), |acc, x| acc + x);
164 *input = [sum; WIDTH];
166 }
167 }
168
169 impl<T, const WIDTH: usize> CryptographicPermutation<[T; WIDTH]> for MockPermutation where
170 T: Copy + core::ops::Add<Output = T> + Default
171 {
172 }
173
174 #[derive(Clone)]
175 struct MockHasher;
176
177 impl<const CHUNK: usize> CryptographicHasher<u64, [u64; CHUNK]> for MockHasher {
178 fn hash_iter<I: IntoIterator<Item = u64>>(&self, iter: I) -> [u64; CHUNK] {
179 let sum: u64 = iter.into_iter().sum();
180 [sum; CHUNK]
182 }
183 }
184
185 #[test]
186 fn test_truncated_permutation_compress() {
187 const N: usize = 2;
188 const CHUNK: usize = 4;
189 const WIDTH: usize = 8;
190
191 let permutation = MockPermutation;
192 let compressor = TruncatedPermutation::<MockPermutation, N, CHUNK, WIDTH>::new(permutation);
193
194 let input: [[u64; CHUNK]; N] = [[1, 2, 3, 4], [5, 6, 7, 8]];
195 let output = compressor.compress(input);
196 let expected_sum = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8;
197
198 assert_eq!(output, [expected_sum; CHUNK]);
199 }
200
201 #[test]
202 fn test_compression_function_from_hasher_compress() {
203 const N: usize = 2;
204 const CHUNK: usize = 4;
205
206 let hasher = MockHasher;
207 let compressor = CompressionFunctionFromHasher::<MockHasher, N, CHUNK>::new(hasher);
208
209 let input = [[10, 20, 30, 40], [50, 60, 70, 80]];
210 let output = compressor.compress(input);
211 let expected_sum = 10 + 20 + 30 + 40 + 50 + 60 + 70 + 80;
212
213 assert_eq!(output, [expected_sum; CHUNK]);
214 }
215
216 #[test]
217 fn test_truncated_permutation_with_zeros() {
218 const N: usize = 2;
219 const CHUNK: usize = 4;
220 const WIDTH: usize = 8;
221
222 let permutation = MockPermutation;
223 let compressor = TruncatedPermutation::<MockPermutation, N, CHUNK, WIDTH>::new(permutation);
224
225 let input: [[u64; CHUNK]; N] = [[0, 0, 0, 0], [0, 0, 0, 0]];
226 let output = compressor.compress(input);
227
228 assert_eq!(output, [0; CHUNK]);
229 }
230
231 #[test]
232 fn test_truncated_permutation_with_extra_width() {
233 const N: usize = 2;
234 const CHUNK: usize = 3;
235 const WIDTH: usize = 10; let permutation = MockPermutation;
238 let compressor = TruncatedPermutation::<MockPermutation, N, CHUNK, WIDTH>::new(permutation);
239
240 let input: [[u64; CHUNK]; N] = [[1, 2, 3], [4, 5, 6]];
241 let output = compressor.compress(input);
242
243 let expected_sum = 1 + 2 + 3 + 4 + 5 + 6;
244
245 assert_eq!(
246 output, [expected_sum; CHUNK],
247 "Compression should correctly handle extra WIDTH space."
248 );
249 }
250
251 #[test]
252 fn compress_many_matches_compressing_each_group_alone() {
253 const N: usize = 2;
254 const CHUNK: usize = 4;
255 const WIDTH: usize = 8;
256
257 let compressor = TruncatedPermutation::<_, N, CHUNK, WIDTH>::new(MockPermutation);
258
259 let groups: [[[u64; CHUNK]; N]; 3] =
261 array::from_fn(|g| array::from_fn(|n| array::from_fn(|i| (g * 8 + n * 4 + i) as u64)));
262
263 let mut batched = [[0u64; CHUNK]; 3];
264 compressor.compress_many(&groups, &mut batched);
265
266 let expected: [[u64; CHUNK]; 3] = groups.map(|group| compressor.compress(group));
267 assert_eq!(batched, expected);
268 }
269
270 #[test]
271 fn compress_many_of_an_empty_batch_writes_nothing() {
272 const N: usize = 2;
273 const CHUNK: usize = 4;
274 const WIDTH: usize = 8;
275
276 let compressor = TruncatedPermutation::<_, N, CHUNK, WIDTH>::new(MockPermutation);
277
278 let groups: [[[u64; CHUNK]; N]; 0] = [];
280 compressor.compress_many(&groups, &mut []);
281 }
282
283 #[test]
284 #[should_panic(expected = "must equal the output count")]
285 fn compress_many_rejects_a_ragged_batch() {
286 const N: usize = 2;
287 const CHUNK: usize = 4;
288 const WIDTH: usize = 8;
289
290 let compressor = TruncatedPermutation::<_, N, CHUNK, WIDTH>::new(MockPermutation);
291
292 let groups = [[[0u64; CHUNK]; N]; 2];
295 let mut out = [[0u64; CHUNK]; 1];
296 compressor.compress_many(&groups, &mut out);
297 }
298
299 #[test]
300 fn compress_many_from_hasher_matches_compressing_each_group_alone() {
301 const N: usize = 2;
302 const CHUNK: usize = 4;
303
304 let compressor = CompressionFunctionFromHasher::<MockHasher, N, CHUNK>::new(MockHasher);
305
306 let groups: [[[u64; CHUNK]; N]; 3] =
307 array::from_fn(|g| array::from_fn(|n| array::from_fn(|i| (g * 8 + n * 4 + i) as u64)));
308
309 let mut batched = vec![[0u64; CHUNK]; 3];
310 compressor.compress_many(&groups, &mut batched);
311
312 let expected: Vec<[u64; CHUNK]> = groups.iter().map(|&g| compressor.compress(g)).collect();
313 assert_eq!(batched, expected);
314 }
315
316 #[test]
317 #[should_panic(expected = "must equal the output count")]
318 fn compress_many_from_hasher_rejects_a_ragged_batch() {
319 const N: usize = 2;
320 const CHUNK: usize = 4;
321
322 let compressor = CompressionFunctionFromHasher::<MockHasher, N, CHUNK>::new(MockHasher);
323
324 let groups = [[[0u64; CHUNK]; N]; 3];
327 let mut out = [[0u64; CHUNK]; 2];
328 compressor.compress_many(&groups, &mut out);
329 }
330}