From 441d9c0470aa46ab08ed52748b3b35ba788427b7 Mon Sep 17 00:00:00 2001 From: Blake Date: Thu, 16 Apr 2026 12:29:10 +0800 Subject: [PATCH 1/2] fix: skip deferred proof verification when proof_stream is empty --- crates/core/executor/src/syscalls/verify.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/core/executor/src/syscalls/verify.rs b/crates/core/executor/src/syscalls/verify.rs index f400e40c3..d46cccfd4 100644 --- a/crates/core/executor/src/syscalls/verify.rs +++ b/crates/core/executor/src/syscalls/verify.rs @@ -17,6 +17,16 @@ impl Syscall for VerifySyscall { ) -> Result, ExecutionError> { let rt = &mut ctx.rt; + // When recovering from a checkpoint, proof_stream is intentionally excluded + // from serialization (to reduce checkpoint size). In that case, skip proof + // verification since it was already performed during checkpoint generation. + if rt.state.proof_stream.is_empty() { + tracing::info!( + "Skipping deferred proof verification: proof_stream is empty (ExecutorMode::Trace)" + ); + return Ok(None); + } + // vkey_ptr is a pointer to [u32; 8] which contains the verification key. // pv_digest_ptr is a pointer to [u32; 8] which contains the public values digest. From a493286d503f0dafb6d517e4f68aca3185579a00 Mon Sep 17 00:00:00 2001 From: Blake Date: Thu, 16 Apr 2026 15:06:26 +0800 Subject: [PATCH 2/2] support for configurable deferred proof verification during execution --- crates/core/executor/src/context.rs | 18 +++++++++++++++++- crates/core/executor/src/executor.rs | 21 +++++++++++++++++++++ crates/core/executor/src/syscalls/verify.rs | 12 +++--------- crates/sdk/src/action.rs | 12 ++++++++++++ 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/crates/core/executor/src/context.rs b/crates/core/executor/src/context.rs index 55304192b..cd8a5bc84 100644 --- a/crates/core/executor/src/context.rs +++ b/crates/core/executor/src/context.rs @@ -21,6 +21,9 @@ pub struct ZKMContext<'a> { /// The maximum number of cpu cycles to use for execution. pub max_cycles: Option, + + /// Skip deferred proof verification. + pub skip_deferred_proof_verification: bool, } /// A builder for [`ZKMContext`]. @@ -30,6 +33,7 @@ pub struct ZKMContextBuilder<'a> { hook_registry_entries: Vec<(u32, BoxedHook<'a>)>, subproof_verifier: Option<&'a dyn SubproofVerifier>, max_cycles: Option, + skip_deferred_proof_verification: bool, } impl<'a> ZKMContext<'a> { @@ -68,7 +72,13 @@ impl<'a> ZKMContextBuilder<'a> { }); let subproof_verifier = take(&mut self.subproof_verifier); let cycle_limit = take(&mut self.max_cycles); - ZKMContext { hook_registry, subproof_verifier, max_cycles: cycle_limit } + let skip_deferred_proof_verification = take(&mut self.skip_deferred_proof_verification); + ZKMContext { + hook_registry, + subproof_verifier, + max_cycles: cycle_limit, + skip_deferred_proof_verification, + } } /// Add a runtime [Hook](super::Hook) into the context. @@ -107,6 +117,12 @@ impl<'a> ZKMContextBuilder<'a> { self.max_cycles = Some(max_cycles); self } + + /// Set the skip deferred proof verification flag. + pub fn set_skip_deferred_proof_verification(&mut self, skip: bool) -> &mut Self { + self.skip_deferred_proof_verification = skip; + self + } } #[cfg(test)] diff --git a/crates/core/executor/src/executor.rs b/crates/core/executor/src/executor.rs index 5e31e399e..4a482d1ac 100644 --- a/crates/core/executor/src/executor.rs +++ b/crates/core/executor/src/executor.rs @@ -46,6 +46,15 @@ pub const DEFAULT_PC_INC: u32 = 4; /// A valid pc should be divisible by 4, so we use 1 to indicate that the pc is not used. pub const UNUSED_PC: u32 = 1; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Whether to verify deferred proofs during execution. +pub enum DeferredProofVerification { + /// Verify deferred proofs during execution. + Enabled, + /// Skip verification of deferred proofs. + Disabled, +} + /// An executor for the MIPS zkVM. /// /// The executor is responsible for executing a user program and tracing important events which @@ -100,6 +109,10 @@ pub struct Executor<'a> { /// The maximum number of cpu cycles to use for execution. pub max_cycles: Option, + /// Skip deferred proof verification. This check is informational only, not related to circuit + /// correctness. + pub deferred_proof_verification: DeferredProofVerification, + /// The state of the execution. pub state: ExecutionState, @@ -327,6 +340,11 @@ impl<'a> Executor<'a> { hook_registry, opts, max_cycles: context.max_cycles, + deferred_proof_verification: if context.skip_deferred_proof_verification { + DeferredProofVerification::Disabled + } else { + DeferredProofVerification::Enabled + }, memory_checkpoint: Memory::default(), uninitialized_memory_checkpoint: Memory::default(), local_memory_access: HashMap::new(), @@ -363,6 +381,9 @@ impl<'a> Executor<'a> { pub fn recover(program: Program, state: ExecutionState, opts: ZKMCoreOpts) -> Self { let mut runtime = Self::new(program, opts); runtime.state = state; + // Disable deferred proof verification since we're recovering from a checkpoint, and the + // checkpoint creator already had a chance to check the proofs. + runtime.deferred_proof_verification = DeferredProofVerification::Disabled; runtime } diff --git a/crates/core/executor/src/syscalls/verify.rs b/crates/core/executor/src/syscalls/verify.rs index d46cccfd4..a9d6e689a 100644 --- a/crates/core/executor/src/syscalls/verify.rs +++ b/crates/core/executor/src/syscalls/verify.rs @@ -1,6 +1,6 @@ use crate::program::MAX_MEMORY; -use crate::ExecutionError; +use crate::{DeferredProofVerification, ExecutionError}; use super::{Syscall, SyscallCode, SyscallContext}; @@ -17,13 +17,7 @@ impl Syscall for VerifySyscall { ) -> Result, ExecutionError> { let rt = &mut ctx.rt; - // When recovering from a checkpoint, proof_stream is intentionally excluded - // from serialization (to reduce checkpoint size). In that case, skip proof - // verification since it was already performed during checkpoint generation. - if rt.state.proof_stream.is_empty() { - tracing::info!( - "Skipping deferred proof verification: proof_stream is empty (ExecutorMode::Trace)" - ); + if rt.deferred_proof_verification == DeferredProofVerification::Disabled { return Ok(None); } @@ -46,13 +40,13 @@ impl Syscall for VerifySyscall { if proof_index >= rt.state.proof_stream.len() { panic!("Not enough proofs were written to the runtime."); } + let (proof, proof_vk) = &rt.state.proof_stream[proof_index]; rt.state.proof_stream_ptr += 1; let vkey_bytes: [u32; 8] = vkey.try_into().unwrap(); let pv_digest_bytes: [u32; 8] = pv_digest.try_into().unwrap(); if let Some(verifier) = rt.subproof_verifier { - let (proof, proof_vk) = &rt.state.proof_stream[proof_index]; if let Err(e) = verifier.verify_deferred_proof(proof, proof_vk, vkey_bytes, pv_digest_bytes) { diff --git a/crates/sdk/src/action.rs b/crates/sdk/src/action.rs index 98b58e197..87a46bb26 100644 --- a/crates/sdk/src/action.rs +++ b/crates/sdk/src/action.rs @@ -69,6 +69,12 @@ impl<'a> Execute<'a> { self.context_builder.max_cycles(max_cycles); self } + + /// Skip deferred proof verification. + pub fn set_skip_deferred_proof_verification(mut self, value: bool) -> Self { + self.context_builder.set_skip_deferred_proof_verification(value); + self + } } /// Builder to prepare and configure proving execution of a program on an input. @@ -221,4 +227,10 @@ impl<'a> Prove<'a> { self.timeout = Some(timeout); self } + + /// Set the skip deferred proof verification flag. + pub fn set_skip_deferred_proof_verification(mut self, value: bool) -> Self { + self.context_builder.set_skip_deferred_proof_verification(value); + self + } }