-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(protocol): add SP1 verification support (#17861)
Co-authored-by: Keszey Dániel <keszeyd@MacBook-Pro.local>
- Loading branch information
Showing
7 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
packages/protocol/contracts/thirdparty/succinct/ISuccinctVerifier.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// SPDX-License-Identifier: MIT | ||
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"; | ||
|
||
/// @title SuccinctVerifier | ||
/// @custom:security-contact security@taiko.xyz | ||
contract SP1Verifier is EssentialContract, IVerifier { | ||
/// @notice The verification keys mappings for the proving programs. | ||
mapping(bytes32 provingProgramVKey => bool trusted) public isProgramTrusted; | ||
|
||
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); | ||
|
||
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. | ||
function init(address _owner, address _addressManager) external initializer { | ||
__Essential_init(_owner, _addressManager); | ||
} | ||
|
||
/// @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); | ||
} | ||
|
||
/// @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; | ||
|
||
// 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(); | ||
} | ||
|
||
// Need to be converted from bytes32 to bytes | ||
bytes32 hashedPublicInput = LibPublicInput.hashPublicInputs( | ||
_tran, address(this), address(0), _ctx.prover, _ctx.metaHash, taikoChainId() | ||
); | ||
|
||
// _proof.data[32:] is the succinct's proof position | ||
(bool success,) = sp1RemoteVerifier().staticcall( | ||
abi.encodeCall( | ||
ISuccinctVerifier.verifyProof, | ||
(bytes32(_proof.data[0:32]), abi.encode(hashedPublicInput), _proof.data[32:]) | ||
) | ||
); | ||
|
||
if (!success) { | ||
revert SP1_INVALID_PROOF(); | ||
} | ||
} | ||
|
||
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)) | ||
}); | ||
|
||
console2.log(); | ||
console2.log("Deployed Sp1Verifier impl at address: %s", sp1Verifier); | ||
console2.log("Deployed Sp1Verifier proxy at address: %s", proxy); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.24; | ||
|
||
import "../L1/TaikoL1TestBase.sol"; | ||
|
||
contract MockSP1Gateway is ISuccinctVerifier { | ||
// 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))) | ||
}) | ||
); | ||
|
||
sp1.setProgramTrusted(bytes32("105"), true); | ||
|
||
registerAddress("sp1_verifier", address(sp1)); | ||
registerAddress("sp1_remote_verifier", address(sp1Gateway)); | ||
} | ||
|
||
// 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") | ||
}); | ||
} | ||
} |