Skip to main content

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}