From e73d2eaff2df486869b1592c5c68c1cefae533de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Mon, 29 Jul 2024 10:48:52 +0200 Subject: [PATCH 1/7] Add SP1 verification support --- .../contracts/verifiers/IVerifier.sol | 25 +++++ .../contracts/verifiers/SP1Verifier.sol | 97 +++++++++++++++++++ .../verifiers/libs/LibPublicInput.sol | 30 +++++- 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 packages/protocol/contracts/verifiers/SP1Verifier.sol diff --git a/packages/protocol/contracts/verifiers/IVerifier.sol b/packages/protocol/contracts/verifiers/IVerifier.sol index a3e0091fcd2..ff275480c24 100644 --- a/packages/protocol/contracts/verifiers/IVerifier.sol +++ b/packages/protocol/contracts/verifiers/IVerifier.sol @@ -28,3 +28,28 @@ interface IVerifier { ) external; } + +/// @title SP1 Verifier Interface +/// @author Succinct Labs +/// @notice This interface is for the deployed SP1 Verifier and 100% brought over from : +/// https://github.com/succinctlabs/sp1-contracts/blob/main/contracts/src/ISP1Verifier.sol +interface ISP1Verifier { + /// @notice Verifies a proof with given public values and vkey. + /// @dev It is expected that the first 4 bytes of proofBytes must match the first 4 bytes of + /// target verifier's VERIFIER_HASH. + /// @param programVKey The verification key for the RISC-V program. + /// @param publicValues The public values encoded as bytes. + /// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes. + function verifyProof( + bytes32 programVKey, + bytes calldata publicValues, + bytes calldata proofBytes + ) + external + view; +} + +interface ISP1VerifierWithHash is ISP1Verifier { + /// @notice Returns the hash of the verifier. + function VERIFIER_HASH() external pure returns (bytes32); +} diff --git a/packages/protocol/contracts/verifiers/SP1Verifier.sol b/packages/protocol/contracts/verifiers/SP1Verifier.sol new file mode 100644 index 00000000000..c5fa199ea8a --- /dev/null +++ b/packages/protocol/contracts/verifiers/SP1Verifier.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; +import "../L1/ITaikoL1.sol"; +import "./IVerifier.sol"; +import "./libs/LibPublicInput.sol"; + +/// @title SuccinctVerifier +/// @custom:security-contact security@taiko.xyz +contract SP1Verifier is EssentialContract, IVerifier { + /// @notice The address of the SP1 verifier contract. + /// @dev This can either be a specific SP1Verifier for a specific version, or the + /// SP1VerifierGateway which can be used to verify proofs for any version of SP1. + /// For the list of supported verifiers on each chain, see: + /// https://github.com/succinctlabs/sp1-contracts/tree/main/contracts/deployments + address public verifier; + + /// @notice The verification keys mappings for the proving programs. + mapping(bytes32 provingProgramVKey => bool trusted) public isProgramTrusted; + + uint256[48] private __gap; + + /// @dev Emitted when a trusted image is set / unset. + /// @param programVKey The id of the image + /// @param trusted The block's assigned prover. + event ProgramTrusted(bytes32 programVKey, bool trusted); + + /// @dev Emitted when a new verifier address is set. + /// @param verifier The address of the verifier. + event NewVerifierAddress(address verifier); + + error SP1_INVALID_PROGRAM_VKEY(); + + /// @notice Initializes the contract with the provided address manager. + /// @param _owner The address of the owner. + /// @param _addressManager The address of the AddressManager. + /// @param _verifier The address of the SP1Verifiers. + function init( + address _owner, + address _addressManager, + address _verifier + ) + external + initializer + { + __Essential_init(_owner, _addressManager); + + verifier = _verifier; + } + + /// @notice Sets/unsets an the program's verification key as trusted entity + /// @param _programVKey The verification key of the program. + /// @param _trusted True if trusted, false otherwise. + function setProgramTrusted(bytes32 _programVKey, bool _trusted) external onlyOwner { + isProgramTrusted[_programVKey] = _trusted; + + emit ProgramTrusted(_programVKey, _trusted); + } + + /// @notice Sets the verifier contract. + /// @param _verifier The address of the verifier contract. + function setVerifierContract(address _verifier) external onlyOwner { + verifier = _verifier; + + emit NewVerifierAddress(_verifier); + } + + /// @inheritdoc IVerifier + function verifyProof( + Context calldata _ctx, + TaikoData.Transition calldata _tran, + TaikoData.TierProof calldata _proof + ) + external + view + { + // Do not run proof verification to contest an existing proof + if (_ctx.isContesting) return; + + // Decode will throw if not proper length/encoding + (bytes32 programVKey, bytes memory proof) = abi.decode(_proof.data, (bytes32, bytes)); + + if (!isProgramTrusted[programVKey]) { + revert SP1_INVALID_PROGRAM_VKEY(); + } + + uint64 chainId = ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; + + bytes memory encodedPublicInput = LibPublicInput.abiEncodePublicInputs( + _tran, address(this), address(0), _ctx.prover, _ctx.metaHash, chainId + ); + + ISP1Verifier(verifier).verifyProof(programVKey, encodedPublicInput, proof); + } +} diff --git a/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol b/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol index 4bf3a6b7d1a..a699e5ad838 100644 --- a/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol +++ b/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol @@ -30,9 +30,35 @@ library LibPublicInput { returns (bytes32) { return keccak256( - abi.encode( - "VERIFY_PROOF", _chainId, _verifierContract, _tran, _newInstance, _prover, _metaHash + abiEncodePublicInputs( + _tran, _verifierContract, _newInstance, _prover, _metaHash, _chainId ) ); } + + /// @notice Abi encode the public input for the proof verification. + /// @param _tran The transition to verify. + /// @param _verifierContract The contract address which as current verifier. + /// @param _newInstance The new instance address. For SGX it is the new signer address, for ZK + /// this variable is not used and must have value address(0). + /// @param _prover The prover address. + /// @param _metaHash The meta hash. + /// @param _chainId The chain id. + /// @return The public input hash. + function abiEncodePublicInputs( + TaikoData.Transition memory _tran, + address _verifierContract, + address _newInstance, + address _prover, + bytes32 _metaHash, + uint64 _chainId + ) + internal + pure + returns (bytes memory) + { + return abi.encode( + "VERIFY_PROOF", _chainId, _verifierContract, _tran, _newInstance, _prover, _metaHash + ); + } } From 1884ca24bc2cc6dcc23bdc99955c79969cb646fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Mon, 29 Jul 2024 15:44:15 +0200 Subject: [PATCH 2/7] encode in place --- .../contracts/verifiers/SP1Verifier.sol | 5 ++-- .../verifiers/libs/LibPublicInput.sol | 30 ++----------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/packages/protocol/contracts/verifiers/SP1Verifier.sol b/packages/protocol/contracts/verifiers/SP1Verifier.sol index c5fa199ea8a..57f9a49398c 100644 --- a/packages/protocol/contracts/verifiers/SP1Verifier.sol +++ b/packages/protocol/contracts/verifiers/SP1Verifier.sol @@ -88,10 +88,11 @@ contract SP1Verifier is EssentialContract, IVerifier { uint64 chainId = ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; - bytes memory encodedPublicInput = LibPublicInput.abiEncodePublicInputs( + // Need to be converted from bytes32 to bytes + bytes32 hashedPublicInput = LibPublicInput.hashPublicInputs( _tran, address(this), address(0), _ctx.prover, _ctx.metaHash, chainId ); - ISP1Verifier(verifier).verifyProof(programVKey, encodedPublicInput, proof); + ISP1Verifier(verifier).verifyProof(programVKey, abi.encode(hashedPublicInput), proof); } } diff --git a/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol b/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol index a699e5ad838..4bf3a6b7d1a 100644 --- a/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol +++ b/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol @@ -30,35 +30,9 @@ library LibPublicInput { returns (bytes32) { return keccak256( - abiEncodePublicInputs( - _tran, _verifierContract, _newInstance, _prover, _metaHash, _chainId + abi.encode( + "VERIFY_PROOF", _chainId, _verifierContract, _tran, _newInstance, _prover, _metaHash ) ); } - - /// @notice Abi encode the public input for the proof verification. - /// @param _tran The transition to verify. - /// @param _verifierContract The contract address which as current verifier. - /// @param _newInstance The new instance address. For SGX it is the new signer address, for ZK - /// this variable is not used and must have value address(0). - /// @param _prover The prover address. - /// @param _metaHash The meta hash. - /// @param _chainId The chain id. - /// @return The public input hash. - function abiEncodePublicInputs( - TaikoData.Transition memory _tran, - address _verifierContract, - address _newInstance, - address _prover, - bytes32 _metaHash, - uint64 _chainId - ) - internal - pure - returns (bytes memory) - { - return abi.encode( - "VERIFY_PROOF", _chainId, _verifierContract, _tran, _newInstance, _prover, _metaHash - ); - } } From e00be79d82ad6f640676b2ace00d80ff6e89257f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jul 2024 11:23:11 +0200 Subject: [PATCH 3/7] add unit tests for SP1 --- .../contracts/verifiers/SP1Verifier.sol | 12 +- packages/protocol/test/L1/TaikoL1TestBase.sol | 1 + packages/protocol/test/TaikoTest.sol | 1 + .../protocol/test/verifiers/SP1Verifier.t.sol | 161 ++++++++++++++++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 packages/protocol/test/verifiers/SP1Verifier.t.sol diff --git a/packages/protocol/contracts/verifiers/SP1Verifier.sol b/packages/protocol/contracts/verifiers/SP1Verifier.sol index 57f9a49398c..f81f79ac2ff 100644 --- a/packages/protocol/contracts/verifiers/SP1Verifier.sol +++ b/packages/protocol/contracts/verifiers/SP1Verifier.sol @@ -32,6 +32,7 @@ contract SP1Verifier is EssentialContract, IVerifier { event NewVerifierAddress(address verifier); error SP1_INVALID_PROGRAM_VKEY(); + error SP1_INVALID_PROOF(); /// @notice Initializes the contract with the provided address manager. /// @param _owner The address of the owner. @@ -93,6 +94,15 @@ contract SP1Verifier is EssentialContract, IVerifier { _tran, address(this), address(0), _ctx.prover, _ctx.metaHash, chainId ); - ISP1Verifier(verifier).verifyProof(programVKey, abi.encode(hashedPublicInput), proof); + // call sp1 verifier (gateway) contract + (bool success,) = address(verifier).staticcall( + abi.encodeCall( + ISP1Verifier.verifyProof, (programVKey, abi.encode(hashedPublicInput), proof) + ) + ); + + if (!success) { + revert SP1_INVALID_PROOF(); + } } } diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index 37eb8dc32b6..71e437ae980 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -11,6 +11,7 @@ abstract contract TaikoL1TestBase is TaikoTest { TaikoData.Config conf; uint256 internal logCount; RiscZeroVerifier public rv; + SP1Verifier public sp1; SgxVerifier public sv; GuardianProver public gp; TestTierProvider public cp; diff --git a/packages/protocol/test/TaikoTest.sol b/packages/protocol/test/TaikoTest.sol index d9d1a103dd6..a8e0ede09dd 100644 --- a/packages/protocol/test/TaikoTest.sol +++ b/packages/protocol/test/TaikoTest.sol @@ -16,6 +16,7 @@ import "../contracts/tokenvault/ERC1155Vault.sol"; import "../contracts/tko/TaikoToken.sol"; import "../contracts/L1/TaikoL1.sol"; import "../contracts/verifiers/SgxVerifier.sol"; +import "../contracts/verifiers/SP1Verifier.sol"; import "../contracts/verifiers/RiscZeroVerifier.sol"; import "../test/L1/TestTierProvider.sol"; import "../contracts/L1/provers/GuardianProver.sol"; diff --git a/packages/protocol/test/verifiers/SP1Verifier.t.sol b/packages/protocol/test/verifiers/SP1Verifier.t.sol new file mode 100644 index 00000000000..1077469e05a --- /dev/null +++ b/packages/protocol/test/verifiers/SP1Verifier.t.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../L1/TaikoL1TestBase.sol"; + +contract MockSP1Gateway is ISP1Verifier { + // To simulate failing and succeeding + bool public verifying; + + error SP1_INVALID_PROOF(); + + function setVerifying(bool _verifying) public { + verifying = _verifying; + } + + function verifyProof( + bytes32, /*programVKey*/ + bytes calldata, /*publicValues*/ + bytes calldata /*proofBytes*/ + ) + external + view + { + require(verifying, "SP1Verifier: invalid proof"); + } +} + +contract TestSP1Verifier is TaikoL1TestBase { + MockSP1Gateway sp1Gateway; + + function deployTaikoL1() internal override returns (TaikoL1) { + return + TaikoL1(payable(deployProxy({ name: "taiko", impl: address(new TaikoL1()), data: "" }))); + } + + function setUp() public override { + // Call the TaikoL1TestBase setUp() + super.setUp(); + + sp1Gateway = new MockSP1Gateway(); + sp1Gateway.setVerifying(true); + + // Deploy Taiko's SP1 proof verifier ('remitter') + sp1 = SP1Verifier( + deployProxy({ + name: "tier_sp1", + impl: address(new SP1Verifier()), + data: abi.encodeCall( + SP1Verifier.init, (address(0), address(addressManager), address(sp1Gateway)) + ) + }) + ); + + sp1.setProgramTrusted(bytes32("105"), true); + + registerAddress("sp1_verifier", address(sp1)); + } + + // Test `verifyProof()` happy path + function test_verifyProof() external { + vm.stopPrank(); + + // Caller not necessary has to be TaikoL1 contract because there is no keys (as in SGX keys) + // to be front run. + vm.startPrank(Alice); + + bytes32 programVKey = bytes32("105"); + bytes memory sp1Proof = hex"00"; + + // TierProof + TaikoData.TierProof memory proof = + TaikoData.TierProof({ tier: 100, data: abi.encode(programVKey, sp1Proof) }); + + vm.warp(block.timestamp + 5); + + (IVerifier.Context memory ctx, TaikoData.Transition memory transition) = + _getDummyContextAndTransition(); + + // `verifyProof()` + sp1.verifyProof(ctx, transition, proof); + + vm.stopPrank(); + } + + function test_verifyProof_invalidProgramVKeyd() external { + vm.stopPrank(); + + // Caller not necessary has to be TaikoL1 contract because there is no keys (as in SGX keys) + // to be front run. + vm.startPrank(Alice); + + bytes32 programVKey = bytes32("101"); + bytes memory sp1Proof = hex"00"; + + // TierProof + TaikoData.TierProof memory proof = + TaikoData.TierProof({ tier: 100, data: abi.encode(programVKey, sp1Proof) }); + + vm.warp(block.timestamp + 5); + + (IVerifier.Context memory ctx, TaikoData.Transition memory transition) = + _getDummyContextAndTransition(); + + // `verifyProof()` + vm.expectRevert(SP1Verifier.SP1_INVALID_PROGRAM_VKEY.selector); + sp1.verifyProof(ctx, transition, proof); + + vm.stopPrank(); + } + + function test_verifyProof_invalidProof() external { + sp1Gateway.setVerifying(false); + vm.stopPrank(); + + // Caller not necessary has to be TaikoL1 contract because there is no keys (as in SGX keys) + // to be front run. + vm.startPrank(Alice); + + bytes32 programVKey = bytes32("105"); + bytes memory sp1Proof = hex"00"; + + // TierProof + TaikoData.TierProof memory proof = + TaikoData.TierProof({ tier: 100, data: abi.encode(programVKey, sp1Proof) }); + + vm.warp(block.timestamp + 5); + + (IVerifier.Context memory ctx, TaikoData.Transition memory transition) = + _getDummyContextAndTransition(); + + vm.expectRevert(SP1Verifier.SP1_INVALID_PROOF.selector); + sp1.verifyProof(ctx, transition, proof); + + vm.stopPrank(); + } + + function _getDummyContextAndTransition() + internal + pure + returns (IVerifier.Context memory ctx, TaikoData.Transition memory transition) + { + // Context + ctx = IVerifier.Context({ + metaHash: bytes32("ab"), + blobHash: bytes32("cd"), + prover: address(0), + msgSender: address(0), + blockId: 10, + isContesting: false, + blobUsed: false + }); + + // Transition + transition = TaikoData.Transition({ + parentHash: bytes32("12"), + blockHash: bytes32("34"), + stateRoot: bytes32("56"), + graffiti: bytes32("78") + }); + } +} From 9f1613fc2593e4d82c7f84de69b1363ae833f0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jul 2024 13:32:47 +0200 Subject: [PATCH 4/7] raneming and deployment script --- .../contracts/verifiers/SP1Verifier.sol | 26 +++++++------- .../protocol/script/DeploySP1Verifier.s.sol | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 packages/protocol/script/DeploySP1Verifier.s.sol diff --git a/packages/protocol/contracts/verifiers/SP1Verifier.sol b/packages/protocol/contracts/verifiers/SP1Verifier.sol index f81f79ac2ff..8d922f0a258 100644 --- a/packages/protocol/contracts/verifiers/SP1Verifier.sol +++ b/packages/protocol/contracts/verifiers/SP1Verifier.sol @@ -10,12 +10,12 @@ import "./libs/LibPublicInput.sol"; /// @title SuccinctVerifier /// @custom:security-contact security@taiko.xyz contract SP1Verifier is EssentialContract, IVerifier { - /// @notice The address of the SP1 verifier contract. + /// @notice The address of the SP1 remote verifier contract. /// @dev This can either be a specific SP1Verifier for a specific version, or the /// SP1VerifierGateway which can be used to verify proofs for any version of SP1. /// For the list of supported verifiers on each chain, see: /// https://github.com/succinctlabs/sp1-contracts/tree/main/contracts/deployments - address public verifier; + address public remoteVerifier; /// @notice The verification keys mappings for the proving programs. mapping(bytes32 provingProgramVKey => bool trusted) public isProgramTrusted; @@ -28,8 +28,8 @@ contract SP1Verifier is EssentialContract, IVerifier { event ProgramTrusted(bytes32 programVKey, bool trusted); /// @dev Emitted when a new verifier address is set. - /// @param verifier The address of the verifier. - event NewVerifierAddress(address verifier); + /// @param newRemoteVerifier The address of the remoteVerifier. + event NewVerifierAddress(address newRemoteVerifier); error SP1_INVALID_PROGRAM_VKEY(); error SP1_INVALID_PROOF(); @@ -37,18 +37,18 @@ contract SP1Verifier is EssentialContract, IVerifier { /// @notice Initializes the contract with the provided address manager. /// @param _owner The address of the owner. /// @param _addressManager The address of the AddressManager. - /// @param _verifier The address of the SP1Verifiers. + /// @param _remoteVerifier The address of the SP1Verifiers. function init( address _owner, address _addressManager, - address _verifier + address _remoteVerifier ) external initializer { __Essential_init(_owner, _addressManager); - verifier = _verifier; + remoteVerifier = _remoteVerifier; } /// @notice Sets/unsets an the program's verification key as trusted entity @@ -60,12 +60,12 @@ contract SP1Verifier is EssentialContract, IVerifier { emit ProgramTrusted(_programVKey, _trusted); } - /// @notice Sets the verifier contract. - /// @param _verifier The address of the verifier contract. - function setVerifierContract(address _verifier) external onlyOwner { - verifier = _verifier; + /// @notice Sets the remoteVerifier contract. + /// @param _remoteVerifier The address of the remoteVerifier contract. + function setRemoteVerifierContract(address _remoteVerifier) external onlyOwner { + remoteVerifier = _remoteVerifier; - emit NewVerifierAddress(_verifier); + emit NewVerifierAddress(_remoteVerifier); } /// @inheritdoc IVerifier @@ -95,7 +95,7 @@ contract SP1Verifier is EssentialContract, IVerifier { ); // call sp1 verifier (gateway) contract - (bool success,) = address(verifier).staticcall( + (bool success,) = address(remoteVerifier).staticcall( abi.encodeCall( ISP1Verifier.verifyProof, (programVKey, abi.encode(hashedPublicInput), proof) ) diff --git a/packages/protocol/script/DeploySP1Verifier.s.sol b/packages/protocol/script/DeploySP1Verifier.s.sol new file mode 100644 index 00000000000..cdd610695eb --- /dev/null +++ b/packages/protocol/script/DeploySP1Verifier.s.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../contracts/verifiers/SP1Verifier.sol"; +import "../test/DeployCapability.sol"; + +contract DeploySP1Verifier is DeployCapability { + // On mainnet, rollup specific address manager is as follows. + address public addressManager = 0x579f40D0BE111b823962043702cabe6Aaa290780; + + modifier broadcast() { + vm.startBroadcast(); + _; + vm.stopBroadcast(); + } + + function run() external broadcast { + address sp1RemoteVerifierOrGateway = vm.envOr("SP1_REMOTE_VERIFIER_GATEWAY", address(0)); // address(0) + // is fine, we can set it later + address owner = vm.envOr("SP1_VERIFIER_OWNER", msg.sender); + + address sp1Verifier = address(new SP1Verifier()); + + address proxy = deployProxy({ + name: "sp1_verifier", + impl: sp1Verifier, + data: abi.encodeCall(SP1Verifier.init, (owner, addressManager, sp1RemoteVerifierOrGateway)) + }); + + console2.log(); + console2.log("Deployed Sp1Verifier impl at address: %s", sp1Verifier); + console2.log("Deployed Sp1Verifier proxy at address: %s", proxy); + } +} From f17bdddda3eb9df1ad714cc9c7aaa61b25fa6f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jul 2024 14:06:20 +0200 Subject: [PATCH 5/7] resolve pr comments --- .../protocol/contracts/common/LibStrings.sol | 1 + .../thirdparty/succinct/ISuccinctVerifier.sol | 27 +++++++++ .../contracts/verifiers/SP1Verifier.sol | 56 +++++-------------- .../protocol/script/DeploySP1Verifier.s.sol | 2 +- .../protocol/test/verifiers/SP1Verifier.t.sol | 7 +-- 5 files changed, 47 insertions(+), 46 deletions(-) create mode 100644 packages/protocol/contracts/thirdparty/succinct/ISuccinctVerifier.sol diff --git a/packages/protocol/contracts/common/LibStrings.sol b/packages/protocol/contracts/common/LibStrings.sol index e064c921250..2baa8eed4bf 100644 --- a/packages/protocol/contracts/common/LibStrings.sol +++ b/packages/protocol/contracts/common/LibStrings.sol @@ -20,6 +20,7 @@ library LibStrings { bytes32 internal constant B_QUOTA_MANAGER = bytes32("quota_manager"); bytes32 internal constant B_SGX_WATCHDOG = bytes32("sgx_watchdog"); bytes32 internal constant B_SIGNAL_SERVICE = bytes32("signal_service"); + bytes32 internal constant B_SP1_REMOTE_VERIFIER = bytes32("sp1_remote_verifier"); bytes32 internal constant B_TAIKO = bytes32("taiko"); bytes32 internal constant B_TAIKO_TOKEN = bytes32("taiko_token"); bytes32 internal constant B_TIER_GUARDIAN = bytes32("tier_guardian"); diff --git a/packages/protocol/contracts/thirdparty/succinct/ISuccinctVerifier.sol b/packages/protocol/contracts/thirdparty/succinct/ISuccinctVerifier.sol new file mode 100644 index 00000000000..3792c9844f2 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/succinct/ISuccinctVerifier.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title SP1 Verifier Interface +/// @author Succinct Labs +/// @notice This interface is for the deployed SP1 Verifier and 100% brought over from : +/// https://github.com/succinctlabs/sp1-contracts/blob/main/contracts/src/ISP1Verifier.sol +interface ISuccinctVerifier { + /// @notice Verifies a proof with given public values and vkey. + /// @dev It is expected that the first 4 bytes of proofBytes must match the first 4 bytes of + /// target verifier's VERIFIER_HASH. + /// @param programVKey The verification key for the RISC-V program. + /// @param publicValues The public values encoded as bytes. + /// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes. + function verifyProof( + bytes32 programVKey, + bytes calldata publicValues, + bytes calldata proofBytes + ) + external + view; +} + +interface ISuccinctWithHash is ISuccinctVerifier { + /// @notice Returns the hash of the verifier. + function VERIFIER_HASH() external pure returns (bytes32); +} diff --git a/packages/protocol/contracts/verifiers/SP1Verifier.sol b/packages/protocol/contracts/verifiers/SP1Verifier.sol index 8d922f0a258..b02efcb222b 100644 --- a/packages/protocol/contracts/verifiers/SP1Verifier.sol +++ b/packages/protocol/contracts/verifiers/SP1Verifier.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; import "../common/EssentialContract.sol"; import "../common/LibStrings.sol"; +import "../thirdparty/succinct/ISuccinctVerifier.sol"; import "../L1/ITaikoL1.sol"; import "./IVerifier.sol"; import "./libs/LibPublicInput.sol"; @@ -10,45 +11,24 @@ import "./libs/LibPublicInput.sol"; /// @title SuccinctVerifier /// @custom:security-contact security@taiko.xyz contract SP1Verifier is EssentialContract, IVerifier { - /// @notice The address of the SP1 remote verifier contract. - /// @dev This can either be a specific SP1Verifier for a specific version, or the - /// SP1VerifierGateway which can be used to verify proofs for any version of SP1. - /// For the list of supported verifiers on each chain, see: - /// https://github.com/succinctlabs/sp1-contracts/tree/main/contracts/deployments - address public remoteVerifier; - /// @notice The verification keys mappings for the proving programs. mapping(bytes32 provingProgramVKey => bool trusted) public isProgramTrusted; - uint256[48] private __gap; + uint256[49] private __gap; /// @dev Emitted when a trusted image is set / unset. /// @param programVKey The id of the image /// @param trusted The block's assigned prover. event ProgramTrusted(bytes32 programVKey, bool trusted); - /// @dev Emitted when a new verifier address is set. - /// @param newRemoteVerifier The address of the remoteVerifier. - event NewVerifierAddress(address newRemoteVerifier); - error SP1_INVALID_PROGRAM_VKEY(); error SP1_INVALID_PROOF(); /// @notice Initializes the contract with the provided address manager. /// @param _owner The address of the owner. /// @param _addressManager The address of the AddressManager. - /// @param _remoteVerifier The address of the SP1Verifiers. - function init( - address _owner, - address _addressManager, - address _remoteVerifier - ) - external - initializer - { + function init(address _owner, address _addressManager) external initializer { __Essential_init(_owner, _addressManager); - - remoteVerifier = _remoteVerifier; } /// @notice Sets/unsets an the program's verification key as trusted entity @@ -60,14 +40,6 @@ contract SP1Verifier is EssentialContract, IVerifier { emit ProgramTrusted(_programVKey, _trusted); } - /// @notice Sets the remoteVerifier contract. - /// @param _remoteVerifier The address of the remoteVerifier contract. - function setRemoteVerifierContract(address _remoteVerifier) external onlyOwner { - remoteVerifier = _remoteVerifier; - - emit NewVerifierAddress(_remoteVerifier); - } - /// @inheritdoc IVerifier function verifyProof( Context calldata _ctx, @@ -80,24 +52,22 @@ contract SP1Verifier is EssentialContract, IVerifier { // Do not run proof verification to contest an existing proof if (_ctx.isContesting) return; - // Decode will throw if not proper length/encoding - (bytes32 programVKey, bytes memory proof) = abi.decode(_proof.data, (bytes32, bytes)); - - if (!isProgramTrusted[programVKey]) { + // Avoid in-memory decoding, so in-place decode with slicing. + // e.g.: bytes32 programVKey = bytes32(_proof.data[0:32]); + if (!isProgramTrusted[bytes32(_proof.data[0:32])]) { revert SP1_INVALID_PROGRAM_VKEY(); } - uint64 chainId = ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; - // Need to be converted from bytes32 to bytes bytes32 hashedPublicInput = LibPublicInput.hashPublicInputs( - _tran, address(this), address(0), _ctx.prover, _ctx.metaHash, chainId + _tran, address(this), address(0), _ctx.prover, _ctx.metaHash, taikoChainId() ); - // call sp1 verifier (gateway) contract - (bool success,) = address(remoteVerifier).staticcall( + // _proof.data[32:] is the succinct's proof position + (bool success,) = (resolve(LibStrings.B_SP1_REMOTE_VERIFIER, false)).staticcall( abi.encodeCall( - ISP1Verifier.verifyProof, (programVKey, abi.encode(hashedPublicInput), proof) + ISuccinctVerifier.verifyProof, + (bytes32(_proof.data[0:32]), abi.encode(hashedPublicInput), _proof.data[32:]) ) ); @@ -105,4 +75,8 @@ contract SP1Verifier is EssentialContract, IVerifier { revert SP1_INVALID_PROOF(); } } + + function taikoChainId() internal view virtual returns (uint64) { + return ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; + } } diff --git a/packages/protocol/script/DeploySP1Verifier.s.sol b/packages/protocol/script/DeploySP1Verifier.s.sol index cdd610695eb..6cfb7c25ae4 100644 --- a/packages/protocol/script/DeploySP1Verifier.s.sol +++ b/packages/protocol/script/DeploySP1Verifier.s.sol @@ -24,7 +24,7 @@ contract DeploySP1Verifier is DeployCapability { address proxy = deployProxy({ name: "sp1_verifier", impl: sp1Verifier, - data: abi.encodeCall(SP1Verifier.init, (owner, addressManager, sp1RemoteVerifierOrGateway)) + data: abi.encodeCall(SP1Verifier.init, (owner, addressManager)) }); console2.log(); diff --git a/packages/protocol/test/verifiers/SP1Verifier.t.sol b/packages/protocol/test/verifiers/SP1Verifier.t.sol index 1077469e05a..cd32c1f42ac 100644 --- a/packages/protocol/test/verifiers/SP1Verifier.t.sol +++ b/packages/protocol/test/verifiers/SP1Verifier.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; import "../L1/TaikoL1TestBase.sol"; -contract MockSP1Gateway is ISP1Verifier { +contract MockSP1Gateway is ISuccinctVerifier { // To simulate failing and succeeding bool public verifying; @@ -45,15 +45,14 @@ contract TestSP1Verifier is TaikoL1TestBase { deployProxy({ name: "tier_sp1", impl: address(new SP1Verifier()), - data: abi.encodeCall( - SP1Verifier.init, (address(0), address(addressManager), address(sp1Gateway)) - ) + data: abi.encodeCall(SP1Verifier.init, (address(0), address(addressManager))) }) ); sp1.setProgramTrusted(bytes32("105"), true); registerAddress("sp1_verifier", address(sp1)); + registerAddress("sp1_remote_verifier", address(sp1Gateway)); } // Test `verifyProof()` happy path From f6b9e3827754ec0c6e3dc1744fe0fd2bc04eee05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jul 2024 14:10:31 +0200 Subject: [PATCH 6/7] move sp1 related interface --- .../contracts/verifiers/IVerifier.sol | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/packages/protocol/contracts/verifiers/IVerifier.sol b/packages/protocol/contracts/verifiers/IVerifier.sol index ff275480c24..a3e0091fcd2 100644 --- a/packages/protocol/contracts/verifiers/IVerifier.sol +++ b/packages/protocol/contracts/verifiers/IVerifier.sol @@ -28,28 +28,3 @@ interface IVerifier { ) external; } - -/// @title SP1 Verifier Interface -/// @author Succinct Labs -/// @notice This interface is for the deployed SP1 Verifier and 100% brought over from : -/// https://github.com/succinctlabs/sp1-contracts/blob/main/contracts/src/ISP1Verifier.sol -interface ISP1Verifier { - /// @notice Verifies a proof with given public values and vkey. - /// @dev It is expected that the first 4 bytes of proofBytes must match the first 4 bytes of - /// target verifier's VERIFIER_HASH. - /// @param programVKey The verification key for the RISC-V program. - /// @param publicValues The public values encoded as bytes. - /// @param proofBytes The proof of the program execution the SP1 zkVM encoded as bytes. - function verifyProof( - bytes32 programVKey, - bytes calldata publicValues, - bytes calldata proofBytes - ) - external - view; -} - -interface ISP1VerifierWithHash is ISP1Verifier { - /// @notice Returns the hash of the verifier. - function VERIFIER_HASH() external pure returns (bytes32); -} From 65da232de004f60bbd3fb46b907259b6cd13bf7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Wed, 31 Jul 2024 10:44:18 +0200 Subject: [PATCH 7/7] add overrideable remoteVerifier() function --- packages/protocol/contracts/verifiers/SP1Verifier.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/protocol/contracts/verifiers/SP1Verifier.sol b/packages/protocol/contracts/verifiers/SP1Verifier.sol index b02efcb222b..90b1b22aa66 100644 --- a/packages/protocol/contracts/verifiers/SP1Verifier.sol +++ b/packages/protocol/contracts/verifiers/SP1Verifier.sol @@ -64,7 +64,7 @@ contract SP1Verifier is EssentialContract, IVerifier { ); // _proof.data[32:] is the succinct's proof position - (bool success,) = (resolve(LibStrings.B_SP1_REMOTE_VERIFIER, false)).staticcall( + (bool success,) = sp1RemoteVerifier().staticcall( abi.encodeCall( ISuccinctVerifier.verifyProof, (bytes32(_proof.data[0:32]), abi.encode(hashedPublicInput), _proof.data[32:]) @@ -79,4 +79,8 @@ contract SP1Verifier is EssentialContract, IVerifier { function taikoChainId() internal view virtual returns (uint64) { return ITaikoL1(resolve(LibStrings.B_TAIKO, false)).getConfig().chainId; } + + function sp1RemoteVerifier() public view virtual returns (address) { + return resolve(LibStrings.B_SP1_REMOTE_VERIFIER, false); + } }