The question a scanner on a loading bay has to answer
There is a set of records and there is one record. You want to establish that the one is in the set. The obvious method is to send the whole set and let the checker look, and for four records that is fine. For a thousand it is absurd, and for a device with a small screen and a slow link it is impossible. The structure tonight removes almost all of that traffic and keeps the certainty, which is a rare combination and the reason this idea earns its place.
★ What you walk out with tonight. The tree: hash every record, then hash pairs upward until one hash is left. The proof: the list of sibling hashes and nothing else, which is exactly log2 of the set size, so 1,024 records need 10. And a failing verification, run in front of you, so you have seen the structure say no.
Self-studyThe words that will keep coming back4 min▶
- Leaf. The hash of one record. The bottom row of the tree, one leaf per record.
- Parent. The hash of two children joined together. Every level halves the count.
- Root. The single hash left at the top. One value standing for the whole set.
CHAIN.merkleRoot(items). - Sibling. The hash next to yours at a given level. The thing you are missing and have to be told.
- Proof. The ordered list of siblings from your leaf up to the root, each with the side it sits on.
CHAIN.merkleProof(items, index). - Verification. Hash the record, fold in each sibling in order, compare the result with the root.
CHAIN.verifyProof(item, path, root). - log2. How many times a number halves before it reaches 1. The proof length, exactly.
- Inclusion. What a proof establishes: this record is in that set. Not that the set is right, and not that nothing is missing from it.
Hash the records, then hash the hashes
11 min liveBuilding the tree is two rules applied until they run out. Rule one: every record becomes its own hash. Rule two: take the hashes two at a time, join each pair and hash that, which halves the row. Repeat rule two until one hash is left. That last hash is the root, and changing any record anywhere below changes it.
LiveWhy the root is worth one hash of storage4 min▶
A block can carry a root instead of carrying every record it covers. The block stays small and fixed in size no matter how many records are behind it, and the commitment is exactly as binding, because no different set of records produces that root. So a register of blocks can commit to a mountain of detail it never stores.
That is the everyday use, and it is the reason the structure turns up in places with no chain anywhere near them. Backup systems, package repositories and file synchronisers all use it for the same reason: one small value that cannot agree with the wrong data.
LiveWhat happens when the count is odd3 min▶
A row with an odd number of hashes cannot be paired off cleanly, and the engine here pairs the last hash with itself. That is one common convention and it keeps the arithmetic simple, which is why the sizes in tonight's table are all powers of two: the clean cases make the log2 relationship visible without a footnote on every row.
Conventions differ between implementations, and two verifiers that disagree about the odd case will disagree about the root of the same records. Same lesson as field order in session 2. The structure is standard; the details around its edges are decisions somebody has to write down.
Self-studyOrdering is part of what the root commits to3 min▶
Pairs are joined in a fixed order, left then right, so the same records in a different order give a different root. That is usually what you want, because the order of a custody register is part of what it says, and a root that ignored order would let two entries be swapped invisibly.
It also means a root cannot answer the question people most often want to ask next: is this record absent from the set. Inclusion is cheap and absence is not, and any system that needs to prove absence has to be designed for it from the start rather than have it bolted on.
The siblings you are missing, and nothing else
11 min liveTo rebuild the root from one record you need that record and, at every level, the hash sitting next to yours. You can compute your own side. You cannot compute theirs, so theirs is what gets sent. That list is the proof, and it has one entry per level.
...
LiveWatch it say no4 min▶
Switch to the altered record. One word of the consignment line changes, the leaf hash it produces is unrecognisable, every parent computed from it is unrecognisable, and the root the verifier rebuilds does not match the root they hold. verifyProof returns false, with no partial credit and no near miss.
It is worth pressing that button before you trust anything the other button says. A verifier that only ever returns true is indistinguishable from a function that returns true, and that mistake has shipped in real systems more than once.
LiveWhat a passing proof establishes, exactly3 min▶
It establishes that this record was one of the records that produced that root. That is all, and it is a lot. It does not say the record is accurate, it does not say the set is complete, and it does not say the record is the most recent version of anything.
It also rests entirely on the root being the right root. The proof is a statement relative to a value the verifier already holds, and where that value came from is a separate question with a separate answer, which is the argument in the card two below.
Self-studyThe side of each sibling is part of the proof3 min▶
Each step in the path carries a hash and a side. Hashing your value joined to the sibling gives one result; hashing the sibling joined to your value gives a completely different one. A proof with the right hashes and the wrong sides fails exactly as hard as a proof with the wrong hashes.
Read the path in the live box and notice the sides changing as the index moves. An even index sits on the left and takes its sibling on the right; an odd index is the other way round. That alternation is the whole of the bookkeeping, and getting it wrong is the commonest bug in a first implementation.
Ten hashes for a thousand records
8 min liveThe proof has one entry per level, and the number of levels is the number of times the set halves before it reaches one. Double the records and the proof grows by exactly one hash. Every row below was measured against a real root, and every one of those proofs verified.
...
LiveThe scanner on the loading bay4 min▶
Give the handheld the root and nothing else. When a consignment arrives it receives that consignment's record and ten hashes, does eleven hash operations, and either matches the root or does not. It never holds the register, never asks anybody's opinion, and cannot be talked round.
That is the practical consequence worth carrying out of tonight. A device too small for the data can still verify its own slice of it, which is what makes the structure useful far outside this subject. Anywhere a small client needs certainty about a large set, this is the shape of the answer.
LiveWhy log2 and not something worse3 min▶
At each level your value has exactly one sibling, whatever the level and whatever the set size. So the proof needs one hash per level, and the levels are the halvings. Nothing about the set's content changes that, which is why the numbers in the table are exact rather than approximate.
It also means the cost of proving membership barely notices the set growing. Going from 256 records to 1,024 quadruples the register and adds two hashes to the proof, which is the sort of curve that makes an idea worth its complexity.
Self-studyWhere the disagreement really is4 min▶
One camp holds that this delivers genuine independence to a small client. The device checks membership for itself, with arithmetic it performs and can repeat, and never has to trust the party that sent the proof. Ten hashes buys a verification that used to require the whole register, and that is a real transfer of power to the edge of the system.
The other camp holds that the independence is partly staged. The proof is only ever a statement relative to a root, and the device did not derive that root, it was handed it. The hard question was never whether the record is under the root; it was which root is the current and correct one, and answering that still means trusting somebody or watching the whole register. On this reading the structure moved the trust rather than removing it.
Both are describing the same ten hashes. The disagreement is about whether a verifier that cannot choose its own root counts as independent, and it is worth deciding where you stand before you design one, because it changes what the device has to be told and how often.
Recompute upward, then compare once
5 min liveVerification has no clever step in it. Hash the record. Fold in each proof hash on the side it says. Compare what you end up with against the root you already hold. One comparison, at the very end, and it is true or it is false.
Prove it, then break it
14 min hands onConsole open on this page. Build a root, take a proof, verify it, then make it fail on purpose. The last step is the one that matters.
Check the hash before you trust a root
Run CHAIN.selfTest().ok. A root is only worth as much as the hash under it, and this takes four seconds.
Count the proof, then predict the next one
Print path.length for 1,024 records and confirm it is 10. Before you run it, write down what you expect for 2,048 and for 512. Then check.
Read the sides
Print the side of each step for index 700, then for index 701. Two neighbouring records, two different patterns. Say why the first step flips while the later steps mostly do not.
Make it fail three different ways
Verify with an amended record, then with a different record from the same set, then with a proof for the wrong index. All three return false. A structure that rejects three different wrong things is worth more than one that accepts the right thing.
Change the set and keep the old proof
Add one record to the end, rebuild the root, and verify the old proof against it. It fails, because the root committed to a set that no longer exists. Write one sentence about what that means for any system where the set grows.
Compare the traffic honestly
For 1,024 records, count what a proof costs: one record and 10 hashes. Then count what sending the set costs. Write the ratio down and keep it for session 10, where the whole engine runs at once.
The habit to keep. When somebody shows you a verifier, ask them to make it fail in front of you. If they cannot produce a case that returns false, you have not been shown a verifier, you have been shown a function that agrees with you.
Three questions
A set holds 1,024 records. How many hashes does a proof of membership contain, and why?
Each level supplies exactly one sibling, whatever the size of the set, and the number of levels is the number of halvings. 1,024 halves to 512, 256, 128, 64, 32, 16, 8, 4, 2 and finally 1, which is ten steps. Proof length is exactly log2 of the set size, and every proof in tonight's table was verified against its root.
Somebody alters a record and then presents its original proof. What happens, and how?
Nothing inspects the edit and nothing holds a copy to compare against. The failure comes from arithmetic: an altered record gives a leaf with no relationship to the original, that difference propagates up every level, and the single comparison at the top does not match. No partial match and no near miss.
A handheld scanner verifies a proof successfully. What has it established, and what has it not?
Inclusion is the claim and it is narrow. The proof is a statement relative to the root the device already holds, and where that root came from is a separate question the proof cannot touch. Absence is not provable this way either, and accuracy was never in scope for any hash.
Homework
Twenty five minutes, on a set of records you already have.
Build a root over your own data
Take a few hundred rows from a table you work with, build a root, and take a proof for one row. Confirm the proof length is what log2 says it should be, and confirm an edited row fails.
Write down who hands out the root
For any system you would actually build this into, name the party that tells a verifier which root is current, and how often. Bring that name to session 7, because it turns out to be a question about identity rather than about hashing.
What this session covers
Cheat sheet
merkleRoot(items), merkleProof(items, i), verifyProof(item, path, root).