Abstract
This EIP adds a forward-compatible validator deposit contract with
variable-length public keys and credential metadata. Each deposit declares a
credential scheme where scheme 0 represents BLS deposits. The execution layer
carries accepted deposits to consensus through EIP-7685.
This EIP does not activate a new validator credential scheme. A future fork can assign new schemes and permanently retire BLS deposits through a system call.
Motivation
The current validator deposit contract fixes the public key and signature to the BLS12-381 sizes. These sizes prevent the contract from carrying larger or otherwise different post-quantum credentials.
The existing contract also maintains a Merkle tree for the original deposit
polling mechanism. EIP-6110 replaced this mechanism with
requests derived from deposit logs. The new contract can therefore omit the
tree and deposit_data_root.
Like the existing path, the new contract transports deposit data but leaves cryptographic validation to consensus. Its scheme identifier selects the consensus rules for the opaque credential fields.
Concurrent BLS support lets tooling migrate before a later fork retires BLS onboarding.
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.
Constants
| Name | Value | Description |
|---|---|---|
CREDENTIAL_DEPOSIT_CONTRACT_ADDRESS | TBD | Address of the new deposit contract |
CREDENTIAL_DEPOSIT_CONTRACT_SALT | TBD | Salt for the deterministic deployment |
CREDENTIAL_DEPOSIT_CONTRACT_INIT_CODE | TBD | Initialization code for the deterministic deployment |
CREDENTIAL_DEPOSIT_CONTRACT_RUNTIME_CODE | TBD | Required runtime code |
CREDENTIAL_DEPOSIT_REQUEST_TYPE | TBD | EIP-7685 request-type byte |
CREDENTIAL_DEPOSIT_EVENT_SIGNATURE_HASH | 0x573a3e66153a38882575e538b0f3fc14cc9ea961a3c7d3b647fb345fab5ea1d6 | Event topic defined below |
INITIAL_ACTIVATION_TIMESTAMP | TBD | Timestamp that enables the new deposit contract and request type |
BLS_RETIREMENT_TIMESTAMP | TBD | Timestamp that retires BLS deposits |
SYSTEM_ADDRESS | 0xfffffffffffffffffffffffffffffffffffffffe | Caller for protocol system calls |
SYSTEM_CALL_GAS_LIMIT | 30_000_000 | Dedicated gas for a system call |
DEPOSIT_MODE_STORAGE_SLOT | 0 | Storage slot for the contract mode |
DEPOSIT_MODE_DISABLED | 0 | Deposits are disabled |
DEPOSIT_MODE_BLS_ENABLED | 1 | BLS deposits are accepted |
DEPOSIT_MODE_BLS_RETIRED | 2 | BLS deposits are rejected |
ENABLE_DEPOSITS_COMMAND | 0x00 | System command for initial activation |
RETIRE_BLS_COMMAND | 0x01 | System command for BLS retirement |
MIN_DEPOSIT_AMOUNT | 1_000_000_000 | Minimum deposit amount in gwei |
MAX_PUBKEY_LENGTH | 8192 | Maximum pubkey length in bytes |
MAX_CREDENTIAL_METADATA_LENGTH | 8192 | Maximum credential_metadata length in bytes |
BLS_CREDENTIAL_SCHEME | 0 | Scheme identifier for BLS deposits |
BLS_PUBKEY_LENGTH | 48 | Length of a BLS12-381 public key |
BLS_CREDENTIAL_METADATA_LENGTH | 96 | Length of BLS credential metadata |
CREDENTIAL_DEPOSIT_REQUEST_TYPE MUST be globally unique among assigned
EIP-7685 request types.
BLS_RETIREMENT_TIMESTAMP MUST be greater than or equal to
INITIAL_ACTIVATION_TIMESTAMP.
The event-signature hash is:
keccak256("CredentialDepositEvent(uint8,bytes,bytes32,uint64,bytes)")Definitions
The initial activation block is the first execution block with:
block.timestamp >= INITIAL_ACTIVATION_TIMESTAMPThe BLS retirement block is the first execution block with:
block.timestamp >= BLS_RETIREMENT_TIMESTAMPA deposit or request is BLS if and only if:
scheme == BLS_CREDENTIAL_SCHEMEThis EIP assigns only the BLS scheme value. All other scheme values are unassigned. A future credential-scheme EIP MUST assign an unused scheme value and MUST define its validation.
Deployment
The EIP-7997 factory MUST deploy the contract before initial activation. Its call input MUST be:
CREDENTIAL_DEPOSIT_CONTRACT_SALT || CREDENTIAL_DEPOSIT_CONTRACT_INIT_CODEThe resulting address MUST equal CREDENTIAL_DEPOSIT_CONTRACT_ADDRESS. The
runtime code MUST equal CREDENTIAL_DEPOSIT_CONTRACT_RUNTIME_CODE.
The deployment MUST initialize DEPOSIT_MODE_STORAGE_SLOT to
DEPOSIT_MODE_DISABLED. A deposit call MUST revert while this mode is active.
At and after initial activation, a block is invalid if the contract is absent or has different runtime code.
This EIP MUST activate after the EIP-6110 transition is complete. EIP-6110 defines this point as:
state.eth1_deposit_index == state.deposit_requests_start_indexContract interface
The contract MUST expose this user function:
function deposit(
uint8 scheme,
bytes calldata pubkey,
bytes32 withdrawal_credentials,
bytes calldata credential_metadata
) external payable;Deposit operation
The contract MUST reject a deposit unless all these amount conditions are true:
msg.valueis a multiple of 1 gwei.msg.value / 1 gweiis at leastMIN_DEPOSIT_AMOUNT.msg.value / 1 gweiis at most2**64 - 1.
The contract MUST reject a deposit if either condition is true:
len(pubkey) > MAX_PUBKEY_LENGTH.len(credential_metadata) > MAX_CREDENTIAL_METADATA_LENGTH.
These limits apply in every mode and to every scheme.
Except in DEPOSIT_MODE_DISABLED, the contract MUST accept an unassigned
scheme that satisfies the amount and length limits above. It MUST NOT validate
credential contents or perform cryptographic validation.
In DEPOSIT_MODE_DISABLED, every deposit MUST revert.
In DEPOSIT_MODE_BLS_ENABLED, a BLS deposit MUST revert unless both conditions
are true:
len(pubkey) == BLS_PUBKEY_LENGTH.len(credential_metadata) == BLS_CREDENTIAL_METADATA_LENGTH.
A BLS deposit that satisfies both conditions and the amount limits MUST succeed.
A deposit with any other scheme that satisfies the amount and length limits MUST succeed.
In DEPOSIT_MODE_BLS_RETIRED, a BLS deposit MUST revert.
Deposit event
A successful deposit MUST emit exactly one event:
event CredentialDepositEvent(
uint8 scheme,
bytes pubkey,
bytes32 withdrawal_credentials,
uint64 amount,
bytes credential_metadata
);amount is uint64(msg.value / 1 gwei).
The event has exactly one topic:
topics[0] = CREDENTIAL_DEPOSIT_EVENT_SIGNATURE_HASHThe event data is the canonical Solidity ABI encoding of:
(scheme, pubkey, withdrawal_credentials, amount, credential_metadata)The event contains no indexed parameters.
Mode-transition system calls
At the start of the initial activation block, the execution layer MUST call
CREDENTIAL_DEPOSIT_CONTRACT_ADDRESS with:
caller: SYSTEM_ADDRESS
calldata: ENABLE_DEPOSITS_COMMAND
value: 0
gas: SYSTEM_CALL_GAS_LIMITThe contract MUST require that the current mode is DEPOSIT_MODE_DISABLED.
The call MUST set the mode to DEPOSIT_MODE_BLS_ENABLED.
At the start of the BLS retirement block, the execution layer MUST call the same address with:
caller: SYSTEM_ADDRESS
calldata: RETIRE_BLS_COMMAND
value: 0
gas: SYSTEM_CALL_GAS_LIMITThe contract MUST require that the current mode is
DEPOSIT_MODE_BLS_ENABLED. The call MUST set the mode to
DEPOSIT_MODE_BLS_RETIRED.
The contract MUST reject all other system commands and all repeated or reverse transitions.
Execution clients MUST use this block-start order:
- Perform the EIP-4788 beacon-root call, if active.
- Perform the EIP-2935 block-hash call, if active.
- Perform the enable call in the initial activation block.
- Perform the retirement call in the BLS retirement block.
- Process transactions.
If both timestamps activate in one block, the execution client MUST perform the enable call before the retirement call.
Each system call MUST return no data and emit no log. The call uses dedicated gas that does not count against the block gas limit.
If a required system call fails, the block is invalid. Deposits in each activation block observe the new mode because the call occurs before transactions.
Execution-layer request derivation
Execution clients MUST inspect receipts in transaction order. They MUST inspect logs in their order within each receipt.
Clients MUST decode every log from CREDENTIAL_DEPOSIT_CONTRACT_ADDRESS with
decode_credential_deposit_event. A decode failure makes the block invalid.
The execution layer MUST NOT classify, filter, or discard a decoded request
based on its scheme, credential lengths, or contents.
The request types use ProgressiveContainer from
EIP-7495. They use ProgressiveList and
ProgressiveByteList from EIP-7916.
EIP-7688 defines their use in evolving consensus containers.
class CredentialDepositRequest(
ProgressiveContainer(active_fields=[1, 1, 1, 1, 1])
):
scheme: uint8
pubkey: ProgressiveByteList
withdrawal_credentials: Bytes32
amount: Gwei
credential_metadata: ProgressiveByteList
class CredentialDepositRequests(
ProgressiveList[CredentialDepositRequest]
):
passThe contract enforces the field-length caps. The progressive SSZ schema does not repeat them.
Clients construct one request list from both deposit contracts. The list preserves transaction, receipt, and log order across both addresses.
EVENT_TYPES = ("uint8", "bytes", "bytes32", "uint64", "bytes")
def decode_credential_deposit_event(log):
assert log.topics == [CREDENTIAL_DEPOSIT_EVENT_SIGNATURE_HASH]
decoded = abi_decode(EVENT_TYPES, log.data)
assert abi_encode(EVENT_TYPES, decoded) == log.data
return decoded
def legacy_log_to_credential_deposit_request(log):
if len(log.topics) == 0 or log.topics[0] != DEPOSIT_EVENT_SIGNATURE_HASH:
return None
assert is_valid_deposit_event_data(log.data)
pubkey, withdrawal_credentials, amount, signature, _index = (
parse_deposit_data(log.data)
)
return CredentialDepositRequest(
scheme=BLS_CREDENTIAL_SCHEME,
pubkey=ProgressiveByteList(pubkey),
withdrawal_credentials=Bytes32(withdrawal_credentials),
amount=Gwei(int.from_bytes(amount, "little")),
credential_metadata=ProgressiveByteList(signature),
)
def get_credential_deposit_requests(block) -> CredentialDepositRequests:
requests = CredentialDepositRequests()
for receipt in block.receipts:
for log in receipt.logs:
if log.address == CREDENTIAL_DEPOSIT_CONTRACT_ADDRESS:
(
scheme,
pubkey,
withdrawal_credentials,
amount,
credential_metadata,
) = decode_credential_deposit_event(log)
requests.append(
CredentialDepositRequest(
scheme=uint8(scheme),
pubkey=ProgressiveByteList(pubkey),
withdrawal_credentials=Bytes32(withdrawal_credentials),
amount=Gwei(amount),
credential_metadata=ProgressiveByteList(
credential_metadata
),
)
)
continue
if (
log.address != DEPOSIT_CONTRACT_ADDRESS
or block.timestamp >= BLS_RETIREMENT_TIMESTAMP
):
continue
request = legacy_log_to_credential_deposit_request(log)
if request is not None:
requests.append(request)
return requestsabi_decode and abi_encode use the canonical Solidity ABI. Decoding MUST
reject an invalid type encoding. Re-encoding MUST reproduce the complete event
data, including offsets and zero padding.
DEPOSIT_CONTRACT_ADDRESS, DEPOSIT_EVENT_SIGNATURE_HASH,
is_valid_deposit_event_data, and parse_deposit_data are defined by
EIP-6110. The legacy deposit index is not copied into the generalized request.
This EIP activates after the EIP-6110 transition, so consensus no longer needs
that index.
The EIP-7685 request object is:
request_type = CREDENTIAL_DEPOSIT_REQUEST_TYPE
request_data = ssz_serialize(get_credential_deposit_requests(block))
request = request_type + request_dataIf the request list is empty, the execution layer MUST omit this request type. Otherwise, it MUST include exactly one request object for this type.
The existing Engine API executionRequests sequence carries the request. This
EIP adds no Engine API field. The execution client MUST verify that
request_data equals the SSZ serialization derived from the block receipts.
Beginning with the initial activation block, execution clients MUST stop
deriving EIP-6110 request objects. A valid EIP-7685 request list MUST NOT
contain a DEPOSIT_REQUEST_TYPE object from EIP-6110.
Consensus-layer containers
The fork appends the following field to ExecutionRequests:
class ExecutionRequests(ProgressiveContainer):
# Existing fields are omitted.
credential_deposits: CredentialDepositRequestsConsensus-layer processing
The deposits field MUST remain in ExecutionRequests. It MUST be empty in the
initial activation block and every later block.
At initial activation, the non-BLS handler is:
def process_non_bls_credential_deposit(
state: BeaconState,
request: CredentialDepositRequest,
) -> None:
returnThe BLS handler is:
def process_bls_credential_deposit(
state: BeaconState,
request: CredentialDepositRequest,
) -> None:
state.pending_deposits.append(
PendingDeposit(
pubkey=BLSPubkey(request.pubkey),
withdrawal_credentials=request.withdrawal_credentials,
amount=request.amount,
signature=BLSSignature(request.credential_metadata),
slot=state.slot,
)
)
def process_credential_deposit_request(
state: BeaconState,
request: CredentialDepositRequest,
) -> None:
if request.scheme == BLS_CREDENTIAL_SCHEME:
process_bls_credential_deposit(state, request)
return
process_non_bls_credential_deposit(state, request)A valid execution payload cannot contain a BLS request with other field lengths. The pinned runtime code rejects such a deposit from the new contract. The EIP-6110 decoder rejects a legacy deposit event with other field lengths.
PendingDeposit and its processing are defined by
EIP-7251. Thus, BLS signature validation, deposit churn, and
validator activation remain unchanged.
The operation order is:
# Existing operations before execution requests are omitted.
assert len(body.execution_requests.deposits) == 0
for_ops(
body.execution_requests.credential_deposits,
process_credential_deposit_request,
)
for_ops(body.execution_requests.withdrawals, process_withdrawal_request)
for_ops(body.execution_requests.consolidations, process_consolidation_request)process_non_bls_credential_deposit MUST leave the state unchanged. The block
retains the request, but BeaconState does not. A credential-scheme EIP can
replace this function. It MUST define validation, state representation, and
top-up behavior for its assigned scheme.
BLS retirement
Before BLS retirement, execution clients MUST convert BLS deposits from both
contracts into CredentialDepositRequest objects. Consensus MUST process them
through the same handler.
Beginning with the BLS retirement block, execution clients MUST stop converting legacy deposit-contract events into generalized requests. They MUST continue to derive every request from the new contract without inspecting its scheme or credential format.
The consensus-layer request processors do not require a BLS retirement branch. A valid execution payload cannot contain a new BLS request after retirement.
Pending deposits that entered state.pending_deposits before BLS retirement
remain eligible for normal pending-deposit processing after retirement.
Rationale
Explicit scheme identifier
Each deposit declares its credential scheme and carries scheme-specific metadata. BLS uses a signature. Consensus interprets both fields, so the contract does not need a scheme registry.
Contract-side BLS length enforcement
BLS length checks preserve the existing interface constraint. A wrong-length deposit reverts before the contract locks its ETH. Other invalid BLS deposits retain the legacy path's lock behavior.
Credential length caps
Execution gas prices calldata and log data, but not consensus propagation and processing. The field caps bound each request. The block gas limit bounds the aggregate request data.
The 8192-byte caps leave room for larger credentials and keep the first variable-length request type bounded.
Log-derived requests
The contract needs no queue because each deposit pays execution gas and locks at least 1 ETH. Receipt order gives a canonical request order.
Separate request field
Repurposing the existing deposits field changes its element type. The field
therefore remains empty after initial activation.
One-way mode transitions
The mode only advances. The disabled mode exists because a pre-activation deposit emits an event that clients do not convert, so no validator receives the locked ETH. The retired mode is irreversible, so no later system call can re-enable quantum-vulnerable BLS onboarding.
Backwards Compatibility
This EIP adds a contract, an EIP-7685 request type, and a field in
ExecutionRequests. These changes require a coordinated execution-layer and
consensus-layer fork.
At initial activation, execution clients stop deriving EIP-6110 requests and use the generalized request type for both deposit contracts.
The new ABI is not drop-in compatible. It adds scheme, removes
deposit_data_root, and changes withdrawal_credentials from dynamic bytes
to bytes32. The legacy contract remains the compatible path for unmodified
BLS tooling until retirement.
Contract and request phases
The deployment window starts after the EIP-6110 transition and contract deployment. It ends before the initial activation block. The migration period ends before the BLS retirement block.
| Phase | New contract | Legacy contract | Execution-layer derivation | Consensus-layer processing |
|---|---|---|---|---|
| Deployment window | Rejects all deposits | Accepts valid legacy deposits | Derives only EIP-6110 requests | Processes only legacy deposits |
| Migration period | Accepts deposits under BLS_ENABLED | Accepts valid legacy deposits | Converts both event formats into the generalized request type | Processes one ordered generalized list |
| Post-retirement | Rejects BLS and accepts valid non-BLS deposits | Still accepts valid legacy deposits | Derives generalized requests only from the new contract | Processes the generalized list |
Test Cases
Implementations MUST pass the test cases.
Reference Implementation
The Solidity reference implementation
illustrates the required contract behavior. The final bytecode and deployment
constants remain TBD.
Security Considerations
Permanently locked deposits
A wrong-length BLS deposit reverts. An invalid exact-length deposit or an unassigned scheme can still pass contract checks. Consensus does not retain or replay an ignored request, and the contract retains its ETH.
Deposit tooling must use an assigned scheme value, match the credentials to that scheme, and validate all available proofs before it sends a transaction.
Legacy contract after retirement
The immutable legacy contract accepts deposits included in or after the retirement block, but the execution layer creates no request. This applies even if the user signed and broadcast the transaction before retirement. The inclusion block controls the result.
Wallets, launchpads, staking providers, and explorers must stop legacy deposits well before retirement. They must make sure that no legacy deposit transaction remains pending at the boundary.
Incorrect scheme value
The contract applies BLS rules only when scheme is 0. Consensus processes
each request under its declared scheme and never infers BLS from field lengths.
An incorrect scheme cannot bypass BLS retirement and can permanently lock the
deposited ETH.
System-call safety
Only SYSTEM_ADDRESS can change the mode. Each transition requires the correct
mode and one-byte command. A failed required call makes the block invalid.
Credential identity
This EIP does not define how a future variable-length public key maps to a validator identity. A future credential-scheme EIP must define registry uniqueness, duplicate deposits, top-ups, and key replacement.
Copyright
Copyright and related rights waived via CC0.
