EIP.tools

EIP.tools

⚠️ DraftStandards Track: Core

EIP-8413: Decouple Stake from Validator Registration

Separates economic stake creation on the execution layer from validator registration on the consensus layer.

Authors
Created2026-09-09
Discussion Linkhttps://ethereum-magicians.org/t/eip-8413-decouple-stake-from-validator-registration/29628
Requires

Markdown

https://raw.githubusercontent.com/kevaundray/EIPs/...

EIP-GPT summary

Contents
AbstractMotivationSpecificationOverviewStake Creation and IdentityAuthorization CommitmentRole RegistrationLifecycle OperationsUnregistered stakeTop-ups and exitsConsolidationsWithdrawalsRecovery of unregistered stakeRationaleVersioned commitmentsConsensus-layer registrationWithdrawal-only recoveryBackwards CompatibilityExisting validatorsTest CasesSecurity ConsiderationsCopyright

Abstract

This EIP separates stake creation from validator registration.

A staking contract on the execution layer accepts ETH, withdrawal credentials, and a versioned authorization commitment, and immediately returns a StakeID.

A separate registration contract accepts the StakeID and registration data. For BLS, the registration data is the validator public key and proof of possession. The consensus layer verifies the registration data before queuing the stake for pending-deposit processing.

Top-ups, withdrawals, and consolidations identify the stake by StakeID instead of the validator public key.

Motivation

Stake creation currently carries validator keys and proofs tied to consensus credential formats. A staking contract that accepts versioned authorization commitments can support future credential schemes without changing its stake creation interface.

Stake owners also need an identifier before validator registration completes. The staking transaction returns a StakeID immediately, allowing users and contracts to reference the position without waiting for a validator index.

This identifier provides a common reference for top-ups, consolidations, and withdrawals. It persists before registration and after validator exit, while the consensus layer remains authoritative for balances and lifecycle rules.

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.

Overview

The staking contract on the execution layer (EL) records stake creation and top-ups in an append-only event tree. A separate registration contract sends registration requests to the consensus layer (CL). The CL maintains stake balances, verifies registration, and manages the validator lifecycle.

Staking has two steps:

  1. Create stake: send ETH to the staking contract and receive a StakeID.
  2. Register a validator: submit the StakeID, validator credential, and registration proof. The consensus layer verifies them and queues the stake for pending-deposit processing.

The existing deposit flow combines these steps. Separating them gives the staker an identifier before validator creation and lets future authorization formats use the same staking contract.

Stake Creation and Identity

A new execution-layer staking contract creates each stake position through this conceptual interface:

function stake(
    bytes32 withdrawalCredentials,
    bytes32 authorizationCommitment
) external payable returns (uint64 stakeId);

authorizationCommitment is a versioned commitment to validator credentials. The initial version commits to the BLS public key authorized to register for the stake position.

Stake creation receives this commitment instead of the validator public key and proof of possession. The credential is revealed and verified later during registration.

The staking contract maintains a single append-only Merkle tree of stake events. We call this the stake-event tree. The stake-event tree contains two event types:

CREATE_STAKE {
    withdrawal_credentials
    authorization_commitment
    initial_amount
}

TOP_UP {
    stake_id
    amount
}

For a newly created stake, StakeID equals the event index of its CREATE_STAKE event:

stake_id := event_index(CREATE_STAKE)

The stake-event tree has height 40 and capacity for 2**40 leaves, including the reserved migration prefix. Valid stake IDs satisfy 0 <= stake_id < 2**40.

Consensus serialization MUST represent each StakeID as an unsigned 40-bit value, including references in stake-related requests.

The Solidity interface uses uint64 for convenience; however, when the EL processes logs, it should check that the top 24 bits are zero. No process should wrap or truncate the StakeID.

Both stake creation and top-ups consume event-tree capacity. Once event_count reaches 2**40, including the reserved migration prefix, the contract MUST reject both operations.

The contract MUST NOT allocate StakeIDs from a separate counter. Event indices advance for every event. Stake IDs are also monotonically increasing but sparse:

tree-index 8  -> TOP_UP(stake_id=3, ...)
tree-index 9  -> TOP_UP(stake_id=5, ...)
tree-index 10 -> CREATE_STAKE(...)          // stake_id = 10
tree-index 11 -> TOP_UP(stake_id=10, ...)
tree-index 12 -> TOP_UP(stake_id=3, ...)
tree-index 13 -> CREATE_STAKE(...)          // stake_id = 13

For a new stake, a valid StakeID = i identifies a CREATE_STAKE event at stake_event_tree[i]. Migrated stakes use their original validator indices, as defined in Existing validators. Indices occupied by TOP_UP events are not valid StakeIDs.

The CREATE_STAKE leaf commits to immutable creation parameters. Each later TOP_UP appends an event that references the original StakeID.

In the existing deposit contract, every deposit, including a top-up, gets a new deposit index. Here, every event gets an index, but only CREATE_STAKE creates a new StakeID.

The exact SSZ or hashing construction remains to be specified. The contract exposes or commits to at least the stake-event tree root and event count.

The CL maintains mutable state separately:

StakePositionState {
    withdrawal_credentials
    authorization_commitment
    balance
    validator_index: Optional[ValidatorIndex]
}

Registration, top-ups, consolidations, withdrawals, and exit-related accounting reference the original StakeID. The initial event count follows the migration rules in Existing validators.

The CL stores each stake's balance by StakeID. Validator rewards and penalties update the balance of the associated stake, replacing the existing balance lookup by ValidatorIndex.

Authorization Commitment

The authorization commitment identifies the credential permitted to register for the stake. Withdrawal credentials separately control withdrawals.

The initial format authorizes validator registration using a BLS public key. Future versions can include role permissions, such as includer-only or PTC-only participation. Those formats and roles are outside this EIP's current scope.

An authorization commitment is a versioned, fixed-size value:

AuthorizationCommitment := Bytes32
byte 0       authorization format version
bytes 1..31  commitment payload

The proposed initial construction is:

authorization_commitment = VERSION_1 || SHA256(public_key)[1:]

The version defines the credential encoding, commitment construction, and proof verification rules. The CL reads it from the stored authorization commitment.

Role Registration

Role registration reveals the preimage of the authorized commitment for a particular StakeID and proves that the submitter is allowed to register a validator under this StakeID. For BLS, this means supplying proves that they are allowed to register a validator under this StakeID. For BLS, this means supplying the proof of possession for the public key in the preimage.

An operator submits a request through a separate EL registration contract using this conceptual interface:

function register(
    uint64 stakeId,
    bytes calldata registrationData
) external;

registrationData contains the credential and proof authorizing registration for the referenced stake.

Note that unlike the previous deposit contract, a bad signature doesn't imply the stake is lost.

The contract produces the following execution request which gets sent to the CL:

ValidatorRegistrationRequest {
    stake_id
    registration_data
}

The CL processes the request in this order:

  1. Verify that the stake exists in CL state, has no pending registration or recovery withdrawal, and is not already bound to a validator.
  2. Decode registration_data according to the version in the stake's authorization commitment.
  3. Verify that the revealed authorization data matches the stake's authorization commitment, including any permissions encoded by that version.
  4. Verify that the registration proof is valid under the stake's authorization version.
  5. Verify that no pending registration reserves the credential and that no existing or exited validator previously used it.

After the stake eligibility checks, decoding and cryptographic verification proceed conceptually as follows:

stake = state.stakes[stake_id]
version = stake.authorization_commitment[0]
authorization, registration_proof = decode_registration_data(
    version,
    registration_data
)
assert compute_authorization_commitment(
    version,
    authorization
) == stake.authorization_commitment
assert verify_registration_proof(
    version,
    authorization,
    registration_proof
)

Successful registration enters pending-deposit processing with the StakeID and verified credential. The CL MUST reserve both against duplicate registration. stake.validator_index remains unset until queue processing creates the validator and assigns its index. Finalization, processing limits, churn, and activation remain subject to CL rules.

Pending-deposit processing MUST NOT credit the stake balance again. Queue encoding and treatment of balance changes while pending remain to be specified.

The stake-to-validator binding is permanent. Neither the StakeID nor the credential can register another validator after exit, slashing, or full withdrawal.

Registration status is authoritative on the CL. The EL MUST NOT expose canonical registration status without an explicit mechanism that returns it from the CL.

Lifecycle Operations

Unregistered stake

A stake position can exist without successful registration. The operator can delay registration, or registration can fail because of a commitment mismatch, invalid proof of possession, or consumed credential.

Withdrawal recovery is described in Recovery of unregistered stake.

Top-ups and exits

A top-up references an existing stake position:

function topup(
    uint64 stakeId
) external payable;

The EL appends a TOP_UP { stake_id, amount } event to the stake-event tree. This advances the event count while preserving the referenced StakeID.

We do not change the CL semantics regarding top-ups:

Stake or validator stateTop-up behavior
UnregisteredIncrease the balance available to the future validator.
ActiveIncrease the stake balance normally.
ExitedIncrease the stake balance and make it withdrawable according to CL rules.

Consolidations

For registered validators, consolidation retains the CL rules introduced by EIP-7251. Requests identify source and target stakes by StakeID instead of validator public keys:

StakeConsolidationRequest {
    source_stake_id
    target_stake_id
}

The CL resolves the stake IDs to their associated validators and applies existing authorization, eligibility, exit, churn, and transfer rules. Balance transfers update the corresponding stake balances. Consolidation MUST NOT change either position's StakeID, authorization commitment, withdrawal credentials, or validator binding.

Withdrawals

Withdrawals reference the stake position:

StakeWithdrawalRequest {
    stake_id
    amount
}

For registered validators, withdrawal rules remain unchanged. Requests identify the stake by StakeID instead of the validator public key.

Recovery of unregistered stake

Before registration is accepted into the pending-deposit queue, the withdrawal authority can request recovery of the full stake balance.

ConstantValueMeaning
MAX_UNREGISTERED_WITHDRAWALS_PER_PAYLOAD8Maximum recovery payments per block.
PENDING_UNREGISTERED_WITHDRAWALS_LIMIT4096Maximum recovery requests waiting in CL state.
  • The CL MUST verify that the request's authenticated sender matches the stake's withdrawal address before adding it to the queue.
  • Requests are immediately eligible, with no mandatory delay. They are processed in acceptance order, up to eight per block and within the overall withdrawal payload limit. Remaining requests wait for a later block.
  • If the queue is full, the CL MUST ignore new recovery requests. The funds remain in the stake, and the owner can retry later.
  • The CL MUST reject unregistered-stake recovery after registration enters the pending-deposit queue. Ordinary validator withdrawal rules apply after validator creation. While recovery is queued, the CL MUST reject registration and duplicate recovery requests for that stake.
  • Recovery preserves the StakeID and authorization commitment. Later top-ups can fund registration under the same commitment.
  • Unregistered stakes MUST NOT be consolidation sources or targets. To fund another stake, the owner first withdraws the ETH, then deposits it through the staking contract.

Rationale

Versioned commitments

A fixed-size commitment lets the staking contract accept future credential formats without changing its interface.

Consensus-layer registration

The CL holds the state needed to check credential reuse and validator status. Verifying registration there avoids duplicating that state in an EL contract. Using the EL to transport the information to the CL lets operators submit registration through the EL's transaction inclusion mechanisms, including fork-choice enforced inclusion lists (FOCIL, EIP-7805), and be rate-limited by EL infrastructure (the gas limit).

Withdrawal-only recovery

Allowing unregistered stakes to consolidate would introduce balance transfers outside the existing validator consolidation rules. Withdrawal recovery returns funds to their owner, who can fund another stake through the staking contract.

Backwards Compatibility

This change requires a coordinated fork, a new staking contract, and updates to staking tools and CL balance accounting.

Activation rules remain unchanged. The fork transition must define how pending legacy deposits receive stake IDs and how deposits to the old contract are handled after the fork.

Existing validators

Each existing validator receives a StakeID equal to its ValidatorIndex, including exited, slashed, and fully withdrawn validators. Migration copies its current balance and withdrawal credentials into the stake position and sets validator_index. The validator retains its credential and lifecycle state without another registration or proof of possession.

Let N be the complete validator registry length at migration, with N < 2**40. The tree reserves leaves [0, N) as zero-filled placeholders, and the contract initializes event_count = N. These leaves contain no creation events. At the fork, the CL creates these stake records from the existing validators and their balances.

New events start at index N, preventing collisions with migrated stake IDs. The reserved prefix leaves capacity for 2**40 - N new events.

Migrated stake IDs remain valid for top-ups, withdrawals, and consolidations under the applicable CL rules.

Test Cases

TODO

Security Considerations

TODO

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