1use alloc::string::String;
2use alloc::vec;
3use alloc::vec::Vec;
4use core::array;
5
6use p3_field::{
7 BasedVectorSpace, PrimeField, PrimeField32, absorb_radix_bits, max_absorb_injective_limbs,
8 reduce_packed, split_pf_to_field_order_limbs, squeeze_field_order_num_limbs,
9};
10use p3_symmetric::{CryptographicPermutation, Hash, MerkleCap};
11
12use crate::{
13 CanFinalizeDigest, CanObserve, CanSample, CanSampleBits, DuplexChallenger, FieldChallenger,
14};
15
16#[derive(Clone, Debug)]
35pub struct MultiField32Challenger<F, PF, P, const WIDTH: usize, const RATE: usize>
36where
37 F: PrimeField32,
38 PF: PrimeField,
39 P: CryptographicPermutation<[PF; WIDTH]>,
40{
41 inner: DuplexChallenger<PF, P, WIDTH, RATE>,
43 f_buffer: Vec<F>,
44 f_squeeze_buffer: Vec<F>,
46}
47
48impl<F, PF, P, const WIDTH: usize, const RATE: usize> MultiField32Challenger<F, PF, P, WIDTH, RATE>
49where
50 F: PrimeField32,
51 PF: PrimeField,
52 P: CryptographicPermutation<[PF; WIDTH]>,
53{
54 #[inline]
57 #[must_use]
58 pub const fn absorb_radix_bits(&self) -> u32 {
59 absorb_radix_bits::<F>()
60 }
61
62 #[inline]
66 #[must_use]
67 pub fn absorb_num_f_elms(&self) -> usize {
68 max_absorb_injective_limbs::<F, PF>()
69 }
70
71 #[inline]
76 #[must_use]
77 pub fn squeeze_num_f_elms(&self) -> usize {
78 squeeze_field_order_num_limbs::<PF, F>()
79 }
80
81 #[inline]
83 #[must_use]
84 pub const fn pending_f_squeeze_len(&self) -> usize {
85 self.f_squeeze_buffer.len()
86 }
87
88 pub fn new(permutation: P) -> Result<Self, String> {
89 if F::order() >= PF::order() {
90 return Err(String::from("F::order() must be less than PF::order()"));
91 }
92 if RATE >= WIDTH {
93 return Err(String::from("RATE must be less than WIDTH"));
94 }
95 if max_absorb_injective_limbs::<F, PF>() * RATE > u8::MAX as usize {
98 return Err(String::from(
99 "absorb length tag must fit in a u8: max_absorb_injective_limbs * RATE must be at most 255",
100 ));
101 }
102
103 Ok(Self {
104 inner: DuplexChallenger::new(permutation),
105 f_buffer: vec![],
106 f_squeeze_buffer: vec![],
107 })
108 }
109
110 fn flush_f_if_non_empty(&mut self) {
111 if self.f_buffer.is_empty() {
112 return;
113 }
114 let n_in = self.f_buffer.len();
115 let absorb_n = self.absorb_num_f_elms();
116 assert!(n_in <= absorb_n * RATE);
117 let rb = self.absorb_radix_bits();
118 let packed: Vec<PF> = self
119 .f_buffer
120 .chunks(absorb_n)
121 .map(|chunk| reduce_packed(chunk, rb))
122 .collect();
123 let tag = u8::try_from(n_in).expect("absorb length tag must fit in a u8");
125 self.inner.absorb_rate_padded_with_tag(&packed, tag);
126 self.f_buffer.clear();
127 self.f_squeeze_buffer.clear();
128 }
129
130 fn refill_f_squeeze_from_inner(&mut self) {
131 self.f_squeeze_buffer.clear();
132 let squeeze_n = self.squeeze_num_f_elms();
133 for &pf in &self.inner.output_buffer {
134 self.f_squeeze_buffer
135 .extend(split_pf_to_field_order_limbs::<PF, F>(pf, squeeze_n));
136 }
137 self.inner.output_buffer.clear();
141 }
142}
143
144impl<F, PF, P, const WIDTH: usize, const RATE: usize> FieldChallenger<F>
145 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
146where
147 F: PrimeField32,
148 PF: PrimeField,
149 P: CryptographicPermutation<[PF; WIDTH]>,
150{
151}
152
153impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanObserve<F>
154 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
155where
156 F: PrimeField32,
157 PF: PrimeField,
158 P: CryptographicPermutation<[PF; WIDTH]>,
159{
160 fn observe(&mut self, value: F) {
161 self.inner.output_buffer.clear();
162 self.f_squeeze_buffer.clear();
163 self.f_buffer.push(value);
164 if self.f_buffer.len() == self.absorb_num_f_elms() * RATE {
165 self.flush_f_if_non_empty();
166 }
167 }
168}
169
170impl<F, PF, const N: usize, P, const WIDTH: usize, const RATE: usize> CanObserve<[F; N]>
171 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
172where
173 F: PrimeField32,
174 PF: PrimeField,
175 P: CryptographicPermutation<[PF; WIDTH]>,
176{
177 fn observe(&mut self, values: [F; N]) {
178 for value in values {
179 self.observe(value);
180 }
181 }
182}
183
184impl<F, PF, const N: usize, P, const WIDTH: usize, const RATE: usize> CanObserve<Hash<F, PF, N>>
185 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
186where
187 F: PrimeField32,
188 PF: PrimeField,
189 P: CryptographicPermutation<[PF; WIDTH]>,
190{
191 fn observe(&mut self, values: Hash<F, PF, N>) {
192 self.inner.output_buffer.clear();
193 self.f_squeeze_buffer.clear();
194 self.flush_f_if_non_empty();
195
196 let words: &[PF; N] = values.as_ref();
197
198 for chunk in words.chunks(RATE) {
199 let tag = u8::try_from(chunk.len()).expect("absorb length tag must fit in a u8");
201 self.inner.absorb_rate_padded_with_tag(chunk, tag);
202 self.f_squeeze_buffer.clear();
203 }
204 }
205}
206
207impl<F, PF, const N: usize, P, const WIDTH: usize, const RATE: usize>
208 CanObserve<&MerkleCap<F, [PF; N]>> for MultiField32Challenger<F, PF, P, WIDTH, RATE>
209where
210 F: PrimeField32,
211 PF: PrimeField,
212 P: CryptographicPermutation<[PF; WIDTH]>,
213{
214 fn observe(&mut self, cap: &MerkleCap<F, [PF; N]>) {
215 for digest in cap.roots() {
216 self.observe(Hash::<F, PF, N>::from(*digest));
217 }
218 }
219}
220
221impl<F, PF, const N: usize, P, const WIDTH: usize, const RATE: usize>
222 CanObserve<MerkleCap<F, [PF; N]>> for MultiField32Challenger<F, PF, P, WIDTH, RATE>
223where
224 F: PrimeField32,
225 PF: PrimeField,
226 P: CryptographicPermutation<[PF; WIDTH]>,
227{
228 fn observe(&mut self, cap: MerkleCap<F, [PF; N]>) {
229 self.observe(&cap);
230 }
231}
232
233impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanObserve<Vec<Vec<F>>>
234 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
235where
236 F: PrimeField32,
237 PF: PrimeField,
238 P: CryptographicPermutation<[PF; WIDTH]>,
239{
240 fn observe(&mut self, valuess: Vec<Vec<F>>) {
241 for values in valuess {
242 for value in values {
243 self.observe(value);
244 }
245 }
246 }
247}
248
249impl<F, EF, PF, P, const WIDTH: usize, const RATE: usize> CanSample<EF>
250 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
251where
252 F: PrimeField32,
253 EF: BasedVectorSpace<F>,
254 PF: PrimeField,
255 P: CryptographicPermutation<[PF; WIDTH]>,
256{
257 fn sample(&mut self) -> EF {
258 EF::from_basis_coefficients_fn(|_| {
259 self.flush_f_if_non_empty();
260 if self.f_squeeze_buffer.is_empty() {
261 if !self.inner.input_buffer.is_empty() || self.inner.output_buffer.is_empty() {
262 self.inner.duplexing();
263 }
264 self.refill_f_squeeze_from_inner();
265 }
266 self.f_squeeze_buffer
267 .pop()
268 .expect("Output buffer should be non-empty")
269 })
270 }
271}
272
273impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanSampleBits<usize>
274 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
275where
276 F: PrimeField32,
277 PF: PrimeField,
278 P: CryptographicPermutation<[PF; WIDTH]>,
279{
280 fn sample_bits(&mut self, bits: usize) -> usize {
289 assert!(bits < (usize::BITS as usize));
290 assert!(
293 (1u64 << bits) < F::ORDER_U64,
294 "requested bit count must fit within the field order"
295 );
296 let rand_f: F = self.sample();
297 let rand_usize = rand_f.as_canonical_u32() as usize;
298 rand_usize & ((1 << bits) - 1)
299 }
300}
301
302impl<F, PF, P, const WIDTH: usize, const RATE: usize> MultiField32Challenger<F, PF, P, WIDTH, RATE>
303where
304 F: PrimeField32,
305 PF: PrimeField,
306 P: CryptographicPermutation<[PF; WIDTH]>,
307{
308 pub(crate) fn pow_check_fn(&self, bits: usize) -> impl Fn(u32) -> bool + Sync + '_ {
325 let rb = self.absorb_radix_bits();
326 let absorb_n = self.absorb_num_f_elms();
327 let squeeze_n = self.squeeze_num_f_elms();
328
329 let n_pending = self.f_buffer.len();
331 let tag = u8::try_from(n_pending + 1).expect("absorb length tag must fit in a u8");
333
334 let chunk_idx = n_pending / absorb_n;
336 let pos_in_chunk = n_pending % absorb_n;
337
338 let shift = PF::from_u64(1u64 << rb).exp_u64(pos_in_chunk as u64);
340
341 let const_tail: PF = reduce_packed(&self.f_buffer[chunk_idx * absorb_n..], rb);
343
344 let base_state: [PF; WIDTH] = array::from_fn(|i| {
347 if i < chunk_idx {
348 reduce_packed(&self.f_buffer[i * absorb_n..(i + 1) * absorb_n], rb)
350 } else if i < RATE {
351 PF::ZERO
353 } else if i == RATE {
354 self.inner.sponge_state[RATE] + PF::from_u8(tag)
356 } else {
357 self.inner.sponge_state[i]
359 }
360 });
361
362 let mask = (1u64 << bits) - 1;
364
365 move |candidate| {
366 let mut state = base_state;
368 state[chunk_idx] = const_tail + shift * PF::from_u32(candidate);
369 self.inner.permutation.permute_mut(&mut state);
370
371 let limbs = split_pf_to_field_order_limbs::<PF, F>(state[RATE - 1], squeeze_n);
373 (u64::from(limbs[squeeze_n - 1].as_canonical_u32()) & mask) == 0
374 }
375 }
376}
377
378impl<F, PF, P, const WIDTH: usize, const RATE: usize> CanFinalizeDigest
379 for MultiField32Challenger<F, PF, P, WIDTH, RATE>
380where
381 F: PrimeField32,
382 PF: PrimeField,
383 P: CryptographicPermutation<[PF; WIDTH]>,
384{
385 type Digest = [PF; RATE];
386
387 fn finalize(mut self) -> [PF; RATE] {
388 let had_pending_f = !self.f_buffer.is_empty();
392 self.flush_f_if_non_empty();
393 if !had_pending_f {
394 self.inner.duplexing();
395 }
396 self.inner.sponge_state[..RATE].try_into().unwrap()
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use p3_baby_bear::BabyBear;
403 use p3_field::{
404 Field, PrimeCharacteristicRing, PrimeField, injective_pack_bits, split_pf_to_packed_limbs,
405 squeeze_field_order_num_limbs,
406 };
407 use p3_goldilocks::Goldilocks;
408 use p3_symmetric::Permutation;
409 use proptest::prelude::*;
410
411 use super::*;
412 use crate::grinding_challenger::GrindingChallenger;
413
414 const WIDTH: usize = 8;
415 const RATE: usize = 4;
416
417 type F = BabyBear;
418 type PF = Goldilocks;
419
420 #[derive(Clone)]
421 struct TestPermutation;
422
423 impl Permutation<[PF; WIDTH]> for TestPermutation {
424 fn permute_mut(&self, input: &mut [PF; WIDTH]) {
425 for (i, val) in input.iter_mut().enumerate() {
426 *val = PF::from_u8((i + 1) as u8);
427 }
428 }
429 }
430
431 impl CryptographicPermutation<[PF; WIDTH]> for TestPermutation {}
432
433 #[derive(Clone)]
436 struct MixingPermutation;
437
438 impl Permutation<[PF; WIDTH]> for MixingPermutation {
439 fn permute_mut(&self, input: &mut [PF; WIDTH]) {
440 let sum: PF = input.iter().copied().sum();
441 for (i, val) in input.iter_mut().enumerate() {
442 *val = sum + PF::from_u8((i + 1) as u8);
443 }
444 }
445 }
446
447 impl CryptographicPermutation<[PF; WIDTH]> for MixingPermutation {}
448
449 #[derive(Clone)]
452 struct WideIdentityPermutation;
453
454 impl<const W: usize> Permutation<[PF; W]> for WideIdentityPermutation {
455 fn permute_mut(&self, _input: &mut [PF; W]) {}
456 }
457
458 impl<const W: usize> CryptographicPermutation<[PF; W]> for WideIdentityPermutation {}
459
460 #[test]
461 fn test_new_rejects_length_tag_overflow() {
462 assert_eq!(max_absorb_injective_limbs::<F, PF>(), 2);
467
468 let too_wide = MultiField32Challenger::<F, PF, _, 129, 128>::new(WideIdentityPermutation);
473 assert_eq!(
474 too_wide.err().as_deref(),
475 Some(
476 "absorb length tag must fit in a u8: max_absorb_injective_limbs * RATE must be at most 255"
477 )
478 );
479
480 let in_range = MultiField32Challenger::<F, PF, _, 128, 127>::new(WideIdentityPermutation);
481 assert!(in_range.is_ok());
482 }
483
484 #[test]
485 fn test_packing() {
486 let c = MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
487 assert_eq!(c.absorb_radix_bits(), 31);
488 assert_eq!(c.absorb_num_f_elms(), 2);
489 assert_eq!(c.squeeze_num_f_elms(), 1);
490 assert_eq!(squeeze_field_order_num_limbs::<PF, F>(), 1);
491 }
492
493 #[test]
494 fn test_output_buffer_excludes_capacity() {
495 let permutation = TestPermutation;
496 let mut challenger =
497 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(permutation).unwrap();
498
499 let squeeze_n = challenger.squeeze_num_f_elms();
500
501 let _: F = challenger.sample();
502
503 let expected_output_size = RATE * squeeze_n;
504
505 assert_eq!(
506 challenger.pending_f_squeeze_len(),
507 expected_output_size - 1,
508 "Pending F squeeze should be RATE * squeeze_num_f_elms minus one sample"
509 );
510 assert_eq!(
511 challenger.inner.output_buffer.len(),
512 0,
513 "After refill, inner PF output buffer is drained like popped F outputs"
514 );
515 }
516
517 #[test]
518 fn test_finalize() {
519 let new_chal =
520 || MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
521
522 let mut c1 = new_chal();
524 let mut c2 = new_chal();
525 for i in 0..5u8 {
526 c1.observe(F::from_u8(i));
527 c2.observe(F::from_u8(i));
528 }
529 assert_eq!(c1.finalize(), c2.finalize());
530
531 let mut c1 = new_chal();
533 let mut c2 = new_chal();
534 for i in 0..5u8 {
535 c1.observe(F::from_u8(i));
536 c2.observe(F::from_u8(i + 1));
537 }
538 assert_ne!(c1.finalize(), c2.finalize());
539 }
540
541 #[test]
550 fn test_finalize_sample_interaction() {
551 let batch_size = {
552 let c =
553 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
554 c.squeeze_num_f_elms() * RATE
555 };
556
557 let digest = |n_samples: usize| {
558 let mut c =
559 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
560 for i in 0..3u8 {
561 c.observe(F::from_u8(i));
562 }
563 for _ in 0..n_samples {
564 let _: F = c.sample();
565 }
566 c.finalize()
567 };
568
569 assert_ne!(digest(0), digest(1));
572
573 assert_eq!(digest(1), digest(2));
575 assert_eq!(digest(1), digest(batch_size));
576
577 assert_ne!(digest(batch_size), digest(batch_size + 1));
579
580 assert_eq!(digest(batch_size + 1), digest(batch_size + 2));
582 }
583
584 #[test]
585 fn test_partial_absorb_length_distinct_from_padded_equivalent() {
586 let ne = {
587 let c =
588 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
589 c.absorb_num_f_elms()
590 };
591 assert_eq!(ne, 2);
592
593 let mut a =
594 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
595 a.observe(F::ONE);
596
597 let mut b =
598 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
599 b.observe(F::ONE);
600 for _ in 1..ne {
601 b.observe(F::ZERO);
602 }
603
604 assert_ne!(a.finalize(), b.finalize());
605 }
606
607 #[test]
608 fn test_absorb_no_radix_overflow_collision() {
609 let mut a =
610 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
611 a.observe(F::from_u32(1 << 30));
612 a.observe(F::ZERO);
613
614 let mut b =
615 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
616 b.observe(F::ZERO);
617 b.observe(F::ONE);
618
619 assert_ne!(a.finalize(), b.finalize());
620 }
621
622 #[test]
623 fn test_duplexing_respects_rate() {
624 let permutation = TestPermutation;
625 let mut challenger =
626 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(permutation).unwrap();
627
628 let absorb_n = challenger.absorb_num_f_elms();
629
630 for i in 0..(absorb_n * RATE) {
631 challenger.observe(F::from_u8(i as u8));
632 }
633
634 assert_eq!(
635 challenger.inner.output_buffer.len(),
636 RATE,
637 "After a full F batch flush, inner holds one rate row of PF elements"
638 );
639 assert_eq!(
640 challenger.pending_f_squeeze_len(),
641 0,
642 "F limbs are produced on sample() via split_pf_to_packed_limbs, not on observe"
643 );
644 }
645
646 #[test]
647 fn test_squeeze_covers_full_f_range() {
648 use p3_field::split_pf_to_field_order_limbs;
655 let pack_bits = injective_pack_bits::<F>();
656 let threshold = 1u32 << pack_bits; let v_raw = F::ORDER_U32 as u64 + threshold as u64 + 1;
662 let pf_val = PF::from_u64(v_raw);
663 let limbs = split_pf_to_field_order_limbs::<PF, F>(pf_val, 1);
664 assert_eq!(limbs[0].as_canonical_u32(), threshold + 1);
666 assert!(
667 limbs[0].as_canonical_u32() > threshold,
668 "c0 must exceed the old base-2^30 ceiling"
669 );
670 }
671
672 #[test]
673 fn test_observe_hash_native_pf_high_bits_distinct() {
674 use num_bigint::BigUint;
675 use p3_bn254::Bn254;
676 use p3_field::split_pf_to_packed_limbs;
677 use p3_symmetric::Hash;
678
679 type PF254 = Bn254;
680
681 #[derive(Clone)]
682 struct Bn254MixingPermutation;
683
684 impl Permutation<[PF254; WIDTH]> for Bn254MixingPermutation {
685 fn permute_mut(&self, input: &mut [PF254; WIDTH]) {
686 let sum: PF254 = input.iter().copied().sum();
687 for (i, val) in input.iter_mut().enumerate() {
688 *val = sum + PF254::from_u8((i + 1) as u8);
689 }
690 }
691 }
692
693 impl CryptographicPermutation<[PF254; WIDTH]> for Bn254MixingPermutation {}
694
695 let pack_bits = injective_pack_bits::<F>();
696 let observe_n = PF254::bits().div_ceil(pack_bits as usize);
697
698 let a = PF254::from_biguint(BigUint::from(1u32)).unwrap();
699 let b = PF254::from_biguint(BigUint::from(1u32) + (BigUint::from(1u32) << 200)).unwrap();
700 assert_ne!(a, b);
701
702 let digest = |h: PF254| {
703 let mut c =
704 MultiField32Challenger::<F, PF254, _, WIDTH, RATE>::new(Bn254MixingPermutation)
705 .unwrap();
706 c.observe(Hash::<F, PF254, 1>::from([h]));
707 c.finalize()
708 };
709
710 assert_ne!(digest(a), digest(b));
711
712 let limbs_a = split_pf_to_packed_limbs::<PF254, F>(a, observe_n, pack_bits);
713 let limbs_b = split_pf_to_packed_limbs::<PF254, F>(b, observe_n, pack_bits);
714 assert_ne!(limbs_a, limbs_b);
715
716 let d_a = a.as_canonical_biguint().to_u64_digits();
717 let d_b = b.as_canonical_biguint().to_u64_digits();
718 let take3 = |d: &[u64]| {
719 let mut v = [0u64; 3];
720 for (i, x) in d.iter().take(3).enumerate() {
721 v[i] = *x;
722 }
723 v
724 };
725 assert_eq!(take3(&d_a), take3(&d_b));
726 }
727
728 #[test]
729 fn test_observe_hash_native_vs_expanded_f_not_equal() {
730 use p3_symmetric::Hash;
731
732 let g = PF::from_u64(123456789);
733 let mut native =
734 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
735 native.observe(Hash::<F, PF, 1>::from([g]));
736
737 let mut via_f =
738 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
739 let pb = injective_pack_bits::<F>();
740 let n = PF::bits().div_ceil(pb as usize);
741 for f in split_pf_to_packed_limbs::<PF, F>(g, n, pb) {
742 via_f.observe(f);
743 }
744
745 assert_ne!(native.finalize(), via_f.finalize());
746 }
747
748 #[test]
749 fn test_inner_sponge_matches_manual_absorb_chain() {
750 let mut m =
751 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
752 for i in 0..8u8 {
753 m.observe(F::from_u8(i));
754 }
755 let d_m = m.inner.sponge_state;
756
757 let mut inner = DuplexChallenger::<PF, _, WIDTH, RATE>::new(MixingPermutation);
758 let packed: Vec<PF> = (0..8)
759 .step_by(2)
760 .map(|j| {
761 reduce_packed::<F, PF>(
762 &[F::from_u8(j), F::from_u8(j + 1)],
763 absorb_radix_bits::<F>(),
764 )
765 })
766 .collect();
767 inner.absorb_rate_padded_with_tag(&packed, 8);
768 assert_eq!(d_m, inner.sponge_state);
769 }
770
771 #[test]
772 fn test_grind_zero_bits_returns_zero() {
773 let mut challenger =
775 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
776
777 let before = challenger.clone();
779
780 let witness = challenger.grind(0);
781
782 assert_eq!(witness, F::ZERO);
783 assert_eq!(challenger.inner.input_buffer, before.inner.input_buffer);
784 assert_eq!(challenger.inner.output_buffer, before.inner.output_buffer);
785 assert_eq!(challenger.inner.sponge_state, before.inner.sponge_state);
786 }
787
788 #[test]
789 #[should_panic = "requested bit count must fit within the field order"]
790 fn test_sample_bits_rejects_oversized_request() {
791 let mut challenger =
794 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
795 let _ = challenger.sample_bits(32);
796 }
797
798 #[test]
799 #[should_panic = "requested bit count must fit within the field order"]
800 fn test_grind_rejects_oversized_request() {
801 let mut challenger =
803 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
804 let _ = challenger.grind(32);
805 }
806
807 #[test]
808 fn test_grind_advances_state_like_direct_check() {
809 for pending in [0usize, 1, 4, 7] {
819 let mut challenger =
820 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
821 for i in 0..pending {
822 challenger.observe(F::from_u8(i as u8));
823 }
824
825 let mut direct = challenger.clone();
827
828 let bits = 2;
829 let witness = challenger.grind(bits);
830
831 assert!(direct.check_witness(bits, witness), "pending = {pending}");
833 assert_eq!(challenger.inner.sponge_state, direct.inner.sponge_state);
834 assert_eq!(challenger.inner.input_buffer, direct.inner.input_buffer);
835 assert_eq!(challenger.inner.output_buffer, direct.inner.output_buffer);
836 assert_eq!(challenger.f_buffer, direct.f_buffer);
837 assert_eq!(challenger.f_squeeze_buffer, direct.f_squeeze_buffer);
838 }
839 }
840
841 #[test]
842 fn test_grind_advances_state_like_direct_check_bn254() {
843 use p3_bn254::Bn254;
846
847 type PF254 = Bn254;
848
849 #[derive(Clone)]
850 struct Bn254MixingPermutation;
851
852 impl Permutation<[PF254; WIDTH]> for Bn254MixingPermutation {
853 fn permute_mut(&self, input: &mut [PF254; WIDTH]) {
854 let sum: PF254 = input.iter().copied().sum();
855 for (i, val) in input.iter_mut().enumerate() {
856 *val = sum + PF254::from_u8((i + 1) as u8);
857 }
858 }
859 }
860
861 impl CryptographicPermutation<[PF254; WIDTH]> for Bn254MixingPermutation {}
862
863 let mut challenger =
864 MultiField32Challenger::<F, PF254, _, WIDTH, RATE>::new(Bn254MixingPermutation)
865 .unwrap();
866 assert!(challenger.squeeze_num_f_elms() > 1);
867 for i in 0..3u8 {
868 challenger.observe(F::from_u8(i));
869 }
870
871 let mut direct = challenger.clone();
872
873 let bits = 2;
874 let witness = challenger.grind(bits);
875
876 assert!(direct.check_witness(bits, witness));
877 assert_eq!(challenger.inner.sponge_state, direct.inner.sponge_state);
878 assert_eq!(challenger.f_buffer, direct.f_buffer);
879 assert_eq!(challenger.f_squeeze_buffer, direct.f_squeeze_buffer);
880 }
881
882 proptest! {
883 #[test]
884 fn prop_pow_check_matches_check_witness(
885 pending_values in prop::collection::vec(0u32..F::ORDER_U32, 0..8),
886 candidate in 0u32..F::ORDER_U32,
887 bits in 1usize..8,
888 ) {
889 let mut challenger =
893 MultiField32Challenger::<F, PF, _, WIDTH, RATE>::new(MixingPermutation).unwrap();
894 for &v in &pending_values {
895 challenger.observe(F::from_u32(v));
896 }
897
898 let fast = challenger.pow_check_fn(bits)(candidate);
899
900 let witness = F::from_u32(candidate);
902 let reference = challenger.clone().check_witness(bits, witness);
903
904 prop_assert_eq!(fast, reference);
905 }
906 }
907}