Engine API Proxy That Proves Execution Payloads
zkboost is an Engine API proxy between a consensus client (CL) and its execution client (EL). It forwards every Engine API request to the EL. For each valid engine_newPayloadV5 payload it generates zkVM proofs of the stateless block execution. It signs them as a validator and submits them to a beacon node as EIP-8025 execution proof envelopes.
- Table of Contents
- Quick Start
- Manual Build
- Configuration
- Engine API
- Proof Submission
- Mock Beacon Node
- Observability
- Supported Backends
- Contributing
- License
See docker/example/testnet for a Docker Compose setup that runs zkboost with real Ere backends on a local testnet. See docker/example/mock-beacon-node for mock backends without a GPU. There mock-beacon-node stands in for the beacon node.
- Rust ≥ 1.94.1
# 1. Clone
git clone https://github.com/eth-act/zkboost.git && cd zkboost
# 2. Build
cargo build --release
# 3. Run
./target/release/zkboost --config <config-path>zkboost reads a TOML file passed with --config <path>. The annotated example below shows every option.
# Engine API server port (default: 3000)
port = 3000
# Ethereum execution layer Engine API endpoint (required).
# Every Engine API request is forwarded to this endpoint.
el_engine_endpoint = "http://localhost:8551"
# Beacon API endpoint that receives every proof at POST /eth/v1/beacon/execution_proofs (required).
cl_beacon_endpoint = "http://localhost:4000"
# EIP-2335 keystore of the validator that signs the proofs and its plain text password (required).
# The files are the ones a lighthouse validator client reads from its validators and secrets directories.
# The validator index is read from the beacon node.
validator_keystore_path = "/validator-keys/keys/<pubkey>/voting-keystore.json"
validator_keystore_password_path = "/validator-keys/secrets/<pubkey>"
# Built-in dashboard served at /dashboard
[dashboard]
# enabled = false
# retention = 256
# External Ere server (calls a remote ere-server via HTTP)
[[zkvm]]
kind = "ere"
proof_type = "ethrex-zisk"
# Timeout for proof generation in seconds (default: 12)
# proof_timeout_secs = 12
# Endpoint of the Ere server
endpoint = "http://ere-server:3000"
# External proving cluster (ZisK and OpenVM proof types)
[[zkvm]]
kind = "cluster"
proof_type = "reth-zisk"
endpoint = "http://zisk-cluster:50051"
elf_url = "https://example.com/stateless-validator-reth-zisk.elf"
# elf_path = "/path/to/program.elf" # mutually exclusive with elf_url
# An OpenVM cluster is the manager of the han0110/axiom-edge fork. Its loadout must hold the guest named after the ELF.
[[zkvm]]
kind = "cluster"
proof_type = "reth-openvm"
endpoint = "http://openvm-cluster:3000"
elf_url = "https://example.com/stateless-validator-reth-openvm.elf"
# Mock zkVMs (in-process). The mock sleeps for the simulated proving time and returns the fixture
# proof of the proof type, a valid proof of another block. A zesu-zisk mock needs endpoint, because
# its guest has no fixture proof.
# Fixed proving time (default)
[[zkvm]]
kind = "mock"
proof_type = "reth-sp1"
mock_proving_time = { kind = "constant", ms = 6000 }
# An Ere server that runs the mock guest of the zkVM from docker/example/mock-beacon-node/mock-guest.
# The mock then proves the expected public values there. mock_proving_time and mock_failure do not apply.
# endpoint = "http://ere-server:3000"
# Random proving time uniformly sampled from [min_ms, max_ms]
[[zkvm]]
kind = "mock"
proof_type = "ethrex-sp1"
mock_proving_time = { kind = "random", min_ms = 2000, max_ms = 8000 }
# Proving time proportional to block gas (ms_per_mgas * gas_used / 1000_000)
[[zkvm]]
kind = "mock"
proof_type = "ethrex-openvm"
mock_proving_time = { kind = "linear", ms_per_mgas = 300 }
# Simulated failure (always returns a proving error)
[[zkvm]]
kind = "mock"
proof_type = "reth-openvm"
mock_failure = trueAvailable proof types:
| Name | EL | zkVM |
|---|---|---|
ethrex-openvm |
ethrex |
OpenVM |
ethrex-sp1 |
ethrex |
SP1 |
ethrex-zisk |
ethrex |
ZisK |
reth-openvm |
reth |
OpenVM |
reth-sp1 |
reth |
SP1 |
reth-zisk |
reth |
ZisK |
zesu-zisk |
zesu |
ZisK |
zkboost serves the Engine API as JSON-RPC at POST /. The CL uses zkboost as its Engine API endpoint in place of the EL.
- Every request is forwarded byte-for-byte to
el_engine_endpoint. The EL response is returned to the CL. - The
AuthorizationJWT header of the CL is passed through unchanged. The EL validates the JWT. zkboost does not validate it. - zkboost takes the chain id from
DEPOSIT_CHAIN_IDof the beacon node spec. engine_newPayloadV5is intercepted. zkboost sends it to the EL asengine_newPayloadWithWitnessV5, which returns the execution witness in the payload status.- zkboost removes the
witnessfield from the payload status before it answers the CL. - When the payload status is
VALID, zkboost builds the stateless input from the payload, the witness, and the chain id. It then dispatches proving to every configured zkVM backend in the background. - Every payload is proven under the Amsterdam rules.
engine_newPayloadV1toengine_newPayloadV4are forwarded without proving. - A payload imported again after a reorg is submitted with the cached proof, not proven twice. zkboost keeps the proofs of the last 64 payloads per proof type, two epochs.
The following endpoints are also available:
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/health |
Health check |
GET |
/metrics |
Prometheus metrics |
GET |
/dashboard |
Built-in dashboard UI (when enabled) |
GET |
/dashboard/state |
Dashboard state JSON snapshot |
GET |
/dashboard/events |
Dashboard SSE event stream |
zkboost delivers every generated proof to the beacon node at cl_beacon_endpoint as an EIP-8025 SignedExecutionProofEnvelope.
zkboost reads these routes of the beacon API.
| Route | Reads | How often |
|---|---|---|
GET /eth/v1/config/spec |
The chain spec | At startup |
GET /eth/v1/beacon/genesis |
The genesis validators root | At startup |
GET /eth/v1/beacon/states/head/validators/{pubkey} |
The index of the signing validator | At startup |
GET /eth/v1/events?topics=execution_payload |
The beacon block root of every imported payload | Streamed |
GET /eth/v1/beacon/headers?parent_root= |
The children of the parent beacon block root | Per proof without an event |
GET /eth/v2/beacon/blocks/{root} |
The payload bid of a listed child | Per listed child |
zkboost forwards payloads without proving until the beacon node answers the three startup routes, and logs one warning per attempt.
Every proof follows these steps.
- zkboost takes the beacon block root from the
execution_payloadevent of the block hash and the slot of the payload. The beacon node sends that event when it imports the payload envelope. - Without such an event, zkboost lists the children of the parent beacon block root. It reads the payload bid of every listed child and takes the child whose bid carries the block hash.
- zkboost compares the slot of the header of that child with the slot of the payload.
- zkboost signs the envelope under
DOMAIN_EXECUTION_PROOFwith the fork version at the slot of the block. It takes that fork version from the chain spec, as a validator client does. - zkboost sends the envelope with
POST /eth/v1/beacon/execution_proofs. The body is the SSZ encoding of a list of at most 4 envelopes and has the headerContent-Type: application/octet-stream.
zkboost submits no proof in these cases.
- Two children carry the payload in their bid, which shows a proposer equivocation. A Gloas payload bid commits to the slot and the parent, therefore one child only is valid.
- The slot of the child header differs from the slot of the payload. The execution header does not commit to the slot, therefore zkboost compares the two sources.
The table gives the EIP-8025 proof type of every zkboost proof type.
proof_type |
zkboost proof type |
|---|---|
1 |
ethrex-openvm |
2 |
ethrex-sp1 |
3 |
ethrex-zisk |
4 |
reth-openvm |
5 |
reth-sp1 |
6 |
reth-zisk |
7 |
zesu-zisk |
mock-beacon-node mocks a beacon node with the EIP-8025 behavior. It stands in for the beacon node in the examples.
- It serves the Engine API to a CL and forwards every request to zkboost with the JWT of the CL unchanged. zkboost therefore receives the Engine API traffic of a real CL.
- It receives the proofs at
POST /eth/v1/beacon/execution_proofs. It looks up the beacon block of every envelope at the CL and verifies the validator signature as the beacon node does. - It verifies each proof with
ere-verifierand checks its public values againstcrates/server/src/proof/zkvm/mock/public_values.bin, the ones of the fixture proofs, with zero padding allowed. A proof of the live payload is therefore rejected. - It forwards every other beacon API request to the CL. zkboost therefore uses the mock beacon node as its
cl_beacon_endpoint.
| Flag | Description |
|---|---|
--cl-endpoint <URL> |
Beacon API endpoint of the CL whose Engine API requests the mock forwards |
--zkboost-endpoint <URL> |
Engine API endpoint of zkboost |
--proof-types <a,b> |
Proof types expected for every payload |
--port <u16> |
Port serving the beacon API (default: 3001) |
--engine-port <u16> |
Port serving the Engine API to the CL (default: 8551) |
zkboost exposes Prometheus-compatible metrics at /metrics for monitoring with Prometheus and Grafana.
The Docker Compose setup includes pre-configured Prometheus and Grafana with a zkboost dashboard. Start the Kurtosis testnet as in docker/example/observability first.
docker compose -f docker/example/observability/docker-compose.yml up -d| Service | URL | Credentials |
|---|---|---|
| zkboost | http://localhost:3000 | - |
| Prometheus | http://localhost:9090 | - |
| Grafana | http://localhost:3002 | admin / admin |
The zkboost dashboard is auto-provisioned and available at Grafana > Dashboards > zkboost.
| Metric | Type | Description |
|---|---|---|
zkboost_http_requests_total |
Counter | Total HTTP requests by endpoint, method, status |
zkboost_http_request_duration_seconds |
Histogram | Request latency by endpoint |
zkboost_http_requests_in_flight |
Gauge | Currently processing requests |
zkboost_witness_fetch_total |
Counter | Witness fetch round trips by status |
zkboost_witness_fetch_duration_seconds |
Histogram | Witness fetch round trip time |
zkboost_witness_bytes |
Histogram | Witness sizes of valid payloads |
zkboost_queue_wait_duration_seconds |
Histogram | Time a proof request waits for a worker |
zkboost_prove_total |
Counter | Prove operations by proof type and status |
zkboost_prove_duration_seconds |
Histogram | Proof generation time |
zkboost_prove_proof_bytes |
Histogram | Generated proof sizes |
zkboost_programs_loaded |
Gauge | Number of loaded zkVMs |
zkboost_build_info |
Gauge | Build version info |
zkboost integrates backends through Ere. Any backend that Ere supports can be added.
Contributions are welcome!
Dual‑licensed under Apache‑2.0 and MIT. Choose either license at your discretion.
