# 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}]"}
    821 views