---
# System prepended metadata

title: zkEL - Considerations & Design

---

# zkEL - Considerations & Design

## Considerations

### Consensus Considerations

The consensus client is dependant on the execution client to determine the validity of a block. This is achieved via invocation of the engine API `engine_newPayload` method. In the case of using execution proofs it is possible that the execution proofs have not been produced and gossiped by the time the block is being imported into the consensus client and as such the payload will be assigned a `SYNCING` status. There are two approaches we can take to address this problem:
1) Withhold import into fork choice store until execution proofs are available
2) Import optimistically into the fork choice store and update payload status when execution proof validity conditions are satisfied

Withholding import into the fork choice store will ultimately result in attestations being produced for the latest block for which the execution payload has been validated. In essence this would mean that the validator would attest to an old head. Voting on an old head is permissible in the Fulu spec however this becomes more complicated for Gloas as described in the section below.

All major consensus clients support optimistic block imports into the fork choice store as defined by the consensus spec ([link](https://ethereum.github.io/consensus-specs/sync/optimistic/)). The implication of importing blocks optimistically is that consensus clients will not engage in consensus (sign attestations etc) whilst the head block is considered optimistic. This is well defined and easy to understand behaviour, however the concern here is that if execution proofs are frequently delivered after the attestation deadline then the node could miss many slots and be panalised for this ([link](https://eth2book.info/capella/part2/incentives/penalties/#attestation-penalties)). Under this regime the consensus client would have to be "online" (i.e. not in optimistic mode) approximately 42.5% of the time to break even ([link](https://eth2book.info/capella/part2/incentives/penalties/#break-even-uptime)).

If a significant subset of validators are performing execution payload validation using execution proofs then there is a concern that a builder could intentionally trigger an inactivity leak by building a block for which execution proofs can not be generated.  In this case an inactivity leak could be triggered if > 1/3 of validators (in terms of voting weight) are using execution proofs.

### Gloas Fork Considerations

The Gloas hardfork will include ePBS - separation of execution payload validation from beacon block validation. We should ensure that any design is compatible with this change. Gloas introduces a new definition for the term payload status ([link](https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/fork-choice.md#custom-types)). In the context of Gloas payload status is defined to represent if the execution payload that has been comitted to in the beacon block has been made available (and is valid), been witheld or is abscent. This should not be confused with pre-existing definition of payload status which is excluisvely concerned with validity of execution payloads.

In Gloas the `LatestMessage` type has been modified such that the attester must specify the `slot` at which the attestation is being made for as opposed to the `epoch` ([link](https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/fork-choice.md#modified-latestmessage)). This has tangible implications for option 1 defined above where we withhold import of blocks. This is because a vote for an old head represents a vote on a fork of the chain in which at the current slot n the head is the head of a previous block with slot < n (suggesting missed slots from the latest slot we have been able to validate the execution payload for to the current slot). This will result in validators with timely execution payload validation (i.e. re-execution nodes) and validators using execution proof verification producing conflicting attestations when execution proofs are late (calls to `is_supporting_vote` will return false - [link](https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/fork-choice.md#new-is_supporting_vote)).

In the future if/when we make execution proofs enforced we can change the Gloas definition of payload status to mean that the execution payload and associated execution proofs been made available. It will be the responsibility of the builder to produce and publish execution proofs in a timely fashion. The execution proofs could be included in the `SignedExecutionPayloadEnvelope` ([link](https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/builder.md#constructing-the-signedexecutionpayloadenvelope)) or out of band on a separate gossip topic. If the proofs are not made available in a timely fashion the PTC and attesters will vote accordingly ([link](https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/fork-choice.md#new-notify_ptc_messages)).


### EngineAPI Considerations

The Engine API was introduced to provide a well-defined interface through which the protocol queries the validity of execution-layer blocks and chains. This interface has evolved incrementally to support new protocol features (e.g. blobs), while preserving its role as a stable boundary. Critically, how validity is evaluated is an implementation detail—whether via re-execution, alternative pipelines, or execution proofs.

Extending the Engine API to support execution proofs is therefore a natural and minimal evolution of the interface. By upholding the Engine API specification and using execution proofs to process requests, we minimise divergence from the spec, avoid introducing new coordination surfaces, and enable seamless integration with existing consensus clients and tooling.

#### EngineAPI Witness Support Proposal

Generation of an execution proof requires access to an execution witness produced by the execution client. In existing proving architectures, this witness is typically obtained via the `debug_executionWitness` JSON-RPC method ([link](https://reth.rs/jsonrpc/debug/#debug_executionwitness)). While functional, this approach incurs additional latency and overhead, as witness retrieval occurs out of band from the normal Engine API flow.

To improve efficiency and reduce end-to-end latency, this proposal considers extending `engine_newPayload` and `engine_getPayload` to optionally include execution witness data alongside existing responses. Integrating witness material into the Engine API payload lifecycle allows witness generation to be colocated with execution, avoiding redundant re-execution or secondary RPC round-trips. This approach maintains the Engine API as the sole integration surface while enabling more efficient execution proving.

### Execution Proof Considerations

An execution proof establishes correctness of a single block transition (i.e., that executing the block against its parent state yields the claimed post-state and receipts), but does not, by itself, establish:
- validity of the parent block (or any ancestor), or
- that the block is part of the canonical chain given the current fork choice state.

As a result, responding to Engine API methods cannot rely on validating an isolated proof in the general case. We must ensure that the block and associated chain being considered is valid.

Three approaches are considered:
- Naïve validation: validate execution proofs for the full prefix from genesis to the target block. This is correct but operationally infeasible.
- Recursive proving: each block proof additionally verifies the proof for its parent, yielding a single proof that attests to validity of the chain prefix up to the target block. This minimizes verification work but increases proof complexity and introduces recursion requirements.
- Trust anchor (weak subjectivity): treat the finalized block provided by the CL as a validity anchor. On the first `engine_forkchoiceUpdated`, we assume that blocks before the finalized block are valid, and validate execution proofs only for the suffix from finalized to the current head. This aligns with Ethereum’s weak subjectivity model and provides an operationally practical verification boundary.

### Fork Choice Considerations

To respond correctly to Engine API requests, we must reason about canonicality in addition to block validity. In particular:
- `engine_newPayload`: verify that the payload is consistent with the current fork-choice state, i.e. that the block extends the current canonical head.
- `engine_forkchoiceUpdated`: verify that the head, safe, and finalized block references are internally consistent (i.e., form a single chain) and are compatible with the current fork-choice state and are associated with valid blocks.

### Proof Provider Considerations

We require a mechanism to obtain execution proofs in order to validate Engine API requests. The Proof Provider is an internal abstraction responsible for fetching proofs and any associated metadata required for verification.

Two concrete instantiations are proposed:
- P2P proof distribution: proofs are disseminated over a dedicated proof network.
- HTTP RPC proof retrieval: proofs are fetched on-demand from a HTTP RPC endpoint exposed by a proof-serving node (likely a consensus client). This mode is suitable for constrained environments (e.g., mobile and browser-based clients) and enables deployments that combine a CL light client (e.g., Helios) with zkEN without requiring persistent P2P connectivity. This is analogous to fetching light clients request consensus proofs from a provider.

## Phase 0 Design - Optional Proofs

This section outlines some proposed changes to the current reference implementation ([link](https://github.com/eth-act/lighthouse/tree/optional-proofs)).

In alignment with the [Engine API](#EngineAPI-Considerations) section we propose the introduction of a `zkEngine`. This is the service that is responsible for handling Engine API requests. We introduce two new request types. `engine_newPayloadHeader` is for processing new payload headers received over the network.
```
def engine_newPayloadHeader(NewPayloadRequestHeader) -> PayloadStatus:
    ...
    return PayloadStatus

def engine_newExecutionProof(proof: ZKEVMProof) -> ExecutionProofResult:
    ...
    return ExecutionProofResult

class ExecutionProofResult:
    status: ExecutionProofStatus
    latest_valid_hash: Optional[Hash32]
    
classs ExecutionProofStatus(Enum):
    INVALID = 0
    VALID = 1
    KNOWN = 2
```

In `ExecutionProofResult` the `status` enum represents if the execution proof has passed validation and `latest_valid_hash` is the block hash of the latest canonical execution payload that has been validated by the execution proof. It should be noted that `latest_valid_hash` may not represent the block hash associated with the execution proof, it may instead be the block hash of a more recent descendent that is part of the canonical chain. This is because the descendent cannot be marked as valid until all ancestors are marked as valid. 

All other `EngineAPI` methods remain unchanged. It should be noted that `engine_newPayloadHeader` will return a payload status of `SYNCING` if the `zkEngine` has not received the required execution proofs yet.

Depending on whether we choose option 1 or 2 from the [Consensus Considerations](#Consensus-Considerations) section the downstream behaviour will be different. For option 1 if a beacon block which was previously in cache waiting for execution payload validation has been validated by the receipt of an execution proof the beacon block and any of its ancestors should be imported to the fork choice store. For option 2 if blocks are imported optimistically into the fork choice store then we should set the beacon block and all ancestors as valid upon execution payload validation result (example of validating optimistic blocks seen [here](https://github.com/eth-act/lighthouse/blob/93766c6e29905c20577610c9885a36e76fd29d9f/consensus/proto_array/src/proto_array_fork_choice.rs#L466-L474)).

In the case when a validator wants to self-build blocks then we should support a mode where the `zkEngine` also propagates engine requests to an external execution node essentially acting as a middleware.

It may be possible that the `zkEngine` can be structured in such a way that allows it to be used with any consensus client via HTTP such that this component can be used as a stop gap solution for client diversity. However the initial implementation will be structured as an in process service.

## Phase 1 Design - Enforced Proofs

TODO
