§ 02 · Cryptography
Merkle trees
One hash that vouches for thousands, and a short proof for any one of them
On this page 7 sections
A Merkle treeMerkle tree: A tree of hashes: hash the items in pairs, then hash those results in pairs, and so on up to one hash at the top. You can prove one item is in the tree with just a handful of hashes. squeezes a whole list of items into one hash, in a way that still lets you prove any single item is on the list.
Every Pearl block uses one. Its transactions are hashed into a tree, and the single hash at the top, the Merkle rootMerkle root: One 32-byte hash that sums up every transaction in a block, made by hashing them in pairs up a Merkle tree. It sits in the header, so changing any transaction changes the block., goes into the block header. Change one byte of any transaction and the root changes, so the header vouches for every transaction in the block.
Try it
Add or edit transactions and watch the tree recompute. Click a leaf to see its proof path.
How is the tree built?
Start with the transaction IDs at the bottom. Then repeat one step until one hash is left:
-
Pair them up
Take the hashes two at a time, left to right.
-
Hash each pair
Join the two 32-byte hashes (64 bytes, in internal byte order) and take SHA256dSHA256d: SHA-256 applied twice: the hash of the hash. Pearl's block hashes, transaction IDs, Merkle roots and proof commitments all use it.. That’s the parent.
-
Odd one out? Pair it with itself
If a level has an odd number of hashes, the last one is hashed together with a copy of itself.
The tree is the same as Bitcoin’s, built by the same code inherited from btcd.
What does a real Pearl tree look like?
Block 130,825 has 115 transactions. Here is how its tree shrinks, level by level:
The one hash left at the top is ab5f7fee74ac4fc304186ab08b556fd03e408576753b1f9ae4574d48251d052a, and that’s exactly what the header stores at bytes 36 to 67 (byte-reversed, as hashes always are):
| Field | Bytes | Value |
|---|---|---|
| Version header.version | 4 @0 | 0x20000000 int32 LE. BIP-9 style 0x20000000 since block 1; no consensus meaning in Pearl. |
| Previous block header.prevBlock | 32 @4 | abc77adee0d6eda61720687d5642efbe4a6a7d9853eee1dad0c559dc431e22d3 Hash of the parent block, stored byte-reversed; shown in display order. |
| Merkle root header.merkleRoot | 32 @36 | ab5f7fee74ac4fc304186ab08b556fd03e408576753b1f9ae4574d48251d052a Root of the SHA256d Merkle tree of this block’s txids, stored byte-reversed. |
| Time header.timestamp | 4 @68 | 1792512899 (2026-10-20 16:14:59 UTC) uint32 LE Unix seconds. Must be at least 1 s after the parent and at most 5 min in the future. |
| Bits header.bits | 4 @72 | 0x176ad0eb Compact target. Difficulty 40208379.91968057. The certificate's jackpot hash (not this header's hash) must meet it. |
| Proof commitment header.proofCommitment | 32 @76 | 86bad84eb6090f941048ea203ac102cf00db145383052c866ccae4e012e36dce SHA256d(LE32(certificate version) ‖ public data). Replaces Bitcoin’s nonce: Pearl headers have no nonce. |
How do you prove one transaction is in the block?
With a Merkle proofMerkle proof: The handful of sibling hashes needed to rebuild a Merkle root from one leaf. It proves the leaf is in the tree without showing the other leaves.: the sibling hash at every level on the way up. Given those, anyone can recompute the root and compare it with the header.
Take the payment at position 17 in block 130,825, d919e02a4adfd739ceef31d98803f0ddd24e1b1fa67b0fe572cdec42c1b384c9. Its proof is seven hashes, computed from the block’s real transaction list:
| Level | Sibling sits on the | Sibling hash |
|---|---|---|
| 0 | left | 5139cf522ff4838c…fc14ed85 (transaction 16) |
| 1 | right | ecc4f5b1d4f78465…f598ce3c |
| 2 | right | cc3ee4c4a2f88632…8ddf8fd2 |
| 3 | right | b574b28183b1846d…932a3f66 |
| 4 | left | 956bdd21491e039c…88544035 |
| 5 | right | d857c044931d768c…fa495f09 |
| 6 | right | 8dfeaffbaa8d5e38…0725ce1c |
The first step hashes transaction 16 and the payment together: SHA256d of the pair gives dce385157c7be73e…2d936174. Six more steps and you land on ab5f7fee…251d052a, the root in the header. The check passes.
That’s 7 × 32 = 224 bytes of proof, instead of downloading all 115 transaction IDs (3,680 bytes) or the whole block.
Make a guess
A block with 4,000 transactions would need a proof of how many hashes?
Think of your answer first, then check it.
Answer 12.
Each level halves the count: 4,000 → 2,000 → 1,000 → 500 → 250 → 125 → 63 → 32 → 16 → 8 → 4 → 2 → 1. That’s 12 steps. Proof size grows with the logarithm of the tree size, so doubling the block adds just one hash.
Why not hash all the transactions in one go?
You could. A single SHA256d over every transaction ID in a row would commit to the whole list just as firmly, and the header would still hold one 32-byte hash.
The difference shows up when you want to prove something about one transaction. With a flat hash, the proof is every other transaction ID. With a tree, it’s one sibling per level: 7 hashes for 115 transactions, 12 for 4,000. The header stays 108 bytes no matter how big the block is, and anyone holding it can check a single transaction cheaply.
Pearl’s mining leans on the same advantage, at a much bigger scale: a pool can check two rows of a gigabyte-sized matrix against a 32-byte fingerprint.
Why is pairing with itself a problem?
Because two different lists can then give the same root. A block with transactions [a, b, c] and a block with [a, b, c, c] build identical trees: the lone c is paired with a copy of itself either way.
This was a real Bitcoin bug, CVE-2012-2459. The fix, which Pearl inherits, is simple: a block with a duplicate transaction is invalid. So the trick can’t be used to make a valid block look invalid, or the other way round.
Where else does Pearl use Merkle trees?
The same shape turns up across Pearl, with different hash functions inside:
- Witness tree. A second tree over the witness transaction IDs (with the coinbase’s set to zero) is committed in the coinbase, exactly as in Bitcoin’s segwit.
- Script trees. Taproot and P2MR outputs commit to a tree of spending scripts, built with the
TapLeafandTapBranchtagged hashes. A P2MR address carries that tree’s root directly. - XMSS keys. A post-quantum XMSS public key contains the root of a tree of 32 one-time keys.
- Mining matrices. A miner fingerprints each matrix with keyed BLAKE3, which is itself a Merkle tree over 1 KiB chunks. When a ticket wins, the pool receives just the needed rows and columns plus their Merkle paths, and checks them against the fingerprints. See noise seeds.
Code: one step of the tree
pearld joins two hashes and double-hashes them in
HashMerkleBranches (node/blockchain/merkle.go:62-70 at v1.5.2, on GitHub):
func HashMerkleBranches(left, right *chainhash.Hash) chainhash.Hash {
// Concatenate the left and right nodes.
var hash [chainhash.HashSize * 2]byte
copy(hash[:chainhash.HashSize], left[:])
copy(hash[chainhash.HashSize:], right[:])
return chainhash.DoubleHashRaw(func(w io.Writer) error {
_, err := w.Write(hash[:])
return err
})
} In JavaScript, with txids in internal byte order:
function merkleRoot(level) {
while (level.length > 1) {
const next = [];
for (let i = 0; i < level.length; i += 2) {
const right = level[i + 1] ?? level[i]; // odd: pair with itself
next.push(sha256(sha256(new Uint8Array([...level[i], ...right]))));
}
level = next;
}
return level[0];
}