Skip to content

Commit 2a38a69

Browse files
authored
Reject continuations exceeding the IsB20 cross-epoch ordering range (#734)
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).
1 parent 5529dbd commit 2a38a69

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

prover/src/continuation.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,18 @@ pub fn prove_continuation(
662662
if executor.pc() == 0 {
663663
break;
664664
}
665+
// The cross-epoch ordering check (IsB20 on `fini_epoch - 1 - init_epoch`)
666+
// only spans `local_to_global::MAX_EPOCHS` epochs. Beyond that the IsB20 bus
667+
// cannot balance, so an honest proof is impossible — fail fast with a clear
668+
// error instead of building an unprovable trace. The verifier already
669+
// rejects any such proof; this is a prover-side guard for a clean message.
670+
if index >= local_to_global::MAX_EPOCHS {
671+
return Err(Error::InvalidContinuationEpochSize(format!(
672+
"execution needs more than {} continuation epochs (the IsB20 cross-epoch \
673+
ordering range); use a larger epoch size",
674+
local_to_global::MAX_EPOCHS
675+
)));
676+
}
665677
let register_init: Vec<u32> = if index == 0 {
666678
register::register_init_from_entry_point(elf.entry_point)
667679
} else {

prover/src/tables/local_to_global.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ type Provenance = PagedMem<(u64, u64, u64)>;
7272
/// (1-based) epoch label, so `init_epoch < fini_epoch` holds for genesis cells.
7373
pub const GENESIS_EPOCH: u64 = 0;
7474

75+
/// Maximum number of epochs a continuation run may have.
76+
///
77+
/// The cross-epoch ordering check proves `init_epoch < fini_epoch` via an `IsB20`
78+
/// (20-bit) lookup on `fini_epoch - 1 - init_epoch`. A genesis-sourced cell
79+
/// finalized in epoch `index` (0-based) has gap `index`, so every epoch must
80+
/// satisfy `index < 2^20`. A run needing more epochs cannot be proved — the
81+
/// IsB20 bus would not balance — so the driver rejects it up front (see
82+
/// `prove_continuation`).
83+
pub const MAX_EPOCHS: u64 = 1 << 20;
84+
7585
/// A cell's state when an epoch first touches it.
7686
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7787
pub struct InitClaim {
@@ -462,7 +472,7 @@ pub fn collect_bitwise_from_l2g(boundaries: &[CellBoundary]) -> Vec<BitwiseOpera
462472
// Ordering: IsB20[fini_epoch - 1 - init_epoch]. Honest rows have
463473
// init_epoch < fini_epoch, so the difference is a small non-negative value.
464474
let diff = b.fini.epoch - 1 - b.init.originating_epoch;
465-
debug_assert!(diff < (1 << 20), "epoch gap exceeds IsB20 range");
475+
debug_assert!(diff < MAX_EPOCHS, "epoch gap exceeds IsB20 range");
466476
ops.push(BitwiseOperation::b20(
467477
(diff & 0xFF) as u8,
468478
((diff >> 8) & 0xFF) as u8,

0 commit comments

Comments
 (0)