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
| Constant | Value |
|---|---|
SYSTEM_ADDRESS | 0xfffffffffffffffffffffffffffffffffffffffe |
EVM_VK_REGISTRY_ADDRESS | 0x00005e9c1447c1a05a642ec9eb76d9c125468357 |
REGISTRY_DEPLOYMENT_SALT | 0x5c4fde244aecad9b7c039f836e4f6bff9978d838dd6d0ae179f3c70aa84a997f |
CURRENT_VERIFICATION_KEY_HASH_SLOT | 0 |
SCHEMA_ID_MAPPING_SLOT | 1 |
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_idFor 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_idThe 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_hashMUST be nonzero;verification_key_hashMUST NOT already be registered;schema_idMUST be nonzero and at most2**16 - 1.
For a reactivation, calldata is:
verification_key_hashThe call updates the current pointer to a previously registered verification
key hash without modifying its schema_id.
verification_key_hashMUST be nonzero;verification_key_hashMUST 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:
- the exact
verification_key_hash; - the L1 feature fork whose EVM semantics the corresponding verification key represents; and
- the
schema_idof 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_hashto 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_LIMITdefined by EIP-8037; - gas consumed by the call MUST NOT contribute to either
block_execution_gas_usedorblock_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_ADDRESScontains 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:
0x3460a1573373fffffffffffffffffffffffffffffffffffffffe14604a576020360360a1575f3580602c57545b801560a1575f52600160205260405f2054801560a15760205260405ff35b602036146085576040360360a1575f35801560a157805f52600160205260405f20805460a157602035801560a1578060101c60a15790555f55005b5f35801560a157805f52600160205260405f20541560a1575f55005b5f5ffdThe registry initcode, REGISTRY_INITCODE, is:
0x60a58060095f395ff33460a1573373fffffffffffffffffffffffffffffffffffffffe14604a576020360360a1575f3580602c57545b801560a1575f52600160205260405f2054801560a15760205260405ff35b602036146085576040360360a1575f35801560a157805f52600160205260405f20805460a157602035801560a1578060101c60a15790555f55005b5f35801560a157805f52600160205260405f20541560a1575f55005b5f5ffdThe 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_INITCODEUnder 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_ADDRESSMUST containREGISTRY_RUNTIME_CODEand have nonce1under EIP-161; and - every applicable EIP-7910
systemContractsobject MUST include anEVM_VK_REGISTRY_ADDRESSmember 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 = 0xd9d16d34ffb15ba3a3d852f0d403e2ce1d691fb54de27ac87cd2f993f3ec330fS1 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 || I1succeeds with no return data. Storage slot0becomesK1and storage slotS1becomesI1. - Zero-value, non-system calls with calldata
bytes32(0)andK1each returnK1 || I1. - A zero-value system call with calldata
K1 || I2reverts with no return data and leaves both storage slots unchanged. - A zero-value system call with calldata
K2 || I2succeeds with no return data. Storage slot0becomesK2, storage slotS2becomesI2, and storage slotS1remainsI1. - A zero-value system call with calldata
K1succeeds with no return data. Storage slot0becomesK1, whileS1andS2remain unchanged. - A zero-value, non-system call with calldata
bytes32(0)returnsK1 || I1, and one with calldataK2returnsK2 || 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
Copyright and related rights waived via CC0.
