Skip to main content

p3_challenger/
duplex_challenger.rs

1use alloc::vec;
2use alloc::vec::Vec;
3use core::error::Error;
4use core::fmt::{Display, Formatter};
5
6use p3_field::{BasedVectorSpace, Field, PrimeCharacteristicRing, PrimeField, PrimeField64};
7use p3_monty_31::{MontyField31, MontyParameters};
8use p3_symmetric::{CryptographicPermutation, Hash, MerkleCap};
9
10use crate::{
11    CanFinalizeDigest, CanObserve, CanSample, CanSampleBits, CanSampleUniformBits, FieldChallenger,
12};
13
14/// A generic duplex sponge challenger over a finite field, used for generating deterministic
15/// challenges from absorbed inputs.
16///
17/// This structure implements a duplex sponge that alternates between:
18/// - Absorbing inputs into the sponge state,
19/// - Applying a cryptographic permutation over the state,
20/// - Squeezing outputs from the state as challenges.
21///
22/// The sponge operates over a state of `WIDTH` elements, divided into:
23/// - A rate of `RATE` elements (the portion exposed to input/output),
24/// - A capacity of `WIDTH - RATE` elements (the hidden part ensuring security).
25///
26/// The challenger buffers observed inputs until the rate is full, applies the permutation,
27/// and then produces challenge outputs from the permuted state. It supports:
28/// - Observing single values, arrays, hashes, or nested vectors,
29/// - Sampling fresh challenges as field elements or bitstrings.
30#[derive(Clone, Debug)]
31pub struct DuplexChallenger<F, P, const WIDTH: usize, const RATE: usize>
32where
33    F: Clone,
34    P: CryptographicPermutation<[F; WIDTH]>,
35{
36    /// The internal sponge state, consisting of `WIDTH` field elements.
37    ///
38    /// The first `RATE` elements form the rate section, where input values are absorbed
39    /// and output values are squeezed.
40    /// The remaining `WIDTH - RATE` elements form the capacity, which provides hidden
41    /// entropy and security against attacks.
42    pub sponge_state: [F; WIDTH],
43
44    /// A buffer holding field elements that have been observed but not yet absorbed.
45    ///
46    /// Inputs added via `observe` are collected here.
47    /// Once the buffer reaches `RATE` elements, the sponge performs a duplexing step:
48    /// it absorbs the inputs into the state and applies the permutation.
49    pub input_buffer: Vec<F>,
50
51    /// A buffer holding field elements that have been squeezed from the sponge state.
52    ///
53    /// Outputs are produced by `duplexing` and stored here.
54    /// Calls to `sample` or `sample_bits` pop values from this buffer.
55    /// When the buffer is empty (or new inputs were absorbed), a new duplexing step is triggered.
56    pub output_buffer: Vec<F>,
57
58    /// The cryptographic permutation applied to the sponge state.
59    ///
60    /// This permutation must provide strong pseudorandomness and collision resistance,
61    /// ensuring that squeezed outputs are indistinguishable from random and securely
62    /// bound to the absorbed inputs.
63    pub permutation: P,
64}
65
66impl<F, P, const WIDTH: usize, const RATE: usize> DuplexChallenger<F, P, WIDTH, RATE>
67where
68    F: Copy,
69    P: CryptographicPermutation<[F; WIDTH]>,
70{
71    pub fn new(permutation: P) -> Self
72    where
73        F: Default,
74    {
75        const {
76            assert!(RATE > 0 && RATE < WIDTH);
77        }
78        Self {
79            sponge_state: [F::default(); WIDTH],
80            input_buffer: vec![],
81            output_buffer: vec![],
82            permutation,
83        }
84    }
85
86    pub(crate) fn duplexing(&mut self)
87    where
88        F: PrimeCharacteristicRing,
89    {
90        let num_absorbed = self.input_buffer.len();
91        assert!(num_absorbed <= RATE);
92
93        // Overwrite the leading rate slots with the buffered inputs.
94        for (i, val) in self.input_buffer.drain(..).enumerate() {
95            self.sponge_state[i] = val;
96        }
97
98        // An empty buffer is a squeeze: permute the current state, leaving the rate untouched.
99        // A non-empty buffer is an absorb, made prefix-free so length and zero-padding cannot collide.
100        if num_absorbed > 0 {
101            // Clear the rate slots the inputs did not overwrite.
102            self.sponge_state[num_absorbed..RATE].fill(F::ZERO);
103            // Bind the absorbed length into the first capacity element.
104            self.sponge_state[RATE] += F::from_u8(num_absorbed as u8);
105        }
106
107        // Apply the permutation.
108        self.permutation.permute_mut(&mut self.sponge_state);
109
110        self.output_buffer.clear();
111        self.output_buffer.extend(&self.sponge_state[..RATE]);
112    }
113}
114
115impl<F, P, const WIDTH: usize, const RATE: usize> DuplexChallenger<F, P, WIDTH, RATE>
116where
117    F: Copy + Default + PrimeField,
118    P: CryptographicPermutation<[F; WIDTH]>,
119{
120    /// Absorb `values.len()` elements into the rate, zero remaining rate slots, add `length_tag`
121    /// to `sponge_state[RATE]`, permute, and refill `output_buffer`.
122    ///
123    /// Clears `input_buffer` and `output_buffer` before absorbing. Used by
124    /// [`MultiField32Challenger`](crate::MultiField32Challenger) so packed scalar and native-digest
125    /// absorbs share this [`DuplexChallenger`]'s sponge state without queuing through `observe`.
126    ///
127    /// # Why the tag is a byte
128    ///
129    /// - The tag distinguishes at most 256 absorb lengths.
130    /// - Callers must keep logical lengths at most 255, or lengths differing by 256 collide.
131    pub fn absorb_rate_padded_with_tag(&mut self, values: &[F], length_tag: u8) {
132        const {
133            assert!(
134                RATE < WIDTH,
135                "RATE must be less than WIDTH for capacity length slot"
136            );
137        }
138        assert!(values.len() <= RATE);
139        self.input_buffer.clear();
140        self.output_buffer.clear();
141        for (i, &v) in values.iter().enumerate() {
142            self.sponge_state[i] = v;
143        }
144        self.sponge_state[values.len()..RATE].fill(F::ZERO);
145        self.sponge_state[RATE] += F::from_u8(length_tag);
146        self.permutation.permute_mut(&mut self.sponge_state);
147        self.output_buffer
148            .extend_from_slice(&self.sponge_state[..RATE]);
149    }
150}
151
152impl<F, P, const WIDTH: usize, const RATE: usize> FieldChallenger<F>
153    for DuplexChallenger<F, P, WIDTH, RATE>
154where
155    F: PrimeField64,
156    P: CryptographicPermutation<[F; WIDTH]>,
157{
158}
159
160impl<F, P, const WIDTH: usize, const RATE: usize> CanObserve<F>
161    for DuplexChallenger<F, P, WIDTH, RATE>
162where
163    F: Copy + PrimeCharacteristicRing,
164    P: CryptographicPermutation<[F; WIDTH]>,
165{
166    fn observe(&mut self, value: F) {
167        // Any buffered output is now invalid.
168        self.output_buffer.clear();
169
170        self.input_buffer.push(value);
171
172        if self.input_buffer.len() == RATE {
173            self.duplexing();
174        }
175    }
176}
177
178impl<F, P, const N: usize, const WIDTH: usize, const RATE: usize> CanObserve<[F; N]>
179    for DuplexChallenger<F, P, WIDTH, RATE>
180where
181    F: Copy + PrimeCharacteristicRing,
182    P: CryptographicPermutation<[F; WIDTH]>,
183{
184    fn observe(&mut self, values: [F; N]) {
185        for value in values {
186            self.observe(value);
187        }
188    }
189}
190
191impl<F, P, const N: usize, const WIDTH: usize, const RATE: usize> CanObserve<Hash<F, F, N>>
192    for DuplexChallenger<F, P, WIDTH, RATE>
193where
194    F: Copy + PrimeCharacteristicRing,
195    P: CryptographicPermutation<[F; WIDTH]>,
196{
197    fn observe(&mut self, values: Hash<F, F, N>) {
198        for value in values {
199            self.observe(value);
200        }
201    }
202}
203
204impl<F, P, const N: usize, const WIDTH: usize, const RATE: usize> CanObserve<&MerkleCap<F, [F; N]>>
205    for DuplexChallenger<F, P, WIDTH, RATE>
206where
207    F: Copy + PrimeCharacteristicRing,
208    P: CryptographicPermutation<[F; WIDTH]>,
209{
210    fn observe(&mut self, cap: &MerkleCap<F, [F; N]>) {
211        for digest in cap.roots() {
212            for value in digest {
213                self.observe(*value);
214            }
215        }
216    }
217}
218
219impl<F, P, const N: usize, const WIDTH: usize, const RATE: usize> CanObserve<MerkleCap<F, [F; N]>>
220    for DuplexChallenger<F, P, WIDTH, RATE>
221where
222    F: Copy + PrimeCharacteristicRing,
223    P: CryptographicPermutation<[F; WIDTH]>,
224{
225    fn observe(&mut self, cap: MerkleCap<F, [F; N]>) {
226        self.observe(&cap);
227    }
228}
229
230// for TrivialPcs
231impl<F, P, const WIDTH: usize, const RATE: usize> CanObserve<Vec<Vec<F>>>
232    for DuplexChallenger<F, P, WIDTH, RATE>
233where
234    F: Copy + PrimeCharacteristicRing,
235    P: CryptographicPermutation<[F; WIDTH]>,
236{
237    fn observe(&mut self, valuess: Vec<Vec<F>>) {
238        for values in valuess {
239            for value in values {
240                self.observe(value);
241            }
242        }
243    }
244}
245
246impl<F, EF, P, const WIDTH: usize, const RATE: usize> CanSample<EF>
247    for DuplexChallenger<F, P, WIDTH, RATE>
248where
249    F: Field,
250    EF: BasedVectorSpace<F>,
251    P: CryptographicPermutation<[F; WIDTH]>,
252{
253    fn sample(&mut self) -> EF {
254        EF::from_basis_coefficients_fn(|_| {
255            // If we have buffered inputs, we must perform a duplexing so that the challenge will
256            // reflect them. Or if we've run out of outputs, we must perform a duplexing to get more.
257            if !self.input_buffer.is_empty() || self.output_buffer.is_empty() {
258                self.duplexing();
259            }
260
261            self.output_buffer
262                .pop()
263                .expect("Output buffer should be non-empty")
264        })
265    }
266}
267
268impl<F, P, const WIDTH: usize, const RATE: usize> CanSampleBits<usize>
269    for DuplexChallenger<F, P, WIDTH, RATE>
270where
271    F: PrimeField64,
272    P: CryptographicPermutation<[F; WIDTH]>,
273{
274    /// The sampled bits are not perfectly uniform, but we can bound the error: every sequence
275    /// appears with probability 1/p-close to uniform (1/2^b).
276    ///
277    /// Proof:
278    /// We denote p = F::ORDER_U64, and b = `bits`.
279    /// If X follows a uniform distribution over F, if we consider the first b bits of X, each
280    /// sequence appears either with probability P1 = ⌊p / 2^b⌋ / p or P2 = (1 + ⌊p / 2^b⌋) / p.
281    /// We have 1/2^b - 1/p ≤ P1, P2 ≤ 1/2^b + 1/p
282    fn sample_bits(&mut self, bits: usize) -> usize {
283        assert!(bits < (usize::BITS as usize));
284        assert!((1 << bits) < F::ORDER_U64);
285        let rand_f: F = self.sample();
286        let rand_usize = rand_f.as_canonical_u64() as usize;
287        rand_usize & ((1 << bits) - 1)
288    }
289}
290
291/// Trait for fields that support uniform bit sampling optimizations
292pub trait UniformSamplingField {
293    /// Maximum number of bits we can sample at negligible (~1/field prime) probability of
294    /// triggering an error / requiring a resample.
295    const MAX_SINGLE_SAMPLE_BITS: usize;
296    /// An array storing the largest value `m_k` for each `k` in [0, 31], such that `m_k`
297    /// is a multiple of `2^k` and less than P. `m_k` is defined as:
298    ///
299    /// \( m_k = ⌊P / 2^k⌋ · 2^k \)
300    ///
301    /// This is used as a rejection sampling threshold (or error trigger), when sampling
302    /// random bits from uniformly sampled field elements. As long as we sample up to the `k`
303    /// least significant bits in the range [0, m_k), we sample from exactly `m_k` elements. As
304    /// `m_k` is divisible by 2^k, each of the least significant `k` bits has exactly the same
305    /// number of zeroes and ones, leading to a uniform sampling.
306    const SAMPLING_BITS_M: [u64; 64];
307}
308
309// Provide a blanket implementation for Monty31 fields here, which forwards the
310// implementation of the variables to the generic argument `<Field>Parameter`,
311// for which we implement the trait (KoalaBear, BabyBear).
312impl<MP> UniformSamplingField for MontyField31<MP>
313where
314    MP: UniformSamplingField + MontyParameters,
315{
316    const MAX_SINGLE_SAMPLE_BITS: usize = MP::MAX_SINGLE_SAMPLE_BITS;
317    const SAMPLING_BITS_M: [u64; 64] = MP::SAMPLING_BITS_M;
318}
319
320// Set of different strategies we currently support for sampling
321// Implementations for each are below.
322/// A zero-sized struct representing the "resample" strategy.
323pub(super) struct ResampleOnRejection;
324/// A zero-sized struct representing the "error" strategy.
325pub(super) struct ErrorOnRejection;
326
327/// Custom error raised when resampling is required for uniform bits but disabled
328/// via `ErrorOnRejection` strategy.
329#[derive(Debug)]
330pub struct ResamplingError {
331    /// The sampled value
332    value: u64,
333    /// The target value we need to be smaller than
334    m: u64,
335}
336
337impl Display for ResamplingError {
338    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
339        write!(
340            f,
341            "Encountered value {0}, which requires resampling for uniform bits as it not smaller than {1}. But resampling is not enabled.",
342            self.value, self.m
343        )
344    }
345}
346
347impl Error for ResamplingError {}
348
349/// A trait that defines a strategy for handling out-of-range samples.
350pub(super) trait BitSamplingStrategy<F, P, const W: usize, const R: usize>
351where
352    F: PrimeField64,
353    P: CryptographicPermutation<[F; W]>,
354{
355    /// Whether to error instead of resampling when a drawn value is too large.
356    const ERROR_ON_REJECTION: bool;
357
358    #[inline]
359    fn sample_value(
360        challenger: &mut DuplexChallenger<F, P, W, R>,
361        m: u64,
362    ) -> Result<F, ResamplingError> {
363        let mut result: F = challenger.sample();
364        if Self::ERROR_ON_REJECTION {
365            if result.as_canonical_u64() >= m {
366                return Err(ResamplingError {
367                    value: result.as_canonical_u64(),
368                    m,
369                });
370            }
371        } else {
372            while result.as_canonical_u64() >= m {
373                result = challenger.sample();
374            }
375        }
376        Ok(result)
377    }
378}
379
380/// Implement rejection sampling
381impl<F, P, const W: usize, const R: usize> BitSamplingStrategy<F, P, W, R> for ResampleOnRejection
382where
383    F: PrimeField64,
384    P: CryptographicPermutation<[F; W]>,
385{
386    const ERROR_ON_REJECTION: bool = false;
387}
388
389/// Implement erroring on a required rejection
390impl<F, P, const W: usize, const R: usize> BitSamplingStrategy<F, P, W, R> for ErrorOnRejection
391where
392    F: PrimeField64,
393    P: CryptographicPermutation<[F; W]>,
394{
395    const ERROR_ON_REJECTION: bool = true;
396}
397
398impl<F, P, const WIDTH: usize, const RATE: usize> DuplexChallenger<F, P, WIDTH, RATE>
399where
400    F: UniformSamplingField + PrimeField64,
401    P: CryptographicPermutation<[F; WIDTH]>,
402{
403    /// Generic implementation for uniform bit sampling, parameterized by a strategy.
404    #[inline]
405    fn sample_uniform_bits_with_strategy<S>(
406        &mut self,
407        bits: usize,
408    ) -> Result<usize, ResamplingError>
409    where
410        S: BitSamplingStrategy<F, P, WIDTH, RATE>,
411    {
412        if bits == 0 {
413            return Ok(0);
414        };
415        assert!(bits < usize::BITS as usize, "bit count must be valid");
416        assert!(
417            (1u64 << bits) < F::ORDER_U64,
418            "bit count exceeds field order"
419        );
420        let m = F::SAMPLING_BITS_M[bits];
421        if bits <= F::MAX_SINGLE_SAMPLE_BITS {
422            // Fast path: Only one sample is needed for sufficient uniformity.
423            let rand_f = S::sample_value(self, m);
424            Ok(rand_f?.as_canonical_u64() as usize & ((1 << bits) - 1))
425        } else {
426            // Slow path: Sample twice to construct the required number of bits.
427            // This reduces the bias introduced by a single, larger sample.
428            let half_bits1 = bits / 2;
429            let half_bits2 = bits - half_bits1;
430            // Sample the first chunk of bits.
431            let rand1 = S::sample_value(self, F::SAMPLING_BITS_M[half_bits1]);
432            let chunk1 = rand1?.as_canonical_u64() as usize & ((1 << half_bits1) - 1);
433            // Sample the second chunk of bits.
434            let rand2 = S::sample_value(self, F::SAMPLING_BITS_M[half_bits2]);
435            let chunk2 = rand2?.as_canonical_u64() as usize & ((1 << half_bits2) - 1);
436
437            // Combine the chunks.
438            Ok(chunk1 | (chunk2 << half_bits1))
439        }
440    }
441}
442
443impl<F, P, const WIDTH: usize, const RATE: usize> CanSampleUniformBits<F>
444    for DuplexChallenger<F, P, WIDTH, RATE>
445where
446    F: UniformSamplingField + PrimeField64,
447    P: CryptographicPermutation<[F; WIDTH]>,
448{
449    fn sample_uniform_bits<const RESAMPLE: bool>(
450        &mut self,
451        bits: usize,
452    ) -> Result<usize, ResamplingError> {
453        if RESAMPLE {
454            self.sample_uniform_bits_with_strategy::<ResampleOnRejection>(bits)
455        } else {
456            self.sample_uniform_bits_with_strategy::<ErrorOnRejection>(bits)
457        }
458    }
459}
460
461impl<F, P, const WIDTH: usize, const RATE: usize> CanFinalizeDigest
462    for DuplexChallenger<F, P, WIDTH, RATE>
463where
464    F: Copy + PrimeCharacteristicRing,
465    P: CryptographicPermutation<[F; WIDTH]>,
466{
467    type Digest = [F; RATE];
468
469    fn finalize(mut self) -> [F; RATE] {
470        // Unconditionally duplex: absorb any pending input and permute.
471        //
472        // Note: sampling only pops from the output buffer without modifying
473        // sponge state, so it does not necessarily affect the digest (e.g.
474        // when the last observe already triggered auto-duplexing).
475        self.duplexing();
476        self.sponge_state[..RATE].try_into().unwrap()
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use p3_baby_bear::BabyBear;
483    use p3_field::PrimeCharacteristicRing;
484    use p3_field::extension::BinomialExtensionField;
485    use p3_goldilocks::Goldilocks;
486    use p3_symmetric::Permutation;
487
488    use super::*;
489    use crate::grinding_challenger::GrindingChallenger;
490
491    const WIDTH: usize = 24;
492    const RATE: usize = 16;
493
494    type G = Goldilocks;
495    type EF2G = BinomialExtensionField<G, 2>;
496
497    type BB = BabyBear;
498
499    #[derive(Clone)]
500    struct TestPermutation {}
501
502    impl<F: Clone> Permutation<[F; WIDTH]> for TestPermutation {
503        fn permute_mut(&self, input: &mut [F; WIDTH]) {
504            input.reverse();
505        }
506    }
507
508    impl<F: Clone> CryptographicPermutation<[F; WIDTH]> for TestPermutation {}
509
510    #[test]
511    fn test_duplex_challenger() {
512        type Chal = DuplexChallenger<G, TestPermutation, WIDTH, RATE>;
513        let permutation = TestPermutation {};
514        let mut duplex_challenger = DuplexChallenger::new(permutation);
515
516        // Observe 12 elements.
517        (0..12).for_each(|element| duplex_challenger.observe(G::from_u8(element as u8)));
518
519        // The absorb writes the 12 inputs into rate slots [0, 12).
520        let mut pre_permute = [G::ZERO; WIDTH];
521        (0..12).for_each(|i| pre_permute[i] = G::from_u8(i as u8));
522        // Slots [12, RATE) stay zero, and the absorbed length lands in the first capacity slot.
523        pre_permute[RATE] = G::from_u8(12);
524
525        // The test permutation reverses the whole state.
526        let mut state_after_duplexing = pre_permute;
527        state_after_duplexing.reverse();
528        // Outputs are the first RATE elements, popped from the back when sampling.
529        let expected_samples: Vec<G> = state_after_duplexing[..RATE]
530            .iter()
531            .copied()
532            .rev()
533            .collect();
534
535        let samples = <Chal as CanSample<G>>::sample_vec(&mut duplex_challenger, 16);
536        assert_eq!(samples, expected_samples);
537    }
538
539    #[test]
540    fn test_absorb_is_length_binding() {
541        type Chal = DuplexChallenger<G, TestPermutation, WIDTH, RATE>;
542
543        // A sponge that overwrote only the filled rate slots would map an absorb and its
544        // zero-extension onto the same state, since the trailing zeros match the slots already
545        // holding zero.
546        //
547        // Invariant: an absorb and the same absorb padded with trailing zeros must yield
548        // different challenges.
549        //
550        //     challenger A absorbs: [5, 7]        -> length tag 2
551        //     challenger B absorbs: [5, 7, 0, 0]  -> length tag 4
552        //     distinct tags -> distinct states -> distinct samples
553        let mut chal_a = Chal::new(TestPermutation {});
554        let mut chal_b = Chal::new(TestPermutation {});
555
556        // Absorb the shorter sequence.
557        chal_a.observe(G::from_u8(5));
558        chal_a.observe(G::from_u8(7));
559
560        // Absorb the same prefix followed by two zeros.
561        chal_b.observe(G::from_u8(5));
562        chal_b.observe(G::from_u8(7));
563        chal_b.observe(G::ZERO);
564        chal_b.observe(G::ZERO);
565
566        // The test permutation is a bijection, so distinct pre-permutation states give distinct
567        // challenge streams.
568        let samples_a = <Chal as CanSample<G>>::sample_vec(&mut chal_a, RATE);
569        let samples_b = <Chal as CanSample<G>>::sample_vec(&mut chal_b, RATE);
570        assert_ne!(samples_a, samples_b);
571    }
572
573    #[test]
574    #[should_panic]
575    fn test_duplex_challenger_sample_bits_security() {
576        type GoldilocksChal = DuplexChallenger<G, TestPermutation, WIDTH, RATE>;
577        let permutation = TestPermutation {};
578        let mut duplex_challenger = GoldilocksChal::new(permutation);
579
580        for _ in 0..100 {
581            assert!(duplex_challenger.sample_bits(129) < 4);
582        }
583    }
584
585    #[test]
586    #[should_panic]
587    fn test_duplex_challenger_sample_bits_security_small_field() {
588        type BabyBearChal = DuplexChallenger<BB, TestPermutation, WIDTH, RATE>;
589        let permutation = TestPermutation {};
590        let mut duplex_challenger = BabyBearChal::new(permutation);
591
592        for _ in 0..100 {
593            assert!(duplex_challenger.sample_bits(40) < 1 << 31);
594        }
595    }
596
597    #[test]
598    #[should_panic]
599    fn test_duplex_challenger_grind_security() {
600        type GoldilocksChal = DuplexChallenger<G, TestPermutation, WIDTH, RATE>;
601        let permutation = TestPermutation {};
602        let mut duplex_challenger = GoldilocksChal::new(permutation);
603
604        // This should cause sample_bits (and hence grind and check_witness) to
605        // panic. If bit sizes were not constrained correctly inside the
606        // challenger, (1 << too_many_bits) would loop around, incorrectly
607        // grinding and accepting a 1-bit PoW.
608        let too_many_bits = usize::BITS as usize;
609
610        let witness = duplex_challenger.grind(too_many_bits);
611        assert!(duplex_challenger.check_witness(too_many_bits, witness));
612    }
613
614    #[test]
615    fn test_observe_single_value() {
616        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
617        chal.observe(G::from_u8(42));
618        assert_eq!(chal.input_buffer, vec![G::from_u8(42)]);
619        assert!(chal.output_buffer.is_empty());
620    }
621
622    #[test]
623    fn test_observe_array_of_values() {
624        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
625        chal.observe([G::from_u8(1), G::from_u8(2), G::from_u8(3)]);
626        assert_eq!(
627            chal.input_buffer,
628            vec![G::from_u8(1), G::from_u8(2), G::from_u8(3)]
629        );
630        assert!(chal.output_buffer.is_empty());
631    }
632
633    #[test]
634    fn test_observe_hash_array() {
635        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
636        let hash = Hash::<G, G, 4>::from([G::from_u8(10); 4]);
637        chal.observe(hash);
638        assert_eq!(chal.input_buffer, vec![G::from_u8(10); 4]);
639    }
640
641    #[test]
642    fn test_observe_nested_vecs() {
643        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
644        chal.observe(vec![
645            vec![G::from_u8(1), G::from_u8(2)],
646            vec![G::from_u8(3)],
647        ]);
648        assert_eq!(
649            chal.input_buffer,
650            vec![G::from_u8(1), G::from_u8(2), G::from_u8(3)]
651        );
652    }
653
654    #[test]
655    fn test_sample_triggers_duplex() {
656        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
657        chal.observe(G::from_u8(5));
658        assert!(chal.output_buffer.is_empty());
659        let _sample: G = chal.sample();
660        assert!(!chal.output_buffer.is_empty());
661    }
662
663    #[test]
664    fn test_sample_multiple_extension_field() {
665        use p3_field::extension::BinomialExtensionField;
666        type EF = BinomialExtensionField<G, 2>;
667        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
668
669        chal.observe(G::from_u8(1));
670        chal.observe(G::from_u8(2));
671        let _: EF = chal.sample();
672        let _: EF = chal.sample();
673    }
674
675    #[test]
676    fn test_sample_bits_within_bounds() {
677        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
678        for i in 0..RATE {
679            chal.observe(G::from_u8(i as u8));
680        }
681
682        // With RATE=16 and input = 0..15, the reversed sponge_state will be 15..0
683        // The first RATE elements of that, i.e. output_buffer, are 15..0
684        // sample_bits(3) will sample the last of those: G::from_u8(0)
685
686        let bits = 3;
687        let value = chal.sample_bits(bits);
688        let expected = G::ZERO.as_canonical_u64() as usize & ((1 << bits) - 1);
689        assert_eq!(value, expected);
690    }
691
692    #[test]
693    fn test_sample_bits_trigger_duplex_when_empty() {
694        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
695        // Force empty buffers
696        assert_eq!(chal.input_buffer.len(), 0);
697        assert_eq!(chal.output_buffer.len(), 0);
698
699        // sampling bits should not panic, should return 0
700        let bits = 2;
701        let sample = chal.sample_bits(bits);
702        let expected = G::ZERO.as_canonical_u64() as usize & ((1 << bits) - 1);
703        assert_eq!(sample, expected);
704    }
705
706    #[test]
707    fn test_output_buffer_pops_correctly() {
708        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
709
710        // Observe RATE elements, causing a duplexing
711        for i in 0..RATE {
712            chal.observe(G::from_u8(i as u8));
713        }
714
715        // The absorbed length RATE sits in the capacity slot.
716        // Reversing the state moves it to index 7 of the output.
717        let expected = [
718            G::from_u8(0),
719            G::from_u8(0),
720            G::from_u8(0),
721            G::from_u8(0),
722            G::from_u8(0),
723            G::from_u8(0),
724            G::from_u8(0),
725            G::from_u8(RATE as u8),
726            G::from_u8(15),
727            G::from_u8(14),
728            G::from_u8(13),
729            G::from_u8(12),
730            G::from_u8(11),
731            G::from_u8(10),
732            G::from_u8(9),
733            G::from_u8(8),
734        ]
735        .to_vec();
736
737        assert_eq!(chal.output_buffer, expected);
738
739        let first: G = chal.sample();
740        let second: G = chal.sample();
741
742        // sampling pops from end of output buffer
743        assert_eq!(first, G::from_u8(8));
744        assert_eq!(second, G::from_u8(9));
745    }
746
747    #[test]
748    fn test_duplexing_only_when_needed() {
749        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
750        chal.output_buffer = vec![G::from_u8(10), G::from_u8(20)];
751
752        // Sample should not call duplexing; just pop from the buffer
753        let sample: G = chal.sample();
754        assert_eq!(sample, G::from_u8(20));
755        assert_eq!(chal.output_buffer, vec![G::from_u8(10)]);
756    }
757
758    #[test]
759    fn test_flush_when_input_full() {
760        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
761
762        // Observe RATE elements, causing a duplexing
763        for i in 0..RATE {
764            chal.observe(G::from_u8(i as u8));
765        }
766
767        // The absorbed length RATE sits in the capacity slot.
768        // Reversing the state moves it to index 7 of the output.
769        let expected_output = [
770            G::from_u8(0),
771            G::from_u8(0),
772            G::from_u8(0),
773            G::from_u8(0),
774            G::from_u8(0),
775            G::from_u8(0),
776            G::from_u8(0),
777            G::from_u8(RATE as u8),
778            G::from_u8(15),
779            G::from_u8(14),
780            G::from_u8(13),
781            G::from_u8(12),
782            G::from_u8(11),
783            G::from_u8(10),
784            G::from_u8(9),
785            G::from_u8(8),
786        ]
787        .to_vec();
788
789        // Input buffer should be drained after duplexing
790        assert!(chal.input_buffer.is_empty());
791
792        // Output buffer should match expected state from duplexing
793        assert_eq!(chal.output_buffer, expected_output);
794    }
795
796    #[test]
797    fn test_observe_base_as_algebra_element_consistency_with_direct_observe() {
798        // Create two identical challengers to verify behavior equivalence
799        let mut chal1 =
800            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
801        let mut chal2 =
802            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
803
804        let base_val = G::from_u8(99);
805
806        // Method 1: Use the convenience method for base-to-extension observation
807        chal1.observe_base_as_algebra_element::<EF2G>(base_val);
808
809        // Method 2: Manually convert to extension field then observe
810        let ext_val = EF2G::from(base_val);
811        chal2.observe_algebra_element(ext_val);
812
813        // Both methods must produce identical internal state
814        assert_eq!(chal1.input_buffer, chal2.input_buffer);
815        assert_eq!(chal1.output_buffer, chal2.output_buffer);
816        assert_eq!(chal1.sponge_state, chal2.sponge_state);
817    }
818
819    #[test]
820    fn test_observe_base_as_algebra_element_stream_consistency() {
821        // Create two identical challengers for stream observation test
822        let mut chal1 =
823            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
824        let mut chal2 =
825            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
826
827        // Define a base value vector
828        let base_values: Vec<_> = (0u8..25).map(G::from_u8).collect();
829
830        // Method 1: Observe stream using convenience method
831        for &val in &base_values {
832            chal1.observe_base_as_algebra_element::<EF2G>(val);
833        }
834
835        // Method 2: Manually convert each element before observing
836        for &val in &base_values {
837            let ext_val = EF2G::from(val);
838            chal2.observe_algebra_element(ext_val);
839        }
840
841        // Verify identical state through sequential observations and duplexing.
842        assert_eq!(chal1.input_buffer, chal2.input_buffer);
843        assert_eq!(chal1.output_buffer, chal2.output_buffer);
844        assert_eq!(chal1.sponge_state, chal2.sponge_state);
845
846        // Verify sampling produces identical challenges
847        let sample1: EF2G = chal1.sample_algebra_element();
848        let sample2: EF2G = chal2.sample_algebra_element();
849        assert_eq!(sample1, sample2);
850
851        // Verify state consistency is maintained after sampling
852        assert_eq!(chal1.input_buffer, chal2.input_buffer);
853        assert_eq!(chal1.output_buffer, chal2.output_buffer);
854        assert_eq!(chal1.sponge_state, chal2.sponge_state);
855    }
856
857    #[test]
858    fn test_observe_algebra_elements_equivalence() {
859        // Test that the two following paths give the same results:
860        // - `observe_algebra_slice`
861        // - `observe_algebra_element` in a loop
862        let mut chal1 =
863            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
864        let mut chal2 =
865            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
866
867        // Create a slice of extension field elements
868        let ext_values: Vec<EF2G> = (0u8..10).map(|i| EF2G::from(G::from_u8(i))).collect();
869
870        // Method 1: Use observe_algebra_slice with slice
871        chal1.observe_algebra_slice(&ext_values);
872
873        // Method 2: Call observe_algebra_element individually
874        for ext_val in &ext_values {
875            chal2.observe_algebra_element(*ext_val);
876        }
877
878        // Verify identical internal state
879        assert_eq!(chal1.input_buffer, chal2.input_buffer);
880        assert_eq!(chal1.output_buffer, chal2.output_buffer);
881        assert_eq!(chal1.sponge_state, chal2.sponge_state);
882
883        // Verify sampling produces identical challenges
884        let sample1: EF2G = chal1.sample_algebra_element();
885        let sample2: EF2G = chal2.sample_algebra_element();
886        assert_eq!(sample1, sample2);
887    }
888
889    #[test]
890    fn test_observe_algebra_elements_empty_slice() {
891        // Test that observing an empty slice does not change state
892        let mut chal1 =
893            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
894        let mut chal2 =
895            DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
896
897        // Observe some values first to have non-trivial state
898        chal1.observe(G::from_u8(42));
899        chal2.observe(G::from_u8(42));
900
901        // Observe empty slice
902        let empty: Vec<EF2G> = vec![];
903        chal1.observe_algebra_slice(&empty);
904
905        // Verify state unchanged
906        assert_eq!(chal1.input_buffer, chal2.input_buffer);
907        assert_eq!(chal1.output_buffer, chal2.output_buffer);
908        assert_eq!(chal1.sponge_state, chal2.sponge_state);
909    }
910
911    #[test]
912    fn test_observe_algebra_elements_triggers_duplexing() {
913        // Test that observing enough elements triggers duplexing
914        let mut chal = DuplexChallenger::<G, TestPermutation, WIDTH, RATE>::new(TestPermutation {});
915
916        // EF2G has dimension 2, so we need RATE/2 elements to fill the buffer
917        //
918        // With RATE=16, we need 8 EF2G elements to trigger duplexing
919        let ext_values: Vec<EF2G> = (0u8..8).map(|i| EF2G::from(G::from_u8(i))).collect();
920
921        assert!(chal.input_buffer.is_empty());
922        assert!(chal.output_buffer.is_empty());
923
924        chal.observe_algebra_slice(&ext_values);
925
926        // After observing 8 EF2G elements (16 base field elements), duplexing should occur
927        assert!(chal.input_buffer.is_empty());
928        assert!(!chal.output_buffer.is_empty());
929    }
930
931    #[test]
932    fn test_finalize() {
933        let new_chal = || DuplexChallenger::<G, _, WIDTH, RATE>::new(TestPermutation {});
934
935        // Deterministic: same observations produce same digest.
936        let mut c1 = new_chal();
937        let mut c2 = new_chal();
938        for i in 0..5u8 {
939            c1.observe(G::from_u8(i));
940            c2.observe(G::from_u8(i));
941        }
942        assert_eq!(c1.finalize(), c2.finalize());
943
944        // Different observations produce different digests.
945        let mut c1 = new_chal();
946        let mut c2 = new_chal();
947        for i in 0..10u8 {
948            c1.observe(G::from_u8(i));
949            c2.observe(G::from_u8(i + 1));
950        }
951        assert_ne!(c1.finalize(), c2.finalize());
952    }
953
954    /// Document how sampling interacts with finalize.
955    ///
956    /// Sampling does not modify the sponge state — it only pops from the
957    /// output buffer. This means the digest only changes when a sample
958    /// triggers a new duplexing (because the output buffer was empty or
959    /// there was pending input). Within one "batch" of RATE outputs, all
960    /// sample counts produce the same digest.
961    #[test]
962    fn test_finalize_sample_interaction() {
963        let digest = |n_samples: usize| {
964            let mut c = DuplexChallenger::<G, _, WIDTH, RATE>::new(TestPermutation {});
965            for i in 0..5u8 {
966                c.observe(G::from_u8(i));
967            }
968            for _ in 0..n_samples {
969                let _: G = c.sample();
970            }
971            c.finalize()
972        };
973
974        // The first sample triggers duplexing (absorbs pending input),
975        // so finalize's duplexing is now an extra permutation on an
976        // already-duplexed state — different from the 0-sample case.
977        assert_ne!(digest(0), digest(1));
978
979        // Samples 1 through RATE all come from the same output batch.
980        // They don't trigger another duplexing, so the sponge state
981        // (and thus the digest) is identical.
982        assert_eq!(digest(1), digest(2));
983        assert_eq!(digest(1), digest(RATE));
984
985        // The (RATE+1)-th sample exhausts the output buffer and triggers
986        // a fresh duplexing, changing the sponge state again.
987        assert_ne!(digest(RATE), digest(RATE + 1));
988
989        // Within the second batch, the digest is again stable.
990        assert_eq!(digest(RATE + 1), digest(RATE + 2));
991    }
992}