twenty_first/math/
tip5.rs

1use std::hash::Hasher;
2
3use arbitrary::Arbitrary;
4use get_size2::GetSize;
5use itertools::Itertools;
6use num_traits::ConstOne;
7use num_traits::ConstZero;
8use serde::Deserialize;
9use serde::Serialize;
10
11use crate::math::b_field_element::BFieldElement;
12pub use crate::math::digest::Digest;
13use crate::math::mds::generated_function;
14use crate::math::x_field_element::EXTENSION_DEGREE;
15use crate::prelude::BFieldCodec;
16use crate::prelude::XFieldElement;
17use crate::util_types::sponge::Domain;
18use crate::util_types::sponge::Sponge;
19
20pub const STATE_SIZE: usize = 16;
21pub const NUM_SPLIT_AND_LOOKUP: usize = 4;
22pub const LOG2_STATE_SIZE: usize = 4;
23pub const CAPACITY: usize = 6;
24pub const RATE: usize = 10;
25pub const NUM_ROUNDS: usize = 5;
26
27/// The lookup table with a high algebraic degree used in the TIP-5 permutation. To verify its
28/// correctness, see the test “lookup_table_is_correct.”
29pub const LOOKUP_TABLE: [u8; 256] = [
30    0, 7, 26, 63, 124, 215, 85, 254, 214, 228, 45, 185, 140, 173, 33, 240, 29, 177, 176, 32, 8,
31    110, 87, 202, 204, 99, 150, 106, 230, 14, 235, 128, 213, 239, 212, 138, 23, 130, 208, 6, 44,
32    71, 93, 116, 146, 189, 251, 81, 199, 97, 38, 28, 73, 179, 95, 84, 152, 48, 35, 119, 49, 88,
33    242, 3, 148, 169, 72, 120, 62, 161, 166, 83, 175, 191, 137, 19, 100, 129, 112, 55, 221, 102,
34    218, 61, 151, 237, 68, 164, 17, 147, 46, 234, 203, 216, 22, 141, 65, 57, 123, 12, 244, 54, 219,
35    231, 96, 77, 180, 154, 5, 253, 133, 165, 98, 195, 205, 134, 245, 30, 9, 188, 59, 142, 186, 197,
36    181, 144, 92, 31, 224, 163, 111, 74, 58, 69, 113, 196, 67, 246, 225, 10, 121, 50, 60, 157, 90,
37    122, 2, 250, 101, 75, 178, 159, 24, 36, 201, 11, 243, 132, 198, 190, 114, 233, 39, 52, 21, 209,
38    108, 238, 91, 187, 18, 104, 194, 37, 153, 34, 200, 143, 126, 155, 236, 118, 64, 80, 172, 89,
39    94, 193, 135, 183, 86, 107, 252, 13, 167, 206, 136, 220, 207, 103, 171, 160, 76, 182, 227, 217,
40    158, 56, 174, 4, 66, 109, 139, 162, 184, 211, 249, 47, 125, 232, 117, 43, 16, 42, 127, 20, 241,
41    25, 149, 105, 156, 51, 53, 168, 145, 247, 223, 79, 78, 226, 15, 222, 82, 115, 70, 210, 27, 41,
42    1, 170, 40, 131, 192, 229, 248, 255,
43];
44
45/// The round constants used in the Tip5 permutation. To verify their correctness, see the test
46/// “round_constants_are_correct.”
47pub const ROUND_CONSTANTS: [BFieldElement; NUM_ROUNDS * STATE_SIZE] = [
48    BFieldElement::new(13630775303355457758),
49    BFieldElement::new(16896927574093233874),
50    BFieldElement::new(10379449653650130495),
51    BFieldElement::new(1965408364413093495),
52    BFieldElement::new(15232538947090185111),
53    BFieldElement::new(15892634398091747074),
54    BFieldElement::new(3989134140024871768),
55    BFieldElement::new(2851411912127730865),
56    BFieldElement::new(8709136439293758776),
57    BFieldElement::new(3694858669662939734),
58    BFieldElement::new(12692440244315327141),
59    BFieldElement::new(10722316166358076749),
60    BFieldElement::new(12745429320441639448),
61    BFieldElement::new(17932424223723990421),
62    BFieldElement::new(7558102534867937463),
63    BFieldElement::new(15551047435855531404),
64    BFieldElement::new(17532528648579384106),
65    BFieldElement::new(5216785850422679555),
66    BFieldElement::new(15418071332095031847),
67    BFieldElement::new(11921929762955146258),
68    BFieldElement::new(9738718993677019874),
69    BFieldElement::new(3464580399432997147),
70    BFieldElement::new(13408434769117164050),
71    BFieldElement::new(264428218649616431),
72    BFieldElement::new(4436247869008081381),
73    BFieldElement::new(4063129435850804221),
74    BFieldElement::new(2865073155741120117),
75    BFieldElement::new(5749834437609765994),
76    BFieldElement::new(6804196764189408435),
77    BFieldElement::new(17060469201292988508),
78    BFieldElement::new(9475383556737206708),
79    BFieldElement::new(12876344085611465020),
80    BFieldElement::new(13835756199368269249),
81    BFieldElement::new(1648753455944344172),
82    BFieldElement::new(9836124473569258483),
83    BFieldElement::new(12867641597107932229),
84    BFieldElement::new(11254152636692960595),
85    BFieldElement::new(16550832737139861108),
86    BFieldElement::new(11861573970480733262),
87    BFieldElement::new(1256660473588673495),
88    BFieldElement::new(13879506000676455136),
89    BFieldElement::new(10564103842682358721),
90    BFieldElement::new(16142842524796397521),
91    BFieldElement::new(3287098591948630584),
92    BFieldElement::new(685911471061284805),
93    BFieldElement::new(5285298776918878023),
94    BFieldElement::new(18310953571768047354),
95    BFieldElement::new(3142266350630002035),
96    BFieldElement::new(549990724933663297),
97    BFieldElement::new(4901984846118077401),
98    BFieldElement::new(11458643033696775769),
99    BFieldElement::new(8706785264119212710),
100    BFieldElement::new(12521758138015724072),
101    BFieldElement::new(11877914062416978196),
102    BFieldElement::new(11333318251134523752),
103    BFieldElement::new(3933899631278608623),
104    BFieldElement::new(16635128972021157924),
105    BFieldElement::new(10291337173108950450),
106    BFieldElement::new(4142107155024199350),
107    BFieldElement::new(16973934533787743537),
108    BFieldElement::new(11068111539125175221),
109    BFieldElement::new(17546769694830203606),
110    BFieldElement::new(5315217744825068993),
111    BFieldElement::new(4609594252909613081),
112    BFieldElement::new(3350107164315270407),
113    BFieldElement::new(17715942834299349177),
114    BFieldElement::new(9600609149219873996),
115    BFieldElement::new(12894357635820003949),
116    BFieldElement::new(4597649658040514631),
117    BFieldElement::new(7735563950920491847),
118    BFieldElement::new(1663379455870887181),
119    BFieldElement::new(13889298103638829706),
120    BFieldElement::new(7375530351220884434),
121    BFieldElement::new(3502022433285269151),
122    BFieldElement::new(9231805330431056952),
123    BFieldElement::new(9252272755288523725),
124    BFieldElement::new(10014268662326746219),
125    BFieldElement::new(15565031632950843234),
126    BFieldElement::new(1209725273521819323),
127    BFieldElement::new(6024642864597845108),
128];
129
130/// The defining, first column of the (circulant) MDS matrix.
131/// Derived from the SHA-256 hash of the ASCII string “Tip5” by dividing the digest into 16-bit
132/// chunks.
133pub const MDS_MATRIX_FIRST_COLUMN: [i64; STATE_SIZE] = [
134    61402, 1108, 28750, 33823, 7454, 43244, 53865, 12034, 56951, 27521, 41351, 40901, 12021, 59689,
135    26798, 17845,
136];
137
138#[derive(
139    Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, GetSize, BFieldCodec, Arbitrary,
140)]
141pub struct Tip5 {
142    pub state: [BFieldElement; STATE_SIZE],
143}
144
145impl Tip5 {
146    #[inline]
147    pub const fn new(domain: Domain) -> Self {
148        use Domain::*;
149
150        let mut state = [BFieldElement::ZERO; STATE_SIZE];
151
152        match domain {
153            VariableLength => (),
154            FixedLength => {
155                let mut i = RATE;
156                while i < STATE_SIZE {
157                    state[i] = BFieldElement::ONE;
158                    i += 1;
159                }
160            }
161        }
162
163        Self { state }
164    }
165
166    #[inline]
167    pub const fn offset_fermat_cube_map(x: u16) -> u16 {
168        let xx = (x + 1) as u64;
169        let xxx = xx * xx * xx;
170        ((xxx + 256) % 257) as u16
171    }
172
173    #[inline]
174    fn split_and_lookup(element: &mut BFieldElement) {
175        // let value = element.value();
176        let mut bytes = element.raw_bytes();
177
178        #[allow(clippy::needless_range_loop)] // faster like so
179        for i in 0..8 {
180            // bytes[i] = Self::offset_fermat_cube_map(bytes[i].into()) as u8;
181            bytes[i] = LOOKUP_TABLE[bytes[i] as usize];
182        }
183
184        *element = BFieldElement::from_raw_bytes(&bytes);
185    }
186
187    #[inline(always)]
188    fn fast_cyclomul16(f: [i64; 16], g: [i64; 16]) -> [i64; 16] {
189        const N: usize = 8;
190        let mut ff_lo = [0i64; N];
191        let mut gg_lo = [0i64; N];
192        let mut ff_hi = [0i64; N];
193        let mut gg_hi = [0i64; N];
194        for i in 0..N {
195            ff_lo[i] = f[i] + f[i + N];
196            ff_hi[i] = f[i] - f[i + N];
197            gg_lo[i] = g[i] + g[i + N];
198            gg_hi[i] = g[i] - g[i + N];
199        }
200
201        let hh_lo = Self::fast_cyclomul8(ff_lo, gg_lo);
202        let hh_hi = Self::complex_negacyclomul8(ff_hi, gg_hi);
203
204        let mut hh = [0i64; 2 * N];
205        for i in 0..N {
206            hh[i] = (hh_lo[i] + hh_hi[i]) >> 1;
207            hh[i + N] = (hh_lo[i] - hh_hi[i]) >> 1;
208        }
209
210        hh
211    }
212
213    #[inline(always)]
214    fn complex_sum<const N: usize>(f: [(i64, i64); N], g: [(i64, i64); N]) -> [(i64, i64); N] {
215        let mut h = [(0i64, 0i64); N];
216        for i in 0..N {
217            h[i].0 = f[i].0 + g[i].0;
218            h[i].1 = f[i].1 + g[i].1;
219        }
220        h
221    }
222
223    #[inline(always)]
224    fn complex_diff<const N: usize>(f: [(i64, i64); N], g: [(i64, i64); N]) -> [(i64, i64); N] {
225        let mut h = [(0i64, 0i64); N];
226        for i in 0..N {
227            h[i].0 = f[i].0 - g[i].0;
228            h[i].1 = f[i].1 - g[i].1;
229        }
230        h
231    }
232
233    #[inline(always)]
234    fn complex_product(f: (i64, i64), g: (i64, i64)) -> (i64, i64) {
235        // don't karatsuba; this is faster
236        (f.0 * g.0 - f.1 * g.1, f.0 * g.1 + f.1 * g.0)
237    }
238
239    #[inline(always)]
240    fn complex_karatsuba2(f: [(i64, i64); 2], g: [(i64, i64); 2]) -> [(i64, i64); 3] {
241        const N: usize = 1;
242
243        let ff = (f[0].0 + f[1].0, f[0].1 + f[1].1);
244        let gg = (g[0].0 + g[1].0, g[0].1 + g[1].1);
245
246        let lo = Self::complex_product(f[0], g[0]);
247        let hi = Self::complex_product(f[1], g[1]);
248
249        let ff_times_gg = Self::complex_product(ff, gg);
250        let lo_plus_hi = (lo.0 + hi.0, lo.1 + hi.1);
251
252        let li = (ff_times_gg.0 - lo_plus_hi.0, ff_times_gg.1 - lo_plus_hi.1);
253
254        let mut result = [(0i64, 0i64); 4 * N - 1];
255        result[0].0 += lo.0;
256        result[0].1 += lo.1;
257        result[N].0 += li.0;
258        result[N].1 += li.1;
259        result[2 * N].0 += hi.0;
260        result[2 * N].1 += hi.1;
261
262        result
263    }
264
265    #[inline(always)]
266    fn complex_karatsuba4(f: [(i64, i64); 4], g: [(i64, i64); 4]) -> [(i64, i64); 7] {
267        const N: usize = 2;
268
269        let ff = Self::complex_sum::<2>(f[..N].try_into().unwrap(), f[N..].try_into().unwrap());
270        let gg = Self::complex_sum::<2>(g[..N].try_into().unwrap(), g[N..].try_into().unwrap());
271
272        let lo = Self::complex_karatsuba2(f[..N].try_into().unwrap(), g[..N].try_into().unwrap());
273        let hi = Self::complex_karatsuba2(f[N..].try_into().unwrap(), g[N..].try_into().unwrap());
274
275        let li = Self::complex_diff::<3>(
276            Self::complex_karatsuba2(ff, gg),
277            Self::complex_sum::<3>(lo, hi),
278        );
279
280        let mut result = [(0i64, 0i64); 4 * N - 1];
281        for i in 0..(2 * N - 1) {
282            result[i].0 = lo[i].0;
283            result[i].1 = lo[i].1;
284        }
285        for i in 0..(2 * N - 1) {
286            result[N + i].0 += li[i].0;
287            result[N + i].1 += li[i].1;
288        }
289        for i in 0..(2 * N - 1) {
290            result[2 * N + i].0 += hi[i].0;
291            result[2 * N + i].1 += hi[i].1;
292        }
293
294        result
295    }
296
297    #[inline(always)]
298    fn complex_negacyclomul8(f: [i64; 8], g: [i64; 8]) -> [i64; 8] {
299        const N: usize = 4;
300
301        let mut f0 = [(0i64, 0i64); N];
302        // let mut f1 = [(0i64,0i64); N];
303        let mut g0 = [(0i64, 0i64); N];
304        // let mut g1 = [(0i64,0i64); N];
305
306        for i in 0..N {
307            f0[i] = (f[i], -f[N + i]);
308            // f1[i] = (f[i],  f[N+i]);
309            g0[i] = (g[i], -g[N + i]);
310            // g1[i] = (g[i],  g[N+i]);
311        }
312
313        let h0 = Self::complex_karatsuba4(f0, g0);
314        // h1 = complex_karatsuba(f1, g1)
315
316        // h = a * h0 + b * h1
317        // where a = 2^-1 * (i*X^(n/2) + 1)
318        // and  b = 2^-1 * (-i*X^(n/2) + 1)
319
320        let mut h = [0i64; 3 * N - 1];
321        for i in 0..(2 * N - 1) {
322            h[i] += h0[i].0;
323            h[i + N] -= h0[i].1;
324            // h[i] += h0[i].0 / 2
325            // h[i+N] -= h0[i].1 / 2
326            // h[i] += h1[i].0 / 2
327            // h[i+N] -= h1[i].1 / 2
328        }
329
330        let mut hh = [0i64; 2 * N];
331        for i in 0..(2 * N) {
332            hh[i] += h[i];
333        }
334        for i in (2 * N)..(3 * N - 1) {
335            hh[i - 2 * N] -= h[i];
336        }
337
338        hh
339    }
340
341    #[inline(always)]
342    fn complex_negacyclomul4(f: [i64; 4], g: [i64; 4]) -> [i64; 4] {
343        const N: usize = 2;
344
345        let mut f0 = [(0i64, 0i64); N];
346        // let mut f1 = [(0i64,0i64); N];
347        let mut g0 = [(0i64, 0i64); N];
348        // let mut g1 = [(0i64,0i64); N];
349
350        for i in 0..N {
351            f0[i] = (f[i], -f[N + i]);
352            // f1[i] = (f[i],  f[N+i]);
353            g0[i] = (g[i], -g[N + i]);
354            // g1[i] = (g[i],  g[N+i]);
355        }
356
357        let h0 = Self::complex_karatsuba2(f0, g0);
358        // h1 = complex_karatsuba(f1, g1)
359
360        // h = a * h0 + b * h1
361        // where a = 2^-1 * (i*X^(n/2) + 1)
362        // and  b = 2^-1 * (-i*X^(n/2) + 1)
363
364        let mut h = [0i64; 4 * N - 1];
365        for i in 0..(2 * N - 1) {
366            h[i] += h0[i].0;
367            h[i + N] -= h0[i].1;
368            // h[i] += h0[i].0 / 2
369            // h[i+N] -= h0[i].1 / 2
370            // h[i] += h1[i].0 / 2
371            // h[i+N] -= h1[i].1 / 2
372        }
373
374        let mut hh = [0i64; 2 * N];
375        for i in 0..(2 * N) {
376            hh[i] += h[i];
377        }
378        for i in (2 * N)..(4 * N - 1) {
379            hh[i - 2 * N] -= h[i];
380        }
381
382        hh
383    }
384
385    #[inline(always)]
386    fn complex_negacyclomul2(f: [i64; 2], g: [i64; 2]) -> [i64; 2] {
387        let f0 = (f[0], -f[1]);
388        let g0 = (g[0], -g[1]);
389
390        let h0 = Self::complex_product(f0, g0);
391
392        [h0.0, -h0.1]
393    }
394
395    #[inline(always)]
396    fn fast_cyclomul8(f: [i64; 8], g: [i64; 8]) -> [i64; 8] {
397        const N: usize = 4;
398        let mut ff_lo = [0i64; N];
399        let mut gg_lo = [0i64; N];
400        let mut ff_hi = [0i64; N];
401        let mut gg_hi = [0i64; N];
402        for i in 0..N {
403            ff_lo[i] = f[i] + f[i + N];
404            ff_hi[i] = f[i] - f[i + N];
405            gg_lo[i] = g[i] + g[i + N];
406            gg_hi[i] = g[i] - g[i + N];
407        }
408
409        let hh_lo = Self::fast_cyclomul4(ff_lo, gg_lo);
410        let hh_hi = Self::complex_negacyclomul4(ff_hi, gg_hi);
411
412        let mut hh = [0i64; 2 * N];
413        for i in 0..N {
414            hh[i] = (hh_lo[i] + hh_hi[i]) >> 1;
415            hh[i + N] = (hh_lo[i] - hh_hi[i]) >> 1;
416        }
417
418        hh
419    }
420
421    #[inline(always)]
422    fn fast_cyclomul4(f: [i64; 4], g: [i64; 4]) -> [i64; 4] {
423        const N: usize = 2;
424        let mut ff_lo = [0i64; N];
425        let mut gg_lo = [0i64; N];
426        let mut ff_hi = [0i64; N];
427        let mut gg_hi = [0i64; N];
428        for i in 0..N {
429            ff_lo[i] = f[i] + f[i + N];
430            ff_hi[i] = f[i] - f[i + N];
431            gg_lo[i] = g[i] + g[i + N];
432            gg_hi[i] = g[i] - g[i + N];
433        }
434
435        let hh_lo = Self::fast_cyclomul2(ff_lo, gg_lo);
436        let hh_hi = Self::complex_negacyclomul2(ff_hi, gg_hi);
437
438        let mut hh = [0i64; 2 * N];
439        for i in 0..N {
440            hh[i] = (hh_lo[i] + hh_hi[i]) >> 1;
441            hh[i + N] = (hh_lo[i] - hh_hi[i]) >> 1;
442        }
443
444        hh
445    }
446
447    #[inline(always)]
448    fn fast_cyclomul2(f: [i64; 2], g: [i64; 2]) -> [i64; 2] {
449        let ff_lo = f[0] + f[1];
450        let ff_hi = f[0] - f[1];
451        let gg_lo = g[0] + g[1];
452        let gg_hi = g[0] - g[1];
453
454        let hh_lo = ff_lo * gg_lo;
455        let hh_hi = ff_hi * gg_hi;
456
457        let mut hh = [0i64; 2];
458        hh[0] = (hh_lo + hh_hi) >> 1;
459        hh[1] = (hh_lo - hh_hi) >> 1;
460
461        hh
462    }
463
464    #[inline(always)]
465    #[allow(dead_code)]
466    fn mds_cyclomul(&mut self) {
467        let mut result = [BFieldElement::ZERO; STATE_SIZE];
468
469        let mut lo: [i64; STATE_SIZE] = [0; STATE_SIZE];
470        let mut hi: [i64; STATE_SIZE] = [0; STATE_SIZE];
471        for (i, b) in self.state.iter().enumerate() {
472            hi[i] = (b.raw_u64() >> 32) as i64;
473            lo[i] = (b.raw_u64() as u32) as i64;
474        }
475
476        lo = Self::fast_cyclomul16(lo, MDS_MATRIX_FIRST_COLUMN);
477        hi = Self::fast_cyclomul16(hi, MDS_MATRIX_FIRST_COLUMN);
478
479        for r in 0..STATE_SIZE {
480            let s = lo[r] as u128 + ((hi[r] as u128) << 32);
481            let s_hi = (s >> 64) as u64;
482            let s_lo = s as u64;
483            let z = (s_hi << 32) - s_hi;
484            let (res, over) = s_lo.overflowing_add(z);
485
486            result[r] = BFieldElement::from_raw_u64(
487                res.wrapping_add(0u32.wrapping_sub(over as u32) as u64),
488            );
489        }
490        self.state = result;
491    }
492
493    #[inline(always)]
494    fn mds_generated(&mut self) {
495        let mut lo: [u64; STATE_SIZE] = [0; STATE_SIZE];
496        let mut hi: [u64; STATE_SIZE] = [0; STATE_SIZE];
497        for i in 0..STATE_SIZE {
498            let b = self.state[i].raw_u64();
499            hi[i] = b >> 32;
500            lo[i] = b & 0xffffffffu64;
501        }
502
503        lo = generated_function(&lo);
504        hi = generated_function(&hi);
505
506        for r in 0..STATE_SIZE {
507            let s = (lo[r] >> 4) as u128 + ((hi[r] as u128) << 28);
508
509            let s_hi = (s >> 64) as u64;
510            let s_lo = s as u64;
511
512            let (res, over) = s_lo.overflowing_add(s_hi * 0xffffffffu64);
513
514            self.state[r] =
515                BFieldElement::from_raw_u64(if over { res + 0xffffffffu64 } else { res });
516        }
517    }
518
519    #[inline(always)]
520    #[allow(clippy::needless_range_loop)]
521    fn sbox_layer(&mut self) {
522        for i in 0..NUM_SPLIT_AND_LOOKUP {
523            Self::split_and_lookup(&mut self.state[i]);
524        }
525
526        for i in NUM_SPLIT_AND_LOOKUP..STATE_SIZE {
527            let sq = self.state[i] * self.state[i];
528            let qu = sq * sq;
529            self.state[i] *= sq * qu;
530        }
531    }
532
533    #[inline(always)]
534    fn round(&mut self, round_index: usize) {
535        self.sbox_layer();
536        self.mds_generated();
537        for i in 0..STATE_SIZE {
538            self.state[i] += ROUND_CONSTANTS[round_index * STATE_SIZE + i];
539        }
540    }
541
542    #[inline(always)]
543    pub fn permutation(&mut self) {
544        for i in 0..NUM_ROUNDS {
545            self.round(i);
546        }
547    }
548
549    /// Functionally equivalent to [`permutation`](Self::permutation). Returns the trace of
550    /// applying the permutation; that is, the initial state of the sponge as well as its state
551    /// after each round.
552    pub fn trace(&mut self) -> [[BFieldElement; STATE_SIZE]; 1 + NUM_ROUNDS] {
553        let mut trace = [[BFieldElement::ZERO; STATE_SIZE]; 1 + NUM_ROUNDS];
554
555        trace[0] = self.state;
556        for i in 0..NUM_ROUNDS {
557            self.round(i);
558            trace[1 + i] = self.state;
559        }
560
561        trace
562    }
563
564    /// Hash 10 elements, or two digests. There is no padding because
565    /// the input length is fixed.
566    pub fn hash_10(input: &[BFieldElement; 10]) -> [BFieldElement; Digest::LEN] {
567        let mut sponge = Self::new(Domain::FixedLength);
568
569        // absorb once
570        sponge.state[..10].copy_from_slice(input);
571
572        sponge.permutation();
573
574        // squeeze once
575        sponge.state[..Digest::LEN].try_into().unwrap()
576    }
577
578    pub fn hash_pair(left: Digest, right: Digest) -> Digest {
579        let mut sponge = Self::new(Domain::FixedLength);
580        sponge.state[..Digest::LEN].copy_from_slice(&left.values());
581        sponge.state[Digest::LEN..2 * Digest::LEN].copy_from_slice(&right.values());
582
583        sponge.permutation();
584
585        let digest_values = sponge.state[..Digest::LEN].try_into().unwrap();
586        Digest::new(digest_values)
587    }
588
589    /// Thin wrapper around [`hash_varlen`](Self::hash_varlen).
590    pub fn hash<T: BFieldCodec>(value: &T) -> Digest {
591        Self::hash_varlen(&value.encode())
592    }
593
594    /// Hash a variable-length sequence of [`BFieldElement`].
595    ///
596    /// - Apply the correct padding
597    /// - [Sponge::pad_and_absorb_all()]
598    /// - [Sponge::squeeze()] once.
599    pub fn hash_varlen(input: &[BFieldElement]) -> Digest {
600        let mut sponge = Self::init();
601        sponge.pad_and_absorb_all(input);
602        let produce: [BFieldElement; crate::util_types::sponge::RATE] = sponge.squeeze();
603
604        Digest::new((&produce[..Digest::LEN]).try_into().unwrap())
605    }
606
607    /// Produce `num_indices` random integer values in the range `[0, upper_bound)`. The
608    /// `upper_bound` must be a power of 2.
609    ///
610    /// This method uses von Neumann rejection sampling.
611    /// Specifically, if the top 32 bits of a BFieldElement are all ones, then the bottom 32 bits
612    /// are not uniformly distributed, and so they are dropped. This method invokes squeeze until
613    /// enough uniform u32s have been sampled.
614    pub fn sample_indices(&mut self, upper_bound: u32, num_indices: usize) -> Vec<u32> {
615        debug_assert!(upper_bound.is_power_of_two());
616        let mut indices = vec![];
617        let mut squeezed_elements = vec![];
618        while indices.len() != num_indices {
619            if squeezed_elements.is_empty() {
620                squeezed_elements = self.squeeze().into_iter().rev().collect_vec();
621            }
622            let element = squeezed_elements.pop().unwrap();
623            if element != BFieldElement::new(BFieldElement::MAX) {
624                indices.push(element.value() as u32 % upper_bound);
625            }
626        }
627        indices
628    }
629
630    /// Produce `num_elements` random [`XFieldElement`] values.
631    ///
632    /// If `num_elements` is not divisible by [`RATE`][rate], spill the remaining elements of the
633    /// last [`squeeze`][Sponge::squeeze].
634    ///
635    /// [rate]: Sponge::RATE
636    pub fn sample_scalars(&mut self, num_elements: usize) -> Vec<XFieldElement> {
637        let num_squeezes = (num_elements * EXTENSION_DEGREE).div_ceil(Self::RATE);
638        debug_assert!(
639            num_elements * EXTENSION_DEGREE <= num_squeezes * Self::RATE,
640            "need {} elements but getting {}",
641            num_elements * EXTENSION_DEGREE,
642            num_squeezes * Self::RATE
643        );
644        (0..num_squeezes)
645            .flat_map(|_| self.squeeze())
646            .collect_vec()
647            .chunks(3)
648            .take(num_elements)
649            .map(|elem| XFieldElement::new([elem[0], elem[1], elem[2]]))
650            .collect()
651    }
652}
653
654impl Sponge for Tip5 {
655    const RATE: usize = RATE;
656
657    fn init() -> Self {
658        Self::new(Domain::VariableLength)
659    }
660
661    fn absorb(&mut self, input: [BFieldElement; RATE]) {
662        self.state[..RATE]
663            .iter_mut()
664            .zip_eq(&input)
665            .for_each(|(a, &b)| *a = b);
666
667        self.permutation();
668    }
669
670    fn squeeze(&mut self) -> [BFieldElement; RATE] {
671        let produce: [BFieldElement; RATE] = (&self.state[..RATE]).try_into().unwrap();
672        self.permutation();
673
674        produce
675    }
676}
677
678impl Hasher for Tip5 {
679    fn finish(&self) -> u64 {
680        self.state[0].value()
681    }
682
683    fn write(&mut self, bytes: &[u8]) {
684        let bfield_elements = bytes.chunks(BFieldElement::BYTES).map(|chunk| {
685            let mut buffer = [0u8; BFieldElement::BYTES];
686            buffer[..chunk.len()].copy_from_slice(chunk);
687            BFieldElement::new(u64::from_le_bytes(buffer))
688        });
689
690        for chunk in bfield_elements.chunks(Tip5::RATE).into_iter() {
691            let mut buffer = [BFieldElement::ZERO; Tip5::RATE];
692            for (buffer_elem, chunk_elem) in buffer.iter_mut().zip(chunk) {
693                *buffer_elem = chunk_elem;
694            }
695            self.absorb(buffer)
696        }
697    }
698}
699
700#[cfg(test)]
701pub(crate) mod tip5_tests {
702    use std::hash::Hash;
703    use std::ops::Mul;
704
705    use insta::assert_snapshot;
706    use prop::sample::size_range;
707    use proptest::prelude::*;
708    use proptest_arbitrary_interop::arb;
709    use rand::Rng;
710    use rand::RngCore;
711    use rayon::prelude::IntoParallelIterator;
712    use rayon::prelude::ParallelIterator;
713    use test_strategy::proptest;
714
715    use super::*;
716    use crate::math::other::random_elements;
717    use crate::math::x_field_element::XFieldElement;
718
719    impl Tip5 {
720        pub(crate) fn randomly_seeded() -> Self {
721            let mut sponge = Self::init();
722            let mut rng = rand::rng();
723            sponge.absorb(rng.random());
724            sponge
725        }
726    }
727
728    #[test]
729    fn get_size_test() {
730        assert_eq!(
731            STATE_SIZE * BFieldElement::ZERO.get_size(),
732            Tip5::randomly_seeded().get_size()
733        );
734    }
735
736    #[test]
737    fn lookup_table_is_correct() {
738        let table: [u8; 256] = (0..256)
739            .map(|t| Tip5::offset_fermat_cube_map(t as u16) as u8)
740            .collect_vec()
741            .try_into()
742            .unwrap();
743
744        println!(
745            "Entire lookup table:\n{}",
746            table.iter().map(|t| format!("{t:02x}")).join(", ")
747        );
748
749        (0_usize..256).for_each(|i| {
750            assert_eq!(
751                LOOKUP_TABLE[i], table[i],
752                "Lookup tables must agree at every index, including index {i}."
753            )
754        });
755    }
756
757    #[test]
758    fn round_constants_are_correct() {
759        let to_int = |bytes: &[u8]| {
760            bytes
761                .iter()
762                .take(16)
763                .enumerate()
764                .map(|(i, b)| (*b as u128) << (8 * i))
765                .sum::<u128>()
766        };
767        let round_constants = (0..NUM_ROUNDS * STATE_SIZE)
768            .map(|i| ["Tip5".to_string().as_bytes(), &[i as u8]].concat())
769            .map(|bytes| blake3::hash(&bytes))
770            .map(|hash| *hash.as_bytes())
771            .map(|bytes| to_int(&bytes))
772            .map(|i| (i % BFieldElement::P as u128) as u64)
773            .map(BFieldElement::from_raw_u64)
774            .collect_vec();
775
776        println!(
777            "In case you changed something, here are all round constants:\n{}",
778            round_constants.iter().map(|c| format!("{c}")).join(", ")
779        );
780
781        (0_usize..NUM_ROUNDS * STATE_SIZE).for_each(|i| {
782            assert_eq!(
783                ROUND_CONSTANTS[i], round_constants[i],
784                "Round constants must agree at every index, including index {i}."
785            )
786        });
787    }
788
789    #[test]
790    fn test_fermat_cube_map_is_permutation() {
791        let mut touched = [false; 256];
792        for i in 0..256 {
793            touched[Tip5::offset_fermat_cube_map(i) as usize] = true;
794        }
795        assert!(touched.iter().all(|t| *t));
796        assert_eq!(Tip5::offset_fermat_cube_map(0), 0);
797        assert_eq!(Tip5::offset_fermat_cube_map(255), 255);
798    }
799
800    #[test]
801    fn calculate_differential_uniformity() {
802        // cargo test calculate_differential_uniformity -- --include-ignored --nocapture
803        // addition-differential
804        let count_satisfiers_fermat = |a, b| {
805            (0..(1 << 8))
806                .map(|x| {
807                    u16::from(
808                        (256 + Tip5::offset_fermat_cube_map((x + a) & 0xff)
809                            - Tip5::offset_fermat_cube_map(x))
810                            & 0xff
811                            == b,
812                    )
813                })
814                .sum()
815        };
816        let du_fermat: u16 = (1..256)
817            .into_par_iter()
818            .map(|a| {
819                (1..256)
820                    .map(|b| count_satisfiers_fermat(a, b))
821                    .max()
822                    .unwrap()
823            })
824            .max()
825            .unwrap();
826        println!("additive differential uniformity for fermat cube map: {du_fermat}");
827
828        // bitwise-differential
829        let count_satisfiers_fermat_bitwise = |a: u16, b: u16| {
830            (0..(1 << 8))
831                .map(|x| {
832                    u16::from(
833                        (Tip5::offset_fermat_cube_map(x ^ a) ^ Tip5::offset_fermat_cube_map(x))
834                            == b,
835                    )
836                })
837                .sum::<u16>()
838        };
839        for a in 1..256 {
840            for b in 1..256 {
841                let num_satisfiers = count_satisfiers_fermat_bitwise(a, b);
842                if num_satisfiers == 256 {
843                    println!("a: {a}, b: {b} -> 256 satisfiers");
844                }
845            }
846        }
847        let du_fermat_bitwise: u16 = (1..256)
848            .into_par_iter()
849            .map(|a| {
850                (1..256)
851                    .map(|b| count_satisfiers_fermat_bitwise(a, b))
852                    .max()
853                    .unwrap()
854            })
855            .max()
856            .unwrap();
857        println!("bitwise differential uniformity for fermat cube map: {du_fermat_bitwise}");
858    }
859
860    #[test]
861    fn calculate_approximation_quality() {
862        let mut fermat_cubed = [0u16; 65536];
863        let mut bfield_cubed = [0u16; 65536];
864        for i in 0..65536 {
865            let cubed = (i as u64) * (i as u64) * (i as u64);
866            fermat_cubed[i] = (cubed % 65537) as u16;
867            bfield_cubed[i] = (cubed & 0xffff) as u16;
868        }
869        let equal_count = fermat_cubed
870            .iter()
871            .zip(bfield_cubed.iter())
872            .filter(|(a, b)| a == b)
873            .count();
874        println!("agreement with low-degree function: {equal_count}");
875    }
876
877    #[test]
878    fn hash10_test_vectors() {
879        let mut preimage = [BFieldElement::ZERO; RATE];
880        let mut digest: [BFieldElement; Digest::LEN];
881        for i in 0..6 {
882            digest = Tip5::hash_10(&preimage);
883            println!(
884                "{:?} -> {:?}",
885                preimage.iter().map(|b| b.value()).collect_vec(),
886                digest.iter().map(|b| b.value()).collect_vec()
887            );
888            preimage[i..Digest::LEN + i].copy_from_slice(&digest);
889        }
890        digest = Tip5::hash_10(&preimage);
891        println!(
892            "{:?} -> {:?}",
893            preimage.iter().map(|b| b.value()).collect_vec(),
894            digest.iter().map(|b| b.value()).collect_vec()
895        );
896        let final_digest = [
897            10869784347448351760,
898            1853783032222938415,
899            6856460589287344822,
900            17178399545409290325,
901            7650660984651717733,
902        ]
903        .map(BFieldElement::new);
904        assert_eq!(
905            digest,
906            final_digest,
907            "expected: {:?}\nbut got: {:?}",
908            final_digest.map(|d| d.value()),
909            digest.map(|d| d.value()),
910        )
911    }
912
913    #[test]
914    fn hash_varlen_test_vectors() {
915        let mut digest_sum = [BFieldElement::ZERO; Digest::LEN];
916        for i in 0..20 {
917            let preimage = (0..i).map(BFieldElement::new).collect_vec();
918            let digest = Tip5::hash_varlen(&preimage);
919            println!(
920                "{:?} -> {:?}",
921                preimage.iter().map(|b| b.value()).collect_vec(),
922                digest.values().iter().map(|b| b.value()).collect_vec()
923            );
924            digest_sum
925                .iter_mut()
926                .zip(digest.values().iter())
927                .for_each(|(s, d)| *s += *d);
928        }
929        println!(
930            "sum of digests: {:?}",
931            digest_sum.iter().map(|b| b.value()).collect_vec()
932        );
933        let expected_sum = [
934            7610004073009036015,
935            5725198067541094245,
936            4721320565792709122,
937            1732504843634706218,
938            259800783350288362,
939        ]
940        .map(BFieldElement::new);
941        assert_eq!(
942            expected_sum,
943            digest_sum,
944            "expected: {:?}\nbut got: {:?}",
945            expected_sum.map(|s| s.value()),
946            digest_sum.map(|s| s.value())
947        );
948    }
949
950    fn manual_hash_varlen(preimage: &[BFieldElement]) -> Digest {
951        let mut sponge = Tip5::init();
952        sponge.pad_and_absorb_all(preimage);
953        let squeeze_result = sponge.squeeze();
954
955        Digest::new((&squeeze_result[..Digest::LEN]).try_into().unwrap())
956    }
957
958    #[test]
959    fn hash_var_len_equivalence_corner_cases() {
960        for preimage_length in 0..=11 {
961            let preimage = vec![BFieldElement::new(42); preimage_length];
962            let hash_varlen_digest = Tip5::hash_varlen(&preimage);
963
964            let digest_through_pad_squeeze_absorb = manual_hash_varlen(&preimage);
965            assert_eq!(digest_through_pad_squeeze_absorb, hash_varlen_digest);
966        }
967    }
968
969    #[proptest]
970    fn hash_var_len_equivalence(#[strategy(arb())] preimage: Vec<BFieldElement>) {
971        let hash_varlen_digest = Tip5::hash_varlen(&preimage);
972        let digest_through_pad_squeeze_absorb = manual_hash_varlen(&preimage);
973        prop_assert_eq!(digest_through_pad_squeeze_absorb, hash_varlen_digest);
974    }
975
976    #[test]
977    fn test_linearity_of_mds() {
978        type SpongeState = [BFieldElement; STATE_SIZE];
979
980        let mds_procedure = |state| {
981            let mut sponge = Tip5 { state };
982            sponge.mds_cyclomul();
983            sponge.state
984        };
985
986        let a: BFieldElement = random_elements(1)[0];
987        let b: BFieldElement = random_elements(1)[0];
988
989        let mul_procedure = |u: SpongeState, v: SpongeState| -> SpongeState {
990            let mul_result = u.iter().zip(&v).map(|(&uu, &vv)| a * uu + b * vv);
991            mul_result.collect_vec().try_into().unwrap()
992        };
993
994        let u: SpongeState = random_elements(STATE_SIZE).try_into().unwrap();
995        let v: SpongeState = random_elements(STATE_SIZE).try_into().unwrap();
996        let w = mul_procedure(u, v);
997
998        let u = mds_procedure(u);
999        let v = mds_procedure(v);
1000        let w = mds_procedure(w);
1001
1002        let w_ = mul_procedure(u, v);
1003
1004        assert_eq!(w, w_);
1005    }
1006
1007    #[test]
1008    fn test_mds_circulancy() {
1009        let mut sponge = Tip5::init();
1010        sponge.state = [BFieldElement::ZERO; STATE_SIZE];
1011        sponge.state[0] = BFieldElement::ONE;
1012
1013        sponge.mds_generated();
1014
1015        let mut mat_first_row = [BFieldElement::ZERO; STATE_SIZE];
1016        mat_first_row[0] = sponge.state[0];
1017        for (i, first_row_elem) in mat_first_row.iter_mut().enumerate().skip(1) {
1018            *first_row_elem = sponge.state[STATE_SIZE - i];
1019        }
1020
1021        println!(
1022            "mds matrix first row: {:?}",
1023            mat_first_row.map(|b| b.value())
1024        );
1025
1026        let initial_state: [BFieldElement; STATE_SIZE] =
1027            random_elements(STATE_SIZE).try_into().unwrap();
1028
1029        let mut mv = [BFieldElement::ZERO; STATE_SIZE];
1030        for i in 0..STATE_SIZE {
1031            for j in 0..STATE_SIZE {
1032                mv[i] += mat_first_row[(STATE_SIZE - i + j) % STATE_SIZE] * initial_state[j];
1033            }
1034        }
1035
1036        let mut sponge_2 = Tip5::init();
1037        sponge_2.state = initial_state;
1038        sponge_2.mds_generated();
1039
1040        assert_eq!(sponge_2.state, mv);
1041    }
1042
1043    #[test]
1044    fn test_complex_karatsuba() {
1045        const N: usize = 4;
1046        let mut f = [(0i64, 0i64); N];
1047        let mut g = [(0i64, 0i64); N];
1048        for i in 0..N {
1049            f[i].0 = i as i64;
1050            g[i].0 = 1;
1051            f[i].1 = 1;
1052            g[i].1 = i as i64;
1053        }
1054
1055        let h0 = Tip5::complex_karatsuba4(f, g);
1056        let h1 = [(0, 1), (0, 2), (0, 4), (0, 8), (0, 13), (0, 14), (0, 10)];
1057
1058        assert_eq!(h0, h1);
1059    }
1060
1061    #[test]
1062    fn test_complex_product() {
1063        let mut rng = rand::rng();
1064        let mut random_small_i64 = || (rng.next_u32() % (1 << 16)) as i64;
1065        for _ in 0..1000 {
1066            let f = (random_small_i64(), random_small_i64());
1067            let g = (random_small_i64(), random_small_i64());
1068            let h0 = Tip5::complex_product(f, g);
1069            let h1 = (f.0 * g.0 - f.1 * g.1, f.0 * g.1 + f.1 * g.0);
1070            assert_eq!(h1, h0);
1071        }
1072    }
1073
1074    #[test]
1075    fn sample_scalars_test() {
1076        let mut sponge = Tip5::randomly_seeded();
1077        let mut product = XFieldElement::ONE;
1078        for amount in 0..=4 {
1079            let scalars = sponge.sample_scalars(amount);
1080            assert_eq!(amount, scalars.len());
1081            product *= scalars
1082                .into_iter()
1083                .fold(XFieldElement::ONE, XFieldElement::mul);
1084        }
1085        assert_ne!(product, XFieldElement::ZERO); // false failure with prob ~2^{-192}
1086    }
1087
1088    #[test]
1089    fn test_mds_agree() {
1090        let mut rng = rand::rng();
1091        let initial_state: [BFieldElement; STATE_SIZE] = (0..STATE_SIZE)
1092            .map(|_| BFieldElement::new(rng.random_range(0..10)))
1093            .collect_vec()
1094            .try_into()
1095            .unwrap();
1096
1097        let mut sponge_cyclomut = Tip5 {
1098            state: initial_state,
1099        };
1100        let mut sponge_generated = Tip5 {
1101            state: initial_state,
1102        };
1103
1104        sponge_cyclomut.mds_cyclomul();
1105        sponge_generated.mds_generated();
1106
1107        assert_eq!(
1108            sponge_cyclomut,
1109            sponge_generated,
1110            "cyclomul =/= generated\n{}\n{}",
1111            sponge_cyclomut.state.into_iter().join(","),
1112            sponge_generated.state.into_iter().join(",")
1113        );
1114    }
1115
1116    #[test]
1117    fn tip5_hasher_trait_test() {
1118        let mut hasher = Tip5::init();
1119        let data = b"hello world";
1120        hasher.write(data);
1121        assert_snapshot!(hasher.finish(), @"2267905471610932299");
1122    }
1123
1124    #[proptest]
1125    fn tip5_hasher_consumes_small_data(#[filter(!#bytes.is_empty())] bytes: Vec<u8>) {
1126        let mut hasher = Tip5::init();
1127        bytes.hash(&mut hasher);
1128
1129        prop_assert_ne!(Tip5::init().finish(), hasher.finish());
1130    }
1131
1132    #[proptest]
1133    fn appending_small_data_to_big_data_changes_tip5_hash(
1134        #[any(size_range(2_000..8_000).lift())] big_data: Vec<u8>,
1135        #[filter(!#small_data.is_empty())] small_data: Vec<u8>,
1136    ) {
1137        let mut hasher = Tip5::init();
1138        big_data.hash(&mut hasher);
1139        let big_data_hash = hasher.finish();
1140
1141        // finish doesn't terminate the hasher; see it's documentation
1142        small_data.hash(&mut hasher);
1143        let all_data_hash = hasher.finish();
1144
1145        prop_assert_ne!(big_data_hash, all_data_hash);
1146    }
1147
1148    #[proptest]
1149    fn tip5_trace_starts_with_initial_state_and_is_equivalent_to_permutation(
1150        #[strategy(arb())] mut tip5: Tip5,
1151    ) {
1152        let [first, .., last] = tip5.clone().trace();
1153        prop_assert_eq!(first, tip5.state);
1154
1155        tip5.permutation();
1156        prop_assert_eq!(last, tip5.state);
1157    }
1158}