Skip to content

Verify a Merkle Proof Yourself

A Merkle tree lets you prove a single transaction is inside a block without downloading the whole block — you just need the transaction plus a short list of sibling hashes (the Merkle path) leading up to the root committed in the block header. This is the mechanism behind SPV (lightweight) wallets. In this page you’ll pull a real proof out of your node and trace, by hand, how a handful of hashes collapse into the one number the header already commits to.

Bitcoin Core exposes the proof machinery directly:

  • gettxoutproof — given a txid, return the serialized Merkle branch (the proof).
  • verifytxoutproof — given a proof, check it against a block header your node already trusts, and return the txids it proves.
Terminal window
# Produce a proof that this txid is in some block your node has
proof=$(bitcoin-cli gettxoutproof '["6dcf0a...e1a4"]')
echo "$proof"
0000002066... (a hex blob: the block header + tx count + the branch hashes + a bit-flag map)
Terminal window
# Verify it. If valid, it echoes back the txid(s) it proves.
bitcoin-cli verifytxoutproof "$proof"
["6dcf0a...e1a4"]

When verifytxoutproof returns the txid, it has done exactly what an SPV wallet does: hashed the branch up to a root and confirmed that root matches the header of a block on the most-work chain. It never needed the other 1,275 transactions in the block. That’s the whole point.

Strip the blob down and a Merkle proof is just three things:

  1. The block header (so you know which Merkle root to match — recompute its hash to confirm it’s a real, PoW-backed header).
  2. The transaction hash you care about (the leaf).
  3. The sibling hashes along the path to the root, plus a tiny bitmap saying left or right at each level.

That’s a few hundred bytes to prove membership in a block of any size. A full block can be many megabytes; the proof is constant-ish. This asymmetry is what makes phones able to verify Bitcoin.

The rule is simple and uniform at every level: concatenate the two child hashes and apply double-SHA-256 (SHA256(SHA256(...)), Bitcoin’s standard hash), then repeat upward. Suppose our transaction is leaf D in a four-leaf block:

ROOT ( = header.merkleroot )
/ \
H_AB H_CD
/ \ / \
A B C D <- our tx is D

To prove D, the node hands us only the siblings we don’t already have: C, then H_AB. Everything else we compute:

Step 0 start with our leaf: D
Step 1 sibling C is given.
D is the RIGHT child, so order = (C, D):
H_CD = dSHA256( C || D )
Step 2 sibling H_AB is given.
H_CD is the RIGHT child, so order = (H_AB, H_CD):
ROOT' = dSHA256( H_AB || H_CD )
Step 3 compare ROOT' == header.merkleroot ?
match -> D is provably in this block.
differ -> proof is bogus, reject.

Two hashes and two given siblings reproduced the root of a block that could contain thousands of transactions. With a million transactions you’d need only ~20 sibling hashes (log₂ of the count) — that’s the magic of the tree.

To prove you can do the hashing yourself (double-SHA-256), here’s the primitive that builds every node in the tree:

Terminal window
# dSHA256 of two concatenated 32-byte hashes (already in internal/little-endian order, hex)
left=11aa... # 32 bytes hex
right=22bb... # 32 bytes hex
printf "$left$right" | xxd -r -p | openssl dgst -sha256 -binary | openssl dgst -sha256

Apply that repeatedly, feeding each result and the next given sibling, until you land on the header’s Merkle root. Then compare to:

Terminal window
bitcoin-cli getblock <blockhash> | jq -r '.merkleroot'

If your hand-computed value equals that field, you have personally verified a Merkle proof — the exact computation a phone wallet runs to trust a payment without trusting a server.

A Merkle proof is how a resource-poor stranger can still verify the ledger. An SPV wallet doesn’t run a full node, yet it can demand a proof that its transaction sits under a real, work-backed header — and check that proof with a few hashes. Trust is replaced by a short, self-checking math receipt. That’s the same principle as the whole chain, shrunk to fit a phone.

  1. Run gettxoutproof for a known txid, then verifytxoutproof on the result. What does the second command return, and what did it have to compute to do so?
  2. In the 4-leaf tree, exactly which two hashes does the proof for leaf D contain, and which do you compute yourself?
  3. Why does a proof need only ~log₂(N) sibling hashes for a block of N transactions?
  4. Explain the big-endian vs little-endian gotcha. What must you reverse, and when?
  5. Fetch a block’s merkleroot with getblock. Conceptually, how would you confirm a specific txid hashes up to that value?