DepositSecurityModule
Due to front-running vulnerability, Lido contributors proposed to establish the Deposit Security Committee dedicated to ensuring the safety of deposits on the Beacon chain:
- monitoring the history of deposits and the set of Lido keys available for the deposit, signing and disseminating messages allowing deposits;
- signing the special message allowing anyone to pause deposits once the malicious Node Operator predeposits are detected.
Each member must generate an EOA address to sign messages with their private key. The addresses of the committee members will be added to the smart contract.
To make a deposit, we propose to collect a quorum of 4/6 of the signatures of the committee members. Members of the committee can collude with node operators and steal money by signing bad data that contains malicious predeposits. To mitigate this, we propose allowing a single committee member to stop deposits and also enforce space deposits in time (e.g., no more than 150 deposits with 25 blocks in between them) to provide the single honest participant the ability to stop further deposits even if the supermajority colludes.
The guardian himself, or anyone else who has a signed pause message, can call pauseDeposits that pauses DepositSecurityModule.
To prevent a replay attack, the guardians sign the block number when malicious predeposits are observed. After a certain number of blocks (pauseIntentValidityPeriodBlocks) message becomes invalid.
Values of the parameters maxDepositsPerBlock and minDepositBlockDistance are controlled by Lido DAO and must be harmonized with appearedEthAmountPerDayLimit of OracleReportSanityChecker. These parameters are set in the StakingRouter contract independently for each module.
View Methods​
getOwner()​
Returns the contract's owner address.
function getOwner() external view returns (address);
getPauseIntentValidityPeriodBlocks()​
Returns pauseIntentValidityPeriodBlocks (see pauseDeposits).
function getPauseIntentValidityPeriodBlocks() external view returns (uint256);
getMaxOperatorsPerUnvetting()​
Returns the maximum number of operators per unvetting (see unvetSigningKeys).
function getMaxOperatorsPerUnvetting() external view returns (uint256);
getGuardianQuorum()​
Returns the number of valid guardian signatures required to vet (depositRoot, nonce) pair.
function getGuardianQuorum() external view returns (uint256);
getGuardians()​
Returns guardian committee member list.
function getGuardians() external view returns (address[] memory);
isGuardian()​
Checks whether the given address is a guardian.
function isGuardian(address addr) external view returns (bool);
Parameters​
| Name | Type | Description |
|---|---|---|
addr | address | Valid ETH-1 address |
getGuardianIndex()​
Returns index of the guardian, or -1 if the address is not a guardian.
function getGuardianIndex(address addr) external view returns (int256);
Parameters​
| Name | Type | Description |
|---|---|---|
addr | address | Valid ETH-1 address |
getLastDepositBlock()​
Returns the block number of the last deposit made through the module.
function getLastDepositBlock() external view returns (uint256);
isMinDepositDistancePassed()​
Returns whether the deposit distance is greater than the minimum required for the staking module
with id stakingModuleId.
function isMinDepositDistancePassed(uint256 stakingModuleId) external view returns (bool);
Parameters​
| Name | Type | Description |
|---|---|---|
stakingModuleId | uint256 | Id of the staking module |
The distance is reset when a deposit is made to any module. This prevents a front-run attack by colluding guardians on several modules at once, providing the necessary window for an honest guardian to react and pause deposits to all modules.
isDepositsPaused()​
Returns whether deposits are paused.
function isDepositsPaused() external view returns (bool);
Methods​
setOwner()​
Sets new owner.
function setOwner(address newValue) external;
Reverts if any of the following is true:
msg.senderis not the owner;newValueis zero address.
Parameters​
| Name | Type | Description |
|---|---|---|
newValue | address | New owner address |
setPauseIntentValidityPeriodBlocks()​
Sets pauseIntentValidityPeriodBlocks.
function setPauseIntentValidityPeriodBlocks(uint256 newValue) external;
Reverts if any of the following is true:
msg.senderis not the owner;newValueis 0 (zero).
Parameters​
| Name | Type | Description |
|---|---|---|
newValue | uint256 | Number of blocks after which message becomes invalid |
setMaxOperatorsPerUnvetting()​
Sets maxOperatorsPerUnvetting.
function setMaxOperatorsPerUnvetting(uint256 newValue) external;
Reverts if any of the following is true:
msg.senderis not the owner;newValueis 0 (zero).
Parameters​
| Name | Type | Description |
|---|---|---|
newValue | uint256 | New maximum number of operators per unvetting |
setGuardianQuorum()​
Sets the number of valid guardian signatures required to vet (depositRoot, nonce) pair (aka "quorum").
function setGuardianQuorum(uint256 newValue) external;
Reverts if any of the following is true:
msg.senderis not the owner;
Parameters​
| Name | Type | Description |
|---|---|---|
newValue | uint256 | New quorum value |
addGuardian()​
Adds a guardian address and sets a new quorum value.
function addGuardian(address addr, uint256 newQuorum) external;
Reverts if any of the following is true:
msg.senderis not the owner;addris zero address;addris already a guardian.
Parameters​
| Name | Type | Description |
|---|---|---|
addr | address | Guardian address |
newQuorum | uint256 | New Quorum value |
addGuardians()​
Adds a set of guardian addresses and sets a new quorum value.
function addGuardians(address[] memory addresses, uint256 newQuorum) external;
Reverts if any of the following is true:
msg.senderis not the owner;- any of the
addressesis zero address; - any of the
addressesis already a guardian.
Parameters​
| Name | Type | Description |
|---|---|---|
addresses | address[] | Array of Guardian addresses |
newQuorum | uint256 | New Quorum value |
removeGuardian()​
Removes a guardian with the given address and sets a new quorum value.
function removeGuardian(address addr, uint256 newQuorum) external;
Reverts if any of the following is true:
msg.senderis not the owner;addris not a guardian.
Parameters​
| Name | Type | Description |
|---|---|---|
addr | address | Guardian address |
newQuorum | uint256 | New Quorum value |
pauseDeposits()​
Pauses deposits if both conditions are satisfied (reverts otherwise):
-
The function is called by a guardian OR
sigis a valid signature by a guardian of the data defined below. -
block.number - blockNumber <= pauseIntentValidityPeriodBlocks
The signature, if present, must be produced for keccak256 hash of the following message (each component taking 32 bytes):
| PAUSE_MESSAGE_PREFIX | blockNumber |
Does nothing if deposits are already paused.
In case of an emergency, the function pauseDeposits is supposed to be called
by all guardians. Thus, only the first call will do the actual change. So
the other calls would be OK operations from the point of view of the protocol logic.
function pauseDeposits(uint256 blockNumber, Signature memory sig) external;
Parameters​
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | Block number with malicious predeposits have been observed by the guardian |
sig | Signature | Short ECDSA guardian signature as defined in EIP-2098 |
unpauseDeposits()​
Unpauses deposits.
function unpauseDeposits() external;
Reverts if any of the following is true:
msg.senderis not the owner.- Deposits not paused.
depositBufferedEther()​
Verifies that all deposit security conditions are satisfied, then calls StakingRouter.deposit, which pulls the required ETH from Lido and performs the deposits. Reverts if any of the required conditions are not met.
Reverts if any of the following is true:
- onchain deposit root is different from the provided one;
- onchain module nonce is different from the provided one;
- quorum is zero or the number of guardian signatures is less than the quorum;
- min deposit distance is not passed;
blockHashis zero or not equal toblockhash(blockNumber);- deposits are paused;
- an invalid or non-guardian signature received;
- signatures are not sorted in ascending order by the guardian address.
- any downstream contract call reverts. See
StakingRouter.depositfor details.
Signatures must be sorted in ascending order by the address of the guardian. Each signature must be produced for the keccak256 hash of the following message (each component taking 32 bytes):
| ATTEST_MESSAGE_PREFIX | blockNumber | blockHash | depositRoot | stakingModuleId | nonce |
function depositBufferedEther(
uint256 blockNumber,
bytes32 blockHash,
bytes32 depositRoot,
uint256 stakingModuleId,
uint256 nonce,
Signature[] calldata sortedGuardianSignatures
) external;
Parameters​
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | Number of the current deposit block |
blockHash | bytes32 | Hash of the current deposit block |
depositRoot | bytes32 | Deposit root of the Ethereum DepositContract |
stakingModuleId | uint256 | Id of the staking module to deposit with |
nonce | uint256 | Nonce of key operations of the staking module |
sortedGuardianSignatures | Signature[] | Short ECDSA guardians signatures as defined in EIP-2098 |
unvetSigningKeys()​
Unvets signing keys for the given node operators.
Reverts if any of the following is true:
- The nonce is not equal to the on-chain nonce of the staking module;
- nodeOperatorIds is not packed with 8 bytes per id;
- vettedSigningKeysCounts is not packed with 16 bytes per count;
- the number of node operators is greater than maxOperatorsPerUnvetting;
- the signature is invalid or the signer is not a guardian;
- blockHash is zero or not equal to the blockhash(blockNumber).
The signature, if present, must be produced for the keccak256 hash of the following message: | UNVET_MESSAGE_PREFIX | blockNumber | blockHash | stakingModuleId | nonce | nodeOperatorIds | vettedSigningKeysCounts |
function unvetSigningKeys(
uint256 blockNumber,
bytes32 blockHash,
uint256 stakingModuleId,
uint256 nonce,
bytes calldata nodeOperatorIds,
bytes calldata vettedSigningKeysCounts,
Signature calldata sig
) external;
Parameters​
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | Number of the current deposit block |
blockHash | bytes32 | Hash of the current deposit block |
stakingModuleId | uint256 | Id of the staking module to deposit with |
nonce | uint256 | Nonce of key operations of the staking module |
nodeOperatorIds | bytes | The list of node operator IDs packed with 8 bytes per id |
vettedSigningKeysCounts | bytes | The list of vetted signing keys counts packed with 16 bytes per count |
sig | Signature | Short ECDSA guardians signatures as defined in EIP-2098 |