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
27pub 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
45pub 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
130pub 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 mut bytes = element.raw_bytes();
177
178 #[allow(clippy::needless_range_loop)] for i in 0..8 {
180 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 (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 g0 = [(0i64, 0i64); N];
304 for i in 0..N {
307 f0[i] = (f[i], -f[N + i]);
308 g0[i] = (g[i], -g[N + i]);
310 }
312
313 let h0 = Self::complex_karatsuba4(f0, g0);
314 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 }
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 g0 = [(0i64, 0i64); N];
348 for i in 0..N {
351 f0[i] = (f[i], -f[N + i]);
352 g0[i] = (g[i], -g[N + i]);
354 }
356
357 let h0 = Self::complex_karatsuba2(f0, g0);
358 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 }
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 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 pub fn hash_10(input: &[BFieldElement; 10]) -> [BFieldElement; Digest::LEN] {
567 let mut sponge = Self::new(Domain::FixedLength);
568
569 sponge.state[..10].copy_from_slice(input);
571
572 sponge.permutation();
573
574 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 pub fn hash<T: BFieldCodec>(value: &T) -> Digest {
591 Self::hash_varlen(&value.encode())
592 }
593
594 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 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 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 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 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); }
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 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}