Skip to content

Commit d9278d2

Browse files
authored
feat: key data reverse lookup (#1520)
**Motivation:** We want to get the operator address from signing key **Modifications:** - Adds storage for `_keyHashToOperator` - Adds a `getOperatorFromSigningKey` view func. Pass in the operatorSet and keyData, which is either the signing key for ECDSA or G1/G1+G2 key for BN254 **Result:** Cleaner API
1 parent 7b4d11f commit d9278d2

File tree

5 files changed

+213
-2
lines changed

5 files changed

+213
-2
lines changed

docs/permissions/KeyRegistrar.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Key features:
1010
* **Per-OperatorSet Configuration**: Each operator set must be configured with a specific curve type before keys can be registered
1111
* **Global Key Registry**: Keys are globally unique - once registered, a key cannot be reused across operatorSets or operators
1212

13+
Keys are stored in a 2-way mapping:
14+
1. (operator, operatorSet) to key
15+
2. keyHash to operator address
16+
1317
---
1418

1519
## Operator Set Configuration
@@ -75,6 +79,7 @@ For ECDSA keys:
7579
*Effects*:
7680
* Registers the key for the operator in the specified operator set
7781
* Adds the key to the global registry
82+
* Associates the key hash with the operator address
7883
* Emits a `KeyRegistered` event with curve type ECDSA
7984

8085
*Requirements*:

src/contracts/interfaces/IKeyRegistrar.sol

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,20 @@ interface IKeyRegistrar is IKeyRegistrarErrors, IKeyRegistrarEvents, ISemVerMixi
150150
*/
151151
function getKeyHash(OperatorSet memory operatorSet, address operator) external view returns (bytes32);
152152

153+
/**
154+
* @notice Gets the operator from signing key
155+
* @param operatorSet The operator set to get the operator for
156+
* @param keyData The key data. For ECDSA, this is the signing key address. For BN254, this can be either the G1 key or the G1 and G2 key combined.
157+
* @return operator. Returns 0x0 if the key is not registered
158+
* @return status registration status. Returns false if the key is not registered
159+
* @dev This function decodes the key data based on the curve type of the operator set
160+
* @dev This function will return the operator address even if the operator is not registered for the operator set
161+
*/
162+
function getOperatorFromSigningKey(
163+
OperatorSet memory operatorSet,
164+
bytes memory keyData
165+
) external view returns (address, bool);
166+
153167
/**
154168
* @notice Returns the message hash for ECDSA key registration
155169
* @param operator The operator address

src/contracts/permissions/KeyRegistrar.sol

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ contract KeyRegistrar is KeyRegistrarStorage, PermissionControllerMixin, Signatu
227227

228228
// Update global key registry
229229
_globalKeyRegistry[keyHash] = true;
230+
231+
// Store the operator for the key hash
232+
_keyHashToOperator[keyHash] = operator;
230233
}
231234

232235
/**
@@ -253,7 +256,7 @@ contract KeyRegistrar is KeyRegistrarStorage, PermissionControllerMixin, Signatu
253256
*/
254257

255258
/// @inheritdoc IKeyRegistrar
256-
function isRegistered(OperatorSet memory operatorSet, address operator) external view returns (bool) {
259+
function isRegistered(OperatorSet memory operatorSet, address operator) public view returns (bool) {
257260
return _operatorKeyInfo[operatorSet.key()][operator].isRegistered;
258261
}
259262

@@ -321,6 +324,29 @@ contract KeyRegistrar is KeyRegistrarStorage, PermissionControllerMixin, Signatu
321324
return _getKeyHashForKeyData(keyInfo.keyData, curveType);
322325
}
323326

327+
/// @inheritdoc IKeyRegistrar
328+
function getOperatorFromSigningKey(
329+
OperatorSet memory operatorSet,
330+
bytes memory keyData
331+
) external view returns (address, bool) {
332+
CurveType curveType = _operatorSetCurveTypes[operatorSet.key()];
333+
334+
// We opt to not use _getKeyHashForKeyData here because it expects the G1 and G2 key encoded together for BN254
335+
bytes32 keyHash;
336+
if (curveType == CurveType.ECDSA) {
337+
keyHash = keccak256(keyData);
338+
} else if (curveType == CurveType.BN254) {
339+
/// We cannot use _getKeyHashForKeyData here because it expects the G1 and G2 key encoded together
340+
(uint256 g1X, uint256 g1Y) = abi.decode(keyData, (uint256, uint256));
341+
keyHash = BN254.hashG1Point(BN254.G1Point(g1X, g1Y));
342+
} else {
343+
revert InvalidCurveType();
344+
}
345+
346+
address operator = _keyHashToOperator[keyHash];
347+
return (operator, isRegistered(operatorSet, operator));
348+
}
349+
324350
/// @inheritdoc IKeyRegistrar
325351
function getECDSAKeyRegistrationMessageHash(
326352
address operator,

src/contracts/permissions/KeyRegistrarStorage.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ abstract contract KeyRegistrarStorage is IKeyRegistrar {
2121
/// @dev Global mapping of key hash to registration status - enforces global uniqueness
2222
mapping(bytes32 keyHash => bool isRegistered) internal _globalKeyRegistry;
2323

24+
/// @dev Mapping from (keyHash) to the operator
25+
mapping(bytes32 keyHash => address operator) internal _keyHashToOperator;
26+
2427
// Construction
2528

2629
constructor(
@@ -34,5 +37,5 @@ abstract contract KeyRegistrarStorage is IKeyRegistrar {
3437
* variables without shifting down storage in the inheritance chain.
3538
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
3639
*/
37-
uint256[47] private __gap;
40+
uint256[46] private __gap;
3841
}

src/test/unit/KeyRegistrarUnit.t.sol

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,11 @@ contract KeyRegistrarUnitTests_registerKey_ECDSA is KeyRegistrarUnitTests {
365365

366366
address storedAddress = keyRegistrar.getECDSAAddress(operatorSet, operator1);
367367
assertEq(storedAddress, ecdsaAddress1);
368+
369+
// Verify getOperatorFromSigningKey returns the correct operator and registration status
370+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey1);
371+
assertEq(retrievedOperator, operator1);
372+
assertTrue(isReg);
368373
}
369374
}
370375

@@ -534,6 +539,11 @@ contract KeyRegistrarUnitTests_registerKey_BN254 is KeyRegistrarUnitTests {
534539
assertEq(storedG2.X[1], bn254G2Key2.X[1]);
535540
assertEq(storedG2.Y[0], bn254G2Key2.Y[0]);
536541
assertEq(storedG2.Y[1], bn254G2Key2.Y[1]);
542+
543+
// Verify getOperatorFromSigningKey returns the correct operator and registration status
544+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, encodedKey);
545+
assertEq(retrievedOperator, operator1);
546+
assertTrue(isReg);
537547
}
538548

539549
function test_registerBN254Key() public {
@@ -557,6 +567,11 @@ contract KeyRegistrarUnitTests_registerKey_BN254 is KeyRegistrarUnitTests {
557567
assertEq(storedG2.X[1], bn254G2Key1.X[1]);
558568
assertEq(storedG2.Y[0], bn254G2Key1.Y[0]);
559569
assertEq(storedG2.Y[1], bn254G2Key1.Y[1]);
570+
571+
// Verify getOperatorFromSigningKey returns the correct operator and registration status
572+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, bn254Key1);
573+
assertEq(retrievedOperator, operator1);
574+
assertTrue(isReg);
560575
}
561576
}
562577

@@ -840,6 +855,154 @@ contract KeyRegistrarUnitTests_ViewFunctions is KeyRegistrarUnitTests {
840855
CurveType curveType = keyRegistrar.getOperatorSetCurveType(operatorSet);
841856
assertEq(uint8(curveType), uint8(CurveType.NONE));
842857
}
858+
859+
function test_getOperatorFromSigningKey_ECDSA() public {
860+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
861+
862+
vm.prank(avs1);
863+
keyRegistrar.configureOperatorSet(operatorSet, CurveType.ECDSA);
864+
865+
// Before registration, should return address(0) and false
866+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey1);
867+
assertEq(retrievedOperator, address(0));
868+
assertFalse(isReg);
869+
870+
// Register key
871+
bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1);
872+
vm.prank(operator1);
873+
keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature);
874+
875+
// After registration, should return the operator and true
876+
(retrievedOperator, isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey1);
877+
assertEq(retrievedOperator, operator1);
878+
assertTrue(isReg);
879+
}
880+
881+
function test_getOperatorFromSigningKey_BN254() public {
882+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
883+
884+
vm.prank(avs1);
885+
keyRegistrar.configureOperatorSet(operatorSet, CurveType.BN254);
886+
887+
// Before registration, should return address(0) and false
888+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, bn254Key1);
889+
assertEq(retrievedOperator, address(0));
890+
assertFalse(isReg);
891+
892+
// Register key
893+
bytes memory signature = _generateBN254Signature(operator1, operatorSet, bn254Key1, bn254PrivKey1);
894+
vm.prank(operator1);
895+
keyRegistrar.registerKey(operator1, operatorSet, bn254Key1, signature);
896+
897+
// After registration, should return the operator and true
898+
// Only pass in the G1 key
899+
bytes memory g1Key = abi.encode(bn254G1Key1.X, bn254G1Key1.Y);
900+
(retrievedOperator, isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, g1Key);
901+
assertEq(retrievedOperator, operator1);
902+
assertTrue(isReg);
903+
}
904+
905+
function test_getOperatorFromSigningKey_multipleOperators() public {
906+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
907+
908+
vm.prank(avs1);
909+
keyRegistrar.configureOperatorSet(operatorSet, CurveType.ECDSA);
910+
911+
// Register different keys for different operators
912+
bytes memory signature1 = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1);
913+
vm.prank(operator1);
914+
keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature1);
915+
916+
bytes memory signature2 = _generateECDSASignature(operator2, operatorSet, ecdsaAddress2, ecdsaPrivKey2);
917+
vm.prank(operator2);
918+
keyRegistrar.registerKey(operator2, operatorSet, ecdsaKey2, signature2);
919+
920+
// Verify each key returns the correct operator and registration status
921+
(address retrievedOperator1, bool isReg1) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey1);
922+
assertEq(retrievedOperator1, operator1);
923+
assertTrue(isReg1);
924+
925+
(address retrievedOperator2, bool isReg2) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey2);
926+
assertEq(retrievedOperator2, operator2);
927+
assertTrue(isReg2);
928+
}
929+
930+
function test_getOperatorFromSigningKey_sameKeyDifferentOperatorSets() public {
931+
OperatorSet memory operatorSet1 = _createOperatorSet(avs1, 0);
932+
OperatorSet memory operatorSet2 = _createOperatorSet(avs1, 1);
933+
934+
vm.startPrank(avs1);
935+
keyRegistrar.configureOperatorSet(operatorSet1, CurveType.ECDSA);
936+
keyRegistrar.configureOperatorSet(operatorSet2, CurveType.ECDSA);
937+
vm.stopPrank();
938+
939+
// Register same operator with same key in first operator set
940+
bytes memory signature = _generateECDSASignature(operator1, operatorSet1, ecdsaAddress1, ecdsaPrivKey1);
941+
vm.prank(operator1);
942+
keyRegistrar.registerKey(operator1, operatorSet1, ecdsaKey1, signature);
943+
944+
// The same key should return the same operator for both operator sets
945+
// But registration status will differ - true for operatorSet1, false for operatorSet2
946+
(address retrievedOperator1, bool isReg1) = keyRegistrar.getOperatorFromSigningKey(operatorSet1, ecdsaKey1);
947+
assertEq(retrievedOperator1, operator1);
948+
assertTrue(isReg1); // Registered in operatorSet1
949+
950+
(address retrievedOperator2, bool isReg2) = keyRegistrar.getOperatorFromSigningKey(operatorSet2, ecdsaKey1);
951+
assertEq(retrievedOperator2, operator1);
952+
assertFalse(isReg2); // NOT registered in operatorSet2
953+
}
954+
955+
function test_getOperatorFromSigningKey_afterDeregistration() public {
956+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
957+
958+
vm.prank(avs1);
959+
keyRegistrar.configureOperatorSet(operatorSet, CurveType.ECDSA);
960+
961+
// Register key
962+
bytes memory signature = _generateECDSASignature(operator1, operatorSet, ecdsaAddress1, ecdsaPrivKey1);
963+
vm.prank(operator1);
964+
keyRegistrar.registerKey(operator1, operatorSet, ecdsaKey1, signature);
965+
966+
// Verify registration
967+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey1);
968+
assertEq(retrievedOperator, operator1);
969+
assertTrue(isReg);
970+
971+
// Deregister
972+
allocationManagerMock.setIsOperatorSlashable(operator1, operatorSet, false);
973+
vm.prank(operator1);
974+
keyRegistrar.deregisterKey(operator1, operatorSet);
975+
976+
// After deregistration, the key should still map to the operator (global registry persists)
977+
// but the registration status should be false
978+
(retrievedOperator, isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, ecdsaKey1);
979+
assertEq(retrievedOperator, operator1);
980+
assertFalse(isReg);
981+
}
982+
983+
function test_getOperatorFromSigningKey_nonExistentKey() public {
984+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
985+
986+
vm.prank(avs1);
987+
keyRegistrar.configureOperatorSet(operatorSet, CurveType.ECDSA);
988+
989+
// Query for a key that was never registered
990+
bytes memory nonExistentKey = abi.encodePacked(address(0xdeadbeef));
991+
(address retrievedOperator, bool isReg) = keyRegistrar.getOperatorFromSigningKey(operatorSet, nonExistentKey);
992+
assertEq(retrievedOperator, address(0));
993+
assertFalse(isReg);
994+
}
995+
996+
function test_getOperatorFromSigningKey_revertUnconfiguredOperatorSet() public {
997+
OperatorSet memory operatorSet = _createOperatorSet(avs1, DEFAULT_OPERATOR_SET_ID);
998+
999+
// Don't configure the operator set - it will have CurveType.NONE
1000+
bytes memory someKey = abi.encodePacked(address(0xdeadbeef));
1001+
1002+
// This should revert because the operator set is not configured
1003+
vm.expectRevert();
1004+
keyRegistrar.getOperatorFromSigningKey(operatorSet, someKey);
1005+
}
8431006
}
8441007

8451008
/**

0 commit comments

Comments
 (0)