Skip to content

Commit

Permalink
feat: move away the EvidenceVerifier from the service (#53)
Browse files Browse the repository at this point in the history
* separate EvidenceVerifier

* whitelist
  • Loading branch information
cool-develope authored Jan 22, 2024
1 parent 4f01648 commit 039ceee
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 177 deletions.
34 changes: 34 additions & 0 deletions contracts/interfaces/IEvidenceVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

// Evidence is the data structure to store the slashing evidence.
struct Evidence {
address operator;
bytes32 blockHash;
bytes32 correctBlockHash;
bytes32 currentCommitteeRoot;
bytes32 correctCurrentCommitteeRoot;
bytes32 nextCommitteeRoot;
bytes32 correctNextCommitteeRoot;
uint256 blockNumber;
uint256 l1BlockNumber;
bytes blockSignature; // 192-byte
bytes commitSignature; // 65-byte
uint32 chainID;
bytes sigProof;
bytes aggProof;
}

// ProofParams is the proof parameters for the BLS signature verification.
struct ProofParams {
uint256[2] a;
uint256[2][2] b;
uint256[2] c;
}

// Path: contracts/interfaces/IEvidenceVerifier.sol
interface IEvidenceVerifier {
function setAggregateVerifierRoute(uint256 routeIndex, address _verifierAddress) external;
function setSingleVerifier(address _verifierAddress) external;
function uploadEvidence(Evidence calldata evidence) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,40 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import {ISlashingAggregateVerifier} from "../interfaces/ISlashingAggregateVerifier.sol";
import {ISlashingSingleVerifier} from "../interfaces/ISlashingSingleVerifier.sol";
import {ILagrangeCommittee, OperatorStatus} from "../interfaces/ILagrangeCommittee.sol";
import {IServiceManager} from "../interfaces/IServiceManager.sol";
import {IEvidenceVerifier, Evidence, ProofParams} from "../interfaces/IEvidenceVerifier.sol";

contract EvidenceVerifier is Initializable, OwnableUpgradeable {
// Evidence is the data structure to store the slashing evidence.
struct Evidence {
address operator;
bytes32 blockHash;
bytes32 correctBlockHash;
bytes32 currentCommitteeRoot;
bytes32 correctCurrentCommitteeRoot;
bytes32 nextCommitteeRoot;
bytes32 correctNextCommitteeRoot;
uint256 blockNumber;
uint256 l1BlockNumber;
bytes blockSignature; // 192-byte
bytes commitSignature; // 65-byte
uint32 chainID;
bytes sigProof;
bytes aggProof;
}

struct ProofParams {
uint256[2] a;
uint256[2][2] b;
uint256[2] c;
}

contract EvidenceVerifier is Initializable, OwnableUpgradeable, IEvidenceVerifier {
uint256 public constant CHAIN_ID_MAINNET = 1;
uint256 public constant CHAIN_ID_OPTIMISM_BEDROCK = 420;
uint256 public constant CHAIN_ID_BASE = 84531;
uint256 public constant CHAIN_ID_ARBITRUM_NITRO = 421613;

ILagrangeCommittee public immutable committee;
IServiceManager public immutable serviceManager;
// aggregate signature verifiers Triage
mapping(uint256 => ISlashingAggregateVerifier) public aggVerifiers;
// single signature verifier
ISlashingSingleVerifier public singleVerifier;

constructor() {
event OperatorSlashed(address operator);

event UploadEvidence(
address operator,
bytes32 blockHash,
bytes32 currentCommitteeRoot,
bytes32 nextCommitteeRoot,
uint256 blockNumber,
uint256 epochNumber,
bytes blockSignature,
bytes commitSignature,
uint32 chainID
);

constructor(ILagrangeCommittee _committee, IServiceManager _serviceManager) {
committee = _committee;
serviceManager = _serviceManager;
_disableInitializers();
}

Expand All @@ -60,6 +57,88 @@ contract EvidenceVerifier is Initializable, OwnableUpgradeable {
singleVerifier = ISlashingSingleVerifier(_verifierAddress);
}

/// upload the evidence to punish the operator.
function uploadEvidence(Evidence calldata evidence) external {
// check the operator is registered or not
require(committee.getServeUntilBlock(evidence.operator) > 0, "The operator is not registered");

// check the operator is slashed or not
require(!committee.getSlashed(evidence.operator), "The operator is slashed");

require(checkCommitSignature(evidence), "The commit signature is not correct");

if (!_checkBlockSignature(evidence)) {
_freezeOperator(evidence.operator);
}

if (evidence.correctBlockHash == evidence.blockHash) {
_freezeOperator(evidence.operator);
}

if (
!_checkCommitteeRoots(
evidence.correctCurrentCommitteeRoot,
evidence.currentCommitteeRoot,
evidence.correctNextCommitteeRoot,
evidence.nextCommitteeRoot,
evidence.l1BlockNumber,
evidence.chainID
)
) {
_freezeOperator(evidence.operator);
}

// TODO what is this for (no condition)?

emit UploadEvidence(
evidence.operator,
evidence.blockHash,
evidence.currentCommitteeRoot,
evidence.nextCommitteeRoot,
evidence.blockNumber,
evidence.l1BlockNumber,
evidence.blockSignature,
evidence.commitSignature,
evidence.chainID
);
}

/// Slash the given operator
function _freezeOperator(address operator) internal {
serviceManager.freezeOperator(operator);
emit OperatorSlashed(operator);
}

function _checkBlockSignature(Evidence memory _evidence) internal returns (bool) {
// establish that proofs are valid
(ILagrangeCommittee.CommitteeData memory cdata,) =
committee.getCommittee(_evidence.chainID, _evidence.l1BlockNumber);

require(_verifyAggregateSignature(_evidence, cdata.height), "Aggregate proof verification failed");

uint256[2] memory blsPubKey = committee.getBlsPubKey(_evidence.operator);
bool sigVerify = _verifySingleSignature(_evidence, blsPubKey);

return (sigVerify);
}

// Slashing condition. Returns veriifcation of chain's current committee root at a given block.
function _checkCommitteeRoots(
bytes32 correctCurrentCommitteeRoot,
bytes32 currentCommitteeRoot,
bytes32 correctNextCommitteeRoot,
bytes32 nextCommitteeRoot,
uint256 blockNumber,
uint32 chainID
) internal returns (bool) {
(ILagrangeCommittee.CommitteeData memory currentCommittee, bytes32 nextRoot) =
committee.getCommittee(chainID, blockNumber);
require(correctCurrentCommitteeRoot == currentCommittee.root, "Reference current committee roots do not match.");
require(correctNextCommitteeRoot == nextRoot, "Reference next committee roots do not match.");

return (currentCommitteeRoot == correctCurrentCommitteeRoot) && (nextCommitteeRoot == correctNextCommitteeRoot);
}

function toUint(bytes memory src) internal pure returns (uint256) {
uint256 value;
for (uint256 i = 0; i < src.length; i++) {
Expand Down Expand Up @@ -176,8 +255,8 @@ contract EvidenceVerifier is Initializable, OwnableUpgradeable {
return slices;
}

function verifySingleSignature(EvidenceVerifier.Evidence memory _evidence, uint256[2] memory blsPubKey)
external
function _verifySingleSignature(Evidence memory _evidence, uint256[2] memory blsPubKey)
internal
view
returns (bool)
{
Expand Down Expand Up @@ -216,8 +295,8 @@ contract EvidenceVerifier is Initializable, OwnableUpgradeable {
return result;
}

function verifyAggregateSignature(EvidenceVerifier.Evidence memory _evidence, uint256 _committeeSize)
external
function _verifyAggregateSignature(Evidence memory _evidence, uint256 _committeeSize)
internal
view
returns (bool)
{
Expand Down
140 changes: 28 additions & 112 deletions contracts/protocol/LagrangeService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,151 +8,67 @@ import {IServiceManager} from "../interfaces/IServiceManager.sol";
import {ILagrangeCommittee, OperatorStatus} from "../interfaces/ILagrangeCommittee.sol";
import {ILagrangeService} from "../interfaces/ILagrangeService.sol";

import {EvidenceVerifier} from "../library/EvidenceVerifier.sol";

contract LagrangeService is Initializable, OwnableUpgradeable, ILagrangeService {
uint256 public constant UPDATE_TYPE_REGISTER = 1;
uint256 public constant UPDATE_TYPE_AMOUNT_CHANGE = 2;
uint256 public constant UPDATE_TYPE_UNREGISTER = 3;
mapping(address => bool) public operatorWhitelist;

ILagrangeCommittee public immutable committee;
IServiceManager public immutable serviceManager;

EvidenceVerifier public evidenceVerifier;

event OperatorRegistered(address operator, uint32 serveUntilBlock);
event OperatorDeregistered(address operator);
event OperatorSubscribed(address operator, uint32 chainID);
event OperatorUnsubscribed(address operator, uint32 chainID);

event OperatorSlashed(address operator);

event UploadEvidence(
address operator,
bytes32 blockHash,
bytes32 currentCommitteeRoot,
bytes32 nextCommitteeRoot,
uint256 blockNumber,
uint256 epochNumber,
bytes blockSignature,
bytes commitSignature,
uint32 chainID
);
modifier onlyWhitelisted() {
require(operatorWhitelist[msg.sender], "Operator is not whitelisted");
_;
}

constructor(ILagrangeCommittee _committee, IServiceManager _serviceManager) {
committee = _committee;
serviceManager = _serviceManager;
_disableInitializers();
}

function initialize(address initialOwner, EvidenceVerifier _evidenceVerifier) external initializer {
function initialize(address initialOwner) external initializer {
_transferOwnership(initialOwner);
evidenceVerifier = _evidenceVerifier;
}

/// Add the operator to the whitelist.
function addOperatorToWhitelist(address operator) external onlyOwner {
operatorWhitelist[operator] = true;
}

/// Remove the operator from the whitelist.
function removeOperatorFromWhitelist(address operator) external onlyOwner {
operatorWhitelist[operator] = false;
}

/// Add the operator to the service.
function register(uint256[2] memory _blsPubKey, uint32 serveUntilBlock) external {
function register(uint256[2] memory _blsPubKey, uint32 serveUntilBlock) external onlyWhitelisted {
committee.addOperator(msg.sender, _blsPubKey, serveUntilBlock);
serviceManager.recordFirstStakeUpdate(msg.sender, serveUntilBlock);
emit OperatorRegistered(msg.sender, serveUntilBlock);
}

/// Subscribe the dedicated chain.
function subscribe(uint32 chainID) external {
function subscribe(uint32 chainID) external onlyWhitelisted {
committee.subscribeChain(msg.sender, chainID);
emit OperatorSubscribed(msg.sender, chainID);
}

function unsubscribe(uint32 chainID) external {
/// Unsubscribe the dedicated chain.
function unsubscribe(uint32 chainID) external onlyWhitelisted {
committee.unsubscribeChain(msg.sender, chainID);
emit OperatorUnsubscribed(msg.sender, chainID);
}

/// deregister the operator from the service.
function deregister() external {
/// Deregister the operator from the service.
function deregister() external onlyWhitelisted {
(bool possible, uint256 unsubscribeBlockNumber) = committee.isUnregisterable(msg.sender);
require(possible, "The operator is not able to deregister");
serviceManager.recordLastStakeUpdateAndRevokeSlashingAbility(msg.sender, uint32(unsubscribeBlockNumber));
}

/// upload the evidence to punish the operator.
function uploadEvidence(EvidenceVerifier.Evidence calldata evidence) external {
// check the operator is registered or not
require(committee.getServeUntilBlock(evidence.operator) > 0, "The operator is not registered");

// check the operator is slashed or not
require(!committee.getSlashed(evidence.operator), "The operator is slashed");

require(evidenceVerifier.checkCommitSignature(evidence), "The commit signature is not correct");

if (!_checkBlockSignature(evidence)) {
_freezeOperator(evidence.operator);
}

if (evidence.correctBlockHash == evidence.blockHash) {
_freezeOperator(evidence.operator);
}

if (
!_checkCommitteeRoots(
evidence.correctCurrentCommitteeRoot,
evidence.currentCommitteeRoot,
evidence.correctNextCommitteeRoot,
evidence.nextCommitteeRoot,
evidence.l1BlockNumber,
evidence.chainID
)
) {
_freezeOperator(evidence.operator);
}

// TODO what is this for (no condition)?

emit UploadEvidence(
evidence.operator,
evidence.blockHash,
evidence.currentCommitteeRoot,
evidence.nextCommitteeRoot,
evidence.blockNumber,
evidence.l1BlockNumber,
evidence.blockSignature,
evidence.commitSignature,
evidence.chainID
);
}

function _checkBlockSignature(EvidenceVerifier.Evidence memory _evidence) internal returns (bool) {
// establish that proofs are valid
(ILagrangeCommittee.CommitteeData memory cdata,) =
committee.getCommittee(_evidence.chainID, _evidence.l1BlockNumber);

require(
evidenceVerifier.verifyAggregateSignature(_evidence, cdata.height), "Aggregate proof verification failed"
);

uint256[2] memory blsPubKey = committee.getBlsPubKey(_evidence.operator);
bool sigVerify = evidenceVerifier.verifySingleSignature(_evidence, blsPubKey);

return (sigVerify);
}

// Slashing condition. Returns veriifcation of chain's current committee root at a given block.
function _checkCommitteeRoots(
bytes32 correctCurrentCommitteeRoot,
bytes32 currentCommitteeRoot,
bytes32 correctNextCommitteeRoot,
bytes32 nextCommitteeRoot,
uint256 blockNumber,
uint32 chainID
) internal returns (bool) {
(ILagrangeCommittee.CommitteeData memory currentCommittee, bytes32 nextRoot) =
committee.getCommittee(chainID, blockNumber);
require(correctCurrentCommitteeRoot == currentCommittee.root, "Reference current committee roots do not match.");
require(correctNextCommitteeRoot == nextRoot, "Reference next committee roots do not match.");

return (currentCommitteeRoot == correctCurrentCommitteeRoot) && (nextCommitteeRoot == correctNextCommitteeRoot);
}

/// Slash the given operator
function _freezeOperator(address operator) internal {
serviceManager.freezeOperator(operator);

emit OperatorSlashed(operator);
emit OperatorDeregistered(msg.sender);
}

function owner() public view override(OwnableUpgradeable, ILagrangeService) returns (address) {
Expand Down
Loading

0 comments on commit 039ceee

Please sign in to comment.