Skip to content

Commit 08e16bd

Browse files
ypatil12rubydusahudsonhrhbagelfaceSara
authored
Feat: generate parameters for checkSignatures by referencing OperatorStateRetriever on-chain (#461)
Motivation: Computation of the parameters needed for signature verification is typically done through the Eigen go or rust services. However, the rust services are not reliable at the current moment and some projects might want to have signature verifications without having to rely on these services. This PR aims to make the computation of the NonSignerStakesAndSignature parameter easier and accessible through implementing it in OperatorStateRetriever contract Modifications: added getNonSignerStakesAndSignature to OperatorStateRetriever added BN256G2 solidity library dependency Result: Computing the data necessary for performing signature verification is more accessible --------- Co-authored-by: rubydusa <[email protected]> Co-authored-by: hudsonhrh <[email protected]> Co-authored-by: bagelface <[email protected]> Co-authored-by: Hudson Headley <[email protected]> Co-authored-by: Sara <[email protected]> Co-authored-by: 0xR-code <[email protected]> Co-authored-by: cathschmidt <[email protected]> Co-authored-by: astodialo <[email protected]> Co-authored-by: RonTuretzky <[email protected]> Co-authored-by: rubydusa <[email protected]> Co-authored-by: Tomás Grüner <[email protected]> Co-authored-by: Ron Turetzky <[email protected]>
1 parent f5adbca commit 08e16bd

File tree

8 files changed

+1249
-4
lines changed

8 files changed

+1249
-4
lines changed

.github/workflows/foundry.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ on:
1212
env:
1313
FOUNDRY_PROFILE: ci
1414
RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
15-
RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }}
1615
HOLESKY_RPC_URL: ${{ secrets.HOLESKY_RPC_URL }}
1716
CHAIN_ID: ${{ secrets.CHAIN_ID }}
1817

foundry.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109

110110
[rpc_endpoints]
111111
mainnet = "${RPC_MAINNET}"
112-
holesky = "${RPC_HOLESKY}"
112+
holesky = "${HOLESKY_RPC_URL}"
113113

114114
[etherscan]
115115
mainnet = { key = "${ETHERSCAN_API_KEY}" }

src/OperatorStateRetriever.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ contract OperatorStateRetriever {
112112
ISlashingRegistryCoordinator registryCoordinator,
113113
uint32 referenceBlockNumber,
114114
bytes calldata quorumNumbers,
115-
bytes32[] calldata nonSignerOperatorIds
116-
) external view returns (CheckSignaturesIndices memory) {
115+
bytes32[] memory nonSignerOperatorIds
116+
) public view returns (CheckSignaturesIndices memory) {
117117
IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
118118
CheckSignaturesIndices memory checkSignaturesIndices;
119119

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.27;
3+
4+
import {IBLSApkRegistry} from "../interfaces/IBLSApkRegistry.sol";
5+
import {IBLSSignatureCheckerTypes} from "../interfaces/IBLSSignatureChecker.sol";
6+
import {IStakeRegistry} from "../interfaces/IStakeRegistry.sol";
7+
import {IIndexRegistry} from "../interfaces/IIndexRegistry.sol";
8+
import {ISlashingRegistryCoordinator} from "../interfaces/ISlashingRegistryCoordinator.sol";
9+
import {BitmapUtils} from "../libraries/BitmapUtils.sol";
10+
import {BN254} from "../libraries/BN254.sol";
11+
import {BN256G2} from "./BN256G2.sol";
12+
import {OperatorStateRetriever} from "../OperatorStateRetriever.sol";
13+
import {ECUtils} from "./ECUtils.sol";
14+
15+
/**
16+
* @title BLSSigCheckOperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system.
17+
* @dev This contract inherits from OperatorStateRetriever and adds the getNonSignerStakesAndSignature function.
18+
* @author Bread coop
19+
*/
20+
contract BLSSigCheckOperatorStateRetriever is OperatorStateRetriever {
21+
using ECUtils for BN254.G1Point;
22+
using BN254 for BN254.G1Point;
23+
using BitmapUtils for uint256;
24+
25+
/// @dev Thrown when the signature is not on the curve.
26+
error InvalidSigma();
27+
// avoid stack too deep
28+
29+
struct GetNonSignerStakesAndSignatureMemory {
30+
BN254.G1Point[] quorumApks;
31+
BN254.G2Point apkG2;
32+
IIndexRegistry indexRegistry;
33+
IBLSApkRegistry blsApkRegistry;
34+
bytes32[] signingOperatorIds;
35+
}
36+
37+
/**
38+
* @notice Returns the stakes and signature information for non-signing operators in specified quorums
39+
* @param registryCoordinator The registry coordinator contract to fetch operator information from
40+
* @param quorumNumbers Array of quorum numbers to check for non-signers
41+
* @param sigma The aggregate BLS signature to verify
42+
* @param operators Array of operator addresses that signed the message
43+
* @param blockNumber Is the block number to get the indices for
44+
* @return NonSignerStakesAndSignature Struct containing:
45+
* - nonSignerQuorumBitmapIndices: Indices for retrieving quorum bitmaps of non-signers
46+
* - nonSignerPubkeys: BLS public keys of operators that did not sign
47+
* - quorumApks: Aggregate public keys for each quorum
48+
* - apkG2: Aggregate public key of all signing operators in G2
49+
* - sigma: The provided signature
50+
* - quorumApkIndices: Indices for retrieving quorum APKs
51+
* - totalStakeIndices: Indices for retrieving total stake info
52+
* - nonSignerStakeIndices: Indices for retrieving non-signer stake info
53+
* @dev Computes the indices of operators that did not sign across all specified quorums
54+
* @dev This function does not validate the signature matches the provided parameters, only that it's in a valid format
55+
*/
56+
function getNonSignerStakesAndSignature(
57+
ISlashingRegistryCoordinator registryCoordinator,
58+
bytes calldata quorumNumbers,
59+
BN254.G1Point calldata sigma,
60+
address[] calldata operators,
61+
uint32 blockNumber
62+
) external view returns (IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory) {
63+
GetNonSignerStakesAndSignatureMemory memory m;
64+
m.quorumApks = new BN254.G1Point[](quorumNumbers.length);
65+
m.indexRegistry = registryCoordinator.indexRegistry();
66+
m.blsApkRegistry = registryCoordinator.blsApkRegistry();
67+
68+
// Safe guard AVSs from generating NonSignerStakesAndSignature with invalid sigma
69+
require(sigma.isOnCurve(), InvalidSigma());
70+
71+
// Compute the g2 APK of the signing operator set
72+
m.signingOperatorIds = new bytes32[](operators.length);
73+
for (uint256 i = 0; i < operators.length; i++) {
74+
m.signingOperatorIds[i] = registryCoordinator.getOperatorId(operators[i]);
75+
BN254.G2Point memory operatorG2Pk = m.blsApkRegistry.getOperatorPubkeyG2(operators[i]);
76+
(m.apkG2.X[1], m.apkG2.X[0], m.apkG2.Y[1], m.apkG2.Y[0]) = BN256G2.ECTwistAdd(
77+
m.apkG2.X[1],
78+
m.apkG2.X[0],
79+
m.apkG2.Y[1],
80+
m.apkG2.Y[0],
81+
operatorG2Pk.X[1],
82+
operatorG2Pk.X[0],
83+
operatorG2Pk.Y[1],
84+
operatorG2Pk.Y[0]
85+
);
86+
}
87+
88+
// Extra scope for stack limit
89+
{
90+
uint32[] memory signingOperatorQuorumBitmapIndices = registryCoordinator
91+
.getQuorumBitmapIndicesAtBlockNumber(blockNumber, m.signingOperatorIds);
92+
uint256 bitmap = BitmapUtils.orderedBytesArrayToBitmap(quorumNumbers);
93+
// Check that all operators are registered (this is like the check in getCheckSignaturesIndices, but we check against _signing_ operators)
94+
for (uint256 i = 0; i < operators.length; i++) {
95+
uint192 signingOperatorQuorumBitmap = registryCoordinator
96+
.getQuorumBitmapAtBlockNumberByIndex(
97+
m.signingOperatorIds[i], blockNumber, signingOperatorQuorumBitmapIndices[i]
98+
);
99+
require(
100+
!uint256(signingOperatorQuorumBitmap).noBitsInCommon(bitmap),
101+
OperatorNotRegistered()
102+
);
103+
}
104+
}
105+
106+
// We use this as a dynamic array
107+
uint256 nonSignerOperatorsCount = 0;
108+
bytes32[] memory nonSignerOperatorIds = new bytes32[](16);
109+
// For every quorum
110+
for (uint256 i = 0; i < quorumNumbers.length; i++) {
111+
bytes32[] memory operatorIdsInQuorum =
112+
m.indexRegistry.getOperatorListAtBlockNumber(uint8(quorumNumbers[i]), blockNumber);
113+
// Operator IDs are computed from the hash of the BLS public keys, so an operatorId's public key can't change over time
114+
// This lets us compute the APK at the given block number
115+
m.quorumApks[i] = _computeG1Apk(registryCoordinator, operatorIdsInQuorum);
116+
// We check for every operator in the quorum
117+
for (uint256 j = 0; j < operatorIdsInQuorum.length; j++) {
118+
bool isNewNonSigner = true;
119+
// If it is in the signing operators array
120+
for (uint256 k = 0; k < m.signingOperatorIds.length; k++) {
121+
if (operatorIdsInQuorum[j] == m.signingOperatorIds[k]) {
122+
isNewNonSigner = false;
123+
break;
124+
}
125+
}
126+
// Or already in the non-signing operators array
127+
for (uint256 l = 0; l < nonSignerOperatorsCount; l++) {
128+
if (nonSignerOperatorIds[l] == operatorIdsInQuorum[j]) {
129+
isNewNonSigner = false;
130+
break;
131+
}
132+
}
133+
// And if not, we add it to the non-signing operators array
134+
if (isNewNonSigner) {
135+
// If we are at the end of the array, we need to resize it
136+
if (nonSignerOperatorsCount == nonSignerOperatorIds.length) {
137+
uint256 newCapacity = nonSignerOperatorIds.length * 2;
138+
bytes32[] memory newNonSignerOperatorIds = new bytes32[](newCapacity);
139+
for (uint256 l = 0; l < nonSignerOperatorIds.length; l++) {
140+
newNonSignerOperatorIds[l] = nonSignerOperatorIds[l];
141+
}
142+
nonSignerOperatorIds = newNonSignerOperatorIds;
143+
}
144+
145+
nonSignerOperatorIds[nonSignerOperatorsCount] = operatorIdsInQuorum[j];
146+
nonSignerOperatorsCount++;
147+
}
148+
}
149+
}
150+
151+
// Trim the nonSignerOperatorIds array to the actual count
152+
bytes32[] memory trimmedNonSignerOperatorIds = new bytes32[](nonSignerOperatorsCount);
153+
BN254.G1Point[] memory nonSignerPubkeys = new BN254.G1Point[](nonSignerOperatorsCount);
154+
for (uint256 i = 0; i < nonSignerOperatorsCount; i++) {
155+
trimmedNonSignerOperatorIds[i] = nonSignerOperatorIds[i];
156+
address nonSignerOperator =
157+
registryCoordinator.getOperatorFromId(trimmedNonSignerOperatorIds[i]);
158+
(nonSignerPubkeys[i],) = m.blsApkRegistry.getRegisteredPubkey(nonSignerOperator);
159+
}
160+
161+
CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices(
162+
registryCoordinator, blockNumber, quorumNumbers, trimmedNonSignerOperatorIds
163+
);
164+
return IBLSSignatureCheckerTypes.NonSignerStakesAndSignature({
165+
nonSignerQuorumBitmapIndices: checkSignaturesIndices.nonSignerQuorumBitmapIndices,
166+
nonSignerPubkeys: nonSignerPubkeys,
167+
quorumApks: m.quorumApks,
168+
apkG2: m.apkG2,
169+
sigma: sigma,
170+
quorumApkIndices: checkSignaturesIndices.quorumApkIndices,
171+
totalStakeIndices: checkSignaturesIndices.totalStakeIndices,
172+
nonSignerStakeIndices: checkSignaturesIndices.nonSignerStakeIndices
173+
});
174+
}
175+
176+
/**
177+
* @notice Computes the aggregate public key (APK) in G1 for a list of operators
178+
* @dev Aggregates the individual G1 public keys of operators by adding them together
179+
* @param registryCoordinator The registry coordinator contract to fetch operator info from
180+
* @param operatorIds Array of operator IDs to compute the aggregate key for
181+
* @return The aggregate public key as a G1 point, computed by summing individual operator pubkeys
182+
*/
183+
function _computeG1Apk(
184+
ISlashingRegistryCoordinator registryCoordinator,
185+
bytes32[] memory operatorIds
186+
) internal view returns (BN254.G1Point memory) {
187+
BN254.G1Point memory apk = BN254.G1Point(0, 0);
188+
IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
189+
for (uint256 i = 0; i < operatorIds.length; i++) {
190+
address operator = registryCoordinator.getOperatorFromId(operatorIds[i]);
191+
BN254.G1Point memory operatorPk;
192+
(operatorPk.X, operatorPk.Y) = blsApkRegistry.operatorToPubkey(operator);
193+
apk = apk.plus(operatorPk);
194+
}
195+
return apk;
196+
}
197+
}

0 commit comments

Comments
 (0)