Skip to main content

p3_binary_field/
challenger.rs

1//! A byte-sampling Fiat–Shamir challenger for the binary tower fields.
2
3use alloc::vec::Vec;
4use core::iter::repeat;
5use core::marker::PhantomData;
6
7use p3_challenger::{
8    CanObserve, CanSample, CanSampleBits, CanSampleUniformBits, FieldChallenger,
9    GrindingChallenger, HashChallenger, ResamplingError,
10};
11use p3_field::BasedVectorSpace;
12use p3_maybe_rayon::prelude::*;
13use p3_symmetric::{CryptographicHasher, Hash, MerkleCap};
14use tracing::instrument;
15
16use crate::tower::TowerLevel;
17
18/// The number of transcript bytes consumed by one bitstring sample.
19///
20/// Fixing this at eight keeps the transcript independent of the target's pointer width.
21const BITS_SAMPLE_BYTES: usize = 8;
22
23/// The headroom, in bits, that a grinding request must leave below the width of the
24/// candidate space `grind` actually searches.
25///
26/// `grind` enumerates candidates with a 64-bit counter, so that space is `min(F::bits(),
27/// 64)` bits wide even for fields wider than `u64`. A candidate satisfies a `bits`-wide
28/// check with probability `2^-bits`, so a search space of `w` bits offers `2^(w - bits)`
29/// expected successes. Eight bits of headroom keep that expectation at `>= 256`, so an
30/// exhaustive search that finds nothing is astronomically unlikely rather than merely
31/// improbable.
32const GRIND_MARGIN_BITS: usize = 8;
33
34/// A challenger for a binary tower field, driven by a challenger over bytes.
35///
36/// A tower level is a whole number of bytes wide, and every bit pattern is an element.
37///
38/// A transcript over the field is therefore a transcript over its bytes.
39/// Nothing is encoded, decoded or rejection-sampled in either direction.
40///
41/// **Observing**: absorbs the little-endian bytes of the element into the inner challenger.
42///
43/// **Sampling**: reads the little-endian bytes of the element from the inner challenger.
44/// Since the field has exactly `2^(8·NUM_BYTES)` elements, uniform bytes give a uniform
45/// field element.
46///
47/// **Proof of work**: unlike the field and bit sampling above, which work at every level,
48/// grinding is only viable in the wider ones. [`GrindingChallenger::grind`] enumerates
49/// candidates with a 64-bit counter and demands `GRIND_MARGIN_BITS` of headroom below that
50/// space, which puts a ceiling of `min(F::bits(), 64) − GRIND_MARGIN_BITS` on the width of a
51/// single request:
52///
53/// | witness field | largest supported `bits` |
54/// | --- | --- |
55/// | [`BinaryField2`](crate::BinaryField2), [`BinaryField4`](crate::BinaryField4), [`BinaryField8`](crate::BinaryField8) | none: every nonzero request panics |
56/// | [`BinaryField16`](crate::BinaryField16) | 8 |
57/// | [`BinaryField32`](crate::BinaryField32) | 24 |
58/// | [`BinaryField64`](crate::BinaryField64) | 56 |
59/// | [`BinaryField128`](crate::BinaryField128) | 56, bounded by the counter rather than the field |
60///
61/// So the levels below `GF(2^32)` implement [`GrindingChallenger`] without being usable
62/// proof-of-work witness types, and a 128-bit witness buys no more width than a 64-bit one.
63#[derive(Clone, Debug)]
64pub struct BinaryChallenger<F, Inner> {
65    inner: Inner,
66    _marker: PhantomData<F>,
67}
68
69impl<F, Inner> BinaryChallenger<F, Inner> {
70    /// Wrap a challenger over bytes.
71    pub const fn new(inner: Inner) -> Self {
72        Self {
73            inner,
74            _marker: PhantomData,
75        }
76    }
77}
78
79impl<F, H> BinaryChallenger<F, HashChallenger<u8, H, 32>>
80where
81    H: CryptographicHasher<u8, [u8; 32]>,
82{
83    /// Wrap a fresh [`HashChallenger`] over the given hasher and initial transcript.
84    pub const fn from_hasher(initial_state: Vec<u8>, hasher: H) -> Self {
85        Self::new(HashChallenger::new(initial_state, hasher))
86    }
87}
88
89impl<F: TowerLevel, Inner: CanObserve<u8>> CanObserve<F> for BinaryChallenger<F, Inner> {
90    fn observe(&mut self, value: F) {
91        let mut bytes = [0; 16];
92        for (slot, byte) in bytes.iter_mut().zip(value.into_bytes()) {
93            *slot = byte;
94        }
95        self.inner.observe_slice(&bytes[..F::NUM_BYTES]);
96    }
97}
98
99impl<F, G, const N: usize, Inner: CanObserve<u8>> CanObserve<Hash<G, u8, N>>
100    for BinaryChallenger<F, Inner>
101{
102    fn observe(&mut self, values: Hash<G, u8, N>) {
103        self.inner.observe_slice(values.as_ref());
104    }
105}
106
107impl<F, G, const N: usize, Inner: CanObserve<u8>> CanObserve<&MerkleCap<G, [u8; N]>>
108    for BinaryChallenger<F, Inner>
109{
110    fn observe(&mut self, cap: &MerkleCap<G, [u8; N]>) {
111        for digest in cap.roots() {
112            self.inner.observe_slice(digest);
113        }
114    }
115}
116
117impl<F, G, const N: usize, Inner: CanObserve<u8>> CanObserve<MerkleCap<G, [u8; N]>>
118    for BinaryChallenger<F, Inner>
119{
120    fn observe(&mut self, cap: MerkleCap<G, [u8; N]>) {
121        self.observe(&cap);
122    }
123}
124
125/// Word digests are absorbed as the little-endian bytes of each word, in order.
126///
127/// The digest's own field is free of this challenger's, as it is for a byte digest.
128/// A commitment over one level can then be bound into a transcript over another.
129impl<F, G, const N: usize, Inner: CanObserve<u8>> CanObserve<Hash<G, u64, N>>
130    for BinaryChallenger<F, Inner>
131{
132    fn observe(&mut self, values: Hash<G, u64, N>) {
133        for value in values {
134            self.inner.observe_slice(&value.to_le_bytes());
135        }
136    }
137}
138
139impl<F, G, const N: usize, Inner: CanObserve<u8>> CanObserve<&MerkleCap<G, [u64; N]>>
140    for BinaryChallenger<F, Inner>
141{
142    fn observe(&mut self, cap: &MerkleCap<G, [u64; N]>) {
143        for digest in cap.roots() {
144            for value in digest {
145                self.inner.observe_slice(&value.to_le_bytes());
146            }
147        }
148    }
149}
150
151impl<F, G, const N: usize, Inner: CanObserve<u8>> CanObserve<MerkleCap<G, [u64; N]>>
152    for BinaryChallenger<F, Inner>
153{
154    fn observe(&mut self, cap: MerkleCap<G, [u64; N]>) {
155        self.observe(&cap);
156    }
157}
158
159impl<F, EF, Inner> CanSample<EF> for BinaryChallenger<F, Inner>
160where
161    F: TowerLevel,
162    EF: BasedVectorSpace<F>,
163    Inner: CanSample<u8>,
164{
165    fn sample(&mut self) -> EF {
166        let inner = &mut self.inner;
167        // Every bit pattern is a field element, so reading `NUM_BYTES` uniform bytes per
168        // basis coefficient samples uniformly from `EF`.
169        EF::from_basis_coefficients_fn(|_| {
170            let mut bytes = [0; 16];
171            inner.sample_into_slice(&mut bytes[..F::NUM_BYTES]);
172            F::from_le_byte_iter(bytes.into_iter())
173        })
174    }
175}
176
177impl<F, Inner> CanSampleBits<usize> for BinaryChallenger<F, Inner>
178where
179    F: TowerLevel,
180    Inner: CanSample<u8>,
181{
182    /// Sample a bitstring by masking bytes taken from the inner stream.
183    ///
184    /// A challenger that bit-decomposes a sampled field element can only extract as many
185    /// bits as that element carries, and so has to bound the request by the field order.
186    /// This one instead reads its entropy straight off the inner byte stream, so how many
187    /// bits are available per call does not depend on `F`'s width at all: a challenger over
188    /// a narrow level can still draw a query index far wider than one of its own elements.
189    ///
190    /// The one real bound is that the result must fit in a `usize`, which also keeps it
191    /// within the `u64` the bytes are read into.
192    fn sample_bits(&mut self, bits: usize) -> usize {
193        assert!(
194            bits < (usize::BITS as usize),
195            "requested bit count must fit within a usize"
196        );
197        let mut bytes = [0; BITS_SAMPLE_BYTES];
198        self.inner.sample_into_slice(&mut bytes);
199        let rand_u64 = u64::from_le_bytes(bytes);
200        (rand_u64 & ((1u64 << bits) - 1)) as usize
201    }
202}
203
204impl<F, Inner> CanSampleUniformBits<F> for BinaryChallenger<F, Inner>
205where
206    F: TowerLevel,
207    Inner: CanSample<u8>,
208{
209    /// Sample uniform bits by masking bytes from the inner stream.
210    ///
211    /// # Overview
212    ///
213    /// The inner stream emits cryptographic-hash bytes uniform on `[0, 2^8)`.
214    ///
215    /// Reading 8 bytes as a 64-bit integer and masking the low `bits` is exactly uniform
216    /// on `[0, 2^bits)`.
217    ///
218    /// No field-element decomposition occurs, so no rejection band exists. The const
219    /// generic is therefore inert: this function never errors and never resamples.
220    fn sample_uniform_bits<const RESAMPLE: bool>(
221        &mut self,
222        bits: usize,
223    ) -> Result<usize, ResamplingError> {
224        // Byte-sourced sampling is uniform without rejection, so the result is always
225        // valid and the error arm is unreachable.
226        Ok(self.sample_bits(bits))
227    }
228}
229
230/// The `index`-th grinding candidate: the element whose low bytes are those of `index`.
231///
232/// This is injective on the candidate range of every level: narrower levels stop before the
233/// counter wraps their bit width, and wider ones are zero-extended.
234#[inline]
235fn candidate<F: TowerLevel>(index: u64) -> F {
236    F::from_le_byte_iter(index.to_le_bytes().into_iter().chain(repeat(0)))
237}
238
239impl<F, Inner> GrindingChallenger for BinaryChallenger<F, Inner>
240where
241    F: TowerLevel,
242    Inner: CanSample<u8> + CanObserve<u8> + Clone + Send + Sync,
243{
244    type Witness = F;
245
246    /// # Panics
247    /// Panics unless `bits + GRIND_MARGIN_BITS <= min(F::bits(), 64)`. See the per-level
248    /// ceiling tabulated on [`BinaryChallenger`].
249    #[instrument(name = "grind for proof-of-work witness", skip_all)]
250    fn grind(&mut self, bits: usize) -> Self::Witness {
251        // Trivial case: 0 bits mean no PoW is required and any witness is valid.
252        if bits == 0 {
253            return F::ZERO;
254        }
255
256        assert!(bits < (usize::BITS as usize));
257        assert!(
258            bits + GRIND_MARGIN_BITS <= F::bits().min(u64::BITS as usize),
259            "requested bit count leaves too small a margin against the witness field's size"
260        );
261
262        // Candidates are enumerated by a 64-bit counter, which exhausts the field only for
263        // the levels narrower than `GF(2^64)`.
264        let num_candidates = if F::bits() >= u64::BITS as usize {
265            u64::MAX
266        } else {
267            1u64 << F::bits()
268        };
269
270        let witness = (0..num_candidates)
271            .into_par_iter()
272            .map_init(
273                || self.clone(),
274                |worker, index| {
275                    worker.inner.clone_from(&self.inner);
276                    let witness = candidate(index);
277                    worker.check_witness(bits, witness).then_some(witness)
278                },
279            )
280            .find_map_any(core::convert::identity)
281            .expect("failed to find witness");
282        assert!(self.check_witness(bits, witness));
283        witness
284    }
285}
286
287impl<F, Inner> FieldChallenger<F> for BinaryChallenger<F, Inner>
288where
289    F: TowerLevel,
290    Inner: CanSample<u8> + CanObserve<u8> + Clone + Send + Sync,
291{
292}
293
294#[cfg(test)]
295mod tests {
296    use alloc::vec;
297    use alloc::vec::Vec;
298
299    use p3_challenger::{
300        CanObserve, CanSample, CanSampleBits, CanSampleUniformBits, FieldChallenger,
301        GrindingChallenger, HashChallenger,
302    };
303    use p3_field::{BasedVectorSpace, Field, PrimeCharacteristicRing, RawDataSerializable};
304    use p3_keccak::Keccak256Hash;
305    use p3_symmetric::{Hash, MerkleCap};
306
307    use crate::challenger::{BITS_SAMPLE_BYTES, BinaryChallenger};
308    use crate::tower::TowerLevel;
309    use crate::{BinaryField8, BinaryField32, BinaryField128, Gf2, Ghash128};
310
311    type Inner = HashChallenger<u8, Keccak256Hash, 32>;
312
313    /// A challenger over an empty transcript.
314    fn mk<F>() -> BinaryChallenger<F, Inner> {
315        BinaryChallenger::from_hasher(vec![], Keccak256Hash)
316    }
317
318    /// An inner challenger over the same empty transcript, to compare byte streams against.
319    fn mk_inner() -> Inner {
320        HashChallenger::new(vec![], Keccak256Hash)
321    }
322
323    /// A fixed nonzero element of `GF(2^128)`.
324    fn a128() -> BinaryField128 {
325        BinaryField128::from_le_bytes([
326            0x21, 0x43, 0x65, 0x87, 0xa9, 0xcb, 0xed, 0x0f, 0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56,
327            0x34, 0x12,
328        ])
329    }
330
331    #[test]
332    fn the_polynomial_basis_field_shares_the_transcript_with_the_tower() {
333        // Both representations are 16 bytes wide with every pattern a valid element, so a
334        // transcript over one is byte-for-byte the transcript over the other.
335        //
336        //     observe tower element -> same bytes absorbed -> same bytes drawn
337        //
338        // What differs is only how those bytes are read as a field element.
339        let element = a128();
340
341        let mut tower = mk::<BinaryField128>();
342        tower.observe(element);
343        let drawn: BinaryField128 = tower.sample();
344
345        let mut ghash = mk::<Ghash128>();
346        ghash.observe(Ghash128::from_le_byte_iter(
347            element.into_bytes().into_iter(),
348        ));
349        let mirrored: Ghash128 = ghash.sample();
350
351        assert_eq!(
352            drawn.into_bytes().into_iter().collect::<Vec<_>>(),
353            mirrored.into_bytes().into_iter().collect::<Vec<_>>()
354        );
355    }
356
357    #[test]
358    fn sampling_is_deterministic_and_observation_dependent() {
359        // Identical (empty) histories produce identical challenges.
360        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
361        let (x, y): (BinaryField128, BinaryField128) = (a.sample(), b.sample());
362        assert_eq!(x, y);
363
364        // Identical observations produce identical challenges.
365        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
366        a.observe(a128());
367        b.observe(a128());
368        let (x, y): (BinaryField128, BinaryField128) = (a.sample(), b.sample());
369        assert_eq!(x, y);
370
371        // Different observations produce different challenges.
372        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
373        a.observe(BinaryField128::ONE);
374        b.observe(BinaryField128::ZERO);
375        let (x, y): (BinaryField128, BinaryField128) = (a.sample(), b.sample());
376        assert_ne!(x, y);
377
378        // Observing at all changes what is sampled next.
379        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
380        b.observe(BinaryField128::ZERO);
381        let (x, y): (BinaryField128, BinaryField128) = (a.sample(), b.sample());
382        assert_ne!(x, y);
383
384        // The transcript advances: consecutive challenges differ, and the sequence is
385        // reproducible from an identical history.
386        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
387        let xs: [BinaryField128; 4] = a.sample_array();
388        let ys: [BinaryField128; 4] = b.sample_array();
389        assert_eq!(xs, ys);
390        assert_ne!(xs[0], xs[1]);
391        assert_ne!(xs[1], xs[2]);
392        assert_ne!(xs[2], xs[3]);
393    }
394
395    #[test]
396    fn observing_an_element_absorbs_its_little_endian_bytes() {
397        let a = a128();
398
399        let mut challenger = mk::<BinaryField128>();
400        challenger.observe(a);
401
402        let mut inner = mk_inner();
403        inner.observe_slice(&a.into_bytes().into_iter().collect::<Vec<_>>());
404
405        // Compare a full digest worth of the resulting byte streams.
406        for _ in 0..32 {
407            let observed: u8 = challenger.inner.sample();
408            let expected: u8 = inner.sample();
409            assert_eq!(observed, expected);
410        }
411    }
412
413    #[test]
414    fn a_sampled_element_is_read_from_the_little_endian_byte_stream() {
415        let mut challenger = mk::<BinaryField128>();
416        let sampled: BinaryField128 = challenger.sample();
417
418        let mut inner = mk_inner();
419        let bytes: [u8; 16] = core::array::from_fn(|_| inner.sample());
420
421        assert_eq!(sampled, BinaryField128::from_le_bytes(bytes));
422    }
423
424    #[test]
425    fn a_sampled_element_of_a_narrow_level_reads_only_its_own_bytes() {
426        let mut challenger = mk::<BinaryField32>();
427        let sampled: BinaryField32 = challenger.sample();
428
429        let mut inner = mk_inner();
430        let bytes: [u8; 4] = core::array::from_fn(|_| inner.sample());
431
432        assert_eq!(sampled, BinaryField32::from_le_bytes(bytes));
433    }
434
435    #[test]
436    fn sampling_an_extension_element_reads_its_basis_coefficients_in_order() {
437        let mut challenger = mk::<BinaryField8>();
438        let sampled: BinaryField128 = challenger.sample();
439
440        let mut coefficients = mk::<BinaryField8>();
441        let coeffs: [BinaryField8; 16] = core::array::from_fn(|_| coefficients.sample());
442
443        assert_eq!(
444            <BinaryField128 as BasedVectorSpace<BinaryField8>>::from_basis_coefficients_fn(|i| {
445                coeffs[i]
446            }),
447            sampled
448        );
449    }
450
451    #[test]
452    fn observing_digests_matches_observing_their_bytes() {
453        let digest = [1_u8, 2, 3, 4];
454
455        let mut from_hash = mk::<BinaryField128>();
456        from_hash.observe(Hash::<BinaryField128, u8, 4>::from(digest));
457
458        let cap = MerkleCap::<BinaryField128, [u8; 4]>::new(vec![digest, [5, 6, 7, 8]]);
459        let mut from_cap_ref = mk::<BinaryField128>();
460        from_cap_ref.observe(&cap);
461        let mut from_cap = mk::<BinaryField128>();
462        from_cap.observe(cap);
463
464        let mut hash_bytes = mk_inner();
465        hash_bytes.observe_slice(&digest);
466        let mut cap_bytes = mk_inner();
467        cap_bytes.observe_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
468
469        for _ in 0..32 {
470            let expected: u8 = hash_bytes.sample();
471            assert_eq!(from_hash.inner.sample(), expected);
472
473            let expected: u8 = cap_bytes.sample();
474            assert_eq!(from_cap_ref.inner.sample(), expected);
475            assert_eq!(from_cap.inner.sample(), expected);
476        }
477    }
478
479    #[test]
480    fn observing_word_digests_matches_observing_their_little_endian_bytes() {
481        let digest = [0x0807_0605_0403_0201_u64, 0x100f_0e0d_0c0b_0a09];
482        let second = [0x1817_1615_1413_1211_u64, 0x201f_1e1d_1c1b_1a19];
483
484        let mut from_hash = mk::<BinaryField128>();
485        from_hash.observe(Hash::<BinaryField128, u64, 2>::from(digest));
486
487        let cap = MerkleCap::<BinaryField128, [u64; 2]>::new(vec![digest, second]);
488        let mut from_cap_ref = mk::<BinaryField128>();
489        from_cap_ref.observe(&cap);
490        let mut from_cap = mk::<BinaryField128>();
491        from_cap.observe(cap);
492
493        let mut hash_bytes = mk_inner();
494        hash_bytes.observe_slice(&core::array::from_fn::<u8, 16, _>(|i| i as u8 + 1));
495        let mut cap_bytes = mk_inner();
496        cap_bytes.observe_slice(&core::array::from_fn::<u8, 32, _>(|i| i as u8 + 1));
497
498        for _ in 0..32 {
499            let expected: u8 = hash_bytes.sample();
500            assert_eq!(from_hash.inner.sample(), expected);
501
502            let expected: u8 = cap_bytes.sample();
503            assert_eq!(from_cap_ref.inner.sample(), expected);
504            assert_eq!(from_cap.inner.sample(), expected);
505        }
506    }
507
508    #[test]
509    fn sampled_bits_fit_the_requested_width_and_are_reproducible() {
510        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
511        for bits in 0..32 {
512            let value = a.sample_bits(bits);
513            assert!(value < (1 << bits), "{value} does not fit in {bits} bits");
514            assert_eq!(value, b.sample_bits(bits));
515        }
516
517        // The bit stream depends on the transcript.
518        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
519        a.observe(BinaryField128::ONE);
520        assert_ne!(a.sample_bits(31), b.sample_bits(31));
521    }
522
523    #[test]
524    fn uniform_bits_agree_with_plain_bits_and_never_reject() {
525        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
526        for bits in [0, 1, 7, 8, 16, 31] {
527            assert_eq!(
528                a.sample_uniform_bits::<true>(bits).unwrap(),
529                b.sample_bits(bits)
530            );
531        }
532
533        let (mut a, mut b) = (mk::<BinaryField128>(), mk::<BinaryField128>());
534        for bits in [0, 1, 7, 8, 16, 31] {
535            assert_eq!(
536                a.sample_uniform_bits::<false>(bits).unwrap(),
537                b.sample_bits(bits)
538            );
539        }
540    }
541
542    /// The bits come from the inner byte stream, not from a sampled element, so a request
543    /// wider than the field itself is well defined and must be served.
544    #[test]
545    fn sample_bits_is_not_limited_by_the_width_of_the_field() {
546        // A `GF(2^8)` challenger sampling `GF(2^128)` challenges still has to produce
547        // query indices of a realistic width.
548        assert_eq!(<BinaryField8 as Field>::bits(), 8);
549        let mut challenger = mk::<BinaryField8>();
550        let sampled = challenger.sample_bits(24);
551        assert!(sampled < (1 << 24));
552
553        let mut inner = mk_inner();
554        let bytes: [u8; BITS_SAMPLE_BYTES] = core::array::from_fn(|_| inner.sample());
555        assert_eq!(sampled, (u64::from_le_bytes(bytes) & 0xff_ffff) as usize);
556
557        // The uniform-bit entry point inherits the same reach.
558        let mut challenger = mk::<BinaryField8>();
559        assert_eq!(challenger.sample_uniform_bits::<true>(24).unwrap(), sampled);
560        let mut challenger = mk::<BinaryField8>();
561        assert_eq!(
562            challenger.sample_uniform_bits::<false>(24).unwrap(),
563            sampled
564        );
565
566        // Right up to the widest request a `usize` can hold.
567        let widest = usize::BITS as usize - 1;
568        let mut challenger = mk::<BinaryField8>();
569        assert!(challenger.sample_bits(widest) < (1 << widest));
570    }
571
572    #[test]
573    #[should_panic = "requested bit count must fit within a usize"]
574    fn sample_bits_rejects_a_request_wider_than_a_usize() {
575        let mut challenger = mk::<BinaryField128>();
576        let _ = challenger.sample_bits(usize::BITS as usize);
577    }
578
579    #[test]
580    #[should_panic = "requested bit count leaves too small a margin"]
581    fn grind_rejects_a_request_wider_than_the_field() {
582        let mut challenger = mk::<BinaryField32>();
583        let _ = challenger.grind(32);
584    }
585
586    #[test]
587    #[should_panic = "requested bit count leaves too small a margin"]
588    fn grind_rejects_a_request_inside_the_margin() {
589        // Narrower than the field, but too close to it: only `2^7` expected witnesses.
590        let mut challenger = mk::<BinaryField32>();
591        let _ = challenger.grind(25);
592    }
593
594    /// An inner challenger that always samples zero bytes, so every candidate trivially
595    /// satisfies `check_witness`. This lets a test drive `grind` at bit widths whose real
596    /// exhaustive search would otherwise be intractably slow to actually run.
597    #[derive(Clone)]
598    struct AlwaysZeroInner;
599
600    impl CanObserve<u8> for AlwaysZeroInner {
601        fn observe(&mut self, _value: u8) {}
602    }
603
604    impl CanSample<u8> for AlwaysZeroInner {
605        fn sample(&mut self) -> u8 {
606            0
607        }
608    }
609
610    #[test]
611    #[should_panic = "requested bit count leaves too small a margin"]
612    fn grind_rejects_a_request_the_u64_cap_no_longer_allows() {
613        // `BinaryField128`'s candidates are enumerated by a 64-bit counter, so the real
614        // search space is 64 bits wide, not the field's 128. Before accounting for that
615        // cap, this request wrongly passed the margin check (`63 + 8 <= 128`) while the
616        // real candidate space only offered `2^(64 - 63) = 2` expected witnesses.
617        let mut challenger = mk::<BinaryField128>();
618        let _ = challenger.grind(63);
619    }
620
621    #[test]
622    fn grind_accepts_the_widest_request_the_u64_cap_allows() {
623        // The u64-capped search space is 64 bits wide, so `64 - GRIND_MARGIN_BITS = 56` is
624        // the widest request the margin should still allow on `BinaryField128`. An inner
625        // challenger that always samples zero bytes makes every candidate an accepted
626        // witness, so the search completes immediately regardless of how wide `bits` is,
627        // letting this exercise the real `grind` path instead of just its assertions.
628        let mut challenger: BinaryChallenger<BinaryField128, AlwaysZeroInner> =
629            BinaryChallenger::new(AlwaysZeroInner);
630        let bits = 56.min(usize::BITS as usize - 1);
631        let witness = challenger.grind(bits);
632        assert!(challenger.check_witness(bits, witness));
633    }
634
635    #[test]
636    fn a_bitstring_sample_reads_a_fixed_number_of_bytes() {
637        // The width of one bitstring draw must not depend on the target's pointer width,
638        // or transcripts would not agree across targets.
639        let mut challenger = mk::<BinaryField128>();
640        let sampled = challenger.sample_bits(31);
641
642        let mut inner = mk_inner();
643        let bytes: [u8; BITS_SAMPLE_BYTES] = core::array::from_fn(|_| inner.sample());
644
645        assert_eq!(BITS_SAMPLE_BYTES, 8);
646        assert_eq!(
647            sampled,
648            (u64::from_le_bytes(bytes) & ((1 << 31) - 1)) as usize
649        );
650
651        // The next draw continues from where the first one stopped.
652        let next = challenger.sample_bits(8);
653        let bytes: [u8; BITS_SAMPLE_BYTES] = core::array::from_fn(|_| inner.sample());
654        assert_eq!(next, (u64::from_le_bytes(bytes) & 0xff) as usize);
655    }
656
657    #[test]
658    fn grind_satisfies_check() {
659        let mut challenger = mk::<BinaryField128>();
660        let witness = challenger.clone().grind(8);
661        assert!(challenger.check_witness(8, witness));
662    }
663
664    #[test]
665    fn grind_over_a_narrow_level_satisfies_check() {
666        let mut challenger = mk::<BinaryField32>();
667        let witness = challenger.clone().grind(8);
668        assert!(challenger.check_witness(8, witness));
669    }
670
671    #[test]
672    fn grind_of_zero_bits_is_free() {
673        let mut challenger = mk::<BinaryField128>();
674        let mut shadow = challenger.clone();
675
676        let witness = challenger.grind(0);
677        assert_eq!(witness, BinaryField128::ZERO);
678
679        // The transcript must be untouched.
680        for _ in 0..32 {
681            let expected: u8 = shadow.inner.sample();
682            assert_eq!(challenger.inner.sample(), expected);
683        }
684    }
685
686    #[test]
687    fn grind_of_zero_bits_is_free_on_a_level_narrower_than_the_margin() {
688        // `Gf2`'s single bit is narrower than `GRIND_MARGIN_BITS`: the zero-bits short
689        // circuit must run before the margin assertion, or this would panic.
690        let mut challenger = mk::<Gf2>();
691        let mut shadow = challenger.clone();
692
693        let witness = challenger.grind(0);
694        assert_eq!(witness, Gf2::ZERO);
695
696        // The transcript must be untouched.
697        for _ in 0..32 {
698            let expected: u8 = shadow.inner.sample();
699            assert_eq!(challenger.inner.sample(), expected);
700        }
701    }
702
703    #[test]
704    fn a_wrong_witness_fails_the_check() {
705        let mut challenger = mk::<BinaryField128>();
706        let witness = challenger.clone().grind(8);
707
708        // Flipping a bit of the witness invalidates it with probability 1 - 2^-8; the
709        // transcript is fixed, so this is a deterministic statement about this witness.
710        let mut wrong = challenger.clone();
711        assert!(!wrong.check_witness(8, witness + BinaryField128::ONE));
712        assert!(challenger.check_witness(8, witness));
713    }
714
715    /// The generic [`FieldChallenger`] helpers go through the base-field basis.
716    #[test]
717    fn field_challenger_algebra_helpers_round_trip() {
718        let mut challenger = mk::<BinaryField8>();
719        challenger.observe_algebra_element(a128());
720        let sampled: BinaryField128 = challenger.sample_algebra_element();
721
722        let mut expected = mk::<BinaryField8>();
723        expected
724            .observe_slice(BasedVectorSpace::<BinaryField8>::as_basis_coefficients_slice(&a128()));
725        let expected: BinaryField128 = expected.sample();
726
727        assert_eq!(sampled, expected);
728    }
729}