openvm_circuit/arch/
vm.rs

1use std::{borrow::Borrow, collections::VecDeque, marker::PhantomData, mem, sync::Arc};
2
3use openvm_circuit::system::program::trace::compute_exe_commit;
4use openvm_instructions::exe::VmExe;
5use openvm_stark_backend::{
6    config::{Com, Domain, StarkGenericConfig, Val},
7    engine::StarkEngine,
8    keygen::types::{LinearConstraint, MultiStarkProvingKey, MultiStarkVerifyingKey},
9    p3_commit::PolynomialSpace,
10    p3_field::{FieldAlgebra, PrimeField32},
11    proof::Proof,
12    prover::types::{CommittedTraceData, ProofInput},
13    utils::metrics_span,
14    verifier::VerificationError,
15    Chip,
16};
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19use tracing::info_span;
20
21use super::{
22    ExecutionError, VmComplexTraceHeights, VmConfig, CONNECTOR_AIR_ID, MERKLE_AIR_ID,
23    PROGRAM_AIR_ID, PROGRAM_CACHED_TRACE_INDEX,
24};
25#[cfg(feature = "bench-metrics")]
26use crate::metrics::VmMetrics;
27use crate::{
28    arch::{hasher::poseidon2::vm_poseidon2_hasher, segment::ExecutionSegment},
29    system::{
30        connector::{VmConnectorPvs, DEFAULT_SUSPEND_EXIT_CODE},
31        memory::{
32            merkle::MemoryMerklePvs,
33            paged_vec::AddressMap,
34            tree::public_values::{UserPublicValuesProof, UserPublicValuesProofError},
35            MemoryImage, CHUNK,
36        },
37        program::trace::VmCommittedExe,
38    },
39};
40
41#[derive(Error, Debug)]
42pub enum GenerationError {
43    #[error("generated trace heights violate constraints")]
44    TraceHeightsLimitExceeded,
45    #[error(transparent)]
46    Execution(#[from] ExecutionError),
47}
48
49/// VM memory state for continuations.
50pub type VmMemoryState<F> = MemoryImage<F>;
51
52#[derive(Clone, Default, Debug)]
53pub struct Streams<F> {
54    pub input_stream: VecDeque<Vec<F>>,
55    pub hint_stream: VecDeque<F>,
56    pub hint_space: Vec<Vec<F>>,
57}
58
59impl<F> Streams<F> {
60    pub fn new(input_stream: impl Into<VecDeque<Vec<F>>>) -> Self {
61        Self {
62            input_stream: input_stream.into(),
63            hint_stream: VecDeque::default(),
64            hint_space: Vec::default(),
65        }
66    }
67}
68
69impl<F> From<VecDeque<Vec<F>>> for Streams<F> {
70    fn from(value: VecDeque<Vec<F>>) -> Self {
71        Streams::new(value)
72    }
73}
74
75impl<F> From<Vec<Vec<F>>> for Streams<F> {
76    fn from(value: Vec<Vec<F>>) -> Self {
77        Streams::new(value)
78    }
79}
80
81pub struct VmExecutor<F, VC> {
82    pub config: VC,
83    pub overridden_heights: Option<VmComplexTraceHeights>,
84    pub trace_height_constraints: Vec<LinearConstraint>,
85    _marker: PhantomData<F>,
86}
87
88#[repr(i32)]
89pub enum ExitCode {
90    Success = 0,
91    Error = 1,
92    Suspended = -1, // Continuations
93}
94
95pub struct VmExecutorResult<SC: StarkGenericConfig> {
96    pub per_segment: Vec<ProofInput<SC>>,
97    /// When VM is running on persistent mode, public values are stored in a special memory space.
98    pub final_memory: Option<VmMemoryState<Val<SC>>>,
99}
100
101pub struct VmExecutorNextSegmentState<F: PrimeField32> {
102    pub memory: MemoryImage<F>,
103    pub input: Streams<F>,
104    pub pc: u32,
105    #[cfg(feature = "bench-metrics")]
106    pub metrics: VmMetrics,
107}
108
109impl<F: PrimeField32> VmExecutorNextSegmentState<F> {
110    pub fn new(memory: MemoryImage<F>, input: impl Into<Streams<F>>, pc: u32) -> Self {
111        Self {
112            memory,
113            input: input.into(),
114            pc,
115            #[cfg(feature = "bench-metrics")]
116            metrics: VmMetrics::default(),
117        }
118    }
119}
120
121pub struct VmExecutorOneSegmentResult<F: PrimeField32, VC: VmConfig<F>> {
122    pub segment: ExecutionSegment<F, VC>,
123    pub next_state: Option<VmExecutorNextSegmentState<F>>,
124}
125
126impl<F, VC> VmExecutor<F, VC>
127where
128    F: PrimeField32,
129    VC: VmConfig<F>,
130{
131    /// Create a new VM executor with a given config.
132    ///
133    /// The VM will start with a single segment, which is created from the initial state.
134    pub fn new(config: VC) -> Self {
135        Self::new_with_overridden_trace_heights(config, None)
136    }
137
138    pub fn set_override_trace_heights(&mut self, overridden_heights: VmComplexTraceHeights) {
139        self.overridden_heights = Some(overridden_heights);
140    }
141
142    pub fn new_with_overridden_trace_heights(
143        config: VC,
144        overridden_heights: Option<VmComplexTraceHeights>,
145    ) -> Self {
146        Self {
147            config,
148            overridden_heights,
149            trace_height_constraints: vec![],
150            _marker: Default::default(),
151        }
152    }
153
154    pub fn continuation_enabled(&self) -> bool {
155        self.config.system().continuation_enabled
156    }
157
158    /// Executes the program in segments.
159    /// After each segment is executed, call the provided closure on the execution result.
160    /// Returns the results from each closure, one per segment.
161    ///
162    /// The closure takes `f(segment_idx, segment) -> R`.
163    pub fn execute_and_then<R, E>(
164        &self,
165        exe: impl Into<VmExe<F>>,
166        input: impl Into<Streams<F>>,
167        mut f: impl FnMut(usize, ExecutionSegment<F, VC>) -> Result<R, E>,
168        map_err: impl Fn(ExecutionError) -> E,
169    ) -> Result<Vec<R>, E> {
170        let mem_config = self.config.system().memory_config;
171        let exe = exe.into();
172        let mut segment_results = vec![];
173        let memory = AddressMap::from_iter(
174            mem_config.as_offset,
175            1 << mem_config.as_height,
176            1 << mem_config.pointer_max_bits,
177            exe.init_memory.clone(),
178        );
179        let pc = exe.pc_start;
180        let mut state = VmExecutorNextSegmentState::new(memory, input, pc);
181
182        #[cfg(feature = "bench-metrics")]
183        {
184            state.metrics.fn_bounds = exe.fn_bounds.clone();
185        }
186
187        let mut segment_idx = 0;
188
189        loop {
190            let _span = info_span!("execute_segment", segment = segment_idx).entered();
191            let one_segment_result = self
192                .execute_until_segment(exe.clone(), state)
193                .map_err(&map_err)?;
194            segment_results.push(f(segment_idx, one_segment_result.segment)?);
195            if one_segment_result.next_state.is_none() {
196                break;
197            }
198            state = one_segment_result.next_state.unwrap();
199            segment_idx += 1;
200        }
201        tracing::debug!("Number of continuation segments: {}", segment_results.len());
202        #[cfg(feature = "bench-metrics")]
203        metrics::counter!("num_segments").absolute(segment_results.len() as u64);
204
205        Ok(segment_results)
206    }
207
208    pub fn execute_segments(
209        &self,
210        exe: impl Into<VmExe<F>>,
211        input: impl Into<Streams<F>>,
212    ) -> Result<Vec<ExecutionSegment<F, VC>>, ExecutionError> {
213        self.execute_and_then(exe, input, |_, seg| Ok(seg), |err| err)
214    }
215
216    /// Executes a program until a segmentation happens.
217    /// Returns the last segment and the vm state for next segment.
218    /// This is so that the tracegen and proving of this segment can be immediately started (on a
219    /// separate machine).
220    pub fn execute_until_segment(
221        &self,
222        exe: impl Into<VmExe<F>>,
223        from_state: VmExecutorNextSegmentState<F>,
224    ) -> Result<VmExecutorOneSegmentResult<F, VC>, ExecutionError> {
225        let exe = exe.into();
226        let mut segment = ExecutionSegment::new(
227            &self.config,
228            exe.program.clone(),
229            from_state.input,
230            Some(from_state.memory),
231            self.trace_height_constraints.clone(),
232            exe.fn_bounds.clone(),
233        );
234        #[cfg(feature = "bench-metrics")]
235        {
236            segment.metrics = from_state.metrics;
237        }
238        if let Some(overridden_heights) = self.overridden_heights.as_ref() {
239            segment.set_override_trace_heights(overridden_heights.clone());
240        }
241        let state = metrics_span("execute_time_ms", || segment.execute_from_pc(from_state.pc))?;
242
243        if state.is_terminated {
244            return Ok(VmExecutorOneSegmentResult {
245                segment,
246                next_state: None,
247            });
248        }
249
250        assert!(
251            self.continuation_enabled(),
252            "multiple segments require to enable continuations"
253        );
254        assert_eq!(
255            state.pc,
256            segment.chip_complex.connector_chip().boundary_states[1]
257                .unwrap()
258                .pc
259        );
260        let final_memory = mem::take(&mut segment.final_memory)
261            .expect("final memory should be set in continuations segment");
262        let streams = segment.chip_complex.take_streams();
263        #[cfg(feature = "bench-metrics")]
264        let metrics = segment.metrics.partial_take();
265        Ok(VmExecutorOneSegmentResult {
266            segment,
267            next_state: Some(VmExecutorNextSegmentState {
268                memory: final_memory,
269                input: streams,
270                pc: state.pc,
271                #[cfg(feature = "bench-metrics")]
272                metrics,
273            }),
274        })
275    }
276
277    pub fn execute(
278        &self,
279        exe: impl Into<VmExe<F>>,
280        input: impl Into<Streams<F>>,
281    ) -> Result<Option<VmMemoryState<F>>, ExecutionError> {
282        let mut last = None;
283        self.execute_and_then(
284            exe,
285            input,
286            |_, seg| {
287                last = Some(seg);
288                Ok(())
289            },
290            |err| err,
291        )?;
292        let last = last.expect("at least one segment must be executed");
293        let final_memory = last.final_memory;
294        let end_state =
295            last.chip_complex.connector_chip().boundary_states[1].expect("end state must be set");
296        if end_state.is_terminate != 1 {
297            return Err(ExecutionError::DidNotTerminate);
298        }
299        if end_state.exit_code != ExitCode::Success as u32 {
300            return Err(ExecutionError::FailedWithExitCode(end_state.exit_code));
301        }
302        Ok(final_memory)
303    }
304
305    pub fn execute_and_generate<SC: StarkGenericConfig>(
306        &self,
307        exe: impl Into<VmExe<F>>,
308        input: impl Into<Streams<F>>,
309    ) -> Result<VmExecutorResult<SC>, GenerationError>
310    where
311        Domain<SC>: PolynomialSpace<Val = F>,
312        VC::Executor: Chip<SC>,
313        VC::Periphery: Chip<SC>,
314    {
315        self.execute_and_generate_impl(exe.into(), None, input)
316    }
317
318    pub fn execute_and_generate_with_cached_program<SC: StarkGenericConfig>(
319        &self,
320        committed_exe: Arc<VmCommittedExe<SC>>,
321        input: impl Into<Streams<F>>,
322    ) -> Result<VmExecutorResult<SC>, GenerationError>
323    where
324        Domain<SC>: PolynomialSpace<Val = F>,
325        VC::Executor: Chip<SC>,
326        VC::Periphery: Chip<SC>,
327    {
328        self.execute_and_generate_impl(
329            committed_exe.exe.clone(),
330            Some(committed_exe.committed_program.clone()),
331            input,
332        )
333    }
334
335    fn execute_and_generate_impl<SC: StarkGenericConfig>(
336        &self,
337        exe: VmExe<F>,
338        committed_program: Option<CommittedTraceData<SC>>,
339        input: impl Into<Streams<F>>,
340    ) -> Result<VmExecutorResult<SC>, GenerationError>
341    where
342        Domain<SC>: PolynomialSpace<Val = F>,
343        VC::Executor: Chip<SC>,
344        VC::Periphery: Chip<SC>,
345    {
346        let mut final_memory = None;
347        let per_segment = self.execute_and_then(
348            exe,
349            input,
350            |seg_idx, mut seg| {
351                // Note: this will only be Some on the last segment; otherwise it is
352                // already moved into next segment state
353                final_memory = mem::take(&mut seg.final_memory);
354                tracing::info_span!("trace_gen", segment = seg_idx)
355                    .in_scope(|| seg.generate_proof_input(committed_program.clone()))
356            },
357            GenerationError::Execution,
358        )?;
359
360        Ok(VmExecutorResult {
361            per_segment,
362            final_memory,
363        })
364    }
365
366    pub fn set_trace_height_constraints(&mut self, constraints: Vec<LinearConstraint>) {
367        self.trace_height_constraints = constraints;
368    }
369}
370
371/// A single segment VM.
372pub struct SingleSegmentVmExecutor<F, VC> {
373    pub config: VC,
374    pub overridden_heights: Option<VmComplexTraceHeights>,
375    pub trace_height_constraints: Vec<LinearConstraint>,
376    _marker: PhantomData<F>,
377}
378
379/// Execution result of a single segment VM execution.
380pub struct SingleSegmentVmExecutionResult<F> {
381    /// All user public values
382    pub public_values: Vec<Option<F>>,
383    /// Heights of each AIR, ordered by AIR ID.
384    pub air_heights: Vec<usize>,
385    /// Heights of (SystemBase, Inventory), in an internal ordering.
386    pub vm_heights: VmComplexTraceHeights,
387}
388
389impl<F, VC> SingleSegmentVmExecutor<F, VC>
390where
391    F: PrimeField32,
392    VC: VmConfig<F>,
393{
394    pub fn new(config: VC) -> Self {
395        Self::new_with_overridden_trace_heights(config, None)
396    }
397
398    pub fn new_with_overridden_trace_heights(
399        config: VC,
400        overridden_heights: Option<VmComplexTraceHeights>,
401    ) -> Self {
402        assert!(
403            !config.system().continuation_enabled,
404            "Single segment VM doesn't support continuation mode"
405        );
406        Self {
407            config,
408            overridden_heights,
409            trace_height_constraints: vec![],
410            _marker: Default::default(),
411        }
412    }
413
414    pub fn set_override_trace_heights(&mut self, overridden_heights: VmComplexTraceHeights) {
415        self.overridden_heights = Some(overridden_heights);
416    }
417
418    pub fn set_trace_height_constraints(&mut self, constraints: Vec<LinearConstraint>) {
419        self.trace_height_constraints = constraints;
420    }
421
422    /// Executes a program, compute the trace heights, and returns the public values.
423    pub fn execute_and_compute_heights(
424        &self,
425        exe: impl Into<VmExe<F>>,
426        input: impl Into<Streams<F>>,
427    ) -> Result<SingleSegmentVmExecutionResult<F>, ExecutionError> {
428        let segment = {
429            let mut segment = self.execute_impl(exe.into(), input.into())?;
430            segment.chip_complex.finalize_memory();
431            segment
432        };
433        let air_heights = segment.chip_complex.current_trace_heights();
434        let vm_heights = segment.chip_complex.get_internal_trace_heights();
435        let public_values = if let Some(pv_chip) = segment.chip_complex.public_values_chip() {
436            pv_chip.core.get_custom_public_values()
437        } else {
438            vec![]
439        };
440        Ok(SingleSegmentVmExecutionResult {
441            public_values,
442            air_heights,
443            vm_heights,
444        })
445    }
446
447    /// Executes a program and returns its proof input.
448    pub fn execute_and_generate<SC: StarkGenericConfig>(
449        &self,
450        committed_exe: Arc<VmCommittedExe<SC>>,
451        input: impl Into<Streams<F>>,
452    ) -> Result<ProofInput<SC>, GenerationError>
453    where
454        Domain<SC>: PolynomialSpace<Val = F>,
455        VC::Executor: Chip<SC>,
456        VC::Periphery: Chip<SC>,
457    {
458        let segment = self.execute_impl(committed_exe.exe.clone(), input)?;
459        let proof_input = tracing::info_span!("trace_gen").in_scope(|| {
460            segment.generate_proof_input(Some(committed_exe.committed_program.clone()))
461        })?;
462        Ok(proof_input)
463    }
464
465    fn execute_impl(
466        &self,
467        exe: VmExe<F>,
468        input: impl Into<Streams<F>>,
469    ) -> Result<ExecutionSegment<F, VC>, ExecutionError> {
470        let pc_start = exe.pc_start;
471        let mut segment = ExecutionSegment::new(
472            &self.config,
473            exe.program.clone(),
474            input.into(),
475            None,
476            self.trace_height_constraints.clone(),
477            exe.fn_bounds.clone(),
478        );
479        if let Some(overridden_heights) = self.overridden_heights.as_ref() {
480            segment.set_override_trace_heights(overridden_heights.clone());
481        }
482        metrics_span("execute_time_ms", || segment.execute_from_pc(pc_start))?;
483        Ok(segment)
484    }
485}
486
487#[derive(Error, Debug)]
488pub enum VmVerificationError {
489    #[error("no proof is provided")]
490    ProofNotFound,
491
492    #[error("program commit mismatch (index of mismatch proof: {index}")]
493    ProgramCommitMismatch { index: usize },
494
495    #[error("initial pc mismatch (initial: {initial}, prev_final: {prev_final})")]
496    InitialPcMismatch { initial: u32, prev_final: u32 },
497
498    #[error("initial memory root mismatch")]
499    InitialMemoryRootMismatch,
500
501    #[error("is terminate mismatch (expected: {expected}, actual: {actual})")]
502    IsTerminateMismatch { expected: bool, actual: bool },
503
504    #[error("exit code mismatch")]
505    ExitCodeMismatch { expected: u32, actual: u32 },
506
507    #[error("AIR has unexpected public values (expected: {expected}, actual: {actual})")]
508    UnexpectedPvs { expected: usize, actual: usize },
509
510    #[error("missing system AIR with ID {air_id}")]
511    SystemAirMissing { air_id: usize },
512
513    #[error("stark verification error: {0}")]
514    StarkError(#[from] VerificationError),
515
516    #[error("user public values proof error: {0}")]
517    UserPublicValuesError(#[from] UserPublicValuesProofError),
518}
519
520pub struct VirtualMachine<SC: StarkGenericConfig, E, VC> {
521    /// Proving engine
522    pub engine: E,
523    /// Runtime executor
524    pub executor: VmExecutor<Val<SC>, VC>,
525    _marker: PhantomData<SC>,
526}
527
528impl<F, SC, E, VC> VirtualMachine<SC, E, VC>
529where
530    F: PrimeField32,
531    SC: StarkGenericConfig,
532    E: StarkEngine<SC>,
533    Domain<SC>: PolynomialSpace<Val = F>,
534    VC: VmConfig<F>,
535    VC::Executor: Chip<SC>,
536    VC::Periphery: Chip<SC>,
537{
538    pub fn new(engine: E, config: VC) -> Self {
539        let executor = VmExecutor::new(config);
540        Self {
541            engine,
542            executor,
543            _marker: PhantomData,
544        }
545    }
546
547    pub fn new_with_overridden_trace_heights(
548        engine: E,
549        config: VC,
550        overridden_heights: Option<VmComplexTraceHeights>,
551    ) -> Self {
552        let executor = VmExecutor::new_with_overridden_trace_heights(config, overridden_heights);
553        Self {
554            engine,
555            executor,
556            _marker: PhantomData,
557        }
558    }
559
560    pub fn config(&self) -> &VC {
561        &self.executor.config
562    }
563
564    pub fn keygen(&self) -> MultiStarkProvingKey<SC> {
565        let mut keygen_builder = self.engine.keygen_builder();
566        let chip_complex = self.config().create_chip_complex().unwrap();
567        for air in chip_complex.airs() {
568            keygen_builder.add_air(air);
569        }
570        keygen_builder.generate_pk()
571    }
572
573    pub fn set_trace_height_constraints(
574        &mut self,
575        trace_height_constraints: Vec<LinearConstraint>,
576    ) {
577        self.executor
578            .set_trace_height_constraints(trace_height_constraints);
579    }
580
581    pub fn commit_exe(&self, exe: impl Into<VmExe<F>>) -> Arc<VmCommittedExe<SC>> {
582        let exe = exe.into();
583        Arc::new(VmCommittedExe::commit(exe, self.engine.config().pcs()))
584    }
585
586    pub fn execute(
587        &self,
588        exe: impl Into<VmExe<F>>,
589        input: impl Into<Streams<F>>,
590    ) -> Result<Option<VmMemoryState<F>>, ExecutionError> {
591        self.executor.execute(exe, input)
592    }
593
594    pub fn execute_and_generate(
595        &self,
596        exe: impl Into<VmExe<F>>,
597        input: impl Into<Streams<F>>,
598    ) -> Result<VmExecutorResult<SC>, GenerationError> {
599        self.executor.execute_and_generate(exe, input)
600    }
601
602    pub fn execute_and_generate_with_cached_program(
603        &self,
604        committed_exe: Arc<VmCommittedExe<SC>>,
605        input: impl Into<Streams<F>>,
606    ) -> Result<VmExecutorResult<SC>, GenerationError>
607    where
608        Domain<SC>: PolynomialSpace<Val = F>,
609    {
610        self.executor
611            .execute_and_generate_with_cached_program(committed_exe, input)
612    }
613
614    pub fn prove_single(
615        &self,
616        pk: &MultiStarkProvingKey<SC>,
617        proof_input: ProofInput<SC>,
618    ) -> Proof<SC> {
619        self.engine.prove(pk, proof_input)
620    }
621
622    pub fn prove(
623        &self,
624        pk: &MultiStarkProvingKey<SC>,
625        results: VmExecutorResult<SC>,
626    ) -> Vec<Proof<SC>> {
627        results
628            .per_segment
629            .into_iter()
630            .enumerate()
631            .map(|(seg_idx, proof_input)| {
632                tracing::info_span!("prove_segment", segment = seg_idx)
633                    .in_scope(|| self.engine.prove(pk, proof_input))
634            })
635            .collect()
636    }
637
638    /// Verify segment proofs, checking continuation boundary conditions between segments if VM
639    /// memory is persistent The behavior of this function differs depending on whether
640    /// continuations is enabled or not. We recommend to call the functions [`verify_segments`]
641    /// or [`verify_single`] directly instead.
642    pub fn verify(
643        &self,
644        vk: &MultiStarkVerifyingKey<SC>,
645        proofs: Vec<Proof<SC>>,
646    ) -> Result<(), VmVerificationError>
647    where
648        Val<SC>: PrimeField32,
649        Com<SC>: AsRef<[Val<SC>; CHUNK]> + From<[Val<SC>; CHUNK]>,
650    {
651        if self.config().system().continuation_enabled {
652            verify_segments(&self.engine, vk, &proofs).map(|_| ())
653        } else {
654            assert_eq!(proofs.len(), 1);
655            verify_single(&self.engine, vk, &proofs.into_iter().next().unwrap())
656                .map_err(VmVerificationError::StarkError)
657        }
658    }
659}
660
661/// Verifies a single proof. This should be used for proof of VM without continuations.
662///
663/// ## Note
664/// This function does not check any public values or extract the starting pc or commitment
665/// to the [VmCommittedExe].
666pub fn verify_single<SC, E>(
667    engine: &E,
668    vk: &MultiStarkVerifyingKey<SC>,
669    proof: &Proof<SC>,
670) -> Result<(), VerificationError>
671where
672    SC: StarkGenericConfig,
673    E: StarkEngine<SC>,
674{
675    engine.verify(vk, proof)
676}
677
678/// The payload of a verified guest VM execution.
679pub struct VerifiedExecutionPayload<F> {
680    /// The Merklelized hash of:
681    /// - Program code commitment (commitment of the cached trace)
682    /// - Merkle root of the initial memory
683    /// - Starting program counter (`pc_start`)
684    ///
685    /// The Merklelization uses Poseidon2 as a cryptographic hash function (for the leaves)
686    /// and a cryptographic compression function (for internal nodes).
687    pub exe_commit: [F; CHUNK],
688    /// The Merkle root of the final memory state.
689    pub final_memory_root: [F; CHUNK],
690}
691
692/// Verify segment proofs with boundary condition checks for continuation between segments.
693///
694/// Assumption:
695/// - `vk` is a valid verifying key of a VM circuit.
696///
697/// Returns:
698/// - The commitment to the [VmCommittedExe] extracted from `proofs`. It is the responsibility of
699///   the caller to check that the returned commitment matches the VM executable that the VM was
700///   supposed to execute.
701/// - The Merkle root of the final memory state.
702///
703/// ## Note
704/// This function does not extract or verify any user public values from the final memory state.
705/// This verification requires an additional Merkle proof with respect to the Merkle root of
706/// the final memory state.
707// @dev: This function doesn't need to be generic in `VC`.
708pub fn verify_segments<SC, E>(
709    engine: &E,
710    vk: &MultiStarkVerifyingKey<SC>,
711    proofs: &[Proof<SC>],
712) -> Result<VerifiedExecutionPayload<Val<SC>>, VmVerificationError>
713where
714    SC: StarkGenericConfig,
715    E: StarkEngine<SC>,
716    Val<SC>: PrimeField32,
717    Com<SC>: AsRef<[Val<SC>; CHUNK]>,
718{
719    if proofs.is_empty() {
720        return Err(VmVerificationError::ProofNotFound);
721    }
722    let mut prev_final_memory_root = None;
723    let mut prev_final_pc = None;
724    let mut start_pc = None;
725    let mut initial_memory_root = None;
726    let mut program_commit = None;
727
728    for (i, proof) in proofs.iter().enumerate() {
729        let res = engine.verify(vk, proof);
730        match res {
731            Ok(_) => (),
732            Err(e) => return Err(VmVerificationError::StarkError(e)),
733        };
734
735        let mut program_air_present = false;
736        let mut connector_air_present = false;
737        let mut merkle_air_present = false;
738
739        // Check public values.
740        for air_proof_data in proof.per_air.iter() {
741            let pvs = &air_proof_data.public_values;
742            let air_vk = &vk.inner.per_air[air_proof_data.air_id];
743            if air_proof_data.air_id == PROGRAM_AIR_ID {
744                program_air_present = true;
745                if i == 0 {
746                    program_commit =
747                        Some(proof.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref());
748                } else if program_commit.unwrap()
749                    != proof.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref()
750                {
751                    return Err(VmVerificationError::ProgramCommitMismatch { index: i });
752                }
753            } else if air_proof_data.air_id == CONNECTOR_AIR_ID {
754                connector_air_present = true;
755                let pvs: &VmConnectorPvs<_> = pvs.as_slice().borrow();
756
757                if i != 0 {
758                    // Check initial pc matches the previous final pc.
759                    if pvs.initial_pc != prev_final_pc.unwrap() {
760                        return Err(VmVerificationError::InitialPcMismatch {
761                            initial: pvs.initial_pc.as_canonical_u32(),
762                            prev_final: prev_final_pc.unwrap().as_canonical_u32(),
763                        });
764                    }
765                } else {
766                    start_pc = Some(pvs.initial_pc);
767                }
768                prev_final_pc = Some(pvs.final_pc);
769
770                let expected_is_terminate = i == proofs.len() - 1;
771                if pvs.is_terminate != FieldAlgebra::from_bool(expected_is_terminate) {
772                    return Err(VmVerificationError::IsTerminateMismatch {
773                        expected: expected_is_terminate,
774                        actual: pvs.is_terminate.as_canonical_u32() != 0,
775                    });
776                }
777
778                let expected_exit_code = if expected_is_terminate {
779                    ExitCode::Success as u32
780                } else {
781                    DEFAULT_SUSPEND_EXIT_CODE
782                };
783                if pvs.exit_code != FieldAlgebra::from_canonical_u32(expected_exit_code) {
784                    return Err(VmVerificationError::ExitCodeMismatch {
785                        expected: expected_exit_code,
786                        actual: pvs.exit_code.as_canonical_u32(),
787                    });
788                }
789            } else if air_proof_data.air_id == MERKLE_AIR_ID {
790                merkle_air_present = true;
791                let pvs: &MemoryMerklePvs<_, CHUNK> = pvs.as_slice().borrow();
792
793                // Check that initial root matches the previous final root.
794                if i != 0 {
795                    if pvs.initial_root != prev_final_memory_root.unwrap() {
796                        return Err(VmVerificationError::InitialMemoryRootMismatch);
797                    }
798                } else {
799                    initial_memory_root = Some(pvs.initial_root);
800                }
801                prev_final_memory_root = Some(pvs.final_root);
802            } else {
803                if !pvs.is_empty() {
804                    return Err(VmVerificationError::UnexpectedPvs {
805                        expected: 0,
806                        actual: pvs.len(),
807                    });
808                }
809                // We assume the vk is valid, so this is only a debug assert.
810                debug_assert_eq!(air_vk.params.num_public_values, 0);
811            }
812        }
813        if !program_air_present {
814            return Err(VmVerificationError::SystemAirMissing {
815                air_id: PROGRAM_AIR_ID,
816            });
817        }
818        if !connector_air_present {
819            return Err(VmVerificationError::SystemAirMissing {
820                air_id: CONNECTOR_AIR_ID,
821            });
822        }
823        if !merkle_air_present {
824            return Err(VmVerificationError::SystemAirMissing {
825                air_id: MERKLE_AIR_ID,
826            });
827        }
828    }
829    let exe_commit = compute_exe_commit(
830        &vm_poseidon2_hasher(),
831        program_commit.unwrap(),
832        initial_memory_root.as_ref().unwrap(),
833        start_pc.unwrap(),
834    );
835    Ok(VerifiedExecutionPayload {
836        exe_commit,
837        final_memory_root: prev_final_memory_root.unwrap(),
838    })
839}
840
841#[derive(Serialize, Deserialize)]
842#[serde(bound(
843    serialize = "Com<SC>: Serialize",
844    deserialize = "Com<SC>: Deserialize<'de>"
845))]
846pub struct ContinuationVmProof<SC: StarkGenericConfig> {
847    pub per_segment: Vec<Proof<SC>>,
848    pub user_public_values: UserPublicValuesProof<{ CHUNK }, Val<SC>>,
849}
850
851impl<SC: StarkGenericConfig> Clone for ContinuationVmProof<SC>
852where
853    Com<SC>: Clone,
854{
855    fn clone(&self) -> Self {
856        Self {
857            per_segment: self.per_segment.clone(),
858            user_public_values: self.user_public_values.clone(),
859        }
860    }
861}