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
24mod 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#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct Verifier;
82
83impl Verifier {
84 pub const fn new() -> Self {
86 Self
87 }
88
89 pub fn proof_compatibility() -> ExecutionProofCompatibility {
91 ExecutionProofCompatibility::current()
92 }
93
94 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 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 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 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 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 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#[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 pub const fn vm_security_parameters(&self) -> &ProofSecurityParameters {
402 &self.vm_security_parameters
403 }
404
405 pub const fn precompile_security_parameters(&self) -> Option<&ProofSecurityParameters> {
407 self.precompile_security_parameters.as_ref()
408 }
409
410 pub const fn is_complete(&self) -> bool {
415 self.outstanding_precompile_root.is_none()
416 }
417
418 pub const fn outstanding_precompile_root(&self) -> Option<DeferredRoot> {
420 self.outstanding_precompile_root
421 }
422}
423
424#[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#[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
467fn roots_overlap(proof_roots: &[Word], accepted_roots: &[Word]) -> bool {
471 proof_roots.iter().any(|root| accepted_roots.contains(root))
472}
473
474#[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}