Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ecdsa key rotation #252

Merged
merged 16 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,20 @@ interface ECDSAStakeRegistryEventsAndErrors {
/// @notice Emitted when the weight required to be an operator changes
/// @param oldMinimumWeight The previous weight
/// @param newMinimumWeight The updated weight
event UpdateMinimumWeight(uint256 oldMinimumWeight, uint256 newMinimumWeight);
event UpdateMinimumWeight(
uint256 oldMinimumWeight,
uint256 newMinimumWeight
);

/// @notice Emitted when the system updates an operator's weight
/// @param _operator The address of the operator updated
/// @param oldWeight The operator's weight before the update
/// @param newWeight The operator's weight after the update
event OperatorWeightUpdated(address indexed _operator, uint256 oldWeight, uint256 newWeight);
event OperatorWeightUpdated(
address indexed _operator,
uint256 oldWeight,
uint256 newWeight
);

/// @notice Emitted when the system updates the total weight
/// @param oldTotalWeight The total weight before the update
Expand All @@ -52,6 +59,17 @@ interface ECDSAStakeRegistryEventsAndErrors {
/// @notice Emits when setting a new threshold weight.
event ThresholdWeightUpdated(uint256 _thresholdWeight);

/// @notice Emitted when an operator's signing key is updated
/// @param operator The address of the operator whose signing key was updated
/// @param updateBlock The block number at which the signing key was updated
/// @param newSigningKey The operator's signing key after the update
/// @param oldSigningKey The operator's signing key before the update
event SigningKeyUpdate(
address indexed operator,
uint256 indexed updateBlock,
address indexed newSigningKey,
address oldSigningKey
);
/// @notice Indicates when the lengths of the signers array and signatures array do not match.
error LengthMismatch();

Expand All @@ -64,7 +82,7 @@ interface ECDSAStakeRegistryEventsAndErrors {
/// @notice Thrown when the threshold update is greater than BPS
error InvalidThreshold();

/// @notice Thrown when missing operators in an update
/// @notice Thrown when missing operators in an update
error MustUpdateAllOperators();

/// @notice Indicates operator weights were out of sync and the signed weight exceed the total
Expand Down
131 changes: 114 additions & 17 deletions src/unaudited/ECDSAStakeRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,33 @@ contract ECDSAStakeRegistry is
__ECDSAStakeRegistry_init(_serviceManager, _thresholdWeight, _quorum);
}

/// @notice Registers a new operator using a provided signature
/// @notice Registers a new operator using a provided signature and signing key
/// @param _operatorSignature Contains the operator's signature, salt, and expiry
/// @param _signingKey The signing key to add to the operator's history
function registerOperatorWithSignature(
address _operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature,
address _signingKey
) external {
_registerOperatorWithSig(_operator, _operatorSignature);
_registerOperatorWithSig(msg.sender, _operatorSignature, _signingKey);
}

/// @notice Deregisters an existing operator
function deregisterOperator() external {
_deregisterOperator(msg.sender);
}

/**
* @notice Updates the signing key for an operator
* @dev Only callable by the operator themselves
* @param _newSigningKey The new signing key to set for the operator
*/
function updateOperatorSigningKey(address _newSigningKey) external {
if (!_operatorRegistered[msg.sender]) {
revert OperatorNotRegistered();
}
_updateOperatorSigningKey(msg.sender, _newSigningKey);
}

/**
* @notice Updates the StakeRegistry's view of one or more operators' stakes adding a new entry in their history of stake checkpoints,
* @dev Queries stakes from the Eigenlayer core DelegationManager contract
Expand Down Expand Up @@ -106,18 +119,18 @@ contract ECDSAStakeRegistry is

/// @notice Verifies if the provided signature data is valid for the given data hash.
/// @param _dataHash The hash of the data that was signed.
/// @param _signatureData Encoded signature data consisting of an array of signers, an array of signatures, and a reference block number.
/// @param _signatureData Encoded signature data consisting of an array of operators, an array of signatures, and a reference block number.
/// @return The function selector that indicates the signature is valid according to ERC1271 standard.
function isValidSignature(
bytes32 _dataHash,
bytes memory _signatureData
) external view returns (bytes4) {
(
address[] memory signers,
address[] memory operators,
bytes[] memory signatures,
uint32 referenceBlock
) = abi.decode(_signatureData, (address[], bytes[], uint32));
_checkSignatures(_dataHash, signers, signatures, referenceBlock);
_checkSignatures(_dataHash, operators, signatures, referenceBlock);
return IERC1271Upgradeable.isValidSignature.selector;
}

Expand All @@ -127,6 +140,37 @@ contract ECDSAStakeRegistry is
return _quorum;
}

/**
* @notice Retrieves the latest signing key for a given operator.
* @param _operator The address of the operator.
* @return The latest signing key of the operator.
*/
function getLastestOperatorSigningKey(
address _operator
) external view returns (address) {
return address(uint160(_operatorSigningKeyHistory[_operator].latest()));
}

/**
* @notice Retrieves the latest signing key for a given operator at a specific block number.
* @param _operator The address of the operator.
* @param _blockNumber The block number to get the operator's signing key.
* @return The signing key of the operator at the given block.
*/
function getLastestOperatorSigningKeyAtBlock(
ChaoticWalrus marked this conversation as resolved.
Show resolved Hide resolved
address _operator,
uint256 _blockNumber
) external view returns (address) {
return
address(
uint160(
_operatorSigningKeyHistory[_operator].getAtBlock(
_blockNumber
)
)
);
}

/// @notice Retrieves the last recorded weight for a given operator.
/// @param _operator The address of the operator.
/// @return uint256 - The latest weight of the operator.
Expand Down Expand Up @@ -312,9 +356,11 @@ contract ECDSAStakeRegistry is

/// @dev registers an operator through a provided signature
/// @param _operatorSignature Contains the operator's signature, salt, and expiry
/// @param _signingKey The signing key to add to the operator's history
function _registerOperatorWithSig(
address _operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature,
address _signingKey
) internal virtual {
if (_operatorRegistered[_operator]) {
revert OperatorAlreadyRegistered();
Expand All @@ -323,13 +369,36 @@ contract ECDSAStakeRegistry is
_operatorRegistered[_operator] = true;
int256 delta = _updateOperatorWeight(_operator);
_updateTotalWeight(delta);
_updateOperatorSigningKey(_operator, _signingKey);
IServiceManager(_serviceManager).registerOperatorToAVS(
_operator,
_operatorSignature
);
emit OperatorRegistered(_operator, _serviceManager);
}

/// @dev Internal function to update an operator's signing key
/// @param _operator The address of the operator to update the signing key for
/// @param _newSigningKey The new signing key to set for the operator
function _updateOperatorSigningKey(
address _operator,
address _newSigningKey
) internal {
address oldSigningKey = address(
uint160(_operatorSigningKeyHistory[_operator].latest())
);
if (_newSigningKey == oldSigningKey) {
return;
}
_operatorSigningKeyHistory[_operator].push(uint160(_newSigningKey));
emit SigningKeyUpdate(
_operator,
block.number,
_newSigningKey,
oldSigningKey
);
}

/// @notice Updates the weight of an operator and returns the previous and current weights.
/// @param _operator The address of the operator to update the weight of.
function _updateOperatorWeight(
Expand Down Expand Up @@ -400,30 +469,33 @@ contract ECDSAStakeRegistry is
/**
* @notice Common logic to verify a batch of ECDSA signatures against a hash, using either last stake weight or at a specific block.
* @param _dataHash The hash of the data the signers endorsed.
* @param _signers A collection of addresses that endorsed the data hash.
* @param _operators A collection of addresses that endorsed the data hash.
* @param _signatures A collection of signatures matching the signers.
* @param _referenceBlock The block number for evaluating stake weight; use max uint32 for latest weight.
*/
function _checkSignatures(
bytes32 _dataHash,
address[] memory _signers,
address[] memory _operators,
bytes[] memory _signatures,
uint32 _referenceBlock
) internal view {
uint256 signersLength = _signers.length;
address lastSigner;
uint256 signersLength = _operators.length;
address currentOperator;
address lastOperator;
address signer;
uint256 signedWeight;

_validateSignaturesLength(signersLength, _signatures.length);
for (uint256 i; i < signersLength; i++) {
address currentSigner = _signers[i];
currentOperator = _operators[i];
signer = _getOperatorSigningKey(currentOperator, _referenceBlock);

_validateSortedSigners(lastSigner, currentSigner);
_validateSignature(currentSigner, _dataHash, _signatures[i]);
_validateSortedSigners(lastOperator, currentOperator);
_validateSignature(signer, _dataHash, _signatures[i]);

lastSigner = currentSigner;
lastOperator = currentOperator;
uint256 operatorWeight = _getOperatorWeight(
currentSigner,
currentOperator,
_referenceBlock
);
signedWeight += operatorWeight;
Expand Down Expand Up @@ -473,6 +545,31 @@ contract ECDSAStakeRegistry is
}
}

/// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block.
/// @param _operator The operator to query their signing key history for
/// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint.
/// @return The weight of the operator.
function _getOperatorSigningKey(
address _operator,
uint32 _referenceBlock
) internal view returns (address) {
if (_referenceBlock == type(uint32).max) {
return
address(
uint160(_operatorSigningKeyHistory[_operator].latest())
);
} else {
return
address(
uint160(
_operatorSigningKeyHistory[_operator].getAtBlock(
_referenceBlock
)
)
);
}
}

/// @notice Retrieves the operator weight for a signer, either at the last checkpoint or a specified block.
/// @param _signer The address of the signer whose weight is returned.
/// @param _referenceBlock The block number to query the operator's weight at, or the maximum uint32 value for the last checkpoint.
Expand Down
11 changes: 9 additions & 2 deletions src/unaudited/ECDSAStakeRegistryStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/
import {CheckpointsUpgradeable} from "@openzeppelin-upgrades/contracts/utils/CheckpointsUpgradeable.sol";
import {ECDSAStakeRegistryEventsAndErrors, Quorum, StrategyParams} from "../interfaces/IECDSAStakeRegistryEventsAndErrors.sol";

abstract contract ECDSAStakeRegistryStorage is ECDSAStakeRegistryEventsAndErrors {
abstract contract ECDSAStakeRegistryStorage is
ECDSAStakeRegistryEventsAndErrors
{
/// @notice Manages staking delegations through the DelegationManager interface
IDelegationManager internal immutable DELEGATION_MANAGER;

Expand All @@ -27,14 +29,19 @@ abstract contract ECDSAStakeRegistryStorage is ECDSAStakeRegistryEventsAndErrors
/// @notice Defines the duration after which the stake's weight expires.
uint256 internal _stakeExpiry;

/// @notice Maps an operator to their signing key history using checkpoints
mapping(address => CheckpointsUpgradeable.History)
internal _operatorSigningKeyHistory;

ChaoticWalrus marked this conversation as resolved.
Show resolved Hide resolved
/// @notice Tracks the total stake history over time using checkpoints
CheckpointsUpgradeable.History internal _totalWeightHistory;

/// @notice Tracks the threshold bps history using checkpoints
CheckpointsUpgradeable.History internal _thresholdWeightHistory;

/// @notice Maps operator addresses to their respective stake histories using checkpoints
mapping(address => CheckpointsUpgradeable.History) internal _operatorWeightHistory;
mapping(address => CheckpointsUpgradeable.History)
internal _operatorWeightHistory;

/// @notice Maps an operator to their registration status
mapping(address => bool) internal _operatorRegistered;
Expand Down
24 changes: 14 additions & 10 deletions src/unaudited/examples/ECDSAStakeRegistryPermissioned.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry {
/// @dev Custom error to signal that an operator is already allowlisted.
error OperatorAlreadyAllowlisted();

constructor(IDelegationManager _delegationManager) ECDSAStakeRegistry(_delegationManager) {
constructor(
IDelegationManager _delegationManager
) ECDSAStakeRegistry(_delegationManager) {
// _disableInitializers();
}

Expand Down Expand Up @@ -63,38 +65,40 @@ contract ECDSAStakeRegistryPermissioned is ECDSAStakeRegistry {
/// Doesn't register the operator into the operator set
/// @param _operator The address of the operator to allowlist.
function _permitOperator(address _operator) internal {
if (allowlistedOperators[_operator]){
if (allowlistedOperators[_operator]) {
revert OperatorAlreadyAllowlisted();
}
allowlistedOperators[_operator] = true;
emit OperatorPermitted(_operator);

}

/// @dev Removes an operator from the allowlist.
/// If the operator is registered, also deregisters the operator.
/// @param _operator The address of the operator to be revoked.
function _revokeOperator(address _operator) internal {
if (!allowlistedOperators[_operator]){
if (!allowlistedOperators[_operator]) {
revert OperatorNotAllowlisted();
}
delete allowlistedOperators[_operator];
emit OperatorRevoked(_operator);
if (_operatorRegistered[_operator]){
if (_operatorRegistered[_operator]) {
_ejectOperator(_operator);
}

}

/// @inheritdoc ECDSAStakeRegistry
function _registerOperatorWithSig(
address _operator,
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature
ISignatureUtils.SignatureWithSaltAndExpiry memory _operatorSignature,
address _operatorSigningKey
) internal override {
if (allowlistedOperators[_operator] != true){
if (allowlistedOperators[_operator] != true) {
revert OperatorNotAllowlisted();
}
super._registerOperatorWithSig(_operator, _operatorSignature);
super._registerOperatorWithSig(
_operator,
_operatorSignature,
_operatorSigningKey
);
}
}

Loading
Loading