Why Merkle trees are everywhere
A hash tree that lets you prove one tiny piece of a giant dataset is correct without re-downloading the whole thing — the trick that quietly underpins Git, Bitcoin, IPFS, and certificate transparency.
On this page
The picture version
Five pictures for a reader who has never hashed anything. The prose below fills in the seams the pictures skip.
1 · The problem
Check that part of 90 GB is right, without reading 90 GB.
2 · The build
Fingerprint each chunk, then keep fingerprinting the fingerprints.
3 · Why the top value is worth anything
Change one chunk anywhere and the change climbs all the way up.
4 · The payoff
To prove one chunk belongs, you send a handful of fingerprints — not the data.
5 · Keep this card
The whole thing on one index card.
Why it exists
You launch a game you haven’t played in months. The launcher announces a 400 MB update to a 90 GB install, downloads it in two minutes, then sits there for thirty seconds saying verifying. Two things just happened that sound impossible next to each other: it worked out which slivers of 90 GB were stale without re-reading 90 GB, and it convinced itself the bytes it just pulled off a stranger’s server were the right ones — again without re-reading 90 GB. That install is the running example for this post: a big pile of data, split into chunks, that somebody needs to check part of.
Strip the launcher away and there are two awkward questions underneath:
- Has any of it changed?
- Can I prove this one specific piece is part of it, to someone who doesn’t have the rest?
The dumb answer to (1) is “hash the whole thing.” That works, and it means re-reading 90 GB to notice that one chunk moved. The dumb answer to (2) is “send the whole thing and let them hash it.” Also correct, also a non-starter when “the whole thing” is the entire Bitcoin chain and “someone” is a phone.
Ralph Merkle’s 1979 thesis (Secrecy, Authentication, and Public Key Systems, Stanford) proposed a clean trick: hash the data in pieces, then hash the hashes pairwise up a tree until one hash remains — the root. The root summarises the whole dataset in one fixed-size fingerprint. To prove a specific leaf belongs under that root, you need only the O(log n) sibling hashes along the path from that leaf to the top — around a kilobyte even for a tree with billions of leaves.
That’s the entire idea. Everything else — Git’s content-addressed object graph, Bitcoin’s SPV proofs, IPFS content addressing, BitTorrent piece verification, certificate transparency, ZFS’s checksum hierarchy, Cassandra’s replica-comparison repair — is what happens when different fields rediscover that the answer to “trust this slice of a big thing” is shaped like a tree of hashes.
Why it matters now
Two present-day pressures keep pushing this primitive into more places.
Datasets keep growing faster than the network and memory used to check them. Verifying a multi-terabyte model checkpoint, a container image, or a monorepo by re-hashing all of it is wasteful when almost none of it moved. A Merkle tree turns “does this match?” into a patch-shaped check: change one leaf and only the log n nodes on its path need re-hashing, so comparing two versions means walking down from the roots and stopping wherever the hashes already agree.
And more systems need to be verifiable by someone who doesn’t trust whoever sent the data. A light wallet on a phone can’t store the whole chain. A browser can’t store every issued TLS cert, but wants assurance that certificates for a domain have been publicly logged. A peer pulling model weights out of a swarm can’t trust any single seeder. In every case the move is identical: get the root from somewhere you already trust — a signed block header, a signed log head, a manifest fetched over HTTPS — then check each piece against it with a tiny inclusion proof.
If you’ve ever wondered why so many unrelated systems converged on “and there’s a Merkle root in the header,” that’s why. It’s the cheapest way to make part of something checkable without shipping the whole.
The short answer
Merkle tree = binary tree + hash of children at every internal node
Picture to keep: a knockout tournament bracket run backwards. Each chunk of data is a competitor at the bottom; every match combines two names into one; the single name at the top summarises the whole draw. To prove your competitor was in the tournament, you don’t list every entrant — you name the opponent at each round on the way up, and anyone can replay those matches and check they reach the same champion.
You hash each chunk of data into a leaf hash. You hash each pair of sibling hashes into their parent. You repeat until one hash remains — the root, a fingerprint of the entire dataset; any change anywhere bubbles up and changes it. To prove a leaf is in the tree, you ship the leaf plus the sibling hash at each level on its path to the root — log₂ n hashes — and the verifier recomputes the root.
How it works
Build it from the failure, one constraint at a time. The 90 GB install is split into chunks L0..L7 (eight is small enough to draw; a real one has millions).
Naive attempt: hash the whole install into one digest. Compare digests with the server and you know instantly whether anything changed. Why it breaks: it’s a yes/no about 90 GB. It cannot tell you which chunk moved, so a one-byte difference means re-downloading everything — and computing it means re-reading everything.
Fix: hash each chunk separately and publish the list. Now you can compare chunk by chunk and download only the ones that differ. Why it breaks: the list is as long as the data is chunky — millions of hashes, tens of megabytes, and you have to fetch and trust all of it before you can check one chunk. A phone verifying one transaction in a blockchain is in exactly this bind.
Fix: hash the hashes, pairwise, until one is left. Now there is a single value at the top that commits to every chunk, and — this is the part that isn’t obvious — you can verify any one chunk against it without holding the others:
ROOT = H(H01_23, H45_67)
/ \
H01_23 = H(H01,H23) H45_67 = H(H45,H67)
/ \ / \
H01=H(h0,h1) H23=H(h2,h3) H45=H(h4,h5) H67=H(h6,h7)
/ \ / \ / \ / \
h0 h1 h2 h3 h4 h5 h6 h7
| | | | | | | |
L0 L1 L2 L3 L4 L5 L6 L7
Each h_i = H(L_i) for some cryptographic hash H (SHA-256 in Bitcoin, in Certificate Transparency, and in Git’s newer object format; Git’s classic and still-dominant format uses SHA-1, which is broken for adversarial collisions but remains in use). The tree is balanced and binary in the textbook version; real systems get fancier (more on the seams below).
Inclusion proof for L5: to convince someone who only knows ROOT — say a peer who has your game’s manifest but none of its chunks — that L5 is the fifth chunk, you send:
L5itself.h4(its sibling).H67(the sibling ofH45).H01_23(the sibling ofH45_67).
The verifier computes h5 = H(L5), then H45 = H(h4, h5), then H45_67 = H(H45, H67), then ROOT' = H(H01_23, H45_67), and checks ROOT' == ROOT. If H is collision-resistant, the only way to forge that match is to find a hash collision — i.e., the proof’s security reduces to the hash function’s.
For a tree with n leaves, the proof is ⌈log₂ n⌉ hashes. A billion leaves with SHA-256 is 30 hashes — about 960 bytes. Three lines of arithmetic, a kilobyte on the wire, and a phone can check one transaction against a chain it will never store. That’s what makes light clients viable.
Updates are also O(log n). Change one leaf and only the log n nodes on its path to the root need rehashing. That’s what makes incremental Git commits cheap, and what lets BitTorrent and IPFS verify individual chunks as they arrive in any order — including your game update, where the verifying step re-hashes the chunks that changed and walks the affected paths, not the 90 GB that didn’t move.
The seams — where the textbook picture is misleading:
- The “second-preimage” hazard. Naive Merkle trees have a sneaky attack: a leaf hash and an internal node hash are indistinguishable, so an attacker can sometimes craft a fake “leaf” that’s actually an interior pair
(h_a, h_b)and convince a verifier it’s a single leaf. The standard fix is domain separation — prepend a different byte (0x00for leaf,0x01for internal) before hashing, as CT does (RFC 6962) and as Bitcoin notably doesn’t. Bitcoin’s odd-sized-tree handling has a related quirk: duplicating the last leaf when there’s an odd number of children means certain distinct transaction lists can hash to the same Merkle root (CVE-2012-2459), which let a peer poison a node’s block-validity cache with a bad version of a real block. Bitcoin Core has shipped mitigations for this family of bug more than once, which is the more useful lesson than “it was patched.” - Real systems aren’t always balanced binary trees. Git’s tree objects are k-ary (one entry per file/subdir, hashed into a directory blob). IPFS uses a Merkle DAG, not a tree (shared subgraphs can be referenced once). Certificate transparency uses an append-only tree with consistency proofs between root versions, not just inclusion proofs. The invariant — “hashes of children determine the parent” — is what travels; the topology adapts.
- Sparse Merkle trees flip the model: every possible key has a fixed leaf position (often
H(key)read as a path), and almost all of them are empty. With a careful default-value scheme you get non-membership proofs — “this account does not exist” — almost for free. Ethereum’s state is not one of these: it uses a Merkle Patricia Trie, a hybrid that adds radix-trie path compression on top of the Merkle structure. Verkle trees are the commonly discussed successor; no firm public rollout timeline has been published. - Hash choice is load-bearing. Git repositories are still overwhelmingly SHA-1. SHA-1 fell first to a practical identical-prefix collision (SHAttered, 2017) and then to a chosen-prefix collision (SHA-1 is a Shambles, 2020); Git responded with collision detection (
sha1dc) and a SHA-256 object format that exists but has seen little migration in practice. The Merkle structure inherits the strength of its hash and not one bit more. - Proof verification needs the right root. A Merkle proof says “this leaf is under this root.” Trust the wrong root and you’ve proven nothing. So every real system pairs the tree with an out-of-band trust anchor: a signed block header, a Certificate Transparency log’s signed tree head (signed by the log operator, whose key browsers already carry — not by an outside notary), a manifest fetched over TLS, a git tag signed with your key.
You started with Merkle tree = binary tree + hash of children at every internal node. What did this post add? — + the sibling path. The tree alone gets you a fingerprint, which any plain hash of the whole file would also give you. It’s the fact that a leaf’s path to the root is only log n hashes wide that turns a fingerprint into a proof you can hand to a phone — and it’s why the launcher can verify a 400 MB patch against a 90 GB install without reading the install.
Famous related terms
- Hash chain —
hash chain = sequence + each entry hashes the previous— the 1-D ancestor of the Merkle tree; gives you tamper-evidence for a log but notO(log n)inclusion proofs. Bitcoin’s block header chain is a hash chain; the transactions inside each block form a Merkle tree. - Merkle DAG —
Merkle DAG = Merkle tree − "must be a tree" + content-addressing— what IPFS and Git’s object graph actually are. Shared subtrees can be referenced by multiple parents. - Merkle Patricia Trie —
MPT = sparse Merkle tree + radix-trie path compression— Ethereum’s state representation; lets you prove account state at a given block. - Verkle tree —
Verkle tree ≈ Merkle tree + vector commitments instead of hashes— produces much smaller proofs (constant-ish per level instead of one hash per sibling). Proposed for Ethereum’s state trie, with no firm public rollout timeline yet. - Certificate transparency log —
CT log = append-only Merkle tree of issued TLS certs + signed roots— the “trust but log” backstop for the public CA system. RFC 6962.
Going deeper
- Ralph C. Merkle — Secrecy, Authentication, and Public Key Systems, Stanford PhD thesis, 1979. Answers “where did this construction come from, and what was it originally for?” — which is signatures, not blockchains.
- RFC 6962 / RFC 9162 — Certificate Transparency. Answers “what does a correct production implementation actually specify?”, down to the leaf-versus-internal domain-separation prefixes and the consistency proofs this post only mentions in passing.
- Satoshi Nakamoto — Bitcoin: A Peer-to-Peer Electronic Cash System, 2008, sections 7 and 8. The rabbit hole, answering “what does this buy a client that refuses to store the data?” in two pages.