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.
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 uint16schema_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:
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.
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:
the exact verification_key_hash;
the L1 feature fork whose EVM semantics the corresponding verification key
represents; and
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:
the exact previously registered verification_key_hash to reactivate;
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:
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:
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-7910systemContracts 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.
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:
A zero-value, non-system call with calldata bytes32(0) reverts with no
return data.
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.
Zero-value, non-system calls with calldata bytes32(0) and K1 each return
K1 || I1.
A zero-value system call with calldata K1 || I2 reverts with no return
data and leaves both storage slots unchanged.
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.
A zero-value system call with calldata K1 succeeds with no return data.
Storage slot 0 becomes K1, while S1 and S2 remain unchanged.
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.
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.