p3_challenger/
lib.rs

1//! Utilities for generating Fiat-Shamir challenges based on an IOP's transcript.
2
3#![no_std]
4
5extern crate alloc;
6
7mod duplex_challenger;
8mod grinding_challenger;
9mod hash_challenger;
10mod multi_field_challenger;
11mod serializing_challenger;
12
13use alloc::vec::Vec;
14use core::array;
15
16pub use duplex_challenger::*;
17pub use grinding_challenger::*;
18pub use hash_challenger::*;
19pub use multi_field_challenger::*;
20use p3_field::{Algebra, BasedVectorSpace, Field, PrimeField64};
21pub use serializing_challenger::*;
22
23/// A generic trait for absorbing elements into the transcript.
24///
25/// Absorbed elements update the internal sponge state,
26/// preparing it to deterministically produce future challenges.
27pub trait CanObserve<T> {
28    /// Absorb a single value into the transcript.
29    fn observe(&mut self, value: T);
30
31    /// Absorb a slice of values into the transcript.
32    fn observe_slice(&mut self, values: &[T])
33    where
34        T: Clone,
35    {
36        for value in values {
37            self.observe(value.clone());
38        }
39    }
40}
41
42/// A trait for sampling challenge elements from the Fiat-Shamir transcript.
43///
44/// Sampling produces pseudo-random elements deterministically derived
45/// from the absorbed inputs and the sponge state.
46pub trait CanSample<T> {
47    /// Sample a single challenge value from the transcript.
48    fn sample(&mut self) -> T;
49
50    /// Sample an array of `N` challenge values from the transcript.
51    fn sample_array<const N: usize>(&mut self) -> [T; N] {
52        array::from_fn(|_| self.sample())
53    }
54
55    /// Sample a `Vec` of `n` challenge values from the transcript.
56    fn sample_vec(&mut self, n: usize) -> Vec<T> {
57        (0..n).map(|_| self.sample()).collect()
58    }
59}
60
61/// A trait for sampling random bitstrings from the Fiat-Shamir transcript.
62pub trait CanSampleBits<T> {
63    /// Sample a random `bits`-bit integer from the transcript.
64    ///
65    /// The distribution should be reasonably close to uniform.
66    /// (In practice, a small bias may arise when bit-decomposing a uniformly
67    /// sampled field element)
68    ///
69    /// Guarantees that the returned value fits within the requested bit width.
70    fn sample_bits(&mut self, bits: usize) -> T;
71}
72
73/// Uniform bit sampling interface.
74///
75/// This trait provides a method for drawing uniformly distributed bitstrings
76/// from a Fiat–Shamir transcript. The goal is to obtain an integer supported
77/// on the range $[0, 2^{bits})$ with each value having equal probability.
78pub trait CanSampleUniformBits<F> {
79    /// Sample a random `bits`-bit integer from the transcript with a guarantee of
80    /// uniformly sampled bits.
81    ///
82    /// Performance overhead depends on the field and number of bits requested.
83    /// E.g. for KoalaBear sampling up to 24 bits uniformly is essentially free.
84    ///
85    /// If `REJECTION_SAMPLE` is set to true then this function will sample multiple field
86    /// elements until it finds one which will produce uniform bits.
87    /// If `REJECTION_SAMPLE` is set to false then this function will sample a single field
88    /// element and produce and error if the value would produce non-uniform bits.
89    ///
90    /// The probability of a panic or a resample is about 1/P for most fields.
91    /// See `UniformSamplingField` implementation for each field for details.
92    fn sample_uniform_bits<const RESAMPLE: bool>(
93        &mut self,
94        bits: usize,
95    ) -> Result<usize, ResamplingError>;
96}
97
98/// A high-level trait combining observation and sampling over a finite field.
99pub trait FieldChallenger<F: Field>:
100    CanObserve<F> + CanSample<F> + CanSampleBits<usize> + Sync
101{
102    /// Absorb an element from a vector space over the base field.
103    ///
104    /// Decomposes the element into its basis coefficients and absorbs each.
105    #[inline(always)]
106    fn observe_algebra_element<A: BasedVectorSpace<F>>(&mut self, alg_elem: A) {
107        self.observe_slice(alg_elem.as_basis_coefficients_slice());
108    }
109
110    /// Absorb a slice of elements from a vector space over the base field.
111    ///
112    /// Decomposes each element into its basis coefficients and absorbs them.
113    #[inline(always)]
114    fn observe_algebra_slice<A: BasedVectorSpace<F> + Clone>(&mut self, alg_elems: &[A]) {
115        for alg_elem in alg_elems {
116            self.observe_algebra_element(alg_elem.clone());
117        }
118    }
119
120    /// Sample an element of a vector space over the base field.
121    ///
122    /// Constructs the element by sampling basis coefficients.
123    #[inline(always)]
124    fn sample_algebra_element<A: BasedVectorSpace<F>>(&mut self) -> A {
125        A::from_basis_coefficients_fn(|_| self.sample())
126    }
127
128    /// Observe base field elements as extension field elements for recursion-friendly transcripts.
129    ///
130    /// This simplifies recursive verifier circuits by using a uniform extension field challenger.
131    /// Instead of observing a mix of base and extension field elements, we convert all base field
132    /// observations (metadata, public values) to extension field elements before passing to the challenger.
133    ///
134    /// # Recursion Benefits
135    ///
136    /// In recursive proof systems, the verifier circuit needs to verify the inner proof. Since STARK
137    /// verification operates entirely in the extension field (challenges, opened values, constraint
138    /// evaluation), having a challenger that only observes extension field elements significantly
139    /// simplifies the recursive circuit implementation.
140    #[inline(always)]
141    fn observe_base_as_algebra_element<EF>(&mut self, val: F)
142    where
143        EF: Algebra<F> + BasedVectorSpace<F>,
144    {
145        self.observe_algebra_element(EF::from(val));
146    }
147}
148
149impl<C, T> CanObserve<T> for &mut C
150where
151    C: CanObserve<T>,
152{
153    #[inline(always)]
154    fn observe(&mut self, value: T) {
155        (*self).observe(value);
156    }
157
158    #[inline(always)]
159    fn observe_slice(&mut self, values: &[T])
160    where
161        T: Clone,
162    {
163        (*self).observe_slice(values);
164    }
165}
166
167impl<C, T> CanSample<T> for &mut C
168where
169    C: CanSample<T>,
170{
171    #[inline(always)]
172    fn sample(&mut self) -> T {
173        (*self).sample()
174    }
175
176    #[inline(always)]
177    fn sample_array<const N: usize>(&mut self) -> [T; N] {
178        (*self).sample_array()
179    }
180
181    #[inline(always)]
182    fn sample_vec(&mut self, n: usize) -> Vec<T> {
183        (*self).sample_vec(n)
184    }
185}
186
187impl<C, T> CanSampleBits<T> for &mut C
188where
189    C: CanSampleBits<T>,
190{
191    #[inline(always)]
192    fn sample_bits(&mut self, bits: usize) -> T {
193        (*self).sample_bits(bits)
194    }
195}
196
197impl<C, F: Field> FieldChallenger<F> for &mut C where C: FieldChallenger<F> {}
198
199impl<C, F> CanSampleUniformBits<F> for &mut C
200where
201    F: PrimeField64,
202    C: CanSampleUniformBits<F>,
203{
204    fn sample_uniform_bits<const RESAMPLE: bool>(
205        &mut self,
206        bits: usize,
207    ) -> Result<usize, ResamplingError> {
208        (*self).sample_uniform_bits::<RESAMPLE>(bits)
209    }
210}