1use alloc::vec;
2use alloc::vec::Vec;
3
4use p3_challenger::{CanObserve, FieldChallenger};
5use p3_commit::{Pcs, PolynomialSpace};
6use p3_field::{BasedVectorSpace, PackedFieldExtension, PackedValue, PrimeCharacteristicRing};
7use p3_lookup::folder::ProverConstraintFolderWithLookups;
8use p3_lookup::lookup_traits::{
9 AirLookupHandler, EmptyLookupGadget, Kind, Lookup, LookupData, LookupGadget,
10};
11use p3_matrix::Matrix;
12use p3_matrix::dense::RowMajorMatrix;
13use p3_maybe_rayon::prelude::*;
14use p3_uni_stark::{
15 OpenedValues, PackedChallenge, PackedVal, ProverConstraintFolder, SymbolicAirBuilder,
16 SymbolicExpression,
17};
18use p3_util::log2_strict_usize;
19use tracing::{debug_span, instrument};
20
21#[cfg(debug_assertions)]
22use crate::check_constraints::DebugConstraintBuilderWithLookups;
23use crate::common::{CommonData, get_perm_challenges};
24use crate::config::{Challenge, Domain, StarkGenericConfig as SGC, Val, observe_instance_binding};
25use crate::proof::{BatchCommitments, BatchOpenedValues, BatchProof, OpenedValuesWithLookups};
26use crate::symbolic::{get_log_num_quotient_chunks, get_symbolic_constraints, lookup_data_to_expr};
27
28#[derive(Debug)]
29pub struct StarkInstance<'a, SC: SGC, A> {
30 pub air: &'a A,
31 pub trace: RowMajorMatrix<Val<SC>>,
32 pub public_values: Vec<Val<SC>>,
33 pub lookups: Vec<Lookup<Val<SC>>>,
34}
35
36impl<'a, SC: SGC, A> StarkInstance<'a, SC, A> {
37 pub fn new_multiple(
38 airs: &'a [A],
39 traces: &[RowMajorMatrix<Val<SC>>],
40 public_values: &[Vec<Val<SC>>],
41 common_data: &CommonData<SC>,
42 ) -> Vec<Self> {
43 airs.iter()
44 .zip(traces.iter())
45 .zip(public_values.iter())
46 .zip(common_data.lookups.iter())
47 .map(|(((air, trace), public_values), lookups)| Self {
48 air,
49 trace: trace.clone(),
50 public_values: public_values.clone(),
51 lookups: lookups.clone(),
52 })
53 .collect()
54 }
55}
56
57#[instrument(skip_all)]
58pub fn prove_batch<
59 SC,
60 #[cfg(debug_assertions)] A: for<'a> AirLookupHandler<DebugConstraintBuilderWithLookups<'a, Val<SC>, SC::Challenge>>
61 + AirLookupHandler<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
62 + for<'a> AirLookupHandler<ProverConstraintFolderWithLookups<'a, SC>>
63 + Clone,
64 #[cfg(not(debug_assertions))] A: for<'a> AirLookupHandler<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
65 + for<'a> AirLookupHandler<ProverConstraintFolderWithLookups<'a, SC>>
66 + Clone,
67 LG,
68>(
69 config: &SC,
70 instances: &[StarkInstance<'_, SC, A>],
71 common: &CommonData<SC>,
72 lookup_gadget: &LG,
73) -> BatchProof<SC>
74where
75 SC: SGC,
76 SymbolicExpression<SC::Challenge>: From<SymbolicExpression<Val<SC>>>,
77 LG: LookupGadget + Sync,
78{
79 let pcs = config.pcs();
80 let mut challenger = config.initialise_challenger();
81
82 let degrees: Vec<usize> = instances.iter().map(|i| i.trace.height()).collect();
84 let log_degrees: Vec<usize> = degrees.iter().copied().map(log2_strict_usize).collect();
85 let log_ext_degrees: Vec<usize> = log_degrees.iter().map(|&d| d + config.is_zk()).collect();
86
87 let (all_lookups, mut lookup_data): (Vec<Vec<_>>, Vec<Vec<_>>) = instances
89 .iter()
90 .map(|inst| {
91 (
92 inst.lookups.clone(),
93 inst.lookups
95 .iter()
96 .filter_map(|lookup| match &lookup.kind {
97 Kind::Global(name) => Some(LookupData {
98 name: name.clone(),
99 aux_idx: lookup.columns[0],
100 expected_cumulated: SC::Challenge::ZERO,
101 }),
102 _ => None,
103 })
104 .collect::<Vec<_>>(),
105 )
106 })
107 .unzip();
108
109 let (trace_domains, ext_trace_domains): (Vec<Domain<SC>>, Vec<Domain<SC>>) = degrees
111 .iter()
112 .map(|°| {
113 (
114 pcs.natural_domain_for_degree(deg),
115 pcs.natural_domain_for_degree(deg * (config.is_zk() + 1)),
116 )
117 })
118 .unzip();
119
120 let airs: Vec<&A> = instances.iter().map(|i| i.air).collect();
122 let pub_vals: Vec<Vec<Val<SC>>> = instances.iter().map(|i| i.public_values.clone()).collect();
123
124 let mut preprocessed_widths = Vec::with_capacity(airs.len());
125 let (log_num_quotient_chunks, num_quotient_chunks): (Vec<usize>, Vec<usize>) = airs
126 .iter()
127 .zip(pub_vals.iter())
128 .enumerate()
129 .map(|(i, (air, pv))| {
130 let pre_w = common
131 .preprocessed
132 .as_ref()
133 .and_then(|g| g.instances[i].as_ref().map(|m| m.width))
134 .unwrap_or(0);
135 preprocessed_widths.push(pre_w);
136 let lq_chunks = get_log_num_quotient_chunks::<Val<SC>, SC::Challenge, A, LG>(
137 air,
138 pre_w,
139 pv.len(),
140 &all_lookups[i],
141 &lookup_data_to_expr(&lookup_data[i]),
142 config.is_zk(),
143 lookup_gadget,
144 );
145 let n_chunks = 1 << (lq_chunks + config.is_zk());
146 (lq_chunks, n_chunks)
147 })
148 .unzip();
149
150 let n_instances = airs.len();
153 challenger.observe_base_as_algebra_element::<Challenge<SC>>(Val::<SC>::from_usize(n_instances));
154
155 for i in 0..n_instances {
157 observe_instance_binding::<SC>(
158 &mut challenger,
159 log_ext_degrees[i],
160 log_degrees[i],
161 A::width(airs[i]),
162 num_quotient_chunks[i],
163 );
164 }
165
166 let main_commit_inputs = instances
168 .iter()
169 .zip(ext_trace_domains.iter().cloned())
170 .map(|(inst, dom)| (dom, inst.trace.clone()))
171 .collect::<Vec<_>>();
172 let (main_commit, main_data) = pcs.commit(main_commit_inputs);
173
174 challenger.observe(main_commit.clone());
176 for pv in &pub_vals {
177 challenger.observe_slice(pv);
178 }
179
180 for &pre_w in preprocessed_widths.iter() {
184 challenger.observe_base_as_algebra_element::<Challenge<SC>>(Val::<SC>::from_usize(pre_w));
185 }
186 if let Some(global) = &common.preprocessed {
187 challenger.observe(global.commitment.clone());
188 }
189
190 let challenges_per_instance =
192 get_perm_challenges::<SC, LG>(&mut challenger, &all_lookups, lookup_gadget);
193
194 let mut permutation_commit_inputs = Vec::with_capacity(n_instances);
196 instances
197 .iter()
198 .enumerate()
199 .zip(ext_trace_domains.iter().cloned())
200 .for_each(|((i, inst), ext_domain)| {
201 if !all_lookups[i].is_empty() {
202 let generated_perm = lookup_gadget.generate_permutation::<SC>(
203 &inst.trace,
204 &inst.air.preprocessed_trace(),
205 &inst.public_values,
206 &all_lookups[i],
207 &mut lookup_data[i],
208 &challenges_per_instance[i],
209 );
210 permutation_commit_inputs
211 .push((ext_domain, generated_perm.clone().flatten_to_base()));
212
213 #[cfg(debug_assertions)]
214 {
215 use crate::check_constraints::check_constraints;
216
217 let preprocessed_trace = inst.air.preprocessed_trace();
218
219 let lookup_constraints_inputs = (
220 all_lookups[i].as_slice(),
221 lookup_data[i].as_slice(),
222 lookup_gadget,
223 );
224 check_constraints(
225 inst.air,
226 &inst.trace,
227 &preprocessed_trace,
228 &generated_perm,
229 &challenges_per_instance[i],
230 &inst.public_values,
231 lookup_constraints_inputs,
232 );
233 }
234 }
235 });
236
237 let permutation_commit_and_data = if !permutation_commit_inputs.is_empty() {
239 let commitment = pcs.commit(permutation_commit_inputs);
240 challenger.observe(commitment.0.clone());
241 Some(commitment)
242 } else {
243 None
244 };
245
246 let alpha: Challenge<SC> = challenger.sample_algebra_element();
250
251 let mut quotient_chunk_domains: Vec<Domain<SC>> = Vec::new();
253 let mut quotient_chunk_mats: Vec<RowMajorMatrix<Val<SC>>> = Vec::new();
254 let mut quotient_chunk_ranges: Vec<(usize, usize)> = Vec::with_capacity(n_instances);
256
257 let mut perm_counter = 0;
258
259 for (i, trace_domain) in trace_domains.iter().enumerate() {
261 let log_chunks = log_num_quotient_chunks[i];
262 let n_chunks = num_quotient_chunks[i];
263 let quotient_domain =
266 ext_trace_domains[i].create_disjoint_domain(1 << (log_ext_degrees[i] + log_chunks));
267
268 let (base_constraints, extension_constraints) = get_symbolic_constraints(
270 airs[i],
271 preprocessed_widths[i],
272 pub_vals[i].len(),
273 &all_lookups[i],
274 &lookup_data_to_expr(&lookup_data[i]),
275 lookup_gadget,
276 );
277 let constraint_len = base_constraints.len() + extension_constraints.len();
278
279 let trace_on_quotient_domain =
281 pcs.get_evaluations_on_domain(&main_data, i, quotient_domain);
282
283 let permutation_on_quotient_domain = permutation_commit_and_data
284 .as_ref()
285 .filter(|_| !all_lookups[i].is_empty())
286 .map(|(_, perm_data)| {
287 let evals = pcs.get_evaluations_on_domain(perm_data, perm_counter, quotient_domain);
288 perm_counter += 1;
289 evals
290 });
291
292 let preprocessed_on_quotient_domain = common
294 .preprocessed
295 .as_ref()
296 .and_then(|g| g.instances[i].as_ref().map(|meta| (g, meta)))
297 .map(|(g, meta)| {
298 pcs.get_evaluations_on_domain_no_random(
299 &g.prover_data,
300 meta.matrix_index,
301 quotient_domain,
302 )
303 });
304
305 let q_values = quotient_values::<SC, A, _, LG>(
307 airs[i],
308 &pub_vals[i],
309 *trace_domain,
310 quotient_domain,
311 &trace_on_quotient_domain,
312 permutation_on_quotient_domain.as_ref(),
313 &all_lookups[i],
314 &lookup_data[i],
315 lookup_gadget,
316 &challenges_per_instance[i],
317 preprocessed_on_quotient_domain.as_ref(),
318 alpha,
319 constraint_len,
320 );
321
322 let q_flat = RowMajorMatrix::new_col(q_values).flatten_to_base();
324 let chunk_mats = quotient_domain.split_evals(n_chunks, q_flat);
325 let chunk_domains = quotient_domain.split_domains(n_chunks);
326
327 let evals = chunk_domains
328 .iter()
329 .zip(chunk_mats.iter())
330 .map(|(d, m)| (*d, m.clone()));
331 let ldes = pcs.get_quotient_ldes(evals, n_chunks);
332
333 let start = quotient_chunk_domains.len();
334 quotient_chunk_domains.extend(chunk_domains);
335 quotient_chunk_mats.extend(ldes);
336 let end = quotient_chunk_domains.len();
337 quotient_chunk_ranges.push((start, end));
338 }
339
340 let (quotient_commit, quotient_data) = pcs.commit_ldes(quotient_chunk_mats);
342 challenger.observe(quotient_commit.clone());
343
344 let (opt_r_commit, opt_r_data) = if SC::Pcs::ZK {
353 let (r_commit, r_data) = pcs
354 .get_opt_randomization_poly_commitment(ext_trace_domains.iter().copied())
355 .expect("ZK is enabled, so we should have randomization commitments");
356 (Some(r_commit), Some(r_data))
357 } else {
358 (None, None)
359 };
360
361 if let Some(r_commit) = &opt_r_commit {
362 challenger.observe(r_commit.clone());
363 }
364
365 let zeta: Challenge<SC> = challenger.sample_algebra_element();
367
368 let (opened_values, opening_proof) = {
370 let mut rounds = Vec::new();
371
372 let round0 = opt_r_data.as_ref().map(|r_data| {
373 let round0_points = trace_domains.iter().map(|_| vec![zeta]).collect::<Vec<_>>();
374 (r_data, round0_points)
375 });
376 rounds.extend(round0);
377 let round1_points = trace_domains
379 .iter()
380 .map(|dom| {
381 vec![
382 zeta,
383 dom.next_point(zeta)
384 .expect("domain should support next_point operation"),
385 ]
386 })
387 .collect::<Vec<_>>();
388 rounds.push((&main_data, round1_points));
389
390 let round2_points = quotient_chunk_ranges
392 .iter()
393 .cloned()
394 .flat_map(|(s, e)| (s..e).map(|_| vec![zeta]))
395 .collect::<Vec<_>>();
396 rounds.push(("ient_data, round2_points));
397
398 if let Some(global) = &common.preprocessed {
401 let pre_points = global
402 .matrix_to_instance
403 .iter()
404 .map(|&inst_idx| {
405 let zeta_next_i = trace_domains[inst_idx]
406 .next_point(zeta)
407 .expect("domain should support next_point operation");
408 vec![zeta, zeta_next_i]
409 })
410 .collect::<Vec<_>>();
411 rounds.push((&global.prover_data, pre_points));
412 }
413
414 let lookup_points: Vec<_> = trace_domains
415 .iter()
416 .zip(&all_lookups)
417 .filter(|&(_, lookups)| !lookups.is_empty())
418 .map(|(dom, _)| {
419 vec![
420 zeta,
421 dom.next_point(zeta)
422 .expect("domain should support next_point operation"),
423 ]
424 })
425 .collect();
426
427 if let Some((_, perm_data)) = &permutation_commit_and_data {
428 let lookup_round = (perm_data, lookup_points);
429 rounds.push(lookup_round);
430 }
431
432 pcs.open_with_preprocessing(rounds, &mut challenger, common.preprocessed.is_some())
433 };
434
435 let trace_idx = SC::Pcs::TRACE_IDX;
437 let quotient_idx = SC::Pcs::QUOTIENT_IDX;
438 let preprocessed_idx = SC::Pcs::PREPROCESSED_TRACE_IDX;
439 let permutation_idx = if common.preprocessed.is_some() {
440 preprocessed_idx + 1
441 } else {
442 preprocessed_idx
443 };
444
445 let trace_values_for_mats = &opened_values[trace_idx];
447 assert_eq!(trace_values_for_mats.len(), n_instances);
448
449 let mut per_instance: Vec<OpenedValuesWithLookups<Challenge<SC>>> =
451 Vec::with_capacity(n_instances);
452
453 let preprocessed_openings = common
455 .preprocessed
456 .as_ref()
457 .map(|_| &opened_values[SC::Pcs::PREPROCESSED_TRACE_IDX]);
458
459 let is_lookup = permutation_commit_and_data.is_some();
460 let permutation_values_for_mats = if is_lookup {
461 &opened_values[permutation_idx]
462 } else {
463 &vec![]
464 };
465 let mut permutation_values_for_mats = permutation_values_for_mats.iter();
466
467 let mut quotient_openings_iter = opened_values[quotient_idx].iter();
468 for (i, (s, e)) in quotient_chunk_ranges.iter().copied().enumerate() {
469 let random = if opt_r_data.is_some() {
470 Some(opened_values[0][i][0].clone())
471 } else {
472 None
473 };
474 let tv = &trace_values_for_mats[i];
476 let trace_local = tv[0].clone();
477 let trace_next = tv[1].clone();
478
479 let mut qcs = Vec::with_capacity(e - s);
481 for _ in s..e {
482 let mat_vals = quotient_openings_iter
483 .next()
484 .expect("chunk index in bounds");
485 qcs.push(mat_vals[0].clone());
486 }
487
488 let (preprocessed_local, preprocessed_next) = if let (Some(global), Some(pre_round)) =
490 (&common.preprocessed, preprocessed_openings)
491 {
492 global.instances[i].as_ref().map_or((None, None), |meta| {
493 let vals = &pre_round[meta.matrix_index];
494 assert_eq!(
495 vals.len(),
496 2,
497 "expected two opening points (zeta, zeta_next) for preprocessed trace"
498 );
499 (Some(vals[0].clone()), Some(vals[1].clone()))
500 })
501 } else {
502 (None, None)
503 };
504
505 let (permutation_local, permutation_next) = if !all_lookups[i].is_empty() {
507 let perm_v = permutation_values_for_mats
508 .next()
509 .expect("instance should have permutation openings");
510 (perm_v[0].clone(), perm_v[1].clone())
511 } else {
512 (vec![], vec![])
513 };
514
515 let base_opened = OpenedValues {
516 trace_local,
517 trace_next,
518 preprocessed_local,
519 preprocessed_next,
520 quotient_chunks: qcs,
521 random,
522 };
523
524 per_instance.push(OpenedValuesWithLookups {
525 base_opened_values: base_opened,
526 permutation_local,
527 permutation_next,
528 });
529 }
530
531 let permutation = permutation_commit_and_data
532 .as_ref()
533 .map(|(comm, _)| comm.clone());
534
535 BatchProof {
536 commitments: BatchCommitments {
537 main: main_commit,
538 quotient_chunks: quotient_commit,
539 random: opt_r_commit,
540 permutation,
541 },
542 opened_values: BatchOpenedValues {
543 instances: per_instance,
544 },
545 opening_proof,
546 global_lookup_data: lookup_data,
547 degree_bits: log_ext_degrees,
548 }
549}
550
551#[instrument(name = "compute quotient polynomial", skip_all)]
552#[allow(clippy::too_many_arguments)]
554pub fn quotient_values<SC, A, Mat, LG>(
555 air: &A,
556 public_values: &[Val<SC>],
557 trace_domain: Domain<SC>,
558 quotient_domain: Domain<SC>,
559 trace_on_quotient_domain: &Mat,
560 opt_permutation_on_quotient_domain: Option<&Mat>,
561 lookups: &[Lookup<Val<SC>>],
562 lookup_data: &[LookupData<SC::Challenge>],
563 lookup_gadget: &LG,
564 permutation_challenges: &[SC::Challenge],
565 preprocessed_on_quotient_domain: Option<&Mat>,
566 alpha: SC::Challenge,
567 constraint_count: usize,
568) -> Vec<SC::Challenge>
569where
570 SC: SGC,
571 A: for<'a> AirLookupHandler<ProverConstraintFolderWithLookups<'a, SC>>,
572 Mat: Matrix<Val<SC>> + Sync,
573 LG: LookupGadget + Sync,
574{
575 let quotient_size = quotient_domain.size();
576 let main_width = trace_on_quotient_domain.width();
577 let (perm_width, perm_height) = opt_permutation_on_quotient_domain
578 .as_ref()
579 .map_or((0, 0), |mat| (mat.width(), mat.height()));
580
581 let ext_degree = SC::Challenge::DIMENSION;
582
583 let mut sels = debug_span!("Compute Selectors")
584 .in_scope(|| trace_domain.selectors_on_coset(quotient_domain));
585
586 let qdb = log2_strict_usize(quotient_domain.size()) - log2_strict_usize(trace_domain.size());
587 let next_step = 1 << qdb;
588
589 let pack_width = PackedVal::<SC>::WIDTH;
591 if quotient_size < pack_width {
592 let pad_len = pack_width;
593 let pad = |v: &mut Vec<_>| v.resize(pad_len, Val::<SC>::default());
595 pad(&mut sels.is_first_row);
596 pad(&mut sels.is_last_row);
597 pad(&mut sels.is_transition);
598 pad(&mut sels.inv_vanishing);
599 }
600
601 let mut alpha_powers = alpha.powers().collect_n(constraint_count);
602 alpha_powers.reverse();
603 let decomposed_alpha_powers: Vec<_> = (0..SC::Challenge::DIMENSION)
606 .map(|i| {
607 alpha_powers
608 .iter()
609 .map(|x| x.as_basis_coefficients_slice()[i])
610 .collect()
611 })
612 .collect();
613 (0..quotient_size)
614 .into_par_iter()
615 .step_by(PackedVal::<SC>::WIDTH)
616 .flat_map_iter(|i_start| {
617 let i_range = i_start..i_start + PackedVal::<SC>::WIDTH;
618
619 let is_first_row = *PackedVal::<SC>::from_slice(&sels.is_first_row[i_range.clone()]);
620 let is_last_row = *PackedVal::<SC>::from_slice(&sels.is_last_row[i_range.clone()]);
621 let is_transition = *PackedVal::<SC>::from_slice(&sels.is_transition[i_range.clone()]);
622 let inv_vanishing = *PackedVal::<SC>::from_slice(&sels.inv_vanishing[i_range]);
623
624 let main = RowMajorMatrix::new(
626 trace_on_quotient_domain.vertically_packed_row_pair(i_start, next_step),
627 main_width,
628 );
629
630 let preprocessed = preprocessed_on_quotient_domain.map(|preprocessed| {
631 let preprocessed_width = preprocessed.width();
632 RowMajorMatrix::new(
633 preprocessed.vertically_packed_row_pair(i_start, next_step),
634 preprocessed_width,
635 )
636 });
637
638 let permutation = opt_permutation_on_quotient_domain.as_ref().map_or_else(
640 || RowMajorMatrix::new(vec![], 0),
641 |permutation_on_quotient_domain| {
642 let perms = (0..perm_width)
643 .step_by(ext_degree)
644 .map(|col| {
645 PackedChallenge::<SC>::from_basis_coefficients_fn(|i| {
646 PackedVal::<SC>::from_fn(|offset| {
647 permutation_on_quotient_domain
648 .get((i_start + offset) % perm_height, col + i)
649 .unwrap()
650 })
651 })
652 })
653 .chain((0..perm_width).step_by(ext_degree).map(|col| {
654 PackedChallenge::<SC>::from_basis_coefficients_fn(|i| {
655 PackedVal::<SC>::from_fn(|offset| {
656 permutation_on_quotient_domain
657 .get((i_start + next_step + offset) % perm_height, col + i)
658 .unwrap()
659 })
660 })
661 }));
662
663 RowMajorMatrix::new(perms.collect::<Vec<_>>(), perm_width / ext_degree)
664 },
665 );
666
667 let accumulator = PackedChallenge::<SC>::ZERO;
668 let inner_folder = ProverConstraintFolder {
669 main: main.as_view(),
670 preprocessed: preprocessed.as_ref().map(|m| m.as_view()),
671 public_values,
672 is_first_row,
673 is_last_row,
674 is_transition,
675 alpha_powers: &alpha_powers,
676 decomposed_alpha_powers: &decomposed_alpha_powers,
677 accumulator,
678 constraint_index: 0,
679 };
680 let packed_perm_challenges = permutation_challenges
681 .iter()
682 .map(|p_c| PackedChallenge::<SC>::from(*p_c))
683 .collect::<Vec<_>>();
684
685 let mut folder = ProverConstraintFolderWithLookups {
686 inner: inner_folder,
687 permutation: permutation.as_view(),
688 permutation_challenges: &packed_perm_challenges,
689 };
690 <A as AirLookupHandler<ProverConstraintFolderWithLookups<'_, SC>>>::eval(
691 air,
692 &mut folder,
693 lookups,
694 lookup_data
695 .iter()
696 .map(|ld| LookupData {
697 name: ld.name.clone(),
698 aux_idx: ld.aux_idx,
699 expected_cumulated: ld.expected_cumulated.into(),
700 })
701 .collect::<Vec<_>>()
702 .as_slice(),
703 lookup_gadget,
704 );
705
706 let quotient = folder.inner.accumulator * inv_vanishing;
708
709 (0..core::cmp::min(quotient_size, PackedVal::<SC>::WIDTH))
711 .map(move |idx_in_packing| quotient.extract(idx_in_packing))
712 })
713 .collect()
714}
715
716pub fn prove_batch_no_lookups<
717 SC,
718 #[cfg(debug_assertions)] A: for<'a> AirLookupHandler<DebugConstraintBuilderWithLookups<'a, Val<SC>, SC::Challenge>>
719 + AirLookupHandler<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
720 + for<'a> AirLookupHandler<ProverConstraintFolderWithLookups<'a, SC>>
721 + Clone,
722 #[cfg(not(debug_assertions))] A: for<'a> AirLookupHandler<SymbolicAirBuilder<Val<SC>, SC::Challenge>>
723 + for<'a> AirLookupHandler<ProverConstraintFolderWithLookups<'a, SC>>
724 + Clone,
725>(
726 config: &SC,
727 instances: &[StarkInstance<'_, SC, A>],
728 common_data: &CommonData<SC>,
729) -> BatchProof<SC>
730where
731 SC: SGC,
732 SymbolicExpression<SC::Challenge>: From<SymbolicExpression<Val<SC>>>,
733{
734 let dummy_lookup_gadget = EmptyLookupGadget;
735 assert!(common_data.lookups.iter().all(|l| l.is_empty()));
736 prove_batch(config, instances, common_data, &dummy_lookup_gadget)
737}