# Proof Node API Proposal [rendered](https://petstore.swagger.io/?url=https://gist.githubusercontent.com/han0110/5e9790e9fa224dc3aaa77264e7cbe5f3/raw/3334cdc71ba491f40732c5a5e7e47129aa2b67e8/api-v0.3.0.json) ## Introduction Proof Node (serves as the HTTP endpoint for [`HttpProofEngine`](https://github.com/eth-act/lighthouse/blob/feat/eip8025/beacon_node/execution_layer/src/eip8025/proof_engine.rs#L103)) provides HTTP APIs for CL to request for execution proof generation/verification of different EL/zkVM combinations. The execution proofs are generated asynchronously, and Server-Sent Events is used to notify CL whether a proof request fails or completes, when a proof request is complete, CL can then download the proof from a deterministic URL. A reference implementation can be found in [`zkboost@v0.3.0`](https://github.com/eth-act/zkboost/tree/v0.3.0). ## APIs - [`POST /v1/execution_proof_requests`](#POST-v1execution_proof_requests) - [` GET /v1/execution_proof_requests`](#GET-v1execution_proof_requests) - [` GET /v1/execution_proofs/{new_payload_request_root}/{proof_type}`](#GET-v1execution_proofsnew_payload_request_rootproof_type) - [`POST /v1/execution_proof_verifications`](#POST-v1execution_proof_verifications) --- #### `POST /v1/execution_proof_requests` Submit an execution proof request. - Query Parameters - `proof_types` / `string` / **required** Comma-separated list of [`proof_type`](#proof_type) values to generate. Must contain at least one valid value. Whitespace around commas is trimmed (e.g. `reth-sp1, ethrex-zisk` is accepted). - Request Body SSZ-encoded [`NewPayloadRequest`](#newpayloadrequest). The `Content-Type` header SHOULD be set to `application/octet-stream`, but the server does not enforce it and will attempt SSZ decoding regardless. - Responses - `200 OK` / `Content-Type: application/json` Proof request accepted. Returns the tree-hash root computed from the submitted `NewPayloadRequest`. ```json { "new_payload_request_root": "0x..." } ``` - `400 Bad Request` Invalid SSZ body, empty or missing `proof_types`, or unconfigured proof type. ```json { "code": 400, "message": "invalid SSZ body: ..." } ``` ```json { "code": 400, "message": "no zkVM configured for proof type 'reth-sp1'" } ``` ```json { "code": 400, "message": "proof_types must contain at least one valid value" } ``` - `500 Internal Server Error` Internal queue failure. ```json { "code": 500, "message": "failed to enqueue proof: ..." } ``` --- #### `GET /v1/execution_proof_requests` Subscribe to execution proof events via Server-Sent Events (SSE). The response uses `Content-Type: text/event-stream`. The server sends SSE comment lines (`:` prefix, per [SSE spec](https://html.spec.whatwg.org/multipage/server-sent-events.html)) every 15 seconds as keep-alive. These are not events and should be ignored by conforming SSE clients. When `new_payload_request_root` is provided, the server first emits cached `proof_complete` events for cached proofs that completed before the subscription started (catch-up), then streams live events filtered to that root. Completed proofs are held in a bounded LRU cache. Clients should download proofs promptly after receiving a `proof_complete` event, as proofs may be evicted under memory pressure. - Query Parameters - `new_payload_request_root` / [`Hash256`](#hash256) / optional Filter events to only this root. When omitted, all events are streamed. - Event Types - `proof_complete` A proof has been generated and is ready for download at the deterministic URL `/v1/execution_proofs/{new_payload_request_root}/{proof_type}`. ```json { "new_payload_request_root": "0x...", "proof_type": "..." } ``` - `proof_failure` Proof generation has failed. The `reason` field indicates the category of failure (see [`failure_reason`](#failure_reason)). The `error` field always contains a human-readable message. ```json { "new_payload_request_root": "0x...", "proof_type": "...", "reason": "witness_timeout", "error": "witness timeout after 12 seconds" } ``` ```json { "new_payload_request_root": "0x...", "proof_type": "...", "reason": "proving_timeout", "error": "proving timeout after 12 seconds" } ``` ```json { "new_payload_request_root": "0x...", "proof_type": "...", "reason": "proving_error", "error": "..." } ``` --- #### `GET /v1/execution_proofs/{new_payload_request_root}/{proof_type}` Download a completed execution proof. - Path Parameters - `new_payload_request_root` / [`Hash256`](#hash256) The root from the original proof request response. - `proof_type` / [`proof_type`](#proof_type) The proof type to download. - Responses - `200 OK` / `Content-Type: application/octet-stream` Returns raw proof bytes. - `404 Not Found` / `Content-Type: application/json` Proof not yet available or does not exist. ```json { "code": 404, "message": "proof not found for root 0x... and type reth-sp1" } ``` --- #### `POST /v1/execution_proof_verifications` Verify an execution proof. A `200 OK` response with `"status": "INVALID"` means that the verification process ran successfully but the proof did not pass verification. HTTP error status codes (4xx, 5xx) indicate that the request itself was malformed or the server could not process it. - Query Parameters - `new_payload_request_root` / [`Hash256`](#hash256) / **required** The tree hash root of the `NewPayloadRequest` the proof was generated for. The server passes this value to the zkVM verifier to check that the proof's public values match the expected payload root. - `proof_type` / [`proof_type`](#proof_type) / **required** The proof type to verify. - Request Body Proof bytes. The `Content-Type` header SHOULD be set to `application/octet-stream`, but the server does not enforce it. - Responses - `200 OK` / `Content-Type: application/json` Verification result. ```json { "status": "VALID" } ``` ```json { "status": "INVALID" } ``` - `404 Not Found` / `Content-Type: application/json` The requested proof type is not configured on this server. ```json { "code": 404, "message": "unknown proof_type: reth-sp1" } ``` ## Common Types ### `Hash256` A 32-byte hash represented as a `0x`-prefixed lowercase hex string (66 characters total). The `0x` prefix is required in both requests and responses. Pattern: `^0x[0-9a-f]{64}$` Example: `"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"` ### `proof_type` A case-sensitive string identifying an EL/zkVM combination. Allowed values: | Value | EL | zkVM | | -------------- | ------ | --------- | | `ethrex-risc0` | Ethrex | RISC Zero | | `ethrex-sp1` | Ethrex | SP1 | | `ethrex-zisk` | Ethrex | Zisk | | `reth-openvm` | Reth | OpenVM | | `reth-risc0` | Reth | RISC Zero | | `reth-sp1` | Reth | SP1 | | `reth-zisk` | Reth | Zisk | ### `failure_reason` A case-sensitive string indicating why a proof request failed. Allowed values: | Value | Description | | ----------------- | ------------------------------------------------------------------------- | | `witness_timeout` | The execution witness could not be fetched within the configured timeout. | | `proving_timeout` | Proof generation did not complete within the configured timeout. | | `proving_error` | An error occurred during proving. The `error` field contains the message. | ### `ProofStatus` A case-sensitive string indicating the result of proof verification. Allowed values: | Value | Description | | --------- | --------------------------------------------------------------------- | | `VALID` | The proof passed verification. | | `INVALID` | The verification process ran successfully but the proof did not pass. | ### `NewPayloadRequest` The SSZ-encoded payload sent by the CL for proof generation. This is the same SSZ type used in the Engine API (`engine_newPayload`), defined in the [consensus-specs](https://github.com/ethereum/consensus-specs). It uses transparent SSZ union encoding across fork variants (Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), meaning there is no selector byte; the fork variant is determined by the structure and length of the SSZ payload. The reference Rust implementation is in the [lighthouse](https://github.com/sigp/lighthouse) types crate. ### Error Response All error responses use the following JSON format with `Content-Type: application/json`: ```json { "code": 400, "message": "<error message>" } ``` ## Rationale - Send `NewPayloadRequest` in SSZ-encoded bytes to server - Already supported by all CL impls - Avoid JSON overhead - Using SSE to notify if a proof request fails or completes - Avoid polling, which may introduce delay after the proof completes. - Broadly supported across programming languages, should have minimal friction for CL integration (compared to gRPC or WebSocket). - SSE sends only proof status to avoid unwanted proof being sent to CL - Using `new_payload_request_root` as the deterministic `proof_gen_id` - Allow falling-behind CL to download the proof directly - Easier debug