Trustless validator pools during phases 0 & 1
After the phase 2 rollout, validator deposits can be made with withdrawal credentials representing the address of a smart contract on a shard.
However, during phases 0 & 1, the only option for withdrawal credentials is the pubkey of a BLS keypair (or an aggregate key) submitted by the depositor. This poses a significant problem for trustless validator pools wanting to operate during this time:
- Assume there is a trustless staking pool consisting of 8 participants, each contributing 4 ETH. Each participant generates a BLS keypair, and the aggregate of these keys is submitted as the withdrawal credentials for the validator.
- If one participant loses their private key, the pool is prevented from withdrawing. Or, a participant can extort the rest of the pool for up to 4 ETH each, refusing to sign a withdrawal message until the fee is paid.
- Using a threshold BLS signature mitigates this problem, but has its own issues. e.g. if a threshold of 6/8 is chosen, then a single actor can use 24 of their own ETH to form an incomplete pool which they have complete control of. Alternatively, 6 of 8 actual participants could form a cartel and take control of a pool.
This makes it difficult for decentralized staking pools to operate during phases 0 & 1, potentially moving the ecosystem towards centralized / custodial platforms. There do not appear to be viable solutions to this problem outside of the protocol; some possible in-protocol solutions are outlined below:
One possible solution would be to record shares in a validator on the beacon chain itself. A naive implementation might look like the following:
Set MAX_VALIDATOR_SHARES to an arbitrary constant, e.g. 32
Update validator container to:
class Validator(Container):
pubkey: BLSPubkey
withdrawal_credentials: Vector[Bytes32, MAX_VALIDATOR_SHARES] # Commitments to pubkeys for withdrawals
withdrawal_shares: Vector[Gwei, MAX_VALIDATOR_SHARES] # Relative shares for each set of withdrawal credentials, by index
effective_balance: Gwei # Balance at stake
slashed: boolean
# Status epochs
activation_eligibility_epoch: Epoch # When criteria for activation were met
activation_epoch: Epoch
exit_epoch: Epoch
withdrawable_epoch: Epoch # When validator can withdraw funds
In the process_deposit method, if deposit.data.pubkey is found in validator_pubkeys (and before increase_balance is called):
- Set
validatortostate.validators[index] - Set
validator_balancetostate.balances[index] - Set
total_withdrawal_sharesto the sum ofvalidator.withdrawal_shares - If
total_withdrawal_shares != validator_balance, for eachvalidator.withdrawal_shares, updatesharetoshare * validator_balance / total_withdrawal_shares - Check for
deposit.data.withdrawal_credentialsinvalidator.withdrawal_credentials:- If found, then add
deposit.data.amounttovalidator.withdrawal_sharesat matching index; - If not found, and
len(validator.withdrawal_credentials) < MAX_VALIDATOR_SHARES, then appenddeposit.data.withdrawal_credentialstovalidator.withdrawal_credentialsand appenddeposit.data.amounttovalidator.withdrawal_shares
- If found, then add
During operations where validator balance is transferred (such as transfers and withdrawals):
- Verify the signed message against each set of withdrawal credentials in
validator.withdrawal_credentialsand find the matching shareindex - Verify that
validator.withdrawal_shares[index] > 0 - Set
transfer_amounttovalidator.withdrawal_shares[index] * validator_balance / total_withdrawal_shares(as described above) - Perform balance transfer using
transfer_amount - Set
validator.withdrawal_shares[index]to0 - Run
decrease_balance
Another possible solution involves adding an alternate withdrawal mechanism to the spec, where the submitted withdrawal credentials represent an eth 1.0 address, instead of a BLS pubkey (/ eth 2.0 address).
During the construction of validator deposit data:
- Set
ETH1_WITHDRAWAL_PREFIXto an arbitrary constant, e.g.Bytes1('0x01') - Set
withdrawal_credentials[:1]toETH1_WITHDRAWAL_PREFIX - Set
withdrawal_credentials[1:21]to the eth 1.0 withdrawal address - Set
withdrawal_credentials[21:]to null bytes
This solution requires no changes to the spec for phases 0 & 1, but obviously does require a strong commitment to an eth1 + eth2 merge e.g. as described in this ethresearch post. It also assumes that there will be a guaranteed mechanism whereby the beacon chain can process withdrawal messages from an eth1 address.
(Thanks to @dankrad for participating in initial discussions around this issue and helping to explore options!)