diff --git a/.gitmodules b/.gitmodules index 5cbc631ba..73acc54c8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ path = l1-contracts/lib/openzeppelin-contracts url = https://github.com/Openzeppelin/openzeppelin-contracts branch = release-v4.9 +[submodule "l1-contracts/lib/eigenda"] + path = l1-contracts/lib/eigenda + url = https://github.com/Layr-Labs/eigenda diff --git a/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol b/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol index 50bccb744..796d9276e 100644 --- a/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/ExecutorProvingTest.sol @@ -8,6 +8,9 @@ import {LogProcessingOutput} from "../../state-transition/chain-interfaces/IExec import {LogProcessingOutput} from "../../state-transition/chain-interfaces/IExecutor.sol"; contract ExecutorProvingTest is ExecutorFacet { + + constructor() ExecutorFacet(address(0)) {} + function getBatchProofPublicInput( bytes32 _prevBatchCommitment, bytes32 _currentBatchCommitment diff --git a/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol b/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol index 8da6425b3..263be1eab 100644 --- a/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol +++ b/l1-contracts/contracts/dev-contracts/test/TestExecutor.sol @@ -5,6 +5,8 @@ import {ExecutorFacet} from "../../state-transition/chain-deps/facets/Executor.s pragma solidity 0.8.24; contract TestExecutor is ExecutorFacet { + + constructor() ExecutorFacet(address(0)) {} /// @dev Since we want to test the blob functionality we want mock the calls to the blobhash opcode. function _getBlobVersionedHash(uint256 _index) internal view virtual override returns (bytes32 versionedHash) { (bool success, bytes memory data) = s.blobVersionedHashRetriever.staticcall(abi.encode(_index)); diff --git a/l1-contracts/contracts/eigenda/EigenDAVerifier.sol b/l1-contracts/contracts/eigenda/EigenDAVerifier.sol new file mode 100644 index 000000000..d21d4e9b2 --- /dev/null +++ b/l1-contracts/contracts/eigenda/EigenDAVerifier.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {EigenDARollupUtils} from "@eigenda/eigenda-utils/libraries/EigenDARollupUtils.sol"; +import {IEigenDAServiceManager} from "@eigenda/eigenda-utils/interfaces/IEigenDAServiceManager.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import {BN254} from "eigenlayer-middleware/libraries/BN254.sol"; + +contract EigenDAVerifier is Ownable { + struct BlobInfo { + IEigenDAServiceManager.BlobHeader blobHeader; + EigenDARollupUtils.BlobVerificationProof blobVerificationProof; + } + + IEigenDAServiceManager public EIGEN_DA_SERVICE_MANAGER; + + constructor(address _initialOwner, address _eigenDAServiceManager) { + _transferOwnership(_initialOwner); + EIGEN_DA_SERVICE_MANAGER = IEigenDAServiceManager(_eigenDAServiceManager); + } + + function setServiceManager(address _eigenDAServiceManager) external onlyOwner { + EIGEN_DA_SERVICE_MANAGER = IEigenDAServiceManager(_eigenDAServiceManager); + } + + function decodeBlobHeader( + bytes calldata blobHeader + ) internal pure returns (IEigenDAServiceManager.BlobHeader memory) { + uint32 offset = 0; + // Decode x + uint32 xLen = uint32(bytes4(blobHeader[0:4])); + uint256 x = uint256(bytes32(blobHeader[4:4 + xLen])); + offset += 4 + xLen; + // Decode y + uint32 yLen = uint32(bytes4(blobHeader[offset:4 + offset])); + uint256 y = uint256(bytes32(blobHeader[4 + offset:4 + offset + yLen])); + offset += 4 + yLen; + + BN254.G1Point memory commitment = BN254.G1Point(x, y); + // Decode dataLength + uint32 dataLength = uint32(bytes4(blobHeader[offset:offset + 4])); + offset += 4; + + // Decode quorumBlobParams + uint32 quorumBlobParamsLen = uint32(bytes4(blobHeader[offset:offset + 4])); + IEigenDAServiceManager.QuorumBlobParam[] memory quorumBlobParams = new IEigenDAServiceManager.QuorumBlobParam[]( + quorumBlobParamsLen + ); + offset += 4; + for (uint256 i = 0; i < quorumBlobParamsLen; i++) { + quorumBlobParams[i].quorumNumber = uint8(uint32(bytes4(blobHeader[offset:offset + 4]))); + quorumBlobParams[i].adversaryThresholdPercentage = uint8(uint32(bytes4(blobHeader[offset + 4:offset + 8]))); + quorumBlobParams[i].confirmationThresholdPercentage = uint8( + uint32(bytes4(blobHeader[offset + 8:offset + 12])) + ); + quorumBlobParams[i].chunkLength = uint32(bytes4(blobHeader[offset + 12:offset + 16])); + offset += 16; + } + return IEigenDAServiceManager.BlobHeader(commitment, dataLength, quorumBlobParams); + } + + function decodeBatchHeader( + bytes calldata batchHeader + ) internal pure returns (IEigenDAServiceManager.BatchHeader memory) { + uint32 offset = 0; + // Decode blobHeadersRoot + bytes32 blobHeadersRoot = bytes32(batchHeader[offset:offset + 32]); + offset += 32; + // Decode quorumNumbers + uint32 quorumNumbersLen = uint32(bytes4(batchHeader[offset:offset + 4])); + bytes memory quorumNumbers = batchHeader[offset + 4:offset + 4 + quorumNumbersLen]; + offset += 4 + quorumNumbersLen; + // Decode signedStakeForQuorums + uint32 signedStakeForQuorumsLen = uint32(bytes4(batchHeader[offset:offset + 4])); + bytes memory signedStakeForQuorums = batchHeader[offset + 4:offset + 4 + signedStakeForQuorumsLen]; + offset += 4 + signedStakeForQuorumsLen; + // Decode referenceBlockNumber + uint32 referenceBlockNumber = uint32(bytes4(batchHeader[offset:offset + 4])); + return + IEigenDAServiceManager.BatchHeader( + blobHeadersRoot, + quorumNumbers, + signedStakeForQuorums, + referenceBlockNumber + ); + } + + function decodeBatchMetadata( + bytes calldata batchMetadata + ) internal pure returns (IEigenDAServiceManager.BatchMetadata memory) { + uint32 offset = 0; + // Decode batchHeader + uint32 batchHeaderLen = uint32(bytes4(batchMetadata[offset:offset + 4])); + IEigenDAServiceManager.BatchHeader memory batchHeader = decodeBatchHeader( + batchMetadata[offset + 4:offset + 4 + batchHeaderLen] + ); + offset += 4 + batchHeaderLen; + // Decode signatoryRecordHash + bytes32 signatoryRecordHash = bytes32(batchMetadata[offset:offset + 32]); + offset += 32; + // Decode confirmationBlockNumber + uint32 confirmationBlockNumber = uint32(bytes4(batchMetadata[offset:offset + 4])); + return IEigenDAServiceManager.BatchMetadata(batchHeader, signatoryRecordHash, confirmationBlockNumber); + } + + function decodeBlobVerificationProof( + bytes calldata blobVerificationProof + ) internal pure returns (EigenDARollupUtils.BlobVerificationProof memory) { + // Decode batchId + uint32 batchId = uint32(bytes4(blobVerificationProof[:4])); + // Decode blobIndex + uint32 blobIndex = uint32(bytes4(blobVerificationProof[4:8])); + // Decode batchMetadata + uint32 batchMetadataLen = uint32(bytes4(blobVerificationProof[8:12])); + IEigenDAServiceManager.BatchMetadata memory batchMetadata = decodeBatchMetadata( + blobVerificationProof[12:batchMetadataLen] + ); + uint32 offset = 12 + batchMetadataLen; + // Decode inclusionProof + uint32 inclusionProofLen = uint32(bytes4(blobVerificationProof[offset:offset + 4])); + bytes memory inclusionProof = blobVerificationProof[offset + 4:offset + 4 + inclusionProofLen]; + offset += 4 + inclusionProofLen; + // Decode quorumIndexes + uint32 quorumIndexesLen = uint32(bytes4(blobVerificationProof[offset:offset + 4])); + bytes memory quorumIndexes = blobVerificationProof[offset + 4:offset + 4 + quorumIndexesLen]; + + return + EigenDARollupUtils.BlobVerificationProof(batchId, blobIndex, batchMetadata, inclusionProof, quorumIndexes); + } + + function decodeBlobInfo(bytes calldata blobInfo) internal pure returns (BlobInfo memory) { + uint32 blobHeaderLen = uint32(bytes4(blobInfo[:4])); + IEigenDAServiceManager.BlobHeader memory blobHeader = decodeBlobHeader(blobInfo[4:blobHeaderLen]); + EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof = decodeBlobVerificationProof( + blobInfo[blobHeaderLen:] + ); + return BlobInfo(blobHeader, blobVerificationProof); + } + + function verifyBlob(bytes calldata blobInfo) external view { + BlobInfo memory blob = decodeBlobInfo(blobInfo); + this._verifyBlob(blob); + } + + function _verifyBlob(BlobInfo calldata blobInfo) external view { + require(address(EIGEN_DA_SERVICE_MANAGER) != address(0), "EigenDAVerifier: EIGEN_DA_SERVICE_MANAGER not set"); + EigenDARollupUtils.verifyBlob(blobInfo.blobHeader, EIGEN_DA_SERVICE_MANAGER, blobInfo.blobVerificationProof); + } +} diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 3c3aa6ee8..b59dbeff4 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -15,6 +15,8 @@ import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; +import {EigenDAVerifier} from "../../../eigenda/EigenDAVerifier.sol"; + /// @title zkSync hyperchain Executor contract capable of processing events emitted in the zkSync hyperchain protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -25,6 +27,12 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// @inheritdoc IZkSyncHyperchainBase string public constant override getName = "ExecutorFacet"; + EigenDAVerifier immutable public eigenDAVerifier; + + constructor(address _eigenDAVerifier) { + eigenDAVerifier = EigenDAVerifier(_eigenDAVerifier); + } + /// @dev Process one batch commit using the previous batch StoredBatchInfo /// @dev returns new batch StoredBatchInfo /// @notice Does not change storage @@ -50,9 +58,9 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { bytes32[] memory blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); if (pricingMode == PubdataPricingMode.Validium) { - // In this scenario, pubdataCommitments has the data of the commitment and the pubdataSource, so the len should be higher or equal than 1 require(_newBatch.pubdataCommitments.length >= 1, "EF: v0l"); + eigenDAVerifier.verifyBlob(_newBatch.pubdataCommitments[1:]); for (uint8 i = uint8(SystemLogKey.BLOB_ONE_HASH_KEY); i <= uint8(SystemLogKey.BLOB_SIX_HASH_KEY); i++) { logOutput.blobHashes[i - uint8(SystemLogKey.BLOB_ONE_HASH_KEY)] = bytes32(0); } diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index f4de831a8..85549ccc8 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -33,6 +33,7 @@ import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-de import {L1SharedBridge} from "contracts/bridge/L1SharedBridge.sol"; import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {EigenDAVerifier} from "contracts/eigenda/EigenDAVerifier.sol"; contract DeployL1Script is Script { using stdToml for string; @@ -50,6 +51,7 @@ contract DeployL1Script is Script { address blobVersionedHashRetriever; address validatorTimelock; address create2Factory; + address eigendaVerifier; } struct BridgehubDeployedAddresses { @@ -113,6 +115,7 @@ contract DeployL1Script is Script { bytes diamondCutData; bytes32 bootloaderHash; bytes32 defaultAAHash; + address eigenServiceManager; } struct TokensConfig { @@ -135,6 +138,7 @@ contract DeployL1Script is Script { deployDefaultUpgrade(); deployGenesisUpgrade(); deployValidatorTimelock(); + deployEigenDAVerifier(); deployGovernance(); deployChainAdmin(); @@ -209,6 +213,7 @@ contract DeployL1Script is Script { config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); + config.contracts.eigenServiceManager = toml.readAddress("$.contracts.eigen_service_manager"); } function instantiateCreate2Factory() internal { @@ -280,6 +285,16 @@ contract DeployL1Script is Script { addresses.validatorTimelock = contractAddress; } + function deployEigenDAVerifier() internal { + bytes memory bytecode = abi.encodePacked( + type(EigenDAVerifier).creationCode, + abi.encode(config.deployerAddress, config.contracts.eigenServiceManager) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("EigenDAVerifier deployed at:", contractAddress); + addresses.eigendaVerifier = contractAddress; + } + function deployGovernance() internal { bytes memory bytecode = abi.encodePacked( type(Governance).creationCode, @@ -347,7 +362,7 @@ contract DeployL1Script is Script { } function deployStateTransitionDiamondFacets() internal { - address executorFacet = deployViaCreate2(type(ExecutorFacet).creationCode); + address executorFacet = deployViaCreate2(abi.encodePacked(type(ExecutorFacet).creationCode,abi.encode(addresses.eigendaVerifier))); console.log("ExecutorFacet deployed at:", executorFacet); addresses.stateTransition.executorFacet = executorFacet; @@ -706,6 +721,7 @@ contract DeployL1Script is Script { ); vm.serializeAddress("deployed_addresses", "validator_timelock_addr", addresses.validatorTimelock); vm.serializeAddress("deployed_addresses", "chain_admin", addresses.chainAdmin); + vm.serializeAddress("deployed_addresses", "eigenda_verifier_addr", addresses.eigendaVerifier); vm.serializeString("deployed_addresses", "bridgehub", bridgehub); vm.serializeString("deployed_addresses", "state_transition", stateTransition); string memory deployedAddresses = vm.serializeString("deployed_addresses", "bridges", bridges); diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 82910dad0..b931fc47b 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -5,7 +5,11 @@ libs = ['node_modules', 'lib'] remappings = [ "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", - "l2-contracts/=../l2-contracts/contracts/" + "l2-contracts/=../l2-contracts/contracts/", + "@eigenda/eigenda-utils/libraries/=lib/eigenda/contracts/src/libraries/", + "@eigenda/eigenda-utils/interfaces/=lib/eigenda/contracts/src/interfaces/", + "eigenlayer-middleware/=lib/eigenda/contracts/lib/eigenlayer-middleware/src/", + "eigenlayer-core/=lib/eigenda/contracts/lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/" ] allow_paths = ["../l2-contracts/contracts"] fs_permissions = [ diff --git a/l1-contracts/lib/eigenda b/l1-contracts/lib/eigenda new file mode 160000 index 000000000..e30979abf --- /dev/null +++ b/l1-contracts/lib/eigenda @@ -0,0 +1 @@ +Subproject commit e30979abf8668eb3aa8033d1ffd32b15eaf9776b