From d392f01c90fe0922ef2de3908e55c941a4473125 Mon Sep 17 00:00:00 2001 From: 0xernesto <78889960+0xernesto@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:47:13 -0700 Subject: [PATCH 1/4] fix: enforce deferred proof verification --- crates/core/executor/src/executor.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/core/executor/src/executor.rs b/crates/core/executor/src/executor.rs index b6bb0fd78..7fcea5b7f 100644 --- a/crates/core/executor/src/executor.rs +++ b/crates/core/executor/src/executor.rs @@ -253,6 +253,15 @@ pub enum ExecutionError { /// The unconstrained cycle limit was exceeded. #[error("unconstrained cycle limit exceeded")] UnconstrainedCycleLimitExceeded(u64), + + /// Not all deferred proofs were verified during execution. + #[error("unread deferred proofs: {actual}/{expected}")] + UnreadDeferredProofs { + /// The total number of deferred proofs that were provided. + expected: usize, + /// The actual number of deferred proofs that were verified. + actual: usize, + }, } impl<'a> Executor<'a> { @@ -2059,7 +2068,7 @@ impl<'a> Executor<'a> { let public_values = self.record.public_values; if done { - self.postprocess(); + self.postprocess()?; // Push the remaining execution record with memory initialize & finalize events. self.bump_record(); @@ -2108,7 +2117,7 @@ impl<'a> Executor<'a> { Ok(done) } - fn postprocess(&mut self) { + fn postprocess(&mut self) -> Result<(), ExecutionError> { // Flush remaining stdout/stderr for (fd, buf) in &self.io_buf { if !buf.is_empty() { @@ -2124,12 +2133,16 @@ impl<'a> Executor<'a> { } } - // Ensure that all proofs and input bytes were read, otherwise warn the user. + // Ensure that all proofs were read, otherwise return an error. if self.state.proof_stream_ptr != self.state.proof_stream.len() { - tracing::warn!( - "Not all proofs were read. Proving will fail during recursion. Did you pass too + tracing::error!( + "Not all proofs were read. Proving would fail during recursion. Did you pass too many proofs in or forget to call verify_sp1_proof?" ); + return Err(ExecutionError::UnreadDeferredProofs { + expected: self.state.proof_stream.len(), + actual: self.state.proof_stream_ptr, + }); } if !self.state.input_stream.is_empty() { @@ -2221,6 +2234,8 @@ impl<'a> Executor<'a> { .push(MemoryInitializeFinalizeEvent::finalize_from_record(addr, &record)); } } + + Ok(()) } fn get_syscall(&mut self, code: SyscallCode) -> Option<&Arc> { From 9e8356043fb220bcb13487fb16696f4372dd4aea Mon Sep 17 00:00:00 2001 From: 0xernesto <78889960+0xernesto@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:38:44 -0700 Subject: [PATCH 2/4] cleanup --- crates/core/executor/src/executor.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/core/executor/src/executor.rs b/crates/core/executor/src/executor.rs index 7fcea5b7f..0379991ee 100644 --- a/crates/core/executor/src/executor.rs +++ b/crates/core/executor/src/executor.rs @@ -255,8 +255,8 @@ pub enum ExecutionError { UnconstrainedCycleLimitExceeded(u64), /// Not all deferred proofs were verified during execution. - #[error("unread deferred proofs: {actual}/{expected}")] - UnreadDeferredProofs { + #[error("unverified deferred proofs: verified={actual}, expected={expected}")] + UnverifiedDeferredProofs { /// The total number of deferred proofs that were provided. expected: usize, /// The actual number of deferred proofs that were verified. @@ -2136,10 +2136,11 @@ impl<'a> Executor<'a> { // Ensure that all proofs were read, otherwise return an error. if self.state.proof_stream_ptr != self.state.proof_stream.len() { tracing::error!( - "Not all proofs were read. Proving would fail during recursion. Did you pass too - many proofs in or forget to call verify_sp1_proof?" + "Not all proofs were verified. \ + Make sure you are passing the correct number of proofs \ + and that you are calling verify_sp1_proof for all proofs." ); - return Err(ExecutionError::UnreadDeferredProofs { + return Err(ExecutionError::UnverifiedDeferredProofs { expected: self.state.proof_stream.len(), actual: self.state.proof_stream_ptr, }); From ed6509e4c530934cf7e1410aba37da8b1e697530 Mon Sep 17 00:00:00 2001 From: 0xernesto <78889960+0xernesto@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:21:19 -0700 Subject: [PATCH 3/4] catch verification panics --- crates/core/executor/src/executor.rs | 65 ++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/crates/core/executor/src/executor.rs b/crates/core/executor/src/executor.rs index 0379991ee..6159f2c38 100644 --- a/crates/core/executor/src/executor.rs +++ b/crates/core/executor/src/executor.rs @@ -262,6 +262,20 @@ pub enum ExecutionError { /// The actual number of deferred proofs that were verified. actual: usize, }, + + /// Insufficient deferred proofs were provided. + #[error("insufficient deferred proofs: unable to verify proof at index {index}")] + InsufficientDeferredProofs { + /// The index of the proof that was requested. + index: usize, + }, + + /// Deferred proof verification failed. + #[error("deferred proof verification failed: {reason}")] + DeferredProofVerificationFailed { + /// The reason for the verification failure. + reason: String, + }, } impl<'a> Executor<'a> { @@ -1623,7 +1637,51 @@ impl<'a> Executor<'a> { // Executing a syscall optionally returns a value to write to the t0 // register. If it returns None, we just keep the // syscall_id in t0. - let res = syscall_impl.execute(&mut precompile_rt, syscall, b, c); + let res = if syscall == SyscallCode::VERIFY_SP1_PROOF { + // Catch panics for VERIFY_SP1_PROOF to provide better error messages. + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + syscall_impl.execute(&mut precompile_rt, syscall, b, c) + })) { + Ok(result) => result, + Err(panic_payload) => { + // Extract the panic message. + let msg = panic_payload + .downcast_ref::() + .map(String::as_str) + .or_else(|| panic_payload.downcast_ref::<&str>().copied()); + + if let Some(msg) = msg { + let index = precompile_rt.rt.state.proof_stream_ptr; + let available = precompile_rt.rt.state.proof_stream.len(); + + if msg.contains("Not enough proofs") { + tracing::error!( + "Insufficient deferred proofs: unable to verify proof \ + at index {index} because only {available} proofs were \ + provided. \ + Make sure you're passing the correct number of proofs \ + and that you're calling verify_sp1_proof for all proofs." + ); + return Err(ExecutionError::InsufficientDeferredProofs { + index, + }); + } else if msg.contains("Failed to verify proof") { + tracing::error!( + "Failed to verify deferred proof at index {index}." + ); + return Err(ExecutionError::DeferredProofVerificationFailed { + reason: msg.to_string(), + }); + } + } + + // Resume default behavior for unknown panics. + std::panic::resume_unwind(panic_payload); + } + } + } else { + syscall_impl.execute(&mut precompile_rt, syscall, b, c) + }; let a = if let Some(val) = res { val } else { syscall_id }; // If the syscall is `HALT` and the exit code is non-zero, return an error. @@ -2137,8 +2195,9 @@ impl<'a> Executor<'a> { if self.state.proof_stream_ptr != self.state.proof_stream.len() { tracing::error!( "Not all proofs were verified. \ - Make sure you are passing the correct number of proofs \ - and that you are calling verify_sp1_proof for all proofs." + Expected to verify {expected} proofs, but only {actual} were verified. \ + Make sure you're passing the correct number of proofs \ + and that you're calling verify_sp1_proof for all proofs." ); return Err(ExecutionError::UnverifiedDeferredProofs { expected: self.state.proof_stream.len(), From 9dd168d93e6bdc2589ca6abc1407d145a6b67802 Mon Sep 17 00:00:00 2001 From: 0xernesto <78889960+0xernesto@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:03:34 -0700 Subject: [PATCH 4/4] fix --- crates/core/executor/src/executor.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/core/executor/src/executor.rs b/crates/core/executor/src/executor.rs index 6159f2c38..b0d8302e8 100644 --- a/crates/core/executor/src/executor.rs +++ b/crates/core/executor/src/executor.rs @@ -2193,16 +2193,15 @@ impl<'a> Executor<'a> { // Ensure that all proofs were read, otherwise return an error. if self.state.proof_stream_ptr != self.state.proof_stream.len() { + let expected = self.state.proof_stream.len(); + let actual = self.state.proof_stream_ptr; tracing::error!( "Not all proofs were verified. \ Expected to verify {expected} proofs, but only {actual} were verified. \ Make sure you're passing the correct number of proofs \ and that you're calling verify_sp1_proof for all proofs." ); - return Err(ExecutionError::UnverifiedDeferredProofs { - expected: self.state.proof_stream.len(), - actual: self.state.proof_stream_ptr, - }); + return Err(ExecutionError::UnverifiedDeferredProofs { expected, actual }); } if !self.state.input_stream.is_empty() {