Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions hardhat-test/ZkEvmVerifierV2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable node/no-missing-import */
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
import { hexlify } from "ethers";
import { hexlify, ZeroAddress } from "ethers";
import fs from "fs";
import { ethers } from "hardhat";

Expand Down Expand Up @@ -95,7 +95,12 @@ describe("ZkEvmVerifierV2", async () => {
const chainProxy = await TransparentUpgradeableProxy.deploy(empty.getAddress(), admin.getAddress(), "0x");

const ScrollChainMockBlob = await ethers.getContractFactory("ScrollChainMockBlob", deployer);
const chainImpl = await ScrollChainMockBlob.deploy(layer2ChainId, deployer.address, verifier.getAddress());
const chainImpl = await ScrollChainMockBlob.deploy(
layer2ChainId,
deployer.address,
verifier.getAddress(),
ZeroAddress
);
await admin.upgrade(chainProxy.getAddress(), chainImpl.getAddress());

chain = await ethers.getContractAt("ScrollChainMockBlob", await chainProxy.getAddress(), deployer);
Expand Down
7 changes: 6 additions & 1 deletion scripts/foundry/DeployL1BridgeContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,12 @@ contract DeployL1BridgeContracts is Script {
}

function deployScrollChain() internal {
ScrollChain impl = new ScrollChain(CHAIN_ID_L2, L1_MESSAGE_QUEUE_PROXY_ADDR, address(rollupVerifier));
ScrollChain impl = new ScrollChain(
CHAIN_ID_L2,
L1_MESSAGE_QUEUE_PROXY_ADDR,
address(rollupVerifier),
address(0)
);

logAddress("L1_SCROLL_CHAIN_IMPLEMENTATION_ADDR", address(impl));
}
Expand Down
31 changes: 31 additions & 0 deletions src/L1/rollup/IScrollChain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ interface IScrollChain {
/// @param withdrawRoot The merkle root on layer2 after this batch.
event FinalizeBatch(uint256 indexed batchIndex, bytes32 indexed batchHash, bytes32 stateRoot, bytes32 withdrawRoot);

/// @notice Emitted when a batch is finalized by tee proof.
/// @param batchIndex The index of the batch.
event FinalizeBatchWithTEEProof(uint256 indexed batchIndex);

/// @notice Emitted when state between zk proof and tee proof mismatch
/// @param batchIndex The index of the batch.
/// @param stateRoot The state root from tee proof.
/// @param withdrawRoot The correct withdraw root from tee proof.
event StateMismatch(uint256 indexed batchIndex, bytes32 stateRoot, bytes32 withdrawRoot);

/// @notice Emitted when mismatched state is resolved.
/// @param batchIndex The index of the batch.
/// @param stateRoot The correct state root.
/// @param withdrawRoot The correct withdraw root.
event ResolveState(uint256 indexed batchIndex, bytes32 stateRoot, bytes32 withdrawRoot);

/// @notice Emitted when owner updates the status of sequencer.
/// @param account The address of account updated.
/// @param status The status of the account updated.
Expand All @@ -48,6 +64,9 @@ interface IScrollChain {
/// @return The latest finalized batch index.
function lastFinalizedBatchIndex() external view returns (uint256);

/// @return The latest finalized batch index by tee proof.
function lastTeeFinalizedBatchIndex() external view returns (uint256);

/// @param batchIndex The index of the batch.
/// @return The batch hash of a committed batch.
function committedBatches(uint256 batchIndex) external view returns (bytes32);
Expand Down Expand Up @@ -140,4 +159,16 @@ interface IScrollChain {
bytes32 withdrawRoot,
bytes calldata aggrProof
) external;

/// @notice Finalize a list of committed batches (i.e. bundle) on layer 1 with TEE proof.
/// @param batchHeader The header of last batch in current bundle, see the encoding in comments of `commitBatch.
/// @param postStateRoot The state root after current bundle.
/// @param withdrawRoot The withdraw trie root after current batch.
/// @param teeProof The tee proof for current bundle.
function finalizeBundleWithTeeProof(
bytes calldata batchHeader,
bytes32 postStateRoot,
bytes32 withdrawRoot,
bytes calldata teeProof
) external;
}
117 changes: 115 additions & 2 deletions src/L1/rollup/ScrollChain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
/// @dev Thrown when the given address is `address(0)`.
error ErrorZeroAddress();

/// @dev Thrown when no unresolved state exists.
error ErrorNoUnresolvedState();

/// @dev Thrown when the finalization is paused.
error ErrorFinalizationPaused();

/// @dev Thrown when the batch is not finalized with zk proof.
error ErrorBatchNotFinalizedWithZKProof();

/*************
* Constants *
*************/
Expand All @@ -126,6 +135,19 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
/// @notice The address of RollupVerifier.
address public immutable verifier;

/// @notice The address of RollupVerifier.
address public immutable teeVerifier;

/***********
* Structs *
***********/

struct UnresolvedState {
uint256 batchIndex;
bytes32 stateRoot;
bytes32 withdrawRoot;
}

/*************
* Variables *
*************/
Expand Down Expand Up @@ -157,6 +179,12 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
/// @inheritdoc IScrollChain
mapping(uint256 => bytes32) public override withdrawRoots;

/// @inheritdoc IScrollChain
uint256 public override lastTeeFinalizedBatchIndex;

/// @notice The state for mismatched batch
UnresolvedState public unresolvedState;

/**********************
* Function Modifiers *
**********************/
Expand All @@ -172,6 +200,11 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
_;
}

modifier whenFinalizeNotPaused() {
if (unresolvedState.batchIndex > 0) revert ErrorFinalizationPaused();
_;
}

/***************
* Constructor *
***************/
Expand All @@ -184,7 +217,8 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
constructor(
uint64 _chainId,
address _messageQueue,
address _verifier
address _verifier,
address _teeVerifier
) {
if (_messageQueue == address(0) || _verifier == address(0)) {
revert ErrorZeroAddress();
Expand All @@ -195,6 +229,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
layer2ChainId = _chainId;
messageQueue = _messageQueue;
verifier = _verifier;
teeVerifier = _teeVerifier;
}

/// @notice Initialize the storage of ScrollChain.
Expand All @@ -218,6 +253,11 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
emit UpdateMaxNumTxInChunk(0, _maxNumTxInChunk);
}

function initializeV2() external reinitializer(2) {
lastTeeFinalizedBatchIndex = lastFinalizedBatchIndex;
emit FinalizeBatchWithTEEProof(lastFinalizedBatchIndex);
}

/*************************
* Public View Functions *
*************************/
Expand Down Expand Up @@ -539,7 +579,7 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
bytes32 _postStateRoot,
bytes32 _withdrawRoot,
bytes calldata _aggrProof
) external override OnlyProver whenNotPaused {
) external override OnlyProver whenFinalizeNotPaused {
if (_postStateRoot == bytes32(0)) revert ErrorStateRootIsZero();

// compute pending batch hash and verify
Expand Down Expand Up @@ -583,10 +623,83 @@ contract ScrollChain is OwnableUpgradeable, PausableUpgradeable, IScrollChain {
emit FinalizeBatch(_batchIndex, _batchHash, _postStateRoot, _withdrawRoot);
}

/// @inheritdoc IScrollChain
function finalizeBundleWithTeeProof(
bytes calldata _batchHeader,
bytes32 _postStateRoot,
bytes32 _withdrawRoot,
bytes calldata _teeProof
) external override OnlyProver whenFinalizeNotPaused {
// compute pending batch hash and verify
(uint256 batchPtr, bytes32 _batchHash, uint256 _batchIndex, ) = _loadBatchHeader(_batchHeader);

// check if this batch is finalized by zk proof.
bytes32 cachedStateRoot = finalizedStateRoots[_batchIndex];
if (cachedStateRoot == bytes32(0)) {
revert ErrorBatchNotFinalizedWithZKProof();
}

uint256 _finalizedBatchIndex = lastTeeFinalizedBatchIndex;
if (_batchIndex <= _finalizedBatchIndex) {
revert ErrorBatchIsAlreadyVerified();
}

bytes memory _publicInput = abi.encodePacked(
layer2ChainId,
uint32(_batchIndex - _finalizedBatchIndex), // numBatches
finalizedStateRoots[_finalizedBatchIndex], // _prevStateRoot
committedBatches[_finalizedBatchIndex], // _prevBatchHash
_postStateRoot,
_batchHash,
_withdrawRoot
);

uint256 batchVersion = BatchHeaderV0Codec.getVersion(batchPtr);
IRollupVerifier(teeVerifier).verifyBundleProof(batchVersion, _batchIndex, _teeProof, _publicInput);

// check withdraw root and state root match with zk proof
if (withdrawRoots[_batchIndex] != _withdrawRoot || cachedStateRoot != _postStateRoot) {
unresolvedState.batchIndex = _batchIndex;
unresolvedState.stateRoot = _postStateRoot;
unresolvedState.withdrawRoot = _withdrawRoot;
emit StateMismatch(_batchIndex, _postStateRoot, _withdrawRoot);
return;
}

lastTeeFinalizedBatchIndex = _batchIndex;

emit FinalizeBatchWithTEEProof(_batchIndex);
}

/************************
* Restricted Functions *
************************/

/// @notice Resolve mismatched state.
///
/// @dev This should only be called by Security Council.
///
/// @param useSGXState Whether we want to use the state root from SGX prover.
function resolveStateMismatch(bool useSGXState) external onlyOwner {
UnresolvedState memory state = unresolvedState;
if (state.batchIndex == 0) {
revert ErrorNoUnresolvedState();
}
if (useSGXState) {
finalizedStateRoots[state.batchIndex] = state.stateRoot;
withdrawRoots[state.batchIndex] = state.withdrawRoot;
emit ResolveState(state.batchIndex, state.stateRoot, state.withdrawRoot);
}

lastTeeFinalizedBatchIndex = state.batchIndex;
emit FinalizeBatchWithTEEProof(state.batchIndex);

// clear state
unresolvedState.batchIndex = 0;
unresolvedState.stateRoot = bytes32(0);
unresolvedState.withdrawRoot = bytes32(0);
}

/// @notice Add an account to the sequencer list.
/// @param _account The address of account to add.
function addSequencer(address _account) external onlyOwner {
Expand Down
5 changes: 3 additions & 2 deletions src/mocks/ScrollChainMockBlob.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ contract ScrollChainMockBlob is ScrollChain {
constructor(
uint64 _chainId,
address _messageQueue,
address _verifier
) ScrollChain(_chainId, _messageQueue, _verifier) {}
address _verifier,
address _sgxVerifier
) ScrollChain(_chainId, _messageQueue, _verifier, _sgxVerifier) {}

/**********************
* Internal Functions *
Expand Down
2 changes: 1 addition & 1 deletion src/mocks/ScrollChainMockFinalize.sol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So with MockFinalize mode we'd still need to run an SGX prover?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, running a SGX prover is cheap. I think it is ok to run it in sepolia.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the verifier is set as address(0), does that mean that we just need someone to call finalizeBundleWithTeeProof (e.g. in rollup-relayer, when fake finalize mode is enabled), and the verifyBundleProof call will be a no-op?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems that I forget to change the constructor. The verifier should not be set as address(0). I did change ScrollChainMockBlob since it is used in unit tests. I'm thinking we should also add unit tests for ScrollChainMockFinalize.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see two options two make ScrollChainMockFinalize useful:

  1. Use address(0) as verifier so that we bypass the whole verification step. Then, rollup-relayer can call finalizeBundleWithTeeProof when fake finalization is enabled.
  2. Keep the verifier, but somehow bypass verifyAttestation, so that we can run the "SGX" prover on normal hardware. (Setting up SGX hardware is too complex for a devnet.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For option 2, we need test the feature in sepolia first before goes to mainnet. After test we can do anything we like, like bypass verifyAttestation or fake finalization on tee proof.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ contract ScrollChainMockFinalize is ScrollChain {
uint64 _chainId,
address _messageQueue,
address _verifier
) ScrollChain(_chainId, _messageQueue, _verifier) {}
) ScrollChain(_chainId, _messageQueue, _verifier, address(0)) {}

/*****************************
* Public Mutating Functions *
Expand Down
Loading