Posted in Engineering

Merkle Trees Explained: How Git, Blockchains, and Distributed Databases Verify Data Integrity

Verifying that a large piece of data hasn’t been tampered with usually means re-checking all of it — unless you can summarize that data into a single, small value that changes the instant anything underneath it changes. That’s exactly what a Merkle tree does: a binary tree of hashes where every leaf hashes a piece of data, every parent hashes its children, and a single root hash at the top stands in for the integrity of everything below it. The idea predates the modern internet — it comes from a 1979 patent by cryptographer Ralph Merkle — and it now quietly verifies every Git commit, every Bitcoin block, and large portions of how distributed databases detect when replicas have drifted apart.

At a Glance

AttributeDetail
What it isA tree of hashes where each leaf hashes a data block and each parent hashes the concatenation of its children
What the root provesA single value that summarizes every leaf — changing any leaf changes the root
Verification costO(log n) hashes to prove one leaf belongs, instead of re-checking all n leaves
Real-world usesGit’s object model, Bitcoin’s block structure, Amazon DynamoDB’s anti-entropy repair, Cassandra, Certificate Transparency logs
OriginRalph C. Merkle, US Patent 4,309,569, “Method of providing digital signatures,” filed September 5, 1979

Short Verdict

A Merkle tree solves one specific problem extremely well: proving a piece of data belongs to a larger, already-agreed-upon set without transmitting or re-checking the whole set. Git uses that property to detect the tiniest change anywhere in a repository’s history from a single commit hash. Bitcoin uses it so a lightweight client can verify one transaction is in a block without downloading the entire block. Distributed databases use it to compare two replicas’ entire datasets by comparing a handful of hashes instead of every row. If what you actually need is a plain checksum of one file, a Merkle tree is overkill — its value shows up specifically when you need to verify or compare a large, structured collection of things cheaply and incrementally.

How a Merkle Tree Works

Ralph Merkle’s 1979 patent, “Method of providing digital signatures,” filed September 5, 1979 and granted January 5, 1982, describes the construction directly: a binary tree of one-way function values, where each leaf is the one-way function (a hash) applied to one data item, and each parent node is computed by applying the one-way function to its two children. The topmost value — the root — summarizes every leaf beneath it, and the patent’s point is that only this single root value needs to be authenticated or stored; everything else can be regenerated or supplied on demand.

Verifying that one specific leaf belongs under a given root doesn’t require the whole tree. The patent describes sending just that leaf’s value along with the sibling values on the path from its leaf up to the root — what it calls the “authentication path.” The verifier recomputes the hashes along that path and checks the result matches the already-trusted root. For n total leaves, that path has about log₂ n values, which is why verifying one item in a million-leaf tree takes roughly 20 hash computations, not a million.

A Minimal Merkle Tree in Python

This builds a tree bottom-up from a list of data blocks and generates an authentication (Merkle) proof for one leaf — the two operations that matter in practice.

import hashlib

def h(data):
    return hashlib.sha256(data).digest()

def build_tree(leaves):
    """Return every level of the tree, leaves first, root last."""
    level = [h(leaf) for leaf in leaves]
    levels = [level]
    while len(level) > 1:
        if len(level) % 2 == 1:
            level = level + [level[-1]]   # duplicate the odd one out
        next_level = [h(level[i] + level[i + 1]) for i in range(0, len(level), 2)]
        levels.append(next_level)
        level = next_level
    return levels

def merkle_proof(levels, index):
    """Sibling hashes needed to verify the leaf at `index` against the root."""
    proof = []
    for level in levels[:-1]:
        sibling_index = index ^ 1   # flip the last bit to get the sibling
        sibling_index = min(sibling_index, len(level) - 1)
        proof.append(level[sibling_index])
        index //= 2
    return proof

def verify_proof(leaf, index, proof, root):
    current = h(leaf)
    for sibling in proof:
        if index % 2 == 0:
            current = h(current + sibling)
        else:
            current = h(sibling + current)
        index //= 2
    return current == root


blocks = [b"tx1", b"tx2", b"tx3", b"tx4", b"tx5"]
levels = build_tree(blocks)
root = levels[-1][0]

proof = merkle_proof(levels, 2)             # prove "tx3" belongs
print(verify_proof(b"tx3", 2, proof, root))  # True
print(verify_proof(b"tx9", 2, proof, root))  # False -- tampered data fails

The proof list is exactly the “authentication path” from Merkle’s patent: a handful of sibling hashes that let any verifier recompute the root from a single leaf without ever seeing the other leaves’ contents.

Real-World Uses

Git’s object model is a Merkle structure even though Git’s own documentation doesn’t use that term. Per the Git Internals chapter of the official Pro Git book, Git stores three object types: blobs (raw file contents, no filename), trees (directory entries — mode, type, SHA-1, and filename — each pointing to a blob or another tree), and commits (a pointer to one top-level tree, plus parent commit(s), author, and message), all addressed by the SHA-1 hash of their own content. Because a tree’s hash is computed from the hashes of the blobs and subtrees it contains, and a commit’s hash is computed from its tree’s hash plus its parent’s hash, changing a single byte in any file changes that file’s blob hash, which changes its containing tree’s hash, which changes every commit hash built on top of it. That’s the Merkle-tree guarantee in practice: Git’s content-addressable object graph makes silent tampering with repository history detectable by comparing hashes, not by re-diffing entire file trees.

Bitcoin’s own whitepaper, Section 7, states the mechanism explicitly: “transactions are hashed in a Merkle Tree, with only the root included in the block’s hash.” Pairs of transaction hashes are combined into intermediate hashes, which combine up to a single Merkle root that goes into the block header. Section 8, “Simplified Payment Verification,” builds directly on this: a lightweight client that only stores block headers can request a “Merkle branch” — the same authentication path Merkle’s patent describes — linking one transaction to the block that contains it, without downloading or verifying every other transaction in that block.

Distributed databases use the same structure for a different purpose: detecting divergence between replicas cheaply. Rather than comparing every row between two copies of a dataset, a database can build a Merkle tree over each replica’s data and compare just the root hashes first — if they match, the replicas are identical; if not, walking down to the mismatched subtree pinpoints exactly which range of data differs, without transferring the rest. This “anti-entropy” repair pattern, used in systems like Amazon’s Dynamo and Apache Cassandra, turns an O(n) full-dataset comparison into an O(log n) walk down the branches that actually disagree.

This connects naturally to two other structures covered on this site: the per-node hashing in a Merkle tree is a direct application of the hashing ideas behind consistent hashing, and the anti-entropy comparison pattern above is the same kind of “summarize before you check” trick that a Bloom filter uses to skip unnecessary disk reads.

Merkle Trees vs. Plain Hashing

FactorSingle Hash of EverythingMerkle Tree
Verifying one item belongsRequires the entire dataset to recompute the hashRequires only O(log n) sibling hashes (the authentication path)
Detecting what changedTells you something changed, not what or whereWalking down from a mismatched node narrows down exactly which leaf differs
Comparing two large datasetsMust compare everything, or trust a single top-level hash with no further detailCompare roots first, then walk down only the branches that disagree
Update costEntire hash must be recomputed from scratchOnly the path from the changed leaf to the root needs recomputation

Trade-offs

  • Overhead for small datasets: building and maintaining a tree structure is unnecessary complexity if you only ever need to checksum one small blob.
  • Security depends entirely on the underlying hash function: a Merkle tree’s guarantees are only as strong as collision resistance in the hash it uses (Git’s historical use of SHA-1, for instance, is why the project has worked on a transition to SHA-256).
  • Odd leaf counts and tree shape require a defined convention (as in the duplicate-last-leaf handling above), and different systems make different choices here, which means two systems’ root hashes for conceptually “the same” data aren’t necessarily comparable unless they use identical construction rules.
  • Big win for incremental verification and comparison: proving membership or detecting a specific point of divergence costs O(log n) instead of O(n), which is the entire reason it’s used in Git, Bitcoin, and distributed-database repair.

Bottom Line

Ralph Merkle’s 1979 construction solved a narrow problem — authenticating one item against a large, already-trusted set cheaply — and that narrow solution turned out to generalize extraordinarily well. Git uses it to make its entire commit history tamper-evident from a single hash. Bitcoin uses it so lightweight clients can verify a transaction without trusting or downloading a full block. Distributed databases use it to find exactly where two replicas diverge without comparing every row. In every case, the appeal is the same: a small, fixed-size summary (the root) that changes the instant anything underneath it changes, paired with a logarithmic-cost way to prove or disprove that a specific piece of data belongs under that summary.

FAQ

Is a Merkle tree the same thing as a hash tree?
Yes — “Merkle tree” and “hash tree” are used interchangeably for this structure; the name credits Ralph Merkle’s 1979 patent describing it.

Does Git actually call its object model a Merkle tree?
Git’s own documentation describes blobs, trees, and commits linked by content-derived SHA-1/SHA-256 hashes without using the term “Merkle tree,” but the structure it describes — parent hashes derived from child hashes, all the way up to a single commit hash — matches Merkle’s construction exactly.

How does Bitcoin’s Merkle root make “lightweight” clients possible?
Per the Bitcoin whitepaper’s Section 8, a client that only stores block headers (which include the Merkle root) can request a short “Merkle branch” proving one transaction is included in a block, without downloading or verifying every other transaction in it.

Why do databases use Merkle trees for replica repair instead of just comparing all the data?
Comparing two full datasets row by row is expensive at scale. Comparing Merkle tree roots first, then walking down only the branches whose hashes disagree, finds the actual point of divergence in roughly logarithmic time instead of linear time.

Sources


Discover more from Software Online Review

Subscribe to get the latest posts sent to your email.

Unknown's avatar

Author:

Innovations, logic & design thinking