EIP.tools

EIP.tools

⚠️ DraftStandards Track: Core

EIP-8357: EVM Verification Key Registry

Exposes fork-approved EVM verification key hashes and their stateless input schema IDs to contracts

Authors
Created2026-07-30
Discussion Linkhttps://ethereum-magicians.org/t/eip-8357-evm-verification-key-registry/29222
Requires

Markdown

https://raw.githubusercontent.com/ethereum/EIPs/re...
Pull Request#12055PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationParametersRegistry stateContract interfaceReadFork updateAdding a verification key hashReactivating a verification key hashBlock processingBytecodeDeploymentRationaleDeterministic deploymentCurrent and pinned verification key hashesRegistry instead of a verifier wildcardReactivating a historical verification key hashBackwards CompatibilityTest CasesReference ImplementationSecurity ConsiderationsCopyright

Abstract

This EIP creates a fixed-address system contract containing the canonical EVM verification key hash for each registered L1 feature fork. Each entry maps one exact verification key hash to the schema_id of the stateless input accepted by the fork-specific EVM program bound by the corresponding verification key.

The contract stores one current_verification_key_hash. A caller may retrieve either the current entry or an exact historical entry. This lets a native rollup follow L1 EVM upgrades automatically or deliberately remain on a historical EVM fork.

Motivation

Native rollups are intended to inherit Ethereum's execution environment and upgrade process instead of maintaining a bespoke verifier and its governance. The rollup contract must therefore identify the verification key that L1 has approved for proving EVM execution.

EIP-8288 verifies proofs against explicitly supplied verification key hashes, but does not tell contracts which hashes L1 recognizes, which one is current, or which input schema each program accepts. A contract needs that schema to reconstruct the program's public output, which commits to it. Hard-coding those values would force each rollup to upgrade through its own governance at every L1 feature fork or remain on an older EVM.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Parameters

ConstantValue
SYSTEM_ADDRESS0xfffffffffffffffffffffffffffffffffffffffe
EVM_VK_REGISTRY_ADDRESS0x00005e9c1447c1a05a642ec9eb76d9c125468357
REGISTRY_DEPLOYMENT_SALT0x5c4fde244aecad9b7c039f836e4f6bff9978d838dd6d0ae179f3c70aa84a997f
CURRENT_VERIFICATION_KEY_HASH_SLOT0
SCHEMA_ID_MAPPING_SLOT1

The all-zero verification key hash is reserved for current-entry lookup and MUST NOT be registered.

Registry state

verification_key_hash is the 32-byte field defined by EIP-8288 for LEANSTARK_SCHEME: a hash of a STARK verification key. A registered verification_key_hash identifies the L1-approved verification key for a deterministic, fork-specific EVM stateless-validation program.

Each registered verification_key_hash stores the nonzero uint16 schema_id of the stateless input that its program accepts, as defined by EIP-8025. The schema_id identifies the input schema and the fork rules that the program executes, and is echoed in the program's public output.

Storage slot CURRENT_VERIFICATION_KEY_HASH_SLOT stores the current verification_key_hash. Registering a new hash or reactivating a previously registered hash replaces this value. Previously registered entries remain available through exact-hash lookup.

Entries are append-only. A registered entry MUST NOT be deleted or overwritten.

For each nonzero verification_key_hash, define its schema ID slot using the standard Solidity mapping layout:

def schema_id_slot(verification_key_hash: Bytes32) -> U256:
    return U256(
        keccak256(
            verification_key_hash
            + uint256_be(SCHEMA_ID_MAPPING_SLOT)
        )
    )

A verification_key_hash is registered if sload(schema_id_slot(verification_key_hash)) is nonzero. The stored value MUST be at most 2**16 - 1.

Contract interface

The contract selects its operation by CALLER:

  • if CALLER == SYSTEM_ADDRESS, execute the fork-update operation;
  • otherwise, execute the read operation.

All calls MUST have value 0.

In calldata and return data, schema_id MUST be encoded as a zero-padded, 32-byte big-endian word.

Read

A non-system caller MUST provide exactly 32 bytes of calldata:

  • an all-zero word requests the current entry; or
  • a nonzero word requests that exact verification_key_hash.

For a valid request, the contract MUST return exactly 64 bytes:

verification_key_hash
|| schema_id

For an all-zero query, verification_key_hash MUST be loaded from CURRENT_VERIFICATION_KEY_HASH_SLOT. For a nonzero query, verification_key_hash MUST equal the supplied value.

The call MUST revert without return data if:

  • calldata is malformed;
  • call value is nonzero;
  • no registered entry exists for the selected verification_key_hash.

The read operation MUST NOT modify state.

Fork update

The system caller supplies either a 64-byte registration or a 32-byte reactivation.

For a registration, calldata is:

verification_key_hash
|| schema_id

The call registers a new verification key hash and updates the current pointer to it. The first fork update registers only the hash active at the registry's activation.

  • verification_key_hash MUST be nonzero;
  • verification_key_hash MUST NOT already be registered;
  • schema_id MUST be nonzero and at most 2**16 - 1.

For a reactivation, calldata is:

verification_key_hash

The call updates the current pointer to a previously registered verification key hash without modifying its schema_id.

  • verification_key_hash MUST be nonzero;
  • verification_key_hash MUST already be registered.

If any condition fails, the call MUST revert. On success, CURRENT_VERIFICATION_KEY_HASH_SLOT is set to verification_key_hash and no data is returned. A successful registration also stores the supplied schema_id.

The fork-update operation is:

def update_registry(calldata: Bytes) -> None:
    assert caller == SYSTEM_ADDRESS
    assert callvalue == 0

    if len(calldata) == 64:
        verification_key_hash = calldata[0:32]
        schema_id = uint256_be_decode(calldata[32:64])

        assert verification_key_hash != bytes32(0)
        assert 0 < schema_id <= 2**16 - 1

        slot = schema_id_slot(verification_key_hash)
        assert sload(slot) == 0

        sstore(slot, schema_id)
    elif len(calldata) == 32:
        verification_key_hash = calldata[0:32]

        assert verification_key_hash != bytes32(0)
        assert sload(schema_id_slot(verification_key_hash)) != 0
    else:
        assert False

    sstore(CURRENT_VERIFICATION_KEY_HASH_SLOT, verification_key_hash)

Adding a verification key hash

Each new verification_key_hash MUST be specified by a distinct Core EIP that lists this EIP in its requires header. The hash is activated in a hard fork.

That EIP MUST define:

  1. the exact verification_key_hash;
  2. the L1 feature fork whose EVM semantics the corresponding verification key represents; and
  3. the schema_id of the stateless input that the corresponding program accepts.

The hard fork that activates this EIP MUST also activate exactly one such Core EIP. Its hash is the registry's initial verification_key_hash.

Reactivating a verification key hash

A hard fork MAY reactivate a previously registered verification_key_hash. A distinct Core EIP that lists this EIP in its requires header MUST define:

  1. the exact previously registered verification_key_hash to reactivate;
  2. the L1 feature fork whose EVM semantics the corresponding verification key represents.

Reactivation changes only CURRENT_VERIFICATION_KEY_HASH_SLOT. It MUST NOT change the hash's stored schema_id.

Block processing

EVM_VK_REGISTRY_UPDATE is the registration or reactivation calldata defined above. For a registration, schema_id is the one defined by the Core EIP that specifies the hash.

In the first block where a hard fork selects a new or reactivated verification_key_hash as current, including the registry activation fork, before processing transactions, execution clients MUST call EVM_VK_REGISTRY_ADDRESS as SYSTEM_ADDRESS with EVM_VK_REGISTRY_UPDATE as calldata and value 0.

This is a system operation and therefore:

  • the call MUST have the dedicated SYSTEM_CALL_GAS_LIMIT defined by EIP-8037;
  • gas consumed by the call MUST NOT contribute to either block_execution_gas_used or block_state_gas_used;
  • both the gas limit assigned to the call and the gas consumed MUST be excluded from checks against the block's gas limit;
  • the call MUST NOT follow EIP-1559 fee-burning semantics;
  • the call MUST be treated as a pre-execution system-contract call under EIP-7928; and
  • the block MUST be invalid if EVM_VK_REGISTRY_ADDRESS contains no code or the call fails.

The call MUST NOT be performed in any other block.

Bytecode

The registry runtime bytecode, REGISTRY_RUNTIME_CODE, is:

0x3460a1573373fffffffffffffffffffffffffffffffffffffffe14604a576020360360a1575f3580602c57545b801560a1575f52600160205260405f2054801560a15760205260405ff35b602036146085576040360360a1575f35801560a157805f52600160205260405f20805460a157602035801560a1578060101c60a15790555f55005b5f35801560a157805f52600160205260405f20541560a1575f55005b5f5ffd

The registry initcode, REGISTRY_INITCODE, is:

0x60a58060095f395ff33460a1573373fffffffffffffffffffffffffffffffffffffffe14604a576020360360a1575f3580602c57545b801560a1575f52600160205260405f2054801560a15760205260405ff35b602036146085576040360360a1575f35801560a157805f52600160205260405f20805460a157602035801560a1578060101c60a15790555f55005b5f35801560a157805f52600160205260405f20541560a1575f55005b5f5ffd

The initcode consists of a simple constructor followed by REGISTRY_RUNTIME_CODE. The constructor returns the runtime bytecode without initializing storage.

Deployment

The registry MUST be deployed through the FACTORY_ADDRESS defined by EIP-7997. Any account MAY deploy it with an ordinary transaction that calls the factory with the 32-byte deployment salt followed by the initcode:

REGISTRY_DEPLOYMENT_SALT
|| REGISTRY_INITCODE

Under EIP-1014, the registry address is:

EVM_VK_REGISTRY_ADDRESS =
    keccak256(
        0xff
        || FACTORY_ADDRESS
        || REGISTRY_DEPLOYMENT_SALT
        || keccak256(REGISTRY_INITCODE)
    )[12:]

Deploying the contract does not activate this EIP or register a verification_key_hash.

The deployment transaction MUST be included in a block preceding the first block of the registry activation fork because the initial registry system call is performed before transactions in the activation block.

From the registry activation fork onward:

  • the account at EVM_VK_REGISTRY_ADDRESS MUST contain REGISTRY_RUNTIME_CODE and have nonce 1 under EIP-161; and
  • every applicable EIP-7910 systemContracts object MUST include an EVM_VK_REGISTRY_ADDRESS member with the registry address, preserving alphabetical key order.

Rationale

Deterministic deployment

EIP-7997 allows any account to deploy the contract. Because EVM_VK_REGISTRY_ADDRESS is bound to REGISTRY_INITCODE, changing the runtime requires different initcode and therefore produces a different address.

Current and pinned verification key hashes

A native rollup can query the current entry to follow L1 EVM upgrades without a separate governance action, or pin a historical entry while its infrastructure adapts. The stored schema_id lets either mode reconstruct the program's public output, which commits to the input schema and fork rules the program executed.

Registry instead of a verifier wildcard

A verifier wildcard could select the current verification key, but would duplicate that policy in every proof-verification mechanism. The registry records current and historical verification key hashes once, while verifiers continue accepting exact hashes. Contracts can follow the current pointer or pin any registered hash. The all-zero value is only a registry lookup selector, never a verification key hash.

Reactivating a historical verification key hash

A later hard fork can restore a previously registered verification key hash as current without inventing a different verification key for the same verification relation. The hash retains its schema_id because the schema is a property of the program that the verification key identifies, not of the hard fork that reactivates it.

Caller-based operation selection makes the 32-byte system reactivation unambiguous from a 32-byte read: only SYSTEM_ADDRESS executes the fork-update operation, while every other caller executes the read operation.

Backwards Compatibility

This EIP introduces backward-incompatible changes to the block validation rules and must be activated through a hard fork. Existing transaction formats are unchanged.

Test Cases

Let:

K1 = 0x0000000000000000000000000000000000000000000000000000000000000001
K2 = 0x0000000000000000000000000000000000000000000000000000000000000002
I1 = 0x0000000000000000000000000000000000000000000000000000000000001501
I2 = 0x000000000000000000000000000000000000000000000000000000000000ffff
S1 = 0xcc69885fda6bcc1a4ace058b4a62bf5e179ea78fd58a1ccd71c22cc9b688792f
S2 = 0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330f

S1 and S2 are schema_id_slot(K1) and schema_id_slot(K2), respectively. Starting from a registry deployed with REGISTRY_INITCODE and empty storage:

  1. A zero-value, non-system call with calldata bytes32(0) reverts with no return data.
  2. A zero-value system call with calldata K1 || I1 succeeds with no return data. Storage slot 0 becomes K1 and storage slot S1 becomes I1.
  3. Zero-value, non-system calls with calldata bytes32(0) and K1 each return K1 || I1.
  4. A zero-value system call with calldata K1 || I2 reverts with no return data and leaves both storage slots unchanged.
  5. A zero-value system call with calldata K2 || I2 succeeds with no return data. Storage slot 0 becomes K2, storage slot S2 becomes I2, and storage slot S1 remains I1.
  6. A zero-value system call with calldata K1 succeeds with no return data. Storage slot 0 becomes K1, while S1 and S2 remain unchanged.
  7. A zero-value, non-system call with calldata bytes32(0) returns K1 || I1, and one with calldata K2 returns K2 || I2.

Calls with nonzero value revert with no return data. Non-system calldata lengths other than 32 bytes and system calldata lengths other than 32 or 64 bytes also revert with no return data. Registrations with a zero verification key hash, zero schema ID, or schema ID greater than 2**16 - 1, and attempts to reactivate a zero or unregistered hash, revert without changing state.

Reference Implementation

See src/verification_key_registry.

Security Considerations

Consumers that select the current entry automatically adopt each fork-selected verification key hash. Proofs generated under the previous verification key may fail after the switch. A rollup unable to produce proofs under the new verification key may halt. Consumers requiring an independent upgrade window can pin an exact registered hash.

Registered verification key hashes are never revoked, even if proofs under the corresponding verification keys are later considered insecure. Consumers selecting a historical entry assume this risk.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth