OP Succinct

Documentation for OP Succinct users and developers.

OP Succinct transforms any OP Stack rollup into a fully type-1 ZK rollup using SP1. OP Succinct provides:

  • 1 hour finality secured by ZKPs, a dramatic improvement over the 7-day withdrawal window of standard OP Stack rollups.
  • Unlimited customization for rollup modifications in pure Rust and easy maitainability.
  • Cost-effective proving with an average cost of proving only fractions of cent per transaction (with an expected 5-10x improvement by EOY), thanks to SP1's blazing fast performance.

All of this has been possible thanks to close collaboration with the core team at OP Labs.

Reach out today if you want a Type-1 zkEVM rollup powered by SP1 (either a new rollup or a conversion from an optimistic rollup).

Architecture

Prerequisites

Every OP Stack rollup is composed of four main components.

  • op-geth: Takes transactions from users and uses them to generate blocks and execute blocks.
  • op-batcher: Batches transactions from users and submits them to the L1.
  • op-node: Reads batch data from L1 and uses it to drive op-geth in non-sequencer mode to perform state transitions.
  • op-proposer: Posts an output root to L1 at regular intervals, which captures the L2 state so withdrawals can be processed.

You can read more about the components in the OP Stack Specification.

OP Succinct

OP Succinct is designed to be an upgrade that replaces the op-proposer component of the OP Stack and its associated on-chain contracts. This change allows the chain to progress only with ZK-proven blocks, while keeping the other components (op-geth, op-batcher, and op-node) unchanged.

Here is a high-level overview of the new components that are introduced in OP Succinct:

  • Span Program. A program that derives and executes batches of blocks. The program is written in Rust and designed to be executed inside the zkVM.
  • Aggregation Program. Aggregates proofs of span programs to reduce on-chain verification cost. This program is also written in Rust and designed to be executed inside the zkVM.
  • OP Succinct L2 Output Oracle. A solidity smart contract that contains an array of L2 state outputs, where each output is a commit to the state of the L2 chain. This contract already exists in Optimism's original system but is modified to verify proofs as the authentication mechanism.
  • OP Succinct Proposer. Observes the posted batches on L1 and controls the proving of the span and aggregation programs. OP Succinct Architecture

Getting Started

In this section, we'll guide you through upgrading an existing OP Stack chain to a fully type-1 ZK rollup using SP1 and OP Succinct.

The steps are the following:

  1. Deploy the OP Succinct L2 Output Oracle Contract. This contract is a modified version of the existing L2OutputOracle contract that uses SP1 to verify the execution and derivation of the L2 state transitions.
  2. Start the OP Succinct Proposer. This service is modified verison of the existing op-proposer service. It posts output roots to the L2 Output Oracle contract at regular intervals by orcheastrating the generation and aggregation of proofs.
  3. Update your OP Stack Chain Configuration. You will need to update your configuration to update the L2OutputOracle contract to the new implementation using your ADMIN key.

Getting Started

Prerequisites

Requirements

You must have the following installed:

You must have the following RPCs available:

  • L1 Archive Node
  • L1 Consensus (Beacon) Node
  • L2 Archive Node
  • L2 Rollup Node

If you do not have an L2 OP Geth node + rollup node running for your rollup, you can follow the node setup instructions to get started.

OP Stack Chain

The rest of this section will assume you have an existing OP Stack Chain running. If you do not have one, there are two ways you can get started:

  • Self-hosted. If you want to run your own OP Stack Chain, please follow Optimism's tutorial first.
  • Rollup-as-a-service providers. You can also use an existing RaaS provider, such as Conduit or Caldera. But you will need access to the admin keys for your rollup.

Deploy L2 Output Oracle

The first step in deploying OP Succinct is to deploy a Solidity smart contract that will verify ZKPs of OP derivation (OP's name for their state transition function) and contain the latest state root of your rollup.

Deployment

1) Clone the op-succinct repo:

git clone https://github.com/succinctlabs/op-succinct.git
cd op-succinct

2) Set environment variables:

cp .env.example .env

Set the following environment variables:

L1_RPC=...
L2_RPC=...
L2_NODE_RPC=...
PRIVATE_KEY=...
ETHERSCAN_API_KEY=...

3) Navigate to the contracts directory:

cd contracts

4) Set Deployment Parameters

Inside the contracts folder there is a file called zkl2ooconfig.json that contains the parameters for the deployment. You will need to fill it with your chain's specific details.

The following parameters are required: proposer, challenger, finalizationPeriod, owner, verifierGateway. The rest of the fields (startingBlockNumber, l2BlockTime, chainId and vkey) are automatically fetched by the fetch-rollup-config script which is invoked by the ZKDeployer forge script. To use a manually set startingBlockNumber, set USE_CACHED_STARTING_BLOCK to true.

ParameterDescription
startingBlockNumberThe L2 block number at which to start generating validity proofs. This should be set to the current L2 block number. You can fetch this with cast bn --rpc-url <L2_RPC>.
submissionIntervalThe number of L2 blocks between each L1 output submission.
l2BlockTimeThe time in seconds between each L2 block.
proposerThe Ethereum address of the proposer account. If address(0), anyone can submit proofs.
challengerThe Ethereum address of the challenger account. If address(0), no one can dispute proofs.
finalizationPeriodThe time period (in seconds) after which a proposed output becomes finalized. Specifically, the time period after which you can withdraw your funds against the proposed output.
chainIdThe chain ID of the L2 network.
ownerThe Ethereum address of the ZKL2OutputOracle owner, who can update the verification key and verifier address.
vkeyThe verification key for the aggregate program. Run cargo run --bin vkey --release to generate this.
rollupConfigHashThe hash of the rollup config. This is used for non-superchain OP stack configurations.
verifierGatewayThe address of the verifier gateway contract. The canonical Succinct verifiers can be found here.
l2OutputOracleProxyThe address of your OP Stack chain's L2 Output Oracle proxy contract which will be upgraded. Only used in ZKUpgrader.

5) Deploy the ZKL2OutputOracle contract:

This foundry script will deploy the ZKL2OutputOracle contract to the specified L1 RPC and use the provided private key to sign the transaction:

forge script script/ZKDeployer.s.sol:ZKDeployer \
    --rpc-url $L1_RPC \
    --private-key $PRIVATE_KEY \
    --ffi \
    --verify \
    --verifier etherscan \
    --etherscan-api-key $ETHERSCAN_API_KEY \
    --broadcast

If successful, you should see the following output:

Script ran successfully.

== Return ==
0: address 0x9b520F7d8031d45Eb8A1D9fE911038576931ab95

## Setting up 1 EVM.

==========================

Chain 11155111

Estimated gas price: 11.826818849 gwei

Estimated total gas used for script: 3012823

Estimated amount required: 0.035632111845100727 ETH

==========================

##### sepolia
✅  [Success]Hash: 0xc57d97ac588563406183969e8ea15bc06496915547114b1df4e024c142df07b4
Contract Address: 0x2e4a7Dc6F19BdE1edF1040f855909afF7CcBeDeC
Block: 6633852
Paid: 0.00858210364707003 ETH (1503205 gas * 5.709203766 gwei)


##### sepolia
✅  [Success]Hash: 0x1343094b0be4e89594aedb57fb795d920e7cc1a76288485e8cf248fa206321ed
Block: 6633852
Paid: 0.001907479233443196 ETH (334106 gas * 5.709203766 gwei)


##### sepolia
✅  [Success]Hash: 0x708ce24c69c2637cadd6cffc654cbe2114e9ea4ec1e69838cd45c1fa27981713
Contract Address: 0x9b520F7d8031d45Eb8A1D9fE911038576931ab95
Block: 6633852
Paid: 0.00250654027540581 ETH (439035 gas * 5.709203766 gwei)

✅ Sequence #1 on sepolia | Total Paid: 0.012996123155919036 ETH (2276346 gas * avg 5.709203766 gwei)
                                                                                                          

==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
##
Start verification for (2) contracts
Start verifying contract `0x9b520F7d8031d45Eb8A1D9fE911038576931ab95` deployed on sepolia

Submitting verification for [lib/optimism/packages/contracts-bedrock/src/universal/Proxy.sol:Proxy] 0x9b520F7d8031d45Eb8A1D9fE911038576931ab95.

...

Keep note of the address of the Proxy contract that was deployed, which in this case is 0x9b520F7d8031d45Eb8A1D9fE911038576931ab95.

It is also returned by the script as 0: address 0x9b520F7d8031d45Eb8A1D9fE911038576931ab95.

Proposer

Now that you have deployed the ZKL2OutputOracle contract, you can start the op-succinct-proposer service which replaces the normal op-proposer service in the OP Stack.

The op-succinct-proposer service will call to Succinct's Prover Network to generate proofs of the execution and derivation of the L2 state transitions.

The modified proposer performs the following tasks:

  1. Monitors L1 state to determine when to request a proof.
  2. Requests proofs from the OP Succinct server. The server sends requests to the Succinct Prover Network.
  3. Once proofs have been generated for a sufficiently large range, aggregates span proofs and submits them on-chain.

We've packaged the op-succinct-proposer service in a docker-compose file to make it easier to run.

1) Set Proposer Parameters

In the root directory, create a file called .env (mirroring .env.example) and set the following environment variables:

ParameterDescription
L1_RPCThe RPC URL for the L1 Ethereum node.
L1_BEACON_RPCThe RPC URL for the L1 Ethereum consensus node.
L2_RPCThe RPC URL for the L2 archive node (OP-Geth).
L2_NODE_RPCThe RPC URL for the L2 node.
POLL_INTERVALThe interval at which to poll for new L2 blocks.
L2OO_ADDRESSThe address of the L2OutputOracle contract.
PRIVATE_KEYThe private key for the op-proposer account.
L2_CHAIN_IDThe chain ID of the L2 network.
MAX_CONCURRENT_PROOF_REQUESTSThe maximum number of concurrent proof requests (default is 20).
MAX_BLOCK_RANGE_PER_SPAN_PROOFThe maximum block range per span proof (default is 30).
OP_SUCCINCT_SERVER_URLThe URL of the OP Succinct server (default is http://op-succinct-server:3000).
PROVER_NETWORK_RPCThe RPC URL for the Succinct Prover Network.
SP1_PRIVATE_KEYThe private key for the SP1 account.
SP1_PROVERThe type of prover to use (set to "network").
SKIP_SIMULATIONWhether to skip simulation of the proof before sending to the SP1 server (default is true).
USE_CACHED_DBWhether to use a cached database for the proposer (default is false). If set to true, the DB is persisted between runs, and can be re-used after the proposer shuts down.

2) Build the Proposer

Build the docker images for the op-succinct-proposer service.

docker-compose build

3) Run the Proposer

This command launches the op-succinct-proposer service in the background. It launches two containers: one container that manages proof generation and another container that is a small fork of the original op-proposer service.

After a few minutes, you should see the op-succinct-proposer service start to generate span proofs. Once enough span proofs have been generated, they will be verified in an aggregate proof and submitted to the L1.

docker-compose up

To see the logs of the op-succinct-proposer service, run:

docker-compose logs -f

and to stop the op-succinct-proposer service, run:

docker-compose down

Configuration

The last step is to update your OP Stack configuration to use the new ZKL2OutputOracle contract managed by the op-succinct-proposer service.

Self-Managed OP Stack Chains

If you are using a self-managed OP Stack chain, you will need to use your ADMIN key to update the existing L2OutputOracle implementation. Recall that the L2OutputOracle is a proxy contract that is upgradeable using the ADMIN key.

To update the L2OutputOracle implementation, you can use an existing script we have in the op-succinct repo:

forge script script/ZKUpgrader.s.sol:ZKUpgrader \
    --rpc-url $L1_RPC \
    --private-key $PRIVATE_KEY \
    --verify \
    --verifier etherscan \
    --etherscan-api-key $ETHERSCAN_API_KEY \
    --broadcast \
    --ffi

RaaS Providers

Cost Estimator

We provide a convenient CLI tool to estimate the RISC-V cycle counts (and cost) for generating ZKPs for a range of blocks for a given rollup.

Overview

First, add the following RPCs to your .env file for your rollup:

# L1 RPC
L1_RPC=
# L1 Consensus RPC
L1_BEACON_RPC=
# L2 Archive Node (OP-Geth)
L2_RPC=

It is required that the L2 RPC is an archival node for your OP stack rollup, with the "debug_dbGet" endpoint enabled.

Then run the following command:

RUST_LOG=info just cost-estimator <start_l2_block> <end_l2_block>

This command will execute op-succinct as if it's in production. First, it will divide the entire block range into smaller ranges optimized along the span batch boundaries. Then it will fetch the required data for generating the ZKP for each of these ranges, and execute the SP1 span program. Once each program finishes, it will collect the statistics and output the aggregate statistics for the entire block range. From this data, you can extrapolate the cycle count to a cost based on the cost per billion cycles.

Example

On Optimism Sepolia, proving the block range 15840000 to 15840050 (50 blocks) generates 4 span proofs, takes ~1.8B cycles and ~2 minutes to execute.

RUST_LOG=info just cost-estimator 15840000 15840050

...Execution Logs...

+--------------------------------+---------------------------+
| Metric                         | Value                     |
+--------------------------------+---------------------------+
| Batch Start                    |                16,240,000 |
| Batch End                      |                16,240,050 |
| Execution Duration (seconds)   |                       130 |
| Total Instruction Count        |             1,776,092,063 |
| Oracle Verify Cycles           |               237,150,812 |
| Derivation Cycles              |               493,177,851 |
| Block Execution Cycles         |               987,885,587 |
| Blob Verification Cycles       |                84,995,660 |
| Total SP1 Gas                  |             2,203,604,618 |
| Number of Blocks               |                        51 |
| Number of Transactions         |                       160 |
| Ethereum Gas Used              |                43,859,242 |
| Cycles per Block               |                74,736,691 |
| Cycles per Transaction         |                23,422,603 |
| Transactions per Block         |                        11 |
| Gas Used per Block             |                 3,509,360 |
| Gas Used per Transaction       |                 1,105,066 |
| BN Pair Cycles                 |                         0 |
| BN Add Cycles                  |                         0 |
| BN Mul Cycles                  |                         0 |
| KZG Eval Cycles                |                         0 |
| EC Recover Cycles              |                 9,407,847 |
+--------------------------------+---------------------------+

Misc

  • For large enough block ranges, the RISC-V SP1 program will surpass the SP1 memory limit. Recommended limit is 20-30 blocks.
  • Your L2 node must have been synced for the blocks in the range you are proving.

L2 Node Setup

Setup Instructions

  1. Clone ops-anton and follow the instructions in the README to set up your rollup.
  2. Go to op-node.sh and set the L2_RPC to your rollup RPC. Modify the l1 and l1.beacon to your L1 and L1 Beacon RPCs. Note: Your L1 node should be an archive node.
  3. If you are starting a node for a different chain, you will need to modify op-network in op-geth.sh here and network in op-node.sh here.
  4. In /L2/op-mainnet (or the directory you chose):
    1. Generate a JWT secret ./generate_jwt.sh
    2. docker network create anton-net (Creates a Docker network for the nodes to communicate on).
    3. just up (Starts all the services).

Your op-geth endpoint will be available at the RPC port chosen here, which in this case is 8547 (e.g. http://localhost:8547).

Your op-node endpoint (rollup node) will be available at the RPC port chosen here, which in this case is 5058 (e.g. http://localhost:5058).

Checking Sync Status

After a few hours, your node should be fully synced and you can use it to begin generating ZKPs.

To check your node's sync status, you can run the following commands:

op-geth:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' http://localhost:8547

op-node:

curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' http://localhost:5058