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}