Dissecting Ethereum delegated staking from a security perspective — Part 2
Welcome to our second article on Security Considerations for Ethereum Delegated Proof of Stake (DPoS) platforms. As we explored previously, there are multiple security challenges associated with delegated staking, nearly all related to the excessive trust expected by staking platforms.
In this post, we’ll delve into what led us to the conclusions presented in the previous article, by answering questions that emerged during security audits to ETH DPoS platforms conducted by Coinspect. As usual, we’ll provide relevant information sources for those looking to learn more.
Firstly, we explain the deposit function from the staking contract in detail:
The deposit function explained
The deposit function has the following signature:
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
)
override external payable
Being:
- Public Key (pubkey)
The validator’s BLS12–381 public key. Its associated private key is used to operate the validator.
2. Withdrawal Credentials (withdrawal_credentials):
Used to withdraw deposited funds and validator earnings once the validator is deactivated. There are two options for the withdrawal credentials:
a) To provide the BLS public key hash allowed to trigger withdrawals in the following format:
withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX
withdrawal_credentials[1:] == hash(bls_withdrawal_pubkey)[1:]
b) To provide a 20-byte ETH address as the recipient for withdrawals. It could be a contract or an EOA address, in the following format:
withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX
withdrawal_credentials[1:12] == b'\x00' * 11
withdrawal_credentials[12:] == eth1_withdrawal_address
3. Signature (signature):
A BLS signature resulting from compute_signing_root(deposit_message*, domain). Used to prove that the signer is in control of the validator private key (proof of possession), preventing rogue attacks on the BLS aggregation property.
4. Deposit Root (deposit_data_root):
The root of the tree resulting from hash_tree_root(deposit_message*, signature)
Where:
deposit_message = pubkey (48 bytes) + withdrawal_credentials (32 bytes)
+ value (in wei)
Additional information on this can be found in the Ethereum consensus specs repository.
Ethereum staking Q&A
When/how are deposit parameters validated?
A few checks are done in the deposit function such as:
- The parameters length
- The deposit contract received at least 1 ether, in a multiple of gwei
- The deposit_data_root parameter was constructed according to the specifications
However, the deposit function does not verify the BLS signature, which takes us to the next question.
How are the deposit BLS signatures verified? What happens to incorrect BLS signatures once funds are deposited?
The deposit function does not validate the BLS signature on-chain. This signature, and thus, the withdrawal credentials are verified in the process_deposit function -we encourage taking a look at it. Note this function would be executed by Ethereum nodes and not on smart contracts as it is part of the Ethereum Consensus Specification.
Once the deposit is sent to the mempool, the BLS signature is validated within approx. 12 hours. It’s worth noticing that it is not safe to assume the deposit went through before it is validated.
If an invalid BLS signature is provided for the first validator’s deposit, funds will potentially be lost. The process_deposit function just ignores the deposit, skipping the remaining instructions. Up to this date, we haven’t found specifications or official Ethereum information indicating whether invalid deposits would be returned to depositors.
Is it possible to verify the BLS signature on-chain?
There’s currently no native functionality to verify a BLS signature on-chain. Although there are a few implementations out there, those are not part of the EVM nor officially approved.
Is it possible to deposit to a validator multiple times?
Yes, it is possible to deposit to a validator multiple times as long as the ETH sent complies with the requirements listed above. However, the first deposit will determine the withdrawal credentials accepted for the validator, since subsequent deposits performed to an already initialized validator could have an invalid 96-byte long signature and still be successfully processed.
From the process_deposit function mentioned above, notice that the withdrawal credentials are only validated when the `pubkey not in validator_pubkeys`condition satisfies. This implies that signatures from deposits after the initial one would not be verified. Therefore, the deposit will only be processed as a top-up.
Feel free to expand on this matter by reading the analysis of the vulnerability report submitted to Lido Finance in October 2021. This vulnerability analysis describes a front-running attack by rogue node operators, where they can initialize a validator using their withdrawal credentials and have victims top it up.
If my validator gets slashed, can I re-deposit?
The answer is no since a slashing means a large penalty and a forceful exit. However, if your validator was penalized and the ending balance did not drop below 16 ETH, you can top it up. On the other hand, if the balance of the validator falls below 16 ETH, a forced exit is applied. As a consequence, top-ups wouldn’t increase the current staking position since the validator wouldn’t be active anymore.
When is it safe to assume a validator was successfully staked? How can I verify it?
Once the 32 ETH are deposited, it is safe to assume a validator was successfully staked only after the completion of the second step of the validator staking process. This is, once the validator is confirmed as “Deposited”. To find out a validator’s status, you can visit the Beacon Chain explorer.
Therefore, in the case scenario of an on-chain platform that allows users to stake validators, approximately 12 hours after the deposit there’s no risk of an invalid BLS signature or deposit front-run. Note that the amount of time is defined in epochs, and therefore the number of hours required might change.
Once I unstake/deactivate the validator, how is the withdrawal process? Can I choose the address where the funds will be deposited?
At this time, it is not yet possible to withdraw unstaked funds from a validator, although the Shanghai/Capella upgrades (in development) should soon enable withdrawals. The withdrawal of funds is contingent upon the type of withdrawal credentials set for the validator; only those validators with ETH1 addresses will be able to withdraw funds. Plus, the Capella upgrade specification introduces automatic withdrawals of withdrawable validators.
Additionally, EIP 4895 which is in review, introduces a new operation supporting validator withdrawals, where withdrawals are “pushed” from the beacon chain to the EVM. The implementation of this hard fork would increase the specified recipient’s balance.
Regarding the withdrawal process, a ~27hr cooldown period must elapse after a validator is dequeued from the consensus layer. This time frame allows the consensus layer to include the last period’s rewards and ensure that no misbehavior is detected.
It is worth pointing out that currently, in phase 0, a validator can abandon its role but cannot withdraw anything until the network advances to the next phase. The only impact is that no penalties or slashes can be applied to that validator as no responsibility lies with him.
For those interested in diving deeper into the details of the withdrawal process, here is a recommended reading.
Key takeaways for depositors
As we’ve seen, one of the main concerns behind depositing to validators via Delegated Proof of Stake (DPoS) platforms is the excess of trust required by certain platforms. Subsequently, when choosing a platform, depositors should examine the amount of trust it requires by reviewing the points described in the previous article, and verify it has undergone security audits.
For more information about Coinspect, please visit www.coinspect.com