diff --git a/src/BLSApkRegistry.sol b/src/BLSApkRegistry.sol index 4fd0e2fb..7e6f5d8f 100644 --- a/src/BLSApkRegistry.sol +++ b/src/BLSApkRegistry.sol @@ -1,13 +1,12 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.12; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; import {BLSApkRegistryStorage} from "./BLSApkRegistryStorage.sol"; - import {IRegistryCoordinator} from "./interfaces/IRegistryCoordinator.sol"; - import {BN254} from "./libraries/BN254.sol"; -contract BLSApkRegistry is BLSApkRegistryStorage { +contract BLSApkRegistry is BLSApkRegistryStorage, EIP712 { using BN254 for BN254.G1Point; /// @notice when applied to a function, only allows the RegistryCoordinator to call it @@ -22,7 +21,10 @@ contract BLSApkRegistry is BLSApkRegistryStorage { /// @notice Sets the (immutable) `registryCoordinator` address constructor( IRegistryCoordinator _registryCoordinator - ) BLSApkRegistryStorage(_registryCoordinator) {} + ) + BLSApkRegistryStorage(_registryCoordinator) + EIP712("BLSApkRegistry", "v0.0.1") + {} /******************************************************************************* EXTERNAL FUNCTIONS - REGISTRY COORDINATOR @@ -95,26 +97,24 @@ contract BLSApkRegistry is BLSApkRegistryStorage { * @notice Called by the RegistryCoordinator register an operator as the owner of a BLS public key. * @param operator is the operator for whom the key is being registered * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership - * @param pubkeyRegistrationMessageHash is a hash that the operator must sign to prove key ownership */ function registerBLSPublicKey( address operator, - PubkeyRegistrationParams calldata params, - BN254.G1Point calldata pubkeyRegistrationMessageHash + PubkeyRegistrationParams calldata params ) external onlyRegistryCoordinator returns (bytes32 operatorId) { bytes32 pubkeyHash = BN254.hashG1Point(params.pubkeyG1); + if(operatorToPubkeyHash[operator] == pubkeyHash) { + return pubkeyHash; + } require( pubkeyHash != ZERO_PK_HASH, "BLSApkRegistry.registerBLSPublicKey: cannot register zero pubkey" ); - require( - operatorToPubkeyHash[operator] == bytes32(0), - "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey" - ); require( pubkeyHashToOperator[pubkeyHash] == address(0), "BLSApkRegistry.registerBLSPublicKey: public key already registered" ); + BN254.G1Point memory _pubkeyRegistrationMessageHash = pubkeyRegistrationMessageHash(operator); // gamma = h(sigma, P, P', H(m)) uint256 gamma = uint256(keccak256(abi.encodePacked( params.pubkeyRegistrationSignature.X, @@ -123,18 +123,27 @@ contract BLSApkRegistry is BLSApkRegistryStorage { params.pubkeyG1.Y, params.pubkeyG2.X, params.pubkeyG2.Y, - pubkeyRegistrationMessageHash.X, - pubkeyRegistrationMessageHash.Y + _pubkeyRegistrationMessageHash.X, + _pubkeyRegistrationMessageHash.Y ))) % BN254.FR_MODULUS; // e(sigma + P * gamma, [-1]_2) = e(H(m) + [1]_1 * gamma, P') require(BN254.pairing( params.pubkeyRegistrationSignature.plus(params.pubkeyG1.scalar_mul(gamma)), BN254.negGeneratorG2(), - pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)), + _pubkeyRegistrationMessageHash.plus(BN254.generatorG1().scalar_mul(gamma)), params.pubkeyG2 ), "BLSApkRegistry.registerBLSPublicKey: either the G1 signature is wrong, or G1 and G2 private key do not match"); + //if keys are already registered for the operator then checkpoint the previous pubkey and pubkeyHash of the operator + if(operatorToPubkeyHash[operator] != bytes32(0)) { + operatorPubkeyHistory[operator].push(PubkeyCheckpoint({ + previousPubkeyG1: operatorToPubkey[operator], + previousPubkeyHash: operatorToPubkeyHash[operator], + blockNumber: uint32(block.number) + })); + } + operatorToPubkey[operator] = params.pubkeyG1; operatorToPubkeyHash[operator] = pubkeyHash; pubkeyHashToOperator[pubkeyHash] = operator; @@ -281,4 +290,105 @@ contract BLSApkRegistry is BLSApkRegistryStorage { function getOperatorId(address operator) public view returns (bytes32) { return operatorToPubkeyHash[operator]; } -} + + /// @notice Returns the length of the pubkey checkpoint history for the given `operator` + function getOperatorPubkeyCheckpointHistoryLength(address operator) external view returns (uint256) { + return operatorPubkeyHistory[operator].length; + } + + /// @notice Returns the `index`th entry in the pubkey checkpoint history for the given `operator` + function getOperatorPubkeyCheckpointByIndex(address operator, uint256 index) external view returns (PubkeyCheckpoint memory) { + require(index < operatorPubkeyHistory[operator].length, "BLSApkRegistry.getPubkeyCheckpointByIndex: index out of bounds"); + return operatorPubkeyHistory[operator][index]; + } + + /** + * @notice Returns the pubkey for the provided `operator` at the given `blockNumber` + * @dev This function is designed to find proper inputs to the `getPubkeyHashAtBlockNumberByIndex` function + * @dev Will revert if an operator has not ever registered a pubkey + * @dev Will revert if `blockNumber` is greater than the current block number + * @dev Will return the first pubkey for the provided `operator` if `blockNumber` is before the first pubkey checkpoint + * and additional logic is required to check if the operator was active at the given `blockNumber` + */ + function getOperatorPubkeyHashAtBlockNumber( + uint32 blockNumber, + address operator + ) external view returns (bytes32 pubkeyHash) { + require(blockNumber < uint32(block.number), "BLSApkRegistry.getOperatorPubkeyHashAtBlockNumber: blockNumber is after current block"); + pubkeyHash = getOperatorId(operator); + + uint256 historyLength = operatorPubkeyHistory[operator].length; + for (uint256 i = 0; i < historyLength; i++) { + if (operatorPubkeyHistory[operator][i].blockNumber > blockNumber) { + pubkeyHash = operatorPubkeyHistory[operator][i].previousPubkeyHash; + break; + } + } + + if (pubkeyHash == bytes32(0)) { + revert("BLSApkRegistry.getOperatorPubkeyHashAtBlockNumber: operator has not registered a pubkey"); + } + } + + /** + * @notice Returns the pubkey for the given `operator` at the given `blockNumber` via the `index`, + * reverting if `index` is incorrect + * @dev This function is meant to be used in concert with `getOperatorPubkeyHashAtBlockNumber`, which + * helps off-chain processes to fetch the correct `index` input + * @dev Will revert if an operator has not ever registered a pubkey + * @dev Will revert if `blockNumber` is greater than the current block number + * @dev Will return the first pubkey for the provided `operator` if `blockNumber` is before the first pubkey checkpoint + * and additional logic is required to check if the operator was active at the given `blockNumber` + * @dev If an operator has no pubkey checkpoints, then the operator's current pubkey is returned for any `blockNumber` and `index` + * @dev If the blockNumber is after the last pubkey checkpoint, then the operator's current pubkey is returned for any `index` + */ + function getOperatorPubkeyHashAtBlockNumberByIndex( + address operator, + uint32 blockNumber, + uint256 index + ) external view returns (bytes32 pubkeyHash) { + require(blockNumber < uint32(block.number), "BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: blockNumber is after current block"); + uint256 historyLength = operatorPubkeyHistory[operator].length; + if(historyLength > 0){ + require(index < historyLength, "BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: index is out of bounds"); + } + + if (historyLength == 0) { + pubkeyHash = getOperatorId(operator); + } else if (operatorPubkeyHistory[operator][historyLength - 1].blockNumber <= blockNumber) { + pubkeyHash = getOperatorId(operator); + } else { + PubkeyCheckpoint memory pubkeyCheckpoint = operatorPubkeyHistory[operator][index]; + + require( + blockNumber < pubkeyCheckpoint.blockNumber, + "BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: pubkeyHash is from later index" + ); + + if(index > 0) { + PubkeyCheckpoint memory previousPubkeyCheckpoint = operatorPubkeyHistory[operator][index - 1]; + require( + blockNumber >= previousPubkeyCheckpoint.blockNumber, + "BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: pubkeyHash is from earlier index" + ); + } + + pubkeyHash = pubkeyCheckpoint.previousPubkeyHash; + } + if (pubkeyHash == bytes32(0)) { + revert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: operator has not registered a pubkey"); + } + } + + /** + * @notice Returns the message hash that an operator must sign to register their BLS public key. + * @param operator is the address of the operator registering their BLS public key + */ + function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) { + return BN254.hashToG1( + _hashTypedDataV4( + keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator)) + ) + ); + } +} \ No newline at end of file diff --git a/src/BLSApkRegistryStorage.sol b/src/BLSApkRegistryStorage.sol index 9597d5ac..5a4ad10a 100644 --- a/src/BLSApkRegistryStorage.sol +++ b/src/BLSApkRegistryStorage.sol @@ -11,6 +11,8 @@ import {BN254} from "./libraries/BN254.sol"; abstract contract BLSApkRegistryStorage is Initializable, IBLSApkRegistry { /// @notice the hash of the zero pubkey aka BN254.G1Point(0,0) bytes32 internal constant ZERO_PK_HASH = hex"ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5"; + /// @notice The EIP-712 typehash used for registering BLS public keys + bytes32 public constant PUBKEY_REGISTRATION_TYPEHASH = keccak256("BN254PubkeyRegistration(address operator)"); /// @notice the registry coordinator contract address public immutable registryCoordinator; @@ -29,6 +31,9 @@ abstract contract BLSApkRegistryStorage is Initializable, IBLSApkRegistry { /// @notice maps quorumNumber => current aggregate pubkey of quorum mapping(uint8 => BN254.G1Point) public currentApk; + /// @notice maps operator address to pubkey history + mapping(address => PubkeyCheckpoint[]) public operatorPubkeyHistory; + constructor(IRegistryCoordinator _registryCoordinator) { registryCoordinator = address(_registryCoordinator); // disable initializers so that the implementation contract cannot be initialized @@ -36,5 +41,5 @@ abstract contract BLSApkRegistryStorage is Initializable, IBLSApkRegistry { } // storage gap for upgradeability - uint256[45] private __GAP; -} + uint256[44] private __GAP; +} \ No newline at end of file diff --git a/src/RegistryCoordinator.sol b/src/RegistryCoordinator.sol index 4006d9f4..d06637ad 100644 --- a/src/RegistryCoordinator.sol +++ b/src/RegistryCoordinator.sol @@ -446,6 +446,16 @@ contract RegistryCoordinator is ejectionCooldown = _ejectionCooldown; } + /** + * @notice Sets the deregistration cooldown, which is the time an operator must wait in + * seconds after fully deregistering before registering for any quorum + * @param _deregistrationCooldown the new deregistration cooldown in seconds + * @dev only callable by the owner + */ + function setDeregistrationCooldown(uint256 _deregistrationCooldown) external onlyOwner { + deregistrationCooldown = _deregistrationCooldown; + } + /******************************************************************************* INTERNAL FUNCTIONS *******************************************************************************/ @@ -480,8 +490,12 @@ contract RegistryCoordinator is require(quorumsToAdd.noBitsInCommon(currentBitmap), "RegistryCoordinator._registerOperator: operator already registered for some quorums being registered for"); uint192 newBitmap = uint192(currentBitmap.plus(quorumsToAdd)); - // Check that the operator can reregister if ejected - require(lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp, "RegistryCoordinator._registerOperator: operator cannot reregister yet"); + // Check that the operator can reregister + require( + lastEjectionTimestamp[operator] + ejectionCooldown < block.timestamp && + lastDeregistrationTimestamp[operator] + deregistrationCooldown < block.timestamp, + "RegistryCoordinator._registerOperator: operator cannot reregister yet" + ); /** * Update operator's bitmap, socket, and status. Only update operatorInfo if needed: @@ -531,7 +545,12 @@ contract RegistryCoordinator is ) internal returns (bytes32 operatorId) { operatorId = blsApkRegistry.getOperatorId(operator); if (operatorId == 0) { - operatorId = blsApkRegistry.registerBLSPublicKey(operator, params, pubkeyRegistrationMessageHash(operator)); + operatorId = blsApkRegistry.registerBLSPublicKey(operator, params); + } else if (_operatorInfo[operator].status == OperatorStatus.DEREGISTERED && + params.pubkeyRegistrationSignature.X != 0 && + params.pubkeyRegistrationSignature.Y != 0 + ) { + operatorId = blsApkRegistry.registerBLSPublicKey(operator, params); } return operatorId; } @@ -616,6 +635,7 @@ contract RegistryCoordinator is // them from the AVS via the EigenLayer core contracts if (newBitmap.isEmpty()) { operatorInfo.status = OperatorStatus.DEREGISTERED; + lastDeregistrationTimestamp[operator] = block.timestamp; serviceManager.deregisterOperatorFromAVS(operator); emit OperatorDeregistered(operator, operatorId); } @@ -926,11 +946,7 @@ contract RegistryCoordinator is * @param operator is the address of the operator registering their BLS public key */ function pubkeyRegistrationMessageHash(address operator) public view returns (BN254.G1Point memory) { - return BN254.hashToG1( - _hashTypedDataV4( - keccak256(abi.encode(PUBKEY_REGISTRATION_TYPEHASH, operator)) - ) - ); + return blsApkRegistry.pubkeyRegistrationMessageHash(operator); } /// @dev need to override function here since its defined in both these contracts diff --git a/src/RegistryCoordinatorStorage.sol b/src/RegistryCoordinatorStorage.sol index 5451efb3..54a195f2 100644 --- a/src/RegistryCoordinatorStorage.sol +++ b/src/RegistryCoordinatorStorage.sol @@ -16,8 +16,6 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { /// @notice The EIP-712 typehash for the `DelegationApproval` struct used by the contract bytes32 public constant OPERATOR_CHURN_APPROVAL_TYPEHASH = keccak256("OperatorChurnApproval(address registeringOperator,bytes32 registeringOperatorId,OperatorKickParam[] operatorKickParams,bytes32 salt,uint256 expiry)OperatorKickParam(uint8 quorumNumber,address operator)"); - /// @notice The EIP-712 typehash used for registering BLS public keys - bytes32 public constant PUBKEY_REGISTRATION_TYPEHASH = keccak256("BN254PubkeyRegistration(address operator)"); /// @notice The maximum value of a quorum bitmap uint256 internal constant MAX_QUORUM_BITMAP = type(uint192).max; /// @notice The basis point denominator @@ -69,6 +67,11 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { /// @notice the delay in seconds before an operator can reregister after being ejected uint256 public ejectionCooldown; + /// @notice the last timestamp an operator fully deregistered + mapping(address => uint256) public lastDeregistrationTimestamp; + /// @notice the delay in seconds before an operator can reregister after fully deregistering + uint256 public deregistrationCooldown; + constructor( IServiceManager _serviceManager, IStakeRegistry _stakeRegistry, @@ -83,5 +86,5 @@ abstract contract RegistryCoordinatorStorage is IRegistryCoordinator { // storage gap for upgradeability // slither-disable-next-line shadowing-state - uint256[39] private __GAP; + uint256[37] private __GAP; } diff --git a/src/interfaces/IBLSApkRegistry.sol b/src/interfaces/IBLSApkRegistry.sol index 2812a0ce..80cb62a3 100644 --- a/src/interfaces/IBLSApkRegistry.sol +++ b/src/interfaces/IBLSApkRegistry.sol @@ -33,6 +33,18 @@ interface IBLSApkRegistry is IRegistry { BN254.G2Point pubkeyG2; } + /** + * @notice Struct used to checkpoint the previous pubkeys and their hashes for a given operator + * @param previousPubkeyG1 is the G1 public key of the operator + * @param previoudPubkeyHash is the hash of the public key of the operator + * @param blockNumber is the block number at which the pubkey was updated + */ + struct PubkeyCheckpoint { + BN254.G1Point previousPubkeyG1; + bytes32 previousPubkeyHash; + uint32 blockNumber; + } + // EVENTS /// @notice Emitted when `operator` registers with the public keys `pubkeyG1` and `pubkeyG2`. event NewPubkeyRegistration(address indexed operator, BN254.G1Point pubkeyG1, BN254.G2Point pubkeyG2); @@ -101,12 +113,10 @@ interface IBLSApkRegistry is IRegistry { * @notice Called by the RegistryCoordinator register an operator as the owner of a BLS public key. * @param operator is the operator for whom the key is being registered * @param params contains the G1 & G2 public keys of the operator, and a signature proving their ownership - * @param pubkeyRegistrationMessageHash is a hash that the operator must sign to prove key ownership */ function registerBLSPublicKey( address operator, - PubkeyRegistrationParams calldata params, - BN254.G1Point calldata pubkeyRegistrationMessageHash + PubkeyRegistrationParams calldata params ) external returns (bytes32 operatorId); /** @@ -139,4 +149,10 @@ interface IBLSApkRegistry is IRegistry { /// @notice returns the ID used to identify the `operator` within this AVS. /// @dev Returns zero in the event that the `operator` has never registered for the AVS function getOperatorId(address operator) external view returns (bytes32); -} + + /** + * @notice Returns the message hash that an operator must sign to register their BLS public key. + * @param operator is the address of the operator registering their BLS public key + */ + function pubkeyRegistrationMessageHash(address operator) external view returns (BN254.G1Point memory); +} \ No newline at end of file diff --git a/test/ffi/BLSPubKeyCompendiumFFI.t.sol b/test/ffi/BLSPubKeyCompendiumFFI.t.sol index 18d49a17..3ff06b95 100644 --- a/test/ffi/BLSPubKeyCompendiumFFI.t.sol +++ b/test/ffi/BLSPubKeyCompendiumFFI.t.sol @@ -30,7 +30,7 @@ contract BLSApkRegistryFFITests is G2Operations { pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(alice); vm.prank(address(registryCoordinator)); - blsApkRegistry.registerBLSPublicKey(alice, pubkeyRegistrationParams, registryCoordinator.pubkeyRegistrationMessageHash(alice)); + blsApkRegistry.registerBLSPublicKey(alice, pubkeyRegistrationParams); assertEq(blsApkRegistry.operatorToPubkeyHash(alice), BN254.hashG1Point(pubkeyRegistrationParams.pubkeyG1), "pubkey hash not stored correctly"); diff --git a/test/unit/BLSApkRegistryUnit.t.sol b/test/unit/BLSApkRegistryUnit.t.sol index 5f800444..65a4c5f0 100644 --- a/test/unit/BLSApkRegistryUnit.t.sol +++ b/test/unit/BLSApkRegistryUnit.t.sol @@ -193,8 +193,7 @@ contract BLSApkRegistryUnitTests is BLSMockAVSDeployer, IBLSApkRegistryEvents { return blsApkRegistry.registerBLSPublicKey( operator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); } @@ -339,8 +338,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is ); blsApkRegistry.registerBLSPublicKey( defaultOperator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); } @@ -358,34 +356,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is ); blsApkRegistry.registerBLSPublicKey( operator, - pubkeyRegistrationParams, - messageHash - ); - } - - function testFuzz_registerOperator_Revert_WhenOperatorAlreadyRegistered( - address operator - ) public filterFuzzedAddressInputs(operator) { - pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage( - operator - ); - BN254.G1Point memory messageHash = registryCoordinator - .pubkeyRegistrationMessageHash(operator); - - cheats.startPrank(address(registryCoordinator)); - blsApkRegistry.registerBLSPublicKey( - operator, - pubkeyRegistrationParams, - messageHash - ); - - cheats.expectRevert( - "BLSApkRegistry.registerBLSPublicKey: operator already registered pubkey" - ); - blsApkRegistry.registerBLSPublicKey( - operator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); } @@ -408,8 +379,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is cheats.startPrank(address(registryCoordinator)); blsApkRegistry.registerBLSPublicKey( operator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); cheats.expectRevert( @@ -417,8 +387,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is ); blsApkRegistry.registerBLSPublicKey( operator2, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); } @@ -447,8 +416,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is ); blsApkRegistry.registerBLSPublicKey( operator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); } @@ -473,8 +441,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is ); blsApkRegistry.registerBLSPublicKey( operator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); } @@ -500,8 +467,7 @@ contract BLSApkRegistryUnitTests_registerBLSPublicKey is ); blsApkRegistry.registerBLSPublicKey( operator, - pubkeyRegistrationParams, - messageHash + pubkeyRegistrationParams ); ( @@ -1152,3 +1118,329 @@ contract BLSApkRegistryUnitTests_quorumApkUpdates is BLSApkRegistryUnitTests { } } } + +contract BLSApkRegistryUnitTests_pubkeyCheckpoints is BLSApkRegistryUnitTests { + using BN254 for BN254.G1Point; + + function testFuzz_registerBLSPublicKey_RegisteringSameKey(address operator) + public + filterFuzzedAddressInputs(operator) + { + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = + registryCoordinator.pubkeyRegistrationMessageHash(operator); + + cheats.startPrank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + bytes32 operatorId = blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + assertEq( + blsApkRegistry.getOperatorId(operator), + operatorId, + "operatorId is not the same" + ); + } + + function testFuzz_registerBLSPublicKey_RegisteringNewKey(address operator) + public + filterFuzzedAddressInputs(operator) + { + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = + registryCoordinator.pubkeyRegistrationMessageHash(operator); + cheats.prank(address(registryCoordinator)); + cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); + emit NewPubkeyRegistration( + operator, pubkeyRegistrationParams.pubkeyG1, pubkeyRegistrationParams.pubkeyG2 + ); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + (BN254.G1Point memory registeredPubkey, bytes32 registeredpkHash) = + blsApkRegistry.getRegisteredPubkey(operator); + assertEq(registeredPubkey.X, defaultPubkey.X, "registeredPubkey not set correctly"); + assertEq(registeredPubkey.Y, defaultPubkey.Y, "registeredPubkey not set correctly"); + assertEq(registeredpkHash, defaultPubkeyHash, "registeredpkHash not set correctly"); + assertEq( + blsApkRegistry.pubkeyHashToOperator(BN254.hashG1Point(defaultPubkey)), + operator, + "operator address not stored correctly" + ); + + privKey = 420; + pubkeyRegistrationParams.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + defaultPubkey = pubkeyRegistrationParams.pubkeyG1; + defaultPubkeyHash = BN254.hashG1Point(defaultPubkey); + pubkeyRegistrationParams.pubkeyG2.X[1] = + uint256(0x22010BC55552F4993B17F82BCDADC5A90839B3D67E382564485A0AA07F7E1923); + pubkeyRegistrationParams.pubkeyG2.X[0] = + uint256(0x2944BFD71F3073401E9D5F9AEA081BE98B98DA48EC8EE80ECFC7E97C7254CEA9); + pubkeyRegistrationParams.pubkeyG2.Y[1] = + uint256(0x1BDC8A9DEF8E46F40008649021A65F97501E2E5988B485368053BBD4487239C6); + pubkeyRegistrationParams.pubkeyG2.Y[0] = + uint256(0x0B7D0B9ECAD9C2884453B975CC656802DE966F21793B5A0F3D2A98A37FD24540); + + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + messageHash = registryCoordinator.pubkeyRegistrationMessageHash(operator); + cheats.prank(address(registryCoordinator)); + cheats.expectEmit(true, true, true, true, address(blsApkRegistry)); + emit NewPubkeyRegistration( + operator, pubkeyRegistrationParams.pubkeyG1, pubkeyRegistrationParams.pubkeyG2 + ); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + (registeredPubkey, registeredpkHash) = + blsApkRegistry.getRegisteredPubkey(operator); + assertEq(registeredPubkey.X, defaultPubkey.X, "registeredPubkey not set correctly"); + assertEq(registeredPubkey.Y, defaultPubkey.Y, "registeredPubkey not set correctly"); + assertEq(registeredpkHash, defaultPubkeyHash, "registeredpkHash not set correctly"); + assertEq( + blsApkRegistry.pubkeyHashToOperator(BN254.hashG1Point(defaultPubkey)), + operator, + "operator address not stored correctly" + ); + } + + function testGetOperatorPubkeyCheckpointHistoryLength(address operator) + public + filterFuzzedAddressInputs(operator) + { + testFuzz_registerBLSPublicKey_RegisteringNewKey(operator); + assertEq(blsApkRegistry.getOperatorPubkeyCheckpointHistoryLength(operator), 1); + } + + function testGetOperatorPubkeyCheckpointByIndex(address operator) + public + filterFuzzedAddressInputs(operator) + { + testFuzz_registerBLSPublicKey_RegisteringNewKey(operator); + IBLSApkRegistry.PubkeyCheckpoint memory pubkeyCheckpoint = blsApkRegistry.getOperatorPubkeyCheckpointByIndex(operator, 0); + + privKey = 69; + pubkeyRegistrationParams.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + defaultPubkey = pubkeyRegistrationParams.pubkeyG1; + defaultPubkeyHash = BN254.hashG1Point(defaultPubkey); + + assertEq(pubkeyCheckpoint.previousPubkeyHash, defaultPubkeyHash); + assertEq(pubkeyCheckpoint.blockNumber, block.number); + } + + function testGetOperatorPubkeyHashAtBlockNumber(address operator) + public + filterFuzzedAddressInputs(operator) + { + uint32 startBlockNumber = 100; + cheats.roll(startBlockNumber); + + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = + registryCoordinator.pubkeyRegistrationMessageHash(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + uint32 firstRotationBlockNumber = startBlockNumber + 100; + cheats.roll(firstRotationBlockNumber); + + privKey = 420; + IBLSApkRegistry.PubkeyRegistrationParams memory pubkeyRegistrationParams_2; + pubkeyRegistrationParams_2.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + BN254.G1Point memory defaultPubkey_2 = pubkeyRegistrationParams_2.pubkeyG1; + bytes32 defaultPubkeyHash_2 = BN254.hashG1Point(defaultPubkey_2); + pubkeyRegistrationParams_2.pubkeyG2.X[1] = + uint256(0x22010BC55552F4993B17F82BCDADC5A90839B3D67E382564485A0AA07F7E1923); + pubkeyRegistrationParams_2.pubkeyG2.X[0] = + uint256(0x2944BFD71F3073401E9D5F9AEA081BE98B98DA48EC8EE80ECFC7E97C7254CEA9); + pubkeyRegistrationParams_2.pubkeyG2.Y[1] = + uint256(0x1BDC8A9DEF8E46F40008649021A65F97501E2E5988B485368053BBD4487239C6); + pubkeyRegistrationParams_2.pubkeyG2.Y[0] = + uint256(0x0B7D0B9ECAD9C2884453B975CC656802DE966F21793B5A0F3D2A98A37FD24540); + + pubkeyRegistrationParams_2.pubkeyRegistrationSignature = _signMessage(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams_2); + + uint32 secondRotationBlockNumber = firstRotationBlockNumber + 100; + cheats.roll(secondRotationBlockNumber); + + privKey = 69420; + IBLSApkRegistry.PubkeyRegistrationParams memory pubkeyRegistrationParams_3; + pubkeyRegistrationParams_3.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + BN254.G1Point memory defaultPubkey_3 = pubkeyRegistrationParams_3.pubkeyG1; + bytes32 defaultPubkeyHash_3 = BN254.hashG1Point(defaultPubkey_3); + pubkeyRegistrationParams_3.pubkeyG2.X[1] = + uint256(0x1B926587D16A1E472345C416C46301AAD2E835FC36D775CD4EC1737C0333D305); + pubkeyRegistrationParams_3.pubkeyG2.X[0] = + uint256(0x087206D4AF34E738AD9384E1E05D4B0FDA6E422624FD52DA2CB0768F8EDC22AD); + pubkeyRegistrationParams_3.pubkeyG2.Y[1] = + uint256(0x2255A62ADE238497EAB7AD273AF43D24BB5F59C1BC4BCB56BA6ACFE2EA8FBBB2); + pubkeyRegistrationParams_3.pubkeyG2.Y[0] = + uint256(0x11791601D228725F86BA5C6DF192224AD321AA4A1216D686B64D653CA6EBD61F); + + pubkeyRegistrationParams_3.pubkeyRegistrationSignature = _signMessage(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams_3); + + cheats.roll(block.number + 2); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(startBlockNumber - 1, operator), defaultPubkeyHash); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(firstRotationBlockNumber - 1, operator), defaultPubkeyHash); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(firstRotationBlockNumber, operator), defaultPubkeyHash_2); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(secondRotationBlockNumber - 1, operator), defaultPubkeyHash_2); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(secondRotationBlockNumber, operator), defaultPubkeyHash_3); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(secondRotationBlockNumber + 1, operator), defaultPubkeyHash_3); + } + + function testGetOperatorPubkeyHashAtBlockNumber_NoCheckpoints(address operator) + public + filterFuzzedAddressInputs(operator) + { + uint32 startBlockNumber = 100; + cheats.roll(startBlockNumber); + + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = + registryCoordinator.pubkeyRegistrationMessageHash(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + cheats.roll(block.number + 2); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(startBlockNumber - 1, operator), defaultPubkeyHash); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(startBlockNumber + 1, operator), defaultPubkeyHash); + } + + function testGetOperatorPubkeyHashAtBlockNumber_revert_FutureBlocknumber(address operator) + public + filterFuzzedAddressInputs(operator) + { + cheats.expectRevert("BLSApkRegistry.getOperatorPubkeyHashAtBlockNumber: blockNumber is after current block"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(uint32(block.number), operator); + } + + function testGetOperatorPubkeyHashAtBlockNumber_revert_NeverRegistered(address operator) + public + filterFuzzedAddressInputs(operator) + { + cheats.expectRevert("BLSApkRegistry.getOperatorPubkeyHashAtBlockNumber: operator has not registered a pubkey"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumber(uint32(block.number - 1), operator); + } + + function testGetOperatorPubkeyHashAtBlockNumberByIndex(address operator) + public + filterFuzzedAddressInputs(operator) + { + uint32 startBlockNumber = 100; + cheats.roll(startBlockNumber); + + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = + registryCoordinator.pubkeyRegistrationMessageHash(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + uint32 firstRotationBlockNumber = startBlockNumber + 100; + cheats.roll(firstRotationBlockNumber); + + privKey = 420; + IBLSApkRegistry.PubkeyRegistrationParams memory pubkeyRegistrationParams_2; + pubkeyRegistrationParams_2.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + BN254.G1Point memory defaultPubkey_2 = pubkeyRegistrationParams_2.pubkeyG1; + bytes32 defaultPubkeyHash_2 = BN254.hashG1Point(defaultPubkey_2); + pubkeyRegistrationParams_2.pubkeyG2.X[1] = + uint256(0x22010BC55552F4993B17F82BCDADC5A90839B3D67E382564485A0AA07F7E1923); + pubkeyRegistrationParams_2.pubkeyG2.X[0] = + uint256(0x2944BFD71F3073401E9D5F9AEA081BE98B98DA48EC8EE80ECFC7E97C7254CEA9); + pubkeyRegistrationParams_2.pubkeyG2.Y[1] = + uint256(0x1BDC8A9DEF8E46F40008649021A65F97501E2E5988B485368053BBD4487239C6); + pubkeyRegistrationParams_2.pubkeyG2.Y[0] = + uint256(0x0B7D0B9ECAD9C2884453B975CC656802DE966F21793B5A0F3D2A98A37FD24540); + + pubkeyRegistrationParams_2.pubkeyRegistrationSignature = _signMessage(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams_2); + + uint32 secondRotationBlockNumber = firstRotationBlockNumber + 100; + cheats.roll(secondRotationBlockNumber); + + privKey = 69420; + IBLSApkRegistry.PubkeyRegistrationParams memory pubkeyRegistrationParams_3; + pubkeyRegistrationParams_3.pubkeyG1 = BN254.generatorG1().scalar_mul(privKey); + BN254.G1Point memory defaultPubkey_3 = pubkeyRegistrationParams_3.pubkeyG1; + bytes32 defaultPubkeyHash_3 = BN254.hashG1Point(defaultPubkey_3); + pubkeyRegistrationParams_3.pubkeyG2.X[1] = + uint256(0x1B926587D16A1E472345C416C46301AAD2E835FC36D775CD4EC1737C0333D305); + pubkeyRegistrationParams_3.pubkeyG2.X[0] = + uint256(0x087206D4AF34E738AD9384E1E05D4B0FDA6E422624FD52DA2CB0768F8EDC22AD); + pubkeyRegistrationParams_3.pubkeyG2.Y[1] = + uint256(0x2255A62ADE238497EAB7AD273AF43D24BB5F59C1BC4BCB56BA6ACFE2EA8FBBB2); + pubkeyRegistrationParams_3.pubkeyG2.Y[0] = + uint256(0x11791601D228725F86BA5C6DF192224AD321AA4A1216D686B64D653CA6EBD61F); + + pubkeyRegistrationParams_3.pubkeyRegistrationSignature = _signMessage(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams_3); + + cheats.roll(block.number + 2); + + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, startBlockNumber - 1, 0), defaultPubkeyHash); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, firstRotationBlockNumber - 1, 0), defaultPubkeyHash); + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: pubkeyHash is from earlier index"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, startBlockNumber - 1, 1); + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: pubkeyHash is from earlier index"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, firstRotationBlockNumber - 1, 1); + + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, firstRotationBlockNumber, 1), defaultPubkeyHash_2); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, secondRotationBlockNumber - 1, 1), defaultPubkeyHash_2); + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: pubkeyHash is from later index"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, firstRotationBlockNumber, 0); + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: pubkeyHash is from later index"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, secondRotationBlockNumber - 1, 0); + + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, secondRotationBlockNumber, 0), defaultPubkeyHash_3); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, secondRotationBlockNumber, 1), defaultPubkeyHash_3); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, secondRotationBlockNumber + 1, 0), defaultPubkeyHash_3); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, secondRotationBlockNumber + 1, 1), defaultPubkeyHash_3); + } + + function testGetOperatorPubkeyHashAtBlockNumberByIndex_NoCheckpoints(address operator) + public + filterFuzzedAddressInputs(operator) + { + uint32 startBlockNumber = 100; + cheats.roll(startBlockNumber); + + pubkeyRegistrationParams.pubkeyRegistrationSignature = _signMessage(operator); + BN254.G1Point memory messageHash = + registryCoordinator.pubkeyRegistrationMessageHash(operator); + cheats.prank(address(registryCoordinator)); + blsApkRegistry.registerBLSPublicKey(operator, pubkeyRegistrationParams); + + cheats.roll(block.number + 2); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, startBlockNumber - 1, 0), defaultPubkeyHash); + assertEq(blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, startBlockNumber + 1, 0), defaultPubkeyHash); + } + + function testGetOperatorPubkeyHashAtBlockNumberByIndex_revert_FutureBlocknumber(address operator) + public + filterFuzzedAddressInputs(operator) + { + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: blockNumber is after current block"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, uint32(block.number), 0); + } + + function testGetOperatorPubkeyHashAtBlockNumberByIndex_revert_NeverRegistered(address operator) + public + filterFuzzedAddressInputs(operator) + { + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: operator has not registered a pubkey"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, uint32(block.number - 1), 0); + } + + function testGetOperatorPubkeyHashAtBlockNumberByIndex_revert_IndexOOB(address operator) + public + filterFuzzedAddressInputs(operator) + { + cheats.expectRevert("BLSApkRegistry.getPubkeyHashAtBlockNumberByIndex: operator has not registered a pubkey"); + blsApkRegistry.getOperatorPubkeyHashAtBlockNumberByIndex(operator, uint32(block.number - 1), 1); + } + +} + diff --git a/test/unit/OperatorStateRetrieverUnit.t.sol b/test/unit/OperatorStateRetrieverUnit.t.sol index 89a9d94a..208b7923 100644 --- a/test/unit/OperatorStateRetrieverUnit.t.sol +++ b/test/unit/OperatorStateRetrieverUnit.t.sol @@ -202,6 +202,7 @@ contract OperatorStateRetrieverUnitTests is MockAVSDeployer { registryCoordinator.deregisterOperator(BitmapUtils.bitmapToBytesArray(quorumBitmapOne)); cheats.roll(registrationBlockNumber + 20); + cheats.warp(block.timestamp + 1); _registerOperatorWithCoordinator(defaultOperator, quorumBitmapTwo, defaultPubKey); cheats.roll(registrationBlockNumber + 25); diff --git a/test/unit/RegistryCoordinatorUnit.t.sol b/test/unit/RegistryCoordinatorUnit.t.sol index c262b0da..50a790b4 100644 --- a/test/unit/RegistryCoordinatorUnit.t.sol +++ b/test/unit/RegistryCoordinatorUnit.t.sol @@ -898,7 +898,7 @@ contract RegistryCoordinatorUnitTests_DeregisterOperator_EjectOperator is Regist quorumNumbers[0] = bytes1(defaultQuorumNumber); cheats.startPrank(defaultOperator); - + cheats.warp(block.timestamp + 1); cheats.roll(reregistrationBlockNumber); // store data before registering, to check against later