Skip to content

Commit b50844c

Browse files
committed
add l1 origin check + unit tests in sequencer solo client
1 parent ded40ae commit b50844c

13 files changed

+315
-26
lines changed

bindings/go/sequencersoloclient/SequencerSoloClient.go

+32-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contracts/core/SequencerSignatureVerifier.sol

+17-4
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,43 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
2323
import {AppStateVerifier} from "../base/AppStateVerifier.sol";
2424

2525
/**
26-
* @title OptimisticProofVerifier
27-
* @notice Verifies proofs related to Optimistic Rollup state updates
26+
* @title SequencerSignatureVerifier
27+
* @notice Verifies ECDSA signatures from a sequencer for client updates. Is used by the SequencerSoloClient to verify
28+
* signatures on client updates.
2829
* @author Polymer Labs
2930
*/
3031
contract SequencerSignatureVerifier is AppStateVerifier, ISignatureVerifier {
3132
using RLPReader for RLPReader.RLPItem;
3233
using RLPReader for bytes;
3334

34-
// @notice known L2 Output Oracle contract address to verify state update proofs against
35-
address public immutable SEQUENCER;
35+
address public immutable SEQUENCER; // The trusted sequencer address that polymer p2p signer holds the private key
36+
// to
3637
bytes32 public immutable CHAIN_ID; // Chain ID of the L2 chain for which the sequencer signs over
3738

3839
constructor(address sequencer_, bytes32 chainId_) {
3940
SEQUENCER = sequencer_;
4041
CHAIN_ID = chainId_;
4142
}
4243

44+
/**
45+
* @notice Verifies that the sequencer signature is valid for a given l1 origin. This is used by the
46+
* SequencerSoloClient update client method.
47+
* @param l2BlockNumber The block number of the L2 block that the state update is for
48+
* @param appHash The app hash of the state update to be saved in the parent soloClient contract
49+
* @param l1BlockHash The hash of the L1 origin that the peptide height corresponds to
50+
* @param signature The sequencer's ECDSA over the state update
51+
*/
4352
function verifyStateUpdate(uint256 l2BlockNumber, bytes32 appHash, bytes32 l1BlockHash, bytes calldata signature)
4453
external
4554
view
4655
{
4756
_verifySequencerSignature(l2BlockNumber, appHash, l1BlockHash, signature);
4857
}
4958

59+
/**
60+
* @notice Verify the ECDSA signature of the sequencer over the given l2BLockNumber, peptideAppHash, and origin
61+
* l1BlockHash
62+
*/
5063
function _verifySequencerSignature(
5164
uint256 l2BlockNumber,
5265
bytes32 appHash,

contracts/core/SequencerSoloClient.sol

+34-14
Original file line numberDiff line numberDiff line change
@@ -20,45 +20,51 @@ pragma solidity 0.8.15;
2020
import {Ics23Proof} from "../interfaces/IProofVerifier.sol";
2121
import {ISignatureVerifier} from "../interfaces/ISignatureVerifier.sol";
2222
import {ILightClient, LightClientType} from "../interfaces/ILightClient.sol";
23+
import {L1Block} from "optimism/L2/L1Block.sol";
2324

2425
/**
25-
* @title SubfinalityLightClient
26+
* @title SequencerSoloClient
2627
* @author Polymer Labs
27-
* @dev This specific light client implementation uses the same client that is used in the op-stack
28+
* @dev This light client implementation verifies a single signature by the polymer p2p-signer, and updates the light
29+
* client if valid signatures are provided.
2830
*/
2931
contract SequencerSoloClient is ILightClient {
3032
LightClientType public constant LIGHT_CLIENT_TYPE = LightClientType.SequencerLightClient; // Stored as a constant
3133
// for cheap on-chain use
34+
35+
ISignatureVerifier public immutable verifier;
36+
L1Block public immutable l1BlockProvider;
37+
3238
uint8 private _LightClientType = uint8(LIGHT_CLIENT_TYPE); // Also redundantly stored as a private mutable type in
3339
// case it needs to be accessed in any proofs
3440

35-
// consensusStates maps from the height to the appHash.
41+
// consensusStates maps from the height to the appHash, and is modified during state updates if it is a valid state
42+
// update.
3643
mapping(uint256 => uint256) public consensusStates;
3744

38-
ISignatureVerifier public immutable verifier;
39-
40-
error CannotUpdatePendingOptimisticConsensusState();
45+
error InvalidL1Origin();
46+
error CannotUpdateClientWithDifferentAppHash();
4147
error AppHashHasNotPassedFraudProofWindow();
4248
error NonMembershipProofsNotYetImplemented();
4349
error NoConsensusStateAtHeight(uint256 height);
4450

45-
constructor(ISignatureVerifier verifier_) {
51+
constructor(ISignatureVerifier verifier_, L1Block _l1BlockProvider) {
4652
verifier = verifier_;
53+
l1BlockProvider = _l1BlockProvider;
4754
}
4855

4956
/**
5057
* @inheritdoc ILightClient
5158
* @param proof An array of bytes that contain the l1blockhash and the sequencer's signature. The first 32 bytes of
52-
* this argument should be
53-
* the l1BlockHash, and the remaining bytes should be the sequencer signature which attests to the peptide AppHash
59+
* this argument should be the l1BlockHash, and the remaining bytes should be the sequencer signature which attests
60+
* to the peptide AppHash
5461
* for that l1BlockHash
5562
*/
5663
function updateClient(bytes calldata proof, uint256 peptideHeight, uint256 peptideAppHash) external override {
57-
if (consensusStates[peptideHeight] != 0) {
58-
return;
64+
if (l1BlockProvider.hash() != bytes32(proof[:32])) {
65+
revert InvalidL1Origin();
5966
}
60-
verifier.verifyStateUpdate(peptideHeight, bytes32(peptideAppHash), bytes32(proof[:32]), proof[32:]);
61-
consensusStates[peptideHeight] = peptideAppHash;
67+
_updateClient(proof, peptideHeight, peptideAppHash);
6268
}
6369

6470
/**
@@ -67,10 +73,10 @@ contract SequencerSoloClient is ILightClient {
6773
function getState(uint256 height) external view returns (uint256 appHash) {
6874
return _getState(height);
6975
}
76+
7077
/**
7178
* @inheritdoc ILightClient
7279
*/
73-
7480
function verifyMembership(Ics23Proof calldata proof, bytes calldata key, bytes calldata expectedValue)
7581
external
7682
view
@@ -88,6 +94,20 @@ contract SequencerSoloClient is ILightClient {
8894
revert NonMembershipProofsNotYetImplemented();
8995
}
9096

97+
function _updateClient(bytes calldata proof, uint256 peptideHeight, uint256 peptideAppHash) internal {
98+
if (consensusStates[peptideHeight] != 0) {
99+
// Note: we don't cache consensusStates[peptideHeight] in mem for gas savings here because this if statement
100+
// would not be triggered too often, and would make the more frequent branch more expensive due to mem
101+
// allocation.
102+
if (consensusStates[peptideHeight] != peptideAppHash) {
103+
revert CannotUpdateClientWithDifferentAppHash();
104+
}
105+
return;
106+
}
107+
verifier.verifyStateUpdate(peptideHeight, bytes32(peptideAppHash), bytes32(proof[:32]), proof[32:]);
108+
consensusStates[peptideHeight] = peptideAppHash;
109+
}
110+
91111
/**
92112
* @dev Returns the internal state of the light client at a given height.
93113
*/

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@open-ibc/vibc-core-smart-contracts",
3-
"version": "4.0.4",
3+
"version": "4.0.5",
44
"main": "dist/index.js",
55
"bin": {
66
"verify-vibc-core-smart-contracts": "./dist/scripts/verify-contract-script.js",

specs/update.spec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
deployer: 'KEY_DEPLOYER'
6666
deployArgs:
6767
- '{{SequencerSigVerifier}}'
68+
- '{{L1BlockAddress}}'
6869

6970
- name: DummyLightClient
7071
description: 'DummyLightClient'

src/evm/contracts/SequencerSoloClient.ts

+14
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface SequencerSoloClientInterface extends Interface {
6262
| "LIGHT_CLIENT_TYPE"
6363
| "consensusStates"
6464
| "getState"
65+
| "l1BlockProvider"
6566
| "updateClient"
6667
| "verifier"
6768
| "verifyMembership"
@@ -80,6 +81,10 @@ export interface SequencerSoloClientInterface extends Interface {
8081
functionFragment: "getState",
8182
values: [BigNumberish]
8283
): string;
84+
encodeFunctionData(
85+
functionFragment: "l1BlockProvider",
86+
values?: undefined
87+
): string;
8388
encodeFunctionData(
8489
functionFragment: "updateClient",
8590
values: [BytesLike, BigNumberish, BigNumberish]
@@ -103,6 +108,10 @@ export interface SequencerSoloClientInterface extends Interface {
103108
data: BytesLike
104109
): Result;
105110
decodeFunctionResult(functionFragment: "getState", data: BytesLike): Result;
111+
decodeFunctionResult(
112+
functionFragment: "l1BlockProvider",
113+
data: BytesLike
114+
): Result;
106115
decodeFunctionResult(
107116
functionFragment: "updateClient",
108117
data: BytesLike
@@ -167,6 +176,8 @@ export interface SequencerSoloClient extends BaseContract {
167176

168177
getState: TypedContractMethod<[height: BigNumberish], [bigint], "view">;
169178

179+
l1BlockProvider: TypedContractMethod<[], [string], "view">;
180+
170181
updateClient: TypedContractMethod<
171182
[
172183
proof: BytesLike,
@@ -204,6 +215,9 @@ export interface SequencerSoloClient extends BaseContract {
204215
getFunction(
205216
nameOrSignature: "getState"
206217
): TypedContractMethod<[height: BigNumberish], [bigint], "view">;
218+
getFunction(
219+
nameOrSignature: "l1BlockProvider"
220+
): TypedContractMethod<[], [string], "view">;
207221
getFunction(
208222
nameOrSignature: "updateClient"
209223
): TypedContractMethod<

0 commit comments

Comments
 (0)