1#![no_std]
2
3extern crate alloc;
4
5#[cfg(feature = "std")]
6extern crate std;
7
8use alloc::{boxed::Box, sync::Arc};
9
10use miden_air::{MidenMultiAir, PublicInputs, Statement, config, security};
11use miden_core::{
12 Felt,
13 deferred::{
14 DeferredRoot, MAX_PRECOMPILE_ROOTS, PrecompileError, PrecompileRegistry, TRUE_DIGEST,
15 },
16 field::QuadFelt,
17 proof::{CURRENT_PVM_VERIFIER_ROOT, CURRENT_VM_VERIFIER_ROOT, MAX_STARK_PROOF_BYTES},
18};
19use miden_crypto::stark::{
20 StarkConfig, VerifierInstance, lmcs::Lmcs, proof::StarkProofData, verifier::VerifierError,
21};
22use miden_serde_utils::deserialize_schema_exact;
23use serde::de::DeserializeOwned;
24use serde_wincode::{SerdeCompat, wincode};
25
26mod exports {
29 pub use miden_core::{
30 Word,
31 program::{ExecutionClaim, KernelDescriptor, ProgramInfo, StackInputs, StackOutputs},
32 proof::{
33 ExecutionProof, ExecutionProofCompatibility, ExecutionProofCompatibilityError,
34 HashFunction, PrecompileProof, PrecompileStatus, StarkProof, VmProof,
35 },
36 };
37 pub mod math {
38 pub use miden_core::Felt;
39 }
40}
41pub use exports::*;
42pub use miden_air::security::{
43 AirShape, InstanceShape, LookupShape, ProofSecurityParameters, ProtocolParams, SecurityReport,
44 SecurityTerm,
45};
46
47pub mod recursive;
48
49struct VerifierSupport {
50 format: u8,
51 accepted_vm_roots: &'static [Word],
52 accepted_pvm_roots: &'static [Word],
53}
54
55impl VerifierSupport {
56 fn check(&self, proof: &ExecutionProof) -> Result<(), VerificationError> {
57 let compatibility = proof.compatibility();
58 if compatibility.format() != self.format {
59 return Err(VerificationError::UnsupportedProofFormat(compatibility.format()));
60 }
61 if !roots_overlap(compatibility.vm_verifier_roots(), self.accepted_vm_roots) {
62 return Err(VerificationError::IncompatibleVmVerifier);
63 }
64 if !roots_overlap(compatibility.pvm_verifier_roots(), self.accepted_pvm_roots) {
65 return Err(VerificationError::IncompatiblePvmVerifier);
66 }
67
68 Ok(())
69 }
70}
71
72const VERIFIER_SUPPORT_V2: VerifierSupport = VerifierSupport {
73 format: ExecutionProofCompatibility::FORMAT_V2,
74 accepted_vm_roots: &[CURRENT_VM_VERIFIER_ROOT],
75 accepted_pvm_roots: &[CURRENT_PVM_VERIFIER_ROOT],
76};
77
78#[derive(Debug, Clone)]
83pub struct Verifier {
84 precompile_registry: Arc<PrecompileRegistry>,
85 min_security_level: Option<u32>,
86}
87
88impl Verifier {
89 pub fn new() -> Self {
92 Self {
93 precompile_registry: Arc::new(miden_precompiles::registry()),
94 min_security_level: None,
95 }
96 }
97
98 pub fn with_min_conjectured_security_level_per_stark(
105 mut self,
106 min_security_level: u32,
107 ) -> Self {
108 self.min_security_level = Some(min_security_level);
109 self
110 }
111
112 pub fn proof_compatibility() -> ExecutionProofCompatibility {
114 ExecutionProofCompatibility::current()
115 }
116
117 pub fn verify(
138 &self,
139 claim: &ExecutionClaim,
140 proof: &ExecutionProof,
141 ) -> Result<VerificationOutcome, VerificationError> {
142 match proof.compatibility().format() {
143 ExecutionProofCompatibility::FORMAT_V2 => {
144 VERIFIER_SUPPORT_V2.check(proof)?;
145 self.verify_v2(claim, proof)
146 },
147 format => Err(VerificationError::UnsupportedProofFormat(format)),
148 }
149 }
150
151 fn verify_v2(
153 &self,
154 claim: &ExecutionClaim,
155 proof: &ExecutionProof,
156 ) -> Result<VerificationOutcome, VerificationError> {
157 let vm = proof.vm();
158 let (outstanding_root, precompile) = match proof.precompile() {
159 PrecompileStatus::Deferred(_) => {
160 let root = vm.precompile_root;
161 if root == TRUE_DIGEST {
162 return Err(VerificationError::DeferredTrueRoot);
163 }
164 (Some(root), None)
165 },
166 PrecompileStatus::Empty => {
167 let vm_root = vm.precompile_root;
168 if vm_root != TRUE_DIGEST {
169 return Err(VerificationError::MissingPrecompileProof);
170 }
171 (None, None)
172 },
173 PrecompileStatus::Proven(precompile) => {
174 self.validate_precompile(precompile, vm.precompile_root)?;
175 (None, Some(precompile))
176 },
177 };
178
179 self.preflight_vm_stark(claim, vm)?;
180 if let Some(precompile) = precompile {
181 self.preflight_precompile_stark(precompile)?;
182 }
183
184 let vm_security_parameters = self.verify_vm(claim, vm)?;
185 self.check_security_level(&vm_security_parameters)?;
186 if let PrecompileStatus::Deferred(witness) = proof.precompile()
189 && (witness.root_unchecked() != vm.precompile_root
190 || witness.compute_root(Arc::clone(&self.precompile_registry))?
191 != vm.precompile_root)
192 {
193 return Err(VerificationError::DeferredWitnessRootMismatch);
194 }
195 let precompile_security_parameters = precompile
196 .map(|precompile| self.verify_precompile(precompile, vm.precompile_root))
197 .transpose()?;
198
199 Ok(VerificationOutcome::new(
200 vm_security_parameters,
201 precompile_security_parameters,
202 outstanding_root,
203 ))
204 }
205
206 pub fn verify_precompile(
221 &self,
222 proof: &PrecompileProof,
223 expected_root: DeferredRoot,
224 ) -> Result<ProofSecurityParameters, VerificationError> {
225 self.validate_precompile(proof, expected_root)?;
226 self.preflight_precompile_stark(proof)?;
227
228 let aggregate_root =
229 proof.aggregate_root().expect("precompile roots were checked to be non-empty");
230 let security_parameters =
231 miden_precompiles_verifier::verify_deferred(&proof.proof, aggregate_root)?;
232 self.check_security_level(&security_parameters)?;
233 Ok(security_parameters)
234 }
235
236 fn check_security_level(
237 &self,
238 parameters: &ProofSecurityParameters,
239 ) -> Result<(), VerificationError> {
240 if let Some(required) = self.min_security_level {
241 let actual = parameters.conjectured_security_level();
242 if actual < required {
243 return Err(VerificationError::InsufficientSecurityLevel { actual, required });
244 }
245 }
246 Ok(())
247 }
248
249 fn validate_precompile(
250 &self,
251 proof: &PrecompileProof,
252 expected_root: DeferredRoot,
253 ) -> Result<(), VerificationError> {
254 let roots = &proof.roots;
255 if roots.is_empty() {
256 return Err(VerificationError::EmptyPrecompileRoots);
257 }
258 if roots.len() > MAX_PRECOMPILE_ROOTS {
259 return Err(VerificationError::TooManyPrecompileRoots {
260 roots: roots.len(),
261 max: MAX_PRECOMPILE_ROOTS,
262 });
263 }
264 if let Some(index) = roots.iter().position(|root| *root == TRUE_DIGEST) {
265 return Err(VerificationError::SettledPrecompileRoot { index });
266 }
267 if expected_root == TRUE_DIGEST {
268 return Err(VerificationError::UnexpectedPrecompileProof);
269 }
270 if !roots.contains(&expected_root) {
271 return Err(VerificationError::InsufficientPrecompileRootCoverage);
272 }
273
274 Ok(())
275 }
276
277 fn preflight_vm_stark(
278 &self,
279 claim: &ExecutionClaim,
280 proof: &VmProof,
281 ) -> Result<(), VerificationError> {
282 let size = proof.proof.bytes().len();
283 if size > MAX_STARK_PROOF_BYTES {
284 return Err(VerificationError::StarkVerificationError(
285 claim.program_root(),
286 Box::new(StarkVerificationError::ProofTooLarge {
287 size,
288 max: MAX_STARK_PROOF_BYTES,
289 }),
290 ));
291 }
292 Ok(())
293 }
294
295 fn preflight_precompile_stark(&self, proof: &PrecompileProof) -> Result<(), VerificationError> {
296 let size = proof.proof.bytes().len();
297 if size > MAX_STARK_PROOF_BYTES {
298 return Err(VerificationError::PrecompileStarkVerification(
299 miden_precompiles_verifier::VerifyError::ProofTooLarge {
300 size,
301 max: MAX_STARK_PROOF_BYTES,
302 },
303 ));
304 }
305 Ok(())
306 }
307
308 fn verify_vm(
315 &self,
316 claim: &ExecutionClaim,
317 proof: &VmProof,
318 ) -> Result<ProofSecurityParameters, VerificationError> {
319 let program_root = claim.program_root();
320 let pub_inputs = PublicInputs::new(
321 claim.to_program_info(),
322 *claim.stack_inputs(),
323 *claim.stack_outputs(),
324 proof.precompile_root,
325 );
326 let (public_values, aux_inputs) = pub_inputs.to_air_inputs();
327
328 let stark = &proof.proof;
329 let proof_bytes = stark.bytes();
330 let pcs_params = config::pcs_params();
331 let num_kernel_procedures = claim.kernel().proc_hashes().len() as u32;
332 match stark.hash_fn() {
333 HashFunction::Blake3_256 => {
334 let config = config::blake3_256_config(pcs_params, config::RELATION_DIGEST);
335 self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
336 },
337 HashFunction::Rpo256 => {
338 let config = config::rpo_config(pcs_params, config::RELATION_DIGEST);
339 self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
340 },
341 HashFunction::Rpx256 => {
342 let config = config::rpx_config(pcs_params, config::RELATION_DIGEST);
343 self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
344 },
345 HashFunction::Poseidon2 => {
346 let config = config::poseidon2_config(pcs_params, config::RELATION_DIGEST);
347 self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
348 },
349 HashFunction::Keccak => {
350 let config = config::keccak_config(pcs_params, config::RELATION_DIGEST);
351 self.verify_stark_proof(&config, &public_values, &aux_inputs, proof_bytes)
352 },
353 }
354 .map_err(|error| VerificationError::StarkVerificationError(program_root, Box::new(error)))
355 .map(|(log_max_height, alignment)| {
356 security::proof_security_parameters(
357 &pcs_params,
358 log_max_height,
359 num_kernel_procedures,
360 alignment,
361 stark.hash_fn().collision_resistance(),
362 )
363 })
364 }
365
366 fn verify_stark_proof<SC>(
373 &self,
374 config: &SC,
375 public_values: &[Felt],
376 aux_inputs: &[Felt],
377 proof_bytes: &[u8],
378 ) -> Result<(u32, usize), StarkVerificationError>
379 where
380 SC: StarkConfig<Felt, QuadFelt>,
381 <SC::Lmcs as Lmcs>::Commitment: DeserializeOwned,
382 {
383 if proof_bytes.len() > MAX_STARK_PROOF_BYTES {
384 return Err(StarkVerificationError::ProofTooLarge {
385 size: proof_bytes.len(),
386 max: MAX_STARK_PROOF_BYTES,
387 });
388 }
389
390 let proof_encoding_config = wincode::config::Configuration::default()
391 .with_preallocation_size_limit::<MAX_STARK_PROOF_BYTES>();
392 let proof = deserialize_schema_exact::<SerdeCompat<StarkProofData<Felt, QuadFelt, SC>>, _>(
393 proof_bytes,
394 proof_encoding_config,
395 )?;
396
397 let mut challenger = config.challenger();
398 config::observe_protocol_params(config.pcs(), &mut challenger);
399
400 let statement = Statement::<Felt, QuadFelt, _>::new(
405 MidenMultiAir::new(),
406 public_values.to_vec(),
407 aux_inputs.to_vec(),
408 )
409 .map_err(|error| StarkVerificationError::Verifier(VerifierError::from(error)))?;
410
411 VerifierInstance::new(config, &statement, None)
412 .expect("Miden AIRs declare no preprocessed columns")
413 .verify(&proof, challenger)?;
414
415 let log_max_height =
416 u32::from(proof.log_trace_heights().iter().copied().max().unwrap_or(0));
417 Ok((log_max_height, config.lmcs().alignment()))
418 }
419}
420
421impl Default for Verifier {
422 fn default() -> Self {
423 Self::new()
424 }
425}
426
427#[must_use = "verification may leave an outstanding precompile obligation"]
429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
430pub struct VerificationOutcome {
431 vm_security_parameters: ProofSecurityParameters,
432 precompile_security_parameters: Option<ProofSecurityParameters>,
433 outstanding_precompile_root: Option<DeferredRoot>,
434}
435
436impl VerificationOutcome {
437 const fn new(
438 vm_security_parameters: ProofSecurityParameters,
439 precompile_security_parameters: Option<ProofSecurityParameters>,
440 outstanding_precompile_root: Option<DeferredRoot>,
441 ) -> Self {
442 Self {
443 vm_security_parameters,
444 precompile_security_parameters,
445 outstanding_precompile_root,
446 }
447 }
448
449 pub const fn vm_security_parameters(&self) -> &ProofSecurityParameters {
451 &self.vm_security_parameters
452 }
453
454 pub const fn precompile_security_parameters(&self) -> Option<&ProofSecurityParameters> {
456 self.precompile_security_parameters.as_ref()
457 }
458
459 pub const fn is_complete(&self) -> bool {
464 self.outstanding_precompile_root.is_none()
465 }
466
467 pub const fn outstanding_precompile_root(&self) -> Option<DeferredRoot> {
469 self.outstanding_precompile_root
470 }
471}
472
473#[derive(Debug, thiserror::Error)]
478#[non_exhaustive]
479pub enum VerificationError {
480 #[error("conjectured security level is {actual} bits, below the required {required} bits")]
481 InsufficientSecurityLevel { actual: u32, required: u32 },
482 #[error("execution proof format {0} is not supported")]
483 UnsupportedProofFormat(u8),
484 #[error("execution proof does not name a compatible VM verifier")]
485 IncompatibleVmVerifier,
486 #[error("execution proof does not name a compatible PVM verifier")]
487 IncompatiblePvmVerifier,
488 #[error("failed to verify VM STARK proof for program with hash {0}")]
489 StarkVerificationError(Word, #[source] Box<StarkVerificationError>),
490 #[error("a deferred execution proof cannot authenticate TRUE_DIGEST")]
491 DeferredTrueRoot,
492 #[error("deferred witness root does not match the VM obligation")]
493 DeferredWitnessRootMismatch,
494 #[error("deferred witness evaluation failed: {0}")]
495 DeferredWitnessEvaluation(#[from] PrecompileError),
496 #[error("a precompile proof must contain at least one constituent root")]
497 EmptyPrecompileRoots,
498 #[error("precompile proof contains too many roots: found {roots}, maximum is {max}")]
499 TooManyPrecompileRoots { roots: usize, max: usize },
500 #[error("precompile proof constituent root at index {index} is already settled")]
501 SettledPrecompileRoot { index: usize },
502 #[error("a precompile proof was supplied for an already settled VM obligation")]
503 UnexpectedPrecompileProof,
504 #[error("a precompile proof is required for a non-empty VM obligation")]
505 MissingPrecompileProof,
506 #[error("precompile proof roots do not cover the VM obligation")]
507 InsufficientPrecompileRootCoverage,
508 #[error("failed to verify aggregate precompile STARK proof: {0}")]
509 PrecompileStarkVerification(#[from] miden_precompiles_verifier::VerifyError),
510}
511
512#[derive(Debug, thiserror::Error)]
514pub enum StarkVerificationError {
515 #[error("failed to deserialize proof: {0}")]
516 Deserialization(#[from] wincode::error::ReadError),
517 #[error("STARK proof is too large: {size} bytes exceeds the {max} byte limit")]
518 ProofTooLarge { size: usize, max: usize },
519 #[error(transparent)]
520 Verifier(#[from] VerifierError),
521}
522
523fn roots_overlap(proof_roots: &[Word], accepted_roots: &[Word]) -> bool {
527 proof_roots.iter().any(|root| accepted_roots.contains(root))
528}
529
530#[cfg(test)]
534mod tests {
535 use alloc::{vec, vec::Vec};
536
537 use miden_core::deferred::{PrecompileWitness, PrecompileWitnessEntry, Tag};
538
539 use super::*;
540
541 fn claim() -> ExecutionClaim {
542 ExecutionClaim::from_program_info(
543 ProgramInfo::default(),
544 StackInputs::default(),
545 StackOutputs::default(),
546 )
547 }
548
549 fn root(value: u64) -> Word {
550 [
551 Felt::new(value).unwrap(),
552 Felt::new(0).unwrap(),
553 Felt::new(0).unwrap(),
554 Felt::new(0).unwrap(),
555 ]
556 .into()
557 }
558
559 fn vm_proof(precompile_root: Word) -> VmProof {
560 VmProof {
561 proof: StarkProof::new(vec![0, 0], HashFunction::Blake3_256),
562 precompile_root,
563 }
564 }
565
566 fn precompile_proof(roots: Vec<Word>) -> PrecompileProof {
567 PrecompileProof {
568 proof: StarkProof::new(vec![0, 0], HashFunction::Poseidon2),
569 roots,
570 }
571 }
572
573 fn complete(vm_root: Word, roots: Option<Vec<Word>>) -> ExecutionProof {
574 let precompile = match roots {
575 Some(roots) => PrecompileStatus::Proven(precompile_proof(roots)),
576 None => PrecompileStatus::Empty,
577 };
578 ExecutionProof::new(vm_proof(vm_root), precompile)
579 }
580
581 #[test]
582 fn verifier_owns_shape_policy() {
583 type CheckError = fn(VerificationError) -> bool;
584
585 let required = root(1);
586 let deferred = PrecompileStatus::Deferred(
587 PrecompileWitness::from_entries(vec![PrecompileWitnessEntry::Join {
588 tag: Tag::AND,
589 lhs: 0,
590 rhs: 0,
591 }])
592 .expect("a logged TRUE is a nonempty obligation"),
593 );
594 let cases: Vec<(ExecutionProof, CheckError)> = vec![
595 (ExecutionProof::new(vm_proof(TRUE_DIGEST), deferred), |error| {
596 matches!(error, VerificationError::DeferredTrueRoot)
597 }),
598 (complete(required, Some(vec![])), |error| {
599 matches!(error, VerificationError::EmptyPrecompileRoots)
600 }),
601 (complete(required, Some(vec![required; MAX_PRECOMPILE_ROOTS + 1])), |error| {
602 matches!(
603 error,
604 VerificationError::TooManyPrecompileRoots { roots, max }
605 if roots == MAX_PRECOMPILE_ROOTS + 1 && max == MAX_PRECOMPILE_ROOTS
606 )
607 }),
608 (complete(required, Some(vec![root(2), TRUE_DIGEST, required])), |error| {
609 matches!(error, VerificationError::SettledPrecompileRoot { index: 1 })
610 }),
611 (complete(required, None), |error| {
612 matches!(error, VerificationError::MissingPrecompileProof)
613 }),
614 (complete(TRUE_DIGEST, Some(vec![required])), |error| {
615 matches!(error, VerificationError::UnexpectedPrecompileProof)
616 }),
617 (complete(root(99), Some(vec![required])), |error| {
618 matches!(error, VerificationError::InsufficientPrecompileRootCoverage)
619 }),
620 ];
621
622 for (proof, check) in cases {
623 let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
624 assert!(check(error));
625 }
626 }
627
628 #[test]
629 fn invalid_vm_stark_is_rejected_before_deferred_witness_evaluation() {
630 use miden_precompiles::{UintDomain, UintPrecompile};
631
632 let witness = PrecompileWitness::from_entries(vec![
633 PrecompileWitnessEntry::Data {
634 tag: UintPrecompile::value_tag(UintDomain::U256),
635 chunks: vec![[Felt::from_u32(0); 8]],
636 },
637 PrecompileWitnessEntry::Data {
638 tag: UintPrecompile::value_tag(UintDomain::U256),
639 chunks: vec![core::array::from_fn(|i| Felt::from_u32(u32::from(i == 0)))],
640 },
641 PrecompileWitnessEntry::Join {
642 tag: UintPrecompile::op_tag(UintPrecompile::EQ_OP_ID),
643 lhs: 1,
644 rhs: 2,
645 },
646 PrecompileWitnessEntry::Join { tag: Tag::AND, lhs: 0, rhs: 3 },
647 ])
648 .unwrap();
649 assert!(matches!(
651 witness.compute_root(Arc::new(miden_precompiles::registry())),
652 Err(error) if matches!(error.root(), PrecompileError::AssertionFailed)
653 ));
654 let proof = ExecutionProof::new(
655 vm_proof(witness.root_unchecked()),
656 PrecompileStatus::Deferred(witness),
657 );
658 assert!(matches!(
659 Verifier::new().verify(&claim(), &proof),
660 Err(VerificationError::StarkVerificationError(..))
661 ));
662 }
663
664 #[test]
665 fn precompile_verifier_owns_artifact_shape_policy() {
666 type CheckError = fn(VerificationError) -> bool;
667
668 let required = root(1);
669 let cases: Vec<(PrecompileProof, Word, CheckError)> = vec![
670 (precompile_proof(vec![]), required, |error| {
671 matches!(error, VerificationError::EmptyPrecompileRoots)
672 }),
673 (precompile_proof(vec![required; MAX_PRECOMPILE_ROOTS + 1]), required, |error| {
674 matches!(
675 error,
676 VerificationError::TooManyPrecompileRoots { roots, max }
677 if roots == MAX_PRECOMPILE_ROOTS + 1 && max == MAX_PRECOMPILE_ROOTS
678 )
679 }),
680 (precompile_proof(vec![required, TRUE_DIGEST]), required, |error| {
681 matches!(error, VerificationError::SettledPrecompileRoot { index: 1 })
682 }),
683 (precompile_proof(vec![required]), TRUE_DIGEST, |error| {
684 matches!(error, VerificationError::UnexpectedPrecompileProof)
685 }),
686 (precompile_proof(vec![required]), root(99), |error| {
687 matches!(error, VerificationError::InsufficientPrecompileRootCoverage)
688 }),
689 ];
690
691 for (proof, expected_root, check) in cases {
692 let error = Verifier::new().verify_precompile(&proof, expected_root).unwrap_err();
693 assert!(check(error));
694 }
695 }
696
697 #[test]
698 fn oversized_precompile_stark_is_rejected_before_vm_stark_verification() {
699 let required = root(1);
700 let proof = ExecutionProof::new(
701 vm_proof(required),
702 PrecompileStatus::Proven(PrecompileProof {
703 proof: StarkProof::new(vec![0; MAX_STARK_PROOF_BYTES + 1], HashFunction::Poseidon2),
704 roots: vec![required],
705 }),
706 );
707
708 let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
709 assert!(matches!(
710 error,
711 VerificationError::PrecompileStarkVerification(
712 miden_precompiles_verifier::VerifyError::ProofTooLarge { size, max }
713 ) if size == MAX_STARK_PROOF_BYTES + 1 && max == MAX_STARK_PROOF_BYTES
714 ));
715 }
716
717 #[test]
718 fn malformed_transport_round_trips_then_verifier_rejects_it() {
719 let malformed = complete(root(1), Some(vec![]));
720 let bytes = malformed.to_bytes();
721 let decoded = ExecutionProof::read_from_bytes(&bytes).unwrap();
722
723 assert_eq!(decoded.to_bytes(), bytes);
724 assert!(matches!(
725 Verifier::new().verify(&claim(), &decoded),
726 Err(VerificationError::EmptyPrecompileRoots)
727 ));
728 }
729
730 #[test]
731 fn verifier_rejects_oversized_directly_constructed_vm_proof() {
732 let proof = ExecutionProof::new(
733 VmProof {
734 proof: StarkProof::new(
735 vec![0; MAX_STARK_PROOF_BYTES + 1],
736 HashFunction::Blake3_256,
737 ),
738 precompile_root: TRUE_DIGEST,
739 },
740 PrecompileStatus::Empty,
741 );
742
743 let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
744 let VerificationError::StarkVerificationError(_, source) = error else {
745 panic!("expected oversized VM STARK proof to be rejected")
746 };
747 assert!(matches!(
748 *source,
749 StarkVerificationError::ProofTooLarge {
750 size,
751 max: MAX_STARK_PROOF_BYTES,
752 } if size == MAX_STARK_PROOF_BYTES + 1
753 ));
754 }
755
756 #[test]
757 fn ordered_root_coverage_reaches_vm_stark_verification() {
758 let vm_root = root(2);
759 let proof = complete(vm_root, Some(vec![root(1), vm_root, root(3)]));
760
761 let error = Verifier::new().verify(&claim(), &proof).unwrap_err();
762 assert!(matches!(error, VerificationError::StarkVerificationError(..)));
763 }
764
765 #[test]
766 fn verifier_requires_compatible_vm_and_pvm_roots() {
767 let proof = complete(TRUE_DIGEST, None);
768 let incompatible_vm = ExecutionProof::from_parts(
769 ExecutionProofCompatibility::new(
770 vec![root(100)],
771 VERIFIER_SUPPORT_V2.accepted_pvm_roots.to_vec(),
772 )
773 .unwrap(),
774 proof.vm().clone(),
775 proof.precompile().clone(),
776 );
777 let incompatible_pvm = ExecutionProof::from_parts(
778 ExecutionProofCompatibility::new(
779 VERIFIER_SUPPORT_V2.accepted_vm_roots.to_vec(),
780 vec![root(200)],
781 )
782 .unwrap(),
783 proof.vm().clone(),
784 proof.precompile().clone(),
785 );
786
787 assert!(matches!(
788 Verifier::new().verify(&claim(), &incompatible_vm),
789 Err(VerificationError::IncompatibleVmVerifier)
790 ));
791 assert!(matches!(
792 Verifier::new().verify(&claim(), &incompatible_pvm),
793 Err(VerificationError::IncompatiblePvmVerifier)
794 ));
795 }
796
797 #[test]
798 fn current_proof_compatibility_excludes_verifier_history() {
799 const OLD_VM_ROOT: Word = Word::new([
800 Felt::new_unchecked(1),
801 Felt::new_unchecked(0),
802 Felt::new_unchecked(0),
803 Felt::new_unchecked(0),
804 ]);
805 const OLD_PVM_ROOT: Word = Word::new([
806 Felt::new_unchecked(2),
807 Felt::new_unchecked(0),
808 Felt::new_unchecked(0),
809 Felt::new_unchecked(0),
810 ]);
811 const SUPPORT: VerifierSupport = VerifierSupport {
812 format: ExecutionProofCompatibility::FORMAT_V2,
813 accepted_vm_roots: &[OLD_VM_ROOT, CURRENT_VM_VERIFIER_ROOT],
814 accepted_pvm_roots: &[OLD_PVM_ROOT, CURRENT_PVM_VERIFIER_ROOT],
815 };
816
817 let proof = complete(TRUE_DIGEST, None);
818
819 assert_eq!(proof.compatibility().vm_verifier_roots(), &[CURRENT_VM_VERIFIER_ROOT]);
820 assert_eq!(proof.compatibility().pvm_verifier_roots(), &[CURRENT_PVM_VERIFIER_ROOT]);
821
822 let old_compatible = ExecutionProof::from_parts(
823 ExecutionProofCompatibility::new(vec![OLD_VM_ROOT], vec![OLD_PVM_ROOT]).unwrap(),
824 proof.vm().clone(),
825 proof.precompile().clone(),
826 );
827 assert!(SUPPORT.check(&old_compatible).is_ok());
828 }
829}