Skip to main content

p3_challenger/
lib.rs

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