Links

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.

Caching blockhashes

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 mapping
mapping(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 and updateOld prove Keccak header chains of length up to 1024.
  • updateHistorical provides a recursive proof of the validity of Keccak header chains of length 128 * 1024 and then reads individual Merkle roots of 1024 blocks using the Merkle proofs in endHashProofs.
These functions emit the event
event UpdateEvent(uint32 startBlockNumber, bytes32 prevHash, bytes32 root, uint32 numFinal);
for each update of a Merkle root.

Reading from the cache

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 via
struct 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.