p3_batch_stark/common.rs
1//! Shared data between batch-STARK prover and verifier.
2//!
3//! This module is intended to store per-instance data that is common to both
4//! proving and verification, such as lookup tables and preprocessed traces.
5//!
6//! The preprocessed support integrates with `p3-uni-stark`'s transparent
7//! preprocessed columns API, but batches all preprocessed traces into a single
8//! global commitment (one matrix per instance that uses preprocessed columns).
9
10use alloc::vec;
11use alloc::vec::Vec;
12
13use hashbrown::HashMap;
14use p3_air::Air;
15use p3_air::symbolic::{SymbolicAirBuilder, SymbolicExpressionExt};
16use p3_challenger::FieldChallenger;
17use p3_commit::Pcs;
18use p3_field::{Algebra, BasedVectorSpace};
19use p3_lookup::LookupAir;
20use p3_lookup::lookup_traits::{Kind, Lookup, LookupGadget};
21use p3_matrix::Matrix;
22use p3_uni_stark::Val;
23use p3_util::log2_strict_usize;
24
25use crate::config::{Challenge, Commitment, Domain, StarkGenericConfig as SGC};
26use crate::prover::StarkInstance;
27
28/// Per-instance metadata for a preprocessed trace that lives inside a
29/// global preprocessed commitment.
30#[derive(Clone)]
31pub struct PreprocessedInstanceMeta {
32 /// Index of this instance's preprocessed matrix inside the global [`Pcs`]
33 /// commitment / prover data.
34 pub matrix_index: usize,
35 /// Width (number of columns) of the preprocessed trace.
36 pub width: usize,
37 /// Log2 of the base trace degree for this instance's preprocessed trace.
38 ///
39 /// This matches the log2 of the main trace degree (without ZK padding)
40 /// for that instance.
41 pub degree_bits: usize,
42}
43
44/// Global preprocessed data shared by all batch-STARK instances.
45///
46/// This batches all per-instance preprocessed traces into a single [`Pcs`]
47/// commitment, while keeping a mapping from instance index to matrix index
48/// and per-matrix metadata.
49pub struct GlobalPreprocessed<SC: SGC> {
50 /// Single [`Pcs`] commitment to all preprocessed traces (one matrix per
51 /// instance that defines preprocessed columns).
52 pub commitment: Commitment<SC>,
53 /// For each STARK instance, optional metadata describing its preprocessed
54 /// trace inside the global commitment.
55 ///
56 /// `instances[i] == None` means instance `i` has no preprocessed columns.
57 pub instances: Vec<Option<PreprocessedInstanceMeta>>,
58 /// Mapping from preprocessed matrix index to the corresponding instance index.
59 ///
60 /// This allows building per-matrix opening schedules and routing opened
61 /// values back to instances.
62 pub matrix_to_instance: Vec<usize>,
63}
64
65/// Struct storing data common to both the prover and verifier.
66// TODO: Optionally cache a single challenger seed for transparent
67// preprocessed data (per-instance widths + global root), so
68// prover and verifier don't have to recompute/rehash it each run.
69pub struct CommonData<SC: SGC> {
70 /// Optional global preprocessed commitment shared by all instances.
71 ///
72 /// When `None`, no instance uses preprocessed columns.
73 pub preprocessed: Option<GlobalPreprocessed<SC>>,
74 /// The lookups used by each STARK instance.
75 /// There is one `Vec<Lookup<Val<SC>>>` per STARK instance.
76 /// They are stored in the same order as the STARK instance inputs provided to `new`.
77 pub lookups: Vec<Vec<Lookup<Val<SC>>>>,
78}
79
80/// Prover-exclusive data not shared with the verifier.
81///
82/// This contains the PCS prover data for preprocessed traces, which is only
83/// needed during proving.
84pub struct ProverOnlyData<SC: SGC> {
85 /// PCS prover data for preprocessed traces.
86 ///
87 /// Present only when at least one instance has preprocessed columns.
88 pub preprocessed_prover_data:
89 Option<<SC::Pcs as Pcs<Challenge<SC>, SC::Challenger>>::ProverData>,
90}
91
92/// Combined prover data containing both common and prover-only data.
93///
94/// This is a convenience struct that bundles [`CommonData`] (shared with verifier)
95/// and [`ProverOnlyData`] (prover-exclusive) together.
96pub struct ProverData<SC: SGC> {
97 /// Data shared between prover and verifier.
98 pub common: CommonData<SC>,
99 /// Prover-exclusive data.
100 pub prover_only: ProverOnlyData<SC>,
101}
102
103impl<SC: SGC> CommonData<SC> {
104 pub const fn new(
105 preprocessed: Option<GlobalPreprocessed<SC>>,
106 lookups: Vec<Vec<Lookup<Val<SC>>>>,
107 ) -> Self {
108 Self {
109 preprocessed,
110 lookups,
111 }
112 }
113
114 /// Create [`CommonData`] with no preprocessed columns or lookups.
115 ///
116 /// Use this when none of your [`Air`] implementations have preprocessed columns or lookups.
117 pub fn empty(num_instances: usize) -> Self {
118 let lookups = vec![Vec::new(); num_instances];
119 Self {
120 preprocessed: None,
121 lookups,
122 }
123 }
124}
125
126impl<SC: SGC> ProverOnlyData<SC> {
127 /// Create empty [`ProverOnlyData`] with no preprocessed prover data.
128 pub const fn empty() -> Self {
129 Self {
130 preprocessed_prover_data: None,
131 }
132 }
133}
134
135impl<SC: SGC> ProverData<SC> {
136 /// Create [`ProverData`] with no preprocessed columns or lookups.
137 ///
138 /// Use this when none of your [`Air`] implementations have preprocessed columns or lookups.
139 pub fn empty(num_instances: usize) -> Self {
140 Self {
141 common: CommonData::empty(num_instances),
142 prover_only: ProverOnlyData::empty(),
143 }
144 }
145}
146
147impl<SC> ProverData<SC>
148where
149 SC: SGC,
150 Challenge<SC>: BasedVectorSpace<Val<SC>>,
151{
152 /// Build [`ProverData`] directly from STARK instances.
153 ///
154 /// This automatically:
155 /// - Derives trace degrees from trace heights
156 /// - Computes extended degrees (base + ZK padding)
157 /// - Sets up preprocessed columns for [`Air`] implementations that define them, committing
158 /// to them in a single global [`Pcs`] commitment.
159 /// - Deduces symbolic lookups from the STARKs
160 ///
161 /// This is a convenience function mainly used for tests.
162 pub fn from_instances<A>(config: &SC, instances: &[StarkInstance<'_, SC, A>]) -> Self
163 where
164 SymbolicExpressionExt<Val<SC>, SC::Challenge>: Algebra<SC::Challenge>,
165 A: Air<SymbolicAirBuilder<Val<SC>, SC::Challenge>> + LookupAir<Val<SC>> + Clone,
166 {
167 let degrees: Vec<usize> = instances.iter().map(|i| i.trace.height()).collect();
168 let log_ext_degrees: Vec<usize> = degrees
169 .iter()
170 .map(|&d| log2_strict_usize(d) + config.is_zk())
171 .collect();
172 let mut airs: Vec<A> = instances.iter().map(|i| i.air.clone()).collect();
173 Self::from_airs_and_degrees(config, &mut airs, &log_ext_degrees)
174 }
175
176 /// Build [`ProverData`] from [`Air`] implementations and their extended trace degree bits.
177 ///
178 /// # Arguments
179 ///
180 /// * `trace_ext_degree_bits` - Log2 of extended trace degrees (including ZK padding)
181 ///
182 /// # Returns
183 ///
184 /// Prover data containing the global preprocessed commitment (if at least
185 /// one [`Air`] defines preprocessed columns) and the PCS prover data.
186 pub fn from_airs_and_degrees<A>(
187 config: &SC,
188 airs: &mut [A],
189 trace_ext_degree_bits: &[usize],
190 ) -> Self
191 where
192 SymbolicExpressionExt<Val<SC>, SC::Challenge>: Algebra<SC::Challenge>,
193 A: Air<SymbolicAirBuilder<Val<SC>, SC::Challenge>> + LookupAir<Val<SC>>,
194 {
195 assert_eq!(
196 airs.len(),
197 trace_ext_degree_bits.len(),
198 "airs and trace_ext_degree_bits must have the same length"
199 );
200
201 let pcs = config.pcs();
202 let is_zk = config.is_zk();
203
204 let mut instances_meta: Vec<Option<PreprocessedInstanceMeta>> =
205 Vec::with_capacity(airs.len());
206 let mut matrix_to_instance: Vec<usize> = Vec::new();
207 let mut domains_and_traces: Vec<(Domain<SC>, _)> = Vec::new();
208
209 for (i, (air, &ext_db)) in airs.iter().zip(trace_ext_degree_bits.iter()).enumerate() {
210 // Derive base trace degree bits from extended degree bits.
211 let base_db = ext_db - is_zk;
212 let maybe_prep = air.preprocessed_trace();
213
214 let Some(preprocessed) = maybe_prep else {
215 instances_meta.push(None);
216 continue;
217 };
218
219 let width = preprocessed.width();
220 if width == 0 {
221 instances_meta.push(None);
222 continue;
223 }
224
225 let degree = 1 << base_db;
226 let ext_degree = 1 << ext_db;
227 assert_eq!(
228 preprocessed.height(),
229 degree,
230 "preprocessed trace height must equal trace degree for instance {}",
231 i
232 );
233
234 let domain = pcs.natural_domain_for_degree(ext_degree);
235 let matrix_index = domains_and_traces.len();
236
237 domains_and_traces.push((domain, preprocessed));
238 matrix_to_instance.push(i);
239
240 instances_meta.push(Some(PreprocessedInstanceMeta {
241 matrix_index,
242 width,
243 degree_bits: ext_db,
244 }));
245 }
246
247 let (preprocessed, preprocessed_prover_data) = if domains_and_traces.is_empty() {
248 (None, None)
249 } else {
250 let (commitment, prover_data) = pcs.commit_preprocessing(domains_and_traces);
251 (
252 Some(GlobalPreprocessed {
253 commitment,
254 instances: instances_meta,
255 matrix_to_instance,
256 }),
257 Some(prover_data),
258 )
259 };
260
261 let lookups = airs.iter_mut().map(|air| air.get_lookups()).collect();
262
263 Self {
264 common: CommonData {
265 preprocessed,
266 lookups,
267 },
268 prover_only: ProverOnlyData {
269 preprocessed_prover_data,
270 },
271 }
272 }
273}
274
275pub fn get_perm_challenges<SC: SGC, LG: LookupGadget>(
276 challenger: &mut SC::Challenger,
277 all_lookups: &[Vec<Lookup<Val<SC>>>],
278 lookup_gadget: &LG,
279) -> Vec<Vec<SC::Challenge>> {
280 let num_challenges_per_lookup = lookup_gadget.num_challenges();
281 let mut global_perm_challenges = HashMap::new();
282
283 all_lookups
284 .iter()
285 .map(|contexts| {
286 // Pre-allocate for the instance's challenges.
287 let num_challenges = contexts.len() * num_challenges_per_lookup;
288 let mut instance_challenges = Vec::with_capacity(num_challenges);
289
290 for context in contexts {
291 match &context.kind {
292 Kind::Global(name) => {
293 // Get or create the global challenges.
294 let challenges: &mut Vec<SC::Challenge> =
295 global_perm_challenges.entry(name).or_insert_with(|| {
296 (0..num_challenges_per_lookup)
297 .map(|_| challenger.sample_algebra_element())
298 .collect()
299 });
300 instance_challenges.extend_from_slice(challenges);
301 }
302 Kind::Local => {
303 instance_challenges.extend(
304 (0..num_challenges_per_lookup)
305 .map(|_| challenger.sample_algebra_element::<SC::Challenge>()),
306 );
307 }
308 }
309 }
310 instance_challenges
311 })
312 .collect()
313}