# EIP-7044
Perpetually Valid Signed Voluntary Exits
--
_PEEP an EIP @dapplion_
---
## Goal
Improve staking operations on splitted credentials, with minimal protocol changes
note:
- Compared to everything else coming with Deneb this EIP has low impact
- It has been included due to it's trivial implementation complexity
- Just something that makes sense to do now, so why not
---
## Voluntary exit
```mermaid
flowchart LR
A(.) -->|Deposit| B(Active)
B -->|Voluntary Exit| C(Exited)
```
note: What is a VoluntaryExit? A validator life-cycle message to start the exit process of a validator. After some time, the funds will be available to the withdrawal credentials.
---
## Staking models
A set of two credentials:
- Withdrawal credentials
- Validating credentials
note: What do this credentials represent? In the beacon chain there's a distinction between validating credentials and withdrawal credentials. Do perform its duties, a validator must sign messages continously, so the validating key must be "hot". That means it's more exposed to attacks, so to limit the liability of this key the ultimate control of staking funds is relegated to the withdrawal credentials. This credentials are "cold" and are not required to sign any message through the validator lifetime.
---
Single entity stakers (solo stakers, fully custodials)
- Entity A: Withdrawal + Validating
note: simpler
---
Third-party staking services (LIDO, RocketPool, etc)
- Entity A: Withdrawal
- Entity B: Validating
---
Validating entity can keep funds hostage
```mermaid
sequenceDiagram
Withdrawal->>+Validating: Deposit, fund validating key
Validating->>+Validating: Trigger voluntary exit
Validating->>+Withdrawal: Funds withdrawn
```
---
Withdrawal entity should be able to recall funds
```mermaid
sequenceDiagram
Withdrawal->>+Validating: Deposit, fund validating key
Withdrawal->>+Validating: Trigger voluntary exit
Validating->>+Withdrawal: Funds withdrawn
```
---
With pre-signed volutary exits before deposit
```mermaid
sequenceDiagram
participant Withdrawal
participant Validating
Validating->>+Withdrawal: Pre-sign voluntary exit
Withdrawal->>+Validating: Deposit, fund validating key
Withdrawal->>+Validating: Broadcast voluntary exit
Validating->>+Withdrawal: Funds withdrawn
```
note: A way to allow the Withdrawal entity to do this trigger is by pre-signing voluntary exits before deposits.
---
With EIP-7002: Execution layer triggerable exits
```mermaid
sequenceDiagram
Withdrawal->>+Validating: Deposit, fund validating key
Withdrawal->>+Validating: Call `trigger_exit()`
Validating->>+Withdrawal: Funds withdrawn
```
note: Adds a cross-layer method to trigger validator exits from the execution layer
---
Pre-signing voluntary exits has a catch..
---
```python
class VoluntaryExit(Container):
epoch: Epoch # Earliest epoch when voluntary exit can be processed
validator_index: ValidatorIndex
class SignedVoluntaryExit(Container):
message: VoluntaryExit
signature: BLSSignature
```
note: The voluntary message expresses the earliest epoch at which the message can be included, otherwise it's invalid
---
```python
def process_voluntary_exit(state, signed_voluntary_exit) -> None:
...
assert state.epoch >= voluntary_exit.epoch
```
note: block processing code ensures that property
---
```python
def process_voluntary_exit(state, signed_voluntary_exit) -> None:
# Verify signature
domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch)
signing_root = compute_signing_root(voluntary_exit, domain)
assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature)
```
note: signature verification on any beacon chain object follows a similar pattern: the signing message is domain separated
---
## Domain separation
- Chain's genesis
- Message type
- Fork version
note: the domain is derived from mixing this separators:
- chain's genesis validators root: uniquely identifies a chain by its genesis set of validators
- message type: separate signatures for blocks to attestations to voluntary exits
- fork version: separate messages signed for Bellatrix, Capella, each unique hard-fork with a unique 4 byte code
---
## Fork version
```yaml
GENESIS_FORK_VERSION: 0x00000000
ALTAIR_FORK_VERSION: 0x01000000
BELLATRIX_FORK_VERSION: 0x02000000
CAPELLA_FORK_VERSION: 0x03000000
DENEB_FORK_VERSION: 0x04000000
```
note: To pre-sign perpetually valid exits you could assume that future `fork_version` will follow the same nonce incrementing pattern. However there's no rule enforcing that and so a future fork could adopt a random fork_version if the community really wanted to.
---
```python
def get_domain(state, domain_type, epoch) -> Domain:
...
fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version
...
```
note: In particular the fork_version is derived only with data available in the spec. While clients have available all previous fork_versions, specified in the config, the spec does not. So messages signed in forks older than the previous fork will coerce their fork_version to the previous. Thus a signature for a fork older than 2 can't be validated.
---
| Message | Perpetually valid |
| -------- | -------- |
| `Deposit` | Yes |
| `SignedVoluntaryExit` | No |
| `SignedBLSToExecutionChange` | Yes |
note: Pre-signing voluntary exits with incrementing fork_versions is brittle, and would just be simpler to have a message independent of the fork version
---
```python
def process_bls_to_execution_change(state, signed_address_change):
...
# Fork-agnostic domain since address changes are valid across forks
domain = compute_domain(DOMAIN_BLS_TO_EXECUTION_CHANGE, fork_version=GENESIS_FORK_VERSION)
signing_root = compute_signing_root(address_change, domain)
assert bls.Verify(address_change.from_bls_pubkey, signing_root, signed_address_change.signature)
```
---
So why are `SignedVoluntaryExit`s not perpetually valid today?
---
```mermaid
graph LR
B[Bellatrix] --> C[Capella]
C --> D1[Deneb fork 1]
D1 --> E1[Electra fork 1]
C --> D2[Deneb fork 2]
D2 --> E2[Electra fork 2]
```
note:
- The divergent signature domains across forked networks would previously have prevented the replay of VoluntaryExits after two hard forks. This specification change causes the replay protection to no longer exist. These potential replays could impact individual stakers on both sides of a fork, but does not put funds at risk and does not impact the security of the chain.
- For that to be relevant you will have to be actively participating in both chains. If that was the case and you are forced to exit on the second chain too, then you can just re-deposit latter.
---
## Other uses:
- Auto-exit in case of long inactivity
- Dead man's switch
note:
- Recovery mechanism when your keys are lost. You sign VoluntaryExit at the start and either keep it in some place (it is completely secure to keep it in a place not that safe as your mnemonic) or submit it to a 3rd party service. Then if you loose keys you either submit this message by yourself or a service does it seeing your inactivity.
- This also may be helpful when your setup experience downtime causing your validator to suffer from inactivity penatlies, and you can’t fix them for whatever reason. But what you can do is to find an access to the chain and send this message.
---
## EIP-7002: Execution layer triggerable exits
- For 0x01 credentials only
- More UX friendly solution for liquid staking
---
## Status
- Included for Deneb
- Implementations: [Lodestar#5688](https://github.com/ChainSafe/lodestar/pull/5688)
---
## Thank you!
Github / Twitter: @dapplion
{"description":"View the slide with \"Slide Mode\".","title":"EIP-7044: PEEP an EIP","contributors":"[{\"id\":\"12d0a06b-0b31-4b69-82eb-e434ac2aa103\",\"add\":12474,\"del\":4585}]"}