From ee60e39baf8bc19c813b388b69bb8d1963aa2fd3 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Fri, 26 Jun 2026 20:23:56 -0300 Subject: [PATCH] Reject continuations exceeding the IsB20 cross-epoch ordering range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cross-epoch ordering check proves `init_epoch < fini_epoch` via an IsB20 (20-bit) lookup on `fini_epoch - 1 - init_epoch`, so a run can have at most 2^20 epochs. Beyond that the IsB20 bus cannot balance and no honest proof exists. Previously this was guarded only by a debug_assert in the prover's bitwise emission, so a release build would build an unprovable trace and fail cryptically — reachable via the library API with a small epoch size (the CLI's min epoch size keeps it out of reach there). Add a hard check in `prove_continuation`'s epoch loop returning `Error::InvalidContinuationEpochSize` with a clear message once the epoch count would exceed the range. This is a prover-side guard only: the verifier already rejects any such proof (the IsB20 table is preprocessed and the ordering sender is rebuilt verifier-side from a positional epoch label), so soundness is unchanged — it just turns a confusing failure into a clean error. Introduce `local_to_global::MAX_EPOCHS` as the single source of truth, used by both the new check and the existing debug_assert (replacing the `1 << 20` literal). --- prover/src/continuation.rs | 12 ++++++++++++ prover/src/tables/local_to_global.rs | 12 +++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/prover/src/continuation.rs b/prover/src/continuation.rs index 2fcce9cda..15b56a1e0 100644 --- a/prover/src/continuation.rs +++ b/prover/src/continuation.rs @@ -662,6 +662,18 @@ pub fn prove_continuation( if executor.pc() == 0 { break; } + // The cross-epoch ordering check (IsB20 on `fini_epoch - 1 - init_epoch`) + // only spans `local_to_global::MAX_EPOCHS` epochs. Beyond that the IsB20 bus + // cannot balance, so an honest proof is impossible — fail fast with a clear + // error instead of building an unprovable trace. The verifier already + // rejects any such proof; this is a prover-side guard for a clean message. + if index >= local_to_global::MAX_EPOCHS { + return Err(Error::InvalidContinuationEpochSize(format!( + "execution needs more than {} continuation epochs (the IsB20 cross-epoch \ + ordering range); use a larger epoch size", + local_to_global::MAX_EPOCHS + ))); + } let register_init: Vec = if index == 0 { register::register_init_from_entry_point(elf.entry_point) } else { diff --git a/prover/src/tables/local_to_global.rs b/prover/src/tables/local_to_global.rs index eb91b36f3..3f9221d57 100644 --- a/prover/src/tables/local_to_global.rs +++ b/prover/src/tables/local_to_global.rs @@ -72,6 +72,16 @@ type Provenance = PagedMem<(u64, u64, u64)>; /// (1-based) epoch label, so `init_epoch < fini_epoch` holds for genesis cells. pub const GENESIS_EPOCH: u64 = 0; +/// Maximum number of epochs a continuation run may have. +/// +/// The cross-epoch ordering check proves `init_epoch < fini_epoch` via an `IsB20` +/// (20-bit) lookup on `fini_epoch - 1 - init_epoch`. A genesis-sourced cell +/// finalized in epoch `index` (0-based) has gap `index`, so every epoch must +/// satisfy `index < 2^20`. A run needing more epochs cannot be proved — the +/// IsB20 bus would not balance — so the driver rejects it up front (see +/// `prove_continuation`). +pub const MAX_EPOCHS: u64 = 1 << 20; + /// A cell's state when an epoch first touches it. #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct InitClaim { @@ -462,7 +472,7 @@ pub fn collect_bitwise_from_l2g(boundaries: &[CellBoundary]) -> Vec> 8) & 0xFF) as u8,