Skip to main content

lib_q_stark_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 complex_field_challenger;
8mod duplex_challenger;
9mod grinding_challenger;
10mod hash_challenger;
11mod multi_field_challenger;
12mod serializing_challenger;
13
14use alloc::vec::Vec;
15use core::array;
16
17pub use complex_field_challenger::ComplexFieldChallenger;
18pub use duplex_challenger::*;
19pub use grinding_challenger::*;
20pub use hash_challenger::*;
21use lib_q_stark_field::{
22    Algebra,
23    BasedVectorSpace,
24    Field,
25};
26use lib_q_stark_sha3_256::Sha3_256Hash;
27// Convenience type aliases for hash-based challengers
28use lib_q_stark_shake128::Shake128Hash;
29use lib_q_stark_shake256::Shake256Hash;
30pub use multi_field_challenger::*;
31pub use serializing_challenger::*;
32
33/// A SHAKE128-based challenger for 32-bit prime fields.
34///
35/// This is a NIST-approved, post-quantum secure challenger suitable for production use.
36/// It uses SHAKE128 (SHA-3 family) for Fiat-Shamir challenge generation.
37/// SHAKE128 provides 128-bit security level, making it a lighter option than SHAKE256.
38pub type Shake128Challenger32<F> = SerializingChallenger32<F, HashChallenger<u8, Shake128Hash, 32>>;
39
40/// A SHAKE128-based challenger for 64-bit prime fields.
41///
42/// This is a NIST-approved, post-quantum secure challenger suitable for production use.
43/// It uses SHAKE128 (SHA-3 family) for Fiat-Shamir challenge generation.
44/// SHAKE128 provides 128-bit security level, making it a lighter option than SHAKE256.
45pub type Shake128Challenger64<F> = SerializingChallenger64<F, HashChallenger<u8, Shake128Hash, 32>>;
46
47/// A SHAKE256-based challenger for 32-bit prime fields.
48///
49/// This is a NIST-approved, post-quantum secure challenger suitable for production use.
50/// It uses SHAKE256 (SHA-3 family) for Fiat-Shamir challenge generation.
51/// SHAKE256 provides 256-bit security level and is the recommended default.
52///
53/// # Modular Architecture
54///
55/// The challenger architecture is generic over hash functions. To use a different NIST-approved
56/// hash function, implement `CryptographicHasher<u8, [u8; N]>` for your hash type and use:
57/// ```ignore
58/// use lib_q_stark_challenger::{SerializingChallenger32, HashChallenger};
59/// type MyChallenger<F> =
60///     SerializingChallenger32<F, HashChallenger<u8, MyHash, N>>;
61/// ```
62///
63/// Available hash options:
64/// - [`Shake128Challenger32`] - SHAKE128 (128-bit security, lighter)
65/// - [`Shake256Challenger32`] - SHAKE256 (256-bit security, recommended)
66/// - [`Sha3_256Challenger32`] - SHA3-256 (256-bit security, fixed-length)
67pub type Shake256Challenger32<F> = SerializingChallenger32<F, HashChallenger<u8, Shake256Hash, 32>>;
68
69/// A SHAKE256-based challenger for 64-bit prime fields.
70///
71/// This is a NIST-approved, post-quantum secure challenger suitable for production use.
72/// It uses SHAKE256 (SHA-3 family) for Fiat-Shamir challenge generation.
73/// SHAKE256 provides 256-bit security level and is the recommended default.
74///
75/// # Modular Architecture
76///
77/// See [`Shake256Challenger32`] for details on the modular hash architecture.
78pub type Shake256Challenger64<F> = SerializingChallenger64<F, HashChallenger<u8, Shake256Hash, 32>>;
79
80/// A SHA3-256-based challenger for 32-bit prime fields.
81///
82/// This is a NIST-approved, post-quantum secure challenger suitable for production use.
83/// It uses SHA3-256 (SHA-3 family) for Fiat-Shamir challenge generation.
84/// SHA3-256 provides 256-bit security level with fixed-length output (unlike XOF functions).
85pub type Sha3_256Challenger32<F> = SerializingChallenger32<F, HashChallenger<u8, Sha3_256Hash, 32>>;
86
87/// A SHA3-256-based challenger for 64-bit prime fields.
88///
89/// This is a NIST-approved, post-quantum secure challenger suitable for production use.
90/// It uses SHA3-256 (SHA-3 family) for Fiat-Shamir challenge generation.
91/// SHA3-256 provides 256-bit security level with fixed-length output (unlike XOF functions).
92pub type Sha3_256Challenger64<F> = SerializingChallenger64<F, HashChallenger<u8, Sha3_256Hash, 32>>;
93
94/// A generic trait for absorbing elements into the transcript.
95///
96/// Absorbed elements update the internal sponge state,
97/// preparing it to deterministically produce future challenges.
98pub trait CanObserve<T> {
99    /// Absorb a single value into the transcript.
100    fn observe(&mut self, value: T);
101
102    /// Absorb a slice of values into the transcript.
103    fn observe_slice(&mut self, values: &[T])
104    where
105        T: Clone,
106    {
107        for value in values {
108            self.observe(value.clone());
109        }
110    }
111}
112
113/// A trait for sampling challenge elements from the Fiat-Shamir transcript.
114///
115/// Sampling produces pseudo-random elements deterministically derived
116/// from the absorbed inputs and the sponge state.
117pub trait CanSample<T> {
118    /// Sample a single challenge value from the transcript.
119    fn sample(&mut self) -> T;
120
121    /// Sample an array of `N` challenge values from the transcript.
122    fn sample_array<const N: usize>(&mut self) -> [T; N] {
123        array::from_fn(|_| self.sample())
124    }
125
126    /// Sample a `Vec` of `n` challenge values from the transcript.
127    fn sample_vec(&mut self, n: usize) -> Vec<T> {
128        (0..n).map(|_| self.sample()).collect()
129    }
130}
131
132/// A trait for sampling random bitstrings from the Fiat-Shamir transcript.
133pub trait CanSampleBits<T> {
134    /// Sample a random `bits`-bit integer from the transcript.
135    ///
136    /// The distribution should be reasonably close to uniform.
137    /// (In practice, a small bias may arise when bit-decomposing a uniformly
138    /// sampled field element)
139    ///
140    /// Guarantees that the returned value fits within the requested bit width.
141    fn sample_bits(&mut self, bits: usize) -> T;
142}
143
144/// A high-level trait combining observation and sampling over a finite field.
145pub trait FieldChallenger<F: Field>:
146    CanObserve<F> + CanSample<F> + CanSampleBits<usize> + Sync
147{
148    /// Absorb an element from a vector space over the base field.
149    ///
150    /// Decomposes the element into its basis coefficients and absorbs each.
151    fn observe_algebra_element<A: BasedVectorSpace<F>>(&mut self, alg_elem: A) {
152        self.observe_slice(alg_elem.as_basis_coefficients_slice());
153    }
154
155    /// Sample an element of a vector space over the base field.
156    ///
157    /// Constructs the element by sampling basis coefficients.
158    fn sample_algebra_element<A: BasedVectorSpace<F>>(&mut self) -> A {
159        A::from_basis_coefficients_fn(|_| self.sample())
160    }
161
162    /// Observe base field elements as extension field elements for recursion-friendly transcripts.
163    ///
164    /// This simplifies recursive verifier circuits by using a uniform extension field challenger.
165    /// Instead of observing a mix of base and extension field elements, we convert all base field
166    /// observations (metadata, public values) to extension field elements before passing to the challenger.
167    ///
168    /// # Recursion Benefits
169    ///
170    /// In recursive proof systems, the verifier circuit needs to verify the inner proof. Since STARK
171    /// verification operates entirely in the extension field (challenges, opened values, constraint
172    /// evaluation), having a challenger that only observes extension field elements significantly
173    /// simplifies the recursive circuit implementation.
174    #[inline]
175    fn observe_base_as_algebra_element<EF>(&mut self, val: F)
176    where
177        EF: Algebra<F> + BasedVectorSpace<F>,
178    {
179        self.observe_algebra_element(EF::from(val));
180    }
181}
182
183impl<C, T> CanObserve<T> for &mut C
184where
185    C: CanObserve<T>,
186{
187    #[inline(always)]
188    fn observe(&mut self, value: T) {
189        (*self).observe(value);
190    }
191
192    #[inline(always)]
193    fn observe_slice(&mut self, values: &[T])
194    where
195        T: Clone,
196    {
197        (*self).observe_slice(values);
198    }
199}
200
201impl<C, T> CanSample<T> for &mut C
202where
203    C: CanSample<T>,
204{
205    #[inline(always)]
206    fn sample(&mut self) -> T {
207        (*self).sample()
208    }
209
210    #[inline(always)]
211    fn sample_array<const N: usize>(&mut self) -> [T; N] {
212        (*self).sample_array()
213    }
214
215    #[inline(always)]
216    fn sample_vec(&mut self, n: usize) -> Vec<T> {
217        (*self).sample_vec(n)
218    }
219}
220
221impl<C, T> CanSampleBits<T> for &mut C
222where
223    C: CanSampleBits<T>,
224{
225    #[inline(always)]
226    fn sample_bits(&mut self, bits: usize) -> T {
227        (*self).sample_bits(bits)
228    }
229}
230
231impl<C, F: Field> FieldChallenger<F> for &mut C
232where
233    C: FieldChallenger<F>,
234{
235    #[inline(always)]
236    fn observe_algebra_element<EF: BasedVectorSpace<F>>(&mut self, ext: EF) {
237        (*self).observe_algebra_element(ext);
238    }
239
240    #[inline(always)]
241    fn sample_algebra_element<EF: BasedVectorSpace<F>>(&mut self) -> EF {
242        (*self).sample_algebra_element()
243    }
244
245    #[inline(always)]
246    fn observe_base_as_algebra_element<EF>(&mut self, val: F)
247    where
248        EF: Algebra<F> + BasedVectorSpace<F>,
249    {
250        (*self).observe_base_as_algebra_element::<EF>(val);
251    }
252}