1use alloc::string::String;
2use alloc::vec;
3use alloc::vec::Vec;
4
5use p3_field::{
6 BasedVectorSpace, PrimeField, PrimeField32, absorb_radix_bits, max_absorb_injective_limbs,
7 reduce_packed, split_pf_to_field_order_limbs, squeeze_field_order_num_limbs,
8};
9use p3_symmetric::{CryptographicPermutation, Hash};
10
11use crate::{CanObserve, CanSample, CanSampleBits, DuplexChallenger, FieldChallenger};
12
13#[derive(Clone, Debug)]
32pub struct MultiField32Challenger<F, PF, P, const WIDTH: usize, const RATE: usize>
33where
34 F: PrimeField32,
35 PF: PrimeField,
36 P: CryptographicPermutation<[PF; WIDTH]>,
37{
38 inner: DuplexChallenger<PF, P, WIDTH, RATE>,
40 f_buffer: Vec<F>,
41 f_squeeze_buffer: Vec<F>,
43}
44
45impl<F, PF, P, const WIDTH: usize, const RATE: usize> MultiField32Challenger<F, PF, P, WIDTH, RATE>
46where
47 F: PrimeField32,
48 PF: PrimeField,
49 P: CryptographicPermutation<[PF; WIDTH]>,
50{
51 #[inline]
54 #[must_use]
55 pub const fn absorb_radix_bits(&self) -> u32 {
56 absorb_radix_bits::<F>()
57 }
58
59 #[inline]
63 #[must_use]
64 pub fn absorb_num_f_elms(&self) -> usize {
65 max_absorb_injective_limbs::<F, PF>()
66 }
67
68 #[inline]
73 #[must_use]
74 pub fn squeeze_num_f_elms(&self) -> usize {
75 squeeze_field_order_num_limbs::<PF, F>()
76 }
77
78 #[inline]
80 #[must_use]
81 pub const fn pending_f_squeeze_len(&self) -> usize {
82 self.f_squeeze_buffer.len()
83 }
84
85 pub fn new(permutation: P) -> Result<Self, String> {
86 if F::order() >= PF::order() {
87 return Err(String::from("F::order() must be less than PF::order()"));
88 }
89 if RATE >= WIDTH {
90 return Err(String::from("RATE must be less than WIDTH"));
91 }
92
93 Ok(Self {
94 inner: DuplexChallenger::new(permutation),
95 f_buffer: vec![],
96 f_squeeze_buffer: vec![],
97 })
98 }
99
100 fn flush_f_if_non_empty(&mut self) {
101 if self.f_buffer.is_empty() {
102 return;
103 }
104 let n_in = self.f_buffer.len();
105 let absorb_n = self.absorb_num_f_elms();
106 assert!(n_in <= absorb_n * RATE);
107 let rb = self.absorb_radix_bits();
108 let packed: Vec<PF> = self
109 .f_buffer
110 .chunks(absorb_n)
111 .map(|chunk| reduce_packed(chunk, rb))
112 .collect();
113 self.inner.absorb_rate_padded_with_tag(&packed, n_in as u8);
114 self.f_buffer.clear();
115 self.f_squeeze_buffer.clear();
116 }
117
118 fn refill_f_squeeze_from_inner(&mut self) {
119 self.f_squeeze_buffer.clear();
120 let squeeze_n = self.squeeze_num_f_elms();
121 for &pf in &self.inner.output_buffer {
122 self.f_squeeze_buffer
123 .extend(split_pf_to_field_order_limbs::<PF, F>(pf, squeeze_n));
124 }
125 self.inner.output_buffer.clear();
129 }
130}
131
132impl<F, PF, P, const WIDTH: usize, const RATE: usize> FieldChallenger<F>
133 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
134where
135 F: PrimeField32,
136 PF: PrimeField,
137 P: CryptographicPermutation<[PF; WIDTH]>,
138{
139}
140
141impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanObserve<F>
142 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
143where
144 F: PrimeField32,
145 PF: PrimeField,
146 P: CryptographicPermutation<[PF; WIDTH]>,
147{
148 fn observe(&mut self, value: F) {
149 self.inner.output_buffer.clear();
150 self.f_squeeze_buffer.clear();
151 self.f_buffer.push(value);
152 if self.f_buffer.len() == self.absorb_num_f_elms() * RATE {
153 self.flush_f_if_non_empty();
154 }
155 }
156}
157
158impl<F, PF, const N: usize, P, const WIDTH: usize, const RATE: usize> CanObserve<[F; N]>
159 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
160where
161 F: PrimeField32,
162 PF: PrimeField,
163 P: CryptographicPermutation<[PF; WIDTH]>,
164{
165 fn observe(&mut self, values: [F; N]) {
166 for value in values {
167 self.observe(value);
168 }
169 }
170}
171
172impl<F, PF, const N: usize, P, const WIDTH: usize, const RATE: usize> CanObserve<Hash<F, PF, N>>
173 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
174where
175 F: PrimeField32,
176 PF: PrimeField,
177 P: CryptographicPermutation<[PF; WIDTH]>,
178{
179 fn observe(&mut self, values: Hash<F, PF, N>) {
180 self.inner.output_buffer.clear();
181 self.f_squeeze_buffer.clear();
182 self.flush_f_if_non_empty();
183
184 let words: &[PF; N] = values.as_ref();
185
186 for chunk in words.chunks(RATE) {
187 self.inner
188 .absorb_rate_padded_with_tag(chunk, chunk.len() as u8);
189 self.f_squeeze_buffer.clear();
190 }
191 }
192}
193
194impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanObserve<Vec<Vec<F>>>
196 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
197where
198 F: PrimeField32,
199 PF: PrimeField,
200 P: CryptographicPermutation<[PF; WIDTH]>,
201{
202 fn observe(&mut self, valuess: Vec<Vec<F>>) {
203 for values in valuess {
204 for value in values {
205 self.observe(value);
206 }
207 }
208 }
209}
210
211impl<F, EF, PF, P, const WIDTH: usize, const RATE: usize> CanSample<EF>
212 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
213where
214 F: PrimeField32,
215 EF: BasedVectorSpace<F>,
216 PF: PrimeField,
217 P: CryptographicPermutation<[PF; WIDTH]>,
218{
219 fn sample(&mut self) -> EF {
220 EF::from_basis_coefficients_fn(|_| {
221 self.flush_f_if_non_empty();
222 if self.f_squeeze_buffer.is_empty() {
223 if !self.inner.input_buffer.is_empty() || self.inner.output_buffer.is_empty() {
224 self.inner.duplexing();
225 }
226 self.refill_f_squeeze_from_inner();
227 }
228 self.f_squeeze_buffer
229 .pop()
230 .expect("Output buffer should be non-empty")
231 })
232 }
233}
234
235impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanSampleBits<usize>
236 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
237where
238 F: PrimeField32,
239 PF: PrimeField,
240 P: CryptographicPermutation<[PF; WIDTH]>,
241{
242 fn sample_bits(&mut self, bits: usize) -> usize {
251 assert!(bits < (usize::BITS as usize));
252 assert!((1 << bits) < F::ORDER_U32);
253 let rand_f: F = self.sample();
254 let rand_usize = rand_f.as_canonical_u32() as usize;
255 rand_usize & ((1 << bits) - 1)
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use p3_baby_bear::BabyBear;
262 use p3_field::{
263 Field, PrimeCharacteristicRing, PrimeField, injective_pack_bits, split_pf_to_packed_limbs,
264 squeeze_field_order_num_limbs,
265 };
266 use p3_goldilocks::Goldilocks;
267 use p3_symmetric::Permutation;
268
269 use super::*;
270
271 const WIDTH: usize = 8;
272 const RATE: usize = 4;
273
274 type F = BabyBear;
275 type PF = Goldilocks;
276
277 #[derive(Clone)]
278 struct TestPermutation;
279
280 impl Permutation<[PF; WIDTH]> for TestPermutation {
281 fn permute_mut(&self, input: &mut [PF; WIDTH]) {
282 for (i, val) in input.iter_mut().enumerate() {
283 *val = PF::from_u8((i + 1) as u8);
284 }
285 }
286 }
287
288 impl CryptographicPermutation<[PF; WIDTH]> for TestPermutation {}
289
290 #[derive(Clone)]
293 struct MixingPermutation;
294
295 impl Permutation<[PF; WIDTH]> for MixingPermutation {
296 fn permute_mut(&self, input: &mut [PF; WIDTH]) {
297 let sum: PF = input.iter().copied().sum();
298 for (i, val) in input.iter_mut().enumerate() {
299 *val = sum + PF::from_u8((i + 1) as u8);
300 }
301 }
302 }
303
304 impl CryptographicPermutation<[PF; WIDTH]> for MixingPermutation {}
305
306 #[test]
307 fn test_packing() {
308 let c = MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
309 assert_eq!(c.absorb_radix_bits(), 31);
310 assert_eq!(c.absorb_num_f_elms(), 2);
311 assert_eq!(c.squeeze_num_f_elms(), 1);
312 assert_eq!(squeeze_field_order_num_limbs::<PF, F>(), 1);
313 }
314
315 #[test]
316 fn test_output_buffer_excludes_capacity() {
317 let permutation = TestPermutation;
318 let mut challenger =
319 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(permutation).unwrap();
320
321 let squeeze_n = challenger.squeeze_num_f_elms();
322
323 let _: F = challenger.sample();
324
325 let expected_output_size = RATE * squeeze_n;
326
327 assert_eq!(
328 challenger.pending_f_squeeze_len(),
329 expected_output_size - 1,
330 "Pending F squeeze should be RATE * squeeze_num_f_elms minus one sample"
331 );
332 assert_eq!(
333 challenger.inner.output_buffer.len(),
334 0,
335 "After refill, inner PF output buffer is drained like popped F outputs"
336 );
337 }
338
339 #[test]
340 fn test_finalize() {
341 let new_chal =
342 || MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
343
344 let mut c1 = new_chal();
346 let mut c2 = new_chal();
347 for i in 0..5u8 {
348 c1.observe(F::from_u8(i));
349 c2.observe(F::from_u8(i));
350 }
351
352 c1.flush_f_if_non_empty();
354 c2.flush_f_if_non_empty();
355 let c1_digest = &c1.inner.sponge_state[..RATE];
356 let c2_digest = &c2.inner.sponge_state[..RATE];
357 assert_eq!(c1_digest, c2_digest);
358
359 let mut c1 = new_chal();
361 let mut c2 = new_chal();
362 for i in 0..5u8 {
363 c1.observe(F::from_u8(i));
364 c2.observe(F::from_u8(i + 1));
365 }
366
367 c1.flush_f_if_non_empty();
369 c2.flush_f_if_non_empty();
370 let c1_digest = &c1.inner.sponge_state[..RATE];
371 let c2_digest = &c2.inner.sponge_state[..RATE];
372 assert_ne!(c1_digest, c2_digest);
373 }
374
375 #[test]
384 fn test_finalize_sample_interaction() {
385 let batch_size = {
386 let c =
387 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
388 c.squeeze_num_f_elms() * RATE
389 };
390
391 let digest = |n_samples: usize| {
392 let mut c =
393 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
394 for i in 0..3u8 {
395 c.observe(F::from_u8(i));
396 }
397 for _ in 0..n_samples {
398 let _: F = c.sample();
399 }
400 c.inner.duplexing();
401 let d: [PF; RATE] = c.inner.sponge_state[..RATE].try_into().unwrap();
402 d
403 };
404
405 assert_ne!(digest(0), digest(1));
408
409 assert_eq!(digest(1), digest(2));
411 assert_eq!(digest(1), digest(batch_size));
412
413 assert_ne!(digest(batch_size), digest(batch_size + 1));
415
416 assert_eq!(digest(batch_size + 1), digest(batch_size + 2));
418 }
419
420 #[test]
421 fn test_partial_absorb_length_distinct_from_padded_equivalent() {
422 let ne = {
423 let c =
424 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
425 c.absorb_num_f_elms()
426 };
427 assert_eq!(ne, 2);
428
429 let mut a =
430 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
431 a.observe(F::ONE);
432
433 let mut b =
434 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
435 b.observe(F::ONE);
436 for _ in 1..ne {
437 b.observe(F::ZERO);
438 }
439
440 a.flush_f_if_non_empty();
442 b.flush_f_if_non_empty();
443 let a_digest = &a.inner.sponge_state[..RATE];
444 let b_digest = &b.inner.sponge_state[..RATE];
445 assert_ne!(a_digest, b_digest);
446 }
447
448 #[test]
449 fn test_absorb_no_radix_overflow_collision() {
450 let mut a =
451 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
452 a.observe(F::from_u32(1 << 30));
453 a.observe(F::ZERO);
454
455 let mut b =
456 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
457 b.observe(F::ZERO);
458 b.observe(F::ONE);
459
460 a.flush_f_if_non_empty();
462 b.flush_f_if_non_empty();
463 let a_digest = &a.inner.sponge_state[..RATE];
464 let b_digest = &b.inner.sponge_state[..RATE];
465 assert_ne!(a_digest, b_digest);
466 }
467
468 #[test]
469 fn test_duplexing_respects_rate() {
470 let permutation = TestPermutation;
471 let mut challenger =
472 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(permutation).unwrap();
473
474 let absorb_n = challenger.absorb_num_f_elms();
475
476 for i in 0..(absorb_n * RATE) {
477 challenger.observe(F::from_u8(i as u8));
478 }
479
480 assert_eq!(
481 challenger.inner.output_buffer.len(),
482 RATE,
483 "After a full F batch flush, inner holds one rate row of PF elements"
484 );
485 assert_eq!(
486 challenger.pending_f_squeeze_len(),
487 0,
488 "F limbs are produced on sample() via split_pf_to_packed_limbs, not on observe"
489 );
490 }
491
492 #[test]
493 fn test_squeeze_covers_full_f_range() {
494 use p3_field::split_pf_to_field_order_limbs;
501 let pack_bits = injective_pack_bits::<F>();
502 let threshold = 1u32 << pack_bits; let v_raw = F::ORDER_U32 as u64 + threshold as u64 + 1;
508 let pf_val = PF::from_u64(v_raw);
509 let limbs = split_pf_to_field_order_limbs::<PF, F>(pf_val, 1);
510 assert_eq!(limbs[0].as_canonical_u32(), threshold + 1);
512 assert!(
513 limbs[0].as_canonical_u32() > threshold,
514 "c0 must exceed the old base-2^30 ceiling"
515 );
516 }
517
518 #[test]
519 fn test_observe_hash_native_pf_high_bits_distinct() {
520 use num_bigint::BigUint;
521 use p3_bn254::Bn254;
522 use p3_field::split_pf_to_packed_limbs;
523 use p3_symmetric::Hash;
524
525 type PF254 = Bn254;
526
527 #[derive(Clone)]
528 struct Bn254MixingPermutation;
529
530 impl Permutation<[PF254; WIDTH]> for Bn254MixingPermutation {
531 fn permute_mut(&self, input: &mut [PF254; WIDTH]) {
532 let sum: PF254 = input.iter().copied().sum();
533 for (i, val) in input.iter_mut().enumerate() {
534 *val = sum + PF254::from_u8((i + 1) as u8);
535 }
536 }
537 }
538
539 impl CryptographicPermutation<[PF254; WIDTH]> for Bn254MixingPermutation {}
540
541 let pack_bits = injective_pack_bits::<F>();
542 let observe_n = PF254::bits().div_ceil(pack_bits as usize);
543
544 let a = PF254::from_biguint(BigUint::from(1u32)).unwrap();
545 let b = PF254::from_biguint(BigUint::from(1u32) + (BigUint::from(1u32) << 200)).unwrap();
546 assert_ne!(a, b);
547
548 let digest = |h: PF254| {
549 let mut c =
550 MultiField32Challenger::<F, PF254, _, WIDTH, RATE>::new(Bn254MixingPermutation)
551 .unwrap();
552 c.observe(Hash::<F, PF254, 1>::from([h]));
553 c.inner.duplexing();
554 let d: [PF254; RATE] = c.inner.sponge_state[..RATE].try_into().unwrap();
555 d
556 };
557
558 assert_ne!(digest(a), digest(b));
559
560 let limbs_a = split_pf_to_packed_limbs::<PF254, F>(a, observe_n, pack_bits);
561 let limbs_b = split_pf_to_packed_limbs::<PF254, F>(b, observe_n, pack_bits);
562 assert_ne!(limbs_a, limbs_b);
563
564 let d_a = a.as_canonical_biguint().to_u64_digits();
565 let d_b = b.as_canonical_biguint().to_u64_digits();
566 let take3 = |d: &[u64]| {
567 let mut v = [0u64; 3];
568 for (i, x) in d.iter().take(3).enumerate() {
569 v[i] = *x;
570 }
571 v
572 };
573 assert_eq!(take3(&d_a), take3(&d_b));
574 }
575
576 #[test]
577 fn test_observe_hash_native_vs_expanded_f_not_equal() {
578 use p3_symmetric::Hash;
579
580 let g = PF::from_u64(123456789);
581 let mut native =
582 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
583 native.observe(Hash::<F, PF, 1>::from([g]));
584
585 let mut via_f =
586 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
587 let pb = injective_pack_bits::<F>();
588 let n = PF::bits().div_ceil(pb as usize);
589 for f in split_pf_to_packed_limbs::<PF, F>(g, n, pb) {
590 via_f.observe(f);
591 }
592
593 native.inner.duplexing();
595 via_f.inner.duplexing();
596 let native_digest = &native.inner.sponge_state[..RATE];
597 let via_f_digest = &via_f.inner.sponge_state[..RATE];
598 assert_ne!(native_digest, via_f_digest);
599 }
600
601 #[test]
602 fn test_inner_sponge_matches_manual_absorb_chain() {
603 let mut m =
604 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
605 for i in 0..8u8 {
606 m.observe(F::from_u8(i));
607 }
608 let d_m = m.inner.sponge_state;
609
610 let mut inner = DuplexChallenger::<PF, _, WIDTH, RATE>::new(MixingPermutation);
611 let packed: Vec<PF> = (0..8)
612 .step_by(2)
613 .map(|j| {
614 reduce_packed::<F, PF>(
615 &[F::from_u8(j), F::from_u8(j + 1)],
616 absorb_radix_bits::<F>(),
617 )
618 })
619 .collect();
620 inner.absorb_rate_padded_with_tag(&packed, 8);
621 assert_eq!(d_m, inner.sponge_state);
622 }
623}