diff --git a/packages/protocol/contracts/L1/ProofVerifier.sol b/packages/protocol/contracts/L1/ProofVerifier.sol new file mode 100644 index 00000000000..cc8bbc8e9d7 --- /dev/null +++ b/packages/protocol/contracts/L1/ProofVerifier.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +// +// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮ +// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃ +// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮ +// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫ +// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃ +// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ +pragma solidity ^0.8.9; + +import "../thirdparty/LibMerkleTrie.sol"; +import "../libs/LibZKP.sol"; + +/// @author dantaik +interface IProofVerifier { + function verifyZKP( + bytes memory verificationKey, + bytes calldata zkproof, + bytes32 blockHash, + address prover, + bytes32 txListHash + ) external pure returns (bool verified); + + function verifyMKP( + bytes memory key, + bytes memory value, + bytes memory proof, + bytes32 root + ) external pure returns (bool verified); +} + +contract ProofVerifier is IProofVerifier { + function verifyZKP( + bytes memory verificationKey, + bytes calldata zkproof, + bytes32 blockHash, + address prover, + bytes32 txListHash + ) external pure returns (bool) { + return + LibZKP.verify({ + verificationKey: verificationKey, + zkproof: zkproof, + blockHash: blockHash, + prover: prover, + txListHash: txListHash + }); + } + + function verifyMKP( + bytes memory key, + bytes memory value, + bytes memory proof, + bytes32 root + ) external pure returns (bool) { + return + LibMerkleTrie.verifyInclusionProof({ + _key: key, + _value: value, + _proof: proof, + _root: root + }); + } +} diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index 66c01c1c79b..795a3380b91 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -41,7 +41,6 @@ library TaikoData { uint64 boostrapDiscountHalvingPeriod; uint64 initialUncleDelay; bool enableTokenomics; - bool skipProofValidation; } struct BlockMetadata { diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index c29c18ab06e..b0afe88b99b 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -8,6 +8,7 @@ // ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ pragma solidity ^0.8.9; +import {IProofVerifier} from "../ProofVerifier.sol"; import "../../common/AddressResolver.sol"; import "../../common/ConfigManager.sol"; import "../../libs/LibAnchorSignature.sol"; @@ -15,9 +16,7 @@ import "../../libs/LibBlockHeader.sol"; import "../../libs/LibReceiptDecoder.sol"; import "../../libs/LibTxDecoder.sol"; import "../../libs/LibTxUtils.sol"; -import "../../libs/LibZKP.sol"; import "../../thirdparty/LibBytesUtils.sol"; -import "../../thirdparty/LibMerkleTrie.sol"; import "../../thirdparty/LibRLPWriter.sol"; import "./LibUtils.sol"; @@ -107,40 +106,43 @@ library LibProving { ); } - if (!config.skipProofValidation) { - // Check anchor tx is the 1st tx in the block - require( - LibMerkleTrie.verifyInclusionProof({ - _key: LibRLPWriter.writeUint(0), - _value: anchorTx, - _proof: evidence.proofs[zkProofsPerBlock], - _root: evidence.header.transactionsRoot - }), - "L1:tx:proof" - ); + IProofVerifier proofVerifier = IProofVerifier( + resolver.resolve("proof_verifier") + ); - // Check anchor tx does not throw + // Check anchor tx is the 1st tx in the block + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: anchorTx, + proof: evidence.proofs[zkProofsPerBlock], + root: evidence.header.transactionsRoot + }), + "L1:tx:proof" + ); - LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder - .decodeReceipt(anchorReceipt); + // Check anchor tx does not throw - require(receipt.status == 1, "L1:receipt:status"); - require( - LibMerkleTrie.verifyInclusionProof({ - _key: LibRLPWriter.writeUint(0), - _value: anchorReceipt, - _proof: evidence.proofs[zkProofsPerBlock + 1], - _root: evidence.header.receiptsRoot - }), - "L1:receipt:proof" - ); - } + LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder + .decodeReceipt(anchorReceipt); + + require(receipt.status == 1, "L1:receipt:status"); + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: anchorReceipt, + proof: evidence.proofs[zkProofsPerBlock + 1], + root: evidence.header.receiptsRoot + }), + "L1:receipt:proof" + ); // ZK-prove block and mark block proven to be valid. _proveBlock({ state: state, config: config, resolver: resolver, + proofVerifier: proofVerifier, evidence: evidence, target: evidence.meta, blockHashOverride: 0 @@ -172,14 +174,29 @@ library LibProving { "L1:proof:size" ); - if (!config.skipProofValidation) { - // Check the 1st receipt is for an InvalidateBlock tx with - // a BlockInvalidated event - LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder - .decodeReceipt(invalidateBlockReceipt); - require(receipt.status == 1, "L1:receipt:status"); - require(receipt.logs.length == 1, "L1:receipt:logsize"); + IProofVerifier proofVerifier = IProofVerifier( + resolver.resolve("proof_verifier") + ); + // Check the event is the first one in the throw-away block + require( + proofVerifier.verifyMKP({ + key: LibRLPWriter.writeUint(0), + value: invalidateBlockReceipt, + proof: evidence.proofs[config.zkProofsPerBlock], + root: evidence.header.receiptsRoot + }), + "L1:receipt:proof" + ); + + // Check the 1st receipt is for an InvalidateBlock tx with + // a BlockInvalidated event + LibReceiptDecoder.Receipt memory receipt = LibReceiptDecoder + .decodeReceipt(invalidateBlockReceipt); + require(receipt.status == 1, "L1:receipt:status"); + require(receipt.logs.length == 1, "L1:receipt:logsize"); + + { LibReceiptDecoder.Log memory log = receipt.logs[0]; require( log.contractAddress == @@ -193,17 +210,6 @@ library LibProving { log.topics[1] == target.txListHash, "L1:receipt:topics" ); - - // Check the event is the first one in the throw-away block - require( - LibMerkleTrie.verifyInclusionProof({ - _key: LibRLPWriter.writeUint(0), - _value: invalidateBlockReceipt, - _proof: evidence.proofs[config.zkProofsPerBlock], - _root: evidence.header.receiptsRoot - }), - "L1:receipt:proof" - ); } // ZK-prove block and mark block proven as invalid. @@ -211,6 +217,7 @@ library LibProving { state: state, config: config, resolver: resolver, + proofVerifier: proofVerifier, evidence: evidence, target: target, blockHashOverride: LibUtils.BLOCK_DEADEND_HASH @@ -221,6 +228,7 @@ library LibProving { TaikoData.State storage state, TaikoData.Config memory config, AddressResolver resolver, + IProofVerifier proofVerifier, Evidence memory evidence, TaikoData.BlockMetadata memory target, bytes32 blockHashOverride @@ -238,8 +246,8 @@ library LibProving { bytes32 blockHash = evidence.header.hashBlockHeader(); for (uint256 i = 0; i < config.zkProofsPerBlock; ++i) { - if (!config.skipProofValidation) { - LibZKP.verify({ + require( + proofVerifier.verifyZKP({ verificationKey: ConfigManager( resolver.resolve("config_manager") ).getValue(string(abi.encodePacked("zk_vkey_", i))), @@ -247,8 +255,9 @@ library LibProving { blockHash: blockHash, prover: evidence.prover, txListHash: evidence.meta.txListHash - }); - } + }), + "L1:zkp" + ); } _markBlockProven({ diff --git a/packages/protocol/contracts/libs/LibSharedConfig.sol b/packages/protocol/contracts/libs/LibSharedConfig.sol index bfd693cca50..80e83fcf614 100644 --- a/packages/protocol/contracts/libs/LibSharedConfig.sol +++ b/packages/protocol/contracts/libs/LibSharedConfig.sol @@ -43,8 +43,7 @@ library LibSharedConfig { proofTimeCap: 60 minutes, boostrapDiscountHalvingPeriod: 180 days, initialUncleDelay: 60 minutes, - enableTokenomics: false, - skipProofValidation: false + enableTokenomics: false }); } } diff --git a/packages/protocol/contracts/libs/LibZKP.sol b/packages/protocol/contracts/libs/LibZKP.sol index de3ef163189..6486b19f214 100644 --- a/packages/protocol/contracts/libs/LibZKP.sol +++ b/packages/protocol/contracts/libs/LibZKP.sol @@ -19,7 +19,7 @@ library LibZKP { bytes32 blockHash, address prover, bytes32 txListHash - ) public pure { + ) internal pure returns (bool verified) { // TODO } } diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index c4ae8b8b290..3df5886ad2d 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -8,9 +8,10 @@ // ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯ pragma solidity ^0.8.9; +import {IProofVerifier} from "../../L1/ProofVerifier.sol"; import "../../L1/TaikoL1.sol"; -contract TestTaikoL1NoTokenomicsNoProofValidation is TaikoL1 { +contract TestTaikoL1 is TaikoL1, IProofVerifier { function getConfig() public pure @@ -49,6 +50,24 @@ contract TestTaikoL1NoTokenomicsNoProofValidation is TaikoL1 { config.boostrapDiscountHalvingPeriod = 180 days; config.initialUncleDelay = 1 minutes; config.enableTokenomics = false; - config.skipProofValidation = true; + } + + function verifyZKP( + bytes memory /*verificationKey*/, + bytes calldata /*zkproof*/, + bytes32 /*blockHash*/, + address /*prover*/, + bytes32 /*txListHash*/ + ) public pure override returns (bool) { + return true; + } + + function verifyMKP( + bytes memory /*key*/, + bytes memory /*value*/, + bytes memory /*proof*/, + bytes32 /*root*/ + ) public pure override returns (bool) { + return true; } } diff --git a/packages/protocol/tasks/deploy_L1.ts b/packages/protocol/tasks/deploy_L1.ts index 06fe2a836e6..3a4aab593ef 100644 --- a/packages/protocol/tasks/deploy_L1.ts +++ b/packages/protocol/tasks/deploy_L1.ts @@ -81,6 +81,13 @@ export async function deployContracts(hre: any) { // AddressManager const AddressManager = await utils.deployContract(hre, "AddressManager"); await utils.waitTx(hre, await AddressManager.init()); + + const ProofVerifier = await utils.deployContract(hre, "ProofVerifier"); + await utils.waitTx( + hre, + await AddressManager.setAddress(`${chainId}.proof_verifier`, ProofVerifier.address) + ); + await utils.waitTx( hre, await AddressManager.setAddress(`${chainId}.dao_vault`, daoVault) @@ -195,7 +202,6 @@ export async function deployContracts(hre: any) { } async function deployBaseLibs(hre: any) { - const libZKP = await utils.deployContract(hre, "LibZKP"); const libReceiptDecoder = await utils.deployContract( hre, "LibReceiptDecoder" @@ -206,7 +212,6 @@ async function deployBaseLibs(hre: any) { const libProposing = await utils.deployContract(hre, "LibProposing", {}); const libProving = await utils.deployContract(hre, "LibProving", { - LibZKP: libZKP.address, LibReceiptDecoder: libReceiptDecoder.address, LibTxDecoder: libTxDecoder.address, }); diff --git a/packages/protocol/test/L1/TaikoL1.test.ts b/packages/protocol/test/L1/TaikoL1.test.ts index 18763fc237d..402f9d91f73 100644 --- a/packages/protocol/test/L1/TaikoL1.test.ts +++ b/packages/protocol/test/L1/TaikoL1.test.ts @@ -21,10 +21,6 @@ describe("TaikoL1", function () { await ethers.getContractFactory("LibTxDecoder") ).deploy(); - const libZKP = await ( - await ethers.getContractFactory("LibZKP") - ).deploy(); - const libProposing = await ( await ethers.getContractFactory("LibProposing") ).deploy(); @@ -33,8 +29,7 @@ describe("TaikoL1", function () { await ethers.getContractFactory("LibProving", { libraries: { LibReceiptDecoder: libReceiptDecoder.address, - LibTxDecoder: libTxDecoder.address, - LibZKP: libZKP.address, + LibTxDecoder: libTxDecoder.address }, }) ).deploy(); @@ -47,7 +42,7 @@ describe("TaikoL1", function () { const feeBase = BigNumber.from(10).pow(18); taikoL1 = await ( await ethers.getContractFactory( - "TestTaikoL1NoTokenomicsNoProofValidation", + "TestTaikoL1", { libraries: { LibVerifying: libVerifying.address,