diff --git a/contracts/src/libraries/EigenDARollupUtils.sol b/contracts/src/libraries/EigenDARollupUtils.sol index 4cab6b28a5..54ff2b33d8 100644 --- a/contracts/src/libraries/EigenDARollupUtils.sol +++ b/contracts/src/libraries/EigenDARollupUtils.sol @@ -31,10 +31,10 @@ library EigenDARollupUtils { * @param blobVerificationProof the relevant data needed to prove inclusion of the blob and that the trust assumptions were as expected */ function verifyBlob( - IEigenDAServiceManager.BlobHeader calldata blobHeader, + IEigenDAServiceManager.BlobHeader memory blobHeader, IEigenDAServiceManager eigenDAServiceManager, - BlobVerificationProof calldata blobVerificationProof - ) external view { + BlobVerificationProof memory blobVerificationProof + ) internal view { require( EigenDAHasher.hashBatchMetadata(blobVerificationProof.batchMetadata) == eigenDAServiceManager.batchIdToBatchMetadataHash(blobVerificationProof.batchId), @@ -97,6 +97,84 @@ library EigenDARollupUtils { ); } + /** + * @notice Verifies the inclusion of a blob within a batch confirmed in `eigenDAServiceManager` and its trust assumptions + * @param blobHeaders the headers of the blobs containing relevant attributes of the blobs + * @param eigenDAServiceManager the contract in which the batch was confirmed + * @param blobVerificationProofs the relevant data needed to prove inclusion of the blobs and that the trust assumptions were as expected + */ + function verifyBlobs( + IEigenDAServiceManager.BlobHeader[] memory blobHeaders, + IEigenDAServiceManager eigenDAServiceManager, + BlobVerificationProof[] memory blobVerificationProofs + ) internal view { + require(blobHeaders.length == blobVerificationProofs.length, "EigenDARollupUtils.verifyBlobs: blobHeaders and blobVerificationProofs must have the same length"); + + bytes memory quorumAdversaryThresholdPercentages = eigenDAServiceManager.quorumAdversaryThresholdPercentages(); + uint256 quorumNumbersRequiredBitmap = BitmapUtils.orderedBytesArrayToBitmap(eigenDAServiceManager.quorumNumbersRequired()); + + for (uint i = 0; i < blobHeaders.length; i++) { + require( + EigenDAHasher.hashBatchMetadata(blobVerificationProofs[i].batchMetadata) + == eigenDAServiceManager.batchIdToBatchMetadataHash(blobVerificationProofs[i].batchId), + "EigenDARollupUtils.verifyBlob: batchMetadata does not match stored metadata" + ); + + require( + Merkle.verifyInclusionKeccak( + blobVerificationProofs[i].inclusionProof, + blobVerificationProofs[i].batchMetadata.batchHeader.blobHeadersRoot, + keccak256(abi.encodePacked(EigenDAHasher.hashBlobHeader(blobHeaders[i]))), + blobVerificationProofs[i].blobIndex + ), + "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 j = 0; j < blobHeaders[i].quorumBlobParams.length; j++) { + // make sure that the quorumIndex matches the given quorumNumber + require(uint8(blobVerificationProofs[i].batchMetadata.batchHeader.quorumNumbers[uint8(blobVerificationProofs[i].quorumIndices[i])]) == blobHeaders[i].quorumBlobParams[i].quorumNumber, + "EigenDARollupUtils.verifyBlob: quorumNumber does not match" + ); + + // make sure that the adversaryThresholdPercentage is less than the given confirmationThresholdPercentage + require(blobHeaders[i].quorumBlobParams[i].adversaryThresholdPercentage + < blobHeaders[i].quorumBlobParams[i].confirmationThresholdPercentage, + "EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not valid" + ); + + // make sure that the adversaryThresholdPercentage is at least the given quorumAdversaryThresholdPercentage + uint8 _adversaryThresholdPercentage = uint8(quorumAdversaryThresholdPercentages[blobHeaders[i].quorumBlobParams[j].quorumNumber]); + if(_adversaryThresholdPercentage > 0){ + require(blobHeaders[i].quorumBlobParams[j].adversaryThresholdPercentage >= _adversaryThresholdPercentage, + "EigenDARollupUtils.verifyBlob: adversaryThresholdPercentage is not met" + ); + } + + // make sure that the stake signed for is greater than the given confirmationThresholdPercentage + require(uint8(blobVerificationProofs[i].batchMetadata.batchHeader.signedStakeForQuorums[uint8(blobVerificationProofs[i].quorumIndices[j])]) + >= blobHeaders[i].quorumBlobParams[j].confirmationThresholdPercentage, + "EigenDARollupUtils.verifyBlob: confirmationThresholdPercentage is not met" + ); + + // mark confirmed quorum in the bitmap + confirmedQuorumsBitmap = BitmapUtils.setBit(confirmedQuorumsBitmap, blobHeaders[i].quorumBlobParams[j].quorumNumber); + } + + // check that required quorums are a subset of the confirmed quorums + require( + BitmapUtils.isSubsetOf( + quorumNumbersRequiredBitmap, + confirmedQuorumsBitmap + ), + "EigenDARollupUtils.verifyBlob: required quorums are not a subset of the confirmed quorums" + ); + } + } + /** * @notice gets the adversary threshold percentage for a given quorum * @param eigenDAServiceManager the contract in which the batch was confirmed @@ -106,7 +184,7 @@ library EigenDARollupUtils { function getQuorumAdversaryThreshold( IEigenDAServiceManager eigenDAServiceManager, uint256 quorumNumber - ) public view returns(uint8 adversaryThresholdPercentage) { + ) internal view returns(uint8 adversaryThresholdPercentage) { if(eigenDAServiceManager.quorumAdversaryThresholdPercentages().length > quorumNumber){ adversaryThresholdPercentage = uint8(eigenDAServiceManager.quorumAdversaryThresholdPercentages()[quorumNumber]); } diff --git a/contracts/test/harnesses/EigenDABlobUtilsHarness.sol b/contracts/test/harnesses/EigenDABlobUtilsHarness.sol index 8a4c7a4582..1a3ceb17c3 100644 --- a/contracts/test/harnesses/EigenDABlobUtilsHarness.sol +++ b/contracts/test/harnesses/EigenDABlobUtilsHarness.sol @@ -14,4 +14,12 @@ contract EigenDABlobUtilsHarness is Test { ) external view { EigenDARollupUtils.verifyBlob(blobHeader, eigenDAServiceManager, blobVerificationProof); } + + function verifyBlobs( + IEigenDAServiceManager.BlobHeader[] calldata blobHeaders, + IEigenDAServiceManager eigenDAServiceManager, + EigenDARollupUtils.BlobVerificationProof[] calldata blobVerificationProofs + ) external view { + EigenDARollupUtils.verifyBlobs(blobHeaders, eigenDAServiceManager, blobVerificationProofs); + } } diff --git a/contracts/test/unit/EigenDABlobUtils.t.sol b/contracts/test/unit/EigenDABlobUtils.t.sol index 5596a82911..aa7afed0fd 100644 --- a/contracts/test/unit/EigenDABlobUtils.t.sol +++ b/contracts/test/unit/EigenDABlobUtils.t.sol @@ -119,6 +119,58 @@ contract EigenDABlobUtilsUnit is BLSMockAVSDeployer { emit log_named_uint("gas used", gasBefore - gasAfter); } + function testVerifyBlobs_TwoBlobs(uint256 pseudoRandomNumber) public { + uint256 numQuorumBlobParams = 2; + IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2); + blobHeader[0] = _generateRandomBlobHeader(pseudoRandomNumber, numQuorumBlobParams); + uint256 anotherPseudoRandomNumber = uint256(keccak256(abi.encodePacked(pseudoRandomNumber))); + blobHeader[1] = _generateRandomBlobHeader(anotherPseudoRandomNumber, numQuorumBlobParams); + + IEigenDAServiceManager.BatchHeader memory batchHeader; + bytes memory firstBlobHash = abi.encodePacked(blobHeader[0].hashBlobHeader()); + bytes memory secondBlobHash = abi.encodePacked(blobHeader[1].hashBlobHeader()); + batchHeader.blobHeadersRoot = keccak256(abi.encodePacked(keccak256(firstBlobHash), keccak256(secondBlobHash))); + // add dummy quorum numbers and quorum threshold percentages making sure confirmationThresholdPercentage = adversaryThresholdPercentage + defaultCodingRatioPercentage + for (uint i = 0; i < blobHeader[1].quorumBlobParams.length; i++) { + batchHeader.quorumNumbers = abi.encodePacked(batchHeader.quorumNumbers, blobHeader[1].quorumBlobParams[i].quorumNumber); + batchHeader.signedStakeForQuorums = abi.encodePacked(batchHeader.signedStakeForQuorums, blobHeader[1].quorumBlobParams[i].adversaryThresholdPercentage + defaultCodingRatioPercentage); + } + batchHeader.referenceBlockNumber = uint32(block.number); + + // add dummy batch metadata + IEigenDAServiceManager.BatchMetadata memory batchMetadata; + batchMetadata.batchHeader = batchHeader; + batchMetadata.signatoryRecordHash = keccak256(abi.encodePacked("signatoryRecordHash")); + batchMetadata.confirmationBlockNumber = defaultConfirmationBlockNumber; + + stdstore + .target(address(eigenDAServiceManager)) + .sig("batchIdToBatchMetadataHash(uint32)") + .with_key(defaultBatchId) + .checked_write(batchMetadata.hashBatchMetadata()); + + EigenDARollupUtils.BlobVerificationProof[] memory blobVerificationProofs = new EigenDARollupUtils.BlobVerificationProof[](2); + blobVerificationProofs[0].batchId = defaultBatchId; + blobVerificationProofs[1].batchId = defaultBatchId; + blobVerificationProofs[0].batchMetadata = batchMetadata; + blobVerificationProofs[1].batchMetadata = batchMetadata; + blobVerificationProofs[0].inclusionProof = abi.encodePacked(keccak256(secondBlobHash)); + blobVerificationProofs[1].inclusionProof = abi.encodePacked(keccak256(firstBlobHash)); + blobVerificationProofs[0].blobIndex = 0; + blobVerificationProofs[1].blobIndex = 1; + blobVerificationProofs[0].quorumIndices = new bytes(batchHeader.quorumNumbers.length); + blobVerificationProofs[1].quorumIndices = new bytes(batchHeader.quorumNumbers.length); + for (uint i = 0; i < batchHeader.quorumNumbers.length; i++) { + blobVerificationProofs[0].quorumIndices[i] = bytes1(uint8(i)); + blobVerificationProofs[1].quorumIndices[i] = bytes1(uint8(i)); + } + + uint256 gasBefore = gasleft(); + eigenDABlobUtilsHarness.verifyBlobs(blobHeader, eigenDAServiceManager, blobVerificationProofs); + uint256 gasAfter = gasleft(); + emit log_named_uint("gas used", gasBefore - gasAfter); + } + function testVerifyBlob_InvalidMetadataHash(uint256 pseudoRandomNumber) public { uint256 numQuorumBlobParams = pseudoRandomNumber % 192; IEigenDAServiceManager.BlobHeader[] memory blobHeader = new IEigenDAServiceManager.BlobHeader[](2);