Skip to content
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

Blob verification changes #321

Merged
merged 23 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
4 changes: 2 additions & 2 deletions contracts/script/GenerateUnitTestHashes.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract GenerateHashes is Script {
quorumBlobParam[0] = IEigenDAServiceManager.QuorumBlobParam({
quorumNumber: 0,
adversaryThresholdPercentage: 80,
quorumThresholdPercentage: 100,
confirmationThresholdPercentage: 100,
chunkLength: 10
});

Expand All @@ -40,7 +40,7 @@ contract GenerateHashes is Script {
quorumBlobParam[0] = IEigenDAServiceManager.QuorumBlobParam({
quorumNumber: 1,
adversaryThresholdPercentage: 80,
quorumThresholdPercentage: 100,
confirmationThresholdPercentage: 100,
chunkLength: 10
});

Expand Down
16 changes: 6 additions & 10 deletions contracts/script/MockRollupDeployer.s.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.9;

import "forge-std/Script.sol";
Expand All @@ -11,27 +12,22 @@ contract MockRollupDeployer is Script {
MockRollup public mockRollup;

BN254.G1Point public s1 = BN254.generatorG1().scalar_mul(2);
uint256 public illegalValue = 1555;

// forge script script/MockRollupDeployer.s.sol:MockRollupDeployer --sig "run(address, bytes32, uint256)" <DASM address> <security hash> <stake> --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv
// <security hash> = keccak256(abi.encode(blobHeader.quorumBlobParams))
function run(address _eigenDAServiceManager, uint256 _stakeRequired) external {
// forge script script/MockRollupDeployer.s.sol:MockRollupDeployer --sig "run(address)" <DASM address> --rpc-url $RPC_URL --private-key $PRIVATE_KEY -vvvv // --broadcast
function run(address _eigenDAServiceManager) external {
vm.startBroadcast();

mockRollup = new MockRollup(
IEigenDAServiceManager(_eigenDAServiceManager),
s1,
illegalValue,
_stakeRequired
s1
);

vm.stopBroadcast();

string memory output = "eigenDA mock rollup deployment output";
vm.serializeAddress(output, "mockRollup", address(mockRollup));

string memory finalJson = vm.serializeString(output, "object", output);

vm.createDir("./script/output", true);
vm.writeJson(finalJson, "./script/output/mock_rollup_deploy_output.json");
vm.stopBroadcast();
}
}
12 changes: 6 additions & 6 deletions contracts/src/core/EigenDAServiceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa
"EigenDAServiceManager.confirmBatch: specified referenceBlockNumber is too far in past"
);

//make sure that the quorumNumbers and quorumThresholdPercentages are of the same length
//make sure that the quorumNumbers and signedStakeForQuorums are of the same length
require(
batchHeader.quorumNumbers.length == batchHeader.quorumThresholdPercentages.length,
"EigenDAServiceManager.confirmBatch: quorumNumbers and quorumThresholdPercentages must be of the same length"
batchHeader.quorumNumbers.length == batchHeader.signedStakeForQuorums.length,
"EigenDAServiceManager.confirmBatch: quorumNumbers and signedStakeForQuorums must be of the same length"
);

// calculate reducedBatchHeaderHash which nodes signed
Expand All @@ -101,12 +101,12 @@ contract EigenDAServiceManager is EigenDAServiceManagerStorage, ServiceManagerBa
);

// check that signatories own at least a threshold percentage of each quourm
for (uint i = 0; i < batchHeader.quorumThresholdPercentages.length; i++) {
// we don't check that the quorumThresholdPercentages are not >100 because a greater value would trivially fail the check, implying
for (uint i = 0; i < batchHeader.signedStakeForQuorums.length; i++) {
// we don't check that the signedStakeForQuorums are not >100 because a greater value would trivially fail the check, implying
// signed stake > total stake
require(
quorumStakeTotals.signedStakeForQuorum[i] * THRESHOLD_DENOMINATOR >=
quorumStakeTotals.totalStakeForQuorum[i] * uint8(batchHeader.quorumThresholdPercentages[i]),
quorumStakeTotals.totalStakeForQuorum[i] * uint8(batchHeader.signedStakeForQuorums[i]),
"EigenDAServiceManager.confirmBatch: signatories do not own at least threshold percentage of a quorum"
);
}
Expand Down
23 changes: 19 additions & 4 deletions contracts/src/core/EigenDAServiceManagerStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ abstract contract EigenDAServiceManagerStorage is IEigenDAServiceManager {
/// @notice Unit of measure (in blocks) for which data will be stored for after confirmation.
uint32 public constant STORE_DURATION_BLOCKS = 2 weeks / 12 seconds;

/// @notice Minimum Batch size, in bytes.
uint32 internal constant MIN_STORE_SIZE = 32;
/// @notice Maximum Batch size, in bytes.
uint32 internal constant MAX_STORE_SIZE = 4e9;
/**
* @notice The maximum amount of blocks in the past that the service will consider stake amounts to still be 'valid'.
* @dev To clarify edge cases, the middleware can look `BLOCK_STALE_MEASURE` blocks into the past, i.e. it may trust stakes from the interval
Expand All @@ -29,6 +25,25 @@ abstract contract EigenDAServiceManagerStorage is IEigenDAServiceManager {
* have to serve after they've deregistered.
*/
uint32 public constant BLOCK_STALE_MEASURE = 150;

/**
* @notice The quorum adversary threshold percentages stored as an ordered bytes array
* this is the percentage of the total stake that must be adversarial to consider a blob invalid
*/
bytes public constant quorumAdversaryThresholdPercentages = hex"2121";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very cryptic. Can we comment on what this means?

0x0aa0 marked this conversation as resolved.
Show resolved Hide resolved

/**
* @notice The quorum confirmation threshold percentages stored as an ordered bytes array
* this is the percentage of the total stake needed to confirm a blob
*/
bytes public constant quorumConfirmationThresholdPercentages = hex"4242";

/**
* @notice The quorum numbers required for confirmation stored as an ordered bytes array
* these quorum numbers have respective canonical thresholds in the
* quorumConfirmationThresholdPercentages and quorumAdversaryThresholdPercentages above
*/
bytes public constant quorumNumbersRequired = hex"0001";

/// @notice The current batchId
uint32 public batchId;
Expand Down
23 changes: 11 additions & 12 deletions contracts/src/interfaces/IEigenDAServiceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface IEigenDAServiceManager is IServiceManager {
struct QuorumBlobParam {
uint8 quorumNumber;
uint8 adversaryThresholdPercentage;
uint8 quorumThresholdPercentage;
uint8 confirmationThresholdPercentage;
uint32 chunkLength; // the length of the chunks in the quorum
}

Expand All @@ -45,7 +45,7 @@ interface IEigenDAServiceManager is IServiceManager {
struct BatchHeader {
bytes32 blobHeadersRoot;
bytes quorumNumbers; // each byte is a different quorum number
bytes quorumThresholdPercentages; // every bytes is an amount less than 100 specifying the percentage of stake
bytes signedStakeForQuorums; // every bytes is an amount less than 100 specifying the percentage of stake
// the must have signed in the corresponding quorum in `quorumNumbers`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"must have signed" -> "have actually signed"? The former is the threshold, whereas this looks the actual signed stake

uint32 referenceBlockNumber;
}
Expand All @@ -54,19 +54,9 @@ interface IEigenDAServiceManager is IServiceManager {
struct BatchMetadata {
BatchHeader batchHeader; // the header of the data store
bytes32 signatoryRecordHash; // the hash of the signatory record
uint96 fee; // the amount of paymentToken paid for the datastore
uint32 confirmationBlockNumber; // the block number at which the batch was confirmed
}

// Relevant metadata for a given datastore
struct BatchMetadataWithSignatoryRecord {
bytes32 batchHeaderHash; // the header hash of the data store
uint32 referenceBlockNumber; // the block number at which stakes
bytes32[] nonSignerPubkeyHashes; // the pubkeyHashes of all of the nonSigners
uint96 fee; // the amount of paymentToken paid for the datastore
uint32 blockNumber; // the block number at which the datastore was confirmed
}

// FUNCTIONS

/// @notice mapping between the batchId to the hash of the metadata of the corresponding Batch
Expand Down Expand Up @@ -94,4 +84,13 @@ interface IEigenDAServiceManager is IServiceManager {

/// @notice The maximum amount of blocks in the past that the service will consider stake amounts to still be 'valid'.
function BLOCK_STALE_MEASURE() external view returns (uint32);

/// @notice Returns the bytes array of quotaAdversaryThresholdPercentages
0x0aa0 marked this conversation as resolved.
Show resolved Hide resolved
function quorumAdversaryThresholdPercentages() external view returns (bytes memory);

/// @notice Returns the bytes array of quotaAdversaryThresholdPercentages
0x0aa0 marked this conversation as resolved.
Show resolved Hide resolved
function quorumConfirmationThresholdPercentages() external view returns (bytes memory);

/// @notice Returns the bytes array of quorumsNumbersRequired
function quorumNumbersRequired() external view returns (bytes memory);
}
56 changes: 48 additions & 8 deletions contracts/src/libraries/EigenDARollupUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {Merkle} from "eigenlayer-core/contracts/libraries/Merkle.sol";
import {BN254} from "eigenlayer-middleware/libraries/BN254.sol";
import {EigenDAHasher} from "./EigenDAHasher.sol";
import {IEigenDAServiceManager} from "../interfaces/IEigenDAServiceManager.sol";
import {BitmapUtils} from "eigenlayer-middleware/libraries/BitmapUtils.sol";

/**
* @title Library of functions to be used by smart contracts wanting to prove blobs on EigenDA and open KZG commitments.
Expand All @@ -20,7 +21,7 @@ library EigenDARollupUtils {
uint8 blobIndex;
IEigenDAServiceManager.BatchMetadata batchMetadata;
bytes inclusionProof;
bytes quorumThresholdIndexes;
bytes quorumIndices;
}

/**
Expand Down Expand Up @@ -50,25 +51,64 @@ library EigenDARollupUtils {
"EigenDARollupUtils.verifyBlob: inclusion proof is invalid"
);

// bitmap of quorum numbers in all quorumBlobParams
uint256 confirmedQuorumsBitmap;

// require that the security param in each blob is met
for (uint i = 0; i < blobHeader.quorumBlobParams.length; i++) {
// make sure that the quorumIndex matches the given quorumNumber
require(uint8(blobVerificationProof.batchMetadata.batchHeader.quorumNumbers[uint8(blobVerificationProof.quorumThresholdIndexes[i])]) == blobHeader.quorumBlobParams[i].quorumNumber,
require(uint8(blobVerificationProof.batchMetadata.batchHeader.quorumNumbers[uint8(blobVerificationProof.quorumIndices[i])]) == blobHeader.quorumBlobParams[i].quorumNumber,
"EigenDARollupUtils.verifyBlob: quorumNumber does not match"
);

// make sure that the adversaryThresholdPercentage is less than the given quorumThresholdPercentage
// make sure that the adversaryThresholdPercentage is less than the given confirmationThresholdPercentage
require(blobHeader.quorumBlobParams[i].adversaryThresholdPercentage
< blobHeader.quorumBlobParams[i].quorumThresholdPercentage,
< blobHeader.quorumBlobParams[i].confirmationThresholdPercentage,
"EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not valid"
);

// make sure that the stake signed for is greater than the given quorumThresholdPercentage
require(uint8(blobVerificationProof.batchMetadata.batchHeader.quorumThresholdPercentages[uint8(blobVerificationProof.quorumThresholdIndexes[i])])
>= blobHeader.quorumBlobParams[i].quorumThresholdPercentage,
"EigenDARollupUtils.verifyBlob: quorumThresholdPercentage is not met"
// make sure that the adversaryThresholdPercentage is at least the given quorumAdversaryThresholdPercentage
uint8 _adversaryThresholdPercentage = getQuorumAdversaryThreshold(eigenDAServiceManager, blobHeader.quorumBlobParams[i].quorumNumber);
if(_adversaryThresholdPercentage > 0){
require(blobHeader.quorumBlobParams[i].adversaryThresholdPercentage >= _adversaryThresholdPercentage,
"EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not met"
);
}

// make sure that the stake signed for is greater than the given confirmationThresholdPercentage
require(uint8(blobVerificationProof.batchMetadata.batchHeader.signedStakeForQuorums[uint8(blobVerificationProof.quorumIndices[i])])
>= blobHeader.quorumBlobParams[i].confirmationThresholdPercentage,
"EigenDARollupUtils.verifyBlob: confirmationThresholdPercentage is not met"
);

// mark confirmed quorum in the bitmap
confirmedQuorumsBitmap = BitmapUtils.setBit(confirmedQuorumsBitmap, blobHeader.quorumBlobParams[i].quorumNumber);
}

// check that required quorums are a subset of the confirmed quorums
require(
BitmapUtils.isSubsetOf(
BitmapUtils.orderedBytesArrayToBitmap(
eigenDAServiceManager.quorumNumbersRequired()
),
confirmedQuorumsBitmap
),
"EigenDARollupUtils.verifyBlob: confirmed quorums are not a subset of the required quorums"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: error message is reversed, should be "required quorums are not a subset of the confirmed quorums"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

);
}

/**
* @notice gets the adversary threshold percentage for a given quorum
* @param eigenDAServiceManager the contract in which the batch was confirmed
* @param quorumNumber the quorum number to get the adversary threshold percentage for
* @dev returns 0 if the quorumNumber is not found
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no clause setting to 0 below for invalid quorumNumber. Does it implicit do so in Sol? Is it a good practice to set it explicitly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes because adversaryThresholdPercentage is defined as 0 by default with return it will return zero if a quorum is not found

*/
function getQuorumAdversaryThreshold(
IEigenDAServiceManager eigenDAServiceManager,
uint256 quorumNumber
) public view returns(uint8 adversaryThresholdPercentage) {
if(eigenDAServiceManager.quorumAdversaryThresholdPercentages().length > quorumNumber){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be >=?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be > I think. If only quorum 0 and 1 are set we want to make sure those numbers are less than the length of the byte array (2)

adversaryThresholdPercentage = uint8(eigenDAServiceManager.quorumAdversaryThresholdPercentages()[quorumNumber]);
}
}

Expand Down
56 changes: 14 additions & 42 deletions contracts/src/rollup/MockRollup.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.9;

import {EigenDARollupUtils} from "../libraries/EigenDARollupUtils.sol";
Expand All @@ -8,87 +7,60 @@ import {IEigenDAServiceManager} from "../interfaces/IEigenDAServiceManager.sol";
import {BN254} from "eigenlayer-middleware/libraries/BN254.sol";

struct Commitment {
address validator; // validator who posted the commitment
address confirmer; // confirmer who posted the commitment
uint32 dataLength; // length of the data
BN254.G1Point polynomialCommitment; // commitment to the polynomial
}

/**
* @title MockRollup
* @author Layr Labs, Inc.
* @notice This contract is used to emulate a rollup contract for the purpose of testing the rollup interface.
*/
contract MockRollup {

IEigenDAServiceManager public eigenDAServiceManager; // EigenDASM contract
BN254.G1Point public tau; //power of tau
uint256 public illegalValue; // special "illegal" value that should not be included in blob
uint256 public stakeRequired; // amount of stake required to register as a validator

///@notice mapping of validators who have registered
mapping(address => bool) public validators;
///@notice mapping of validators who have been blacklisted
mapping(address => bool) public blacklist;
///@notice mapping of timestamps to commitments
mapping(uint256 => Commitment) public commitments;

constructor(IEigenDAServiceManager _eigenDAServiceManager, BN254.G1Point memory _tau, uint256 _illegalValue, uint256 _stakeRequired) {
constructor(IEigenDAServiceManager _eigenDAServiceManager, BN254.G1Point memory _tau) {
eigenDAServiceManager = _eigenDAServiceManager;
tau = _tau;
illegalValue = _illegalValue;
stakeRequired = _stakeRequired;
}

///@notice registers msg.sender as validator by putting up 1 ether of stake
function registerValidator() external payable {
require(msg.value == stakeRequired, "MockRollup.registerValidator: Must send stake required to register");
require(!validators[msg.sender], "MockRollup.registerValidator: Validator already registered");
require(!blacklist[msg.sender], "MockRollup.registerValidator: Validator blacklisted");
validators[msg.sender] = true;
}

Copy link
Contributor

@siddimore siddimore Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can anyone call mockrollup postcommitment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

/**
* @notice a function for validators to post a commitment to a blob on behalf of the rollup
* @notice a function for a confirmer to post a commitment to a blob and verfiy it on EigenDA
* @param blobHeader the blob header
* @param blobVerificationProof the blob verification proof
*/
function postCommitment(
IEigenDAServiceManager.BlobHeader memory blobHeader,
EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof
) external {
require(validators[msg.sender], "MockRollup.postCommitment: Validator not registered");
require(commitments[block.timestamp].validator == address(0), "MockRollup.postCommitment: Commitment already posted");
// require commitment has not already been posted
require(commitments[block.timestamp].confirmer == address(0), "MockRollup.postCommitment: Commitment already posted");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should there be a check on who can call confirmer? or should the comment be not a confirmer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the check to simplify so that we can just use any address to verify the blobs


// verify that the blob was included in the batch
EigenDARollupUtils.verifyBlob(blobHeader, eigenDAServiceManager, blobVerificationProof);

// store the commitment
commitments[block.timestamp] = Commitment(msg.sender, blobHeader.dataLength, blobHeader.commitment);
}

/**
* @notice a function for users to challenge a commitment that contains the illegal value
* @notice a function for users to challenge a commitment against a provided value
* @param timestamp the timestamp of the commitment being challenged
* @param point the point on the polynomial to evaluate
* @param proof revelvant KZG proof
* @param challengeValue The value expected upon opening the commitment
*/
function challengeCommitment(uint256 timestamp, uint256 point, BN254.G2Point memory proof) external {
function challengeCommitment(uint256 timestamp, uint256 point, BN254.G2Point memory proof, uint256 challengeValue) external returns (bool) {
Commitment memory commitment = commitments[timestamp];
require(commitment.validator != address(0), "MockRollup.challengeCommitment: Commitment not posted");
// require the commitment exists
require(commitment.confirmer != address(0), "MockRollup.challengeCommitment: Commitment not posted");

// point on the polynomial must be less than the length of the data stored
require(point < commitment.dataLength, "MockRollup.challengeCommitment: Point must be less than data length");

// verify that the commitment contains the illegal value
require(EigenDARollupUtils.openCommitment(point, illegalValue, tau, commitment.polynomialCommitment, proof), "MockRollup.challengeCommitment: Does not evaluate to illegal value");

// blacklist the validator
validators[commitment.validator] = false;
blacklist[commitment.validator] = true;

// send validators stake to the user who challenged the commitment
(bool success, ) = msg.sender.call{value: 1 ether}("");
require(success);

// verify that the commitment contains the challenge value
return EigenDARollupUtils.openCommitment(point, challengeValue, tau, commitment.polynomialCommitment, proof);
}

}
Loading
Loading