Skip to content

Commit 84fb838

Browse files
authored
Merge pull request #460 from BreadchainCoop/on-chain-check-signatures
**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
2 parents f5adbca + bae50c0 commit 84fb838

File tree

7 files changed

+1183
-4
lines changed

7 files changed

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

0 commit comments

Comments
 (0)