owned this note
owned this note
Published
Linked with GitHub
Pubkey-Change: Ethereum 2.0 Validator signing key-change Mechanism
===
![downloads](https://img.shields.io/github/downloads/atom/atom/total.svg)
![build](https://img.shields.io/appveyor/ci/:user/:repo.svg)
![chat](https://img.shields.io/discord/:serverId.svg)
# Table of Contents
[TOC]
## Project Proposal
Recent research [2][3][4] has shown the viability of ransomware attacks on Ethereum 2.0 Proof-of-Stake (PoS) validators, whereby an attacker who has compromised a validator signing key threatens to perform slashable actions unless a ransom is paid. Given the size of Ethereum validator stakes (32 ETH or 35,000 USD), validators are likely to become an extremely attractive target for future ransomware.
The goal is to explore consensus protocol adaptations to mitigate the risks of ransomware attacks on Ethereum 2.0 validators.
The project's objective is to design a new pubkey-change mechanism that will allow validators to improve their operational security by quickly changing the compromised signing key and replacing it without having to execute voluntary exit or withdrawing the stake and setting up a new validator.
#### Key Benefits
A validator whose signing key is compromised no longer has to exit the beacon chain in order to change their signing key. This feature is critical, considering it is currently not possible to withdraw the funds upon successful exit. Additionally, even when the withdrawal feature is enabled, in order to maintain stability, all withdrawal requests will be queued, thus, making funds unavailable for a period of time. From an investment perspective, any period of inactivity would mean an opportunity loss from potential rewards that could be earned whilst performing validator duties.
In our previous studies, we have demonstrated that there exists a game where criminals who have stolen the validator signing key could blackmail and extort money from the validator/s in order not to inflict any financial losses through slashing penalties. One could argue that a validator whose signing key has been compromised could perform voluntary exit; however, a smart or well informed criminal could immediately perform a slashable activity using the compromised signing key. As a result, the exit window would increase from a few hours/days to a maximum of 36 days (double check), thus, prolonging the extortion attack. In addition, it is important to note that whilst the validator/s is in the exit queue, the validator is expected to continue fulfilling its duties, and normal slashing rules apply until successfully exiting the Beacon chain. And it is for this reason the pubkey-change feature would be a useful feature to include in the future.
### How does the pubkey-change work?
**Assumption:** An Attacker has successfully breached the validator machine and stolen the validator signing key. The immediate next action for the validator is to change the signing key before the attacker can inflict any slashing penalties. Since the attacker already possesses the signing key, it cannot be used to initiate pubkey-change, as the attacker could do the same. Ideally, the change needs to be initiated using a key not previously compromised and only in possession of the validator --> Validator Withdrawal key
#### Conditions
* 1. The current signing key cannot be used to change the compromised signing key.
* 2. A period where the validator is made in-active whilst pubkey-change is performed.
### Development Progress Pubkey-change
Define a new payload-level object called a `pubkey-change` that describes pubkey-change that have initiated and validated at the consensus layer.
#### Pseudocode
Pubkey-change is a proposed consensus-layer upgrade containing a signing key change, a feature allowing validators to quickly change their compromised signing key.
##### Pseudocode: process_pubkey_change_updates
```python
def process_pubkey_change_updates(state: BeaconState) -> None:
for validator_index in len(state.validators):
validator = state.validators[validator_index]
if validator.pubkey != validator.new_pubkey:
if validator.pubkey_change_epoch == get_current_epoch(state):
validator.pubkey = validator.new_pubkey
validator.pubkey_change_epoch = FAR_FUTURE_EPOCH
else:
# Sanity check
assert(validator.pubkey_change_epoch) > get_current_epoch(state)
```
##### Pseudocode: process_pubkey_change
```python
def process_pubkey_change(state: BeaconState, signed_pubkey_change: SignedPubKeyChange) -> None:
pubkey_change = signed_pubkey_change.message
assert pubkey_change.validator_index < len(state.validators)
validator = state.validators[pubkey_change.validator_index]
# Verify the validator is active
assert is_active_validator(validator, get_current_epoch(state))
# Verify exit has not been initiated
assert validator.exit_epoch == FAR_FUTURE_EPOCH
assert validator.withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX
assert validator.withdrawal_credentials[1:] == hash(pubkey_change.from_bls_pubkey)[1:]
assert len(validator.prev_pubkeys) < MAX_VALIDATOR_PUBKEY_CHANGES
for prev_pubkey in validator.prev_pubkeys:
assert prev_pubkey != pubkey_change.new_pubkey
domain = get_domain(state, DOMAIN_PUBKEY_CHANGE)
signing_root = compute_signing_root(pubkey_change, domain)
assert bls.Verify(pubkey_change.from_bls_pubkey, signing_root, signed_pubkey_change.signature)
initiate_pubkey_change(state, pubkey_change.validator_index,
pubkey_change.new_pubkey)
```
##### Pseudocode: initiate_pubkey_change
*Note*: The function `initiate_pubkey_change` is new.
```python
def initiate_pubkey_change(state: BeaconState, index: ValidatorIndex,
new_pubkey: BLSPubkey) -> None: # [New in Revoke]
"""
pubkey != newpubkey (we have an outstanding queued revocation)
pubkey == pubkey (no outstanding revocations)
Also set revocation epoch to current epoch + PUBKEY_CHANGE_DELAY.
"""
# Return if validator already initiated revocation
validator = state.validators[index]
assert validator.pubkey != new_pubkey
# TODO: Other asserts?
# Set validator key revocation epoch
validator.new_pubkey = new_pubkey
# Schedule change for epoch boundary (for now)
validator.pubkey_change_epoch = get_current_epoch(state) + PUBKEY_CHANGE_DELAY
```
##### Pseudocode: test_process_pubkey_change
```python
from eth2spec.test.helpers.keys import pubkeys, privkeys, pubkey_to_privkey
from eth2spec.test.helpers.pubkey_changes import get_signed_pubkey_change
from eth2spec.test.context import spec_state_test, expect_assertion_error, with_revoke_and_later, always_bls
def run_pubkey_change_processing(spec, state, signed_pubkey_change, valid=True):
"""
Run ``process_pubkey_change``, yielding:
- pre-state ('pre')
- pubkey-change ('pubkey_change')
- post-state ('post').
If ``valid == False``, run expecting ``AssertionError``
"""
# yield pre-state - this will store some values to check later that certain updates happended.
yield 'pre', state
yield 'pubkey_change', signed_pubkey_change
# If the pubkey_change is invalid, processing is aborted, and there is no post-state.
if not valid:
expect_assertion_error(lambda: spec.process_pubkey_change(state, signed_pubkey_change))
yield 'post', None
return
# process pubkey change
spec.process_pubkey_change(state, signed_pubkey_change)
# Make sure the address change has been processed
validator_index = signed_pubkey_change.message.validator_index
validator = state.validators[validator_index]
assert validator.new_pubkey == signed_pubkey_change.message.new_pubkey
assert validator.pubkey_change_epoch == signed_pubkey_change.message.epoch
# yield post-state
yield 'post', state
```
We define the following Python custom types for type hinting and readability:
### New containers
#### `PubKeyChange`
```python
class PubKeyChange(Container): # [New in Revoke]
validator_index: ValidatorIndex
from_bls_pubkey: BLSPubkey
new_pubkey: BLSPubkey
```
#### `SignedPubKeyChange`
```python
class SignedPubKeyChange(Container): # [New in Revoke]
message: PubKeyChange
signature: BLSSignature
```
#### Preset
##### Max operations per block
| Name | Value |
| -------- | -------- |
| `MAX_BLS_TO_EXECUTION_CHANGES` | `2**4` (= 16)
##### Execution
| Name | Value | Description |
| -------- | -------- | -------- |
| `MAX_REVOCATIONS_PER_PAYLOAD` | `uint64`(2**4) (= 16) | Maximum amount of `revocations` allowed in each payload - Review
#### New Containers
### New Pubkey-change processing
| Name | Value | Description |
| - | - | - |
| `MAX_BLOCK_PUBKEY_CHANGES` | `uint64(2**4)` (= 16) | Maximum amount of pubkey-change allowed in each block |
| Name | Value | Description |
| - | - | - |
| `MAX_VALIDATOR_PUBKEY_CHANGES` | `uint64(2**4)` (= 16) | Maximum amount of validators allowed to initiate pubkey-change |
| Name | Value |
| - | - |
| `PUBKEY_CHANGE_DELAY` | `Epoch` (1) |
```python
ETH1_ADDRESS_WITHDRAWAL_PREFIX
```
### Github
https://github.com/abhudia04/consensus-specs-dev
### Security Considerations
1.Stop validators from abusing the pubkey-change mechanism.
2. In order to improve the security a queueing system will be implemented similar to withdrawal.
#### `is_public_change_scheduled`
```python
def is_pubkey_change_scheduled(validator: Validator) -> bool:
return validator.pubkey != validator.new_pubkey
```
### Challenges/Learning
1. Ability to able to update the smart contract aka deposit agreement.
2. Many approaches to solving the current, exploitable, implementation of slashing involve additional complexity. Key chains, pubkey-change mechanisms, and re-enrolment of affected validators all add significant complexity, in the form of command and control traffic over Ethereum (smart contract execution).
3. Maybe focused to much on implementing the feature and understanding the pyspec implementation. As a result towards the end it was noted that we didn't discuss some important security implications.
4. Creating a PR early to get feedback from Ethereum community
<!---
Notes from Meeting 1st Dec 2022
1.
-->
### Goal of the project
The aim of the project is to increase resilience to ransomware/extortion attacks against ETH 2.0 validators, while respecting the stated objectives of ETH 2.0 penalty mechanisms for malicious behaviour. The main research objective is to devise a signing pubkey-change mechanism which eliminates the profitability of extortion attacks by allowing validators to explicitly update their signing keys after initial slashing (subject to cooling-off/quarantine periods as per the current ETH 2.0 slashing definition).
### Future/next steps - Questions
- Finish implementation and an answer the following questions in the final report:
- What if the validator initiates exit during the same epoch with old pubkey?
- Validator penalties before and after the pubkey-change is applied?
- Security implications, e.g. staking pools?
- Further tests against the implemented pubkey-change feature
- Would pubkey-change make validator set cache difficult in client side?
- Implications of pubkey-change in sync committee- edge case. Note: old pubkey is still in 'state.current_sync_committee.pubkeys'
- explore process_forgiveness - under what circumstance/s could the slashing penalties be forgiven.
### Resources
* https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#execution-engine
* https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#process_execution_payload
* https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#new-process_bls_to_execution_change
* https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md
* https://ethresear.ch/t/eth1-eth2-client-relationship/7248
## Mentors
* Fredrik (Security)
* Hsiao-Wei Wang (PySpec)
## Appendix and FAQ
:::info
**Find this document incomplete?** Leave a comment!
:::
###### tags: `Templates` `Documentation`