Caching block hashes
How the `AxiomV0` contract works and how to interact with it
The
AxiomV0
smart contract caches blockhashes from the Ethereum history and allows smart contracts to verify them against this cache. We explain how this works below. AxiomV0
caches the Keccak Merkle roots of consecutive length 1024 sequences of blocks with block numbers [1024 * x, ..., 1024 * x + 1023]
for an index x
in the mappingmapping(uint32 => bytes32) public historicalRoots;
Here
historicalRoots[startBlockNumber]
holds the hash keccak(prevHash || root || numFinal)
, where prevHash
is the block hash of block startBlockNumber - 1
, root
is the Merkle root of the block hashes with index in [startBlockNumber, startBlockNumber + 1023]
, with block hashes after startBlockNumber + numFinal - 1
replaced by 0
, and numFinal
is the number of block hashes verified in this range of blocks.Blockhashes are cached by calling the
updateRecent
, updateOld
, or updateHistorical
functions with the following function signatures:function updateRecent(bytes calldata proofData) external;
function updateOld(
bytes32 nextRoot,
uint32 nextNumFinal,
bytes calldata proofData
) external;
function updateHistorical(
bytes32 nextRoot,
uint32 nextNumFinal,
bytes32[HISTORICAL_NUM_ROOTS] calldata roots,
bytes32[TREE_DEPTH + 1][HISTORICAL_NUM_ROOTS - 1] calldata endHashProofs,
bytes calldata proofData
) external;
These functions verify a ZKP of the Keccak header chain and update
historicalRoots
accordingly:updateRecent
andupdateOld
prove Keccak header chains of length up to1024
.updateHistorical
provides a recursive proof of the validity of Keccak header chains of length128 * 1024
and then reads individual Merkle roots of 1024 blocks using the Merkle proofs inendHashProofs
.
These functions emit the event
event UpdateEvent(uint32 startBlockNumber, bytes32 prevHash, bytes32 root, uint32 numFinal);
for each update of a Merkle root.
To read from the block hash cache,
AxiomV0
provides the isBlockHashValid
method which takes in a witness that a block hash is included in the cache, formatted viastruct BlockHashWitness {
uint32 blockNumber;
bytes32 claimedBlockHash;
bytes32 prevHash;
uint32 numFinal;
bytes32[TREE_DEPTH] merkleProof;
}
This method verifies that
merkleProof
is a valid Merkle path for the relevant block hash and checks that the Merkle root lies in the cache.