IZKVerifier Interface

Table of Contents

Overview

IZKVerifier is the on-chain interface that decouples ZKDisputeGame from any specific proving system. The game always casts the configured verifier address to IZKVerifier and calls verify() through the interface, rather than depending on a concrete verifier type.

By default, the verifier is the SP1PlonkAdapter singleton deployed with each OPCM release. It wraps Succinct's PLONK verifier for SP1 (SP1 PLONK v6.1.0 on Ethereum mainnet and Sepolia).

Interface

interface IZKVerifier is ISemver {
    function verifierType() external view returns (string memory);

    function verify(bytes32 _programId, bytes calldata _publicValues, bytes calldata _proof) external view;
}

verifierType() returns a string identifying the proving system. For the SP1PlonkAdapter it is "SP1-PLONK-" followed by the wrapped verifier's VERSION() (e.g., "SP1-PLONK-v6.1.0"). Callers can use it to check which backend is deployed without inspecting the bytecode.

ParameterDescription
_programIdCorresponds to absolutePrestate() in the dispute game — identifies the ZK program version.
_publicValuesABI-encoded public inputs committed to by the proof (see Usage in prove()).
_proofThe raw proof blob submitted by the prover.

The verifier MUST revert on an invalid proof. A call that returns without reverting is treated as proof acceptance; the game will set claimData.prover = msg.sender and transition claimData.status to UnchallengedAndValidProofProvided or ChallengedAndValidProofProvided.

SP1PlonkAdapter

SP1PlonkAdapter (version 1.0.0) implements IZKVerifier on top of an SP1 PLONK verifier. It is deployed as a non-proxied singleton once per OPCM release.

  • The constructor takes the wrapped ISP1Verifier and MUST revert with SP1PlonkAdapter_InvalidVerifier() if the given address has no code.
  • sp1Verifier() returns the wrapped ISP1Verifier.
  • verify(programId, publicValues, proof) forwards its arguments unchanged to ISP1Verifier.verifyProof(programId, publicValues, proof) and reverts if that call reverts.

The wrapped verifier's VERIFIER_HASH() identifies the PLONK circuit it accepts proofs for. It MUST match the circuit used by the prover's SP1 SDK, otherwise every proof fails verification. OPCM release verification (VerifyOPCM) checks both the wrapped verifier address and its VERIFIER_HASH().

Usage in prove()

ZKDisputeGame.prove() constructs publicValues from on-chain game state and forwards them to the verifier:

function prove(bytes calldata _proofBytes) external returns (ProposalStatus) {
    bytes memory publicValues = abi.encode(
        l1Head(),
        startingProposal.root,
        rootClaim(),
        l2SequenceNumber(),
        msg.sender
    );

    IZKVerifier(verifier()).verify(
        absolutePrestate(),
        publicValues,
        _proofBytes
    );

    // record proof submission and transition claimData.status
}
FieldSourceDescription
l1HeadCWIA (immutable)L1 block hash captured at game creation. Authenticates all observed L1 data.
startingProposal.rootStorage (set in initialize)Super root hash of the parent game's claim, or the anchor state if parentIndex == type(uint32).max.
rootClaimCWIA (immutable)Super root hash being asserted by this game.
l2SequenceNumberCWIA (immutable)Super root timestamp corresponding to rootClaim.
msg.senderTransactionAddress of the prover. Binds the proof to the submitter, preventing front-running: a proof is generated for a specific address and cannot be re-submitted by a different address without regenerating the entire proof.

All public values come from immutable CWIA data or storage set during initialize(), so no caller-supplied data beyond _proofBytes affects what the verifier checks. l1Head is captured as blockhash(block.number - 1) by DisputeGameFactory.create() and packed into the clone's CWIA data.

Chain scoping is provided by the SuperRootProof preimage committed to by rootClaim; the (chainId, outputRoot) pairs in that preimage cover every chain in the interop set (or a single entry in standalone deployments), so no separate l2ChainId field is needed in the public values. See ZK Program Inputs for the full public values specification.

Verifier Upgrade Path

The verifier and absolutePrestate fields live in the CWIA game args (see CWIA Layout). Both change by setting new game args for ZK_DISPUTE_GAME in the DisputeGameFactory:

  • An OPCM upgrade() applies the current release defaults: verifier is always the release's SP1PlonkAdapter (see ZKDisputeGameConfig), and absolutePrestate comes from ZKDisputeGameConfig.
  • The DisputeGameFactory owner can also manage them out of band with setImplementation(ZK_DISPUTE_GAME, impl, gameArgs). A later OPCM upgrade() resets verifier to the release's SP1PlonkAdapter.

Existing game clones keep the verifier and absolutePrestate stored in their own CWIA data, so in-progress games play out under the old configuration. New games use the updated game args.