Table of Contents
Keystore State Oracle
The Keystore State Oracle (KSO) is the core smart contract connecting L2s to the Keystore rollup.
As discussed in Proving, the L1 bridge persists outputRoot
s into storage during finalization, where outputRoot
is computed as
keccak256(abi.encodePacked(stateRoot, withdrawalsRoot, lastValidBlockhash))
The primary role of the KSO is to read an outputRoot
from L1, decommit the stateRoot
, and expose it to the L2's execution environment. The read of outputRoot
s from L1 can be facilitated in two ways:
- L1 Storage Proof: For L2s that expose L1 blockhash access, the value of the specific storage slot on the L1 bridge can be decommitted with a storage proof.
- Bridge Transaction: For other L2s, the KSO can receive output roots from L1 via a native bridge transaction.
Finally, the stateRoot
can be decommitted from the outputRoot
by taking a claimed preimage as additional input.
The full process of pushing state roots into the KSO is permissionless. For L2s like OP Stack that can access the L1 blockhash, the KSO exposes the following interface:
struct StorageProof {
bytes32 storageValue;
bytes blockHeader;
bytes[] accountProof;
bytes[] storageProof;
}
struct OutputRootPreimage {
bytes32 stateRoot;
bytes32 withdrawalsRoot;
bytes32 lastValidBlockhash;
}
/// Caches an L1 blockhash in storage
function cacheBlockhash() external;
function cacheKeystoreStateRootWithProof(
StorageProof calldata storageProof,
OutputRootPreimage calldata outputRootPreimage
)
The entity responsible for the push will cache an L1 blockhash in the KSO's storage, after which it can verify a keystore state root against the L1 blockhash with an L1 storage proof.
For L2s that do not enshrine L1 blockhash access, the KSO will expose the following alternative interface.
struct OutputRootPreimage {
bytes32 stateRoot;
bytes32 withdrawalsRoot;
bytes32 lastValidBlockhash;
}
/// On an L1 Broadcaster contract
function broadcast(OutputRootPreimage calldata outputRootPreimage, ...) external {
bytes32 keystoreOutputRoot = keystoreBridge.latestOutputRoot();
require(
keccak256(
abi.encodePacked(
outputRootPreimage.stateRoot,
outputRootPreimage.withdrawalsRoot,
outputRootPreimage.lastValidBlockhash
)
) == keystoreOutputRoot,
"Invalid output root preimage"
);
l2Bridge.sendCrossChainMessage(
keystoreStateOracle, abi.encodeCall(cacheKeystoreStateRoot, (outputRootPreimage.stateRoot, block.timestamp))
);
}
/// On L2
function cacheKeystoreStateRoot(bytes32 keystoreStateRoot, uint256 timestamp) external onlyBridge;
The pushing entity will initiate a bridge transaction from L1 to send the keystore state root to the module on L2.