Can someone explain Merkle Trees to me in a practical sense?
It might make more intuitive sense if you reverse the question. Suppose you&#x27;ve uploaded a file to AWS, let&#x27;s say 1MB, but you don&#x27;t entirely trust that they won&#x27;t change the data on you. You&#x27;re about to sell all your computers and go on a boat trip around the world with nothing but the clothes on your back and a single piece of paper, and when you get back next year, you want to retrieve that data from AWS and know for sure whether they corrupted it or not. (And, for some reason, you&#x27;re ok with losing it entirely if they did corrupt it: this is error detection, not error-correction).<p>So before you upload it, you compute the SHA256 hash of the file, and you write down that 32-byte hash on your piece of paper. Then you upload the data, delete the local copy, and sell all your computers. A year from now, when you download it again and want to make sure AWS hasn&#x27;t corrupted the contents, you hash the downloaded data and make sure it matches the hash you wrote down.<p>Next, suppose you&#x27;re uploading a 1GB dataset. And you know that a year from now, you&#x27;re only going to need 1MB of it (at a time). You don&#x27;t really want to download 1000x the data just to do the validity check. So instead you split the dataset up into 1MB chunks, hash each one separately, concatenated the hashes (giving you a 32000 byte string), and do a second-level hash of <i>that</i>. You upload the 1GB dataset and the 32000 bytes of hashes, and you write down just the single 32 byte root hash. Next year, when you download that 1MB, you also download the 32000 bytes. You hash the 32000 bytes and compare it against the root hash that you wrote down, and then you hash the 1MB that you care about and compare it against the small portion of the 32000 bytes. That gives you a two-level tree shape.<p>But now suppose you&#x27;ve got a 1 <i>TB</i> dataset. That concatenated list of hashes is going to be 32<i>1000</i>1000 = 32MB long, which is a drag, since you have to download the whole thing (to hash the whole thing) in order to validate any part of it. You can see where this is going: you introduce another level, you have 1M leaves of 1MB each, in groups of 1000, each of which produces a second-level hash, then you concatenate and hash all 1000 of those second-level hashes to get the root hash.<p>A full (binary) Merkle tree is the log2(N) version of this. You split the dataset up into blocks, hash those to get the leaves of the tree, hash the leaves to get half as many intermediate nodes, hash those, etc, until you wind up with a single root hash. Then you upload the dataset and all the nodes (leaves and intermediate notes), and you remember the root. Next year, you reconstruct the shape of the tree, and figure out which subset of the nodes you&#x27;ll need to verify everything (basically the sibling node of the leaf you want to download, and the sibling node of their mutual parent, and the sibling of that node, etc, up to the top, where the last piece you need is the one child of the root node that <i>isn&#x27;t</i> an ancestor of the leaf you&#x27;re downloading). We called this the &quot;uncle chain&quot;, and it will be log(N) long. Then you go to your backend store and fetch the leaf and those log(N) hashes, and re-perform the subset of the initial hashing that got you the root. Finally you have a reconstructed root, and you can compare that to the one you&#x27;ve been holding all year long.<p>In Tahoe-LAFS (<a href="https:&#x2F;&#x2F;tahoe-lafs.org" rel="nofollow">https:&#x2F;&#x2F;tahoe-lafs.org</a>), we had a metric we named &quot;alacrity&quot;, which we defined as the number of bytes you have to fetch from your storage servers before you can deliver the first byte of decrypted and validated data to the user. The size of the leaves directly impacts the alacrity, as does the size of the uncle chain. In the linear approach (the 1MB example), the alacrity is the entire file: O(N) (really just &quot;N&quot;). The one-level 1000-way &quot;tree&quot; reduces that a lot, but the alacrity is still linear, the sum of the block size and something like (N&#x2F;1MB)<i>32. By going to a full tree, you minimize the alacrity overhead to logarithmic.<p>The tradeoff is overhead. The flat hashing adds zero overhead: every byte on the server is a byte that the client wanted to store. The full tree is maximal overhead: for every block&#x2F;leaf, you need 2x hashes to build up the tree. So for a 1TB file, and 1MB blocks, you&#x27;d have 64MB of hashes to store, and the alacrity is 1MB plus about 10</i>32 = 320 bytes (since log2(1M) ~= 10). You can reduce the alacrity by half by halving the blocksize, but that doubles your overhead.<p>To use this, the validating side must know the shape of the tree (how much data goes into each leaf, how many leaves get hashed into each intermediate node, and how many levels of the tree you&#x27;ve got). But this is usually a deterministic function of the filesize. And then it requires that you have a way to download a specific subset of the data from the server (maybe using HTTP Range queries, or some kind of random-access seek() call), to gather just the useful hash nodes and nothing else.<p>Hope that helps!
 -Brian