diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 27c7f8f817..3de73cf929 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -29,6 +29,8 @@ categories: label: eip7441 - title: EIP-7805 label: eip7805 + - title: EIP-8025 + label: eip8025 # Testing - title: Testing diff --git a/specs/_features/eip8025/beacon-chain.md b/specs/_features/eip8025/beacon-chain.md new file mode 100644 index 0000000000..513e257eef --- /dev/null +++ b/specs/_features/eip8025/beacon-chain.md @@ -0,0 +1,215 @@ +# EIP-8025 -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) +- [Constants](#constants) + - [Execution](#execution) + - [Domain types](#domain-types) +- [Configuration](#configuration) +- [Containers](#containers) + - [New containers](#new-containers) + - [`ExecutionProof`](#executionproof) + - [`SignedExecutionProof`](#signedexecutionproof) + - [Extended Containers](#extended-containers) +- [Helper functions](#helper-functions) + - [Execution proof functions](#execution-proof-functions) + - [`verify_execution_proof`](#verify_execution_proof) + - [`verify_execution_proofs`](#verify_execution_proofs) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Execution payload processing](#execution-payload-processing) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + + + +## Introduction + +This document contains the consensus specs for EIP-8025. This enables stateless +validation of execution payloads through cryptographic proofs. + +*Note*: This specification is built upon [Fulu](../../fulu/beacon-chain.md) and +is under active development. + +*Note*: This specification assumes the reader is familiar with the +[public zkEVM methods exposed](./zkevm.md). + +## Constants + +### Execution + +| Name | Value | +| ---------------------------------- | -------------------------------------- | +| `MAX_EXECUTION_PROOFS_PER_PAYLOAD` | `uint64(4)` | +| `PROGRAM` | `ProgramBytecode(b"DEFAULT__PROGRAM")` | + +### Domain types + +| Name | Value | +| ------------------------ | -------------------------- | +| `DOMAIN_EXECUTION_PROOF` | `DomainType('0x0B000000')` | + +## Configuration + +*Note*: The configuration values are not definitive. + +| Name | Value | +| ------------------------------- | ----------- | +| `MIN_REQUIRED_EXECUTION_PROOFS` | `uint64(1)` | + +## Containers + +### New containers + +#### `ExecutionProof` + +```python +class ExecutionProof(Container): + beacon_root: Root + zk_proof: ZKEVMProof + validator_index: ValidatorIndex +``` + +#### `SignedExecutionProof` + +```python +class SignedExecutionProof(Container): + message: ExecutionProof + signature: BLSSignature +``` + +### Extended Containers + +*Note*: `BeaconState` and `BeaconBlockBody` remain the same. No modifications +are required for execution proofs since they are handled externally. + +## Helper functions + +### Execution proof functions + +#### `verify_execution_proof` + +```python +def verify_execution_proof( + signed_proof: SignedExecutionProof, + parent_hash: Hash32, + block_hash: Hash32, + state: BeaconState, + el_program: ProgramBytecode, +) -> bool: + """ + Verify an execution proof against a payload header using zkEVM verification. + """ + + # Note: signed_proof.message.beacon_root verification will be done at a higher level + + # Verify the validator signature + proof_message = signed_proof.message + validator = state.validators[proof_message.validator_index] + signing_root = compute_signing_root(proof_message, get_domain(state, DOMAIN_EXECUTION_PROOF)) + if not bls.Verify(validator.pubkey, signing_root, signed_proof.signature): + return False + + # Derive program bytecode from the EL program identifier and proof type + program_bytecode = ProgramBytecode( + el_program + proof_message.zk_proof.proof_type.to_bytes(1, "little") + ) + + return verify_zkevm_proof(proof_message.zk_proof, parent_hash, block_hash, program_bytecode) +``` + +#### `verify_execution_proofs` + +```python +def verify_execution_proofs(parent_hash: Hash32, block_hash: Hash32, state: BeaconState) -> bool: + """ + Verify that execution proofs are available and valid for an execution payload. + """ + # `retrieve_execution_proofs` is implementation and context dependent. + # It returns all execution proofs for the given payload block hash. + signed_execution_proofs = retrieve_execution_proofs(block_hash) + + # Verify there are sufficient proofs + if len(signed_execution_proofs) < MIN_REQUIRED_EXECUTION_PROOFS: + return False + + # Verify all execution proofs + for signed_proof in signed_execution_proofs: + if not verify_execution_proof(signed_proof, parent_hash, block_hash, state, PROGRAM): + return False + + return True +``` + +## Beacon chain state transition function + +### Execution payload processing + +#### Modified `process_execution_payload` + +```python +def process_execution_payload( + state: BeaconState, + body: BeaconBlockBody, + execution_engine: ExecutionEngine, + stateless_validation: bool = False, +) -> None: + """ + Note: This function is modified to support optional stateless validation with execution proofs. + """ + payload = body.execution_payload + + # Verify consistency of the parent hash with respect to the previous execution payload header + assert payload.parent_hash == state.latest_execution_payload_header.block_hash + # Verify prev_randao + assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_time_at_slot(state, state.slot) + # Verify commitments are under limit + assert ( + len(body.blob_kzg_commitments) + <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block + ) + + if stateless_validation: + # Stateless validation using execution proofs + assert verify_execution_proofs(payload.parent_hash, payload.block_hash, state) + else: + # Traditional validation - execute the payload + versioned_hashes = [ + kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments + ] + assert execution_engine.verify_and_notify_new_payload( + NewPayloadRequest( + execution_payload=payload, + versioned_hashes=versioned_hashes, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests=body.execution_requests, + ) + ) + + # Cache execution payload header + state.latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipts_root=payload.receipts_root, + logs_bloom=payload.logs_bloom, + prev_randao=payload.prev_randao, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + withdrawals_root=hash_tree_root(payload.withdrawals), + blob_gas_used=payload.blob_gas_used, + excess_blob_gas=payload.excess_blob_gas, + ) +``` diff --git a/specs/_features/eip8025/p2p-interface.md b/specs/_features/eip8025/p2p-interface.md new file mode 100644 index 0000000000..bfc6397423 --- /dev/null +++ b/specs/_features/eip8025/p2p-interface.md @@ -0,0 +1,108 @@ +# EIP-8025 -- Networking + +This document contains the networking specification for EIP-8025. + +## Table of contents + + + +- [Table of contents](#table-of-contents) +- [Constants](#constants) +- [Containers](#containers) +- [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`execution_proof_{subnet_id}`](#execution_proof_subnet_id) +- [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [ExecutionProofsByHash](#executionproofsbyhash) + + + +## Constants + +*Note*: There are `MAX_EXECUTION_PROOFS_PER_PAYLOAD` (from +[beacon-chain.md](./beacon-chain.md)) execution proof subnets to provide 1-to-1 +mapping with proof systems. Each proof system gets its own dedicated subnet. + +## Containers + +*Note*: Execution proofs are broadcast directly as `SignedExecutionProof` +containers. No additional message wrapper is needed. + +## The gossip domain: gossipsub + +### Topics and messages + +#### Global topics + +##### `execution_proof_{subnet_id}` + +Execution proof subnets are used to propagate execution proofs for specific +proof systems. + +The execution proof subnet for a given `proof_id` is: + +```python +def compute_subnet_for_execution_proof(proof_id: ProofID) -> SubnetID: + assert proof_id < MAX_EXECUTION_PROOFS_PER_PAYLOAD + return SubnetID(proof_id) +``` + +The following validations MUST pass before forwarding the +`signed_execution_proof` on the network: + +- _[IGNORE]_ The proof is the first valid proof received for the tuple + `(signed_execution_proof.message.zk_proof.public_inputs.block_hash, subnet_id)`. +- _[REJECT]_ The `signed_execution_proof.message.validator_index` is within the + known validator registry. +- _[REJECT]_ The `signed_execution_proof.signature` is valid with respect to the + validator's public key. +- _[REJECT]_ The `signed_execution_proof.message.zk_proof.proof_data` is + non-empty. +- _[REJECT]_ The proof system ID matches the subnet: + `signed_execution_proof.message.zk_proof.proof_type == subnet_id`. +- _[REJECT]_ The execution proof is valid as verified by + `verify_execution_proof()` with the appropriate parent and block hashes from + the execution layer. + +## The Req/Resp domain + +### Messages + +#### ExecutionProofsByHash + +**Protocol ID:** `/eth2/beacon/req/execution_proofs_by_hash/1/` + +The `` field is calculated as +`context = compute_fork_digest(fork_version, genesis_validators_root)`. + +Request Content: + +``` +( + Hash32 # block_hash +) +``` + +Response Content: + +``` +( + List[SignedExecutionProof, MAX_EXECUTION_PROOFS_PER_PAYLOAD] +) +``` + +Requests execution proofs for the given execution payload `block_hash`. The +response MUST contain all available proofs for the requested block hash, up to +`MAX_EXECUTION_PROOFS_PER_PAYLOAD`. + +The following validations MUST pass: + +- _[REJECT]_ The `block_hash` is a 32-byte value. + +The response MUST contain: + +- All available execution proofs for the requested block hash. +- The response MUST NOT contain more than `MAX_EXECUTION_PROOFS_PER_PAYLOAD` + proofs. diff --git a/specs/_features/eip8025/validator.md b/specs/_features/eip8025/validator.md new file mode 100644 index 0000000000..1f80bd0ffd --- /dev/null +++ b/specs/_features/eip8025/validator.md @@ -0,0 +1,92 @@ +# EIP-8025 -- Honest Validator + +**Notice**: This document is a work-in-progress for researchers and +implementers. + +## Table of contents + + + +- [Table of contents](#table-of-contents) +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Configuration](#configuration) +- [Optional execution proof generation](#optional-execution-proof-generation) + - [`generate_execution_proof`](#generate_execution_proof) + - [`broadcast_execution_proof`](#broadcast_execution_proof) + + + +## Introduction + +This document represents optional execution proof generation capabilities that +validators may choose to implement. + +## Prerequisites + +This document is an extension of the +[Fulu -- Honest Validator](../../fulu/validator.md) guide. All behaviors and +definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +## Configuration + +| Name | Value | +| ------------------------------------ | ------- | +| `EXECUTION_PROOF_GENERATION_ENABLED` | `False` | + +## Optional execution proof generation + +Validators MAY choose to generate execution proofs for payloads they propose or +receive. + +### `generate_execution_proof` + +```python +def generate_execution_proof( + payload: ExecutionPayload, execution_witness: ZKExecutionWitness, proof_id: ProofID +) -> Optional[SignedExecutionProof]: + """ + Generate an execution proof for the given payload. + """ + if not EXECUTION_PROOF_GENERATION_ENABLED: + return None + + zk_proof = generate_zkevm_proof(payload, execution_witness, PROGRAM, proof_id) + + if zk_proof is None: + return None + + validator_index = get_validator_index() + beacon_root = get_current_beacon_root() + + execution_proof_message = ExecutionProof( + beacon_root=beacon_root, + zk_proof=zk_proof, + validator_index=validator_index, + ) + + signing_root = compute_signing_root( + execution_proof_message, get_domain(get_current_state(), DOMAIN_EXECUTION_PROOF) + ) + signature = bls.Sign(get_validator_private_key(), signing_root) + + return SignedExecutionProof( + message=execution_proof_message, + signature=signature, + ) +``` + +### `broadcast_execution_proof` + +```python +def broadcast_execution_proof(signed_proof: SignedExecutionProof) -> None: + """ + Broadcast an execution proof to the network. + """ + # Broadcast on the appropriate subnet based on proof system + subnet_id = compute_subnet_for_execution_proof(signed_proof.message.zk_proof.proof_type) + topic = f"execution_proof_{subnet_id}" + + broadcast_to_topic(topic, signed_proof) +``` diff --git a/specs/_features/eip8025/zkevm.md b/specs/_features/eip8025/zkevm.md new file mode 100644 index 0000000000..2905d5de9f --- /dev/null +++ b/specs/_features/eip8025/zkevm.md @@ -0,0 +1,221 @@ +# EIP-8025 -- zkEVM + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Constants](#constants) +- [Custom types](#custom-types) +- [Cryptographic types](#cryptographic-types) +- [Containers](#containers) + - [`ZKEVMProof`](#zkevmproof) + - [`PrivateInput`](#privateinput) + - [`PublicInput`](#publicinput) +- [Helper functions](#helper-functions) + - [Preprocessing](#preprocessing) + - [`generate_keys`](#generate_keys) + - [Proof verification](#proof-verification) + - [`verify_execution_proof_impl`](#verify_execution_proof_impl) + - [`generate_verification_key`](#generate_verification_key) + - [Proof generation](#proof-generation) + - [`generate_execution_proof_impl`](#generate_execution_proof_impl) + - [`generate_proving_key`](#generate_proving_key) + - [`verify_zkevm_proof`](#verify_zkevm_proof) + - [`generate_zkevm_proof`](#generate_zkevm_proof) + + + +## Introduction + +This document specifies the cryptographic operations for zkEVM based execution +proofs enabling stateless validation of execution payloads. + +*Note*: This specification provides placeholder implementations. Production +implementations should use established zkEVM systems. + +## Constants + +All of the constants below are subject to change and one should not overindex on +them. `MAX_PROOF_SIZE`, `MAX_PROVING_KEY_SIZE`, and `MAX_VERIFICATION_KEY_SIZE` +are all arbitrary. `MAX_WITNESS_SIZE` is the worst case witness size for the MPT +for a payload with a maximum gas limit of 30M gas. + +| Name | Value | +| --------------------------- | ---------------------- | +| `MAX_PROOF_SIZE` | `307200` (= 300KiB) | +| `MAX_PROVING_KEY_SIZE` | `2**28` (= 256MiB) | +| `MAX_VERIFICATION_KEY_SIZE` | `2**20` (= 1MiB) | +| `MAX_WITNESS_SIZE` | `314572800` (= 300MiB) | + +## Custom types + +| Name | SSZ equivalent | Description | +| ------------ | -------------- | ------------------------------- | +| `ZKEVMProof` | `Container` | Proof of execution of a program | + +## Cryptographic types + +*Note*: `ProgramBytecode` represents the bytecode for a particular execution +layer client. The size depends on the client; `16` is a placeholder. + +| Name | SSZ equivalent | Description | +| -------------------- | ------------------------------------- | ------------------------------------------------------------- | +| `ProgramBytecode` | `ByteList[16]` | Execution layer program bytecode | +| `ProofID` | `uint8` | Identifier for proof system | +| `ProvingKey` | `ByteList[MAX_PROVING_KEY_SIZE]` | Key used for proof generation | +| `VerificationKey` | `ByteList[MAX_VERIFICATION_KEY_SIZE]` | Key used for proof verification | +| `ZKExecutionWitness` | `ByteList[MAX_WITNESS_SIZE]` | zkEVM execution witness data for stateless program execution | +| `PrivateInput` | `Container` | Private inputs for execution proof generation | +| `PublicInput` | `Container` | Public inputs for execution proof generation and verification | + +## Containers + +### `ZKEVMProof` + +```python +class ZKEVMProof(Container): + proof_data: ByteList[MAX_PROOF_SIZE] + proof_type: ProofID + public_inputs: PublicInput +``` + +### `PrivateInput` + +```python +class PrivateInput(Container): + execution_payload: ExecutionPayload + execution_witness: ZKExecutionWitness +``` + +### `PublicInput` + +```python +class PublicInput(Container): + block_hash: Hash32 + parent_hash: Hash32 +``` + +## Helper functions + +### Preprocessing + +#### `generate_keys` + +```python +def generate_keys( + program_bytecode: ProgramBytecode, proof_id: ProofID +) -> tuple[ProvingKey, VerificationKey]: + """ + Generate proving and verification keys for the given program bytecode and proof system. + """ + proving_key = generate_proving_key(program_bytecode, proof_id) + verification_key = generate_verification_key(program_bytecode, proof_id) + + return (proving_key, verification_key) +``` + +### Proof verification + +#### `verify_execution_proof_impl` + +```python +def verify_execution_proof_impl(proof: ZKEVMProof, verification_key: VerificationKey) -> bool: + """ + Verify a zkEVM execution proof using the verification key. + """ + if len(proof.proof_data) > MAX_PROOF_SIZE: + return False + + return True +``` + +#### `generate_verification_key` + +```python +def generate_verification_key( + program_bytecode: ProgramBytecode, proof_id: ProofID +) -> VerificationKey: + """ + Generate a verification key for the given program bytecode and proof system. + """ + verification_key = VerificationKey(program_bytecode + proof_id.to_bytes(1, "little")) + return verification_key +``` + +### Proof generation + +#### `generate_execution_proof_impl` + +```python +def generate_execution_proof_impl( + private_input: PrivateInput, + proving_key: ProvingKey, + proof_id: ProofID, + public_inputs: PublicInput, +) -> ZKEVMProof: + """ + Generate a zkEVM execution proof using the proving key, private inputs and public inputs + """ + proof_data = hash( + public_inputs.block_hash + public_inputs.parent_hash + proof_id.to_bytes(1, "little") + ) + + return ZKEVMProof( + proof_data=ByteList(proof_data), proof_type=proof_id, public_inputs=public_inputs + ) +``` + +#### `generate_proving_key` + +```python +def generate_proving_key(program_bytecode: ProgramBytecode, proof_id: ProofID) -> ProvingKey: + """ + Generate a proving key for the given program bytecode and proof system. + """ + return ProvingKey(program_bytecode + proof_id.to_bytes(1, "little")) +``` + +### `verify_zkevm_proof` + +```python +def verify_zkevm_proof( + zk_proof: ZKEVMProof, parent_hash: Hash32, block_hash: Hash32, program_bytecode: ProgramBytecode +) -> bool: + """ + Public method to verify a zkEVM execution proof against block hashes. + """ + # Validate that public inputs match the provided parent and current block hash + if zk_proof.public_inputs.block_hash != block_hash: + return False + if zk_proof.public_inputs.parent_hash != parent_hash: + return False + + _, verification_key = generate_keys(program_bytecode, zk_proof.proof_type) + + return verify_execution_proof_impl(zk_proof, verification_key) +``` + +### `generate_zkevm_proof` + +```python +def generate_zkevm_proof( + execution_payload: ExecutionPayload, + execution_witness: ZKExecutionWitness, + program_bytecode: ProgramBytecode, + proof_id: ProofID, +) -> Optional[ZKEVMProof]: + """ + Public method to generate an execution proof for a payload. + """ + proving_key, _ = generate_keys(program_bytecode, proof_id) + + public_inputs = PublicInput( + block_hash=execution_payload.block_hash, parent_hash=execution_payload.parent_hash + ) + private_input = PrivateInput( + execution_payload=execution_payload, execution_witness=execution_witness + ) + + return generate_execution_proof_impl(private_input, proving_key, proof_id, public_inputs) +```