Architecture
Overview
OP Succinct's fault proof mode offers faster finality without ZK proving costs for every block. This mode uses:
- The same OP Succinct ZK program
OPSuccinctFaultDisputeGame
contract integrated with OP Stack's DisputeGameFactory- Single-round dispute resolution with ZK proofs
We assume that the reader has a solid understanding of the OP Stack's DisputeGameFactory
and IDisputeGame
interface. Documentation can be found here. We implement the IDisputeGame
interface with a ZK-enabled fault proof (using the OP Succinct ZK program) instead of the standard interactive bisection game that the vanilla OP Stack uses.
Core Concepts
- Proposals: Each proposal represents a claimed state transition from a start L2 block to an end L2 block with a
startL2OutputRoot
and aclaimedL2OutputRoot
where the output root is a commitment to the entirety of L2 state. - Challenges: Participants can challenge proposals they believe are invalid.
- Proofs: ZK proofs that verify the correctness of state transitions contained in proposals, anchored against an L1 block hash.
- Resolution: Process of determining whether a proposal is valid or not.
Dispute Game Implementation
Proposing new state roots goes through the regular flow of the DisputeGameFactory
to the OPSuccinctFaultDisputeGame
contract that implements the IDisputeGame
interface. Each proposal contains a link to a previous parent proposal (unless it is the first proposal after initialization, in which case it stores the parent index as uint32.max
), and includes a l2BlockNumber
and claimed l2OutputRoot
.
Once a proposal is published and a OPSuccinctFaultDisputeGame
created, the dispute game can be in one of several states:
- Unchallenged: The initial state of a new proposal.
- Challenged: A proposal that has been challenged but not yet proven.
- UnchallengedAndValidProofProvided: A proposal that has been proven valid with a verified proof.
- ChallengedAndValidProofProvided: A challenged proposal that has been proven valid with a verified proof.
- Resolved: The final state after resolution, either
GameStatus.CHALLENGER_WINS
orGameStatus.DEFENDER_WINS
.
Note that "challenging" a proposal does not require a proof--as we want challenges to be able to be submitted quickly, without waiting for proof generation delay. Once a challenge is submitted, then the proposal's "timeout" is set to MAX_PROVE_DURATION
parameter that allows for an extended amount of time to generate a proof to prove that the original proposal is correct. If a proof of validity is not submitted by the deadline, then the proposal is assumed to be invalid and the challenger wins. If a valid proof is submitted by the deadline, then the original proposer wins the dispute. Note that if a parent game is resolved in favor of a challenger wining, then any child game will also be considered invalid.
Illustrative Example
graph TB A[Genesis State Block: 0 Root: 0x123] --> |Parent| B[Proposal 1 Block: 1000 Root: 0xabc Game Status: DEFENDER_WINS] B --> |Parent| C[Proposal 2A Block: 2000 Root: 0xdef Game Status: CHALLENGER_WINS] B --> |Parent| D[Proposal 2B Block: 2000 Root: 0xff1 Game Status: Unchallenged] C --> |Parent| E[Proposal 3A Block: 3000 Root: 0xbee Game Status: Unchallenged] D --> |Parent| F[Proposal 3B Block: 3000 Root: 0xfab Game Status: ChallengedAndValidProofProvided] classDef genesis fill:#d4edda,stroke:#155724 classDef defender_wins fill:#28a745,stroke:#1e7e34,color:#fff classDef challenger_wins fill:#dc3545,stroke:#bd2130,color:#fff classDef unchallenged fill:#e2e3e5,stroke:#383d41 classDef proven fill:#17a2b8,stroke:#138496,color:#fff class A genesis class B defender_wins class C challenger_wins class D,E unchallenged class F proven
In this example, Proposal 3A would always resolve to CHALLENGER_WINS
, as its parent 2A has CHALLENGER_WINS
. Proposal 3B would resolve to DEFENDER_WINS
if and only if its parent 2B successfully is unchallenged past its deadline and the final status is DEFENDER_WINS
.
Contract Description
Immutable Variables
MAX_CHALLENGE_DURATION
: Time window during which a proposal can be challenged.MAX_PROVE_DURATION
: Time allowed for proving a challenge.GAME_TYPE
: The type of the game, which is set in theDisputeGameFactory
contract.DISPUTE_GAME_FACTORY
: The factory contract that creates this game.SP1_VERIFIER
: The verifier contract that verifies the SP1 proof.ROLLUP_CONFIG_HASH
: Hash of the chain's rollup configurationAGGREGATION_VKEY
: The verification key for the aggregation SP1 program.RANGE_VKEY_COMMITMENT
: The commitment to the BabyBear representation of the verification key of the range SP1 program.PROOF_REWARD
: Amount of ETH required to submit a challenge (the reward given to a proof generator).ANCHOR_STATE_REGISTRY
: The anchor state registry contract.ACCESS_MANAGER
: The access manager contract.
Types
ProposalStatus
While GameStatus
(IN_PROGRESS, DEFENDER_WINS, CHALLENGER_WINS) represents the final outcome of the game, we need ProposalStatus
to:
- allow proving for fast finality even if the proposal is unchallenged.
- allow anyone to prove a proposal even the prover is not the proposer.
Represents the current state of a proposal in the dispute game:
enum ProposalStatus {
Unchallenged, // Initial state: New proposal without challenge
Challenged, // Challenged by someone, awaiting proof
UnchallengedAndValidProofProvided, // Valid proof provided without any challenge
ChallengedAndValidProofProvided, // Valid proof provided after being challenged
Resolved // Final state after game resolution
}
Proposal Status Transitions:
Unchallenged
→Challenged
(viachallenge()
)UnchallengedAndValidProofProvided
(viaprove()
)Resolved
(viaresolve()
)
Challenged
→ChallengedAndValidProofProvided
(viaprove()
)Resolved
(viaresolve()
)
UnchallengedAndValidProofProvided
→Resolved
(viaresolve()
)ChallengedAndValidProofProvided
→Resolved
(viaresolve()
)
ClaimData
Core data structure holding the state of a proposal:
struct ClaimData {
uint32 parentIndex; // Reference to parent game (uint32.max for first game)
address counteredBy; // Address of challenger (address(0) if unchallenged)
address prover; // Address that provided valid proof (address(0) if unproven)
Claim claim; // The claimed L2 output root
ProposalStatus status; // Current status of the proposal
Timestamp deadline; // Time by which proof must be provided
}
Key differences from Optimism's implementation:
parentIndex
is initialized oninitialize()
.- Simplified to single claim instead of array of claims.
- Removed
claimant
andposition
fields since there is no bisection. - Removed
bond
field since bonds are stored in the game contract. - Added
prover
field to enable anyone to prove a proposal even the prover is not the proposer. - Added proposal status.
- Uses deadline instead of clock with period getters.
Key Functions
Initialization
function initialize() external payable virtual
Initializes the dispute game with:
-
Initial state of the game
-
startingOutputRoot
: Starting point for verification- For first game: Anchor state root for the game type
- For subsequent games: Parent game's root claim and block number
-
claimData
: Core game stateparentIndex
: Reference to the parent game (uint32.max
for first game)counteredBy
: Initiallyaddress(0)
prover
: Initiallyaddress(0)
claim
: Proposer's claimed output rootstatus
: Set toProposalStatus.Unchallenged
deadline
: Set toblock.timestamp + MAX_CHALLENGE_DURATION
-
-
Validation Rules
-
Parent Game (if not first game)
- Must not have been blacklisted
- Must have been respected game type when created
- Must not have been won by challenger
-
Output Root
- Must be after starting block number
- First game starts from the anchor state root for the game type
- Later games start from parent's output root
-
-
Bond Management
- Proposer's bond enforced by factory
- Held in contract until resolution
Initialization will revert if:
- Already initialized
- Invalid parent game
- Root claim at/before starting block
- Incorrect calldata size for
extraData
- Proposer is not whitelisted
Challenge
function challenge() external payable returns (ProposalStatus)
Allows participants to challenge a proposal by:
- Depositing the proof reward (the challenge bond)
- Setting the proposal deadline to be
+ provingTime
over the current timestamp - Updating proposal state to
ProposalStatus.Challenged
Attempting to challenge a game will revert if:
- Game is already challenged, proven or resolved
- Deadline has already passed
- Challenger is not whitelisted
Proving
function prove(bytes calldata proofBytes) external returns (ProposalStatus)
Validates a proposal with a proof:
-
Timing Requirements
- Must be submitted before the proof deadline
- Clock starts when game is created (for unchallenged proofs)
- Clock starts when game is challenged (for challenged proofs)
-
Proof Verification
- Uses SP1 verifier to validate the aggregation proof against public inputs:
- L1 head hash (from game creation)
- Starting output root (from parent game or anchor state root)
- Claimed output root
- Claimed L2 block number
- Rollup configuration hash
- Range verification key commitment
- Uses aggregation verification key to verify the proof
- Uses SP1 verifier to validate the aggregation proof against public inputs:
-
State Updates
- Records the prover's address in
claimData.prover
- Updates proposal status if the proof is valid:
ProposalStatus.UnchallengedAndValidProofProvided
if there was no challengeProposalStatus.ChallengedAndValidProofProvided
if there was a challenge
- Records the prover's address in
-
Rewards
- No immediate reward distribution
- Proof reward is distributed in
claimCredit()
:- If challenged: prover receives the challenger's bond
- If unchallenged: no reward but can have fast finality
Attempting to submit a proof will revert if:
- Proof is not submitted before the proof deadline
- Proof is not valid
- If a prover tries to prove a game that has already been proven
Resolution
function resolve() external returns (GameStatus)
Resolves the game by:
- Checking parent game status. Ensures that the parent game is resolved and that the proposal is valid. If the proposal is invalid (aka
CHALLENGER_WINS
), then set the current game status toCHALLENGER_WINS
. - If the current game is in
UnchallengedAndValidProofProvided
orChallengedAndValidProofProvided
state, then setDEFENDER_WINS
. - Ensure that the deadline has passed, and if the proposal is
Unchallenged
, then setDEFENDER_WINS
. - Ensure that the deadline has passed, and if the proposal is
Challenged
, then setCHALLENGER_WINS
- Distributing bonds based on outcome
- Distribution result is stored in
mapping(address => uint256) public normalModeCredit;
. - Actual distribution is done when appropriate recipient calls
claimCredit()
. - If parent game is
CHALLENGER_WINS
, then the proposer's bond is distributed to the challenger. - But if there was no challenge for the current game, then the proposer's bond is burned.
- Distribution result is stored in
Attempting to resolve will revert if:
- Parent game is not yet resolved
- Game is already resolved (i.e, not
IN_PROGRESS
) - Challenge/proof window has not expired
Reward Distribution
function claimCredit(address _recipient) external
Claims rewards by:
- Closing the game and determining bond distribution mode if not already set
- Checking recipient's credit balance based on distribution mode:
NORMAL
: UsesnormalModeCredit
for standard game outcomesREFUND
: UsesrefundModeCredit
when game is improper
- Setting recipient's credit balance to 0
- Transferring ETH to recipient
Attempting to claim will revert if:
- Game is not resolved
- Game is not finalized according to AnchorStateRegistry
- Recipient has no credit to claim
- ETH transfer fails
- Invalid bond distribution mode
Bond distribution modes:
NORMAL
: Standard outcome-based distribution- Defender wins unchallenged: Proposer gets full bond
- Defender wins challenged: Prover gets reward, proposer gets remainder
- Challenger wins: Challenger gets full bond
REFUND
: Returns bonds to original depositors when game is improper
Security Model
Bond System
The contract implements a bond system to incentivize honest behavior:
- Proposal Bond: Required to submit a proposal.
- Proof Reward (Challenge Bond): Required to challenge a proposal, which is paid to successful provers.
Time Windows
Two key time windows ensure fair participation:
- Challenge Window: Period during which proposals can be challenged.
- Proving Window: Time allowed for submitting proofs after a challenge.
Parent-Child Relationships
- Each game (except genesis) has a parent
- Invalid parent automatically invalidates children
- Child games can only be resolved if their parent game is resolved
Acknowledgements
Zach Obront, who worked on the first version of OP Succinct, prototyped a similar MultiProof
dispute game implementation with OP Succinct as part of his work with the Ithaca team. This fault proof implementation takes some inspiration from his multiproof work.