Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/NewtonProverTaskManager.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {INewtonProverTaskManager} from "./interfaces/INewtonProverTaskManager.sol";
import {TaskManagerStorage} from "./middlewares/TaskManagerStorage.sol";
import {NewtonMessage} from "./core/NewtonMessage.sol";
import {TaskLib} from "./libraries/TaskLib.sol";
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import {ChallengeVerifier} from "./middlewares/ChallengeVerifier.sol";
import {AttestationValidator} from "./middlewares/AttestationValidator.sol";
import {IPauserRegistry} from "@eigenlayer/contracts/interfaces/IPauserRegistry.sol";
import {OperatorRegistry} from "./middlewares/OperatorRegistry.sol";
import {OperatorVerifierLib} from "./libraries/OperatorVerifierLib.sol";
import {ISlashingRegistryCoordinator} from
"@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol";
contract NewtonProverTaskManager is TaskManagerStorage {
/* CUSTOM ERRORS */
error OnlyAggregator();
error OnlyTaskGenerator();
error AttestationHashMismatch();
error AttestationExpired();
error AttestationAlreadySpent();
error InvalidAggregatorAddress();
/* MODIFIERS */
modifier onlyAggregator() {
require(msg.sender == aggregator, OnlyAggregator());
_;
}
// onlyTaskGenerator is used to restrict createNewTask from only being called by a permissioned entity
// in a real world scenario, this would be removed by instead making createNewTask a payable function
modifier onlyTaskGenerator() {
require(OperatorRegistry(operatorRegistry).isTaskGenerator(msg.sender), OnlyTaskGenerator());
_;
}
// onlyAttestationClient is used to restrict validateAttestation from only being called by the correct policy client
modifier onlyAttestationClient(
NewtonMessage.Attestation calldata attestation
) {
TaskLib.onlyAttestationClient(attestation);
_;
}
modifier onlyValidTaskResponse(Task calldata task, TaskResponse calldata taskResponse) {
TaskLib.sanityCheckTaskResponse(
task, taskResponse, uint32(block.number), taskResponseWindowBlock
);
_;
}
constructor(
OperatorRegistry _operatorRegistry,
IPauserRegistry _pauserRegistry
) TaskManagerStorage(_operatorRegistry, _pauserRegistry) {}
function initialize(
address initialOwner,
address _aggregator,
address _serviceManager,
address _operatorRegistry,
address _challengeVerifier,
address _attestationValidator,
uint32 _taskResponseWindowBlock
) public initializer {
_transferOwnership(initialOwner);
aggregator = _aggregator;
serviceManager = _serviceManager;
operatorRegistry = _operatorRegistry;
challengeVerifier = _challengeVerifier;
attestationValidator = _attestationValidator;
taskResponseWindowBlock = _taskResponseWindowBlock;
}
function createNewTask(
address policyClient,
NewtonMessage.Intent calldata intent,
NewtonMessage.PolicyTaskData calldata policyTaskData,
bytes calldata quorumNumbers,
uint32 quorumThresholdPercentage
) external onlyTaskGenerator {
INewtonProverTaskManager.Task memory newTask = TaskLib.createTask(
nonce, policyClient, intent, policyTaskData, quorumNumbers, quorumThresholdPercentage
);
allTaskHashes[newTask.taskId] = keccak256(abi.encode(newTask));
emit NewTaskCreated(newTask.taskId, newTask);
unchecked {
++nonce;
}
}
function respondToTask(
Task calldata task,
TaskResponse calldata taskResponse,
NonSignerStakesAndSignature memory nonSignerStakesAndSignature
) external onlyAggregator onlyValidTaskResponse(task, taskResponse) {
bytes32 taskId = taskResponse.taskId;
require(keccak256(abi.encode(task)) == allTaskHashes[taskId], TaskLib.TaskMismatch());
require(allTaskResponses[taskId] == bytes32(0), TaskLib.TaskAlreadyResponded());
(, bytes32 hashOfNonSigners) = OperatorVerifierLib.verifyTaskResponseSignatures(
task,
taskResponse,
nonSignerStakesAndSignature,
ISlashingRegistryCoordinator(operatorRegistry),
this.checkSignatures
);
uint32 referenceBlock = uint32(block.number);
uint32 responseExpireBlock = referenceBlock + task.policyConfig.expireAfter;
ResponseCertificate memory responseCertificate = ResponseCertificate(
referenceBlock, hashOfNonSigners, nonSignerStakesAndSignature, responseExpireBlock
);
allTaskResponses[taskId] = keccak256(abi.encode(taskResponse, responseCertificate));
ChallengeVerifier(challengeVerifier).setTaskHashes(taskId, allTaskHashes[taskId]);
ChallengeVerifier(challengeVerifier).setTaskResponses(taskId, allTaskResponses[taskId]);
if (TaskLib.evaluateResult(taskResponse.evaluationResult)) {
AttestationValidator(attestationValidator).createAttestationHash(
taskId,
taskResponse.policyId,
taskResponse.policyClient,
taskResponse.intent,
responseExpireBlock
);
}
emit TaskResponded(taskResponse, responseCertificate);
}
function raiseAndResolveChallenge(
Task calldata task,
TaskResponse calldata taskResponse,
ResponseCertificate calldata responseCertificate,
ChallengeData calldata challenge,
BN254.G1Point[] memory pubkeysOfNonSigningOperators
) external {
bool isChallengeResolved = ChallengeVerifier(challengeVerifier).raiseAndResolveChallenge(
task, taskResponse, responseCertificate, challenge, pubkeysOfNonSigningOperators
);
bytes32 taskId = taskResponse.taskId;
if (isChallengeResolved) {
// Challenged attestation is now invalid
AttestationValidator(attestationValidator).invalidateAttestation(taskId);
emit TaskChallengedSuccessfully(taskId, msg.sender);
} else {
emit TaskChallengedUnsuccessfully(taskId, msg.sender);
}
}
function validateAttestation(
NewtonMessage.Attestation calldata attestation
) external onlyAttestationClient(attestation) returns (bool) {
if (ChallengeVerifier(challengeVerifier).isTaskChallenged(attestation.taskId)) {
return false;
}
bool isAttestationValid =
AttestationValidator(attestationValidator).validateAttestation(attestation);
if (isAttestationValid) {
emit AttestationSpent(attestation.taskId, attestation);
}
return isAttestationValid;
}
function updateAggregator(
address _aggregator
) external onlyOwner {
if (_aggregator == address(0)) revert InvalidAggregatorAddress();
aggregator = _aggregator;
}
function updateTaskResponseWindowBlock(
uint32 _taskResponseWindowBlock
) external onlyOwner {
taskResponseWindowBlock = _taskResponseWindowBlock;
}
}
"
},
"src/interfaces/INewtonProverTaskManager.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import "@eigenlayer-middleware/src/interfaces/IBLSSignatureChecker.sol";
import {NewtonMessage} from "../core/NewtonMessage.sol";
import {INewtonPolicy} from "./INewtonPolicy.sol";
interface INewtonProverTaskManager {
// EVENTS
event NewTaskCreated(bytes32 indexed taskId, Task task);
event TaskResponded(TaskResponse taskResponse, ResponseCertificate responseCertificate);
event TaskChallengedSuccessfully(bytes32 indexed taskId, address indexed challenger);
event TaskChallengedUnsuccessfully(bytes32 indexed taskId, address indexed challenger);
event AttestationSpent(bytes32 indexed taskId, NewtonMessage.Attestation attestation);
// STRUCTS
// task submitter decides on the criteria for a task to be completed
// note that this does not mean the task was "correctly" answered (i.e. the number was proved correctly)
// this is for the challenge logic to verify
// task is completed (and contract will accept its TaskResponse) when each quorumNumbers specified here
// are signed by at least quorumThresholdPercentage of the operators
// note that we set the quorumThresholdPercentage to be the same for all quorumNumbers, but this could be changed
struct Task {
// the unique identifier for the task
bytes32 taskId;
// policy client address
address policyClient;
// policy id
bytes32 policyId;
// the nonce of the task
uint32 nonce;
// the intent of the task
NewtonMessage.Intent intent;
// the policy task data of the task
NewtonMessage.PolicyTaskData policyTaskData;
// policy configuration for the policy program
INewtonPolicy.PolicyConfig policyConfig;
// the block number when the task was created
uint32 taskCreatedBlock;
// the quorum numbers of the task
bytes quorumNumbers;
// the quorum threshold percentage of the task
uint32 quorumThresholdPercentage;
}
// Task response is hashed and signed by operators.
// these signatures are aggregated and sent to the contract as response.
struct TaskResponse {
// Can be obtained by the operator from the event NewTaskCreated.
bytes32 taskId;
// policy client address
address policyClient;
// policy id of the task
bytes32 policyId;
// the policy address of the task
address policyAddress;
// the intent of the task
NewtonMessage.Intent intent;
// Policy evaluation result.
bytes evaluationResult;
}
// Certificate is filled by the protocol contract for each taskResponse signed by operators.
// This Certificate is used by policy clients to attest the validity of policy evaluation result
// during intent execution.
// This certificate is also used by the challenger, who monitors and if invalid, raises challenge
// with zero-knowledge proof of the policy evaluation result discrepancy.
// NOTE: this can be used as an attestation for not just single chain but multi-chain attestation.
struct ResponseCertificate {
// the block number when the response certificate is created
uint32 referenceBlock;
// the hash of the non-signers
bytes32 hashOfNonSigners;
// the non-signers and their stakes
IBLSSignatureChecker.NonSignerStakesAndSignature nonSignerStakesAndSignature;
// the block number when the task response expires
uint32 responseExpireBlock;
}
// Challenge data is submitted by the challenger.
// Contains the proof data and verification key for onchain verification of the policy evaluation result.
// TODO: add support for risc0 zk proofs, and other proof types.
struct ChallengeData {
// Can be obtained by the operator from the event NewTaskCreated.
bytes32 taskId;
// sp1 zk proof to attest the policy evaluation result of the challenger
bytes proof;
// The committed proof output to verify against the task response data.
bytes data;
}
// FUNCTIONS
// NOTE: this function creates new task.
function createNewTask(
address policyClient,
NewtonMessage.Intent calldata intent,
NewtonMessage.PolicyTaskData calldata policyTaskData,
bytes calldata quorumNumbers,
uint32 quorumThresholdPercentage
) external;
// NOTE: this function responds to existing tasks.
function respondToTask(
Task calldata task,
TaskResponse calldata taskResponse,
IBLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature
) external;
// NOTE: this function raises challenge to existing tasks.
function raiseAndResolveChallenge(
Task calldata task,
TaskResponse calldata taskResponse,
ResponseCertificate calldata responseCertificate,
ChallengeData calldata challenge,
BN254.G1Point[] memory pubkeysOfNonSigningOperators
) external;
// NOTE: this function authorizes existing task responses.
function validateAttestation(
NewtonMessage.Attestation calldata attestation
) external returns (bool);
}
"
},
"src/middlewares/TaskManagerStorage.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@eigenlayer/contracts/permissions/Pausable.sol";
import {OperatorRegistry} from "./OperatorRegistry.sol";
import {OperatorStateRetriever} from "@eigenlayer-middleware/src/OperatorStateRetriever.sol";
import {BLSSignatureChecker} from "@eigenlayer-middleware/src/BLSSignatureChecker.sol";
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
abstract contract TaskManagerStorage is
Initializable,
OwnableUpgradeable,
Pausable,
BLSSignatureChecker,
OperatorStateRetriever,
INewtonProverTaskManager
{
using BN254 for BN254.G1Point;
/**
*
* CONSTANTS AND IMMUTABLES
*
*/
/// @notice The threshold denominator for quorum calculations
uint256 internal constant _THRESHOLD_DENOMINATOR = 100;
/**
*
* STATE
*
*/
/// @notice The current task nonce
uint32 public nonce;
/// @notice Core entity addresses
address public serviceManager;
address public aggregator;
/// @notice Operator registry contract
address public operatorRegistry;
/// @notice Task-related mappings
mapping(bytes32 => bytes32) public allTaskHashes;
mapping(bytes32 => bytes32) public allTaskResponses;
/// @notice Challenge verifier contract address
address public challengeVerifier;
/// @notice Attestation validator contract address
address public attestationValidator;
/// @notice The task response window block
uint32 public taskResponseWindowBlock;
constructor(
OperatorRegistry _operatorRegistry,
IPauserRegistry _pauserRegistry
) BLSSignatureChecker(_operatorRegistry) Pausable(_pauserRegistry) {
operatorRegistry = address(_operatorRegistry);
taskResponseWindowBlock = 30; // default to 30 blocks
}
// storage gap for upgradeability
// slither-disable-next-line shadowing-state
uint256[52] private __GAP;
}
"
},
"src/core/NewtonMessage.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
/// @notice Contract for a NewtonMessage
contract NewtonMessage {
// STRUCTS
/// @notice Intent struct for a transaction authorization
struct Intent {
// equivalent to tx.origin/from
address from;
// equivalent to to
address to;
// equivalent to msg.value
uint256 value;
// ABI-encoded calldata. function selector and arguments
bytes data;
// chain id of the chain that the transaction is on
uint256 chainId;
// encoded ABI of the function that is being called
// e.g. abi.encodePacked("function transfer(address,uint256)")
bytes functionSignature;
}
/// @notice Attestation struct for a transaction authorization
struct Attestation {
// task id
bytes32 taskId;
// policy id
bytes32 policyId;
// policy client
address policyClient;
// intent
Intent intent;
// expiration block number for the attestation
uint32 expiration;
}
/// @notice PolicyData struct for a policy data and its attestation proof
struct PolicyData {
// encoded policy data
bytes data;
// attestation proof for the policy data.
bytes attestation;
// policy data address
address policyDataAddress;
// expiration block number for the policy data
uint32 expireBlock;
}
/// @notice PolicyTaskData struct for a policy data
struct PolicyTaskData {
// policy id
bytes32 policyId;
// policy address
address policyAddress;
// policy program binary
bytes policy;
// an array of policy data with attestation
// NOTE: order matters, the first policy data is the first policy data in the policy data set of the policy.
PolicyData[] policyData;
}
/// @notice VerificationInfo struct for a policy data verification
struct VerificationInfo {
// verifier
address verifier;
// verified
bool verified;
// timestamp
uint256 timestamp;
}
/// @notice error type for unauthorized access
error Unauthorized(string reason);
}
"
},
"src/libraries/TaskLib.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
import {INewtonPolicy} from "../interfaces/INewtonPolicy.sol";
import {NewtonMessage} from "../core/NewtonMessage.sol";
import {INewtonPolicyClient} from "../interfaces/INewtonPolicyClient.sol";
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {PolicyValidationLib} from "./PolicyValidationLib.sol";
/**
* @title TaskLib
* @dev Library for task evaluation and result processing
*/
library TaskLib {
/* CUSTOM ERRORS */
error TaskMismatch();
error InvalidPolicyId();
error InvalidPolicyAddress();
error TaskAlreadyResponded();
error TaskResponseTooLate(
uint32 blockNumber, uint32 taskCreatedBlock, uint32 taskResponseWindowBlock
);
error OnlyPolicyClient();
error InvalidPolicyClient();
error InterfaceNotSupported();
/* FUNCTIONS */
function createTask(
uint32 nonce,
address policyClient,
NewtonMessage.Intent calldata intent,
NewtonMessage.PolicyTaskData calldata policyTaskData,
bytes calldata quorumNumbers,
uint32 quorumThresholdPercentage
) external view returns (INewtonProverTaskManager.Task memory) {
// Validate policy client and get basic info
(address policyAddress, bytes32 policyId) =
PolicyValidationLib.checkVerifiedPolicy(policyClient, policyTaskData);
uint32 currentBlock = uint32(block.number);
// Validate policy data attestations
PolicyValidationLib.validatePolicyData(policyAddress, policyTaskData, currentBlock);
// Create task
INewtonProverTaskManager.Task memory newTask = INewtonProverTaskManager.Task({
nonce: nonce,
intent: intent,
policyId: policyId,
policyClient: policyClient,
policyTaskData: policyTaskData,
policyConfig: INewtonPolicy(policyAddress).getPolicyConfig(policyId),
taskCreatedBlock: currentBlock,
quorumNumbers: quorumNumbers,
quorumThresholdPercentage: quorumThresholdPercentage,
taskId: bytes32(0)
});
bytes32 taskId = keccak256(abi.encode(newTask));
newTask.taskId = taskId;
return newTask;
}
/**
* @dev Evaluates the result of a task execution
* @param evaluationResult The result data to evaluate
* @return bool True if the result indicates success/true
*/
function evaluateResult(
bytes memory evaluationResult
) external pure returns (bool) {
uint256 length = evaluationResult.length;
// Case 1: ABI-encoded bool true (32 bytes)
if (length == 32) {
uint256 val;
assembly {
val := mload(add(evaluationResult, 32))
}
return val == 1;
}
// Case 2: ABI-encoded "true" string (96+ bytes)
if (length >= 96) {
uint256 strLen;
assembly {
strLen := mload(add(evaluationResult, 64))
}
if (strLen == 4) {
bytes32 strData;
assembly {
strData := mload(add(evaluationResult, 96))
}
return strData == 0x7472756500000000000000000000000000000000000000000000000000000000;
}
}
return false;
}
/**
* @dev Sanity checks the task response. Throws if any of the checks fail.
* @param task The task
* @param taskResponse The task response
* @param blockNumber The block number
* @param responseWindowBlock The response window block
*/
function sanityCheckTaskResponse(
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
uint32 blockNumber,
uint32 responseWindowBlock
) external pure {
require(taskResponse.policyId == task.policyId, InvalidPolicyId());
require(taskResponse.policyClient == task.policyClient, InvalidPolicyClient());
require(
taskResponse.policyAddress == task.policyTaskData.policyAddress, InvalidPolicyAddress()
);
require(
blockNumber <= task.taskCreatedBlock + responseWindowBlock,
TaskResponseTooLate(blockNumber, task.taskCreatedBlock, responseWindowBlock)
);
}
function sanityCheckAttestation(
NewtonMessage.Attestation calldata attestation
) external view {
require(
attestation.policyId == INewtonPolicyClient(attestation.policyClient).getPolicyId(),
PolicyValidationLib.PolicyIdMismatch()
);
}
// onlyAttestationClient is used to restrict validateAttestation from only being called by the correct policy client
function onlyAttestationClient(
NewtonMessage.Attestation calldata attestation
) external view {
require(msg.sender.code.length > 0, OnlyPolicyClient());
bytes4 interfaceId = type(INewtonPolicyClient).interfaceId;
(bool success, bytes memory result) = msg.sender.staticcall(
abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId)
);
require(
success && result.length == 32 && abi.decode(result, (bool)), InterfaceNotSupported()
);
require(msg.sender == attestation.policyClient, InvalidPolicyClient());
}
}
"
},
"lib/eigenlayer-middleware/src/libraries/BN254.sol": {
"content": "// SPDX-License-Identifier: MIT
// several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// The remainder of the code in this library is written by LayrLabs Inc. and is also under an MIT license
pragma solidity ^0.8.27;
/**
* @title Library for operations on the BN254 elliptic curve.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
* @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
*/
library BN254 {
// modulus for the underlying field F_p of the elliptic curve
uint256 internal constant FP_MODULUS =
21888242871839275222246405745257275088696311157297823662689037894645226208583;
// modulus for the underlying field F_r of the elliptic curve
uint256 internal constant FR_MODULUS =
21888242871839275222246405745257275088548364400416034343698204186575808495617;
struct G1Point {
uint256 X;
uint256 Y;
}
// Encoding of field elements is: X[1] * i + X[0]
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
/// @dev Thrown when the sum of two points of G1 fails
error ECAddFailed();
/// @dev Thrown when the scalar multiplication of a point of G1 fails
error ECMulFailed();
/// @dev Thrown when the scalar is too large.
error ScalarTooLarge();
/// @dev Thrown when the pairing check fails
error ECPairingFailed();
/// @dev Thrown when the exponentiation mod fails
error ExpModFailed();
function generatorG1() internal pure returns (G1Point memory) {
return G1Point(1, 2);
}
// generator of group G2
/// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
uint256 internal constant G2x1 =
11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 internal constant G2x0 =
10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 internal constant G2y1 =
4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 internal constant G2y0 =
8495653923123431417604973247489272438418190587263600148770280649306958101930;
/// @notice returns the G2 generator
/// @dev mind the ordering of the 1s and 0s!
/// this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
/// "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
function generatorG2() internal pure returns (G2Point memory) {
return G2Point([G2x1, G2x0], [G2y1, G2y0]);
}
// negation of the generator of group G2
/// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
uint256 internal constant nG2x1 =
11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 internal constant nG2x0 =
10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 internal constant nG2y1 =
17805874995975841540914202342111839520379459829704422454583296818431106115052;
uint256 internal constant nG2y0 =
13392588948715843804641432497768002650278120570034223513918757245338268106653;
function negGeneratorG2() internal pure returns (G2Point memory) {
return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]);
}
bytes32 internal constant powersOfTauMerkleRoot =
0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;
/**
* @param p Some point in G1.
* @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
*/
function negate(
G1Point memory p
) internal pure returns (G1Point memory) {
// The prime q in the base field F_q for G1
if (p.X == 0 && p.Y == 0) {
return G1Point(0, 0);
} else {
return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
}
}
/**
* @return r the sum of two points of G1
*/
function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
uint256[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ECAddFailed());
}
/**
* @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds
* @param p the point to multiply
* @param s the scalar to multiply by
* @dev this function is only safe to use if the scalar is 9 bits or less
*/
function scalar_mul_tiny(
BN254.G1Point memory p,
uint16 s
) internal view returns (BN254.G1Point memory) {
require(s < 2 ** 9, ScalarTooLarge());
// if s is 1 return p
if (s == 1) {
return p;
}
// the accumulated product to return
BN254.G1Point memory acc = BN254.G1Point(0, 0);
// the 2^n*p to add to the accumulated product in each iteration
BN254.G1Point memory p2n = p;
// value of most significant bit
uint16 m = 1;
// index of most significant bit
uint8 i = 0;
//loop until we reach the most significant bit
while (s >= m) {
unchecked {
// if the current bit is 1, add the 2^n*p to the accumulated product
if ((s >> i) & 1 == 1) {
acc = plus(acc, p2n);
}
// double the 2^n*p for the next iteration
p2n = plus(p2n, p2n);
// increment the index and double the value of the most significant bit
m <<= 1;
++i;
}
}
// return the accumulated product
return acc;
}
/**
* @return r the product of a point on G1 and a scalar, i.e.
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
* points p.
*/
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
uint256[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ECMulFailed());
}
/**
* @return The result of computing the pairing check
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
* For example,
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
*/
function pairing(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2
) internal view returns (bool) {
G1Point[2] memory p1 = [a1, b1];
G2Point[2] memory p2 = [a2, b2];
uint256[12] memory input;
for (uint256 i = 0; i < 2; i++) {
uint256 j = i * 6;
input[j + 0] = p1[i].X;
input[j + 1] = p1[i].Y;
input[j + 2] = p2[i].X[0];
input[j + 3] = p2[i].X[1];
input[j + 4] = p2[i].Y[0];
input[j + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ECPairingFailed());
return out[0] != 0;
}
/**
* @notice This function is functionally the same as pairing(), however it specifies a gas limit
* the user can set, as a precompile may use the entire gas budget if it reverts.
*/
function safePairing(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2,
uint256 pairingGas
) internal view returns (bool, bool) {
G1Point[2] memory p1 = [a1, b1];
G2Point[2] memory p2 = [a2, b2];
uint256[12] memory input;
for (uint256 i = 0; i < 2; i++) {
uint256 j = i * 6;
input[j + 0] = p1[i].X;
input[j + 1] = p1[i].Y;
input[j + 2] = p2[i].X[0];
input[j + 3] = p2[i].X[1];
input[j + 4] = p2[i].Y[0];
input[j + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
}
//Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
//Success is true if the precompile actually goes through (aka all inputs are valid)
return (success, out[0] != 0);
}
/// @return hashedG1 the keccak256 hash of the G1 Point
/// @dev used for BLS signatures
function hashG1Point(
BN254.G1Point memory pk
) internal pure returns (bytes32 hashedG1) {
assembly {
mstore(0, mload(pk))
mstore(0x20, mload(add(0x20, pk)))
hashedG1 := keccak256(0, 0x40)
}
}
/// @return the keccak256 hash of the G2 Point
/// @dev used for BLS signatures
function hashG2Point(
BN254.G2Point memory pk
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1]));
}
/**
* @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
*/
function hashToG1(
bytes32 _x
) internal view returns (G1Point memory) {
uint256 beta = 0;
uint256 y = 0;
uint256 x = uint256(_x) % FP_MODULUS;
while (true) {
(beta, y) = findYFromX(x);
// y^2 == beta
if (beta == mulmod(y, y, FP_MODULUS)) {
return G1Point(x, y);
}
x = addmod(x, 1, FP_MODULUS);
}
return G1Point(0, 0);
}
/**
* Given X, find Y
*
* where y = sqrt(x^3 + b)
*
* Returns: (x^3 + b), y
*/
function findYFromX(
uint256 x
) internal view returns (uint256, uint256) {
// beta = (x^3 + b) % p
uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);
// y^2 = x^3 + b
// this acts like: y = sqrt(beta) = beta^((p+1) / 4)
uint256 y = expMod(
beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS
);
return (beta, y);
}
function expMod(
uint256 _base,
uint256 _exponent,
uint256 _modulus
) internal view returns (uint256 retval) {
bool success;
uint256[1] memory output;
uint256[6] memory input;
input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
input[1] = 0x20; // expLen = new(big.Int).SetBytes(getData(input, 32, 32))
input[2] = 0x20; // modLen = new(big.Int).SetBytes(getData(input, 64, 32))
input[3] = _base;
input[4] = _exponent;
input[5] = _modulus;
assembly {
success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, ExpModFailed());
return output[0];
}
}
"
},
"src/middlewares/ChallengeVerifier.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
import {TaskLib} from "../libraries/TaskLib.sol";
import {ChallengeLib} from "../libraries/ChallengeLib.sol";
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
contract ChallengeVerifier is Initializable, OwnableUpgradeable {
/* CUSTOM ERRORS */
error ChallengeNotEnabled();
error NotChallengable();
error ChallengePeriodExpired();
error OnlyTaskManager();
/* EVENTS */
event ChallengeEnabled(bool isChallengeEnabled);
/* STORAGE */
address public immutable taskManager;
address public immutable serviceManager;
address public immutable registryCoordinator;
address public immutable blsApkRegistry;
address public immutable allocationManager;
address public immutable instantSlasher;
bool public isChallengeEnabled;
uint32 public taskChallengeWindowBlock;
mapping(bytes32 => bool) public taskSuccesfullyChallenged;
mapping(bytes32 => bytes32) public allTaskHashes;
mapping(bytes32 => bytes32) public allTaskResponses;
/* MODIFIERS */
modifier onlyTaskManager() {
require(msg.sender == taskManager, OnlyTaskManager());
_;
}
/* CONSTRUCTOR */
constructor(
address _serviceManager,
address _taskManager,
address _registryCoordinator,
address _blsApkRegistry,
address _allocationManager,
address _instantSlasher
) {
taskManager = _taskManager;
serviceManager = _serviceManager;
registryCoordinator = _registryCoordinator;
blsApkRegistry = _blsApkRegistry;
allocationManager = _allocationManager;
instantSlasher = _instantSlasher;
}
/* INITIALIZER */
function initialize(
bool _isChallengeEnabled,
uint32 _taskChallengeWindowBlock,
address _owner
) public initializer {
__Ownable_init();
_transferOwnership(_owner);
isChallengeEnabled = _isChallengeEnabled;
taskChallengeWindowBlock = _taskChallengeWindowBlock;
}
/* EXTERNAL FUNCTIONS */
function raiseAndResolveChallenge(
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
INewtonProverTaskManager.ResponseCertificate calldata responseCertificate,
INewtonProverTaskManager.ChallengeData calldata challenge,
BN254.G1Point[] memory pubkeysOfNonSigningOperators
) external onlyTaskManager returns (bool) {
require(isChallengeEnabled, ChallengeNotEnabled());
require(
keccak256(abi.encode(task)) == allTaskHashes[taskResponse.taskId],
TaskLib.TaskMismatch()
);
require(
_isChallengable(task, taskResponse, responseCertificate, challenge), NotChallengable()
);
require(
uint32(block.number) <= responseCertificate.referenceBlock + taskChallengeWindowBlock,
ChallengePeriodExpired()
);
bytes32 taskId = taskResponse.taskId;
bool isResponseCorrect = TaskLib.evaluateResult(challenge.data);
if (isResponseCorrect) {
return false;
}
// Process non-signing operators and validate
(
bytes32[] memory hashesOfPubkeysOfNonSigningOperators,
address[] memory addressOfNonSigningOperators
) = ChallengeLib.processNonSigners(pubkeysOfNonSigningOperators, blsApkRegistry);
ChallengeLib.validateSignatoryRecord(
task.taskCreatedBlock,
hashesOfPubkeysOfNonSigningOperators,
responseCertificate.hashOfNonSigners
);
// Slash signing operators
ChallengeLib.ChallengeContext memory ctx = ChallengeLib.ChallengeContext({
blsApkRegistry: blsApkRegistry,
operatorStateRetriever: taskManager, // task manager is the operator state retriever
registryCoordinator: registryCoordinator,
allocationManager: allocationManager,
instantSlasher: instantSlasher,
serviceManager: serviceManager
});
ChallengeLib.slashSigningOperators(
ctx, task.quorumNumbers, task.taskCreatedBlock, addressOfNonSigningOperators
);
taskSuccesfullyChallenged[taskId] = true;
return true;
}
function _isChallengable(
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
INewtonProverTaskManager.ResponseCertificate calldata responseCertificate,
INewtonProverTaskManager.ChallengeData calldata challenge
) internal view returns (bool) {
bytes32 taskId = taskResponse.taskId;
return task.taskId == taskId && allTaskResponses[taskId] != bytes32(0)
&& allTaskResponses[taskId] == keccak256(abi.encode(taskResponse, responseCertificate))
&& !taskSuccesfullyChallenged[taskId]
&& uint32(block.number) <= responseCertificate.responseExpireBlock
&& challenge.taskId == taskId;
}
/* SETTER FUNCTIONS FOR COMPOSITION */
function setTaskHashes(bytes32 taskId, bytes32 taskHash) external onlyTaskManager {
allTaskHashes[taskId] = taskHash;
}
function setTaskResponses(bytes32 taskId, bytes32 taskResponseHash) external onlyTaskManager {
allTaskResponses[taskId] = taskResponseHash;
}
function isTaskChallenged(
bytes32 taskId
) external view returns (bool) {
return taskSuccesfullyChallenged[taskId];
}
/* OWNER FUNCTIONS */
function setIsChallengeEnabled(
bool _isChallengeEnabled
) external onlyOwner {
isChallengeEnabled = _isChallengeEnabled;
emit ChallengeEnabled(_isChallengeEnabled);
}
function updateTaskChallengeWindowBlock(
uint32 _taskChallengeWindowBlock
) external onlyOwner {
taskChallengeWindowBlock = _taskChallengeWindowBlock;
}
}
"
},
"src/middlewares/AttestationValidator.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {NewtonMessage} from "../core/NewtonMessage.sol";
import {TaskLib} from "../libraries/TaskLib.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
contract AttestationValidator is Initializable, OwnableUpgradeable {
/* CUSTOM ERRORS */
error AttestationHashMismatch();
error AttestationExpired();
error AttestationAlreadySpent();
error OnlyTaskManager();
/* STORAGE */
address public immutable taskManager;
mapping(bytes32 => bytes32) public attestations;
/* MODIFIERS */
modifier onlyTaskManager() {
require(msg.sender == taskManager, OnlyTaskManager());
_;
}
/* CONSTRUCTOR */
constructor(
address _taskManager
) {
taskManager = _taskManager;
}
/* INITIALIZER */
function initialize(
address _owner
) public initializer {
__Ownable_init();
_transferOwnership(_owner);
}
/* EXTERNAL FUNCTIONS */
function validateAttestation(
NewtonMessage.Attestation calldata attestation
) external onlyTaskManager returns (bool) {
TaskLib.sanityCheckAttestation(attestation);
bytes32 attestationHash = keccak256(abi.encode(attestation));
require(attestations[attestation.taskId] == attestationHash, AttestationHashMismatch());
require(uint32(block.number) <= attestation.expiration, AttestationExpired());
// Prevent double spending of the same attestation by setting the attestation hash to 0
require(attestations[attestation.taskId] != bytes32(0), AttestationAlreadySpent());
attestations[attestation.taskId] = bytes32(0);
return true;
}
function invalidateAttestation(
bytes32 taskId
) external onlyTaskManager {
attestations[taskId] = bytes32(0);
}
function createAttestationHash(
bytes32 taskId,
bytes32 policyId,
address policyClient,
NewtonMessage.Intent calldata intent,
uint32 expiration
) external onlyTaskManager returns (bytes32) {
NewtonMessage.Attestation memory attestation =
NewtonMessage.Attestation(taskId, policyId, policyClient, intent, expiration);
bytes32 attestationHash = keccak256(abi.encode(attestation));
attestations[taskId] = attestationHash;
return attestationHash;
}
function isAttestationValid(
NewtonMessage.Attestation calldata attestation
) external view returns (bool) {
TaskLib.sanityCheckAttestation(attestation);
bytes32 attestationHash = keccak256(abi.encode(attestation));
return attestations[attestation.taskId] == attestationHash
&& uint32(block.number) <= attestation.expiration
&& attestations[attestation.taskId] != bytes32(0);
}
function getAttestationHash(
bytes32 taskId
) external view returns (bytes32) {
return attestations[taskId];
}
}
"
},
"lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
/**
* @title Interface for the `PauserRegistry` contract.
* @author Layr Labs, Inc.
* @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
*/
interface IPauserRegistry {
error OnlyUnpauser();
error InputAddressZero();
event PauserStatusChanged(address pauser, bool canPause);
event UnpauserChanged(address previousUnpauser, address newUnpauser);
/// @notice Mapping of addresses to whether they hold the pauser role.
function isPauser(
address pauser
) external view returns (bool);
/// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
function unpauser() external view returns (address);
}
"
},
"src/middlewares/OperatorRegistry.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {SlashingRegistryCoordinator} from
"@eigenlayer-middleware/src/SlashingRegistryCoordinator.sol";
import {IStakeRegistry} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol";
import {IBLSApkRegistry} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol";
import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol";
import {ISocketRegistry} from "@eigenlayer-middleware/src/interfaces/ISocketRegistry.sol";
import {IAllocationManager} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol";
import {IPauserRegistry} from "@eigenlayer/contracts/interfaces/IPauserRegistry.sol";
import {ChainLib} from "../libraries/ChainLib.sol";
contract OperatorRegistry is SlashingRegistryCoordinator {
/* CUSTOM ERRORS */
error GeneratorDoesNotExist();
error GeneratorAlreadyExists();
error OperatorNotWhitelisted(address operator);
error OperatorAlreadyWhitelisted(address operator);
error OperatorNotInWhitelist(address operator);
error InvalidAddress();
/* EVENTS */
event TaskGeneratorAdded(address indexed generator);
event TaskGeneratorRemoved(address indexed generator);
/* STORAGE */
/// @notice Mapping to track whitelisted operators
mapping(address => bool) public whitelistedOperators;
/// @notice Array to keep track of all whitelisted operators for enumeration
address[] public whitelistedOperatorsList;
/// @notice Mapping to track if an operator is in the whitelist array (for efficient removal)
mapping(address => uint256) private _whitelistedOperatorsIndex;
/// @notice Task generator management
mapping(address => bool) public taskGenerators;
/* EVENTS */
event OperatorWhitelisted(address indexed operator, bool isWhitelisted);
constructor(
IStakeRegistry _stakeRegistry,
IBLSApkRegistry _blsApkRegistry,
IIndexRegistry _indexRegistry,
ISocketRegistry _socketRegistry,
IAllocationManager _allocationManager,
IPauserRegistry _pauserRegistry,
string memory _version
)
SlashingRegistryCoordinator(
_stakeRegistry,
_blsApkRegistry,
_indexRegistry,
_socketRegistry,
_allocationManager,
_pauserRegistry,
_version
)
{}
/// @dev Hook to allow for any pre-register logic in `_registerOperator`
function _beforeRegisterOperator(
address operator,
bytes32 operatorId,
bytes memory quorumNumbers,
uint192 currentBitmap
) internal virtual override {
// Check if operator is whitelisted
ChainLib.requireSupportedChain();
if (ChainLib.isMainnet() && !whitelistedOperators[operator]) {
revert OperatorNotWhitelisted(operator);
}
}
/// @dev Hook to allow for any post-register logic in `_registerOperator`
function _afterRegisterOperator(
address operator,
bytes32 operatorId,
bytes memory quorumNumbers,
uint192 newBitmap
) internal virtual override {}
/// @dev Hook to allow for any pre-deregister logic in `_deregisterOperator`
function _beforeDeregisterOperator(
address operator,
bytes32 operatorId,
bytes memory quorumNumbers,
uint192 currentBitmap
) internal virtual override {}
/// @dev Hook to allow for any post-deregister logic in `_deregisterOperator`
function _afterDeregisterOperator(
address operator,
bytes32 operatorId,
bytes memory quorumNumbers,
uint192 newBitmap
) internal virtual override {}
/* WHITELIST MANAGEMENT FUNCTIONS */
/**
* @notice Add an operator to the whitelist
* @param operator The operator address to whitelist
* @dev Only callable by the owner
*/
function addToWhitelist(
address operator
) external onlyOwner {
if (operator == address(0)) revert InvalidAddress();
if (whitelistedOperators[operator]) revert OperatorAlreadyWhitelisted(operator);
whitelistedOperators[operator] = true;
_whitelistedOperatorsIndex[operator] = whitelistedOperatorsList.length;
whitelistedOperatorsList.push(operator);
emit OperatorWhitelisted(operator, true);
}
/**
* @notice Remove an operator from the whitelist
* @param operator The operator address to remove from whitelist
* @dev Only callable by the owner
*/
function removeFromWhitelist(
address operator
) external onlyOwner {
if (!whitelistedOperators[operator]) revert OperatorNotInWhitelist(operator);
whitelistedOperators[operator] = false;
// Remove from array efficiently
uint256 index = _whitelistedOperatorsIndex[operator];
uint256 lastIndex = whitelistedOperatorsList.length - 1;
if (index != lastIndex) {
address lastOperator = whitelistedOperatorsList[lastIndex];
whitelistedOperatorsList[index] = lastOperator;
_whitelistedOperatorsIndex[lastOperator] = index;
}
whitelistedOperatorsList.pop();
delete _whitelistedOperatorsIndex[operator];
emit OperatorWhitelisted(operator, false);
}
/**
* @notice Add multiple operators to the whitelist in a single transaction
* @param operators Array of operator addresses to whitelist
* @dev Only callable by the owner
*/
function addMultipleToWhitelist(
address[] calldata operators
) external onlyOwner {
for (uint256 i = 0; i < operators.length; i++) {
address operator = operators[i];
if (operator == address(0)) revert InvalidAddress();
if (whitelistedOperators[operator]) revert OperatorAlreadyWhitelisted(operator);
whitelistedOperators[operator] = true;
_whitelistedOperatorsIndex[operator] = whitelistedOperatorsList.length;
whitelistedOperatorsList.push(operator);
emit OperatorWhitelisted(operator, true);
}
}
/**
* @notice Check if an operator is whitelisted
* @param operator The operator address to check
* @return True if the operator is whitelisted, false otherwise
*/
function isOperatorWhitelisted(
address operator
) external view returns (bool) {
return whitelistedOperators[operator];
}
function addTaskGenerator(
address generator
) external onlyOwner {
if (generator == address(0)) revert InvalidAddress();
if (taskGenerators[generator]) revert GeneratorAlreadyExists();
taskGenerators[generator] = true;
emit TaskGeneratorAdded(generator);
}
function addMultipleToTaskGenerators(
address[] calldata generators
) external onlyOwner {
for (uint256 i = 0; i < generators.length; i++) {
address generator = generators[i];
if (generator == address(0)) revert InvalidAddress();
if (taskGenerators[generator]) revert GeneratorAlreadyExists();
taskGenerators[generator] = true;
emit TaskGeneratorAdded(generator);
}
}
function removeTaskGenerator(
address generator
) external onlyOwner {
if (!taskGenerators[generator]) revert GeneratorDoesNotExist();
taskGenerators[generator] = false;
delete taskGenerators[generator];
emit TaskGeneratorRemoved(generator);
}
function isTaskGenerator(
address generator
) external view returns (bool) {
return taskGenerators[generator];
}
}
"
},
"src/libraries/OperatorVerifierLib.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;
import {BN254} from "@eigenlayer-middleware/src/libraries/BN254.sol";
import {BitmapUtils} from "@eigenlayer-middleware/src/libraries/BitmapUtils.sol";
import {IBLSApkRegistry} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol";
import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol";
import {ISlashingRegistryCoordinator} from
"@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol";
import {OperatorRegistry} from "../middlewares/OperatorRegistry.sol";
import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
import {IBLSSignatureChecker} from "@eigenlayer-middleware/src/interfaces/IBLSSignatureChecker.sol";
library OperatorVerifierLib {
error OperatorNotWhitelisted();
error InsufficientQuorumStake();
/**
* @notice Verify that all signing operators are whitelisted
* @param registryCoordinator The registry coordinator contract
* @param task The task being responded to
* @param nonSignerStakesAndSignature The BLS signature data containing operator information
*/
function verifySigningOperatorsWhitelisted(
ISlashingRegistryCoordinator registryCoordinator,
INewtonProverTaskManager.Task calldata task,
IBLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature
) internal view {
if (address(registryCoordinator) == address(0)) {
return; // Skip whitelist check if registry not set
}
OperatorRegistry registry = OperatorRegistry(address(registryCoordinator));
IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
IIndexRegistry indexRegistry = registryCoordinator.indexRegistry();
// Get all operators registered for each quorum at the task creation block
// Then subtract non-signers to get the actual signers
for (uint256 i = 0; i < task.quorumNumbers.length; i++) {
uint8 quorumNumber = uint8(task.quorumNumbers[i]);
// Get all operator IDs registered for this quorum at the task creation block
bytes32[] memory operatorIds =
indexRegistry.getOperatorListAtBlockNumber(quorumNumber, task.taskCreatedBlock);
// Create a set of non-signer operator IDs for this quorum
bytes32[] memory nonSignerIds =
new bytes32[](nonSignerStakesAndSignature.nonSignerPubkeys.length);
uint256 nonSignerCount = 0;
for (uint256 j = 0; j < nonSignerStakesAndSignature.nonSignerPubkeys.length; j++) {
bytes32 pubkeyHash =
BN254.hashG1Point(nonSignerStakesAndSignature.nonSignerPubkeys[j]);
// Check if this non-signer was registered for this quorum
uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
pubkeyHash,
task.taskCreatedBlock,
nonSignerStakesAndSignature.nonSignerQuorumBitmapIndices[j]
);
if (BitmapUtils.isSet(quorumBitmap, quorumNumber)) {
nonSignerIds[nonSignerCount] = pubkeyHash;
nonSignerCount++;
}
}
// Check all operators in this quorum - if they're not non-signers, they're signers
for (uint256 k = 0; k < operatorIds.length; k++) {
bytes32 operatorId = operatorIds[k];
// Check if this operator is a non-signer
bool isNonSigner = false;
for (uint256 m = 0; m < nonSignerCount; m++) {
if (nonSignerIds[m] == operatorId) {
isNonSigner = true;
break;
}
}
// Skip if this operator is a non-signer
if (isNonSigner) {
continue;
}
// This operator is a signer - get their address and check whitelist
address operator = blsApkRegistry.getOperatorFromPubkeyHash(operatorId);
// Skip if operator address is zero (unregistered pubkey)
if (operator == address(0)) {
continue;
}
// Check if operator is whitelisted
if (!registry.isOperatorWhitelisted(operator)) {
revert OperatorNotWhitelisted();
}
}
}
}
/**
* @notice Verify task response signatures, quorum thresholds, and whitelist status
* @param task The task being responded to
* @param taskResponse The task response
* @param nonSignerStakesAndSignature The BLS signature data
* @param registryCoordinator The registry coordinator contract
* @param checkSignatures The function to check BLS signatures
* @return quorumStakeTotals The quorum stake totals
* @return hashOfNonSigners The hash of non-signers
*/
function verifyTaskResponseSignatures(
INewtonProverTaskManager.Task calldata task,
INewtonProverTaskManager.TaskResponse calldata taskResponse,
IBLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature,
ISlashingRegistryCoordinator registryCoordinator,
function(bytes32,bytes memory,uint32,IBLSSignatureChecker.NonSignerStakesAndSignature memory) external view returns (IBLSSignatureChecker.QuorumStakeTotals memory, bytes32)
checkSignatures
)
internal
view
returns (
IBLSSignatureChecker.QuorumStakeTotals memory quorumStakeTotals,
bytes32 hashOfNonSigners
)
{
// Check signatures and threshold
bytes32 message = keccak256(abi.encode(taskResponse));
(quorumStakeTotals, hashOfNonSigners) = checkSignatures(
message, task.quorumNumbers, uint32(task.taskCreatedBlock), nonSignerStakesAndSignature
);
// Validate quorum thresholds
uint8 threshold = uint8(task.quorumThresholdPercentage);
for (uint256 i; i < task.quorumNumbers.length;) {
require(
quorumStakeTotals.signedStakeForQuorum[i] * 100
>= quorumStakeTotals.totalStakeForQuorum[i] * threshold,
InsufficientQuorumStake()
);
unchecked {
++i;
}
}
// Verify that all signing operators are whitelisted
verifySigningOperatorsWhitelisted(registryCoordinator, task, nonSignerStakesAndSignature);
}
}
"
},
"lib/eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;
import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
import {IStakeRegistry} from "./IStakeRegistry.sol";
import {IIndexRegistry} from "./IIndexRegistry.sol";
import {BN254} from "../libraries/BN254.sol";
import {IAllocationManager} from
"eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
import {IStakeRegistry, IStakeRegistryTypes} from "./IStakeRegistry.sol";
import {IIndexRegistry} from "./IIndexRegistry.sol";
import {ISocketRegistry} from "./ISocketRegistry.sol";
import {BN254} from "../libraries/BN254.sol";
import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol";
interface ISlashingRegistryCoordinatorErrors {
/// @notice Thrown when array lengths in input parameters don't match.
error InputLengthMismatch();
/// @notice Thrown when an invalid registration type is provided.
error InvalidRegistrationType();
/// @notice Thrown when non-allocation manager calls restricted function.
error OnlyAllocationManager();
/// @notice Thrown when non-ejector calls restricted function.
error OnlyEjector();
/// @notice Thrown when operating on a non-existent quorum.
error QuorumDoesNotExist();
/// @notice Thrown when registering/deregistering with empty bitmap.
error BitmapEmpty();
/// @notice Thrown when registering for already registered quorums.
error AlreadyRegisteredForQuorums();
/// @notice Thrown when registering before ejection cooldown expires.
error CannotReregisterYet();
/// @notice Thrown when unregistered operator attempts restricted operation.
error NotRegistered();
/// @notice Thrown when operator attempts self-churn.
error CannotChurnSelf();
/// @notice Thrown when operator count doesn't match quorum requirements.
error QuorumOperatorCountMismatch();
/// @notice Thrown when operator has insufficient stake for churn.
Submitted on: 2025-10-03 10:14:53
Comments
Log in to comment.
No comments yet.