Skip to content

Feat: generate parameters for checkSignatures by referencing OperatorStateRetriever on-chain #455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 38 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d4fff29
feat: getNonSignerStakesAndSignature
rubydusa Mar 11, 2025
f1186b6
chore: add BN256G2
rubydusa Mar 11, 2025
55ef0ed
refactor: BN256G2 ^0.4.24 -> ^0.8.27
rubydusa Mar 11, 2025
595b824
fix: compute g2 apk
rubydusa Mar 11, 2025
b322318
test: inital test of getNonSignerStakesAndSignature
hudsonhrh Mar 14, 2025
d050b75
test: getNonSignerStakesAndSignature tests now use real G2 points
hudsonhrh Mar 14, 2025
cd0de93
test: added tests for diffferent revert scenariois
hudsonhrh Mar 14, 2025
89aac24
fix: _makeG2Point comments
rubydusa Mar 15, 2025
f65636d
chore: use G2 coordinates from BN254
rubydusa Mar 15, 2025
1bb0c3f
fix: compute quorum APKs by timestamp
rubydusa Mar 15, 2025
22b355a
refactor: safe gaurd against invalid sigmas
rubydusa Mar 15, 2025
22810cd
test: getNonSignerStakesAndSignature changing Quorum set
rubydusa Mar 15, 2025
973e278
fix: convert blockNumber to uint32
rubydusa Mar 15, 2025
2d7c073
chore: clarify operatorIds -> signingOperatorIds
rubydusa Mar 17, 2025
f310f09
Merge branch 'latest-dev' into on-chain-check-signatures
rubydusa Mar 18, 2025
76ffef7
chore: delete non-sensical no signers test
rubydusa Mar 18, 2025
e758ffa
chore: add natspec to new functions in OperatorStateRetriever
rubydusa Mar 26, 2025
021d32c
docs: fix capitalization of comments and natspec for getNonSignerStak…
bagelface Mar 27, 2025
a294309
revert comment changes
hudsonhrh Mar 27, 2025
e0bd81f
Update isOnCurve function as pure
Mar 30, 2025
c3e1ba1
docs: fix "getNonSignerStakesAndSignature" natspec comment
diterra-code Mar 30, 2025
4f6b395
fix: formatting
cathschmidt Apr 2, 2025
beffb0b
fix: clean up
Astodialo Apr 5, 2025
59a4b02
Merge branch 'dev' of https://github.com/Layr-Labs/eigenlayer-middlew…
rubydusa Apr 6, 2025
463166c
fix: type in `_computeG1APK` natspec
rubydusa Apr 6, 2025
0530e77
fix: incorrect curve equation in doc comment
rubydusa Apr 6, 2025
2899e89
fix: typo
rubydusa Apr 9, 2025
235a7d1
chore: add comment on g2 apk loop
rubydusa Apr 9, 2025
b07ad3a
Merge branch 'dev' into on-chain-check-signatures
RonTuretzky Apr 9, 2025
ff9b6b9
chore: explain `InvalidSigma()`
rubydusa Apr 10, 2025
dd8aaff
chore: add @dev comment about sigma
rubydusa Apr 10, 2025
1e57364
fix: check for correctness of indices and pubkeys in tests
rubydusa Apr 10, 2025
32c57d9
chore: forge fmt for CI
rubydusa Apr 10, 2025
3cf04f3
refactor: moving lib
RonTuretzky Apr 22, 2025
76a8c5c
chore: modularize bls operator state retriever
ypatil12 Apr 29, 2025
2230a52
chore: format
ypatil12 Apr 29, 2025
28c3384
chore: clenanup RPCs
ypatil12 Apr 29, 2025
bae50c0
chore: revert naming
ypatil12 Apr 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ on:
env:
FOUNDRY_PROFILE: ci
RPC_MAINNET: ${{ secrets.RPC_MAINNET }}
RPC_HOLESKY: ${{ secrets.RPC_HOLESKY }}
HOLESKY_RPC_URL: ${{ secrets.HOLESKY_RPC_URL }}
CHAIN_ID: ${{ secrets.CHAIN_ID }}

Expand Down
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@

[rpc_endpoints]
mainnet = "${RPC_MAINNET}"
holesky = "${RPC_HOLESKY}"
holesky = "${HOLESKY_RPC_URL}"

[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
Expand Down
4 changes: 2 additions & 2 deletions src/OperatorStateRetriever.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@ contract OperatorStateRetriever {
ISlashingRegistryCoordinator registryCoordinator,
uint32 referenceBlockNumber,
bytes calldata quorumNumbers,
bytes32[] calldata nonSignerOperatorIds
) external view returns (CheckSignaturesIndices memory) {
bytes32[] memory nonSignerOperatorIds
) public view returns (CheckSignaturesIndices memory) {
IStakeRegistry stakeRegistry = registryCoordinator.stakeRegistry();
CheckSignaturesIndices memory checkSignaturesIndices;

Expand Down
207 changes: 207 additions & 0 deletions src/unaudited/BLSSigCheckOperatorStateRetriever.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {IBLSApkRegistry} from "../interfaces/IBLSApkRegistry.sol";
import {IBLSSignatureCheckerTypes} from "../interfaces/IBLSSignatureChecker.sol";
import {IStakeRegistry} from "../interfaces/IStakeRegistry.sol";
import {IIndexRegistry} from "../interfaces/IIndexRegistry.sol";
import {ISlashingRegistryCoordinator} from "../interfaces/ISlashingRegistryCoordinator.sol";
import {BitmapUtils} from "../libraries/BitmapUtils.sol";
import {BN254} from "../libraries/BN254.sol";
import {BN256G2} from "./BN256G2.sol";
import {OperatorStateRetriever} from "../OperatorStateRetriever.sol";

/**
* @title BLSSigCheckOperatorStateRetriever with view functions that allow to retrieve the state of an AVSs registry system.
* @dev This contract inherits from OperatorStateRetriever and adds the getNonSignerStakesAndSignature function.
* @author Bread coop
*/
contract BLSSigCheckOperatorStateRetriever is OperatorStateRetriever {
/// @dev Thrown when the signature is not on the curve.
error InvalidSigma();
// avoid stack too deep

struct GetNonSignerStakesAndSignatureMemory {
BN254.G1Point[] quorumApks;
BN254.G2Point apkG2;
IIndexRegistry indexRegistry;
IBLSApkRegistry blsApkRegistry;
bytes32[] signingOperatorIds;
}

/**
* @notice Returns the stakes and signature information for non-signing operators in specified quorums
* @param registryCoordinator The registry coordinator contract to fetch operator information from
* @param quorumNumbers Array of quorum numbers to check for non-signers
* @param sigma The aggregate BLS signature to verify
* @param operators Array of operator addresses that signed the message
* @param blockNumber Is the block number to get the indices for
* @return NonSignerStakesAndSignature Struct containing:
* - nonSignerQuorumBitmapIndices: Indices for retrieving quorum bitmaps of non-signers
* - nonSignerPubkeys: BLS public keys of operators that did not sign
* - quorumApks: Aggregate public keys for each quorum
* - apkG2: Aggregate public key of all signing operators in G2
* - sigma: The provided signature
* - quorumApkIndices: Indices for retrieving quorum APKs
* - totalStakeIndices: Indices for retrieving total stake info
* - nonSignerStakeIndices: Indices for retrieving non-signer stake info
* @dev Computes the indices of operators that did not sign across all specified quorums
* @dev This function does not validate the signature matches the provided parameters, only that it's in a valid format
*/
function getNonSignerStakesAndSignature(
ISlashingRegistryCoordinator registryCoordinator,
bytes calldata quorumNumbers,
BN254.G1Point calldata sigma,
address[] calldata operators,
uint32 blockNumber
) external view returns (IBLSSignatureCheckerTypes.NonSignerStakesAndSignature memory) {
GetNonSignerStakesAndSignatureMemory memory m;
m.quorumApks = new BN254.G1Point[](quorumNumbers.length);
m.indexRegistry = registryCoordinator.indexRegistry();
m.blsApkRegistry = registryCoordinator.blsApkRegistry();

// Safe guard AVSs from generating NonSignerStakesAndSignature with invalid sigma
require(_isOnCurve(sigma), InvalidSigma());

// Compute the g2 APK of the signing operator set
m.signingOperatorIds = new bytes32[](operators.length);
for (uint256 i = 0; i < operators.length; i++) {
m.signingOperatorIds[i] = registryCoordinator.getOperatorId(operators[i]);
BN254.G2Point memory operatorG2Pk = m.blsApkRegistry.getOperatorPubkeyG2(operators[i]);
(m.apkG2.X[1], m.apkG2.X[0], m.apkG2.Y[1], m.apkG2.Y[0]) = BN256G2.ECTwistAdd(
m.apkG2.X[1],
m.apkG2.X[0],
m.apkG2.Y[1],
m.apkG2.Y[0],
operatorG2Pk.X[1],
operatorG2Pk.X[0],
operatorG2Pk.Y[1],
operatorG2Pk.Y[0]
);
}

// Extra scope for stack limit
{
uint32[] memory signingOperatorQuorumBitmapIndices = registryCoordinator
.getQuorumBitmapIndicesAtBlockNumber(blockNumber, m.signingOperatorIds);
// Check that all operators are registered (this is like the check in getCheckSignaturesIndices, but we check against _signing_ operators)
for (uint256 i = 0; i < operators.length; i++) {
uint192 signingOperatorQuorumBitmap = registryCoordinator
.getQuorumBitmapAtBlockNumberByIndex(
m.signingOperatorIds[i], blockNumber, signingOperatorQuorumBitmapIndices[i]
);
require(signingOperatorQuorumBitmap != 0, OperatorNotRegistered());
}
}

// We use this as a dynamic array
uint256 nonSignerOperatorsCount = 0;
bytes32[] memory nonSignerOperatorIds = new bytes32[](16);
// For every quorum
for (uint256 i = 0; i < quorumNumbers.length; i++) {
bytes32[] memory operatorIdsInQuorum =
m.indexRegistry.getOperatorListAtBlockNumber(uint8(quorumNumbers[i]), blockNumber);
// Operator IDs are computed from the hash of the BLS public keys, so an operatorId's public key can't change over time
// This lets us compute the APK at the given block number
m.quorumApks[i] = _computeG1Apk(registryCoordinator, operatorIdsInQuorum);
// We check for every operator in the quorum
for (uint256 j = 0; j < operatorIdsInQuorum.length; j++) {
bool isNewNonSigner = true;
// If it is in the signing operators array
for (uint256 k = 0; k < m.signingOperatorIds.length; k++) {
if (operatorIdsInQuorum[j] == m.signingOperatorIds[k]) {
isNewNonSigner = false;
break;
}
}
// Or already in the non-signing operators array
for (uint256 l = 0; l < nonSignerOperatorsCount; l++) {
if (nonSignerOperatorIds[l] == operatorIdsInQuorum[j]) {
isNewNonSigner = false;
break;
}
}
// And if not, we add it to the non-signing operators array
if (isNewNonSigner) {
// If we are at the end of the array, we need to resize it
if (nonSignerOperatorsCount == nonSignerOperatorIds.length) {
uint256 newCapacity = nonSignerOperatorIds.length * 2;
bytes32[] memory newNonSignerOperatorIds = new bytes32[](newCapacity);
for (uint256 l = 0; l < nonSignerOperatorIds.length; l++) {
newNonSignerOperatorIds[l] = nonSignerOperatorIds[l];
}
nonSignerOperatorIds = newNonSignerOperatorIds;
}

nonSignerOperatorIds[nonSignerOperatorsCount] = operatorIdsInQuorum[j];
nonSignerOperatorsCount++;
}
}
}

// Trim the nonSignerOperatorIds array to the actual count
bytes32[] memory trimmedNonSignerOperatorIds = new bytes32[](nonSignerOperatorsCount);
for (uint256 i = 0; i < nonSignerOperatorsCount; i++) {
trimmedNonSignerOperatorIds[i] = nonSignerOperatorIds[i];
}

BN254.G1Point[] memory nonSignerPubkeys = new BN254.G1Point[](nonSignerOperatorsCount);
for (uint256 i = 0; i < nonSignerOperatorsCount; i++) {
address nonSignerOperator =
registryCoordinator.getOperatorFromId(trimmedNonSignerOperatorIds[i]);
(nonSignerPubkeys[i],) = m.blsApkRegistry.getRegisteredPubkey(nonSignerOperator);
}

CheckSignaturesIndices memory checkSignaturesIndices = getCheckSignaturesIndices(
registryCoordinator, blockNumber, quorumNumbers, trimmedNonSignerOperatorIds
);
return IBLSSignatureCheckerTypes.NonSignerStakesAndSignature({
nonSignerQuorumBitmapIndices: checkSignaturesIndices.nonSignerQuorumBitmapIndices,
nonSignerPubkeys: nonSignerPubkeys,
quorumApks: m.quorumApks,
apkG2: m.apkG2,
sigma: sigma,
quorumApkIndices: checkSignaturesIndices.quorumApkIndices,
totalStakeIndices: checkSignaturesIndices.totalStakeIndices,
nonSignerStakeIndices: checkSignaturesIndices.nonSignerStakeIndices
});
}

/**
* @notice Computes the aggregate public key (APK) in G1 for a list of operators
* @dev Aggregates the individual G1 public keys of operators by adding them together
* @param registryCoordinator The registry coordinator contract to fetch operator info from
* @param operatorIds Array of operator IDs to compute the aggregate key for
* @return The aggregate public key as a G1 point, computed by summing individual operator pubkeys
*/
function _computeG1Apk(
ISlashingRegistryCoordinator registryCoordinator,
bytes32[] memory operatorIds
) internal view returns (BN254.G1Point memory) {
BN254.G1Point memory apk = BN254.G1Point(0, 0);
IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
for (uint256 i = 0; i < operatorIds.length; i++) {
address operator = registryCoordinator.getOperatorFromId(operatorIds[i]);
BN254.G1Point memory operatorPk;
(operatorPk.X, operatorPk.Y) = blsApkRegistry.operatorToPubkey(operator);
apk = BN254.plus(apk, operatorPk);
}
return apk;
}

/**
* @notice Checks if a point lies on the BN254 elliptic curve
* @dev The curve equation is y^2 = x^3 + 3 (mod p)
* @param p The point to check, in G1
* @return true if the point lies on the curve, false otherwise
*/
function _isOnCurve(
BN254.G1Point memory p
) internal pure returns (bool) {
uint256 y2 = mulmod(p.Y, p.Y, BN254.FP_MODULUS);
uint256 x2 = mulmod(p.X, p.X, BN254.FP_MODULUS);
uint256 x3 = mulmod(p.X, x2, BN254.FP_MODULUS);
uint256 rhs = addmod(x3, 3, BN254.FP_MODULUS);
return y2 == rhs;
}
}
Loading
Loading