1use std::{iter, sync::Arc};
6
7use openvm_cpu_backend::CpuBackend;
8#[cfg(feature = "cuda")]
9use openvm_cuda_common::stream::GpuDeviceCtx;
10use openvm_poseidon2_air::POSEIDON2_WIDTH;
11use openvm_stark_backend::{
12 interaction::BusIndex,
13 keygen::types::{LinearConstraint, MultiStarkVerifyingKey},
14 proof::{Proof, TraceVData},
15 prover::{AirProvingContext, CommittedTraceData, ProverBackend},
16 AirRef, EngineDeviceCtx, FiatShamirTranscript, StarkEngine, StarkProtocolConfig,
17 TranscriptHistory, TranscriptLog,
18};
19use openvm_stark_sdk::config::baby_bear_poseidon2::{BabyBearPoseidon2Config, CHUNK, EF, F};
20use p3_field::BasedVectorSpace;
21use p3_maybe_rayon::prelude::*;
22
23use crate::{
24 batch_constraint::{
25 expr_eval::CachedTraceRecord, BatchConstraintModule, LOCAL_SYMBOLIC_EXPRESSION_AIR_IDX,
26 },
27 bus::{
28 AirPresenceBus, AirShapeBus, BatchConstraintModuleBus, CachedCommitBus, ColumnClaimsBus,
29 CommitmentsBus, ConstraintSumcheckRandomnessBus, ConstraintsFoldingInputBus, Eq3bShapeBus,
30 EqNegBaseRandBus, EqNegResultBus, EqNsNLogupMaxBus, ExpressionClaimNMaxBus,
31 FinalTranscriptStateBus, FractionFolderInputBus, GkrModuleBus, HyperdimBus,
32 InteractionsFoldingInputBus, LiftedHeightsBus, MerkleVerifyBus, NLiftBus,
33 Poseidon2CompressBus, Poseidon2PermuteBus, PreHashBus, PublicValuesBus, SelUniBus,
34 StackingIndicesBus, StackingModuleBus, TranscriptBus, WhirModuleBus, WhirMuBus,
35 WhirOpeningPointBus, WhirOpeningPointLookupBus, XiRandomnessBus,
36 },
37 gkr::GkrModule,
38 primitives::{
39 bus::{ExpBitsLenBus, PowerCheckerBus, RangeCheckerBus, RightShiftBus},
40 exp_bits_len::{ExpBitsLenAir, ExpBitsLenCpuTraceGenerator},
41 pow::{PowerCheckerAir, PowerCheckerCpuTraceGenerator},
42 },
43 proof_shape::ProofShapeModule,
44 stacking::StackingModule,
45 transcript::TranscriptModule,
46 utils::poseidon2_hash_slice_with_states,
47 whir::WhirModule,
48};
49
50mod dummy;
51pub(crate) mod frame;
52
53pub use frame::check_param_compatibility;
54
55const BATCH_CONSTRAINT_MOD_IDX: usize = 0;
56pub(crate) const POW_CHECKER_HEIGHT: usize = 32;
57
58pub enum CachedTraceCtx<PB: ProverBackend> {
59 PcsData(CommittedTraceData<PB>),
60 Records(CachedTraceRecord),
61}
62
63#[derive(Debug, Copy, Clone)]
64pub struct VerifierConfig {
65 pub continuations_enabled: bool,
66 pub final_state_bus_enabled: bool,
67 pub has_cached: bool,
68}
69
70impl Default for VerifierConfig {
71 fn default() -> Self {
72 Self {
73 continuations_enabled: false,
74 final_state_bus_enabled: false,
75 has_cached: true,
76 }
77 }
78}
79
80#[derive(Debug)]
81pub struct VerifierExternalData<'a> {
82 pub poseidon2_compress_inputs: &'a Vec<[F; POSEIDON2_WIDTH]>,
83 pub poseidon2_permute_inputs: &'a Vec<[F; POSEIDON2_WIDTH]>,
84 pub range_check_inputs: &'a Vec<usize>,
85 pub power_check_inputs: &'a Vec<usize>,
86 pub required_heights: Option<&'a [usize]>,
87 pub final_transcript_state: Option<&'a mut [F; POSEIDON2_WIDTH]>,
88}
89
90pub trait VerifierTraceGen<
93 PB: ProverBackend,
94 SC: StarkProtocolConfig<F = F>,
95 DC: Clone + Send + Sync,
96>
97{
98 fn new(
99 child_mvk: Arc<MultiStarkVerifyingKey<BabyBearPoseidon2Config>>,
100 config: VerifierConfig,
101 ) -> Self;
102
103 fn commit_child_vk<E: StarkEngine<SC = SC, PB = PB>>(
104 &self,
105 engine: &E,
106 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
107 ) -> CommittedTraceData<PB>
108 where
109 DC: From<EngineDeviceCtx<E>>;
110
111 fn cached_trace_record(
112 &self,
113 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
114 ) -> CachedTraceRecord;
115
116 #[allow(clippy::ptr_arg)]
119 fn generate_proving_ctxs<
120 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
121 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
122 >(
123 &self,
124 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
125 cached_trace_ctx: CachedTraceCtx<PB>,
126 proofs: &[Proof<BabyBearPoseidon2Config>],
127 external_data: &mut VerifierExternalData,
128 device_ctx: &DC,
129 initial_transcript: TS,
130 ) -> Option<Vec<AirProvingContext<PB>>>;
131
132 fn generate_proving_ctxs_base<
133 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
134 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
135 >(
136 &self,
137 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
138 cached_trace_ctx: CachedTraceCtx<PB>,
139 proofs: &[Proof<BabyBearPoseidon2Config>],
140 device_ctx: &DC,
141 initial_transcript: TS,
142 ) -> Vec<AirProvingContext<PB>> {
143 let poseidon2_compress_inputs = vec![];
144 let range_check_inputs = vec![];
145 let power_check_inputs = vec![];
146
147 let mut external_data = VerifierExternalData {
148 poseidon2_compress_inputs: &poseidon2_compress_inputs,
149 poseidon2_permute_inputs: &poseidon2_compress_inputs,
150 range_check_inputs: &range_check_inputs,
151 power_check_inputs: &power_check_inputs,
152 required_heights: None,
153 final_transcript_state: None,
154 };
155
156 self.generate_proving_ctxs::<TS>(
157 child_vk,
158 cached_trace_ctx,
159 proofs,
160 &mut external_data,
161 device_ctx,
162 initial_transcript,
163 )
164 .unwrap()
165 }
166}
167
168pub trait AggregationSubCircuit {
170 fn airs<SC: StarkProtocolConfig<F = F>>(&self) -> Vec<AirRef<SC>>;
171 fn bus_inventory(&self) -> &BusInventory;
172 fn next_bus_idx(&self) -> BusIndex;
173 fn max_num_proofs(&self) -> usize;
174}
175
176pub trait AirModule {
177 fn num_airs(&self) -> usize;
178 fn airs<SC: StarkProtocolConfig<F = F>>(&self) -> Vec<AirRef<SC>>;
179}
180
181pub trait GlobalTraceGenCtx {
184 type ChildVerifyingKey;
186 type MultiProof: ?Sized;
188 type PreflightRecords: ?Sized;
191}
192
193pub trait TraceGenModule<GC: GlobalTraceGenCtx, PB: ProverBackend>: Send + Sync {
199 type ModuleSpecificCtx<'a>;
200
201 fn generate_proving_ctxs(
202 &self,
203 child_vk: &GC::ChildVerifyingKey,
204 proofs: &GC::MultiProof,
205 preflights: &GC::PreflightRecords,
206 ctx: &Self::ModuleSpecificCtx<'_>,
207 required_heights: Option<&[usize]>,
208 ) -> Option<Vec<AirProvingContext<PB>>>;
209}
210
211pub struct GlobalCtxCpu;
212
213impl GlobalTraceGenCtx for GlobalCtxCpu {
214 type ChildVerifyingKey = MultiStarkVerifyingKey<BabyBearPoseidon2Config>;
215 type MultiProof = [Proof<BabyBearPoseidon2Config>];
216 type PreflightRecords = [Preflight];
217}
218
219#[derive(Clone, Copy, Debug, Default)]
220pub struct BusIndexManager {
221 bus_idx_max: BusIndex,
223}
224
225impl BusIndexManager {
226 pub fn new() -> Self {
227 Self { bus_idx_max: 0 }
228 }
229
230 pub fn new_bus_idx(&mut self) -> BusIndex {
231 let idx = self.bus_idx_max;
232 self.bus_idx_max = self.bus_idx_max.checked_add(1).unwrap();
233 idx
234 }
235}
236
237#[derive(Clone, Debug)]
238pub struct BusInventory {
239 pub transcript_bus: TranscriptBus,
241 pub poseidon2_permute_bus: Poseidon2PermuteBus,
242 pub poseidon2_compress_bus: Poseidon2CompressBus,
243 pub merkle_verify_bus: MerkleVerifyBus,
244 pub gkr_module_bus: GkrModuleBus,
245 pub bc_module_bus: BatchConstraintModuleBus,
246 pub stacking_module_bus: StackingModuleBus,
247 pub whir_module_bus: WhirModuleBus,
248 pub whir_mu_bus: WhirMuBus,
249
250 pub air_shape_bus: AirShapeBus,
252 pub air_presence_bus: AirPresenceBus,
253 pub hyperdim_bus: HyperdimBus,
254 pub lifted_heights_bus: LiftedHeightsBus,
255 pub stacking_indices_bus: StackingIndicesBus,
256 pub commitments_bus: CommitmentsBus,
257 pub public_values_bus: PublicValuesBus,
258 pub column_claims_bus: ColumnClaimsBus,
259 pub range_checker_bus: RangeCheckerBus,
260 pub power_checker_bus: PowerCheckerBus,
261 pub expression_claim_n_max_bus: ExpressionClaimNMaxBus,
262 pub constraints_folding_input_bus: ConstraintsFoldingInputBus,
263 pub interactions_folding_input_bus: InteractionsFoldingInputBus,
264 pub fraction_folder_input_bus: FractionFolderInputBus,
265 pub n_lift_bus: NLiftBus,
266 pub eq_n_logup_n_max_bus: EqNsNLogupMaxBus,
267 pub eq_3b_shape_bus: Eq3bShapeBus,
268
269 pub xi_randomness_bus: XiRandomnessBus,
271 pub constraint_randomness_bus: ConstraintSumcheckRandomnessBus,
272 pub whir_opening_point_bus: WhirOpeningPointBus,
273 pub whir_opening_point_lookup_bus: WhirOpeningPointLookupBus,
274
275 pub exp_bits_len_bus: ExpBitsLenBus,
277 pub right_shift_bus: RightShiftBus,
278 pub sel_uni_bus: SelUniBus,
279 pub eq_neg_result_bus: EqNegResultBus,
280 pub eq_neg_base_rand_bus: EqNegBaseRandBus,
281
282 pub cached_commit_bus: CachedCommitBus,
284 pub pre_hash_bus: PreHashBus,
285 pub final_state_bus: FinalTranscriptStateBus,
286}
287
288#[derive(Clone, Debug, Default)]
290pub struct Preflight {
291 pub transcript: TranscriptLog<F, [F; POSEIDON2_WIDTH]>,
294 pub proof_shape: ProofShapePreflight,
295 pub gkr: GkrPreflight,
296 pub batch_constraint: BatchConstraintPreflight,
297 pub stacking: StackingPreflight,
298 pub whir: WhirPreflight,
299 pub poseidon2_perm_inputs: Vec<[F; POSEIDON2_WIDTH]>,
300 pub poseidon2_compress_inputs: Vec<[F; POSEIDON2_WIDTH]>,
301 pub initial_row_states: Vec<Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>>>,
302 pub codeword_states: Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>>,
304}
305
306#[derive(Clone, Debug, Default)]
307pub struct ProofShapePreflight {
308 pub sorted_trace_vdata: Vec<(usize, TraceVData<BabyBearPoseidon2Config>)>,
309 pub starting_tidx: Vec<usize>,
310 pub pvs_tidx: Vec<usize>,
311 pub post_tidx: usize,
312 pub n_max: usize,
313 pub n_logup: usize,
314 pub l_skip: usize,
315}
316
317impl ProofShapePreflight {
318 pub fn n_global(&self) -> usize {
319 self.n_max.max(self.n_logup)
320 }
321}
322
323#[derive(Clone, Debug, Default)]
324pub struct GkrPreflight {
325 pub post_tidx: usize,
326 pub xi: Vec<(usize, EF)>,
327}
328
329#[derive(Clone, Debug, Default)]
330pub struct BatchConstraintPreflight {
331 pub lambda_tidx: usize,
332 pub tidx_before_univariate: usize,
333 pub tidx_before_multilinear: usize,
334 pub tidx_before_column_openings: usize,
335 pub post_tidx: usize,
336 pub xi: Vec<EF>,
337 pub sumcheck_rnd: Vec<EF>,
338 pub eq_ns: Vec<EF>,
339 pub eq_sharp_ns: Vec<EF>,
340 pub eq_ns_frontloaded: Vec<EF>,
341 pub eq_sharp_ns_frontloaded: Vec<EF>,
342}
343
344#[derive(Clone, Debug, Default)]
345pub struct StackingPreflight {
346 pub intermediate_tidx: [usize; 3],
347 pub post_tidx: usize,
348 pub univariate_poly_rand_eval: EF,
349 pub stacking_batching_challenge: EF,
350 pub mu_pow_witness: F,
352 pub mu_pow_sample: F,
354 pub lambda: EF,
355 pub sumcheck_rnd: Vec<EF>,
356}
357
358#[derive(Clone, Debug, Default)]
359pub struct WhirPreflight {
360 pub whir_round_tidx_per_round: Vec<usize>,
361 pub query_tidx_per_round: Vec<usize>,
362 pub alphas: Vec<EF>,
363 pub z0s: Vec<EF>,
364 pub gammas: Vec<EF>,
365 pub folding_pow_samples: Vec<F>,
366 pub query_pow_samples: Vec<F>,
367 pub queries: Vec<F>,
368}
369
370impl BusInventory {
371 pub(crate) fn new(b: &mut BusIndexManager) -> Self {
372 Self {
373 transcript_bus: TranscriptBus::new(b.new_bus_idx()),
374 poseidon2_permute_bus: Poseidon2PermuteBus::new(b.new_bus_idx()),
375 poseidon2_compress_bus: Poseidon2CompressBus::new(b.new_bus_idx()),
376 merkle_verify_bus: MerkleVerifyBus::new(b.new_bus_idx()),
377
378 gkr_module_bus: GkrModuleBus::new(b.new_bus_idx()),
380 bc_module_bus: BatchConstraintModuleBus::new(b.new_bus_idx()),
381 stacking_module_bus: StackingModuleBus::new(b.new_bus_idx()),
382 whir_module_bus: WhirModuleBus::new(b.new_bus_idx()),
383 whir_mu_bus: WhirMuBus::new(b.new_bus_idx()),
384
385 air_shape_bus: AirShapeBus::new(b.new_bus_idx()),
387 air_presence_bus: AirPresenceBus::new(b.new_bus_idx()),
388 hyperdim_bus: HyperdimBus::new(b.new_bus_idx()),
389 lifted_heights_bus: LiftedHeightsBus::new(b.new_bus_idx()),
390 stacking_indices_bus: StackingIndicesBus::new(b.new_bus_idx()),
391 commitments_bus: CommitmentsBus::new(b.new_bus_idx()),
392 public_values_bus: PublicValuesBus::new(b.new_bus_idx()),
393 sel_uni_bus: SelUniBus::new(b.new_bus_idx()),
394 range_checker_bus: RangeCheckerBus::new(b.new_bus_idx()),
395 power_checker_bus: PowerCheckerBus::new(b.new_bus_idx()),
396 expression_claim_n_max_bus: ExpressionClaimNMaxBus::new(b.new_bus_idx()),
397 constraints_folding_input_bus: ConstraintsFoldingInputBus::new(b.new_bus_idx()),
398 interactions_folding_input_bus: InteractionsFoldingInputBus::new(b.new_bus_idx()),
399 fraction_folder_input_bus: FractionFolderInputBus::new(b.new_bus_idx()),
400 n_lift_bus: NLiftBus::new(b.new_bus_idx()),
401 eq_n_logup_n_max_bus: EqNsNLogupMaxBus::new(b.new_bus_idx()),
402 eq_3b_shape_bus: Eq3bShapeBus::new(b.new_bus_idx()),
403
404 xi_randomness_bus: XiRandomnessBus::new(b.new_bus_idx()),
406 constraint_randomness_bus: ConstraintSumcheckRandomnessBus::new(b.new_bus_idx()),
407 whir_opening_point_bus: WhirOpeningPointBus::new(b.new_bus_idx()),
408 whir_opening_point_lookup_bus: WhirOpeningPointLookupBus::new(b.new_bus_idx()),
409
410 column_claims_bus: ColumnClaimsBus::new(b.new_bus_idx()),
412
413 exp_bits_len_bus: ExpBitsLenBus::new(b.new_bus_idx()),
414 right_shift_bus: RightShiftBus::new(b.new_bus_idx()),
415 eq_neg_base_rand_bus: EqNegBaseRandBus::new(b.new_bus_idx()),
416 eq_neg_result_bus: EqNegResultBus::new(b.new_bus_idx()),
417
418 cached_commit_bus: CachedCommitBus::new(b.new_bus_idx()),
420 pre_hash_bus: PreHashBus::new(b.new_bus_idx()),
421 final_state_bus: FinalTranscriptStateBus::new(b.new_bus_idx()),
422 }
423 }
424}
425
426#[repr(C)]
428#[derive(Clone, Copy, Debug)]
429pub struct PoseidonStatePair {
430 pub pre_state: [F; POSEIDON2_WIDTH],
431 pub post_state: [F; POSEIDON2_WIDTH],
432}
433
434struct MerklePrecomputation {
435 poseidon2_perm_inputs: Vec<[F; POSEIDON2_WIDTH]>,
436 poseidon2_compress_inputs: Vec<[F; POSEIDON2_WIDTH]>,
437 initial_row_states: Vec<Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>>>,
438 codeword_states: Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>>,
439}
440
441#[derive(Clone, Copy, strum_macros::Display)]
442enum TraceModuleRef<'a> {
443 Transcript(&'a TranscriptModule),
444 ProofShape(&'a ProofShapeModule),
445 Gkr(&'a GkrModule),
446 BatchConstraint(&'a BatchConstraintModule),
447 Stacking(&'a StackingModule),
448 Whir(&'a WhirModule),
449}
450
451impl<'a> TraceModuleRef<'a> {
452 #[tracing::instrument(
453 name = "wrapper.run_preflight",
454 level = "trace",
455 skip_all,
456 fields(air_module = %self)
457 )]
458 fn run_preflight<TS>(
459 self,
460 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
461 proof: &Proof<BabyBearPoseidon2Config>,
462 preflight: &mut Preflight,
463 sponge: &mut TS,
464 ) where
465 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
466 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
467 {
468 match self {
469 TraceModuleRef::ProofShape(module) => {
470 module.run_preflight(child_vk, proof, preflight, sponge)
471 }
472 TraceModuleRef::Gkr(module) => module.run_preflight(proof, preflight, sponge),
473 TraceModuleRef::BatchConstraint(module) => {
474 module.run_preflight(child_vk, proof, preflight, sponge)
475 }
476 TraceModuleRef::Stacking(module) => module.run_preflight(proof, preflight, sponge),
477 TraceModuleRef::Whir(module) => module.run_preflight(proof, preflight, sponge),
478 _ => panic!("TraceModuleRef::run_preflight called with invalid module"),
479 }
480 }
481
482 #[allow(clippy::too_many_arguments)]
483 #[tracing::instrument(
484 name = "wrapper.generate_proving_ctxs",
485 level = "trace",
486 skip_all,
487 fields(air_module = %self)
488 )]
489 fn generate_cpu_ctxs<SC: StarkProtocolConfig<F = F>>(
490 self,
491 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
492 proofs: &[Proof<BabyBearPoseidon2Config>],
493 preflights: &[Preflight],
494 cached_trace_record: Option<&CachedTraceRecord>,
495 pow_checker_gen: &Arc<PowerCheckerCpuTraceGenerator<2, POW_CHECKER_HEIGHT>>,
496 exp_bits_len_gen: &ExpBitsLenCpuTraceGenerator,
497 external_data: &VerifierExternalData,
498 required_heights: Option<&[usize]>,
499 ) -> Option<Vec<AirProvingContext<CpuBackend<SC>>>> {
500 match self {
501 TraceModuleRef::Transcript(module) => module.generate_proving_ctxs(
502 child_vk,
503 proofs,
504 preflights,
505 &(
506 external_data.poseidon2_permute_inputs,
507 external_data.poseidon2_compress_inputs,
508 ),
509 required_heights,
510 ),
511 TraceModuleRef::ProofShape(module) => module.generate_proving_ctxs(
512 child_vk,
513 proofs,
514 preflights,
515 &(
516 pow_checker_gen.clone(),
517 external_data.range_check_inputs.as_slice(),
518 ),
519 required_heights,
520 ),
521 TraceModuleRef::Gkr(module) => module.generate_proving_ctxs(
522 child_vk,
523 proofs,
524 preflights,
525 exp_bits_len_gen,
526 required_heights,
527 ),
528 TraceModuleRef::BatchConstraint(module) => module.generate_proving_ctxs(
529 child_vk,
530 proofs,
531 preflights,
532 &(cached_trace_record, pow_checker_gen.clone()),
533 required_heights,
534 ),
535 TraceModuleRef::Stacking(module) => {
536 module.generate_proving_ctxs(child_vk, proofs, preflights, &(), required_heights)
537 }
538 TraceModuleRef::Whir(module) => module.generate_proving_ctxs(
539 child_vk,
540 proofs,
541 preflights,
542 exp_bits_len_gen,
543 required_heights,
544 ),
545 }
546 }
547}
548
549pub struct VerifierSubCircuit<const MAX_NUM_PROOFS: usize> {
556 bus_inventory: BusInventory,
557 bus_idx_manager: BusIndexManager,
558
559 transcript: TranscriptModule,
560 proof_shape: ProofShapeModule,
561 gkr: GkrModule,
562 batch_constraint: BatchConstraintModule,
563 stacking: StackingModule,
564 whir: WhirModule,
565}
566
567impl<const MAX_NUM_PROOFS: usize> VerifierSubCircuit<MAX_NUM_PROOFS> {
568 pub fn new(child_mvk: Arc<MultiStarkVerifyingKey<BabyBearPoseidon2Config>>) -> Self {
569 Self::new_with_options(child_mvk, VerifierConfig::default())
570 }
571
572 pub fn new_with_options(
573 child_mvk: Arc<MultiStarkVerifyingKey<BabyBearPoseidon2Config>>,
574 config: VerifierConfig,
575 ) -> Self {
576 let proof_shape_constraint = LinearConstraint {
585 coefficients: child_mvk
586 .inner
587 .per_air
588 .iter()
589 .map(|avk| avk.num_interactions() as u32)
590 .collect(),
591 threshold: child_mvk.inner.params.logup.max_interaction_count,
592 };
593 for (i, constraint) in child_mvk.inner.trace_height_constraints.iter().enumerate() {
594 assert!(
595 constraint.is_implied_by(&proof_shape_constraint),
596 "child_vk trace_height_constraint[{i}] is not implied by ProofShapeAir's check. \
597 The recursion circuit cannot enforce this constraint. \
598 Constraint: coefficients={:?}, threshold={}",
599 constraint.coefficients,
600 constraint.threshold,
601 );
602 }
603
604 let mut bus_idx_manager = BusIndexManager::new();
605 let bus_inventory = BusInventory::new(&mut bus_idx_manager);
606
607 let transcript = TranscriptModule::new(
608 bus_inventory.clone(),
609 child_mvk.inner.params.clone(),
610 config.final_state_bus_enabled,
611 );
612 let child_mvk_frame = child_mvk.as_ref().into();
613 let proof_shape = ProofShapeModule::new(
614 &child_mvk_frame,
615 &mut bus_idx_manager,
616 bus_inventory.clone(),
617 config.continuations_enabled,
618 );
619 let gkr = GkrModule::new(&child_mvk, &mut bus_idx_manager, bus_inventory.clone());
620 let batch_constraint = BatchConstraintModule::new(
621 &child_mvk,
622 &mut bus_idx_manager,
623 bus_inventory.clone(),
624 MAX_NUM_PROOFS,
625 config.has_cached,
626 );
627 let stacking = StackingModule::new(&child_mvk, &mut bus_idx_manager, bus_inventory.clone());
628 let whir = WhirModule::new(&child_mvk, &mut bus_idx_manager, bus_inventory.clone());
629
630 VerifierSubCircuit {
631 bus_inventory,
632 bus_idx_manager,
633 transcript,
634 proof_shape,
635 gkr,
636 batch_constraint,
637 stacking,
638 whir,
639 }
640 }
641
642 #[tracing::instrument(name = "execute_preflight", skip_all)]
643 fn run_preflight_without_merkle<TS>(
644 &self,
645 mut sponge: TS,
646 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
647 proof: &Proof<BabyBearPoseidon2Config>,
648 ) -> Preflight
649 where
650 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
651 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
652 {
653 let mut preflight = Preflight::default();
654
655 let preflight_modules = [
657 TraceModuleRef::ProofShape(&self.proof_shape),
658 TraceModuleRef::Gkr(&self.gkr),
659 TraceModuleRef::BatchConstraint(&self.batch_constraint),
660 TraceModuleRef::Stacking(&self.stacking),
661 TraceModuleRef::Whir(&self.whir),
662 ];
663 for module in &preflight_modules {
664 module.run_preflight(child_vk, proof, &mut preflight, &mut sponge);
665 }
666 preflight.transcript = sponge.into_log();
667
668 preflight
669 }
670
671 #[tracing::instrument(name = "execute_preflight", skip_all)]
673 pub fn run_preflight<TS>(
674 &self,
675 sponge: TS,
676 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
677 proof: &Proof<BabyBearPoseidon2Config>,
678 ) -> Preflight
679 where
680 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
681 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
682 {
683 let mut preflight = self.run_preflight_without_merkle(sponge, child_vk, proof);
684 Self::apply_merkle_precomputation_cpu(proof, &mut preflight);
685
686 preflight
687 }
688
689 fn apply_merkle_precomputation_cpu(
690 proof: &Proof<BabyBearPoseidon2Config>,
691 preflight: &mut Preflight,
692 ) {
693 let merkle_precomputation = Self::compute_merkle_precomputation(proof);
694 preflight.poseidon2_perm_inputs = merkle_precomputation.poseidon2_perm_inputs;
695 preflight.poseidon2_compress_inputs = merkle_precomputation.poseidon2_compress_inputs;
696 preflight.initial_row_states = merkle_precomputation.initial_row_states;
697 preflight.codeword_states = merkle_precomputation.codeword_states;
698 }
699
700 #[cfg(feature = "cuda")]
701 #[tracing::instrument(name = "apply_merkle_precomputation", skip_all)]
702 fn apply_merkle_precomputation(
703 proof: &Proof<BabyBearPoseidon2Config>,
704 preflight: &mut Preflight,
705 device_ctx: &GpuDeviceCtx,
706 ) {
707 let merkle_precomputation = Self::compute_merkle_precomputation_cuda(proof, device_ctx);
708 preflight.poseidon2_perm_inputs = merkle_precomputation.poseidon2_perm_inputs;
709 preflight.poseidon2_compress_inputs = merkle_precomputation.poseidon2_compress_inputs;
710 preflight.initial_row_states = merkle_precomputation.initial_row_states;
711 preflight.codeword_states = merkle_precomputation.codeword_states;
712 }
713
714 #[cfg_attr(feature = "cuda", allow(dead_code))]
715 #[tracing::instrument(name = "compute_merkle_precomputation", level = "info", skip_all)]
716 fn compute_merkle_precomputation(
717 proof: &Proof<BabyBearPoseidon2Config>,
718 ) -> MerklePrecomputation {
719 let initial_chunks: usize = proof
720 .whir_proof
721 .initial_round_opened_rows
722 .iter()
723 .flat_map(|c| c.iter().flat_map(|q| q.iter()))
724 .map(|row| row.len().div_ceil(CHUNK))
725 .sum();
726 let codeword_chunks: usize = proof
727 .whir_proof
728 .codeword_opened_values
729 .iter()
730 .map(|r| r.iter().map(|q| q.len()).sum::<usize>())
731 .sum();
732
733 let mut poseidon2_perm_inputs = Vec::with_capacity(initial_chunks);
736 let mut poseidon2_compress_inputs = Vec::with_capacity(codeword_chunks);
737
738 let initial_row_states: Vec<Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>>> = proof
739 .whir_proof
740 .initial_round_opened_rows
741 .iter()
742 .map(|opened_rows_per_commit| {
743 opened_rows_per_commit
744 .iter()
745 .map(|opened_rows_per_query| {
746 opened_rows_per_query
747 .iter()
748 .map(|opened_row| {
749 let (_leaf_hash, pre_states, post_states) =
750 poseidon2_hash_slice_with_states(opened_row);
751 poseidon2_perm_inputs.extend(pre_states);
752 post_states
753 })
754 .collect()
755 })
756 .collect()
757 })
758 .collect();
759
760 let codeword_states = proof
761 .whir_proof
762 .codeword_opened_values
763 .iter()
764 .map(|round_values| {
765 round_values
766 .iter()
767 .map(|opened_values_per_query| {
768 opened_values_per_query
769 .iter()
770 .map(|opened_value| {
771 let (_leaf_hash, pre_states, post_states) =
772 poseidon2_hash_slice_with_states(
773 opened_value.as_basis_coefficients_slice(),
774 );
775 poseidon2_compress_inputs.extend(pre_states);
779 debug_assert_eq!(post_states.len(), 1);
780 post_states.into_iter().next().unwrap()
781 })
782 .collect()
783 })
784 .collect()
785 })
786 .collect();
787
788 MerklePrecomputation {
789 poseidon2_perm_inputs,
790 poseidon2_compress_inputs,
791 initial_row_states,
792 codeword_states,
793 }
794 }
795
796 #[cfg(feature = "cuda")]
797 #[tracing::instrument(name = "compute_merkle_precomputation_cuda", level = "info", skip_all)]
798 fn compute_merkle_precomputation_cuda(
799 proof: &Proof<BabyBearPoseidon2Config>,
800 device_ctx: &GpuDeviceCtx,
801 ) -> MerklePrecomputation {
802 use openvm_cuda_common::{
803 copy::{MemCopyD2H, MemCopyH2D},
804 d_buffer::DeviceBuffer,
805 };
806
807 use crate::cuda::abi::{merkle_precomputation_hash_vectors, VectorDescriptor};
808
809 let num_chunks = |len: usize| len.div_ceil(CHUNK);
810
811 let mut num_vectors = 0usize;
812 let mut total_data_len = 0usize;
813 let mut total_chunks = 0usize;
814
815 for row in proof
816 .whir_proof
817 .initial_round_opened_rows
818 .iter()
819 .flat_map(|per_commit| per_commit.iter().flat_map(|per_query| per_query.iter()))
820 {
821 num_vectors += 1;
822 total_data_len += row.len();
823 total_chunks += num_chunks(row.len());
824 }
825 let num_perm_chunks = total_chunks;
826
827 for opened_value in proof
828 .whir_proof
829 .codeword_opened_values
830 .iter()
831 .flat_map(|per_round| per_round.iter().flat_map(|per_query| per_query.iter()))
832 {
833 let len = <EF as BasedVectorSpace<F>>::as_basis_coefficients_slice(opened_value).len();
834 num_vectors += 1;
835 total_data_len += len;
836 total_chunks += num_chunks(len);
837 }
838
839 let mut flat_data = Vec::with_capacity(total_data_len);
840 let mut descriptors = Vec::with_capacity(num_vectors);
841 let mut output_offset_chunks = 0usize;
842
843 let mut push_vector = |data: &[F]| {
844 let len = data.len();
845 let chunks = num_chunks(len);
846 descriptors.push(VectorDescriptor {
847 data_offset: flat_data.len(),
848 len,
849 output_offset: output_offset_chunks,
850 });
851 output_offset_chunks += chunks;
852 flat_data.extend_from_slice(data);
853 };
854
855 for row in proof
856 .whir_proof
857 .initial_round_opened_rows
858 .iter()
859 .flat_map(|per_commit| per_commit.iter().flat_map(|per_query| per_query.iter()))
860 {
861 push_vector(row);
862 }
863 for opened_value in proof
864 .whir_proof
865 .codeword_opened_values
866 .iter()
867 .flat_map(|per_round| per_round.iter().flat_map(|per_query| per_query.iter()))
868 {
869 push_vector(opened_value.as_basis_coefficients_slice());
870 }
871
872 debug_assert_eq!(descriptors.len(), num_vectors);
873 debug_assert_eq!(flat_data.len(), total_data_len);
874 debug_assert_eq!(output_offset_chunks, total_chunks);
875
876 let d_data = flat_data
878 .to_device_on(device_ctx)
879 .expect("failed to upload data");
880 let d_descriptors = descriptors
881 .to_device_on(device_ctx)
882 .expect("failed to upload descriptors");
883 let d_pre_states =
884 DeviceBuffer::<F>::with_capacity_on(total_chunks * POSEIDON2_WIDTH, device_ctx);
885 let d_post_states =
886 DeviceBuffer::<F>::with_capacity_on(total_chunks * POSEIDON2_WIDTH, device_ctx);
887
888 unsafe {
889 merkle_precomputation_hash_vectors(
890 &d_data,
891 &d_descriptors,
892 num_vectors,
893 &d_pre_states,
894 &d_post_states,
895 device_ctx.stream.as_raw(),
896 )
897 .expect("hash_vectors kernel failed");
898 }
899
900 let pre_states_flat = d_pre_states
902 .to_host_on(device_ctx)
903 .expect("failed to download pre_states");
904 let post_states_flat = d_post_states
905 .to_host_on(device_ctx)
906 .expect("failed to download post_states");
907 debug_assert_eq!(pre_states_flat.len(), total_chunks * POSEIDON2_WIDTH);
908 debug_assert_eq!(post_states_flat.len(), total_chunks * POSEIDON2_WIDTH);
909
910 let (perm_flat, compress_flat) =
912 pre_states_flat.split_at(num_perm_chunks * POSEIDON2_WIDTH);
913 let poseidon2_perm_inputs: Vec<[F; POSEIDON2_WIDTH]> = perm_flat
914 .chunks_exact(POSEIDON2_WIDTH)
915 .map(|chunk| chunk.try_into().unwrap())
916 .collect();
917 let poseidon2_compress_inputs: Vec<[F; POSEIDON2_WIDTH]> = compress_flat
918 .chunks_exact(POSEIDON2_WIDTH)
919 .map(|chunk| chunk.try_into().unwrap())
920 .collect();
921
922 let mut post_iter = post_states_flat.chunks_exact(POSEIDON2_WIDTH);
923
924 let initial_row_states: Vec<Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>>> = proof
925 .whir_proof
926 .initial_round_opened_rows
927 .iter()
928 .map(|per_commit| {
929 per_commit
930 .iter()
931 .map(|per_query| {
932 per_query
933 .iter()
934 .map(|row| {
935 (0..num_chunks(row.len()))
936 .map(|_| post_iter.next().unwrap().try_into().unwrap())
937 .collect()
938 })
939 .collect()
940 })
941 .collect()
942 })
943 .collect();
944
945 let codeword_states: Vec<Vec<Vec<[F; POSEIDON2_WIDTH]>>> = proof
946 .whir_proof
947 .codeword_opened_values
948 .iter()
949 .map(|per_round| {
950 per_round
951 .iter()
952 .map(|per_query| {
953 per_query
954 .iter()
955 .map(|opened_value| {
956 debug_assert_eq!(
957 num_chunks(
958 <EF as BasedVectorSpace<F>>::as_basis_coefficients_slice(
959 opened_value
960 )
961 .len()
962 ),
963 1
964 );
965 post_iter.next().unwrap().try_into().unwrap()
966 })
967 .collect()
968 })
969 .collect()
970 })
971 .collect();
972
973 debug_assert_eq!(post_iter.len(), 0);
974
975 MerklePrecomputation {
976 poseidon2_perm_inputs,
977 poseidon2_compress_inputs,
978 initial_row_states,
979 codeword_states,
980 }
981 }
982
983 #[allow(clippy::type_complexity)]
986 fn split_required_heights<'a>(
987 &self,
988 required_heights: Option<&'a [usize]>,
989 ) -> (Vec<Option<&'a [usize]>>, Option<usize>, Option<usize>) {
990 let bc_n = self.batch_constraint.num_airs();
991 let t_n = self.transcript.num_airs();
992 let ps_n = self.proof_shape.num_airs();
993 let gkr_n = self.gkr.num_airs();
994 let st_n = self.stacking.num_airs();
995 let w_n = self.whir.num_airs();
996 let module_air_counts = [bc_n, t_n, ps_n, gkr_n, st_n, w_n];
997
998 let Some(heights) = required_heights else {
999 return (vec![None; module_air_counts.len()], None, None);
1000 };
1001
1002 let total_module_airs: usize = module_air_counts.iter().sum();
1003 let total = total_module_airs + 2; assert_eq!(heights.len(), total);
1005
1006 let mut offset = 0usize;
1007 let mut per_module = Vec::with_capacity(module_air_counts.len());
1008 for n in module_air_counts {
1009 per_module.push(Some(&heights[offset..offset + n]));
1010 offset += n;
1011 }
1012 debug_assert_eq!(heights.len() - offset, 2);
1013
1014 (per_module, Some(heights[offset]), Some(heights[offset + 1]))
1015 }
1016}
1017
1018impl<const MAX_NUM_PROOFS: usize> AggregationSubCircuit for VerifierSubCircuit<MAX_NUM_PROOFS> {
1019 fn airs<SC: StarkProtocolConfig<F = F>>(&self) -> Vec<AirRef<SC>> {
1020 let exp_bits_len_air = ExpBitsLenAir::new(
1021 self.bus_inventory.exp_bits_len_bus,
1022 self.bus_inventory.right_shift_bus,
1023 );
1024 let power_checker_air = PowerCheckerAir::<2, POW_CHECKER_HEIGHT> {
1025 pow_bus: self.bus_inventory.power_checker_bus,
1026 range_bus: self.bus_inventory.range_checker_bus,
1027 };
1028
1029 iter::empty()
1031 .chain(self.batch_constraint.airs())
1032 .chain(self.transcript.airs())
1033 .chain(self.proof_shape.airs())
1034 .chain(self.gkr.airs())
1035 .chain(self.stacking.airs())
1036 .chain(self.whir.airs())
1037 .chain([
1038 Arc::new(power_checker_air) as AirRef<_>,
1039 Arc::new(exp_bits_len_air) as AirRef<_>,
1040 ])
1041 .collect()
1042 }
1043
1044 fn bus_inventory(&self) -> &BusInventory {
1045 &self.bus_inventory
1046 }
1047
1048 fn next_bus_idx(&self) -> BusIndex {
1049 self.bus_idx_manager.bus_idx_max
1050 }
1051
1052 fn max_num_proofs(&self) -> usize {
1053 MAX_NUM_PROOFS
1054 }
1055}
1056
1057impl<SC: StarkProtocolConfig<F = F>, const MAX_NUM_PROOFS: usize>
1058 VerifierTraceGen<CpuBackend<SC>, SC, ()> for VerifierSubCircuit<MAX_NUM_PROOFS>
1059{
1060 fn new(
1061 child_mvk: Arc<MultiStarkVerifyingKey<BabyBearPoseidon2Config>>,
1062 config: VerifierConfig,
1063 ) -> Self {
1064 Self::new_with_options(child_mvk, config)
1065 }
1066
1067 fn commit_child_vk<E: StarkEngine<SC = SC, PB = CpuBackend<SC>>>(
1068 &self,
1069 engine: &E,
1070 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
1071 ) -> CommittedTraceData<CpuBackend<SC>>
1072 where
1073 (): From<EngineDeviceCtx<E>>,
1074 {
1075 crate::batch_constraint::commit_child_vk(engine, child_vk, self.batch_constraint.has_cached)
1076 }
1077
1078 fn cached_trace_record(
1079 &self,
1080 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
1081 ) -> CachedTraceRecord {
1082 crate::batch_constraint::expr_eval::build_cached_trace_record(
1083 child_vk,
1084 self.batch_constraint.has_cached,
1085 )
1086 }
1087
1088 #[tracing::instrument(name = "subcircuit_generate_proving_ctxs", skip_all)]
1089 fn generate_proving_ctxs<
1090 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
1091 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
1092 >(
1093 &self,
1094 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
1095 cached_trace_ctx: CachedTraceCtx<CpuBackend<SC>>,
1096 proofs: &[Proof<BabyBearPoseidon2Config>],
1097 external_data: &mut VerifierExternalData,
1098 _device_ctx: &(),
1099 initial_transcript: TS,
1100 ) -> Option<Vec<AirProvingContext<CpuBackend<SC>>>> {
1101 debug_assert!(proofs.len() <= MAX_NUM_PROOFS);
1102 let span = tracing::Span::current();
1106 let mut preflights = std::thread::scope(|s| {
1107 let handles: Vec<_> = proofs
1108 .iter()
1109 .map(|proof| {
1110 let sponge = initial_transcript.clone();
1111 let span = span.clone();
1112 s.spawn(move || {
1113 let _guard = span.enter();
1114 #[cfg(feature = "cuda")]
1115 {
1116 self.run_preflight_without_merkle(sponge, child_vk, proof)
1117 }
1118 #[cfg(not(feature = "cuda"))]
1119 {
1120 self.run_preflight(sponge, child_vk, proof)
1121 }
1122 })
1123 })
1124 .collect();
1125 handles
1126 .into_iter()
1127 .map(|h| h.join().unwrap())
1128 .collect::<Vec<_>>()
1129 });
1130 #[cfg(feature = "cuda")]
1131 for (proof, preflight) in proofs.iter().zip(preflights.iter_mut()) {
1132 Self::apply_merkle_precomputation_cpu(proof, preflight);
1133 }
1134
1135 if let Some(final_transcript_state) = &mut external_data.final_transcript_state {
1136 debug_assert_eq!(preflights.len(), 1);
1140 debug_assert!(preflights[0].transcript.samples().last().unwrap());
1141 let state = *preflights[0].transcript.perm_results().last().unwrap();
1142 *(*final_transcript_state) = state;
1143 preflights[0].poseidon2_compress_inputs.push(state);
1144 }
1145
1146 let power_checker_gen =
1147 Arc::new(PowerCheckerCpuTraceGenerator::<2, POW_CHECKER_HEIGHT>::default());
1148 for &log in external_data.power_check_inputs {
1149 power_checker_gen.add_pow(log);
1150 }
1151 let exp_bits_len_gen = ExpBitsLenCpuTraceGenerator::default();
1152
1153 let (module_required, power_checker_required, exp_bits_len_required) =
1154 self.split_required_heights(external_data.required_heights);
1155
1156 let modules = vec![
1158 TraceModuleRef::BatchConstraint(&self.batch_constraint),
1159 TraceModuleRef::Transcript(&self.transcript),
1160 TraceModuleRef::ProofShape(&self.proof_shape),
1161 TraceModuleRef::Gkr(&self.gkr),
1162 TraceModuleRef::Stacking(&self.stacking),
1163 TraceModuleRef::Whir(&self.whir),
1164 ];
1165 let cached_trace_record = match &cached_trace_ctx {
1166 CachedTraceCtx::Records(cached_trace_record) => Some(cached_trace_record),
1167 _ => None,
1168 };
1169
1170 let span = tracing::Span::current();
1171 let ctxs_by_module = modules
1172 .into_par_iter()
1173 .zip(module_required)
1174 .map(|(module, required_heights)| {
1175 let _guard = span.enter();
1176 module.generate_cpu_ctxs(
1177 child_vk,
1178 proofs,
1179 &preflights,
1180 cached_trace_record,
1181 &power_checker_gen,
1182 &exp_bits_len_gen,
1183 external_data,
1184 required_heights,
1185 )
1186 })
1187 .collect::<Vec<_>>();
1188
1189 let mut ctxs_by_module: Vec<Vec<AirProvingContext<CpuBackend<SC>>>> =
1190 ctxs_by_module.into_iter().collect::<Option<Vec<_>>>()?;
1191 match cached_trace_ctx {
1192 CachedTraceCtx::PcsData(child_vk_pcs_data) => {
1193 assert!(self.batch_constraint.has_cached);
1194 ctxs_by_module[BATCH_CONSTRAINT_MOD_IDX][LOCAL_SYMBOLIC_EXPRESSION_AIR_IDX]
1195 .cached_mains = vec![child_vk_pcs_data];
1196 }
1197 CachedTraceCtx::Records(cached_trace_record) => {
1198 assert!(!self.batch_constraint.has_cached);
1199 ctxs_by_module[BATCH_CONSTRAINT_MOD_IDX][LOCAL_SYMBOLIC_EXPRESSION_AIR_IDX]
1200 .public_values = cached_trace_record.dag_commit_info.unwrap().commit.to_vec();
1201 }
1202 };
1203
1204 let mut ctx_per_trace = ctxs_by_module.into_iter().flatten().collect::<Vec<_>>();
1205 if power_checker_required.is_some_and(|h| h != POW_CHECKER_HEIGHT) {
1206 return None;
1207 }
1208 tracing::trace_span!("wrapper.generate_proving_ctxs", air_module = "Primitives",).in_scope(
1210 || {
1211 tracing::trace_span!("wrapper.generate_trace", air = "PowerChecker").in_scope(
1212 || {
1213 ctx_per_trace.push(AirProvingContext::simple_no_pis(
1214 power_checker_gen.generate_trace_row_major(),
1215 ));
1216 },
1217 );
1218 },
1219 );
1220 let exp_bits_trace_rm = tracing::trace_span!("wrapper.generate_trace", air = "ExpBitsLen")
1221 .in_scope(|| exp_bits_len_gen.generate_trace_row_major(exp_bits_len_required))?;
1222 ctx_per_trace.push(AirProvingContext::simple_no_pis(exp_bits_trace_rm));
1223 Some(ctx_per_trace)
1224 }
1225}
1226
1227#[cfg(feature = "cuda")]
1228pub mod cuda_tracegen {
1229 use std::iter::zip;
1230
1231 use openvm_cuda_backend::{hash_scheme::GpuHashScheme, GenericGpuBackend, GpuBackend};
1232
1233 use super::*;
1234 use crate::{
1235 cuda::{preflight::PreflightGpu, proof::ProofGpu, vk::VerifyingKeyGpu},
1236 primitives::{
1237 exp_bits_len::ExpBitsLenTraceGenerator as GpuExpBitsLenTraceGenerator,
1238 pow::cuda::PowerCheckerGpuTraceGenerator,
1239 },
1240 };
1241
1242 impl<'a> TraceModuleRef<'a> {
1243 #[allow(clippy::too_many_arguments)]
1244 #[tracing::instrument(
1245 name = "wrapper.generate_proving_ctxs",
1246 level = "trace",
1247 skip_all,
1248 fields(air_module = %self)
1249 )]
1250 fn generate_gpu_proving_ctxs(
1251 self,
1252 child_vk: &VerifyingKeyGpu,
1253 proofs: &[ProofGpu],
1254 preflights: &[PreflightGpu],
1255 device_ctx: &openvm_cuda_common::stream::GpuDeviceCtx,
1256 cached_trace_record: Option<&CachedTraceRecord>,
1257 pow_checker_gen: &Arc<PowerCheckerGpuTraceGenerator<2, POW_CHECKER_HEIGHT>>,
1258 exp_bits_len_gen: &GpuExpBitsLenTraceGenerator,
1259 external_data: &VerifierExternalData,
1260 required_heights: Option<&[usize]>,
1261 ) -> Option<Vec<AirProvingContext<GpuBackend>>> {
1262 match self {
1263 TraceModuleRef::Transcript(module) => module.generate_proving_ctxs(
1264 child_vk,
1265 proofs,
1266 preflights,
1267 &(
1268 external_data.poseidon2_permute_inputs,
1269 external_data.poseidon2_compress_inputs,
1270 device_ctx,
1271 ),
1272 required_heights,
1273 ),
1274 TraceModuleRef::ProofShape(module) => module.generate_proving_ctxs(
1275 child_vk,
1276 proofs,
1277 preflights,
1278 &(
1279 pow_checker_gen.clone(),
1280 external_data.range_check_inputs.as_slice(),
1281 device_ctx,
1282 ),
1283 required_heights,
1284 ),
1285 TraceModuleRef::Gkr(module) => module.generate_proving_ctxs(
1286 child_vk,
1287 proofs,
1288 preflights,
1289 &(exp_bits_len_gen, device_ctx),
1290 required_heights,
1291 ),
1292 TraceModuleRef::BatchConstraint(module) => module.generate_proving_ctxs(
1293 child_vk,
1294 proofs,
1295 preflights,
1296 &(
1297 cached_trace_record,
1298 pow_checker_gen.cpu_checker().unwrap(),
1299 device_ctx,
1300 ),
1301 required_heights,
1302 ),
1303 TraceModuleRef::Stacking(module) => module.generate_proving_ctxs(
1304 child_vk,
1305 proofs,
1306 preflights,
1307 device_ctx,
1308 required_heights,
1309 ),
1310 TraceModuleRef::Whir(module) => module.generate_proving_ctxs(
1311 child_vk,
1312 proofs,
1313 preflights,
1314 &(exp_bits_len_gen, device_ctx),
1315 required_heights,
1316 ),
1317 }
1318 }
1319 }
1320
1321 fn coerce_gpu_proving_ctx<HS: GpuHashScheme>(
1326 ctx: AirProvingContext<GpuBackend>,
1327 ) -> AirProvingContext<GenericGpuBackend<HS>> {
1328 debug_assert!(ctx.cached_mains.is_empty());
1329 AirProvingContext {
1330 cached_mains: vec![],
1331 common_main: ctx.common_main,
1332 public_values: ctx.public_values,
1333 }
1334 }
1335
1336 impl<HS: GpuHashScheme, const MAX_NUM_PROOFS: usize>
1337 VerifierTraceGen<GenericGpuBackend<HS>, HS::SC, openvm_cuda_common::stream::GpuDeviceCtx>
1338 for VerifierSubCircuit<MAX_NUM_PROOFS>
1339 {
1340 fn new(
1341 child_mvk: Arc<MultiStarkVerifyingKey<BabyBearPoseidon2Config>>,
1342 config: VerifierConfig,
1343 ) -> Self {
1344 Self::new_with_options(child_mvk, config)
1345 }
1346
1347 fn commit_child_vk<E: StarkEngine<SC = HS::SC, PB = GenericGpuBackend<HS>>>(
1348 &self,
1349 engine: &E,
1350 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
1351 ) -> CommittedTraceData<GenericGpuBackend<HS>>
1352 where
1353 GpuDeviceCtx: From<EngineDeviceCtx<E>>,
1354 {
1355 crate::batch_constraint::commit_child_vk(
1356 engine,
1357 child_vk,
1358 self.batch_constraint.has_cached,
1359 )
1360 }
1361
1362 fn cached_trace_record(
1363 &self,
1364 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
1365 ) -> CachedTraceRecord {
1366 crate::batch_constraint::expr_eval::build_cached_trace_record(
1367 child_vk,
1368 self.batch_constraint.has_cached,
1369 )
1370 }
1371
1372 #[tracing::instrument(name = "subcircuit_generate_proving_ctxs", skip_all)]
1373 fn generate_proving_ctxs<
1374 TS: FiatShamirTranscript<BabyBearPoseidon2Config>
1375 + TranscriptHistory<F = F, State = [F; POSEIDON2_WIDTH]>,
1376 >(
1377 &self,
1378 child_vk: &MultiStarkVerifyingKey<BabyBearPoseidon2Config>,
1379 cached_trace_ctx: CachedTraceCtx<GenericGpuBackend<HS>>,
1380 proofs: &[Proof<BabyBearPoseidon2Config>],
1381 external_data: &mut VerifierExternalData,
1382 device_ctx: &openvm_cuda_common::stream::GpuDeviceCtx,
1383 initial_transcript: TS,
1384 ) -> Option<Vec<AirProvingContext<GenericGpuBackend<HS>>>> {
1385 debug_assert!(proofs.len() <= MAX_NUM_PROOFS);
1386 let child_vk_gpu = VerifyingKeyGpu::new(child_vk, device_ctx);
1387 let proofs_gpu = proofs
1388 .iter()
1389 .map(|proof_cpu| ProofGpu::new(child_vk, proof_cpu, device_ctx))
1390 .collect::<Vec<_>>();
1391 let span = tracing::Span::current();
1394 let mut preflights_cpu = std::thread::scope(|s| {
1395 let handles: Vec<_> = proofs
1396 .iter()
1397 .map(|proof| {
1398 let sponge = initial_transcript.clone();
1399 let span = span.clone();
1400 s.spawn(move || {
1401 let _guard = span.enter();
1402 self.run_preflight_without_merkle(sponge, child_vk, proof)
1403 })
1404 })
1405 .collect();
1406 handles
1407 .into_iter()
1408 .map(|h| h.join().unwrap())
1409 .collect::<Vec<_>>()
1410 });
1411
1412 for (proof, preflight) in proofs.iter().zip(preflights_cpu.iter_mut()) {
1414 Self::apply_merkle_precomputation(proof, preflight, device_ctx);
1415 }
1416
1417 if let Some(final_transcript_state) = &mut external_data.final_transcript_state {
1418 debug_assert_eq!(preflights_cpu.len(), 1);
1422 debug_assert!(preflights_cpu[0].transcript.samples().last().unwrap());
1423 let state = *preflights_cpu[0].transcript.perm_results().last().unwrap();
1424 *(*final_transcript_state) = state;
1425 preflights_cpu[0].poseidon2_compress_inputs.push(state);
1426 }
1427
1428 let power_checker_gen = Arc::new(
1429 PowerCheckerGpuTraceGenerator::<2, POW_CHECKER_HEIGHT>::hybrid(device_ctx.clone()),
1430 );
1431 if let Some(cpu_checker) = power_checker_gen.cpu_checker() {
1432 for &log in external_data.power_check_inputs {
1433 cpu_checker.add_pow(log);
1434 }
1435 }
1436 let exp_bits_len_gen = GpuExpBitsLenTraceGenerator::new(device_ctx.clone());
1437
1438 let (module_required, power_checker_required, exp_bits_len_required) =
1439 self.split_required_heights(external_data.required_heights);
1440
1441 let preflights_gpu = zip(proofs, preflights_cpu)
1444 .map(|(proof, preflight_cpu)| {
1445 PreflightGpu::new(child_vk, proof, &preflight_cpu, device_ctx)
1446 })
1447 .collect::<Vec<_>>();
1448 let modules = vec![
1449 TraceModuleRef::BatchConstraint(&self.batch_constraint),
1450 TraceModuleRef::Transcript(&self.transcript),
1451 TraceModuleRef::ProofShape(&self.proof_shape),
1452 TraceModuleRef::Gkr(&self.gkr),
1453 TraceModuleRef::Stacking(&self.stacking),
1454 TraceModuleRef::Whir(&self.whir),
1455 ];
1456 let cached_trace_record = match &cached_trace_ctx {
1457 CachedTraceCtx::Records(cached_trace_record) => Some(cached_trace_record),
1458 _ => None,
1459 };
1460
1461 let mut ctxs_by_module_gpu = Vec::with_capacity(modules.len());
1466 for (module, required_heights) in modules.into_iter().zip(module_required) {
1467 ctxs_by_module_gpu.push(module.generate_gpu_proving_ctxs(
1468 &child_vk_gpu,
1469 &proofs_gpu,
1470 &preflights_gpu,
1471 device_ctx,
1472 cached_trace_record,
1473 &power_checker_gen,
1474 &exp_bits_len_gen,
1475 external_data,
1476 required_heights,
1477 )?);
1478 }
1479 device_ctx.stream.synchronize().unwrap();
1480 let mut ctxs_by_module: Vec<Vec<AirProvingContext<GenericGpuBackend<HS>>>> =
1481 ctxs_by_module_gpu
1482 .into_iter()
1483 .map(|module_ctxs| {
1484 module_ctxs
1485 .into_iter()
1486 .map(coerce_gpu_proving_ctx::<HS>)
1487 .collect()
1488 })
1489 .collect();
1490 match cached_trace_ctx {
1491 CachedTraceCtx::PcsData(child_vk_pcs_data) => {
1492 assert!(self.batch_constraint.has_cached);
1493 ctxs_by_module[BATCH_CONSTRAINT_MOD_IDX][LOCAL_SYMBOLIC_EXPRESSION_AIR_IDX]
1494 .cached_mains = vec![child_vk_pcs_data];
1495 }
1496 CachedTraceCtx::Records(cached_trace_record) => {
1497 assert!(!self.batch_constraint.has_cached);
1498 ctxs_by_module[BATCH_CONSTRAINT_MOD_IDX][LOCAL_SYMBOLIC_EXPRESSION_AIR_IDX]
1499 .public_values =
1500 cached_trace_record.dag_commit_info.unwrap().commit.to_vec();
1501 }
1502 }
1503
1504 let mut ctx_per_trace = ctxs_by_module.into_iter().flatten().collect::<Vec<_>>();
1505 if power_checker_required.is_some_and(|h| h != POW_CHECKER_HEIGHT) {
1506 return None;
1507 }
1508 tracing::trace_span!("wrapper.generate_proving_ctxs", air_module = "Primitives",)
1510 .in_scope(|| {
1511 tracing::trace_span!("wrapper.generate_trace", air = "PowerChecker").in_scope(
1512 || {
1513 let pow_bits_trace = power_checker_gen.generate_trace();
1514 ctx_per_trace.push(AirProvingContext::simple_no_pis(pow_bits_trace));
1515 },
1516 );
1517 });
1518 let exp_bits_trace = tracing::trace_span!("wrapper.generate_trace", air = "ExpBitsLen")
1519 .in_scope(|| exp_bits_len_gen.generate_trace_device(exp_bits_len_required))?;
1520 ctx_per_trace.push(AirProvingContext::simple_no_pis(exp_bits_trace));
1521 Some(ctx_per_trace)
1522 }
1523 }
1524}