Merkle tree
A Merkle tree, or hash tree, is a binary tree built over a sequence of data blocks: each leaf is the hash of one block, and each interior node is the hash of the concatenation of its children[1]. The root hash is a short commitment to the entire sequence. Anyone who holds only the root can check that a block belongs to the sequence, at a given position, from the sibling hashes along the block's path to the root, which are logarithmic in number; and two parties holding large sequences can find where they differ by exchanging hashes top-down, descending only where they disagree. Ralph Merkle introduced the construction in 1979 to sign many messages with one public key[2][3]. It is now used in version control, cryptocurrencies, certificate transparency logs, replicated storage and post-quantum signatures.
Construction
Let be a collision-resistant hash function and the data blocks. The Merkle tree hash of RFC 6962, used by Certificate Transparency, splits a sequence of blocks at , the largest power of two less than , and hashes leaves and interior nodes with different one-byte prefixes[4]:
The left subtree is always a perfect binary tree, and a tree with leaves has depth . Building it takes leaf hashes and interior hashes.
An RFC 6962 Merkle tree over an array of strings, with audit paths and their verification. Sha256.digest is a plain implementation of SHA-256.
(* A Merkle tree over a list of entries, as specified by RFC 6962.Leaves and interior nodes are hashed with different one-byte prefixes. *)let h = Sha256.digestlet leaf_hash d = h ("\x00" ^ d)let node_hash l r = h ("\x01" ^ l ^ r)(* The largest power of two strictly less than n (n >= 2). *)let split n = let k = ref 1 in while 2 * !k < n do k := 2 * !k done; !klet sub a i n = Array.sub a i nlet rec root (d : string array) =match Array.length d with| 0 -> h ""| 1 -> leaf_hash d.(0)| n -> let k = split n in node_hash (root (sub d 0 k)) (root (sub d k (n - k)))(* The audit path for entry m: the sibling hashes from the leaf up. *)let rec path m (d : string array) =match Array.length d with| 1 -> []| n ->let k = split n inif m < k then path m (sub d 0 k) @ [root (sub d k (n - k))]else path (m - k) (sub d k (n - k)) @ [root (sub d 0 k)](* Recompute the root from one entry, its index, the tree size and thepath. The verifier needs nothing else. *)let root_from_path m n entry path =let rec go m n path = (* path is in top-down order here *)if n = 1 then leaf_hash entryelselet k = split n inmatch path with| sib :: rest -> if m < k then node_hash (go m k rest) sibelse node_hash sib (go (m - k) (n - k) rest)| [] -> failwith "path too short"ingo m n (List.rev path)let short s = String.sub (Sha256.hex s) 0 12
Inclusion proofs
To show that is the -th block of a sequence whose root is , a prover sends the audit path: the hash of the sibling of every node on the path from 's leaf to the root. The verifier hashes , combines the result with each sibling in turn, on the left or the right according to the bits of the position, and accepts if it arrives at . The proof has hashes; the verifier needs nothing else about the other blocks[1][4].
Proving and verifying membership of one entry.
open Merklelet entries = [| "alice pays bob 5"; "bob pays carol 2"; "carol pays dan 7";"dan pays erin 1"; "erin pays frank 3"; "frank pays gina 4";"gina pays alice 9" |]
Running it.
root of 7 entries: 8eda8ac2eef5audit path for entry 4 (3 hashes):826cb8405bce5046acbe2ec2491857a27003entry 4 as recorded: trueentry 4 altered: falsepath length in a tree of 1000000 entries: 20
Changing any entry, or presenting the entry at a different position, produces a different root unless the verifier has found a collision in . The argument is an induction on the tree: if two different sequences had the same root, then at the first node where they differ, the hashed inputs differ and the outputs agree.
A log that only grows can also prove that its tree of entries extends an earlier tree of entries without rewriting it, again with hashes. Certificate Transparency logs publish such consistency proofs so that auditors can detect a log that shows different histories to different clients[4][8][13].
Domain separation and odd levels
The prefixes matter. Without them, an interior node is the hash of 64 bytes, and those 64 bytes are also a valid leaf. A tree with the leaves then has the same root as the two-leaf tree whose leaves are and , which gives a second preimage of the root without breaking . How an odd number of nodes is paired matters too. Bitcoin's transaction tree pairs the last node of an odd level with itself[5], so a block's transaction list and the same list with its last transaction repeated have the same root; an attacker could use this to make nodes reject a valid block, which was reported as CVE-2012-2459[6].
A naive tree without prefixes that pairs an odd node with itself, against the RFC 6962 tree.
(* A naive tree: no prefixes, and an odd node is paired with itself,as in Bitcoin's transaction tree. *)let h = Sha256.digestlet rec naive_level = function| [] -> []| [x] -> [h (x ^ x)]| x :: y :: rest -> h (x ^ y) :: naive_level restlet rec naive_top = function [x] -> x | l -> naive_top (naive_level l)let naive_root leaves = naive_top (List.map h leaves)
Running it.
naive [a;b;c]: d31a37ef6ac1naive [a;b;c;c]: d31a37ef6ac1naive [a;b;c;d]: 14ede5e8e97anaive [x;y]: 14ede5e8e97aRFC 6962 [a;b;c;d]: 33376a3bd63eRFC 6962 [x;y]: 22c47d7198deRFC 6962 [a;b;c]: 36642e73c254RFC 6962 [a;b;c;c]: e9636069c740
Comparing replicas
Two replicas that each keep a Merkle tree over the same key ranges can find their differences by comparing roots, then the children of any node whose hashes differ, and so on down to the leaves. When entries differ in a tree of , this compares hashes instead of entries. Amazon's Dynamo used such trees for anti-entropy between replicas[7], and Cassandra and Riak adopted the scheme.
Finding the two changed entries among 65,536.
open Merkle(* A tree kept in memory, so two replicas can compare it level by level. *)type tree = Leaf of int * string | Node of string * tree * treelet hash_of = function Leaf (_, x) -> x | Node (x, _, _) -> xlet rec build d lo n =if n = 1 then Leaf (lo, leaf_hash d.(lo))elselet k = split n inlet l = build d lo k and r = build d (lo + k) (n - k) inNode (node_hash (hash_of l) (hash_of r), l, r)(* Find the entries that differ, descending only into subtrees whosehashes disagree. Counts the hashes that had to be compared. *)let compared = ref 0let rec diff a b =incr compared;if hash_of a = hash_of b then []else match a, b with| Leaf (i, _), Leaf _ -> [i]| Node (_, al, ar), Node (_, bl, br) -> diff al bl @ diff ar br| _ -> invalid_arg "trees of different shapes"
Running it.
entries: 65536, differing: [4242; 60000]hashes compared: 63
Applications
Content addressing
In Git, every file is stored under the hash of its contents, a directory is stored as a list of names and child hashes, and a commit records the hash of its root directory and of its parents[10]. The result is a Merkle DAG rather than a balanced tree: a commit hash commits to the complete history and every file in it, and two trees can be compared by skipping subdirectories whose hashes agree. File systems such as ZFS store each block's checksum in its parent pointer for the same reason, and peer-to-peer systems such as IPFS and BitTorrent address content by Merkle roots.
Blockchains
A Bitcoin block header contains the Merkle root of the block's transactions. A lightweight client that stores only headers can check that a transaction was included in a block from its audit path, which Nakamoto called simplified payment verification[5].
Transparency logs
Certificate Transparency requires certificate authorities to submit certificates to public append-only logs, which are Merkle trees; browsers can demand an inclusion proof, and monitors check consistency proofs between successive published roots[4][13].
Hash-based signatures
Merkle's original purpose was signatures. A Lamport one-time signature key signs a single message[11]. Generating one-time key pairs and publishing the root of a Merkle tree over their public keys gives a single public key for messages: a signature is a one-time signature, the one-time public key, and its audit path[1]. The signer needs to produce authentication paths efficiently, which Szydlo showed can be done in time and space per signature[9]. Because their security rests only on the hash function, these schemes are believed to resist quantum computers; SPHINCS+ was standardized by NIST as SLH-DSA in 2024[12].
History
Merkle described hash trees in his 1979 Stanford thesis[2] and in a patent filed the same year and granted in 1982[3], as a way to authenticate many Lamport one-time signature keys with a single value[11]. The published paper, “A digital signature based on a conventional encryption function”, appeared at CRYPTO ’87[1]. The structure spread well beyond signatures: Git (2005) and Bitcoin (2008) put Merkle DAGs and trees at the core of their data models[5][10], Dynamo used them for replica repair[7], and Crosby and Wallach's history trees for tamper-evident logs[8] led to Certificate Transparency, standardized in RFC 6962 in 2013[4].
see also
- TrieA trie, or prefix tree, is a tree for a set of strings in which each edge is labelled with a character and each key is the path from the root to a node; keys with a common prefix share the nodes of that prefix. Lookup and insertion take time proportional to the length of the key, independent of the number of keys, and all keys with a given prefix are found in one subtree, in sorted order. Radix and Patricia trees compress chains of single-child nodes, and the same idea applies to integers, read bit by bit.
- Hash array mapped trieA hash array mapped trie (HAMT) is a trie over the bits of the keys' hash values: each level consumes a few bits, usually five, to choose one of 32 children, and each node stores only its occupied children in a compact array, located by counting bits in a 32-bit bitmap. Lookups follow about log32 n levels. Updated by copying the path to the changed leaf, a HAMT is a persistent hash map with small, cache-friendly nodes, and it is the standard implementation of immutable maps and sets in Clojure, Scala and Haskell's unordered-containers.
- Bloom filterA Bloom filter is a probabilistic data structure for set membership that uses a bit array of m bits and k hash functions. Adding an element sets the k bits its hashes select; a query answers “possibly present” if all k bits are set and “definitely absent” otherwise. It never gives a false negative, and its false-positive rate is about (1 − e^(−kn/m))^k after n insertions, which for the best k is about 0.6185^(m/n): around 10 bits per element give a 1% error, however large the elements are. Bloom filters are used to skip expensive lookups for keys that are not there, in databases, caches, networks and spell checkers.
- Three-way mergeA three-way merge combines two versions of a document that were derived independently from a common ancestor, the base. Comparing each version with the base shows which side changed each region: a region changed on one side only is taken from that side, a region changed identically on both is taken once, and a region changed differently on both is a conflict left for a person to resolve. The base is what makes the merge possible; with only the two versions, an added line cannot be told from a deleted one. The diff3 algorithm is the standard line-based three-way merge, and it is what version control systems such as Git use to merge files.
further reading
- [1]R. C. Merkle, “A digital signature based on a conventional encryption function”, CRYPTO ’87, Lecture Notes in Computer Science 293 (1988).
- [2]R. C. Merkle, Secrecy, Authentication, and Public Key Systems, PhD thesis, Stanford University (1979).
- [3]R. C. Merkle, “Method of providing digital signatures”, US Patent 4,309,569 (filed 1979, granted 1982).
- [4]B. Laurie, A. Langley, E. Kasper, “Certificate Transparency”, RFC 6962 (2013).
- [5]S. Nakamoto, “Bitcoin: a peer-to-peer electronic cash system” (2008).
- [6]CVE-2012-2459, “Bitcoin block Merkle calculation exploit” (2012).
- [7]G. DeCandia et al., “Dynamo: Amazon’s highly available key-value store”, SOSP (2007).
- [8]S. A. Crosby, D. S. Wallach, “Efficient data structures for tamper-evident logging”, USENIX Security Symposium (2009).
- [9]M. Szydlo, “Merkle tree traversal in log space and time”, EUROCRYPT (2004).
- [10]S. Chacon, B. Straub, Pro Git, ch. 10 “Git Internals”, Apress (2nd ed., 2014).
- [11]L. Lamport, “Constructing digital signatures from a one-way function”, SRI International technical report CSL-98 (1979).
- [12]National Institute of Standards and Technology, “Stateless Hash-Based Digital Signature Standard”, FIPS 205 (2024).
- [13]B. Laurie, E. Messeri, R. Stradling, “Certificate Transparency Version 2.0”, RFC 9162 (2021).
last updated