Skip to main content

miden_verifier/
lib.rs

1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use alloc::boxed::Box;
9
10use miden_air::{MidenMultiAir, PublicInputs, Statement, config, security};
11use miden_core::{
12    Felt,
13    deferred::{DeferredRoot, MAX_PRECOMPILE_ROOTS, TRUE_DIGEST, fold_deferred_root},
14    field::QuadFelt,
15    proof::{CURRENT_PVM_VERIFIER_ROOT, CURRENT_VM_VERIFIER_ROOT, MAX_STARK_PROOF_BYTES},
16};
17use miden_crypto::stark::{
18    StarkConfig, VerifierInstance, lmcs::Lmcs, proof::StarkProofData, verifier::VerifierError,
19};
20use miden_serde_utils::deserialize_schema_exact;
21use serde::de::DeserializeOwned;
22use serde_wincode::{SerdeCompat, wincode};
23
24// RE-EXPORTS
25// ================================================================================================
26mod exports {
27    pub use miden_core::{
28        Word,
29        program::{ExecutionClaim, KernelDescriptor, ProgramInfo, StackInputs, StackOutputs},
30        proof::{
31            ExecutionProof, ExecutionProofCompatibility, ExecutionProofCompatibilityError,
32            HashFunction, PrecompileProof, PrecompileStatus, StarkProof, VmProof,
33        },
34    };
35    pub mod math {
36        pub use miden_core::Felt;
37    }
38}
39pub use exports::*;
40pub use miden_air::security::{
41    AirShape, InstanceShape, LookupShape, ProofSecurityParameters, ProtocolParams, SecurityReport,
42    SecurityTerm,
43};
44
45pub mod recursive;
46
47struct VerifierSupport {
48    format: u8,
49    accepted_vm_roots: &'static [Word],
50    accepted_pvm_roots: &'static [Word],
51}
52
53impl VerifierSupport {
54    fn check(&self, proof: &ExecutionProof) -> Result<(), VerificationError> {
55        let compatibility = proof.compatibility();
56        if compatibility.format() != self.format {
57            return Err(VerificationError::UnsupportedProofFormat(compatibility.format()));
58        }
59        if !roots_overlap(compatibility.vm_verifier_roots(), self.accepted_vm_roots) {
60            return Err(VerificationError::IncompatibleVmVerifier);
61        }
62        if !roots_overlap(compatibility.pvm_verifier_roots(), self.accepted_pvm_roots) {
63            return Err(VerificationError::IncompatiblePvmVerifier);
64        }
65
66        Ok(())
67    }
68}
69
70const VERIFIER_SUPPORT_V1: VerifierSupport = VerifierSupport {
71    format: ExecutionProofCompatibility::FORMAT_V1,
72    accepted_vm_roots: &[CURRENT_VM_VERIFIER_ROOT],
73    accepted_pvm_roots: &[CURRENT_PVM_VERIFIER_ROOT],
74};
75
76// VERIFIER
77// ================================================================================================
78
79/// Verifier for deferred and complete Miden execution proofs.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct Verifier;
82
83impl Verifier {
84    /// Creates a verifier with the canonical verification limits.
85    pub const fn new() -> Self {
86        Self
87    }
88
89    /// Returns the compatibility declared by proofs produced by the current prover.
90    pub fn proof_compatibility() -> ExecutionProofCompatibility {
91        ExecutionProofCompatibility::current()
92    }
93
94    /// Verifies a deferred or complete versioned execution proof against its public claim.
95    ///
96    /// The VM STARK authenticates the carried precompile root in either state. For a deferred
97    /// proof, the verifier does not inspect the carried `DeferredStateWire`; it verifies the VM
98    /// STARK and returns the authenticated root as an outstanding obligation. The wire is
99    /// prover-side data and is validated separately when converted into a precompile witness.
100    /// Complete proofs that contain precompile work additionally verify the aggregate precompile
101    /// STARK against the VM-authenticated root.
102    ///
103    /// The outcome reports the authenticated security parameters of the components actually
104    /// verified and any precompile root that remains outstanding. Callers can use
105    /// [`ProofSecurityParameters::conjectured_security_level`] to estimate each verified proof's
106    /// conjectured security level, then apply their own acceptance policy.
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if the proof structure is invalid or a required STARK rejects.
111    pub fn verify(
112        &self,
113        claim: &ExecutionClaim,
114        proof: &ExecutionProof,
115    ) -> Result<VerificationOutcome, VerificationError> {
116        match proof.compatibility().format() {
117            ExecutionProofCompatibility::FORMAT_V1 => {
118                VERIFIER_SUPPORT_V1.check(proof)?;
119                self.verify_v1(claim, proof)
120            },
121            format => Err(VerificationError::UnsupportedProofFormat(format)),
122        }
123    }
124
125    /// Verifies an execution proof encoded with transport format 1.
126    fn verify_v1(
127        &self,
128        claim: &ExecutionClaim,
129        proof: &ExecutionProof,
130    ) -> Result<VerificationOutcome, VerificationError> {
131        let vm = proof.vm();
132        let (outstanding_root, precompile) = match proof.precompile() {
133            PrecompileStatus::Deferred(_) => {
134                let root = vm.precompile_root;
135                if root == TRUE_DIGEST {
136                    return Err(VerificationError::DeferredTrueRoot);
137                }
138                (Some(root), None)
139            },
140            PrecompileStatus::Empty => {
141                let vm_root = vm.precompile_root;
142                if vm_root != TRUE_DIGEST {
143                    return Err(VerificationError::MissingPrecompileProof);
144                }
145                (None, None)
146            },
147            PrecompileStatus::Proven(precompile) => {
148                self.validate_precompile(precompile, vm.precompile_root)?;
149                (None, Some(precompile))
150            },
151        };
152
153        self.preflight_vm_stark(claim, vm)?;
154        if let Some(precompile) = precompile {
155            self.preflight_precompile_stark(precompile)?;
156        }
157
158        let vm_security_parameters = self.verify_vm(claim, vm)?;
159        let precompile_security_parameters = precompile
160            .map(|precompile| self.verify_precompile(precompile, vm.precompile_root))
161            .transpose()?;
162
163        Ok(VerificationOutcome::new(
164            vm_security_parameters,
165            precompile_security_parameters,
166            outstanding_root,
167        ))
168    }
169
170    /// Verifies a precompile proof against an expected outstanding execution root.
171    ///
172    /// The expected root may occur anywhere in the proof's ordered constituent roots. All roots,
173    /// including compatible extras and duplicate occurrences, are folded from the first root to
174    /// derive the aggregate precompile STARK statement. On success, this returns the precompile
175    /// STARK's authenticated security parameters.
176    ///
177    /// The expected root and every constituent root must differ from [`TRUE_DIGEST`].
178    ///
179    /// # Errors
180    ///
181    /// Returns an error if the artifact shape or expected-root coverage is invalid, or if the
182    /// precompile STARK rejects.
183    pub fn verify_precompile(
184        &self,
185        proof: &PrecompileProof,
186        expected_root: DeferredRoot,
187    ) -> Result<ProofSecurityParameters, VerificationError> {
188        self.validate_precompile(proof, expected_root)?;
189        self.preflight_precompile_stark(proof)?;
190
191        let aggregate_root = proof
192            .roots
193            .iter()
194            .copied()
195            .reduce(fold_deferred_root)
196            .expect("precompile roots were checked to be non-empty");
197        Ok(miden_precompiles_verifier::verify_deferred(&proof.proof, aggregate_root)?)
198    }
199
200    fn validate_precompile(
201        &self,
202        proof: &PrecompileProof,
203        expected_root: DeferredRoot,
204    ) -> Result<(), VerificationError> {
205        let roots = &proof.roots;
206        if roots.is_empty() {
207            return Err(VerificationError::EmptyPrecompileRoots);
208        }
209        if roots.len() > MAX_PRECOMPILE_ROOTS {
210            return Err(VerificationError::TooManyPrecompileRoots {
211                roots: roots.len(),
212                max: MAX_PRECOMPILE_ROOTS,
213            });
214        }
215        if let Some(index) = roots.iter().position(|root| *root == TRUE_DIGEST) {
216            return Err(VerificationError::SettledPrecompileRoot { index });
217        }
218        if expected_root == TRUE_DIGEST {
219            return Err(VerificationError::UnexpectedPrecompileProof);
220        }
221        if !roots.contains(&expected_root) {
222            return Err(VerificationError::InsufficientPrecompileRootCoverage);
223        }
224
225        Ok(())
226    }
227
228    fn preflight_vm_stark(
229        &self,
230        claim: &ExecutionClaim,
231        proof: &VmProof,
232    ) -> Result<(), VerificationError> {
233        let size = proof.proof.bytes().len();
234        if size > MAX_STARK_PROOF_BYTES {
235            return Err(VerificationError::StarkVerificationError(
236                claim.program_root(),
237                Box::new(StarkVerificationError::ProofTooLarge {
238                    size,
239                    max: MAX_STARK_PROOF_BYTES,
240                }),
241            ));
242        }
243        Ok(())
244    }
245
246    fn preflight_precompile_stark(&self, proof: &PrecompileProof) -> Result<(), VerificationError> {
247        let size = proof.proof.bytes().len();
248        if size > MAX_STARK_PROOF_BYTES {
249            return Err(VerificationError::PrecompileStarkVerification(
250                miden_precompiles_verifier::VerifyError::ProofTooLarge {
251                    size,
252                    max: MAX_STARK_PROOF_BYTES,
253                },
254            ));
255        }
256        Ok(())
257    }
258
259    /// Verifies the Miden VM STARK proof and returns its authenticated security parameters.
260    ///
261    /// The returned parameters include the largest AIR trace height, the DEEP term count implied
262    /// by the commitment scheme's column alignment, and the lookup boundary terms implied by the
263    /// authenticated kernel. The PCS parameters and commitment collision resistance come from the
264    /// configuration used to verify the proof.
265    fn verify_vm(
266        &self,
267        claim: &ExecutionClaim,
268        proof: &VmProof,
269    ) -> Result<ProofSecurityParameters, VerificationError> {
270        let program_root = claim.program_root();
271        let pub_inputs = PublicInputs::new(
272            claim.to_program_info(),
273            *claim.stack_inputs(),
274            *claim.stack_outputs(),
275            proof.precompile_root,
276        );
277        let (public_values, aux_inputs) = pub_inputs.to_air_inputs();
278
279        let stark = &proof.proof;
280        let proof_bytes = stark.bytes();
281        let pcs_params = config::pcs_params();
282        let num_kernel_procedures = claim.kernel().proc_hashes().len() as u32;
283        match stark.hash_fn() {
284            HashFunction::Blake3_256 => {
285                let config = config::blake3_256_config(pcs_params, config::RELATION_DIGEST);
286                self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
287            },
288            HashFunction::Rpo256 => {
289                let config = config::rpo_config(pcs_params, config::RELATION_DIGEST);
290                self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
291            },
292            HashFunction::Rpx256 => {
293                let config = config::rpx_config(pcs_params, config::RELATION_DIGEST);
294                self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
295            },
296            HashFunction::Poseidon2 => {
297                let config = config::poseidon2_config(pcs_params, config::RELATION_DIGEST);
298                self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
299            },
300            HashFunction::Keccak => {
301                let config = config::keccak_config(pcs_params, config::RELATION_DIGEST);
302                self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
303            },
304        }
305        .map_err(|error| VerificationError::StarkVerificationError(program_root, Box::new(error)))
306        .map(|(log_max_height, alignment)| {
307            security::proof_security_parameters(
308                &pcs_params,
309                log_max_height,
310                num_kernel_procedures,
311                alignment,
312                stark.hash_fn().collision_resistance(),
313            )
314        })
315    }
316
317    /// Verifies a multi-AIR STARK proof for the Miden VM statement, returning the proof's largest
318    /// AIR log height and the LMCS's column alignment for grading.
319    ///
320    /// Pre-seeds the challenger with protocol parameters, AIR public values, and statement
321    /// `aux_inputs` (program hash, final deferred root, and kernel-procedure digests). Then
322    /// delegates to the lifted multi-AIR verifier.
323    fn verify_stark_proof<SC>(
324        &self,
325        config: &SC,
326        public_values: &[Felt],
327        aux_inputs: &[Felt],
328        proof_bytes: &[u8],
329    ) -> Result<(u32, usize), StarkVerificationError>
330    where
331        SC: StarkConfig<Felt, QuadFelt>,
332        <SC::Lmcs as Lmcs>::Commitment: DeserializeOwned,
333    {
334        if proof_bytes.len() > MAX_STARK_PROOF_BYTES {
335            return Err(StarkVerificationError::ProofTooLarge {
336                size: proof_bytes.len(),
337                max: MAX_STARK_PROOF_BYTES,
338            });
339        }
340
341        let proof_encoding_config = wincode::config::Configuration::default()
342            .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
343        let proof = deserialize_schema_exact::<SerdeCompat<StarkProofData<Felt, QuadFelt, SC>>, _>(
344            proof_bytes,
345            proof_encoding_config,
346        )?;
347
348        let mut challenger = config.challenger();
349        config::observe_protocol_params(config.pcs(), &mut challenger);
350
351        // `air_inputs` are the public values read by the AIRs (stack i/o); `aux_inputs` are the
352        // statement inputs read during observation/boundary correction. The lifted verifier absorbs
353        // both into Fiat-Shamir internally, and derives the multi-AIR ordering deterministically
354        // from the proof's per-AIR trace heights.
355        let statement = Statement::<Felt, QuadFelt, _>::new(
356            MidenMultiAir::new(),
357            public_values.to_vec(),
358            aux_inputs.to_vec(),
359        )
360        .map_err(|error| StarkVerificationError::Verifier(VerifierError::from(error)))?;
361
362        VerifierInstance::new(config, &statement, None)
363            .expect("Miden AIRs declare no preprocessed columns")
364            .verify(&proof, challenger)?;
365
366        let log_max_height =
367            u32::from(proof.log_trace_heights().iter().copied().max().unwrap_or(0));
368        Ok((log_max_height, config.lmcs().alignment()))
369    }
370}
371
372impl Default for Verifier {
373    fn default() -> Self {
374        Self::new()
375    }
376}
377
378/// Result of fully verifying an execution proof and all supplied STARKs.
379#[must_use = "verification may leave an outstanding precompile obligation"]
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub struct VerificationOutcome {
382    vm_security_parameters: ProofSecurityParameters,
383    precompile_security_parameters: Option<ProofSecurityParameters>,
384    outstanding_precompile_root: Option<DeferredRoot>,
385}
386
387impl VerificationOutcome {
388    const fn new(
389        vm_security_parameters: ProofSecurityParameters,
390        precompile_security_parameters: Option<ProofSecurityParameters>,
391        outstanding_precompile_root: Option<DeferredRoot>,
392    ) -> Self {
393        Self {
394            vm_security_parameters,
395            precompile_security_parameters,
396            outstanding_precompile_root,
397        }
398    }
399
400    /// Returns the authenticated security parameters of the verified MVM proof.
401    pub const fn vm_security_parameters(&self) -> &ProofSecurityParameters {
402        &self.vm_security_parameters
403    }
404
405    /// Returns the authenticated security parameters if verification included a PVM proof.
406    pub const fn precompile_security_parameters(&self) -> Option<&ProofSecurityParameters> {
407        self.precompile_security_parameters.as_ref()
408    }
409
410    /// Returns whether this verified outcome has no outstanding precompile obligation.
411    ///
412    /// This result is produced only after verifier-owned shape validation and verification of every
413    /// required STARK.
414    pub const fn is_complete(&self) -> bool {
415        self.outstanding_precompile_root.is_none()
416    }
417
418    /// Returns the authenticated precompile root that remains to be proved, if any.
419    pub const fn outstanding_precompile_root(&self) -> Option<DeferredRoot> {
420        self.outstanding_precompile_root
421    }
422}
423
424// ERRORS
425// ================================================================================================
426
427/// Errors that can occur during proof verification.
428#[derive(Debug, thiserror::Error)]
429pub enum VerificationError {
430    #[error("execution proof format {0} is not supported")]
431    UnsupportedProofFormat(u8),
432    #[error("execution proof does not name a compatible VM verifier")]
433    IncompatibleVmVerifier,
434    #[error("execution proof does not name a compatible PVM verifier")]
435    IncompatiblePvmVerifier,
436    #[error("failed to verify VM STARK proof for program with hash {0}")]
437    StarkVerificationError(Word, #[source] Box<StarkVerificationError>),
438    #[error("a deferred execution proof cannot authenticate TRUE_DIGEST")]
439    DeferredTrueRoot,
440    #[error("a precompile proof must contain at least one constituent root")]
441    EmptyPrecompileRoots,
442    #[error("precompile proof contains too many roots: found {roots}, maximum is {max}")]
443    TooManyPrecompileRoots { roots: usize, max: usize },
444    #[error("precompile proof constituent root at index {index} is already settled")]
445    SettledPrecompileRoot { index: usize },
446    #[error("a precompile proof was supplied for an already settled VM obligation")]
447    UnexpectedPrecompileProof,
448    #[error("a precompile proof is required for a non-empty VM obligation")]
449    MissingPrecompileProof,
450    #[error("precompile proof roots do not cover the VM obligation")]
451    InsufficientPrecompileRootCoverage,
452    #[error("failed to verify aggregate precompile STARK proof: {0}")]
453    PrecompileStarkVerification(#[from] miden_precompiles_verifier::VerifyError),
454}
455
456/// Errors that can occur during low-level STARK proof verification.
457#[derive(Debug, thiserror::Error)]
458pub enum StarkVerificationError {
459    #[error("failed to deserialize proof: {0}")]
460    Deserialization(#[from] wincode::error::ReadError),
461    #[error("STARK proof is too large: {size} bytes exceeds the {max} byte limit")]
462    ProofTooLarge { size: usize, max: usize },
463    #[error(transparent)]
464    Verifier(#[from] VerifierError),
465}
466
467// HELPER FUNCTIONS
468// ================================================================================================
469
470fn roots_overlap(proof_roots: &[Word], accepted_roots: &[Word]) -> bool {
471    proof_roots.iter().any(|root| accepted_roots.contains(root))
472}
473
474// TESTS
475// ================================================================================================
476
477#[cfg(test)]
478mod tests {
479    use alloc::{vec, vec::Vec};
480
481    use miden_core::deferred::DeferredStateWire;
482
483    use super::*;
484
485    fn claim() -> ExecutionClaim {
486        ExecutionClaim::from_program_info(
487            ProgramInfo::default(),
488            StackInputs::default(),
489            StackOutputs::default(),
490        )
491    }
492
493    fn root(value: u64) -> Word {
494        [
495            Felt::new(value).unwrap(),
496            Felt::new(0).unwrap(),
497            Felt::new(0).unwrap(),
498            Felt::new(0).unwrap(),
499        ]
500        .into()
501    }
502
503    fn vm_proof(precompile_root: Word) -> VmProof {
504        VmProof {
505            proof: StarkProof::new(vec![0, 0], HashFunction::Blake3_256),
506            precompile_root,
507        }
508    }
509
510    fn precompile_proof(roots: Vec<Word>) -> PrecompileProof {
511        PrecompileProof {
512            proof: StarkProof::new(vec![0, 0], HashFunction::Poseidon2),
513            roots,
514        }
515    }
516
517    fn complete(vm_root: Word, roots: Option<Vec<Word>>) -> ExecutionProof {
518        let precompile = match roots {
519            Some(roots) => PrecompileStatus::Proven(precompile_proof(roots)),
520            None => PrecompileStatus::Empty,
521        };
522        ExecutionProof::new(vm_proof(vm_root), precompile)
523    }
524
525    #[test]
526    fn verifier_owns_shape_policy() {
527        type CheckError = fn(VerificationError) -> bool;
528
529        let required = root(1);
530        let cases: Vec<(ExecutionProof, CheckError)> = vec![
531            (
532                ExecutionProof::new(
533                    vm_proof(TRUE_DIGEST),
534                    PrecompileStatus::Deferred(DeferredStateWire::default()),
535                ),
536                |error| matches!(error, VerificationError::DeferredTrueRoot),
537            ),
538            (complete(required, Some(vec![])), |error| {
539                matches!(error, VerificationError::EmptyPrecompileRoots)
540            }),
541            (complete(required, Some(vec![required; MAX_PRECOMPILE_ROOTS + 1])), |error| {
542                matches!(
543                    error,
544                    VerificationError::TooManyPrecompileRoots { roots, max }
545                        if roots == MAX_PRECOMPILE_ROOTS + 1 && max == MAX_PRECOMPILE_ROOTS
546                )
547            }),
548            (complete(required, Some(vec![root(2), TRUE_DIGEST, required])), |error| {
549                matches!(error, VerificationError::SettledPrecompileRoot { index: 1 })
550            }),
551            (complete(required, None), |error| {
552                matches!(error, VerificationError::MissingPrecompileProof)
553            }),
554            (complete(TRUE_DIGEST, Some(vec![required])), |error| {
555                matches!(error, VerificationError::UnexpectedPrecompileProof)
556            }),
557            (complete(root(99), Some(vec![required])), |error| {
558                matches!(error, VerificationError::InsufficientPrecompileRootCoverage)
559            }),
560        ];
561
562        for (proof, check) in cases {
563            let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
564            assert!(check(error));
565        }
566    }
567
568    #[test]
569    fn precompile_verifier_owns_artifact_shape_policy() {
570        type CheckError = fn(VerificationError) -> bool;
571
572        let required = root(1);
573        let cases: Vec<(PrecompileProof, Word, CheckError)> = vec![
574            (precompile_proof(vec![]), required, |error| {
575                matches!(error, VerificationError::EmptyPrecompileRoots)
576            }),
577            (precompile_proof(vec![required; MAX_PRECOMPILE_ROOTS + 1]), required, |error| {
578                matches!(
579                    error,
580                    VerificationError::TooManyPrecompileRoots { roots, max }
581                        if roots == MAX_PRECOMPILE_ROOTS + 1 && max == MAX_PRECOMPILE_ROOTS
582                )
583            }),
584            (precompile_proof(vec![required, TRUE_DIGEST]), required, |error| {
585                matches!(error, VerificationError::SettledPrecompileRoot { index: 1 })
586            }),
587            (precompile_proof(vec![required]), TRUE_DIGEST, |error| {
588                matches!(error, VerificationError::UnexpectedPrecompileProof)
589            }),
590            (precompile_proof(vec![required]), root(99), |error| {
591                matches!(error, VerificationError::InsufficientPrecompileRootCoverage)
592            }),
593        ];
594
595        for (proof, expected_root, check) in cases {
596            let error = Verifier::new().verify_precompile(&proof, expected_root).unwrap_err();
597            assert!(check(error));
598        }
599    }
600
601    #[test]
602    fn oversized_precompile_stark_is_rejected_before_vm_stark_verification() {
603        let required = root(1);
604        let proof = ExecutionProof::new(
605            vm_proof(required),
606            PrecompileStatus::Proven(PrecompileProof {
607                proof: StarkProof::new(vec![0; MAX_STARK_PROOF_BYTES + 1], HashFunction::Poseidon2),
608                roots: vec![required],
609            }),
610        );
611
612        let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
613        assert!(matches!(
614            error,
615            VerificationError::PrecompileStarkVerification(
616                miden_precompiles_verifier::VerifyError::ProofTooLarge { size, max }
617            ) if size == MAX_STARK_PROOF_BYTES + 1 && max == MAX_STARK_PROOF_BYTES
618        ));
619    }
620
621    #[test]
622    fn malformed_transport_round_trips_then_verifier_rejects_it() {
623        let malformed = complete(root(1), Some(vec![]));
624        let bytes = malformed.to_bytes();
625        let decoded = ExecutionProof::read_from_bytes(&bytes).unwrap();
626
627        assert_eq!(decoded.to_bytes(), bytes);
628        assert!(matches!(
629            Verifier::new().verify(&claim(), &decoded),
630            Err(VerificationError::EmptyPrecompileRoots)
631        ));
632    }
633
634    #[test]
635    fn verifier_rejects_oversized_directly_constructed_vm_proof() {
636        let proof = ExecutionProof::new(
637            VmProof {
638                proof: StarkProof::new(
639                    vec![0; MAX_STARK_PROOF_BYTES + 1],
640                    HashFunction::Blake3_256,
641                ),
642                precompile_root: TRUE_DIGEST,
643            },
644            PrecompileStatus::Empty,
645        );
646
647        let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
648        let VerificationError::StarkVerificationError(_, source) = error else {
649            panic!("expected oversized VM STARK proof to be rejected")
650        };
651        assert!(matches!(
652            *source,
653            StarkVerificationError::ProofTooLarge {
654                size,
655                max: MAX_STARK_PROOF_BYTES,
656            } if size == MAX_STARK_PROOF_BYTES + 1
657        ));
658    }
659
660    #[test]
661    fn ordered_root_coverage_reaches_vm_stark_verification() {
662        let vm_root = root(2);
663        let proof = complete(vm_root, Some(vec![root(1), vm_root, root(3)]));
664
665        let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
666        assert!(matches!(error, VerificationError::StarkVerificationError(..)));
667    }
668
669    #[test]
670    fn verifier_requires_compatible_vm_and_pvm_roots() {
671        let proof = complete(TRUE_DIGEST, None);
672        let incompatible_vm = ExecutionProof::from_parts(
673            ExecutionProofCompatibility::new(
674                vec![root(100)],
675                VERIFIER_SUPPORT_V1.accepted_pvm_roots.to_vec(),
676            )
677            .unwrap(),
678            proof.vm().clone(),
679            proof.precompile().clone(),
680        );
681        let incompatible_pvm = ExecutionProof::from_parts(
682            ExecutionProofCompatibility::new(
683                VERIFIER_SUPPORT_V1.accepted_vm_roots.to_vec(),
684                vec![root(200)],
685            )
686            .unwrap(),
687            proof.vm().clone(),
688            proof.precompile().clone(),
689        );
690
691        assert!(matches!(
692            Verifier::new().verify(&claim(), &incompatible_vm),
693            Err(VerificationError::IncompatibleVmVerifier)
694        ));
695        assert!(matches!(
696            Verifier::new().verify(&claim(), &incompatible_pvm),
697            Err(VerificationError::IncompatiblePvmVerifier)
698        ));
699    }
700
701    #[test]
702    fn current_proof_compatibility_excludes_verifier_history() {
703        const OLD_VM_ROOT: Word = Word::new([
704            Felt::new_unchecked(1),
705            Felt::new_unchecked(0),
706            Felt::new_unchecked(0),
707            Felt::new_unchecked(0),
708        ]);
709        const OLD_PVM_ROOT: Word = Word::new([
710            Felt::new_unchecked(2),
711            Felt::new_unchecked(0),
712            Felt::new_unchecked(0),
713            Felt::new_unchecked(0),
714        ]);
715        const SUPPORT: VerifierSupport = VerifierSupport {
716            format: ExecutionProofCompatibility::FORMAT_V1,
717            accepted_vm_roots: &[OLD_VM_ROOT, CURRENT_VM_VERIFIER_ROOT],
718            accepted_pvm_roots: &[OLD_PVM_ROOT, CURRENT_PVM_VERIFIER_ROOT],
719        };
720
721        let proof = complete(TRUE_DIGEST, None);
722
723        assert_eq!(proof.compatibility().vm_verifier_roots(), &[CURRENT_VM_VERIFIER_ROOT]);
724        assert_eq!(proof.compatibility().pvm_verifier_roots(), &[CURRENT_PVM_VERIFIER_ROOT]);
725
726        let old_compatible = ExecutionProof::from_parts(
727            ExecutionProofCompatibility::new(vec![OLD_VM_ROOT], vec![OLD_PVM_ROOT]).unwrap(),
728            proof.vm().clone(),
729            proof.precompile().clone(),
730        );
731        assert!(SUPPORT.check(&old_compatible).is_ok());
732    }
733}