ZK Dispute Game
Table of Contents
- Overview
- Definitions
- Contracts Involved
- Actors
- Game Args Layout
- OPCM Integration
- Assumptions
- aZKG-001: ZK Verifier Soundness
- aZKG-002: Absolute Prestate Uniquely Identifies the ZK Program
- aZKG-003: Parent Chaining Preserves Correctness
- aZKG-004: Bonds Are Economically Rational
- aZKG-005: Guardian Acts Honestly and Timely
- aZKG-006: Anchor State Advances Slowly Relative to Proposal Frequency
- aZKG-007: Proof Generation Is Feasible Within the Prove Window
- Invariants
- iZKG-001: A Valid Proof Always Wins
- iZKG-002: A Game Without a Valid Proof and With a Challenger Resolves as CHALLENGER_WINS
- iZKG-003: Bond Safety via DelayedWETH
- iZKG-004: Permissionless Participation
- iZKG-005: Parent Invalidity Propagates to Children
- iZKG-006: closeGame Reverts When Paused
- iZKG-007: Only Finalized Games Can Close
- iZKG-008: Blacklisted and Retired Games Enter REFUND Mode
- iZKG-009: Child Resolution Requires Resolved Parent
- iZKG-010: At Most One Challenge Per Game
- iZKG-011: Bond Conservation
- iZKG-012: Monotonic State Progression
Overview
The ZKDisputeGame is a dispute game that resolves disputes in a single round using ZK
(zero-knowledge) proofs, registered as game type 10. It integrates into the same OP Stack
dispute infrastructure — DisputeGameFactory, AnchorStateRegistry, DelayedWETH, and
OPContractsManager.
A proposer posts an output root with a bond. Anyone can challenge it by depositing a challenger bond. If no challenge is submitted before the challenge window expires, the proposer wins by default. If a challenge is submitted, there are two possible outcomes: either a prover submits a valid ZK proof to defend the claim and the proposer wins, or the proving window expires without a valid proof and the challenger wins. Resolution is permissionless once the game is over and the parent game is resolved.
The proving system is accessed through the generic IZKVerifier interface.
The first supported backend is SP1 (PLONK) by Succinct. See ZK Fault Proof VM
for details on the off-chain proving component.
For the full game lifecycle and bond accounting see Game Mechanics.
Definitions
ZKDisputeGame
The smart contract implementing the single-round ZK dispute protocol. Each game instance is a
lightweight MCP clone of a shared implementation contract, deployed by
DisputeGameFactory.
MCP Clone
A minimal proxy clone (ERC-1167) created by DisputeGameFactory that shares the ZKDisputeGame
implementation bytecode but has its own per-chain configuration appended as immutable constructor
arguments via the Clones-with-Immutable-Args (CWIA) pattern.
Game Args (CWIA)
The per-chain configuration bytes appended to each MCP clone by DisputeGameFactory. Enable a
single implementation contract to serve every chain. See Game Args Layout
for the full field breakdown.
L2 Sequence Number
The L2 block number asserted by a game's root claim. Used to validate parent–child ordering and to verify that a parent's proven state falls within or above the anchor state.
Parent Game
A previously created ZKDisputeGame whose proven output root serves as the starting state for a
new game's ZK proof. A game with parentIndex == type(uint32).max starts from the anchor state
directly.
Challenge Deadline
The timestamp after which a game can no longer be challenged. Computed as
createdAt + maxChallengeDuration.
Prove Deadline
The timestamp after which a challenged game can no longer receive a proof submission. Set to
block.timestamp + maxProveDuration when challenge() is called, resetting the prior challenge
deadline.
Absolute Prestate
A bytes32 value that uniquely identifies the ZK program version being proven. It serves as the
program identity passed to IZKVerifier.verify() and is injected into each game instance via the
CWIA game args. See Absolute Prestate for details.
Game Over
The condition under which a game can be resolved. gameOver() returns true when:
statusisUnchallengedAndValidProofProvidedorChallengedAndValidProofProvided, or- The current deadline has expired.
Contracts Involved
| Contract | Role |
|---|---|
| ZKDisputeGame (clones) | Per-proposal game instance. Runs the challenge → prove → resolve lifecycle and tracks bond accounting. |
| ZKDisputeGame (implementation) | Shared bytecode base for MCP clones. Deployed and upgraded by OPCM. |
| DisputeGameFactory | Creates MCP clones via create(...). Appends per-chain gameArgs (CWIA). |
| AnchorStateRegistry | Source of truth for finalization, anchor state, respected game type, and blacklisting. Enforces pause checks. |
| DelayedWETH | Bond custody with a deposit → unlock → withdraw lifecycle. Provides a time window for the Guardian to freeze funds post-resolution. |
| IZKVerifier | Generic verifier interface. The concrete deployment for the initial release uses Succinct's PLONK verifier. |
Actors
| Actor | Role |
|---|---|
| Proposer | Fully permissionless. Creates games via DisputeGameFactory.create() with the required initBond. |
| Challenger | Fully permissionless. Disputes a proposal by calling challenge() and depositing challengerBond. |
| Prover | Fully permissionless. Submits a valid ZK proof via prove(proofBytes). May be the same address as the proposer or the challenger. |
| Guardian | Pauses the system, blacklists games, sets the respected game type, and retires old games via updateRetirementTimestamp(). |
| OPCM / ProxyAdmin Owner | Deploys implementations, configures game types in the factory, and manages absolutePrestate and verifier versions. |
Game Args Layout
The following fields are packed into gameArgs in order:
| Field | Type | Description |
|---|---|---|
absolutePrestate | bytes32 | ZK program identity (e.g., SP1 verification key) |
verifier | address | Address of the IZKVerifier contract |
maxChallengeDuration | Duration | Time window for challenges after game creation |
maxProveDuration | Duration | Time window for proof submission after a challenge |
challengerBond | uint256 | Bond required to challenge a proposal |
anchorStateRegistry | address | Address of AnchorStateRegistry |
weth | address | Address of per-chain DelayedWETH |
l2ChainId | uint256 | L2 chain identifier, sourced from SystemConfig |
anchorStateRegistry, weth, and l2ChainId are injected by OPContractManager._makeGameArgs()
directly from the chain's existing deployment.
OPCM Integration
ZKDisputeGame integrates into OPCM v2 as game type 10 (ZK_GAME_TYPE) through the existing
DisputeGameConfig, reusing the same pattern as Cannon and Permissioned Cannon.
Three additions are required:
ZKDisputeGameis deployed once via theDeployImplementationsscript and tracked inOPContractsManagerContainer.Implementationsalongside existing fault game implementations.- A
ZKDisputeGameConfigstruct carries the per-chain parameters that the caller provides. OPCM's_makeGameArgs()decodes it, injects the chain-specific values it already knows, and packs the final CWIA bytes for the factory. ZK_GAME_TYPEis added to thevalidGameTypesarray in_assertValidFullConfig().
ZKDisputeGameConfig
struct ZKDisputeGameConfig {
Claim absolutePrestate;
address verifier;
Duration maxChallengeDuration;
Duration maxProveDuration;
uint256 challengerBond;
}
if (_gcfg.gameType.raw() == GameTypes.ZK_GAME_TYPE.raw()) {
ZKDisputeGameConfig memory cfg = abi.decode(_gcfg.gameArgs, (ZKDisputeGameConfig));
return abi.encodePacked(
cfg.absolutePrestate,
cfg.verifier,
cfg.maxChallengeDuration,
cfg.maxProveDuration,
cfg.challengerBond,
address(_anchorStateRegistry),
address(_delayedWETH),
_l2ChainId
);
}
Assumptions
aZKG-001: ZK Verifier Soundness
The IZKVerifier implementation is sound: it is computationally infeasible to produce a proof
that passes verify() for an incorrect state transition.
Mitigations
- The PLONK verifier for SP1 is independently audited.
- The verifier address comes from
gameArgs, managed by OPCM. Governance controls upgrades. - The
IZKVerifierinterface intentionally decouples the game from any specific proving system, enabling a verifier swap without redeploying the game implementation.
aZKG-002: Absolute Prestate Uniquely Identifies the ZK Program
The absolutePrestate value uniquely identifies the ZK program version. Two different programs
MUST NOT share the same absolutePrestate.
Mitigations
- For SP1,
absolutePrestatecorresponds to the program's verification key, which is derived from the program binary and circuit structure. - OPCM manages
absolutePrestateper chain; program updates require a correspondingabsolutePrestateupdate via governance.
aZKG-003: Parent Chaining Preserves Correctness
A chain of ZKDisputeGame instances resolving as DEFENDER_WINS implies that the final
rootClaim is a valid output root, provided the initial parent started from a known-good anchor
state.
Mitigations
- Each proof commits to
startingOutputRoot(the parent's claim) as a public value, cryptographically linking consecutive games. - Parent validation at creation time prevents games from
chaining off blacklisted, retired, or
CHALLENGER_WINSparents. - If a parent is blacklisted or retired after child games have been created, the Guardian MUST individually blacklist or retire those child games to place them in REFUND mode.
aZKG-004: Bonds Are Economically Rational
The initBond, challengerBond, maxChallengeDuration, and maxProveDuration values are set
such that honest participation is economically rational and griefing is costly.
Mitigations
- All bond and duration parameters are in
gameArgsand can be tuned per chain by OPCM without redeploying the implementation. - Benchmark proving costs and document standard values for common chain configurations, analogous to how fault proof bonds are standardized today.
- Bonds too low invite spam; bonds too high discourage honest participation. Durations must be long enough to allow proof generation but short enough to preserve withdrawal latency benefits.
aZKG-005: Guardian Acts Honestly and Timely
The Guardian is trusted to pause the system, blacklist invalid games, and retire superseded game types before fraudulent games achieve Valid Claims.
Mitigations
- The
DISPUTE_GAME_FINALITY_DELAY_SECONDSairgap between resolution andcloseGameprovides the Guardian a window to act. DelayedWETHprovides an additional window aftercloseGameto freeze funds.
aZKG-006: Anchor State Advances Slowly Relative to Proposal Frequency
There is no technical mechanism that enforces the anchor state to advance slowly — any resolved
game that passes the finality delay can call closeGame() and advance it. However, the minimum
time for a game to advance the anchor state is maxChallengeDuration + DISPUTE_GAME_FINALITY_DELAY_SECONDS
(12+ hours in practice), and under normal operation this is expected to be much larger than typical
proposal frequency (e.g., 1 hour), making orphan risk from parent validation negligible.
Mitigations
- A rational proposer would never use a parent whose
l2SequenceNumberis below the anchor, as it unnecessarily increases the proving range. - Parent validation requires the parent's
l2SequenceNumberto be strictly above the anchor state, preventing chains from building on stale starting points.
aZKG-007: Proof Generation Is Feasible Within the Prove Window
A prover with access to the required L1 and L2 data can generate a valid proof within
maxProveDuration under normal operating conditions.
Mitigations
maxProveDurationmust be set with headroom above worst-case proving times, to account for prover network latency, queue depth, and hardware variability.- Multiple independent provers (whether incentivized by bonds or operated by the proposer directly) reduce the risk of a single point of failure in proof delivery.
- Off-chain monitoring on the ratio of successful
prove()calls to challenged games can detect when proving infrastructure is unable to keep up with the configured window.
Invariants
iZKG-001: A Valid Proof Always Wins
If a valid ZK proof is submitted before the current deadline, the game MUST resolve as
DEFENDER_WINS (assuming a valid parent chain).
Impact
Severity: High
A violation lets a correct proposer be cheated out of their bond, breaking the economic security of the game and the correctness of withdrawal finalization.
iZKG-002: A Game Without a Valid Proof and With a Challenger Resolves as CHALLENGER_WINS
If a game was challenged and the prove deadline expires without a valid proof, resolve() MUST
produce CHALLENGER_WINS.
Impact
Severity: High
A violation would allow invalid output roots to be finalized on L1, enabling theft of funds from the bridge.
iZKG-003: Bond Safety via DelayedWETH
All bonds MUST be deposited into and withdrawn from DelayedWETH. The game contract MUST NOT
hold raw ETH bonds.
Impact
Severity: Critical
Raw ETH bonds held directly in the game contract cannot be recovered — the contract is immutable and non-upgradeable, so any ETH stuck in it is permanently lost. This also bypasses the Guardian's ability to freeze funds post-resolution.
iZKG-004: Permissionless Participation
create(), challenge(), prove(), and resolve() MUST be callable by any address. No
AccessManager or allowlist MAY gate these functions.
Impact
Severity: High
Permissioned access would reduce censorship resistance and deviate from the Stage 1 security model.
iZKG-005: Parent Invalidity Propagates to Children
If a parent game resolves as CHALLENGER_WINS, all child games MUST also resolve as
CHALLENGER_WINS, regardless of whether a valid proof was submitted for the child.
Impact
Severity: High
Failure to propagate would allow a chain of games to finalize an output root that descends from an invalid state, enabling withdrawal of funds that do not exist on L2.
iZKG-006: closeGame Reverts When Paused
closeGame() MUST revert if AnchorStateRegistry reports the system as paused.
Impact
Severity: High
Allowing bond distribution while paused would bypass the Guardian's ability to freeze funds during an active security incident.
iZKG-007: Only Finalized Games Can Close
closeGame() MUST revert unless AnchorStateRegistry.isFinalized(this) returns true.
Impact
Severity: High
Closing before the finality delay would remove the Guardian's window to blacklist or pause the game before funds are distributed.
iZKG-008: Blacklisted and Retired Games Enter REFUND Mode
If a game is blacklisted or retired, closeGame() MUST enter REFUND mode: bonds are returned to
the original depositors rather than distributed to winners.
Impact
Severity: High
Failure to refund would cause honest participants to lose bonds when the Guardian must invalidate a game for safety reasons unrelated to the game's correctness.
iZKG-009: Child Resolution Requires Resolved Parent
resolve() MUST revert if the parent game has not yet resolved. The resolution dependency chain
MUST be honored in topological order.
Impact
Severity: Critical
Without this, iZKG-005 cannot hold. A child could resolve as DEFENDER_WINS and finalize a
withdrawal before the parent is invalidated, allowing funds to be withdrawn against an output root
that descends from an invalid state.
iZKG-010: At Most One Challenge Per Game
challenge() MUST revert if the game has already been challenged (i.e., the game is not in the
Unchallenged state).
Impact
Severity: High
A second challenge could reset the prove deadline and give challengers unbounded time to delay
resolution, overwrite the original challenger's address and steal their bond credit, or produce
double the bond liability in DelayedWETH with only one challengerBond deposited.
iZKG-011: Bond Conservation
For any resolved game, the sum of all bonds distributed plus any amount sent to address(0) MUST
equal initBond + challengerBond (or initBond alone if the game was never challenged). No value
may be created from nothing or permanently locked beyond the defined burn path.
Impact
Severity: High
A violation means either fund loss for participants (bonds locked forever with no recipient) or an
exploitable source of unbacked ETH withdrawals from DelayedWETH.
iZKG-012: Monotonic State Progression
The game status MUST only advance forward through the state machine. No transition from a later
state back to an earlier one is permitted (e.g., Challenged → Unchallenged is invalid).
Impact
Severity: High
State regression would corrupt deadline logic (the prove deadline is set when challenge() is
called) and bond accounting (bonds are allocated per state transition). Functions that use game
status as a guard could be re-entered in unexpected ways if the state can regress.