Skip to content

Commit

Permalink
feat(protocol): add SP1 verification support (#17861)
Browse files Browse the repository at this point in the history
Co-authored-by: Keszey Dániel <keszeyd@MacBook-Pro.local>
  • Loading branch information
adaki2004 and Keszey Dániel authored Aug 1, 2024
1 parent d9bd72b commit 2936312
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/protocol/contracts/common/LibStrings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
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);
}
86 changes: 86 additions & 0 deletions packages/protocol/contracts/verifiers/SP1Verifier.sol
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);
}
}
34 changes: 34 additions & 0 deletions packages/protocol/script/DeploySP1Verifier.s.sol
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);
}
}
1 change: 1 addition & 0 deletions packages/protocol/test/L1/TaikoL1TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/test/TaikoTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
160 changes: 160 additions & 0 deletions packages/protocol/test/verifiers/SP1Verifier.t.sol
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")
});
}
}

0 comments on commit 2936312

Please sign in to comment.