For the complete documentation index, see llms.txt. This page is also available as Markdown.

75312 bc low offchain challenger nullify retries are unbounded when dual proof game is externally challenged after proof readiness

Submitted on Apr 28th 2026 at 13:20:14 UTC by @pks271 for Audit Comp | Base Azul

  • Report ID: #75312

  • Report Type: Blockchain/DLT

  • Report severity: Low

  • Target: https://github.com/base/base/tree/v0.8.0-rc.28

  • Impacts:

    • A bug in the respective layer 0/1/2 network code that results in unintended smart contract behavior with no concrete funds at direct risk

    • Unbounded gas consumption in any in-scope contract function callable by external parties

Description

Bug Description

commit hash: e3467a2048881213b56739a54a876efb9c6ea103 (v0.8.0-rc.28)

In the challenger dual-proof path (InvalidDualProposal), the driver can build a nullify() proof from an unchallenged game state (tee_prover != 0, zk_prover != 0, countered_index == 0) and move the entry to ReadyToSubmit.

If the game is externally challenged before the submit happens, the on-chain nullify() rules change immediately:

  • counteredByIntermediateRootIndexPlusOne > 0 requires nullifying the challenged index exactly;

  • intermediateRootToProve must equal the challenged root;

  • proof type must be ZK.

The current off-chain submit path does not re-validate countered_index compatibility before sending the pending nullify() call. It only checks status, tee_prover, and zk_prover. As a result, stale pre-challenge (invalid_index, expected_root) is repeatedly submitted and deterministically reverts.

On revert, the entry stays in ReadyToSubmit and is retried every tick. MAX_PROOF_RETRIES is not applied because retry_count only tracks proving retries, not submit retries.

Call trace

crates/proof/challenge/src/driver.rs::step -> process_invalid_proposal (InvalidDualProposal) -> initiate_proof(intent=Nullify) -> pending ReadyToSubmit -> external challenge sets countered index -> poll_or_submit submits stale nullify -> AggregateVerifier.nullify() challenged branch rejects -> Err branch keeps ReadyToSubmit -> next tick repeats

Code

Impact

An external actor can race the challenger by challenging a dual-proof game after proof readiness but before submit. This forces deterministic submit reverts on stale nullify() parameters. Because submit retries are unbounded in ReadyToSubmit, the challenger can enter a persistent gas/liveness degradation loop for the same game.

Recommendation

  • Add submit-side stale-context checks before nullify():

    • fetch countered_index;

    • if challenged (countered_index > 0), ensure pending invalid_index and expected_root still match challenged index/root semantics;

    • otherwise drop/rebuild pending entry from fresh scan classification.

  • Add MAX_SUBMIT_RETRIES (separate from proof retries) and stop retrying deterministic terminal reverts.

Proof of Concept

steps:

  1. Game is dual-proof and unchallenged (tee_prover != 0, zk_prover != 0, countered_index == 0).

  2. Challenger validates roots and creates a Nullify pending proof with pre-challenge (invalid_index, expected_root).

  3. Before submit, external actor calls challenge(), moving the game into challenged state (countered_index > 0).

  4. Challenger submits stale nullify() parameters; on-chain call reverts deterministically.

  5. Driver keeps entry ReadyToSubmit and retries on every tick.

Patch

Run:

Result:

Was this helpful?