diff --git a/src/contracts/interfaces/IKeyRegistrar.sol b/src/contracts/interfaces/IKeyRegistrar.sol index a7eddd460e..32b9de1015 100644 --- a/src/contracts/interfaces/IKeyRegistrar.sol +++ b/src/contracts/interfaces/IKeyRegistrar.sol @@ -1,6 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity >=0.5.0; +import {OperatorSet} from "../libraries/OperatorSetLib.sol"; +import {BN254} from "../libraries/BN254.sol"; +import "./ISemVerMixin.sol"; + +interface IKeyRegistrarErrors { + /// Key Management + error KeyAlreadyRegistered(); + error InvalidKeyFormat(); + error ZeroAddress(); + error ZeroPubkey(); + error InvalidCurveType(); + error InvalidKeypair(); + error ConfigurationAlreadySet(); + error OperatorSetNotConfigured(); + error KeyNotFound(OperatorSet operatorSet, address operator); +} + interface IKeyRegistrarTypes { /// @dev Enum defining supported curve types enum CurveType { @@ -15,3 +32,119 @@ interface IKeyRegistrarTypes { bytes keyData; // Flexible storage for different curve types } } + +interface IKeyRegistrarEvents is IKeyRegistrarTypes { + event KeyRegistered(OperatorSet operatorSet, address indexed operator, CurveType curveType, bytes pubkey); + event KeyDeregistered(OperatorSet operatorSet, address indexed operator, CurveType curveType); + event AggregateBN254KeyUpdated(OperatorSet operatorSet, BN254.G1Point newAggregateKey); + event OperatorSetConfigured(OperatorSet operatorSet, CurveType curveType); +} + +interface IKeyRegistrar is IKeyRegistrarErrors, IKeyRegistrarEvents, ISemVerMixin { + /** + * @notice Configures an operator set with curve type + * @param operatorSet The operator set to configure + * @param curveType Type of curve (ECDSA, BN254) + * @dev Only authorized callers for the AVS can configure operator sets + */ + function configureOperatorSet(OperatorSet memory operatorSet, CurveType curveType) external; + + /** + * @notice Registers a cryptographic key for an operator with a specific operator set + * @param operator Address of the operator to register key for + * @param operatorSet The operator set to register the key for + * @param pubkey Public key bytes + * @param signature Signature proving ownership (only needed for BN254 keys) + * @dev Can be called by operator directly or by addresses they've authorized via PermissionController + * @dev Reverts if key is already registered + */ + function registerKey( + address operator, + OperatorSet memory operatorSet, + bytes calldata pubkey, + bytes calldata signature + ) external; + + /** + * @notice Deregisters a cryptographic key for an operator with a specific operator set + * @param operator Address of the operator to deregister key for + * @param operatorSet The operator set to deregister the key from + * @dev Can be called by avs directly or by addresses they've authorized via PermissionController + * @dev Reverts if key was not registered + * @dev Keys remain in global key registry to prevent reuse + */ + function deregisterKey(address operator, OperatorSet memory operatorSet) external; + + /** + * @notice Checks if an operator has a registered key + * @param operatorSet The operator set to check and update + * @param operator Address of the operator + * @return whether the operator has a registered key + * @dev This function is called by the AVSRegistrar when an operator registers for an AVS + * @dev Only authorized callers for the AVS can call this function + * @dev Reverts if operator doesn't have a registered key for this operator set + */ + function checkKey(OperatorSet memory operatorSet, address operator) external view returns (bool); + + /** + * @notice Checks if a key is registered for an operator with a specific operator set + * @param operatorSet The operator set to check + * @param operator Address of the operator + * @return True if the key is registered + */ + function isRegistered(OperatorSet memory operatorSet, address operator) external view returns (bool); + + /** + * @notice Gets the configuration for an operator set + * @param operatorSet The operator set to get configuration for + * @return The operator set configuration + */ + function getOperatorSetCurveType( + OperatorSet memory operatorSet + ) external view returns (CurveType); + + /** + * @notice Gets the BN254 public key for an operator with a specific operator set + * @param operatorSet The operator set to get the key for + * @param operator Address of the operator + * @return g1Point The BN254 G1 public key + * @return g2Point The BN254 G2 public key + */ + function getBN254Key( + OperatorSet memory operatorSet, + address operator + ) external view returns (BN254.G1Point memory g1Point, BN254.G2Point memory g2Point); + + /** + * @notice Gets the ECDSA public key for an operator with a specific operator set as bytes + * @param operatorSet The operator set to get the key for + * @param operator Address of the operator + * @return pubkey The ECDSA public key + */ + function getECDSAKey(OperatorSet memory operatorSet, address operator) external view returns (bytes memory); + + /** + * @notice Gets the ECDSA public key for an operator with a specific operator set + * @param operatorSet The operator set to get the key for + * @param operator Address of the operator + * @return pubkey The ECDSA public key + */ + function getECDSAAddress(OperatorSet memory operatorSet, address operator) external view returns (address); + + /** + * @notice Checks if a key hash is globally registered + * @param keyHash Hash of the key + * @return True if the key is globally registered + */ + function isKeyGloballyRegistered( + bytes32 keyHash + ) external view returns (bool); + + /** + * @notice Gets the key hash for an operator with a specific operator set + * @param operatorSet The operator set to get the key hash for + * @param operator Address of the operator + * @return keyHash The key hash + */ + function getKeyHash(OperatorSet memory operatorSet, address operator) external view returns (bytes32); +} diff --git a/src/contracts/permissions/KeyRegistrar.sol b/src/contracts/permissions/KeyRegistrar.sol new file mode 100644 index 0000000000..e67a4a1a14 --- /dev/null +++ b/src/contracts/permissions/KeyRegistrar.sol @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../libraries/BN254.sol"; +import "../mixins/PermissionControllerMixin.sol"; +import "../mixins/SignatureUtilsMixin.sol"; +import "../interfaces/IPermissionController.sol"; +import "../interfaces/IAllocationManager.sol"; +import "../libraries/OperatorSetLib.sol"; +import "./KeyRegistrarStorage.sol"; + +/** + * @title KeyRegistrar + * @notice A core singleton contract that manages operator keys for different AVSs with global key uniqueness + * @dev Provides registration and deregistration of keys with support for aggregate keys + * Keys must be unique globally across all AVSs and operator sets + * Operators call functions directly to manage their own keys + * Aggregate keys are updated via callback from AVSRegistrar on registration and deregistration + */ +contract KeyRegistrar is KeyRegistrarStorage, PermissionControllerMixin, SignatureUtilsMixin { + using BN254 for BN254.G1Point; + + // EIP-712 type hashes + bytes32 public constant ECDSA_KEY_REGISTRATION_TYPEHASH = + keccak256("ECDSAKeyRegistration(address operator,address avs,uint32 operatorSetId,address keyAddress)"); + + bytes32 public constant BN254_KEY_REGISTRATION_TYPEHASH = + keccak256("BN254KeyRegistration(address operator,address avs,uint32 operatorSetId,bytes keyData)"); + + /** + * @dev Constructor for the KeyRegistrar contract + * @param _permissionController The permission controller contract + * @param _allocationManager The allocation manager contract + * @param _version The version string for the contract + */ + constructor( + IPermissionController _permissionController, + IAllocationManager _allocationManager, + string memory _version + ) + KeyRegistrarStorage(_allocationManager) + PermissionControllerMixin(_permissionController) + SignatureUtilsMixin(_version) + {} + + /// @inheritdoc IKeyRegistrar + function configureOperatorSet( + OperatorSet memory operatorSet, + CurveType curveType + ) external checkCanCall(operatorSet.avs) { + require(curveType == CurveType.ECDSA || curveType == CurveType.BN254, InvalidCurveType()); + + // Prevent overwriting existing configurations + CurveType _curveType = operatorSetCurveTypes[operatorSet.key()]; + require(_curveType == CurveType.NONE, ConfigurationAlreadySet()); + + operatorSetCurveTypes[operatorSet.key()] = curveType; + + emit OperatorSetConfigured(operatorSet, curveType); + } + + /// @inheritdoc IKeyRegistrar + function registerKey( + address operator, + OperatorSet memory operatorSet, + bytes calldata keyData, + bytes calldata signature + ) external checkCanCall(operator) { + CurveType curveType = operatorSetCurveTypes[operatorSet.key()]; + require(curveType != CurveType.NONE, OperatorSetNotConfigured()); + + // Check if the key is already registered + require(!operatorKeyInfo[operatorSet.key()][operator].isRegistered, KeyAlreadyRegistered()); + + // Register key based on curve type - both now require signature verification + if (curveType == CurveType.ECDSA) { + _registerECDSAKey(operatorSet, operator, keyData, signature); + } else if (curveType == CurveType.BN254) { + _registerBN254Key(operatorSet, operator, keyData, signature); + } else { + revert InvalidCurveType(); + } + + emit KeyRegistered(operatorSet, operator, curveType, keyData); + } + + /// @inheritdoc IKeyRegistrar + function deregisterKey(address operator, OperatorSet memory operatorSet) external { + require(address(allocationManager.getAVSRegistrar(operatorSet.avs)) == msg.sender, InvalidPermissions()); + + CurveType curveType = operatorSetCurveTypes[operatorSet.key()]; + require(curveType != CurveType.NONE, OperatorSetNotConfigured()); + + KeyInfo memory keyInfo = operatorKeyInfo[operatorSet.key()][operator]; + + require(keyInfo.isRegistered, KeyNotFound(operatorSet, operator)); + + // Clear key info + delete operatorKeyInfo[operatorSet.key()][operator]; + + emit KeyDeregistered(operatorSet, operator, curveType); + } + + /// @inheritdoc IKeyRegistrar + function checkKey(OperatorSet memory operatorSet, address operator) external view returns (bool) { + CurveType curveType = operatorSetCurveTypes[operatorSet.key()]; + require(curveType != CurveType.NONE, OperatorSetNotConfigured()); + + KeyInfo memory keyInfo = operatorKeyInfo[operatorSet.key()][operator]; + return keyInfo.isRegistered; + } + + /** + * + * INTERNAL FUNCTIONS + * + */ + + /** + * @notice Validates and registers an ECDSA address with EIP-712 signature verification + * @param operatorSet The operator set to register the key for + * @param operator Address of the operator + * @param keyData The ECDSA address encoded as bytes (20 bytes) + * @param signature EIP-712 signature over the registration message + * @dev Validates address format, verifies signature ownership, and ensures global uniqueness + */ + function _registerECDSAKey( + OperatorSet memory operatorSet, + address operator, + bytes calldata keyData, + bytes calldata signature + ) internal { + // Validate ECDSA address format + require(keyData.length == 20, InvalidKeyFormat()); + + // Decode address from bytes + address keyAddress = address(bytes20(keyData)); + require(keyAddress != address(0), ZeroPubkey()); + + // Calculate key hash using the address + bytes32 keyHash = _getKeyHashForKeyData(keyData, CurveType.ECDSA); + + // Check global uniqueness + require(!globalKeyRegistry[keyHash], KeyAlreadyRegistered()); + + // Create EIP-712 compliant message hash + bytes32 structHash = keccak256( + abi.encode(ECDSA_KEY_REGISTRATION_TYPEHASH, operator, operatorSet.avs, operatorSet.id, keyAddress) + ); + bytes32 signableDigest = _calculateSignableDigest(structHash); + + _checkIsValidSignatureNow(keyAddress, signableDigest, signature, type(uint256).max); + + // Store key data + _storeKeyData(operatorSet, operator, keyData, keyHash); + } + + /** + * @notice Validates and registers a BN254 public key with proper signature verification + * @param operatorSet The operator set to register the key for + * @param operator Address of the operator + * @param keyData The BN254 public key bytes (G1 and G2 components) + * @param signature Signature proving key ownership + * @dev Validates keypair, verifies signature using hash-to-G1, and ensures global uniqueness + */ + function _registerBN254Key( + OperatorSet memory operatorSet, + address operator, + bytes calldata keyData, + bytes calldata signature + ) internal { + BN254.G1Point memory g1Point; + BN254.G2Point memory g2Point; + + { + // Decode BN254 G1 and G2 points from the keyData bytes + (uint256 g1X, uint256 g1Y, uint256[2] memory g2X, uint256[2] memory g2Y) = + abi.decode(keyData, (uint256, uint256, uint256[2], uint256[2])); + + // Validate G1 point + g1Point = BN254.G1Point(g1X, g1Y); + require(!(g1X == 0 && g1Y == 0), ZeroPubkey()); + + // Construct BN254 G2 point from coordinates + g2Point = BN254.G2Point(g2X, g2Y); + } + + // Create EIP-712 compliant message hash + bytes32 structHash = keccak256( + abi.encode(BN254_KEY_REGISTRATION_TYPEHASH, operator, operatorSet.avs, operatorSet.id, keccak256(keyData)) + ); + bytes32 signableDigest = _calculateSignableDigest(structHash); + + // hash to g1 + BN254.G1Point memory messagePoint = BN254.hashToG1(signableDigest); + _verifyBN254Signature(messagePoint, signature, g1Point, g2Point); + + // Calculate key hash and check global uniqueness + bytes32 keyHash = _getKeyHashForKeyData(keyData, CurveType.BN254); + require(!globalKeyRegistry[keyHash], KeyAlreadyRegistered()); + + // Store key data + _storeKeyData(operatorSet, operator, keyData, keyHash); + } + + /** + * @notice Internal helper to store key data and update global registry + * @param operatorSet The operator set + * @param operator The operator address + * @param pubkey The public key data + * @param keyHash The key hash + */ + function _storeKeyData( + OperatorSet memory operatorSet, + address operator, + bytes memory pubkey, + bytes32 keyHash + ) internal { + // Store key data + operatorKeyInfo[operatorSet.key()][operator] = KeyInfo({isRegistered: true, keyData: pubkey}); + + // Update global key registry + globalKeyRegistry[keyHash] = true; + } + + /** + * @notice Internal helper to get key hash for pubkey data using consistent hashing + * @param pubkey The public key data + * @param curveType The curve type (ECDSA or BN254) + * @return keyHash The key hash + */ + function _getKeyHashForKeyData(bytes memory pubkey, CurveType curveType) internal pure returns (bytes32) { + if (curveType == CurveType.ECDSA) { + return keccak256(pubkey); + } else if (curveType == CurveType.BN254) { + (uint256 g1X, uint256 g1Y,,) = abi.decode(pubkey, (uint256, uint256, uint256[2], uint256[2])); + return BN254.hashG1Point(BN254.G1Point(g1X, g1Y)); + } + + revert InvalidCurveType(); + } + + /** + * @notice Verifies a BN254 signature + * @param messagePoint The G1 point representing the hashed message + * @param signature The signature bytes + * @param pubkeyG1 The G1 component of the public key + * @param pubkeyG2 The G2 component of the public key + */ + function _verifyBN254Signature( + BN254.G1Point memory messagePoint, + bytes memory signature, + BN254.G1Point memory pubkeyG1, + BN254.G2Point memory pubkeyG2 + ) public view { + // Decode signature + BN254.G1Point memory sigPoint; + { + (uint256 sigX, uint256 sigY) = abi.decode(signature, (uint256, uint256)); + sigPoint = BN254.G1Point(sigX, sigY); + } + + // gamma = h(sigma, P, P', H(m)) - exact same pattern as BLSApkRegistry + uint256 gamma = uint256( + keccak256( + abi.encodePacked( + sigPoint.X, + sigPoint.Y, + pubkeyG1.X, + pubkeyG1.Y, + pubkeyG2.X[0], + pubkeyG2.X[1], + pubkeyG2.Y[0], + pubkeyG2.Y[1], + messagePoint.X, + messagePoint.Y + ) + ) + ) % BN254.FR_MODULUS; + + // e(sigma + P * gamma, [1]_2) = e(H(m) + [1]_1 * gamma, P') + require( + BN254.pairing( + sigPoint.plus(pubkeyG1.scalar_mul(gamma)), + BN254.negGeneratorG2(), + messagePoint.plus(BN254.generatorG1().scalar_mul(gamma)), + pubkeyG2 + ), + InvalidSignature() + ); + } + + /** + * + * VIEW FUNCTIONS + * + */ + + /// @inheritdoc IKeyRegistrar + function isRegistered(OperatorSet memory operatorSet, address operator) external view returns (bool) { + return operatorKeyInfo[operatorSet.key()][operator].isRegistered; + } + + /// @inheritdoc IKeyRegistrar + function getOperatorSetCurveType( + OperatorSet memory operatorSet + ) external view returns (CurveType) { + return operatorSetCurveTypes[operatorSet.key()]; + } + + /// @inheritdoc IKeyRegistrar + function getBN254Key( + OperatorSet memory operatorSet, + address operator + ) external view returns (BN254.G1Point memory g1Point, BN254.G2Point memory g2Point) { + // Validate operator set curve type + CurveType curveType = operatorSetCurveTypes[operatorSet.key()]; + require(curveType == CurveType.BN254, InvalidCurveType()); + + KeyInfo memory keyInfo = operatorKeyInfo[operatorSet.key()][operator]; + + if (!keyInfo.isRegistered) { + // Create default values for an empty key + uint256[2] memory zeroArray = [uint256(0), uint256(0)]; + return (BN254.G1Point(0, 0), BN254.G2Point(zeroArray, zeroArray)); + } + + (uint256 g1X, uint256 g1Y, uint256[2] memory g2X, uint256[2] memory g2Y) = + abi.decode(keyInfo.keyData, (uint256, uint256, uint256[2], uint256[2])); + + return (BN254.G1Point(g1X, g1Y), BN254.G2Point(g2X, g2Y)); + } + + /// @inheritdoc IKeyRegistrar + function getECDSAKey(OperatorSet memory operatorSet, address operator) public view returns (bytes memory) { + // Validate operator set curve type + CurveType curveType = operatorSetCurveTypes[operatorSet.key()]; + require(curveType == CurveType.ECDSA, InvalidCurveType()); + + KeyInfo memory keyInfo = operatorKeyInfo[operatorSet.key()][operator]; + return keyInfo.keyData; // Returns the 20-byte address as bytes + } + + /// @inheritdoc IKeyRegistrar + function getECDSAAddress(OperatorSet memory operatorSet, address operator) external view returns (address) { + return address(bytes20(getECDSAKey(operatorSet, operator))); + } + + /// @inheritdoc IKeyRegistrar + function isKeyGloballyRegistered( + bytes32 keyHash + ) external view returns (bool) { + return globalKeyRegistry[keyHash]; + } + + /// @inheritdoc IKeyRegistrar + function getKeyHash(OperatorSet memory operatorSet, address operator) external view returns (bytes32) { + KeyInfo memory keyInfo = operatorKeyInfo[operatorSet.key()][operator]; + CurveType curveType = operatorSetCurveTypes[operatorSet.key()]; + + if (!keyInfo.isRegistered) { + return bytes32(0); + } + + return _getKeyHashForKeyData(keyInfo.keyData, curveType); + } + + /** + * @notice Returns the message hash for ECDSA key registration + * @param operator The operator address + * @param operatorSet The operator set + * @param keyAddress The ECDSA key address + * @return The message hash for signing + */ + function getECDSAKeyRegistrationMessageHash( + address operator, + OperatorSet memory operatorSet, + address keyAddress + ) external view returns (bytes32) { + bytes32 structHash = keccak256( + abi.encode(ECDSA_KEY_REGISTRATION_TYPEHASH, operator, operatorSet.avs, operatorSet.id, keyAddress) + ); + return _calculateSignableDigest(structHash); + } + + /** + * @notice Returns the message hash for BN254 key registration + * @param operator The operator address + * @param operatorSet The operator set + * @param keyData The BN254 key data + * @return The message hash for signing + */ + function getBN254KeyRegistrationMessageHash( + address operator, + OperatorSet memory operatorSet, + bytes calldata keyData + ) external view returns (bytes32) { + bytes32 structHash = keccak256( + abi.encode(BN254_KEY_REGISTRATION_TYPEHASH, operator, operatorSet.avs, operatorSet.id, keccak256(keyData)) + ); + return _calculateSignableDigest(structHash); + } +} diff --git a/src/contracts/permissions/KeyRegistrarStorage.sol b/src/contracts/permissions/KeyRegistrarStorage.sol new file mode 100644 index 0000000000..72babed31d --- /dev/null +++ b/src/contracts/permissions/KeyRegistrarStorage.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "../interfaces/IAllocationManager.sol"; +import "../interfaces/IKeyRegistrar.sol"; + +abstract contract KeyRegistrarStorage is IKeyRegistrar { + // Constants + + /// @dev Gas limit for pairing operations to prevent DoS + uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 400_000; + + /// @dev Hash of zero ECDSA public key (0x04 + 64 zero bytes) + bytes32 internal constant ZERO_ECDSA_PUBKEY_HASH = keccak256(abi.encodePacked(bytes1(0x04), new bytes(64))); + + // Immutables + + /// @dev Reference to the AllocationManager contract + IAllocationManager public immutable allocationManager; + + // Mutatables + + /// @dev Maps (operatorSetKey, operator) to their key info + mapping(bytes32 => mapping(address => KeyInfo)) internal operatorKeyInfo; + + /// @dev Maps operatorSetKey to the key type + mapping(bytes32 => CurveType) internal operatorSetCurveTypes; + + /// @dev Global mapping of key hash to registration status - enforces global uniqueness + mapping(bytes32 => bool) internal globalKeyRegistry; + + // Construction + + constructor( + IAllocationManager _allocationManager + ) { + allocationManager = _allocationManager; + } + + /** + * @dev This empty reserved space is put in place to allow future versions to add new + * variables without shifting down storage in the inheritance chain. + * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + */ + uint256[47] private __gap; +} diff --git a/src/test/mocks/AllocationManagerMock.sol b/src/test/mocks/AllocationManagerMock.sol index 35ab172f54..cebf680941 100644 --- a/src/test/mocks/AllocationManagerMock.sol +++ b/src/test/mocks/AllocationManagerMock.sol @@ -5,6 +5,7 @@ import "forge-std/Test.sol"; import "src/contracts/interfaces/IStrategy.sol"; import "src/contracts/libraries/Snapshots.sol"; import "src/contracts/libraries/OperatorSetLib.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; contract AllocationManagerMock is Test { address constant DEFAULT_BURN_ADDRESS = address(0x00000000000000000000000000000000000E16E4); @@ -18,6 +19,7 @@ contract AllocationManagerMock is Test { mapping(bytes32 operatorSetKey => bool) public _isOperatorSet; mapping(address avs => uint) public getOperatorSetCount; mapping(address => mapping(IStrategy => Snapshots.DefaultWadHistory)) internal _maxMagnitudeHistory; + mapping(address => address) internal _avsRegistrar; mapping(bytes32 operatorSetKey => address) public _getRedistributionRecipient; mapping(bytes32 operatorSetKey => uint) public _getSlashCount; @@ -92,4 +94,12 @@ contract AllocationManagerMock is Test { function setAVSSetCount(address avs, uint numSets) external { getOperatorSetCount[avs] = numSets; } + + function setAVSRegistrar(address avs, address avsRegistrar) external { + _avsRegistrar[avs] = avsRegistrar; + } + + function getAVSRegistrar(address avs) external view returns (address) { + return _avsRegistrar[avs]; + } } diff --git a/src/test/unit/KeyRegistrarUnit.t.sol b/src/test/unit/KeyRegistrarUnit.t.sol new file mode 100644 index 0000000000..c54f5d5569 --- /dev/null +++ b/src/test/unit/KeyRegistrarUnit.t.sol @@ -0,0 +1,781 @@ +//SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +import "forge-std/Test.sol"; +import "src/contracts/permissions/KeyRegistrar.sol"; +import "src/contracts/interfaces/IKeyRegistrar.sol"; +import "src/contracts/libraries/BN254.sol"; +import "src/contracts/interfaces/IPermissionController.sol"; +import "src/contracts/mixins/PermissionControllerMixin.sol"; +import "src/test/utils/EigenLayerUnitTestSetup.sol"; +import "src/contracts/libraries/OperatorSetLib.sol"; +import "src/contracts/interfaces/ISignatureUtilsMixin.sol"; + +contract KeyRegistrarUnitTests is EigenLayerUnitTestSetup { + using BN254 for BN254.G1Point; + + KeyRegistrar public keyRegistrar; + + address public owner = address(0x1); + address public operator1 = address(0x2); + address public operator2 = address(0x3); + address public avs1 = address(0x4); + address public avs2 = address(0x5); + + uint32 public constant DEFAULT_OPERATOR_SET_ID = 0; + + // Test addresses for ECDSA (20 bytes each) + address public ecdsaAddress1 = address(0x1234567890123456789012345678901234567890); + address public ecdsaAddress2 = address(0x9876543210987654321098765432109876543210); + bytes public ecdsaKey1 = abi.encodePacked(ecdsaAddress1); + bytes public ecdsaKey2 = abi.encodePacked(ecdsaAddress2); + + // Private keys for ECDSA addresses (for signature generation) + uint public ecdsaPrivKey1 = 0x1234567890123456789012345678901234567890123456789012345678901234; + uint public ecdsaPrivKey2 = 0x9876543210987654321098765432109876543210987654321098765432109876; + + // Test keys for BN254 + uint public bn254PrivKey1 = 69; + uint public bn254PrivKey2 = 123; + + BN254.G1Point bn254G1Key1; + BN254.G1Point bn254G1Key2; + BN254.G2Point bn254G2Key1; + BN254.G2Point bn254G2Key2; + + bytes public bn254Key1; + bytes public bn254Key2; + + event KeyRegistered(OperatorSet operatorSet, address indexed operator, IKeyRegistrarTypes.CurveType curveType, bytes pubkey); + event KeyDeregistered(OperatorSet operatorSet, address indexed operator, IKeyRegistrarTypes.CurveType curveType); + event OperatorSetConfigured(OperatorSet operatorSet, IKeyRegistrarTypes.CurveType curveType); + + function setUp() public virtual override { + EigenLayerUnitTestSetup.setUp(); + + keyRegistrar = new KeyRegistrar( + IPermissionController(address(permissionController)), IAllocationManager(address(allocationManagerMock)), "1.0.0" + ); + + // Set up ECDSA addresses that correspond to the private keys + ecdsaAddress1 = vm.addr(ecdsaPrivKey1); + ecdsaAddress2 = vm.addr(ecdsaPrivKey2); + ecdsaKey1 = abi.encodePacked(ecdsaAddress1); + ecdsaKey2 = abi.encodePacked(ecdsaAddress2); + + // Set up BN254 keys with proper G2 components + bn254G1Key1 = BN254.generatorG1().scalar_mul(bn254PrivKey1); + bn254G1Key2 = BN254.generatorG1().scalar_mul(bn254PrivKey2); + + // Valid G2 points that correspond to the private keys + bn254G2Key1.X[1] = 19_101_821_850_089_705_274_637_533_855_249_918_363_070_101_489_527_618_151_493_230_256_975_900_223_847; + bn254G2Key1.X[0] = 5_334_410_886_741_819_556_325_359_147_377_682_006_012_228_123_419_628_681_352_847_439_302_316_235_957; + bn254G2Key1.Y[1] = 354_176_189_041_917_478_648_604_979_334_478_067_325_821_134_838_555_150_300_539_079_146_482_658_331; + bn254G2Key1.Y[0] = 4_185_483_097_059_047_421_902_184_823_581_361_466_320_657_066_600_218_863_748_375_739_772_335_928_910; + + bn254G2Key2.X[1] = 19_276_105_129_625_393_659_655_050_515_259_006_463_014_579_919_681_138_299_520_812_914_148_935_621_072; + bn254G2Key2.X[0] = 14_066_454_060_412_929_535_985_836_631_817_650_877_381_034_334_390_275_410_072_431_082_437_297_539_867; + bn254G2Key2.Y[1] = 12_642_665_914_920_339_463_975_152_321_804_664_028_480_770_144_655_934_937_445_922_690_262_428_344_269; + bn254G2Key2.Y[0] = 10_109_651_107_942_685_361_120_988_628_892_759_706_059_655_669_161_016_107_907_096_760_613_704_453_218; + + bn254Key1 = abi.encode(bn254G1Key1.X, bn254G1Key1.Y, bn254G2Key1.X, bn254G2Key1.Y); + bn254Key2 = abi.encode(bn254G1Key2.X, bn254G1Key2.Y, bn254G2Key2.X, bn254G2Key2.Y); + + allocationManagerMock.setAVSRegistrar(avs1, avs1); + allocationManagerMock.setAVSRegistrar(avs2, avs2); + } + + function _createOperatorSet(address avs, uint32 operatorSetId) internal pure returns (OperatorSet memory) { + return OperatorSet({avs: avs, id: operatorSetId}); + } + + function _generateECDSASignature(address operator, OperatorSet memory operatorSet, address keyAddress, uint privKey) + internal + view + returns (bytes memory) + { + bytes32 structHash = + keccak256(abi.encode(keyRegistrar.ECDSA_KEY_REGISTRATION_TYPEHASH(), operator, operatorSet.avs, operatorSet.id, keyAddress)); + bytes32 messageHash = keyRegistrar.domainSeparator(); + messageHash = keccak256(abi.encodePacked("\x19\x01", messageHash, structHash)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, messageHash); + return abi.encodePacked(r, s, v); + } + + function _generateBN254Signature(address operator, OperatorSet memory operatorSet, bytes memory pubkey, uint privKey) + internal + view + returns (bytes memory) + { + bytes32 structHash = keccak256( + abi.encode(keyRegistrar.BN254_KEY_REGISTRATION_TYPEHASH(), operator, operatorSet.avs, operatorSet.id, keccak256(pubkey)) + ); + bytes32 messageHash = keyRegistrar.domainSeparator(); + messageHash = keccak256(abi.encodePacked("\x19\x01", messageHash, structHash)); + + BN254.G1Point memory msgPoint = BN254.hashToG1(messageHash); + BN254.G1Point memory signature = msgPoint.scalar_mul(privKey); + + return abi.encode(signature.X, signature.Y); + } + + // ============ Operator Set Configuration Tests ============ + + function testConfigureOperatorSet() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + vm.expectEmit(true, true, true, true); + emit OperatorSetConfigured(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + IKeyRegistrarTypes.CurveType curveType = keyRegistrar.getOperatorSetCurveType(operatorSet); + assertEq(uint8(curveType), uint8(IKeyRegistrarTypes.CurveType.ECDSA)); + } + + function testConfigureOperatorSet_RevertUnauthorized() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(operator1); + vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + } + + function testConfigureOperatorSet_BN254() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + vm.expectEmit(true, true, true, true); + emit OperatorSetConfigured(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + IKeyRegistrarTypes.CurveType curveType = keyRegistrar.getOperatorSetCurveType(operatorSet); + assertEq(uint8(curveType), uint8(IKeyRegistrarTypes.CurveType.BN254)); + } + + function testConfigureOperatorSet_RevertConfigurationAlreadySet() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.prank(avs1); + vm.expectRevert(IKeyRegistrarErrors.ConfigurationAlreadySet.selector); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + } + + function testConfigureOperatorSet_RevertInvalidCurveType() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + vm.expectRevert(IKeyRegistrarErrors.InvalidCurveType.selector); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.NONE); + } + + // ============ ECDSA Key Registration Tests ============ + + function testRegisterECDSAKey() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + vm.expectEmit(true, true, true, true); + emit KeyRegistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.ECDSA, ecdsaKey1); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature); + + assertTrue(keyRegistrar.isRegistered(operatorSet, operator1)); + bytes memory storedKey = keyRegistrar.getECDSAKey(operatorSet, operator1); + assertEq(storedKey, ecdsaKey1); + + address storedAddress = keyRegistrar.getECDSAAddress(operatorSet, operator1); + assertEq(storedAddress, ecdsaAddress1); + } + + function testRegisterECDSAKey_RevertInvalidFormat() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + // Invalid length (not 20 bytes) + bytes memory invalidKey = hex"04b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4"; + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.InvalidKeyFormat.selector); + keyRegistrar.registerKey(operator1, operatorSet, invalidKey, ""); + + // Another invalid length + bytes memory invalidKey2 = + hex"b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c86a57d8a35ebfd17be4b8d44d0f7b8d5b5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"; + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.InvalidKeyFormat.selector); + keyRegistrar.registerKey(operator1, operatorSet, invalidKey2, ""); + } + + function testRegisterECDSAKey_RevertZeroPubkey() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory zeroKey = abi.encodePacked(address(0)); + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.ZeroPubkey.selector); + keyRegistrar.registerKey(operator1, operatorSet, zeroKey, ""); + } + + function testRegisterECDSAKey_RevertInvalidSignature() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + // Use wrong private key for signature + bytes memory wrongSignature = _generateECDSASignature( + operator1, + operatorSet, + ecdsaAddress1, + ecdsaPrivKey2 // Wrong private key + ); + + vm.prank(operator1); + vm.expectRevert(ISignatureUtilsMixinErrors.InvalidSignature.selector); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, wrongSignature); + } + + function testRegisterECDSAKey_RevertAlreadyRegistered() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature); + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.KeyAlreadyRegistered.selector); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature); + } + + function testRegisterECDSAKey_RevertGloballyRegistered() public { + OperatorSet memory operatorSet1 = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + OperatorSet memory operatorSet2 = _createOperatorSet(avs2, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet1, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.prank(avs2); + keyRegistrar.configureOperatorSet(operatorSet2, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory signature1 = _generateECDSASignature(operator1, operatorSet1, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet1, ecdsaKey1, signature1); + + bytes memory signature2 = _generateECDSASignature(operator2, operatorSet2, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator2); + vm.expectRevert(IKeyRegistrarErrors.KeyAlreadyRegistered.selector); + keyRegistrar.registerKey(operator2, operatorSet2, ecdsaKey1, signature2); + } + + // ============ BN254 Key Registration Tests ============ + + function testRegisterBN254Key() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory signature = _generateBN254Signature(operator1, operatorSet, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + vm.expectEmit(true, true, true, true); + emit KeyRegistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.BN254, bn254Key1); + keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, signature); + + assertTrue(keyRegistrar.isRegistered(operatorSet, operator1)); + (BN254.G1Point memory storedG1, BN254.G2Point memory storedG2) = keyRegistrar.getBN254Key(operatorSet, operator1); + assertEq(storedG1.X, bn254G1Key1.X); + assertEq(storedG1.Y, bn254G1Key1.Y); + } + + function testRegisterBN254Key_RevertZeroPubkey() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory zeroKey = abi.encode(uint(0), uint(0), bn254G2Key1.X, bn254G2Key1.Y); + bytes memory signature = _generateBN254Signature(operator1, operatorSet, zeroKey, bn254PrivKey1); + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.ZeroPubkey.selector); + keyRegistrar.registerKey(operator1, operatorSet, zeroKey, signature); + } + + function testRegisterBN254Key_RevertInvalidSignature() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory invalidSignature = abi.encode(uint(1), uint(2)); + + vm.prank(operator1); + vm.expectRevert(ISignatureUtilsMixinErrors.InvalidSignature.selector); + keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, invalidSignature); + } + + function testRegisterBN254Key_RevertAlreadyRegistered() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory signature = _generateBN254Signature(operator1, operatorSet, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, signature); + + bytes32 keyHash = keyRegistrar.getKeyHash(operatorSet, operator1); + bytes32 expectedHash = BN254.hashG1Point(bn254G1Key1); + assertEq(keyHash, expectedHash); + } + + function testGetKeyHash_UnregisteredOperator() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + bytes32 zeroHash = keyRegistrar.getKeyHash(operatorSet, operator2); + assertEq(zeroHash, bytes32(0)); + } + + function testIsKeyGloballyRegistered() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes32 keyHash = keccak256(ecdsaKey1); + assertFalse(keyRegistrar.isKeyGloballyRegistered(keyHash)); + + bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature); + + assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash)); + } + + function testGetBN254Key_EmptyForUnregistered() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + (BN254.G1Point memory g1Point, BN254.G2Point memory g2Point) = keyRegistrar.getBN254Key(operatorSet, operator1); + + assertEq(g1Point.X, 0); + assertEq(g1Point.Y, 0); + assertEq(g2Point.X[0], 0); + assertEq(g2Point.X[1], 0); + assertEq(g2Point.Y[0], 0); + assertEq(g2Point.Y[1], 0); + } + + function testGetECDSAKey_EmptyForUnregistered() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory emptyKey = keyRegistrar.getECDSAKey(operatorSet, operator1); + assertEq(emptyKey.length, 0); + } + + function testGetECDSAAddress_ZeroForUnregistered() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + address zeroAddress = keyRegistrar.getECDSAAddress(operatorSet, operator1); + assertEq(zeroAddress, address(0)); + } + + // ============ Authorization Tests ============ + + function testRegisterKey_RevertOperatorSetNotConfigured() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.OperatorSetNotConfigured.selector); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, ""); + } + + function testRegisterKey_RevertUnauthorized() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.prank(operator2); + vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, ""); + } + + function testDeregisterKey_RevertOperatorSetNotConfigured() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + vm.expectRevert(IKeyRegistrarErrors.OperatorSetNotConfigured.selector); + keyRegistrar.deregisterKey(operator1, operatorSet); + } + + function testCheckKey_RevertOperatorSetNotConfigured() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + vm.expectRevert(IKeyRegistrarErrors.OperatorSetNotConfigured.selector); + keyRegistrar.checkKey(operatorSet, operator1); + } + + // ============ Multiple Operator Sets Tests ============ + + function testMultipleOperatorSets() public { + uint32 operatorSetId1 = 0; + uint32 operatorSetId2 = 1; + + OperatorSet memory operatorSet1 = _createOperatorSet(avs1, operatorSetId1); + OperatorSet memory operatorSet2 = _createOperatorSet(avs1, operatorSetId2); + + vm.startPrank(avs1); + keyRegistrar.configureOperatorSet(operatorSet1, IKeyRegistrarTypes.CurveType.ECDSA); + keyRegistrar.configureOperatorSet(operatorSet2, IKeyRegistrarTypes.CurveType.BN254); + vm.stopPrank(); + + IKeyRegistrarTypes.CurveType curveType1 = keyRegistrar.getOperatorSetCurveType(operatorSet1); + IKeyRegistrarTypes.CurveType curveType2 = keyRegistrar.getOperatorSetCurveType(operatorSet2); + + assertEq(uint8(curveType1), uint8(IKeyRegistrarTypes.CurveType.ECDSA)); + assertEq(uint8(curveType2), uint8(IKeyRegistrarTypes.CurveType.BN254)); + + bytes memory signature = _generateECDSASignature(operator1, operatorSet1, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet1, ecdsaKey1, signature); + + assertTrue(keyRegistrar.isRegistered(operatorSet1, operator1)); + assertFalse(keyRegistrar.isRegistered(operatorSet2, operator1)); + } + + function testMultipleOperatorSets_DifferentKeyTypes() public { + uint32 ecdsaSetId = 0; + uint32 bn254SetId = 1; + + OperatorSet memory ecdsaOperatorSet = _createOperatorSet(avs1, ecdsaSetId); + OperatorSet memory bn254OperatorSet = _createOperatorSet(avs1, bn254SetId); + + vm.startPrank(avs1); + keyRegistrar.configureOperatorSet(ecdsaOperatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + keyRegistrar.configureOperatorSet(bn254OperatorSet, IKeyRegistrarTypes.CurveType.BN254); + vm.stopPrank(); + + // Register ECDSA key for one operator set + bytes memory ecdsaSignature = _generateECDSASignature(operator1, ecdsaOperatorSet, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, ecdsaOperatorSet, ecdsaKey1, ecdsaSignature); + + // Register BN254 key for another operator set + bytes memory bn254Signature = _generateBN254Signature(operator1, bn254OperatorSet, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, bn254OperatorSet, bn254Key1, bn254Signature); + + // Verify both registrations + assertTrue(keyRegistrar.isRegistered(ecdsaOperatorSet, operator1)); + assertTrue(keyRegistrar.isRegistered(bn254OperatorSet, operator1)); + + // Verify key retrieval + bytes memory ecdsaKey = keyRegistrar.getECDSAKey(ecdsaOperatorSet, operator1); + assertEq(ecdsaKey, ecdsaKey1); + + address ecdsaAddr = keyRegistrar.getECDSAAddress(ecdsaOperatorSet, operator1); + assertEq(ecdsaAddr, ecdsaAddress1); + + (BN254.G1Point memory g1Point,) = keyRegistrar.getBN254Key(bn254OperatorSet, operator1); + assertEq(g1Point.X, bn254G1Key1.X); + assertEq(g1Point.Y, bn254G1Key1.Y); + } + + // ============ Global Key Persistence Tests ============ + + function testGlobalKeyPersistence() public { + OperatorSet memory operatorSet1 = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + OperatorSet memory operatorSet2 = _createOperatorSet(avs2, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet1, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.prank(avs2); + keyRegistrar.configureOperatorSet(operatorSet2, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory signature1 = _generateECDSASignature(operator1, operatorSet1, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet1, ecdsaKey1, signature1); + + bytes32 keyHash = keccak256(ecdsaKey1); + assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash)); + + vm.prank(avs1); + keyRegistrar.deregisterKey(operator1, operatorSet1); + + // Key should still be globally registered after deregistration + assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash)); + + bytes memory signature2 = _generateECDSASignature(operator2, operatorSet2, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator2); + vm.expectRevert(IKeyRegistrarErrors.KeyAlreadyRegistered.selector); + keyRegistrar.registerKey(operator2, operatorSet2, ecdsaKey1, signature2); + } + + function testGlobalKeyPersistence_BN254() public { + OperatorSet memory operatorSet1 = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + OperatorSet memory operatorSet2 = _createOperatorSet(avs2, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet1, IKeyRegistrarTypes.CurveType.BN254); + + vm.prank(avs2); + keyRegistrar.configureOperatorSet(operatorSet2, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory signature1 = _generateBN254Signature(operator1, operatorSet1, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet1, bn254Key1, signature1); + + bytes32 keyHash = BN254.hashG1Point(bn254G1Key1); + assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash)); + + vm.prank(avs1); + keyRegistrar.deregisterKey(operator1, operatorSet1); + + // Key should still be globally registered after deregistration + assertTrue(keyRegistrar.isKeyGloballyRegistered(keyHash)); + + bytes memory signature2 = _generateBN254Signature(operator2, operatorSet2, bn254Key1, bn254PrivKey1); + + vm.prank(operator2); + vm.expectRevert(IKeyRegistrarErrors.KeyAlreadyRegistered.selector); + keyRegistrar.registerKey(operator2, operatorSet2, bn254Key1, signature2); + } + + // ============ Cross-Curve Type Tests ============ + + function testCrossCurveGlobalUniqueness() public { + // Configure ECDSA and BN254 operator sets + OperatorSet memory ecdsaOperatorSet = _createOperatorSet(avs1, 0); + OperatorSet memory bn254OperatorSet = _createOperatorSet(avs1, 1); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(ecdsaOperatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(bn254OperatorSet, IKeyRegistrarTypes.CurveType.BN254); + + // Register ECDSA key + bytes memory ecdsaSignature = _generateECDSASignature(operator1, ecdsaOperatorSet, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, ecdsaOperatorSet, ecdsaKey1, ecdsaSignature); + + // Register BN254 key (should succeed as they have different hashes) + bytes memory bn254Signature = _generateBN254Signature(operator1, bn254OperatorSet, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, bn254OperatorSet, bn254Key1, bn254Signature); + + // Both should be registered + assertTrue(keyRegistrar.isRegistered(ecdsaOperatorSet, operator1)); + assertTrue(keyRegistrar.isRegistered(bn254OperatorSet, operator1)); + } + + // ============ Error Condition Tests ============ + + function testRegisterKey_RevertWrongCurveType() public { + // Configure for ECDSA but try to register BN254 key + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory signature = _generateBN254Signature(operator1, operatorSet, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + vm.expectRevert(IKeyRegistrarErrors.InvalidKeyFormat.selector); + keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, signature); + } + + function testGetECDSAKey_RevertWrongCurveType() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + vm.expectRevert(IKeyRegistrarErrors.InvalidCurveType.selector); + keyRegistrar.getECDSAKey(operatorSet, operator1); + } + + function testGetBN254Key_RevertWrongCurveType() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.expectRevert(IKeyRegistrarErrors.InvalidCurveType.selector); + keyRegistrar.getBN254Key(operatorSet, operator1); + } + + // ============ Version Tests ============ + + function testVersion() public { + string memory version = keyRegistrar.version(); + assertEq(version, "1.0.0"); + } + + // ============ Signature Verification Tests ============ + + function testVerifyBN254Signature() public { + bytes32 messageHash = keccak256("test message"); + + // Generate signature with private key + BN254.G1Point memory msgPoint = BN254.hashToG1(messageHash); + BN254.G1Point memory signature = msgPoint.scalar_mul(bn254PrivKey1); + bytes memory signatureBytes = abi.encode(signature.X, signature.Y); + + // Should not revert for valid signature + keyRegistrar._verifyBN254Signature(msgPoint, signatureBytes, bn254G1Key1, bn254G2Key1); + } + + function testVerifyBN254Signature_RevertInvalid() public { + bytes32 messageHash = keccak256("test message"); + bytes memory invalidSignature = abi.encode(uint(1), uint(2)); + BN254.G1Point memory msgPoint = BN254.hashToG1(messageHash); + + vm.expectRevert(ISignatureUtilsMixinErrors.InvalidSignature.selector); + keyRegistrar._verifyBN254Signature(msgPoint, invalidSignature, bn254G1Key1, bn254G2Key1); + } + + function testRegisterBN254Key_RevertGloballyRegistered() public { + OperatorSet memory operatorSet1 = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + OperatorSet memory operatorSet2 = _createOperatorSet(avs2, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet1, IKeyRegistrarTypes.CurveType.BN254); + + vm.prank(avs2); + keyRegistrar.configureOperatorSet(operatorSet2, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory signature1 = _generateBN254Signature(operator1, operatorSet1, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet1, bn254Key1, signature1); + + bytes memory signature2 = _generateBN254Signature(operator2, operatorSet2, bn254Key1, bn254PrivKey1); + + vm.prank(operator2); + vm.expectRevert(IKeyRegistrarErrors.KeyAlreadyRegistered.selector); + keyRegistrar.registerKey(operator2, operatorSet2, bn254Key1, signature2); + } + + function testRegisterBN254Key_WrongSignature() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + // Use wrong private key for signature + bytes memory wrongSignature = _generateBN254Signature( + operator1, + operatorSet, + bn254Key1, + bn254PrivKey2 // Wrong private key + ); + + vm.prank(operator1); + vm.expectRevert(ISignatureUtilsMixinErrors.InvalidSignature.selector); + keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, wrongSignature); + } + + // ============ Key Deregistration Tests ============ + + function testDeregisterKey_ECDSA() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature); + + vm.prank(avs1); + vm.expectEmit(true, true, true, true); + emit KeyDeregistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.ECDSA); + keyRegistrar.deregisterKey(operator1, operatorSet); + + assertFalse(keyRegistrar.isRegistered(operatorSet, operator1)); + } + + function testDeregisterKey_BN254() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.BN254); + + bytes memory signature = _generateBN254Signature(operator1, operatorSet, bn254Key1, bn254PrivKey1); + + vm.prank(operator1); + keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, signature); + + vm.prank(avs1); + vm.expectEmit(true, true, true, true); + emit KeyDeregistered(operatorSet, operator1, IKeyRegistrarTypes.CurveType.BN254); + keyRegistrar.deregisterKey(operator1, operatorSet); + + assertFalse(keyRegistrar.isRegistered(operatorSet, operator1)); + } + + function testDeregisterKey_RevertKeyNotFound() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(avs1); + keyRegistrar.configureOperatorSet(operatorSet, IKeyRegistrarTypes.CurveType.ECDSA); + + vm.prank(avs1); + vm.expectRevert(abi.encodeWithSelector(IKeyRegistrarErrors.KeyNotFound.selector, operatorSet, operator1)); + keyRegistrar.deregisterKey(operator1, operatorSet); + } + + function testDeregisterKey_RevertUnauthorized() public { + OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID); + + vm.prank(operator1); + vm.expectRevert(PermissionControllerMixin.InvalidPermissions.selector); + keyRegistrar.deregisterKey(operator1, operatorSet); + } +}