State Proofs
If validators are no longer storing the state of compressed accounts, they no longer have all the information needed to validate every transaction, since the validity may depend on the state of compressed accounts. This is where state proofs come in; they provide cryptographic proof of the state of an account, which a validator can check, without having previously stored the state of that account. Specifically, we need to be able to give proofs for the following situations:TN_STATE_PROOF_TYPE_CREATION: When we are creating a new account, we need to prove that it has never been created before.TN_STATE_PROOF_TYPE_EXISTING: When we compress an account, we can get a proof that the account became compressed in its current state. Later, when we want to use the account again, we need to decompress the account. We can use the proof to show that it was previously compressed in the state that we claim it is.TN_STATE_PROOF_TYPE_UPDATING: When we decompress an account, we can get a proof that the account was previously compressed and in its current state. After modifying an account, we may want to compress the account again. We need to provide the proof to show that the account was previously in a certain compressed state, and should now be updated to a new compressed state.
Usage Examples
Here’s a simple smart contract that creates an account using a state proof:getStateProof(account_address, "CREATION") and included in the instruction
data when calling the smart contract.
Merkle Tree Structure
Thru organizes account data in a compressed binary Merkle tree based on account addresses. The tree is structured as a trie where each bit of an account’s address determines the path:- All accounts are located at leaves at the bottom of the tree, at a maximum depth of 256.
- To locate an account, follow the sequence of bits in its address. Bit 0 → go left, Bit 1 → go right
- Leaf nodes: Store hashes of account data
(
SHA-256(0x00 || pubkey || value_hash)). - Internal nodes: Store hashes of their children
(
SHA-256(0x01 || left_hash || right_hash)). The tree is compressed, meaning that internal nodes only exist when account addresses actually diverge. Nodes with only one child are eliminated, keeping the tree compact. - Root node: Single hash representing the entire global account state. This is the only value that validators need to record.
Proof Structure
State proofs contain all information needed to cryptographically verify account operations against historical blockchain state. Variable Size: State proofs have variable size depending on the length of the path the proof takes through the tree. Thepath_bitset indicates which
sibling hashes are included, allowing proofs to be compact for accounts in
sparse areas of the tree. In the C SDK, use
tn_state_proof_footprint_from_header() to determine
the exact size of a proof before allocation.
All state proofs share a common header:
type_slot: Encodes both the proof type (0=EXISTING, 1=UPDATING, 2=CREATION) and the historical slot number when the account state existedpath_bitset: A 256-bit bitmap where each bit indicates whether a sibling hash is provided for that tree level
How State Proofs Work
All state proofs work by reconstructing paths through the Merkle tree and verifying the result matches a known state root. Each proof type targets a different aspect of account state.CREATION proofs demonstrate that a target address is available for a new
account. The key insight is that the proof contains an existing account that
shares the longest possible address prefix with the target account.
- Find the divergence point: Follow both the target address and existing account address bit by bit until they diverge - one goes left, the other goes right
- Verify the existing path: Use the sibling hashes to reconstruct the path from the existing account to the historical state root
- Confirm availability: Since the existing account’s path is valid and the target address diverges from it, the target address must be unoccupied
EXISTING proofs verify that an account exists with exactly the provided data
at a historical point in time.
- Compute the leaf hash: Hash the provided account metadata and data to get the leaf value
- Follow the address path: Use each bit of the account address to navigate left or right through the tree
- Reconstruct to root: At each level, combine the current hash with the appropriate sibling hash to compute the parent hash
- Verify the result: The final computed hash must match the historical state root stored on-chain
UPDATING proofs prove an account’s previous state before modification,
enabling validated state transitions.
- Start with previous state: Use the provided
existing_leaf_hashas the leaf value (the account’s old state) - Reconstruct the historical path: Follow the account’s address using the sibling hashes to rebuild the path to root
- Validate the previous state: The reconstructed path must reach the historical state root, proving the account genuinely had the claimed previous state