From c43c7ce4f8171bde50e6701a6ce413e6ea44d57e Mon Sep 17 00:00:00 2001 From: Azat Serikov Date: Fri, 27 Dec 2024 14:17:29 +0500 Subject: [PATCH 1/2] feat: BYODSM --- contracts/0.8.25/lib/DepositLogistics.sol | 41 ++++++++++++++++++- contracts/0.8.25/vaults/StakingVault.sol | 9 ++-- .../vaults/interfaces/IStakingVault.sol | 5 ++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/contracts/0.8.25/lib/DepositLogistics.sol b/contracts/0.8.25/lib/DepositLogistics.sol index da47fab30..b6316d1a6 100644 --- a/contracts/0.8.25/lib/DepositLogistics.sol +++ b/contracts/0.8.25/lib/DepositLogistics.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.25; import { Memory } from "../lib/Memory.sol"; import { IDepositContract } from "../interfaces/IDepositContract.sol"; +import {ECDSA} from "contracts/common/lib/ECDSA.sol"; /** * @title DepositLogistics @@ -13,6 +14,11 @@ import { IDepositContract } from "../interfaces/IDepositContract.sol"; * @dev Provides functionality to process multiple validator deposits to the Beacon Chain deposit contract */ library DepositLogistics { + struct Signature { + bytes32 r; + bytes32 vs; + } + /** * @notice Byte length of the BLS12-381 public key (a.k.a. validator public key) */ @@ -28,6 +34,11 @@ library DepositLogistics { */ uint256 internal constant AMOUNT_LENGTH = 8; + /** + * @notice Domain separator for the security signature + */ + bytes32 internal constant GUARDIAN_SIGNATURE_PREFIX = keccak256(abi.encodePacked(" ")); + /** * @notice Error thrown when the number of deposits is zero */ @@ -48,6 +59,11 @@ library DepositLogistics { */ error AmountsLengthMismatch(uint256 actual, uint256 expected); + /** + * @notice Error thrown when the guardian signature does not match the expected guardian + */ + error InvalidGuardianSignature(); + /** * @notice Processes multiple validator deposits to the Beacon Chain deposit contract * @param _depositContract The deposit contract interface @@ -63,7 +79,9 @@ library DepositLogistics { bytes memory _creds, bytes memory _pubkeys, bytes memory _sigs, - bytes memory _amounts + bytes memory _amounts, + Signature memory _guardianSignature, + address _guardian ) internal { if (_deposits == 0) revert ZeroDeposits(); if (_pubkeys.length != PUBKEY_LENGTH * _deposits) revert PubkeysLengthMismatch(_pubkeys.length, PUBKEY_LENGTH * _deposits); @@ -75,6 +93,9 @@ library DepositLogistics { bytes memory sig = Memory.alloc(SIG_LENGTH); bytes memory amount = Memory.alloc(AMOUNT_LENGTH); + // aggregate deposit data roots + bytes memory depositDataRoots; + for (uint256 i; i < _deposits; i++) { // Copy pubkey, sig, and amount to the allocated memory slots Memory.copy(_pubkeys, pubkey, i * PUBKEY_LENGTH, 0, PUBKEY_LENGTH); @@ -84,8 +105,26 @@ library DepositLogistics { uint256 amountInWei = _gweiBytesToWei(amount); bytes32 root = _computeRoot(_creds, pubkey, sig, amount); + depositDataRoots = abi.encodePacked(depositDataRoots, root); + _depositContract.deposit{value: amountInWei}(pubkey, _creds, sig, root); } + + // reverts the deposit transaction if the current deposit root does not match the expected one. + // The expected deposit root is the one against which the guardian signature was produced, i.e. + // against which the deposit data was validated to make sure there were no deposits with these pubkeys, + // i.e. no deposit front-running. + + // the guardians signs a concatenation of 3 things: + // 1. deposit prefix (a constant that reduces the signature validity space) + // 2. the deposit root against which the deposit data was validated + // 3. the aggregate of all deposit data roots + + bytes32 currentDepositRoot = _depositContract.get_deposit_root(); + bytes32 securityMessageHash = keccak256(abi.encodePacked(GUARDIAN_SIGNATURE_PREFIX, currentDepositRoot, depositDataRoots)); + address signer = ECDSA.recover(securityMessageHash, _guardianSignature.r, _guardianSignature.vs); + + if (signer != _guardian) revert InvalidGuardianSignature(); } /** diff --git a/contracts/0.8.25/vaults/StakingVault.sol b/contracts/0.8.25/vaults/StakingVault.sol index ccbb515b3..c064184e0 100644 --- a/contracts/0.8.25/vaults/StakingVault.sol +++ b/contracts/0.8.25/vaults/StakingVault.sol @@ -322,17 +322,18 @@ contract StakingVault is IStakingVault, IBeaconProxy, OwnableUpgradeable { * @param _pubkeys Concatenated validator public keys * @param _signatures Concatenated deposit data signatures * @param _amounts Concatenated deposit amounts in gwei + * @param _guardianSignature Guardian signature (owner in this case) * @dev Includes a check to ensure StakingVault is balanced before making deposits */ function depositToBeaconChain( uint256 _numberOfDeposits, bytes calldata _pubkeys, bytes calldata _signatures, - bytes calldata _amounts + bytes calldata _amounts, + DepositLogistics.Signature memory _guardianSignature ) external { if (_numberOfDeposits == 0) revert ZeroArgument("_numberOfDeposits"); if (!isBalanced()) revert Unbalanced(); - if (msg.sender != _getStorage().operator) revert NotAuthorized("depositToBeaconChain", msg.sender); DepositLogistics.processDeposits( IDepositContract(address(DEPOSIT_CONTRACT)), @@ -340,7 +341,9 @@ contract StakingVault is IStakingVault, IBeaconProxy, OwnableUpgradeable { bytes.concat(withdrawalCredentials()), _pubkeys, _signatures, - _amounts + _amounts, + _guardianSignature, + owner() ); emit DepositedToBeaconChain(msg.sender, _numberOfDeposits); diff --git a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol index fe0e82b6e..150ecdd49 100644 --- a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol +++ b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol @@ -4,6 +4,8 @@ // See contracts/COMPILERS.md pragma solidity 0.8.25; +import { DepositLogistics } from "../../lib/DepositLogistics.sol"; + /** * @title IStakingVault * @author Lido @@ -36,7 +38,8 @@ interface IStakingVault { uint256 _numberOfDeposits, bytes calldata _pubkeys, bytes calldata _signatures, - bytes calldata _sizes + bytes calldata _sizes, + DepositLogistics.Signature memory _guardianSignature ) external; function requestValidatorExit(bytes calldata _pubkeys) external; function lock(uint256 _locked) external; From 6604f2f0775f4f107854e6fafa334591c6f8fb74 Mon Sep 17 00:00:00 2001 From: Azat Serikov Date: Fri, 27 Dec 2024 14:20:41 +0500 Subject: [PATCH 2/2] fix: add prefix --- contracts/0.8.25/lib/DepositLogistics.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.25/lib/DepositLogistics.sol b/contracts/0.8.25/lib/DepositLogistics.sol index b6316d1a6..ce7c9ee22 100644 --- a/contracts/0.8.25/lib/DepositLogistics.sol +++ b/contracts/0.8.25/lib/DepositLogistics.sol @@ -37,7 +37,7 @@ library DepositLogistics { /** * @notice Domain separator for the security signature */ - bytes32 internal constant GUARDIAN_SIGNATURE_PREFIX = keccak256(abi.encodePacked(" ")); + bytes32 internal constant GUARDIAN_SIGNATURE_PREFIX = keccak256(abi.encodePacked("GuardianSignaturePrefix")); /** * @notice Error thrown when the number of deposits is zero