Skip to main content

twenty_first/tip5/
mod.rs

1//! The arithmetization-oriented, cryptographic hash function Tip5.
2//!
3//! This module contains the reference implementation of [“The Tip5 Hash
4//! Function for Recursive STARKs”](https://eprint.iacr.org/2023/107.pdf), as
5//! well as an [AVX-512](https://en.wikipedia.org/wiki/AVX-512)-accelerated
6//! implementation, which subject to conditional compilation and compatible
7//! hardware.
8
9use std::hash::Hasher;
10
11use arbitrary::Arbitrary;
12pub use digest::Digest;
13use get_size2::GetSize;
14use itertools::Itertools;
15use num_traits::ConstOne;
16use num_traits::ConstZero;
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::math::x_field_element::EXTENSION_DEGREE;
21use crate::prelude::BFieldCodec;
22use crate::prelude::BFieldElement;
23use crate::prelude::XFieldElement;
24use crate::util_types::sponge::Domain;
25use crate::util_types::sponge::Sponge;
26
27pub const STATE_SIZE: usize = 16;
28pub const NUM_SPLIT_AND_LOOKUP: usize = 4;
29pub const LOG2_STATE_SIZE: usize = 4;
30pub const CAPACITY: usize = 6;
31pub const RATE: usize = 10;
32pub const NUM_ROUNDS: usize = 5;
33
34pub mod digest;
35
36#[cfg(target_arch = "x86_64")]
37mod avx512;
38#[cfg(test)]
39mod inverse;
40#[cfg(test)]
41mod naive;
42
43/// The lookup table with a high algebraic degree used in the TIP-5 permutation. To verify its
44/// correctness, see the test “lookup_table_is_correct.”
45pub const LOOKUP_TABLE: [u8; 256] = [
46    0, 7, 26, 63, 124, 215, 85, 254, 214, 228, 45, 185, 140, 173, 33, 240, 29, 177, 176, 32, 8,
47    110, 87, 202, 204, 99, 150, 106, 230, 14, 235, 128, 213, 239, 212, 138, 23, 130, 208, 6, 44,
48    71, 93, 116, 146, 189, 251, 81, 199, 97, 38, 28, 73, 179, 95, 84, 152, 48, 35, 119, 49, 88,
49    242, 3, 148, 169, 72, 120, 62, 161, 166, 83, 175, 191, 137, 19, 100, 129, 112, 55, 221, 102,
50    218, 61, 151, 237, 68, 164, 17, 147, 46, 234, 203, 216, 22, 141, 65, 57, 123, 12, 244, 54, 219,
51    231, 96, 77, 180, 154, 5, 253, 133, 165, 98, 195, 205, 134, 245, 30, 9, 188, 59, 142, 186, 197,
52    181, 144, 92, 31, 224, 163, 111, 74, 58, 69, 113, 196, 67, 246, 225, 10, 121, 50, 60, 157, 90,
53    122, 2, 250, 101, 75, 178, 159, 24, 36, 201, 11, 243, 132, 198, 190, 114, 233, 39, 52, 21, 209,
54    108, 238, 91, 187, 18, 104, 194, 37, 153, 34, 200, 143, 126, 155, 236, 118, 64, 80, 172, 89,
55    94, 193, 135, 183, 86, 107, 252, 13, 167, 206, 136, 220, 207, 103, 171, 160, 76, 182, 227, 217,
56    158, 56, 174, 4, 66, 109, 139, 162, 184, 211, 249, 47, 125, 232, 117, 43, 16, 42, 127, 20, 241,
57    25, 149, 105, 156, 51, 53, 168, 145, 247, 223, 79, 78, 226, 15, 222, 82, 115, 70, 210, 27, 41,
58    1, 170, 40, 131, 192, 229, 248, 255,
59];
60
61/// The round constants used in the Tip5 permutation. To verify their correctness, see the test
62/// “round_constants_are_correct.”
63pub const ROUND_CONSTANTS: [BFieldElement; NUM_ROUNDS * STATE_SIZE] = [
64    BFieldElement::new(13630775303355457758),
65    BFieldElement::new(16896927574093233874),
66    BFieldElement::new(10379449653650130495),
67    BFieldElement::new(1965408364413093495),
68    BFieldElement::new(15232538947090185111),
69    BFieldElement::new(15892634398091747074),
70    BFieldElement::new(3989134140024871768),
71    BFieldElement::new(2851411912127730865),
72    BFieldElement::new(8709136439293758776),
73    BFieldElement::new(3694858669662939734),
74    BFieldElement::new(12692440244315327141),
75    BFieldElement::new(10722316166358076749),
76    BFieldElement::new(12745429320441639448),
77    BFieldElement::new(17932424223723990421),
78    BFieldElement::new(7558102534867937463),
79    BFieldElement::new(15551047435855531404),
80    BFieldElement::new(17532528648579384106),
81    BFieldElement::new(5216785850422679555),
82    BFieldElement::new(15418071332095031847),
83    BFieldElement::new(11921929762955146258),
84    BFieldElement::new(9738718993677019874),
85    BFieldElement::new(3464580399432997147),
86    BFieldElement::new(13408434769117164050),
87    BFieldElement::new(264428218649616431),
88    BFieldElement::new(4436247869008081381),
89    BFieldElement::new(4063129435850804221),
90    BFieldElement::new(2865073155741120117),
91    BFieldElement::new(5749834437609765994),
92    BFieldElement::new(6804196764189408435),
93    BFieldElement::new(17060469201292988508),
94    BFieldElement::new(9475383556737206708),
95    BFieldElement::new(12876344085611465020),
96    BFieldElement::new(13835756199368269249),
97    BFieldElement::new(1648753455944344172),
98    BFieldElement::new(9836124473569258483),
99    BFieldElement::new(12867641597107932229),
100    BFieldElement::new(11254152636692960595),
101    BFieldElement::new(16550832737139861108),
102    BFieldElement::new(11861573970480733262),
103    BFieldElement::new(1256660473588673495),
104    BFieldElement::new(13879506000676455136),
105    BFieldElement::new(10564103842682358721),
106    BFieldElement::new(16142842524796397521),
107    BFieldElement::new(3287098591948630584),
108    BFieldElement::new(685911471061284805),
109    BFieldElement::new(5285298776918878023),
110    BFieldElement::new(18310953571768047354),
111    BFieldElement::new(3142266350630002035),
112    BFieldElement::new(549990724933663297),
113    BFieldElement::new(4901984846118077401),
114    BFieldElement::new(11458643033696775769),
115    BFieldElement::new(8706785264119212710),
116    BFieldElement::new(12521758138015724072),
117    BFieldElement::new(11877914062416978196),
118    BFieldElement::new(11333318251134523752),
119    BFieldElement::new(3933899631278608623),
120    BFieldElement::new(16635128972021157924),
121    BFieldElement::new(10291337173108950450),
122    BFieldElement::new(4142107155024199350),
123    BFieldElement::new(16973934533787743537),
124    BFieldElement::new(11068111539125175221),
125    BFieldElement::new(17546769694830203606),
126    BFieldElement::new(5315217744825068993),
127    BFieldElement::new(4609594252909613081),
128    BFieldElement::new(3350107164315270407),
129    BFieldElement::new(17715942834299349177),
130    BFieldElement::new(9600609149219873996),
131    BFieldElement::new(12894357635820003949),
132    BFieldElement::new(4597649658040514631),
133    BFieldElement::new(7735563950920491847),
134    BFieldElement::new(1663379455870887181),
135    BFieldElement::new(13889298103638829706),
136    BFieldElement::new(7375530351220884434),
137    BFieldElement::new(3502022433285269151),
138    BFieldElement::new(9231805330431056952),
139    BFieldElement::new(9252272755288523725),
140    BFieldElement::new(10014268662326746219),
141    BFieldElement::new(15565031632950843234),
142    BFieldElement::new(1209725273521819323),
143    BFieldElement::new(6024642864597845108),
144];
145
146/// The defining, first column of the (circulant) MDS matrix.
147/// Derived from the SHA-256 hash of the ASCII string “Tip5” by dividing the digest into 16-bit
148/// chunks.
149pub const MDS_MATRIX_FIRST_COLUMN: [i64; STATE_SIZE] = [
150    61402, 1108, 28750, 33823, 7454, 43244, 53865, 12034, 56951, 27521, 41351, 40901, 12021, 59689,
151    26798, 17845,
152];
153
154/// The wrapping integer arithmetic of [`Tip5::generated_function`].
155pub(super) trait WrappingArithmetic: Copy {
156    fn wrapping_add(self, rhs: Self) -> Self;
157    fn wrapping_sub(self, rhs: Self) -> Self;
158    fn wrapping_mul(self, rhs: u64) -> Self;
159}
160
161impl WrappingArithmetic for u64 {
162    #[inline(always)]
163    fn wrapping_add(self, rhs: Self) -> Self {
164        u64::wrapping_add(self, rhs)
165    }
166
167    #[inline(always)]
168    fn wrapping_sub(self, rhs: Self) -> Self {
169        u64::wrapping_sub(self, rhs)
170    }
171
172    #[inline(always)]
173    fn wrapping_mul(self, rhs: u64) -> Self {
174        u64::wrapping_mul(self, rhs)
175    }
176}
177
178#[derive(
179    Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, GetSize, BFieldCodec, Arbitrary,
180)]
181#[repr(align(64))] // for SIMD, align along BFieldElement
182pub struct Tip5 {
183    pub state: [BFieldElement; STATE_SIZE],
184}
185
186impl Tip5 {
187    /// One round of the Tip5 permutation, without any SIMD.
188    #[inline(always)]
189    fn round(&mut self, round_index: usize) {
190        self.sbox_layer();
191        self.mds_generated();
192        for i in 0..STATE_SIZE {
193            self.state[i] += ROUND_CONSTANTS[round_index * STATE_SIZE + i];
194        }
195    }
196
197    #[inline(always)]
198    fn sbox_layer(&mut self) {
199        let (lookup_elements, power_map_elements) = self.state.split_at_mut(NUM_SPLIT_AND_LOOKUP);
200        for element in lookup_elements {
201            Self::split_and_lookup(element);
202        }
203
204        for element in power_map_elements {
205            let sq = *element * *element;
206            let qu = sq * sq;
207            *element *= sq * qu;
208        }
209    }
210
211    #[inline]
212    fn split_and_lookup(element: &mut BFieldElement) {
213        // let value = element.value();
214        let mut bytes = element.raw_bytes();
215
216        for i in 0..8 {
217            // bytes[i] = Self::offset_fermat_cube_map(bytes[i].into()) as u8;
218            bytes[i] = LOOKUP_TABLE[bytes[i] as usize];
219        }
220
221        *element = BFieldElement::from_raw_bytes(&bytes);
222    }
223
224    #[inline(always)]
225    fn mds_generated(&mut self) {
226        let mut lo: [u64; STATE_SIZE] = [0; STATE_SIZE];
227        let mut hi: [u64; STATE_SIZE] = [0; STATE_SIZE];
228        for i in 0..STATE_SIZE {
229            let b = self.state[i].raw_u64();
230            hi[i] = b >> 32;
231            lo[i] = b & 0xffff_ffff;
232        }
233
234        lo = Self::generated_function(lo);
235        hi = Self::generated_function(hi);
236
237        // In isolation, the following reduction modulo BFieldElement::P is
238        // buggy. Concretely, it can produce degenerate Montgomery
239        // representations, that is, `state[r].0` can be greater than or equal
240        // to BFieldElement::P. While there are many inputs for which this can
241        // happen, the easiest with which to trace the behavior manually is:
242        // lo[r] = 0x10;
243        // hi[r] = 0xf_ffff_fff0;
244        //
245        // These starting values lead to `s_hi = 0`, `s_lo = BFieldElement::P`.
246        // Since `s_hi` is 0, the `overflowing_add` does nothing, and the
247        // degenerate representation from `s_lo` is directly transferred to the
248        // state element.
249        //
250        // All that said, due to the specific context this method is always (!)
251        // used in, the bug does not propagate. In particular, this method is
252        // followed up with round-constant addition. Due to a quirk in the
253        // implementation of `BFieldElement::Add` and a property of the round
254        // constants, any degenerate representations are corrected.
255        //
256        // Below, you can find tests for the specific properties claimed. The
257        // doc-string of those tests mention the name of this method.
258        for r in 0..STATE_SIZE {
259            let s = (lo[r] >> 4) as u128 + ((hi[r] as u128) << 28);
260
261            let s_hi = (s >> 64) as u64;
262            let s_lo = s as u64;
263
264            let (res, over) = s_lo.overflowing_add(s_hi * 0xffff_ffff);
265
266            self.state[r] = BFieldElement::from_raw_u64(if over { res + 0xffff_ffff } else { res });
267        }
268    }
269
270    /// The product of the MDS matrix with the input vector, scaled by 16, in
271    /// wrapping integer arithmetic. Since the matrix is circulant, the product
272    /// is a cyclic convolution, which is computed with a divide-and-conquer
273    /// approach that needs far fewer multiplications than the textbook method.
274    ///
275    /// Generic over the integer type so that the batched AVX-512 permutation
276    /// can use it for eight states at once.
277    #[inline(always)]
278    pub(super) fn generated_function<T: WrappingArithmetic>(
279        input: [T; STATE_SIZE],
280    ) -> [T; STATE_SIZE] {
281        let node_34 = input[0].wrapping_add(input[8]);
282        let node_38 = input[4].wrapping_add(input[12]);
283        let node_36 = input[2].wrapping_add(input[10]);
284        let node_40 = input[6].wrapping_add(input[14]);
285        let node_35 = input[1].wrapping_add(input[9]);
286        let node_39 = input[5].wrapping_add(input[13]);
287        let node_37 = input[3].wrapping_add(input[11]);
288        let node_41 = input[7].wrapping_add(input[15]);
289        let node_50 = node_34.wrapping_add(node_38);
290        let node_52 = node_36.wrapping_add(node_40);
291        let node_51 = node_35.wrapping_add(node_39);
292        let node_53 = node_37.wrapping_add(node_41);
293        let node_160 = input[0].wrapping_sub(input[8]);
294        let node_161 = input[1].wrapping_sub(input[9]);
295        let node_165 = input[5].wrapping_sub(input[13]);
296        let node_163 = input[3].wrapping_sub(input[11]);
297        let node_167 = input[7].wrapping_sub(input[15]);
298        let node_162 = input[2].wrapping_sub(input[10]);
299        let node_166 = input[6].wrapping_sub(input[14]);
300        let node_164 = input[4].wrapping_sub(input[12]);
301        let node_58 = node_50.wrapping_add(node_52);
302        let node_59 = node_51.wrapping_add(node_53);
303        let node_90 = node_34.wrapping_sub(node_38);
304        let node_91 = node_35.wrapping_sub(node_39);
305        let node_93 = node_37.wrapping_sub(node_41);
306        let node_92 = node_36.wrapping_sub(node_40);
307        let node_64 = node_58.wrapping_add(node_59).wrapping_mul(524757);
308        let node_67 = node_58.wrapping_sub(node_59).wrapping_mul(52427);
309        let node_71 = node_50.wrapping_sub(node_52);
310        let node_72 = node_51.wrapping_sub(node_53);
311        let node_177 = node_161.wrapping_add(node_165);
312        let node_179 = node_163.wrapping_add(node_167);
313        let node_178 = node_162.wrapping_add(node_166);
314        let node_176 = node_160.wrapping_add(node_164);
315        let node_69 = node_64.wrapping_add(node_67);
316        let node_397 = node_71
317            .wrapping_mul(18446744073709525744)
318            .wrapping_sub(node_72.wrapping_mul(53918));
319        let node_1857 = node_90.wrapping_mul(395512);
320        let node_99 = node_91.wrapping_add(node_93);
321        let node_1865 = node_91.wrapping_mul(18446744073709254400);
322        let node_1869 = node_93.wrapping_mul(179380);
323        let node_1873 = node_92.wrapping_mul(18446744073709509368);
324        let node_1879 = node_160.wrapping_mul(35608);
325        let node_185 = node_161.wrapping_add(node_163);
326        let node_1915 = node_161.wrapping_mul(18446744073709340312);
327        let node_1921 = node_163.wrapping_mul(18446744073709494992);
328        let node_1927 = node_162.wrapping_mul(18446744073709450808);
329        let node_228 = node_165.wrapping_add(node_167);
330        let node_1939 = node_165.wrapping_mul(18446744073709420056);
331        let node_1945 = node_167.wrapping_mul(18446744073709505128);
332        let node_1951 = node_166.wrapping_mul(216536);
333        let node_1957 = node_164.wrapping_mul(18446744073709515080);
334        let node_70 = node_64.wrapping_sub(node_67);
335        let node_702 = node_71
336            .wrapping_mul(53918)
337            .wrapping_add(node_72.wrapping_mul(18446744073709525744));
338        let node_1961 = node_90.wrapping_mul(18446744073709254400);
339        let node_1963 = node_91.wrapping_mul(395512);
340        let node_1965 = node_92.wrapping_mul(179380);
341        let node_1967 = node_93.wrapping_mul(18446744073709509368);
342        let node_1970 = node_160.wrapping_mul(18446744073709340312);
343        let node_1973 = node_161.wrapping_mul(35608);
344        let node_1982 = node_162.wrapping_mul(18446744073709494992);
345        let node_1985 = node_163.wrapping_mul(18446744073709450808);
346        let node_1988 = node_166.wrapping_mul(18446744073709505128);
347        let node_1991 = node_167.wrapping_mul(216536);
348        let node_1994 = node_164.wrapping_mul(18446744073709420056);
349        let node_1997 = node_165.wrapping_mul(18446744073709515080);
350        let node_98 = node_90.wrapping_add(node_92);
351        let node_184 = node_160.wrapping_add(node_162);
352        let node_227 = node_164.wrapping_add(node_166);
353        let node_86 = node_69.wrapping_add(node_397);
354        let node_403 = node_1857.wrapping_sub(
355            node_99
356                .wrapping_mul(18446744073709433780)
357                .wrapping_sub(node_1865)
358                .wrapping_sub(node_1869)
359                .wrapping_add(node_1873),
360        );
361        let node_271 = node_177.wrapping_add(node_179);
362        let node_1891 = node_177.wrapping_mul(18446744073709208752);
363        let node_1897 = node_179.wrapping_mul(18446744073709448504);
364        let node_1903 = node_178.wrapping_mul(115728);
365        let node_1909 = node_185.wrapping_mul(18446744073709283688);
366        let node_1933 = node_228.wrapping_mul(18446744073709373568);
367        let node_88 = node_70.wrapping_add(node_702);
368        let node_708 = node_1961
369            .wrapping_add(node_1963)
370            .wrapping_sub(node_1965.wrapping_add(node_1967));
371        let node_1976 = node_178.wrapping_mul(18446744073709448504);
372        let node_1979 = node_179.wrapping_mul(115728);
373        let node_87 = node_69.wrapping_sub(node_397);
374        let node_897 = node_1865
375            .wrapping_add(node_98.wrapping_mul(353264))
376            .wrapping_sub(node_1857)
377            .wrapping_sub(node_1873)
378            .wrapping_sub(node_1869);
379        let node_2007 = node_184.wrapping_mul(18446744073709486416);
380        let node_2013 = node_227.wrapping_mul(180000);
381        let node_89 = node_70.wrapping_sub(node_702);
382        let node_1077 = node_98
383            .wrapping_mul(18446744073709433780)
384            .wrapping_add(node_99.wrapping_mul(353264))
385            .wrapping_sub(node_1961.wrapping_add(node_1963))
386            .wrapping_sub(node_1965.wrapping_add(node_1967));
387        let node_2020 = node_184.wrapping_mul(18446744073709283688);
388        let node_2023 = node_185.wrapping_mul(18446744073709486416);
389        let node_2026 = node_227.wrapping_mul(18446744073709373568);
390        let node_2029 = node_228.wrapping_mul(180000);
391        let node_2035 = node_176.wrapping_mul(18446744073709550688);
392        let node_2038 = node_176.wrapping_mul(18446744073709208752);
393        let node_2041 = node_177.wrapping_mul(18446744073709550688);
394        let node_270 = node_176.wrapping_add(node_178);
395        let node_152 = node_86.wrapping_add(node_403);
396        let node_412 = node_1879.wrapping_sub(
397            node_271
398                .wrapping_mul(18446744073709105640)
399                .wrapping_sub(node_1891)
400                .wrapping_sub(node_1897)
401                .wrapping_add(node_1903)
402                .wrapping_sub(
403                    node_1909
404                        .wrapping_sub(node_1915)
405                        .wrapping_sub(node_1921)
406                        .wrapping_add(node_1927),
407                )
408                .wrapping_sub(
409                    node_1933
410                        .wrapping_sub(node_1939)
411                        .wrapping_sub(node_1945)
412                        .wrapping_add(node_1951),
413                )
414                .wrapping_add(node_1957),
415        );
416        let node_154 = node_88.wrapping_add(node_708);
417        let node_717 = node_1970.wrapping_add(node_1973).wrapping_sub(
418            node_1976
419                .wrapping_add(node_1979)
420                .wrapping_sub(node_1982.wrapping_add(node_1985))
421                .wrapping_sub(node_1988.wrapping_add(node_1991))
422                .wrapping_add(node_1994.wrapping_add(node_1997)),
423        );
424        let node_156 = node_87.wrapping_add(node_897);
425        let node_906 = node_1915
426            .wrapping_add(node_2007)
427            .wrapping_sub(node_1879)
428            .wrapping_sub(node_1927)
429            .wrapping_sub(
430                node_1897
431                    .wrapping_sub(node_1921)
432                    .wrapping_sub(node_1945)
433                    .wrapping_add(
434                        node_1939
435                            .wrapping_add(node_2013)
436                            .wrapping_sub(node_1957)
437                            .wrapping_sub(node_1951),
438                    ),
439            );
440        let node_158 = node_89.wrapping_add(node_1077);
441        let node_1086 = node_2020
442            .wrapping_add(node_2023)
443            .wrapping_sub(node_1970.wrapping_add(node_1973))
444            .wrapping_sub(node_1982.wrapping_add(node_1985))
445            .wrapping_sub(
446                node_2026
447                    .wrapping_add(node_2029)
448                    .wrapping_sub(node_1994.wrapping_add(node_1997))
449                    .wrapping_sub(node_1988.wrapping_add(node_1991)),
450            );
451        let node_153 = node_86.wrapping_sub(node_403);
452        let node_1237 = node_1909
453            .wrapping_sub(node_1915)
454            .wrapping_sub(node_1921)
455            .wrapping_add(node_1927)
456            .wrapping_add(node_2035)
457            .wrapping_sub(node_1879)
458            .wrapping_sub(node_1957)
459            .wrapping_sub(
460                node_1933
461                    .wrapping_sub(node_1939)
462                    .wrapping_sub(node_1945)
463                    .wrapping_add(node_1951),
464            );
465        let node_155 = node_88.wrapping_sub(node_708);
466        let node_1375 = node_1982
467            .wrapping_add(node_1985)
468            .wrapping_add(node_2038.wrapping_add(node_2041))
469            .wrapping_sub(node_1970.wrapping_add(node_1973))
470            .wrapping_sub(node_1994.wrapping_add(node_1997))
471            .wrapping_sub(node_1988.wrapping_add(node_1991));
472        let node_157 = node_87.wrapping_sub(node_897);
473        let node_1492 = node_1921
474            .wrapping_add(
475                node_1891
476                    .wrapping_add(node_270.wrapping_mul(114800))
477                    .wrapping_sub(node_2035)
478                    .wrapping_sub(node_1903),
479            )
480            .wrapping_sub(
481                node_1915
482                    .wrapping_add(node_2007)
483                    .wrapping_sub(node_1879)
484                    .wrapping_sub(node_1927),
485            )
486            .wrapping_sub(
487                node_1939
488                    .wrapping_add(node_2013)
489                    .wrapping_sub(node_1957)
490                    .wrapping_sub(node_1951),
491            )
492            .wrapping_sub(node_1945);
493        let node_159 = node_89.wrapping_sub(node_1077);
494        let node_1657 = node_270
495            .wrapping_mul(18446744073709105640)
496            .wrapping_add(node_271.wrapping_mul(114800))
497            .wrapping_sub(node_2038.wrapping_add(node_2041))
498            .wrapping_sub(node_1976.wrapping_add(node_1979))
499            .wrapping_sub(
500                node_2020
501                    .wrapping_add(node_2023)
502                    .wrapping_sub(node_1970.wrapping_add(node_1973))
503                    .wrapping_sub(node_1982.wrapping_add(node_1985)),
504            )
505            .wrapping_sub(
506                node_2026
507                    .wrapping_add(node_2029)
508                    .wrapping_sub(node_1994.wrapping_add(node_1997))
509                    .wrapping_sub(node_1988.wrapping_add(node_1991)),
510            );
511
512        [
513            node_152.wrapping_add(node_412),
514            node_154.wrapping_add(node_717),
515            node_156.wrapping_add(node_906),
516            node_158.wrapping_add(node_1086),
517            node_153.wrapping_add(node_1237),
518            node_155.wrapping_add(node_1375),
519            node_157.wrapping_add(node_1492),
520            node_159.wrapping_add(node_1657),
521            node_152.wrapping_sub(node_412),
522            node_154.wrapping_sub(node_717),
523            node_156.wrapping_sub(node_906),
524            node_158.wrapping_sub(node_1086),
525            node_153.wrapping_sub(node_1237),
526            node_155.wrapping_sub(node_1375),
527            node_157.wrapping_sub(node_1492),
528            node_159.wrapping_sub(node_1657),
529        ]
530    }
531}
532
533impl Tip5 {
534    #[inline]
535    pub const fn new(domain: Domain) -> Self {
536        let mut state = const { [BFieldElement::ZERO; STATE_SIZE] };
537
538        match domain {
539            Domain::VariableLength => (),
540            Domain::FixedLength => {
541                let mut i = RATE;
542                while i < STATE_SIZE {
543                    state[i] = BFieldElement::ONE;
544                    i += 1;
545                }
546            }
547        }
548
549        Self { state }
550    }
551
552    #[inline]
553    pub fn permutation(&mut self) {
554        #[cfg(target_arch = "x86_64")]
555        if Self::avx512_is_available() {
556            // SAFETY: The required CPU features were just detected.
557            unsafe { self.permutation_avx512() };
558            return;
559        }
560
561        self.permutation_scalar();
562    }
563
564    /// The Tip5 permutation, without any SIMD.
565    #[inline]
566    fn permutation_scalar(&mut self) {
567        for i in 0..NUM_ROUNDS {
568            self.round(i);
569        }
570    }
571
572    /// One round of the Tip5 permutation, using SIMD if available.
573    #[inline]
574    fn round_dispatching(&mut self, round_index: usize) {
575        #[cfg(target_arch = "x86_64")]
576        if Self::avx512_is_available() {
577            // SAFETY: The required CPU features were just detected.
578            unsafe { self.round_avx512(round_index) };
579            return;
580        }
581
582        self.round(round_index);
583    }
584
585    /// Functionally equivalent to [`permutation`](Self::permutation). Returns the trace of
586    /// applying the permutation; that is, the initial state of the sponge as well as its state
587    /// after each round.
588    pub fn trace(&mut self) -> [[BFieldElement; STATE_SIZE]; 1 + NUM_ROUNDS] {
589        let mut trace = [[BFieldElement::ZERO; STATE_SIZE]; 1 + NUM_ROUNDS];
590
591        trace[0] = self.state;
592        for i in 0..NUM_ROUNDS {
593            self.round_dispatching(i);
594            trace[1 + i] = self.state;
595        }
596
597        trace
598    }
599
600    /// Hash 10 [`BFieldElement`]s.
601    ///
602    /// There is no input-padding because the input length is fixed.
603    ///
604    /// When you want to hash together two [`Digest`]s, use [`Self::hash_pair`]
605    /// instead. In some rare cases you do want to hash a fixed-length string
606    /// of individual [`BFieldElement`]s, which is why this function is exposed.
607    ///
608    /// See also: [`Self::hash_pair`], [`Self::hash`], [`Self::hash_varlen`].
609    pub fn hash_10(input: &[BFieldElement; RATE]) -> [BFieldElement; Digest::LEN] {
610        let mut sponge = Self::new(Domain::FixedLength);
611
612        // absorb once
613        sponge.state[..RATE].copy_from_slice(input);
614
615        sponge.permutation();
616
617        // squeeze once
618        sponge.state[..Digest::LEN].try_into().unwrap()
619    }
620
621    /// Hash two [`Digest`]s together.
622    ///
623    /// This function is syntax sugar for calling [`Self::hash_10`] on the
624    /// concatenation of the digests' values.
625    ///
626    /// See also: [`Self::hash_10`], [`Self::hash`], [`Self::hash_varlen`].
627    pub fn hash_pair(left: Digest, right: Digest) -> Digest {
628        let mut sponge = Self::new(Domain::FixedLength);
629        sponge.state[..Digest::LEN].copy_from_slice(&left.values());
630        sponge.state[Digest::LEN..2 * Digest::LEN].copy_from_slice(&right.values());
631
632        sponge.permutation();
633
634        let digest_values = sponge.state[..Digest::LEN].try_into().unwrap();
635        Digest::new(digest_values)
636    }
637
638    /// Hash an object based on its [`BFieldCodec`]-encoding.
639    ///
640    /// Thin wrapper around [`hash_varlen`](Self::hash_varlen).
641    ///
642    /// See also: [`Self::hash_10`], [`Self::hash_pair`], [`Self::hash_varlen`].
643    pub fn hash<T: BFieldCodec>(value: &T) -> Digest {
644        Self::hash_varlen(&value.encode())
645    }
646
647    /// Hash a variable-length sequence of [`BFieldElement`].
648    ///
649    /// This function pads the input as its length is variable.
650    ///
651    /// Note that [`Self::hash_varlen`] and [`Self::hash_10`] are different
652    /// functions, even when the input to the former, after padding, agrees with
653    /// the input to the latter. The difference comes from the initial value of
654    /// the capacity-part of the state, which in the case of variable-length
655    /// hashing is all-zeros but in the case of fixed-length hashing is
656    /// all-ones.
657    ///
658    /// Prefer [`Self::hash`] whenever an object is being hashed whose type
659    /// implements [`BFieldCodec`]. However, such an object is not always
660    /// available, which is why this function is exposed.
661    ///
662    /// See also: [`Self::hash_10`], [`Self::hash_pair`], [`Self::hash`].
663    //
664    // - Apply the correct padding
665    // - [Sponge::pad_and_absorb_all()]
666    // - Read the digest from the resulting state.
667    pub fn hash_varlen(input: &[BFieldElement]) -> Digest {
668        let mut sponge = Self::init();
669        sponge.pad_and_absorb_all(input);
670        let produce = (&sponge.state[..Digest::LEN]).try_into().unwrap();
671
672        Digest::new(produce)
673    }
674
675    /// [`hash_varlen`](Self::hash_varlen) of many inputs, in order.
676    ///
677    /// Equivalent to hashing every input on its own, but considerably faster
678    /// on CPUs with AVX-512: there, groups of consecutive inputs of equal
679    /// length are hashed side by side, one input per SIMD lane.
680    ///
681    /// See also: [`Self::hash_pair_many`].
682    pub fn hash_varlen_many(inputs: &[&[BFieldElement]]) -> Vec<Digest> {
683        #[cfg(target_arch = "x86_64")]
684        if Self::avx512_is_available() {
685            let (batches, remainder) = inputs.as_chunks::<{ avx512::BATCH_SIZE }>();
686            let mut digests = Vec::with_capacity(inputs.len());
687            for batch in batches {
688                let len = batch[0].len();
689                if batch.iter().all(|input| input.len() == len) {
690                    // SAFETY: The required CPU features were detected above.
691                    digests.extend(unsafe { Self::hash_varlen_batch(*batch) });
692                } else {
693                    digests.extend(batch.iter().map(|input| Self::hash_varlen(input)));
694                }
695            }
696            digests.extend(remainder.iter().map(|input| Self::hash_varlen(input)));
697            return digests;
698        }
699
700        Self::hash_varlen_many_scalar(inputs)
701    }
702
703    /// [`hash_varlen_many`](Self::hash_varlen_many), without any SIMD.
704    fn hash_varlen_many_scalar(inputs: &[&[BFieldElement]]) -> Vec<Digest> {
705        inputs
706            .iter()
707            .map(|input| Self::hash_varlen(input))
708            .collect()
709    }
710
711    /// [`hash_pair`](Self::hash_pair) of many pairs, in order.
712    ///
713    /// Equivalent to hashing every pair on its own, but considerably faster
714    /// on CPUs with AVX-512, where the pairs are hashed side by side, one
715    /// pair per SIMD lane.
716    ///
717    /// See also: [`Self::hash_varlen_many`].
718    pub fn hash_pair_many(pairs: &[[Digest; 2]]) -> Vec<Digest> {
719        #[cfg(target_arch = "x86_64")]
720        if Self::avx512_is_available() {
721            let (batches, remainder) = pairs.as_chunks::<{ avx512::BATCH_SIZE }>();
722            let mut digests = Vec::with_capacity(pairs.len());
723            for batch in batches {
724                // SAFETY: The required CPU features were detected above.
725                digests.extend(unsafe { Self::hash_pair_batch(batch) });
726            }
727            digests.extend(remainder.iter().map(|&[l, r]| Self::hash_pair(l, r)));
728            return digests;
729        }
730
731        Self::hash_pair_many_scalar(pairs)
732    }
733
734    /// [`hash_pair_many`](Self::hash_pair_many), without any SIMD.
735    fn hash_pair_many_scalar(pairs: &[[Digest; 2]]) -> Vec<Digest> {
736        pairs.iter().map(|&[l, r]| Self::hash_pair(l, r)).collect()
737    }
738
739    /// Produce `num_indices` random integer values in the range `[0, upper_bound)`. The
740    /// `upper_bound` must be a power of 2.
741    ///
742    /// This method uses von Neumann rejection sampling.
743    /// Specifically, if the top 32 bits of a BFieldElement are all ones, then the bottom 32 bits
744    /// are not uniformly distributed, and so they are dropped. This method invokes squeeze until
745    /// enough uniform u32s have been sampled.
746    ///
747    /// # Panics
748    ///
749    /// Panics if `upper_bound` is not a power of two.
750    pub fn sample_indices(&mut self, upper_bound: u32, num_indices: usize) -> Vec<u32> {
751        assert!(upper_bound.is_power_of_two());
752
753        let mut indices = Vec::with_capacity(num_indices);
754        let mut buffer = const { [BFieldElement::ZERO; RATE] };
755        let mut next_in_buffer = RATE;
756        while indices.len() < num_indices {
757            if next_in_buffer == RATE {
758                buffer = self.squeeze();
759                next_in_buffer = 0;
760            }
761            let element = buffer[next_in_buffer];
762            next_in_buffer += 1;
763
764            if element != const { BFieldElement::new(BFieldElement::MAX) } {
765                indices.push(element.value() as u32 % upper_bound);
766            }
767        }
768
769        indices
770    }
771
772    /// Produce `num_elements` random [`XFieldElement`] values.
773    ///
774    /// If `num_elements` is not divisible by [`RATE`][rate], spill the remaining elements of the
775    /// last [`squeeze`][Sponge::squeeze].
776    ///
777    /// [rate]: Sponge::RATE
778    pub fn sample_scalars(&mut self, num_elements: usize) -> Vec<XFieldElement> {
779        let num_squeezes = (num_elements * EXTENSION_DEGREE).div_ceil(Self::RATE);
780        debug_assert!(num_elements * EXTENSION_DEGREE <= num_squeezes * Self::RATE);
781
782        (0..num_squeezes)
783            .flat_map(|_| self.squeeze())
784            .tuples()
785            .take(num_elements)
786            .map(|(x0, x1, x2)| XFieldElement::new([x0, x1, x2]))
787            .collect()
788    }
789}
790
791impl Sponge for Tip5 {
792    const RATE: usize = RATE;
793
794    fn init() -> Self {
795        Self::new(Domain::VariableLength)
796    }
797
798    fn absorb(&mut self, input: [BFieldElement; RATE]) {
799        self.state[..RATE]
800            .iter_mut()
801            .zip_eq(&input)
802            .for_each(|(a, &b)| *a = b);
803
804        self.permutation();
805    }
806
807    fn squeeze(&mut self) -> [BFieldElement; RATE] {
808        let produce: [BFieldElement; RATE] = (&self.state[..RATE]).try_into().unwrap();
809        self.permutation();
810
811        produce
812    }
813}
814
815impl Hasher for Tip5 {
816    fn finish(&self) -> u64 {
817        self.state[0].value()
818    }
819
820    fn write(&mut self, bytes: &[u8]) {
821        let bfield_elements = bytes.chunks(BFieldElement::BYTES).map(|chunk| {
822            let mut buffer = [0u8; BFieldElement::BYTES];
823            buffer[..chunk.len()].copy_from_slice(chunk);
824            BFieldElement::new(u64::from_le_bytes(buffer))
825        });
826
827        for chunk in bfield_elements.chunks(Tip5::RATE).into_iter() {
828            let mut buffer = [BFieldElement::ZERO; Tip5::RATE];
829            for (buffer_elem, chunk_elem) in buffer.iter_mut().zip(chunk) {
830                *buffer_elem = chunk_elem;
831            }
832            self.absorb(buffer)
833        }
834    }
835}
836
837#[cfg(test)]
838#[cfg_attr(coverage_nightly, coverage(off))]
839pub(crate) mod tests {
840    use std::hash::Hash;
841    use std::ops::Mul;
842
843    use prop::sample::size_range;
844    use proptest::prelude::*;
845    use proptest_arbitrary_adapter::arb;
846    use rand::Rng;
847    use rayon::prelude::IntoParallelIterator;
848    use rayon::prelude::ParallelIterator;
849
850    use super::*;
851    use crate::math::other::random_elements;
852    use crate::math::x_field_element::XFieldElement;
853    use crate::tests::proptest;
854    use crate::tests::test;
855
856    impl proptest::arbitrary::Arbitrary for Tip5 {
857        type Parameters = ();
858
859        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
860            arb().boxed()
861        }
862
863        type Strategy = BoxedStrategy<Self>;
864    }
865
866    impl Tip5 {
867        fn mds_cyclomul(&mut self) {
868            let mut result = [BFieldElement::ZERO; STATE_SIZE];
869
870            let mut lo: [i64; STATE_SIZE] = [0; STATE_SIZE];
871            let mut hi: [i64; STATE_SIZE] = [0; STATE_SIZE];
872            for (i, b) in self.state.iter().enumerate() {
873                hi[i] = (b.raw_u64() >> 32) as i64;
874                lo[i] = (b.raw_u64() as u32) as i64;
875            }
876
877            lo = Self::fast_cyclomul16(lo, MDS_MATRIX_FIRST_COLUMN);
878            hi = Self::fast_cyclomul16(hi, MDS_MATRIX_FIRST_COLUMN);
879
880            for r in 0..STATE_SIZE {
881                let s = lo[r] as u128 + ((hi[r] as u128) << 32);
882                let s_hi = (s >> 64) as u64;
883                let s_lo = s as u64;
884                let z = (s_hi << 32) - s_hi;
885                let (res, over) = s_lo.overflowing_add(z);
886
887                result[r] = BFieldElement::from_raw_u64(
888                    res.wrapping_add(0u32.wrapping_sub(over as u32) as u64),
889                );
890            }
891            self.state = result;
892        }
893
894        fn fast_cyclomul16(f: [i64; 16], g: [i64; 16]) -> [i64; 16] {
895            const N: usize = 8;
896            let mut ff_lo = [0i64; N];
897            let mut gg_lo = [0i64; N];
898            let mut ff_hi = [0i64; N];
899            let mut gg_hi = [0i64; N];
900            for i in 0..N {
901                ff_lo[i] = f[i] + f[i + N];
902                ff_hi[i] = f[i] - f[i + N];
903                gg_lo[i] = g[i] + g[i + N];
904                gg_hi[i] = g[i] - g[i + N];
905            }
906
907            let hh_lo = Self::fast_cyclomul8(ff_lo, gg_lo);
908            let hh_hi = Self::complex_negacyclomul8(ff_hi, gg_hi);
909
910            let mut hh = [0i64; 2 * N];
911            for i in 0..N {
912                hh[i] = (hh_lo[i] + hh_hi[i]) >> 1;
913                hh[i + N] = (hh_lo[i] - hh_hi[i]) >> 1;
914            }
915
916            hh
917        }
918
919        fn fast_cyclomul8(f: [i64; 8], g: [i64; 8]) -> [i64; 8] {
920            const N: usize = 4;
921            let mut ff_lo = [0i64; N];
922            let mut gg_lo = [0i64; N];
923            let mut ff_hi = [0i64; N];
924            let mut gg_hi = [0i64; N];
925            for i in 0..N {
926                ff_lo[i] = f[i] + f[i + N];
927                ff_hi[i] = f[i] - f[i + N];
928                gg_lo[i] = g[i] + g[i + N];
929                gg_hi[i] = g[i] - g[i + N];
930            }
931
932            let hh_lo = Self::fast_cyclomul4(ff_lo, gg_lo);
933            let hh_hi = Self::complex_negacyclomul4(ff_hi, gg_hi);
934
935            let mut hh = [0i64; 2 * N];
936            for i in 0..N {
937                hh[i] = (hh_lo[i] + hh_hi[i]) >> 1;
938                hh[i + N] = (hh_lo[i] - hh_hi[i]) >> 1;
939            }
940
941            hh
942        }
943
944        fn fast_cyclomul4(f: [i64; 4], g: [i64; 4]) -> [i64; 4] {
945            const N: usize = 2;
946            let mut ff_lo = [0i64; N];
947            let mut gg_lo = [0i64; N];
948            let mut ff_hi = [0i64; N];
949            let mut gg_hi = [0i64; N];
950            for i in 0..N {
951                ff_lo[i] = f[i] + f[i + N];
952                ff_hi[i] = f[i] - f[i + N];
953                gg_lo[i] = g[i] + g[i + N];
954                gg_hi[i] = g[i] - g[i + N];
955            }
956
957            let hh_lo = Self::fast_cyclomul2(ff_lo, gg_lo);
958            let hh_hi = Self::complex_negacyclomul2(ff_hi, gg_hi);
959
960            let mut hh = [0i64; 2 * N];
961            for i in 0..N {
962                hh[i] = (hh_lo[i] + hh_hi[i]) >> 1;
963                hh[i + N] = (hh_lo[i] - hh_hi[i]) >> 1;
964            }
965
966            hh
967        }
968
969        fn fast_cyclomul2(f: [i64; 2], g: [i64; 2]) -> [i64; 2] {
970            let ff_lo = f[0] + f[1];
971            let ff_hi = f[0] - f[1];
972            let gg_lo = g[0] + g[1];
973            let gg_hi = g[0] - g[1];
974
975            let hh_lo = ff_lo * gg_lo;
976            let hh_hi = ff_hi * gg_hi;
977
978            let mut hh = [0i64; 2];
979            hh[0] = (hh_lo + hh_hi) >> 1;
980            hh[1] = (hh_lo - hh_hi) >> 1;
981
982            hh
983        }
984
985        fn complex_negacyclomul8(f: [i64; 8], g: [i64; 8]) -> [i64; 8] {
986            const N: usize = 4;
987
988            let mut f0 = [(0i64, 0i64); N];
989            let mut g0 = [(0i64, 0i64); N];
990
991            for i in 0..N {
992                f0[i] = (f[i], -f[N + i]);
993                g0[i] = (g[i], -g[N + i]);
994            }
995
996            let h0 = Self::complex_karatsuba4(f0, g0);
997
998            let mut h = [0i64; 3 * N - 1];
999            for i in 0..(2 * N - 1) {
1000                h[i] += h0[i].0;
1001                h[i + N] -= h0[i].1;
1002            }
1003
1004            let mut hh = [0i64; 2 * N];
1005            for i in 0..(2 * N) {
1006                hh[i] += h[i];
1007            }
1008            for i in (2 * N)..(3 * N - 1) {
1009                hh[i - 2 * N] -= h[i];
1010            }
1011
1012            hh
1013        }
1014
1015        fn complex_negacyclomul4(f: [i64; 4], g: [i64; 4]) -> [i64; 4] {
1016            const N: usize = 2;
1017
1018            let mut f0 = [(0i64, 0i64); N];
1019            let mut g0 = [(0i64, 0i64); N];
1020
1021            for i in 0..N {
1022                f0[i] = (f[i], -f[N + i]);
1023                g0[i] = (g[i], -g[N + i]);
1024            }
1025
1026            let h0 = Self::complex_karatsuba2(f0, g0);
1027
1028            let mut h = [0i64; 4 * N - 1];
1029            for i in 0..(2 * N - 1) {
1030                h[i] += h0[i].0;
1031                h[i + N] -= h0[i].1;
1032            }
1033
1034            let mut hh = [0i64; 2 * N];
1035            for i in 0..(2 * N) {
1036                hh[i] += h[i];
1037            }
1038            for i in (2 * N)..(4 * N - 1) {
1039                hh[i - 2 * N] -= h[i];
1040            }
1041
1042            hh
1043        }
1044
1045        fn complex_negacyclomul2(f: [i64; 2], g: [i64; 2]) -> [i64; 2] {
1046            let f0 = (f[0], -f[1]);
1047            let g0 = (g[0], -g[1]);
1048
1049            let h0 = Self::complex_product(f0, g0);
1050
1051            [h0.0, -h0.1]
1052        }
1053
1054        #[inline(always)]
1055        fn complex_karatsuba4(f: [(i64, i64); 4], g: [(i64, i64); 4]) -> [(i64, i64); 7] {
1056            const N: usize = 2;
1057
1058            let ff = Self::complex_sum::<2>(f[..N].try_into().unwrap(), f[N..].try_into().unwrap());
1059            let gg = Self::complex_sum::<2>(g[..N].try_into().unwrap(), g[N..].try_into().unwrap());
1060
1061            let lo =
1062                Self::complex_karatsuba2(f[..N].try_into().unwrap(), g[..N].try_into().unwrap());
1063            let hi =
1064                Self::complex_karatsuba2(f[N..].try_into().unwrap(), g[N..].try_into().unwrap());
1065
1066            let li = Self::complex_diff::<3>(
1067                Self::complex_karatsuba2(ff, gg),
1068                Self::complex_sum::<3>(lo, hi),
1069            );
1070
1071            let mut result = [(0i64, 0i64); 4 * N - 1];
1072            for i in 0..(2 * N - 1) {
1073                result[i].0 = lo[i].0;
1074                result[i].1 = lo[i].1;
1075            }
1076            for i in 0..(2 * N - 1) {
1077                result[N + i].0 += li[i].0;
1078                result[N + i].1 += li[i].1;
1079            }
1080            for i in 0..(2 * N - 1) {
1081                result[2 * N + i].0 += hi[i].0;
1082                result[2 * N + i].1 += hi[i].1;
1083            }
1084
1085            result
1086        }
1087
1088        fn complex_karatsuba2(f: [(i64, i64); 2], g: [(i64, i64); 2]) -> [(i64, i64); 3] {
1089            const N: usize = 1;
1090
1091            let ff = (f[0].0 + f[1].0, f[0].1 + f[1].1);
1092            let gg = (g[0].0 + g[1].0, g[0].1 + g[1].1);
1093
1094            let lo = Self::complex_product(f[0], g[0]);
1095            let hi = Self::complex_product(f[1], g[1]);
1096
1097            let ff_times_gg = Self::complex_product(ff, gg);
1098            let lo_plus_hi = (lo.0 + hi.0, lo.1 + hi.1);
1099
1100            let li = (ff_times_gg.0 - lo_plus_hi.0, ff_times_gg.1 - lo_plus_hi.1);
1101
1102            let mut result = [(0i64, 0i64); 4 * N - 1];
1103            result[0].0 += lo.0;
1104            result[0].1 += lo.1;
1105            result[N].0 += li.0;
1106            result[N].1 += li.1;
1107            result[2 * N].0 += hi.0;
1108            result[2 * N].1 += hi.1;
1109
1110            result
1111        }
1112
1113        fn complex_product(f: (i64, i64), g: (i64, i64)) -> (i64, i64) {
1114            // don't karatsuba; this is faster
1115            (f.0 * g.0 - f.1 * g.1, f.0 * g.1 + f.1 * g.0)
1116        }
1117
1118        fn complex_sum<const N: usize>(f: [(i64, i64); N], g: [(i64, i64); N]) -> [(i64, i64); N] {
1119            let mut h = [(0i64, 0i64); N];
1120            for i in 0..N {
1121                h[i].0 = f[i].0 + g[i].0;
1122                h[i].1 = f[i].1 + g[i].1;
1123            }
1124            h
1125        }
1126
1127        fn complex_diff<const N: usize>(f: [(i64, i64); N], g: [(i64, i64); N]) -> [(i64, i64); N] {
1128            let mut h = [(0i64, 0i64); N];
1129            for i in 0..N {
1130                h[i].0 = f[i].0 - g[i].0;
1131                h[i].1 = f[i].1 - g[i].1;
1132            }
1133            h
1134        }
1135
1136        fn offset_fermat_cube_map(x: u16) -> u16 {
1137            let xx = (x + 1) as u64;
1138            let xxx = xx * xx * xx;
1139            ((xxx + 256) % 257) as u16
1140        }
1141    }
1142
1143    #[macro_rules_attr::apply(proptest)]
1144    fn get_size(tip5: Tip5) {
1145        assert_eq!(STATE_SIZE * BFieldElement::ZERO.get_size(), tip5.get_size());
1146    }
1147
1148    #[macro_rules_attr::apply(test)]
1149    fn lookup_table_is_correct() {
1150        let table: [u8; 256] = (0..256)
1151            .map(|t| Tip5::offset_fermat_cube_map(t as u16) as u8)
1152            .collect_vec()
1153            .try_into()
1154            .unwrap();
1155
1156        println!(
1157            "Entire lookup table:\n{}",
1158            table.iter().map(|t| format!("{t:02x}")).join(", ")
1159        );
1160
1161        (0_usize..256).for_each(|i| {
1162            assert_eq!(
1163                LOOKUP_TABLE[i], table[i],
1164                "Lookup tables must agree at every index, including index {i}."
1165            )
1166        });
1167    }
1168
1169    #[macro_rules_attr::apply(test)]
1170    fn round_constants_are_correct() {
1171        let to_int = |bytes: &[u8]| {
1172            bytes
1173                .iter()
1174                .take(16)
1175                .enumerate()
1176                .map(|(i, b)| (*b as u128) << (8 * i))
1177                .sum::<u128>()
1178        };
1179        let round_constants = (0..NUM_ROUNDS * STATE_SIZE)
1180            .map(|i| ["Tip5".to_string().as_bytes(), &[i as u8]].concat())
1181            .map(|bytes| blake3::hash(&bytes))
1182            .map(|hash| *hash.as_bytes())
1183            .map(|bytes| to_int(&bytes))
1184            .map(|i| (i % BFieldElement::P as u128) as u64)
1185            .map(BFieldElement::from_raw_u64)
1186            .collect_vec();
1187
1188        println!(
1189            "In case you changed something, here are all round constants:\n{}",
1190            round_constants.iter().map(|c| format!("{c}")).join(", ")
1191        );
1192
1193        (0_usize..NUM_ROUNDS * STATE_SIZE).for_each(|i| {
1194            assert_eq!(
1195                ROUND_CONSTANTS[i], round_constants[i],
1196                "Round constants must agree at every index, including index {i}."
1197            )
1198        });
1199    }
1200
1201    #[macro_rules_attr::apply(test)]
1202    fn test_fermat_cube_map_is_permutation() {
1203        let mut touched = [false; 256];
1204        for i in 0..256 {
1205            touched[Tip5::offset_fermat_cube_map(i) as usize] = true;
1206        }
1207        assert!(touched.iter().all(|t| *t));
1208        assert_eq!(Tip5::offset_fermat_cube_map(0), 0);
1209        assert_eq!(Tip5::offset_fermat_cube_map(255), 255);
1210    }
1211
1212    /// Ensure that the claims made in [`Tip5::mds_generated`] are true.
1213    ///
1214    /// In particular, `BFieldElement::Add` internally uses the equality
1215    /// `a + b = a - (p - b)`. If `a` is a degenerate representation (i.e., is
1216    /// larger than or equal to [`BFieldElement::P`]), then a small enough `b`
1217    /// makes the result of the addition non-degenerate by removing the
1218    /// “surplus” from `a`. In particular, this correction will happen if the
1219    /// following inequality holds:
1220    ///
1221    ///      p - b > u64::MAX - p
1222    ///   ↔  p - b > 2^64 - 1 - (2^64 - 2^32 + 1)
1223    ///   ↔  p - b > 2^32 - 2
1224    ///   ↔     -b > 2^32 - 2 - p
1225    ///   ↔      b < p + 2 - 2^32
1226    ///
1227    /// While it’s not particularly beautiful to depend on such implementation
1228    /// details, Ferdinand is too scared to change the implementation of Tip5.
1229    /// If you change the behavior of `BFieldElement::Add`, please make sure
1230    /// that [`Tip5`] is not breaking.
1231    ///
1232    /// The test [`round_constants_correct_degenerate_lhs_when_adding`] makes
1233    /// sure that all round constants have the required property.
1234    ///
1235    /// See also: https://www.hyrumslaw.com/. Sorry about that.
1236    #[macro_rules_attr::apply(proptest)]
1237    fn adding_degenerate_lhs_and_small_enough_rhs_makes_sum_non_degenerate(
1238        #[strategy(BFieldElement::P..)] raw_a: u64,
1239        #[strategy(0..BFieldElement::P + 2 - (1 << 32))] raw_b: u64,
1240    ) {
1241        let a = BFieldElement::from_raw_u64(raw_a);
1242        let b = BFieldElement::from_raw_u64(raw_b);
1243        let raw_sum = (a + b).raw_u64();
1244        prop_assert!(raw_sum < BFieldElement::P);
1245    }
1246
1247    /// Ensure that the claims made in [`Tip5::mds_generated`] are true.
1248    ///
1249    /// [`adding_degenerate_lhs_and_small_enough_rhs_makes_sum_non_degenerate`]
1250    /// explains the requirement in greater detail.
1251    #[macro_rules_attr::apply(test)]
1252    fn round_constants_correct_degenerate_lhs_when_adding() {
1253        for constant in ROUND_CONSTANTS {
1254            assert!(constant.raw_u64() < BFieldElement::P + 2 - (1 << 32));
1255        }
1256    }
1257
1258    /// Ensure that the claims made in [`Tip5::mds_generated`] are true.
1259    #[macro_rules_attr::apply(test)]
1260    fn tip5_recovers_from_degenerate_field_element_representations() {
1261        let state = [
1262            0x1063_c4bf_5d8b_b0dd,
1263            0xdb62_75d3_71fe_05d0,
1264            0xde58_cae3_0144_cdae,
1265            0xc774_e646_81d3_622e,
1266            0xc4a9_47d1_0a5a_a466,
1267            0xda55_77a0_0a91_3151,
1268            0xe80e_978b_3836_dcd0,
1269            0x8dd1_61f0_a3ac_00c2,
1270            0x6857_f251_a9c0_f693,
1271            0x4923_a368_3046_178e,
1272            0x6e6f_c54a_9b81_010b,
1273            0xcb84_fa5b_b9fa_ec36,
1274            0x93cb_f9db_4c5c_b1ea,
1275            0xf215_d9b9_2dc8_7266,
1276            0x88f0_9783_d2ae_3c57,
1277            0x6d29_f9ce_94a9_0b71,
1278        ]
1279        .map(BFieldElement::new);
1280        let expected = [
1281            0xa5d3_2d62_9e60_d72e,
1282            0x5516_ef90_d277_3d74,
1283            0x65d3_fa1c_de45_f6cb,
1284            0x7bf0_e725_dfa5_906b,
1285            0x67a2_db4b_141b_90e9,
1286            0x91db_162d_3230_9083,
1287            0xefec_1d00_146a_05c9,
1288            0xcca0_d656_6bca_8186,
1289            0x405b_aeb5_b3f8_7f02,
1290            0xd897_0158_7027_8f76,
1291            0xd4b2_ee48_10aa_c7d1,
1292            0x27b4_51e7_06a5_c2fc,
1293            0xe9b4_177f_0a0e_ffe4,
1294            0x0c60_def0_f2c5_287f,
1295            0x703a_a06d_327c_cc34,
1296            0x536f_2355_0ebf_98f1,
1297        ]
1298        .map(BFieldElement::new);
1299
1300        let mut dbg_tip5 = Tip5 { state };
1301        dbg_tip5.sbox_layer();
1302        dbg_tip5.mds_generated();
1303
1304        // If this assertion fails, you might have improved the internals of
1305        // Tip5 in a way that makes the properties tested for in
1306        // `adding_degenerate_lhs_and_small_enough_rhs_makes_sum_non_degenerate`
1307        // and
1308        // `round_constants_correct_degenerate_lhs_when_adding`
1309        // superfluous. If that is the case, feel free to remove those tests
1310        // as well as this one.
1311        debug_assert!(dbg_tip5.state[1].raw_u64() >= BFieldElement::P);
1312
1313        let mut tip5 = Tip5 { state };
1314        tip5.permutation();
1315        assert_eq!(expected, tip5.state);
1316
1317        let mut naive = naive::NaiveTip5 { state };
1318        naive.permutation();
1319        assert_eq!(expected, naive.state);
1320
1321        // The batched permutation must recover in every lane, too.
1322        #[cfg(target_arch = "x86_64")]
1323        if Tip5::avx512_is_available() {
1324            // SAFETY (all `unsafe` blocks): The required CPU features were
1325            // detected above.
1326            let mut batch = unsafe { avx512::Tip5Batch::new(Domain::VariableLength) };
1327            for (i, &element) in state.iter().enumerate() {
1328                unsafe { batch.set(i, [element; avx512::BATCH_SIZE]) };
1329            }
1330            unsafe { batch.permutation() };
1331            for (i, &element) in expected.iter().enumerate() {
1332                assert_eq!([element; avx512::BATCH_SIZE], unsafe { batch.get(i) });
1333            }
1334        }
1335    }
1336
1337    #[macro_rules_attr::apply(test)]
1338    fn calculate_differential_uniformity() {
1339        // cargo test calculate_differential_uniformity -- --include-ignored --nocapture
1340        // addition-differential
1341        let count_satisfiers_fermat = |a, b| {
1342            (0..(1 << 8))
1343                .map(|x| {
1344                    u16::from(
1345                        (256 + Tip5::offset_fermat_cube_map((x + a) & 0xff)
1346                            - Tip5::offset_fermat_cube_map(x))
1347                            & 0xff
1348                            == b,
1349                    )
1350                })
1351                .sum()
1352        };
1353        let du_fermat: u16 = (1..256)
1354            .into_par_iter()
1355            .map(|a| {
1356                (1..256)
1357                    .map(|b| count_satisfiers_fermat(a, b))
1358                    .max()
1359                    .unwrap()
1360            })
1361            .max()
1362            .unwrap();
1363        println!("additive differential uniformity for fermat cube map: {du_fermat}");
1364
1365        // bitwise-differential
1366        let count_satisfiers_fermat_bitwise = |a: u16, b: u16| {
1367            (0..(1 << 8))
1368                .map(|x| {
1369                    u16::from(
1370                        (Tip5::offset_fermat_cube_map(x ^ a) ^ Tip5::offset_fermat_cube_map(x))
1371                            == b,
1372                    )
1373                })
1374                .sum::<u16>()
1375        };
1376        for a in 1..256 {
1377            for b in 1..256 {
1378                let num_satisfiers = count_satisfiers_fermat_bitwise(a, b);
1379                if num_satisfiers == 256 {
1380                    println!("a: {a}, b: {b} -> 256 satisfiers");
1381                }
1382            }
1383        }
1384        let du_fermat_bitwise: u16 = (1..256)
1385            .into_par_iter()
1386            .map(|a| {
1387                (1..256)
1388                    .map(|b| count_satisfiers_fermat_bitwise(a, b))
1389                    .max()
1390                    .unwrap()
1391            })
1392            .max()
1393            .unwrap();
1394        println!("bitwise differential uniformity for fermat cube map: {du_fermat_bitwise}");
1395    }
1396
1397    #[macro_rules_attr::apply(test)]
1398    fn calculate_approximation_quality() {
1399        let mut fermat_cubed = [0u16; 65536];
1400        let mut bfield_cubed = [0u16; 65536];
1401        for i in 0..65536 {
1402            let cubed = (i as u64) * (i as u64) * (i as u64);
1403            fermat_cubed[i] = (cubed % 65537) as u16;
1404            bfield_cubed[i] = (cubed & 0xffff) as u16;
1405        }
1406        let equal_count = fermat_cubed
1407            .iter()
1408            .zip(bfield_cubed.iter())
1409            .filter(|(a, b)| a == b)
1410            .count();
1411        println!("agreement with low-degree function: {equal_count}");
1412    }
1413
1414    /// A snapshot of a [`Digest`], in hexadecimal form. The exact procedure to
1415    /// arrive at the `Digest` in question is involved and best read from the
1416    /// code below.
1417    ///
1418    /// It is paramount that Tip5 has at least some snapshot tests. The reason
1419    /// is that (with the current implementation), conditional compilation
1420    /// changes the concrete instructions that make up Tip5. Testing different
1421    /// binaries for equivalent behavior is easiest when that behavior is pinned
1422    /// through snapshots.
1423    const MAGIC_SNAPSHOT_HEX: &str =
1424        "109cc2fe453bd9962f754b96d8f5b919b60af030940a275f5540da195fef65ee651c1b6fa19b2c6a";
1425
1426    #[macro_rules_attr::apply(test)]
1427    fn hash10_test_vectors_snapshot() {
1428        let mut preimage = [BFieldElement::ZERO; RATE];
1429        for i in 0..6 {
1430            let digest = Tip5::hash_10(&preimage);
1431            preimage[i..i + Digest::LEN].copy_from_slice(&digest);
1432        }
1433        let final_digest = Digest::new(Tip5::hash_10(&preimage)).to_hex();
1434        assert_eq!(MAGIC_SNAPSHOT_HEX, final_digest);
1435    }
1436
1437    #[macro_rules_attr::apply(test)]
1438    fn hash_varlen_test_vectors() {
1439        let mut digest_sum = [BFieldElement::ZERO; Digest::LEN];
1440        for i in 0..20 {
1441            let preimage = (0..i).map(BFieldElement::new).collect_vec();
1442            let digest = Tip5::hash_varlen(&preimage);
1443            digest_sum
1444                .iter_mut()
1445                .zip(digest.values().iter())
1446                .for_each(|(s, d)| *s += *d);
1447        }
1448
1449        let final_digest = Digest::new(digest_sum).to_hex();
1450        assert_eq!(
1451            "efbafa86622a9c69652f8a1c4ffd734f021ad23a0a8085412a877de0f9170b18ea4ff69b6fff9a03",
1452            final_digest,
1453        );
1454    }
1455
1456    #[macro_rules_attr::apply(test)]
1457    fn snapshot() {
1458        let state = [
1459            0x0000_000f_ffff_fff0,
1460            0x0000_0000_ffff_ffff,
1461            0x0000_0000_ffff_ffff,
1462            0x0000_0028_ffff_ffd7,
1463            0x0000_0006_ffff_fff9,
1464            0x0000_0002_ffff_fffd,
1465            0x0000_0000_ffff_ffff,
1466            0x0000_0030_ffff_ffcf,
1467            0x0000_0397_ffff_fc68,
1468            0x0000_000f_ffff_fff0,
1469            0x316b_fb72_3638_2123,
1470            0x216f_521b_66ef_83f5,
1471            0x5689_d7b3_63f5_2df0,
1472            0xeb2f_59e3_aeae_25fc,
1473            0xb082_99d2_77cb_b4dc,
1474            0xcbe3_d9fd_c534_9140,
1475        ]
1476        .map(BFieldElement::from_raw_u64);
1477
1478        let mut tip5 = Tip5 { state };
1479        tip5.permutation();
1480
1481        let expected = [
1482            0x15d3_8ea9_29f6_632a,
1483            0xf988_e509_ff73_8bb4,
1484            0x48bc_dfae_88a2_e9f3,
1485            0x8733_9e83_2daa_c02a,
1486            0x511e_4126_8150_fdac,
1487        ]
1488        .map(BFieldElement::from_raw_u64);
1489
1490        assert_eq!(&expected, &tip5.state[0..5]);
1491
1492        // The same vector must hold in every lane of the batched permutation.
1493        // It is a regression test for the modular reduction, so it must not be
1494        // skipped silently on the machines that matter.
1495        #[cfg(target_arch = "x86_64")]
1496        if Tip5::avx512_is_available() {
1497            // SAFETY (all `unsafe` blocks): The required CPU features were
1498            // detected above.
1499            let mut batch = unsafe { avx512::Tip5Batch::new(Domain::VariableLength) };
1500            for (i, &element) in state.iter().enumerate() {
1501                unsafe { batch.set(i, [element; avx512::BATCH_SIZE]) };
1502            }
1503            unsafe { batch.permutation() };
1504            for (i, &element) in expected.iter().enumerate() {
1505                assert_eq!([element; avx512::BATCH_SIZE], unsafe { batch.get(i) });
1506            }
1507        }
1508    }
1509
1510    fn manual_hash_varlen(preimage: &[BFieldElement]) -> Digest {
1511        let mut sponge = Tip5::init();
1512        sponge.pad_and_absorb_all(preimage);
1513        let squeeze_result = sponge.squeeze();
1514
1515        Digest::new((&squeeze_result[..Digest::LEN]).try_into().unwrap())
1516    }
1517
1518    #[macro_rules_attr::apply(test)]
1519    fn hash_var_len_equivalence_edge_cases() {
1520        for preimage_length in 0..=11 {
1521            let preimage = vec![BFieldElement::new(42); preimage_length];
1522            let hash_varlen_digest = Tip5::hash_varlen(&preimage);
1523
1524            let digest_through_pad_squeeze_absorb = manual_hash_varlen(&preimage);
1525            assert_eq!(digest_through_pad_squeeze_absorb, hash_varlen_digest);
1526        }
1527    }
1528
1529    #[macro_rules_attr::apply(proptest)]
1530    fn hash_var_len_equivalence(#[strategy(arb())] preimage: Vec<BFieldElement>) {
1531        let hash_varlen_digest = Tip5::hash_varlen(&preimage);
1532        let digest_through_pad_squeeze_absorb = manual_hash_varlen(&preimage);
1533        prop_assert_eq!(digest_through_pad_squeeze_absorb, hash_varlen_digest);
1534    }
1535
1536    #[macro_rules_attr::apply(test)]
1537    fn test_linearity_of_mds() {
1538        type SpongeState = [BFieldElement; STATE_SIZE];
1539
1540        let mds_procedure = |state| {
1541            let mut sponge = Tip5 { state };
1542            sponge.mds_cyclomul();
1543            sponge.state
1544        };
1545
1546        let a: BFieldElement = random_elements(1)[0];
1547        let b: BFieldElement = random_elements(1)[0];
1548
1549        let mul_procedure = |u: SpongeState, v: SpongeState| -> SpongeState {
1550            let mul_result = u.iter().zip(&v).map(|(&uu, &vv)| a * uu + b * vv);
1551            mul_result.collect_vec().try_into().unwrap()
1552        };
1553
1554        let u: SpongeState = random_elements(STATE_SIZE).try_into().unwrap();
1555        let v: SpongeState = random_elements(STATE_SIZE).try_into().unwrap();
1556        let w = mul_procedure(u, v);
1557
1558        let u = mds_procedure(u);
1559        let v = mds_procedure(v);
1560        let w = mds_procedure(w);
1561
1562        let w_ = mul_procedure(u, v);
1563
1564        assert_eq!(w, w_);
1565    }
1566
1567    #[macro_rules_attr::apply(test)]
1568    fn test_mds_circulancy() {
1569        let mut sponge = Tip5::init();
1570        sponge.state = [BFieldElement::ZERO; STATE_SIZE];
1571        sponge.state[0] = BFieldElement::ONE;
1572
1573        sponge.mds_cyclomul();
1574
1575        let mut mat_first_row = [BFieldElement::ZERO; STATE_SIZE];
1576        mat_first_row[0] = sponge.state[0];
1577        for (i, first_row_elem) in mat_first_row.iter_mut().enumerate().skip(1) {
1578            *first_row_elem = sponge.state[STATE_SIZE - i];
1579        }
1580
1581        println!(
1582            "mds matrix first row: {:?}",
1583            mat_first_row.map(|b| b.value())
1584        );
1585
1586        let initial_state: [BFieldElement; STATE_SIZE] =
1587            random_elements(STATE_SIZE).try_into().unwrap();
1588
1589        let mut mv = [BFieldElement::ZERO; STATE_SIZE];
1590        for i in 0..STATE_SIZE {
1591            for j in 0..STATE_SIZE {
1592                mv[i] += mat_first_row[(STATE_SIZE - i + j) % STATE_SIZE] * initial_state[j];
1593            }
1594        }
1595
1596        let mut sponge_2 = Tip5::init();
1597        sponge_2.state = initial_state;
1598        sponge_2.mds_cyclomul();
1599
1600        assert_eq!(sponge_2.state, mv);
1601    }
1602
1603    #[macro_rules_attr::apply(test)]
1604    fn test_complex_karatsuba() {
1605        const N: usize = 4;
1606        let mut f = [(0i64, 0i64); N];
1607        let mut g = [(0i64, 0i64); N];
1608        for i in 0..N {
1609            f[i].0 = i as i64;
1610            g[i].0 = 1;
1611            f[i].1 = 1;
1612            g[i].1 = i as i64;
1613        }
1614
1615        let h0 = Tip5::complex_karatsuba4(f, g);
1616        let h1 = [(0, 1), (0, 2), (0, 4), (0, 8), (0, 13), (0, 14), (0, 10)];
1617
1618        assert_eq!(h0, h1);
1619    }
1620
1621    #[macro_rules_attr::apply(test)]
1622    fn test_complex_product() {
1623        let mut rng = rand::rng();
1624        let mut random_small_i64 = || (rng.next_u32() % (1 << 16)) as i64;
1625        for _ in 0..1000 {
1626            let f = (random_small_i64(), random_small_i64());
1627            let g = (random_small_i64(), random_small_i64());
1628            let h0 = Tip5::complex_product(f, g);
1629            let h1 = (f.0 * g.0 - f.1 * g.1, f.0 * g.1 + f.1 * g.0);
1630            assert_eq!(h1, h0);
1631        }
1632    }
1633
1634    #[macro_rules_attr::apply(proptest)]
1635    fn sample_scalars(mut tip5: Tip5, #[strategy(0_usize..=100)] num_scalars: usize) {
1636        tip5.permutation(); // remove any 0s that exist due to shrinking
1637
1638        let scalars = tip5.sample_scalars(num_scalars);
1639        prop_assert_eq!(num_scalars, scalars.len());
1640
1641        let product = scalars
1642            .into_iter()
1643            .fold(XFieldElement::ONE, XFieldElement::mul);
1644        prop_assert_ne!(product, XFieldElement::ZERO);
1645    }
1646
1647    #[macro_rules_attr::apply(proptest)]
1648    fn test_mds_matrix_mul_methods_agree(state: [BFieldElement; STATE_SIZE]) {
1649        let mut sponge_cyclomut = Tip5 { state };
1650        let mut sponge_generated = Tip5 { state };
1651
1652        sponge_cyclomut.mds_cyclomul();
1653        sponge_generated.mds_generated();
1654
1655        prop_assert_eq!(
1656            &sponge_cyclomut,
1657            &sponge_generated,
1658            "cyclomul =/= generated\n{}\n{}",
1659            sponge_cyclomut.state.into_iter().join(","),
1660            sponge_generated.state.into_iter().join(",")
1661        );
1662    }
1663
1664    /// Only meaningful on CPUs with the relevant AVX-512 extensions; passes
1665    /// trivially otherwise.
1666    #[cfg(target_arch = "x86_64")]
1667    #[macro_rules_attr::apply(proptest)]
1668    fn avx512_permutation_agrees_with_scalar_permutation(state: [BFieldElement; STATE_SIZE]) {
1669        if !Tip5::avx512_is_available() {
1670            return Ok(());
1671        }
1672
1673        let mut scalar = Tip5 { state };
1674        let mut avx512 = Tip5 { state };
1675        for round_index in 0..NUM_ROUNDS {
1676            scalar.round(round_index);
1677            // SAFETY: The required CPU features were detected above.
1678            unsafe { avx512.round_avx512(round_index) };
1679            prop_assert_eq!(&scalar, &avx512, "round {}", round_index);
1680        }
1681
1682        let mut scalar_permutation = Tip5 { state };
1683        let mut avx512_permutation = Tip5 { state };
1684        scalar_permutation.permutation_scalar();
1685        // SAFETY: The required CPU features were detected above.
1686        unsafe { avx512_permutation.permutation_avx512() };
1687        prop_assert_eq!(scalar_permutation, avx512_permutation);
1688    }
1689
1690    /// Only meaningful on CPUs with the relevant AVX-512 extensions; passes
1691    /// trivially otherwise.
1692    #[cfg(target_arch = "x86_64")]
1693    #[macro_rules_attr::apply(proptest)]
1694    fn batched_permutation_agrees_with_naive_permutation(
1695        states: [[BFieldElement; STATE_SIZE]; avx512::BATCH_SIZE],
1696    ) {
1697        if !Tip5::avx512_is_available() {
1698            return Ok(());
1699        }
1700
1701        // SAFETY (all `unsafe` blocks): The required CPU features were
1702        // detected above.
1703        let mut batch = unsafe { avx512::Tip5Batch::new(Domain::VariableLength) };
1704        for i in 0..STATE_SIZE {
1705            unsafe { batch.set(i, states.map(|state| state[i])) };
1706        }
1707        unsafe { batch.permutation() };
1708
1709        for (lane, state) in states.into_iter().enumerate() {
1710            let mut naive = naive::NaiveTip5 { state };
1711            naive.permutation();
1712            for (i, &naive_element) in naive.state.iter().enumerate() {
1713                let batched_element = unsafe { batch.get(i) }[lane];
1714                prop_assert_eq!(
1715                    naive_element,
1716                    batched_element,
1717                    "lane {}, element {}",
1718                    lane,
1719                    i
1720                );
1721            }
1722        }
1723    }
1724
1725    #[macro_rules_attr::apply(proptest)]
1726    fn hash_varlen_many_agrees_with_hash_varlen(
1727        #[strategy(proptest::collection::vec(proptest::collection::vec(arb(), 0..35), 0..30))]
1728        inputs: Vec<Vec<BFieldElement>>,
1729    ) {
1730        let inputs = inputs.iter().map(Vec::as_slice).collect_vec();
1731        let individually = inputs
1732            .iter()
1733            .map(|input| Tip5::hash_varlen(input))
1734            .collect_vec();
1735        prop_assert_eq!(individually.clone(), Tip5::hash_varlen_many(&inputs));
1736        prop_assert_eq!(individually, Tip5::hash_varlen_many_scalar(&inputs));
1737    }
1738
1739    #[macro_rules_attr::apply(proptest)]
1740    fn hash_varlen_many_agrees_with_hash_varlen_for_equal_lengths(
1741        #[strategy(0_usize..35)] _len: usize,
1742        #[strategy(proptest::collection::vec(proptest::collection::vec(arb(), #_len), 0..30))]
1743        inputs: Vec<Vec<BFieldElement>>,
1744    ) {
1745        let inputs = inputs.iter().map(Vec::as_slice).collect_vec();
1746        let individually = inputs
1747            .iter()
1748            .map(|input| Tip5::hash_varlen(input))
1749            .collect_vec();
1750        prop_assert_eq!(individually, Tip5::hash_varlen_many(&inputs));
1751    }
1752
1753    #[macro_rules_attr::apply(proptest)]
1754    fn hash_pair_many_agrees_with_hash_pair(
1755        #[strategy(proptest::collection::vec(arb(), 0..30))] pairs: Vec<[Digest; 2]>,
1756    ) {
1757        let individually = pairs
1758            .iter()
1759            .map(|&[l, r]| Tip5::hash_pair(l, r))
1760            .collect_vec();
1761        prop_assert_eq!(individually.clone(), Tip5::hash_pair_many_scalar(&pairs));
1762        prop_assert_eq!(individually, Tip5::hash_pair_many(&pairs));
1763    }
1764
1765    #[macro_rules_attr::apply(test)]
1766    fn avx512_reduction_regression_vector() {
1767        let state = crate::bfe_array![
1768            0x0000_000f_ffff_fff0_u64,
1769            0x0000_0000_ffff_ffff_u64,
1770            0x0000_0000_ffff_ffff_u64,
1771            0x0000_0028_ffff_ffd7_u64,
1772            0x0000_0006_ffff_fff9_u64,
1773            0x0000_0002_ffff_fffd_u64,
1774            0x0000_0000_ffff_ffff_u64,
1775            0x0000_0030_ffff_ffcf_u64,
1776            0x0000_0397_ffff_fc68_u64,
1777            0x0000_000f_ffff_fff0_u64,
1778            0x316b_fb72_3638_2123_u64,
1779            0x216f_521b_66ef_83f5_u64,
1780            0x5689_d7b3_63f5_2df0_u64,
1781            0xeb2f_59e3_aeae_25fc_u64,
1782            0xb082_99d2_77cb_b4dc_u64,
1783            0xcbe3_d9fd_c534_9140_u64,
1784        ];
1785        let expected = crate::bfe_array![
1786            0x231d_a775_9f66_2fd9_u64,
1787            0x9bad_454d_eb24_c327_u64,
1788            0x8b9f_67d4_0440_bc7e_u64,
1789            0xfd56_ad41_eb9d_2514_u64,
1790            0xeb4e_1c7c_2b83_5d30_u64,
1791            0x9e86_05fe_9bc2_891f_u64,
1792            0x0da2_ae9f_e4a6_d684_u64,
1793            0x3944_688b_8da6_e25d_u64,
1794            0x4bc6_d6d4_e868_ecbe_u64,
1795            0x2293_50dc_cc9c_4677_u64,
1796            0xe532_c7c1_200d_0349_u64,
1797            0x92c2_1e76_49ef_2e40_u64,
1798            0xa24f_5d2f_dfd9_fc52_u64,
1799            0xd391_72f3_43db_c0f0_u64,
1800            0x4236_2d8f_3a6e_9720_u64,
1801            0xf7ee_7105_bb49_bd3e_u64,
1802        ];
1803
1804        let mut naive = naive::NaiveTip5 { state };
1805        naive.permutation();
1806        assert_eq!(expected, naive.state);
1807
1808        let mut scalar = Tip5 { state };
1809        for round_index in 0..NUM_ROUNDS {
1810            scalar.round(round_index);
1811        }
1812        assert_eq!(expected, scalar.state);
1813
1814        let mut dispatching = Tip5 { state };
1815        dispatching.permutation();
1816        assert_eq!(expected, dispatching.state);
1817
1818        #[cfg(target_arch = "x86_64")]
1819        if Tip5::avx512_is_available() {
1820            // SAFETY (all `unsafe` blocks): The required CPU features were
1821            // detected above.
1822            let mut batch = unsafe { avx512::Tip5Batch::new(Domain::VariableLength) };
1823            for (i, &element) in state.iter().enumerate() {
1824                unsafe { batch.set(i, [element; avx512::BATCH_SIZE]) };
1825            }
1826            unsafe { batch.permutation() };
1827            for (i, &element) in expected.iter().enumerate() {
1828                assert_eq!([element; avx512::BATCH_SIZE], unsafe { batch.get(i) });
1829            }
1830        }
1831    }
1832
1833    #[macro_rules_attr::apply(test)]
1834    fn tip5_hasher_trait_snapshot_test() {
1835        let mut hasher = Tip5::init();
1836        let data = b"hello world";
1837        hasher.write(data);
1838        assert_eq!(hasher.finish(), 2267905471610932299);
1839    }
1840
1841    #[macro_rules_attr::apply(proptest)]
1842    fn tip5_hasher_consumes_small_data(#[filter(!#bytes.is_empty())] bytes: Vec<u8>) {
1843        let mut hasher = Tip5::init();
1844        bytes.hash(&mut hasher);
1845
1846        prop_assert_ne!(Tip5::init().finish(), hasher.finish());
1847    }
1848
1849    #[macro_rules_attr::apply(proptest)]
1850    fn appending_small_data_to_big_data_changes_tip5_hash(
1851        #[any(size_range(2_000..8_000).lift())] big_data: Vec<u8>,
1852        #[filter(!#small_data.is_empty())] small_data: Vec<u8>,
1853    ) {
1854        let mut hasher = Tip5::init();
1855        big_data.hash(&mut hasher);
1856        let big_data_hash = hasher.finish();
1857
1858        // finish doesn't terminate the hasher; see it's documentation
1859        small_data.hash(&mut hasher);
1860        let all_data_hash = hasher.finish();
1861
1862        prop_assert_ne!(big_data_hash, all_data_hash);
1863    }
1864
1865    #[macro_rules_attr::apply(proptest)]
1866    fn tip5_trace_starts_with_initial_state_and_is_equivalent_to_permutation(mut tip5: Tip5) {
1867        let [first, .., last] = tip5.clone().trace();
1868        prop_assert_eq!(first, tip5.state);
1869
1870        tip5.permutation();
1871        prop_assert_eq!(last, tip5.state);
1872    }
1873}