diff --git a/packages/protocol/contracts/layer1/verifiers/Risc0Verifier.sol b/packages/protocol/contracts/layer1/verifiers/Risc0Verifier.sol index dcbac258239..b157b6afc6e 100644 --- a/packages/protocol/contracts/layer1/verifiers/Risc0Verifier.sol +++ b/packages/protocol/contracts/layer1/verifiers/Risc0Verifier.sol @@ -24,7 +24,11 @@ contract Risc0Verifier is EssentialContract, IVerifier { /// @param trusted True if trusted, false otherwise event ImageTrusted(bytes32 imageId, bool trusted); - error RISC_ZERO_INVALID_IMAGE_ID(); + /// @dev Emitted when a proof is verified + event ProofVerified(bytes32 metaHash, bytes32 publicInputHash); + + error RISC_ZERO_INVALID_BLOCK_PROOF_IMAGE_ID(); + error RISC_ZERO_INVALID_AGGREGATION_IMAGE_ID(); error RISC_ZERO_INVALID_PROOF(); /// @notice Initializes the contract with the provided address manager. @@ -59,7 +63,7 @@ contract Risc0Verifier is EssentialContract, IVerifier { (bytes memory seal, bytes32 imageId) = abi.decode(_proof.data, (bytes, bytes32)); if (!isImageTrusted[imageId]) { - revert RISC_ZERO_INVALID_IMAGE_ID(); + revert RISC_ZERO_INVALID_BLOCK_PROOF_IMAGE_ID(); } bytes32 publicInputHash = LibPublicInput.hashPublicInputs( @@ -80,13 +84,52 @@ contract Risc0Verifier is EssentialContract, IVerifier { /// @inheritdoc IVerifier function verifyBatchProof( - ContextV2[] calldata, /*_ctxs*/ - TaikoData.TierProof calldata /*_proof*/ + ContextV2[] calldata _ctxs, + TaikoData.TierProof calldata _proof ) external - pure - notImplemented - { } + { + // Decode will throw if not proper length/encoding + (bytes memory seal, bytes32 blockImageId, bytes32 aggregationImageId) = + abi.decode(_proof.data, (bytes, bytes32, bytes32)); + + // Check if the aggregation program is trusted + if (!isImageTrusted[aggregationImageId]) { + revert RISC_ZERO_INVALID_AGGREGATION_IMAGE_ID(); + } + // Check if the block proving program is trusted + if (!isImageTrusted[blockImageId]) { + revert RISC_ZERO_INVALID_BLOCK_PROOF_IMAGE_ID(); + } + + // Collect public inputs + bytes32[] memory publicInputs = new bytes32[](_ctxs.length + 1); + // First public input is the block proving program key + publicInputs[0] = blockImageId; + // All other inputs are the block program public inputs (a single 32 byte value) + for (uint256 i; i < _ctxs.length; ++i) { + publicInputs[i + 1] = LibPublicInput.hashPublicInputs( + _ctxs[i].tran, + address(this), + address(0), + _ctxs[i].prover, + _ctxs[i].metaHash, + taikoChainId() + ); + emit ProofVerified(_ctxs[i].metaHash, publicInputs[i + 1]); + } + + // journalDigest is the sha256 hash of the hashed public input + bytes32 journalDigest = sha256(abi.encodePacked(publicInputs)); + + // call risc0 verifier contract + (bool success,) = resolve(LibStrings.B_RISCZERO_GROTH16_VERIFIER, false).staticcall( + abi.encodeCall(IRiscZeroVerifier.verify, (seal, aggregationImageId, journalDigest)) + ); + if (!success) { + revert RISC_ZERO_INVALID_PROOF(); + } + } function taikoChainId() internal view virtual returns (uint64) { return ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; diff --git a/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol b/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol index 4a562d94b82..d903abf9757 100644 --- a/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol +++ b/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol @@ -131,10 +131,10 @@ contract SgxVerifier is EssentialContract, IVerifier { if (!verified) revert SGX_INVALID_ATTESTATION(); - address[] memory _address = new address[](1); - _address[0] = address(bytes20(_attestation.localEnclaveReport.reportData)); + address[] memory addresses = new address[](1); + addresses[0] = address(bytes20(_attestation.localEnclaveReport.reportData)); - return _addInstances(_address, false)[0]; + return _addInstances(addresses, false)[0]; } /// @inheritdoc IVerifier @@ -172,14 +172,51 @@ contract SgxVerifier is EssentialContract, IVerifier { /// @inheritdoc IVerifier function verifyBatchProof( - ContextV2[] calldata, /*_ctxs*/ - TaikoData.TierProof calldata /*_proof*/ + ContextV2[] calldata _ctxs, + TaikoData.TierProof calldata _proof ) external - view - notImplemented onlyFromNamedEither(LibStrings.B_TAIKO, LibStrings.B_TIER_TEE_ANY) - { } + { + // Size is: 109 bytes + // 4 bytes + 20 bytes + 20 bytes + 65 bytes (signature) = 109 + if (_proof.data.length != 109) revert SGX_INVALID_PROOF(); + + uint32 id = uint32(bytes4(_proof.data[:4])); + address oldInstance = address(bytes20(_proof.data[4:24])); + address newInstance = address(bytes20(_proof.data[24:44])); + bytes memory signature = _proof.data[44:]; + + // Collect public inputs + bytes32[] memory publicInputs = new bytes32[](_ctxs.length + 2); + // First public input is the current instance public key + publicInputs[0] = bytes32(uint256(uint160(oldInstance))); + publicInputs[1] = bytes32(uint256(uint160(newInstance))); + // All other inputs are the block program public inputs (a single 32 byte value) + for (uint256 i; i < _ctxs.length; ++i) { + // TODO: For now this assumes the new instance public key to remain the same + publicInputs[i + 2] = LibPublicInput.hashPublicInputs( + _ctxs[i].tran, + address(this), + newInstance, + _ctxs[i].prover, + _ctxs[i].metaHash, + taikoChainId() + ); + } + + bytes32 signatureHash = keccak256(abi.encodePacked(publicInputs)); + // Verify the blocks + if (oldInstance != ECDSA.recover(signatureHash, signature)) { + revert SGX_INVALID_PROOF(); + } + + if (!_isInstanceValid(id, oldInstance)) revert SGX_INVALID_INSTANCE(); + + if (newInstance != oldInstance && newInstance != address(0)) { + _replaceInstance(id, oldInstance, newInstance); + } + } function taikoChainId() internal view virtual returns (uint64) { return ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; diff --git a/packages/protocol/test/layer1/verifiers/Risc0Verifier.t.sol b/packages/protocol/test/layer1/verifiers/Risc0Verifier.t.sol index 67915c77fa3..fcb63f057e7 100644 --- a/packages/protocol/test/layer1/verifiers/Risc0Verifier.t.sol +++ b/packages/protocol/test/layer1/verifiers/Risc0Verifier.t.sol @@ -105,7 +105,7 @@ contract TestRiscZeroVerifier is TaikoL1TestBase { (IVerifier.Context memory ctx, TaikoData.Transition memory transition) = _getDummyContextAndTransition(); - vm.expectRevert(Risc0Verifier.RISC_ZERO_INVALID_IMAGE_ID.selector); + vm.expectRevert(Risc0Verifier.RISC_ZERO_INVALID_BLOCK_PROOF_IMAGE_ID.selector); rv.verifyProof(ctx, transition, proof); vm.stopPrank(); diff --git a/packages/protocol/test/layer1/verifiers/RiscZeroGroth16Verifier.t.sol b/packages/protocol/test/layer1/verifiers/RiscZeroGroth16Verifier.t.sol index 5e3667e58e3..05ba5dab8e2 100644 --- a/packages/protocol/test/layer1/verifiers/RiscZeroGroth16Verifier.t.sol +++ b/packages/protocol/test/layer1/verifiers/RiscZeroGroth16Verifier.t.sol @@ -97,4 +97,64 @@ contract RiscZeroGroth16VerifierTest is TaikoL1TestBase { graffiti: 0x8008500000000000000000000000000000000000000000000000000000000000 }); } + + function test_risc0_verifyBatchProof() public { + vm.startPrank(Emma); + + bytes32 aggProofImageId = 0x83e7411adcc296e0a021ff032a868434aa2a519b9d11ad44d11d443832280b44; + bytes32 blkProofImageId = 0x28879b90699846864c97f8f32e1b12aabd8ce13135302345d6ad242fa81ab40d; + + // proof generation elf + rv.setImageIdTrusted(aggProofImageId, true); + // proof aggregation elf + rv.setImageIdTrusted(blkProofImageId, true); + + vm.startPrank(address(L1)); + + // Context + IVerifier.ContextV2[] memory ctxs = new IVerifier.ContextV2[](2); + ctxs[0] = IVerifier.ContextV2({ + metaHash: 0x207b2833fb6d804612da24d8785b870a19c7a3f25fa4aaeb9799cd442d65b031, + blobHash: 0x01354e8725e60ad91b32ec4ab19158572a0a5b06b2d4d83f6269c9a7d068f49b, + prover: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + msgSender: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + blockId: 393_333, + isContesting: false, + blobUsed: true, + tran: TaikoData.Transition({ + parentHash: 0xce519622a374dc014c005d7857de26d952751a9067d3e23ffe14da247aa8a399, + blockHash: 0x941d557653da2214cbf3d30af8d9cadbc7b5f77b6c3e48bca548eba04eb9cd79, + stateRoot: 0x4203a2fd98d268d272acb24d91e25055a779b443ff3e732f2cee7abcf639b5e9, + graffiti: 0x8008500000000000000000000000000000000000000000000000000000000000 + }) + }); + ctxs[1] = IVerifier.ContextV2({ + metaHash: 0x946ba1a9c02fc2f01da49e31cb5be83c118193d0389987c6be616ce76426b44d, + blobHash: 0x01abac8c1fb54f87ff7b0cbf14259b9d5ee7a8de458c587dd6eda43ef8354b4f, + prover: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + msgSender: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + blockId: 393_334, + isContesting: false, + blobUsed: true, + tran: TaikoData.Transition({ + parentHash: 0x941d557653da2214cbf3d30af8d9cadbc7b5f77b6c3e48bca548eba04eb9cd79, + blockHash: 0xc0dad38646ab264be30995b7b7fd02db65e7115126fb52bfad94c0fc9572287c, + stateRoot: 0x222061caab95b6bd0f8dd398088030979efbe56e282cd566f7abd77838558eb9, + graffiti: 0x8008500000000000000000000000000000000000000000000000000000000000 + }) + }); + + bytes memory seal = + hex"310fe59810425afc4ed2bae56dfd76e9045f6cd41da30ae8f07a239e86fdb157bf37b0f51b937cb8deccab0d201623d530d0800c208f66dad3f6a38bc1df34408994dec1179209a5f94411e015b20e723512150cfb7e295debeb7ef4f8186cddcf19ba6527ee0d2a0fb8825568682a2fe48e2f73fe9fa052379824751c3bd3f1353f44fe1857e07f5b4801846637b68eafb93aba0c8de8fdfffc76af62a513966f92d9750a977bce0568eb7438fa3497848bfce3e5fd815d9c24b4600e12d0d405d1fd76301ccf27547bddd49a2fa12d1a414f49c2030d0cdf29a87684964a171eefb7e82a5f86acbaacd8cd24d6c3bab06a568f4869087e825ee79237770f23315f3c5c"; + // TierProof + TaikoData.TierProof memory proof = TaikoData.TierProof({ + tier: 100, + data: abi.encode(seal, blkProofImageId, aggProofImageId) + }); + + // `verifyProof()` + rv.verifyBatchProof(ctxs, proof); + + vm.stopPrank(); + } } diff --git a/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol b/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol index 2618a640bae..5631423fddb 100644 --- a/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol +++ b/packages/protocol/test/layer1/verifiers/SgxVerifier.t.sol @@ -380,4 +380,59 @@ contract TestSgxVerifier is TaikoL1TestBase, AttestationBase { vm.stopPrank(); } + + // Test `verifyBatchProof()` happy path + function test_verifyBatchProofs() public { + // setup instances + address newInstance = address(0x6Aa1108c1903E3AeF092FF46E4C506fD3ac567c0); + address[] memory instances = new address[](1); + instances[0] = newInstance; + uint256[] memory ids = sv.addInstances(instances); + console.log("Instance ID: ", ids[0]); + + vm.startPrank(address(L1)); + + // Context + IVerifier.ContextV2[] memory ctxs = new IVerifier.ContextV2[](2); + ctxs[0] = IVerifier.ContextV2({ + metaHash: 0x207b2833fb6d804612da24d8785b870a19c7a3f25fa4aaeb9799cd442d65b031, + blobHash: 0x01354e8725e60ad91b32ec4ab19158572a0a5b06b2d4d83f6269c9a7d068f49b, + prover: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + msgSender: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + blockId: 393_333, + isContesting: false, + blobUsed: true, + tran: TaikoData.Transition({ + parentHash: 0xce519622a374dc014c005d7857de26d952751a9067d3e23ffe14da247aa8a399, + blockHash: 0x941d557653da2214cbf3d30af8d9cadbc7b5f77b6c3e48bca548eba04eb9cd79, + stateRoot: 0x4203a2fd98d268d272acb24d91e25055a779b443ff3e732f2cee7abcf639b5e9, + graffiti: 0x8008500000000000000000000000000000000000000000000000000000000000 + }) + }); + ctxs[1] = IVerifier.ContextV2({ + metaHash: 0x946ba1a9c02fc2f01da49e31cb5be83c118193d0389987c6be616ce76426b44d, + blobHash: 0x01abac8c1fb54f87ff7b0cbf14259b9d5ee7a8de458c587dd6eda43ef8354b4f, + prover: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + msgSender: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8, + blockId: 393_334, + isContesting: false, + blobUsed: true, + tran: TaikoData.Transition({ + parentHash: 0x941d557653da2214cbf3d30af8d9cadbc7b5f77b6c3e48bca548eba04eb9cd79, + blockHash: 0xc0dad38646ab264be30995b7b7fd02db65e7115126fb52bfad94c0fc9572287c, + stateRoot: 0x222061caab95b6bd0f8dd398088030979efbe56e282cd566f7abd77838558eb9, + graffiti: 0x8008500000000000000000000000000000000000000000000000000000000000 + }) + }); + + // TierProof + bytes memory data = + hex"000000016aa1108c1903e3aef092ff46e4c506fd3ac567c06aa1108c1903e3aef092ff46e4c506fd3ac567c0dda91ea274c36678a0680bae65216b40bd935e646b6364ea669a6de9b58e0cd11e1c1b86765f98ac5a3113fdc08296aa663378e8e2e44cf08db7a4ba6e5f00f21b"; + TaikoData.TierProof memory proof = TaikoData.TierProof({ tier: 0, data: data }); + + // `verifyProof()` + sv.verifyBatchProof(ctxs, proof); + + vm.stopPrank(); + } }