<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Carter Feldman on Medium]]></title>
        <description><![CDATA[Stories by Carter Feldman on Medium]]></description>
        <link>https://medium.com/@carterfeldman?source=rss-44247fb3ab1d------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*xIfLYiJPsf9UIo4kwjZg9w.png</url>
            <title>Stories by Carter Feldman on Medium</title>
            <link>https://medium.com/@carterfeldman?source=rss-44247fb3ab1d------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 16 May 2026 16:38:48 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@carterfeldman/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[A pessimistic future for optimistic rollups]]></title>
            <link>https://medium.com/@carterfeldman/a-pessimistic-future-for-optimistic-rollups-72a781165da7?source=rss-44247fb3ab1d------2</link>
            <guid isPermaLink="false">https://medium.com/p/72a781165da7</guid>
            <category><![CDATA[cryptocurrency]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[blockchain-technology]]></category>
            <dc:creator><![CDATA[Carter Feldman]]></dc:creator>
            <pubDate>Fri, 30 Jun 2023 06:39:12 GMT</pubDate>
            <atom:updated>2023-06-30T06:39:12.771Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>“The absence of evidence is not the evidence of absence”<br>- Carl Sagan</blockquote><p>Optimistic rollups like Arbitrum and Optimism have been gaining increasing attention lately, and many dApps and users have begun considering migrating to optimistic solutions as momentum for Layer 2 adoption builds.</p><p>At a glance, it looks like Optimistic rollups may soon secure a permanent role within the greater blockchain ecosystem as a scaling layer for Ethereum’s rock solid consensus, delivering the best of both worlds to users: Ethereum-level security and high transaction capacity — Providing a great service to the blockchain industry as a whole.</p><p>But looks, alas, can be deceiving.</p><blockquote>In fact, as we look closer, it becomes increasingly clear that Optimistic rollups neither provide a meaningful increase in blockchain scale nor do they guarantee or even approximate the security of Ethereum.</blockquote><h4>Methodology</h4><p>In this blog post, we will consider Arbitrum as it is the largest Optimistic Rollup at the time of writing, but most if not all of the points made are generally applicable to other Optimistic rollups.</p><p>In the spirit of <strong>objectivity</strong> and <strong>fairness</strong>, we will focus on evaluating <strong>the two key value propositions/claims of made on the Arbitrum official website</strong>:</p><ol><li>Optimistic Rollups are a “Secure <strong>Scaling Solution</strong> for Ethereum” with innovative <strong>high-throughput</strong></li><li>Optimistic Rollups have <strong>“Ethereum-level” security </strong>(or as the Arbitrum team puts it, the claim that Arbitrum “inherits Ethereum-level security”)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OJvq5rPncVnabsFdzZfoqQ.png" /><figcaption>Source: <a href="https://arbitrum.io/">https://arbitrum.io/</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2luKcTfnIrrIQ9qLDLxB6A.png" /><figcaption>Source: <a href="https://developer.arbitrum.io/intro#q-hello-whats-arbitrum">https://developer.arbitrum.io/intro#q-hello-whats-arbitrum</a></figcaption></figure><p>Finally,<strong> </strong>we will finish the post on <strong>an Optimistic note</strong> for the future of <strong>blockchain security </strong>and<strong> scalability, </strong>powered by <strong>ZK:</strong></p><blockquote>While Optimistic rollups don’t meaningfully contribute to blockchain security or scalability, there is an rising technology that can: Zero Knowledge Proofs (ZKP)</blockquote><h3>Optimistic Security Fallacy 1: AIT AIT AIT AIT</h3><p>At the core of Optimistic rollup’s claim to “inherit Ethereum”’s security is the way in which fraudulent transactions can be stopped from gaining finality. Let’s consider Aribtrum’s security model:</p><ol><li>The Arbitrum internal team operates a centralized sequencer node that orders the transaction. This node centrally decides which transactions are processed at a what time, and can any transactions it likes (we have already sacrificed Ethereum’s censorship resistance)</li><li>The centralized sequencer operated by the Arbitrum internal team submits the call data need to reconstruct the L2 transactions to L1 (this is necessarily faster than actually executing the transactions, otherwise everyone might as well just execute on L1).</li><li>A group of “defensive validators” picked by the Arbitrum internal team listen to transactions from the centralized sequencer operated by the Arbitrum internal team and process the transactions locally.</li><li>The defensive validators picked by the Arbitrum internal team then execute the transactions sent to them by the Arbitrum internal team and make sure the execution data matches the data submitted on chain by the Arbitrum internal team.</li><li>If the Arbitrum internal team submits incorrect information about a transaction, the the defensive validators picked by the Arbitrum internal team can challenge the execution, but once the challenge occurs, the challenge is paused to give time for the Arbitrum internal team to upgrade the smart contracts in collaboration with the close partners of the Arbitrum Security Council (a group of folks well connected and including the Arbitrum internal team).</li></ol><p>Sounds like <strong>Ethereum-level</strong> security to me!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KF_WFTSE1zqPTjbpNc3mKA.png" /><figcaption>A helpful list of the defensive validators selected by the Arbitrum internal team: <a href="https://docs.arbitrum.foundation/state-of-progressive-decentralization#arbitrum-one">https://docs.arbitrum.foundation/state-of-progressive-decentralization#arbitrum-one</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wP-ywKdPZjmdPDcmZ2z5XA.png" /><figcaption>When the Arbitrum internal team submits a fraudulent transaction and is challenged,<strong> it is clearly a great time to allow for updating the logic of the contract which processes the challenge</strong> (<strong>hmmmmmmm</strong>): <a href="https://developer.arbitrum.io/proving/challenge-manager">https://developer.arbitrum.io/proving/challenge-manager</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MZNFoYkOEpIRZ4lrR-TQzg.png" /><figcaption><strong>hmmmmmm…… </strong>(<a href="https://developer.arbitrum.io/proving/challenge-manager">https://developer.arbitrum.io/proving/challenge-manager</a>)</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gCesiuA3cYwOzu6TrXlKRw.png" /><figcaption><a href="https://docs.arbitrum.foundation/state-of-progressive-decentralization#2-validator-ownership">https://docs.arbitrum.foundation/state-of-progressive-decentralization#2-validator-ownership</a></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/952/1*Zz7-lYawxJwNwqtxZO4glw.png" /><figcaption>Dr. Evil agrees that the Arbitrum Internal Team could never steal your money.</figcaption></figure><h3>Optimistic Security Fallacy 2: Self Inconsistency</h3><p>In our second note we mentioned that “The centralized sequencer operated by the Arbitrum internal team submits the call data need to reconstruct the L2 transactions to L1 (<strong>this is necessarily faster than actually executing the transactions, otherwise everyone might as well just execute on L1</strong>).”.</p><p>Let’s dive into that a bit more deeply, noting that this describes the call to the function addSequencerL2BatchFromOrigin on chain:</p><ol><li>The call to addSequencerL2BatchFromOrigin must contain all the data necessary to reconstitute the state, as the centralized sequencer could just decide not to send anyone the data necessary to check if they were submitting fraudulent transactions (otherwise no one could create a challenge/fraud proof or even verify if fraud had occurred).</li><li>The execution running time of addSequencerL2BatchFromOrigin must be faster than the sum of the running time of all the transactions it attests to (otherwise you might as well just execute the transactions on Ethereum instead of on Arbitrum).</li></ol><p>So if the data feed from the Arbitrum internal team is cut off, the validators get the execution data to check the validity of l2 batches posted by the arbitrum internal team. They can derive the EVM transactions from the stuff posted on chain, and execute locally.</p><p>But, by the time the validators have derived and executed one batch of transactions posted on L1, the sequencer could have posted 5 more batches on chain.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ysV8fwz9EoJ7mIWkP766CQ.png" /><figcaption>Ethereum-level security</figcaption></figure><h3>Are Optimistic Rollups Scaling Ethereum?</h3><p>Now that we know Optimistic rollup’s core thesis is self-inconsistent due to the inability of validators to ever catch up if the sequencer is compromised, we can at least give them some points for scale, right? Unlike some adjectives that blockchains like to describe themselves with (innovative, revolutionary, etc.), the word <strong>scale</strong> describes something we can measure and compare how well Arbitrum using unimpeachable, real world data.</p><p>To judge claim that a side chain <strong>X </strong>is a “Scaling Layer” for chain <strong>Y</strong>, we need to measure how much more scalable chain <strong>X</strong> is than chain <strong>Y</strong> (is it 10 times as scalable, 100 times as scalable, etc).</p><p><strong>To measure the veracity of the scalability claim made by optimistic rollups</strong>, we can find the <strong>maximum scalability factor</strong> (a number that tells us how much better chain X is at scaling than chain Y) by dividing the maximum sustained TPS of side chain <strong>X</strong> by the maximum sustained TPS of chain <strong>Y:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ePEtlmG8d8qWIz_YV6lrog.png" /><figcaption>We can measure how much better chain X is at scaling than chain Y calculating the Scalability Factor</figcaption></figure><p>Knowing how much investor money has been poured into Optimistic rollup technology and the bold claims made by projects like Arbitrum, we know that the world’s most popular optimistic rollup will be far more scalable than Ethereum. <strong><em>But</em></strong>, just to be sure, <strong>let’s calculate and see for ourselves</strong>.</p><p>To do our scaling analysis, we can use a great tool called Dune.com to run a SQL query on all of the historical data to precisely calculate the Scalability Factor for Aribtrum and others:</p><pre>WITH bt AS (<br>SELECT<br>    DATE_TRUNC(&#39;{{Time Granularity}}&#39;, NOW()) - interval &#39;{{Trailing Num Periods}}&#39; {{Time Granularity}} AS dtmin,<br>    DATE_TRUNC(&#39;{{Time Granularity}}&#39;, NOW()) AS dtmax<br>)<br><br>SELECT <br>chain<br>, cast(num_txs as double)/90.0 AS num_txs_per_day<br>, cast(num_txs as double)/(90.0 * (60.0*60.0*24.0) ) AS num_txs_per_second<br>FROM<br>(<br>    SELECT &#39;Optimism&#39; as chain ,COUNT(*) AS num_txs FROM optimism.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    AND to NOT IN (SELECT address FROM labels.system_addresses WHERE blockchain = &#39;optimism&#39; )<br>    UNION ALL<br>    SELECT &#39;Arbitrum&#39; as chain ,COUNT(*) AS num_txs FROM arbitrum.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    AND to NOT IN (SELECT address FROM labels.system_addresses WHERE blockchain = &#39;arbitrum&#39; )<br>    UNION ALL<br>    SELECT &#39;Ethereum&#39; as chain ,COUNT(*) AS num_txs FROM ethereum.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    AND to NOT IN (SELECT address FROM labels.system_addresses WHERE blockchain = &#39;ethereum&#39; )<br>    UNION ALL<br>    SELECT &#39;Solana&#39; as chain ,COUNT(*) AS num_txs FROM solana.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    UNION ALL<br>    SELECT &#39;Polygon&#39; as chain ,COUNT(*) AS num_txs FROM polygon.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    AND to NOT IN (SELECT address FROM labels.system_addresses WHERE blockchain = &#39;polygon&#39; )<br>    UNION ALL<br>    SELECT &#39;BNB&#39; as chain ,COUNT(*) AS num_txs FROM bnb.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    AND to NOT IN (SELECT address FROM labels.system_addresses WHERE blockchain = &#39;bnb&#39; )<br>    UNION ALL<br>    <br>    SELECT &#39;Avalanche C-Chain&#39; as chain ,COUNT(*) AS num_txs FROM avalanche_c.transactions WHERE block_time &gt; (SELECT dtmin FROM bt) AND block_time &lt; (SELECT dtmax FROM bt)<br>    AND to NOT IN (SELECT address FROM labels.system_addresses WHERE blockchain = &#39;avalanche_c&#39; )<br>    ) as tps_info</pre><p>Ok, now let’s run our query and check the results!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ms0PaSQ6N7v12-s6JK3DPg.png" /><figcaption>Reproducable Query: <a href="https://dune.com/cjf/scaling-stats">https://dune.com/cjf/scaling-stats</a></figcaption></figure><p>We now know the average TPS for Ethereum is 12.11, so we can also calculate the scaling factors for Aribtrum and Optimism:</p><ul><li>Aribtrum Average TPS = 11.24, ARB Scaling Factor = 11.24/12.11 = <strong>0.928</strong></li><li>Optimism Average TPS = 3.95 OP Scaling Factor = 3.95/12.11 = <strong>0.326</strong></li></ul><p>Hence, Optimistic rollups have <strong>not come close to providing anything like a significant leap forward in scale for Ethereum.</strong></p><p>You would need 30 <strong>isolated</strong> Arbitrum’s (no communication between rollups) to approach Solana scale let alone the Web2 scale that many fans expect Layer 2’s to one day deliver.</p><h3>A Slow AWS: #NotMyL2</h3><p>Blockchain has value because people know that transactions are final, computation is secure, and state integrity is preserved at all costs. No one can ever touch their money as long as they have their own keys.</p><p><strong>People only put up with our low scalability and poor user experience because of this</strong>.</p><p>So Optimistic rollups beg the question:</p><blockquote><strong>Is it a prudent idea for the Blockchain community to surrender our only advantages for such a pitiful increase in scalability?</strong></blockquote><p>But we need to scale up as a community, so are we doomed to transform into slow AWS controlled by cabals/DAOs less trustworthy than Amazon?</p><h3>Don’t trust AIT, Trust Math</h3><p>Zero Knowledge Proofs can provide off chain computation without the need for an Arbitrum internal team or security council, and recursive ZKP can provide far higher scale than these “Optimistic” chains.</p><p>In a future article we will discuss some of these solutions, but in general:’</p><p><strong><em>If you want to build a dApp, build it on Scroll, Kakarot, QED, Starknet, Polygon or zkSync.</em></strong></p><p><strong>Sleep well at night knowing the only think you will need to trust is Math.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=72a781165da7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Hackers Guide to Layer 2: Zero Merkle Trees from Scratch]]></title>
            <link>https://medium.com/@carterfeldman/a-hackers-guide-to-layer-2-zero-merkle-trees-from-scratch-d612ea846016?source=rss-44247fb3ab1d------2</link>
            <guid isPermaLink="false">https://medium.com/p/d612ea846016</guid>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[blockchain-development]]></category>
            <category><![CDATA[zero-knowledge-proofs]]></category>
            <category><![CDATA[merkle-tree]]></category>
            <dc:creator><![CDATA[Carter Feldman]]></dc:creator>
            <pubDate>Thu, 15 Jun 2023 11:00:20 GMT</pubDate>
            <atom:updated>2023-06-15T11:00:20.190Z</atom:updated>
            <content:encoded><![CDATA[<blockquote><em>This is the second weekly installment of </em><strong><em>A Hackers Guide to Layer 2: Building a zkVM from Scratch.</em></strong></blockquote><blockquote><em>Follow </em><a href="https://twitter.com/cmpeq"><strong><em>@cmpeq</em></strong></a><em> on twitter to stay up to date with future installments.<br>Tutorial code snippets: </em><a href="https://github.com/cf/zero-merkle-tree-tutorial"><strong><em>github.com/cf/zero-merkle-tree-tutorial</em></strong></a></blockquote><h4>In this tutorial, we will:</h4><ul><li>Learn how to build an efficient <strong>zero merkle tree</strong> implementation capable of processing changes in trees with <strong>quadrillions of leaves</strong>, and implement it in TypeScript from scratch.</li><li>Learn how to build an <strong>append-only merkle tree</strong> which only requires<strong> O(log(n)) storage</strong>, and implement it in TypeScript from scratch.</li><li>Learn about <strong>Spiderman proofs</strong> and how to efficiently generate/verify <strong>batch updates</strong> to a merkle tree with a <strong>single, succinct proof</strong>.</li></ul><h4>Note</h4><p>Before reading this tutorial, it is <strong><em>highly recommended</em></strong> to first work through the <a href="https://medium.com/@carterfeldman/a-hackers-guide-to-layer-2-merkle-trees-from-scratch-f682fc31bced"><strong>first installment</strong></a> where we cover merkle tree fundamentals, and we will be frequently referencing content from what we learned in the previous guide.</p><blockquote><em>What I cannot create, I do not understand.<br>Richard Feynman</em></blockquote><h3>Introduction</h3><p>In the previous installment of A Hackers Guide to Layer 2, we covered the basics of merkle trees and built a simple merkle tree database implementation.</p><p>While what we built was simple and usable for small trees, our previous merkle tree implementation will be<strong> very slow</strong> when working with large trees:</p><ul><li>Every time we called getRoot (to compute the tree’s merkle root), we had to <strong>rehash the whole merkle tree</strong> (<strong>O(2^height)</strong> time complexity)</li><li>Creating a new merkle tree instance required us to pass the tree’s height and <strong>an array of leaves </strong>with size 2^height to the constructor (memory is also <strong>O(2^n)</strong>)</li></ul><p>Unfortunately, our <strong>zkVM</strong> will need to work with merkle trees that are both <strong>large</strong> (height &gt; 32) and <strong>sparse</strong> (most of the leaves are zero).</p><p>We will explain why this is the case in future installments, but it is intuitively related to the fact that our zkVM will use merkle trees to <strong>verifiably store and organize persistent data</strong> (think of merkle trees as a verifiable hard drive + file system for our zkVM).</p><p>That being said, we are not out of luck, as we can take advantage of merkle tree’s <strong>self-similarity</strong> to build a much more efficient database for our use case.</p><h3>The Zero Hashes</h3><p>To begin tackling the problem of building a merkle tree better suited for our large/sparse use case, we can gain some insight by first examining an empty merkle tree and looking for patterns that we can take advantage of:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*a1aG1EH_DOjs8o2BRDnQ4Q.png" /><figcaption>An Empty Merkle Tree (TreeHeight = 3)</figcaption></figure><p>If we recall that a node’s value is equal to the hash of its children, we can draw rectangles around the nodes which have the same value:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-nW8WurrpGei7Oc-RyVdyA@2x.png" /><figcaption>Since the nodes of the bottom level all have the same value, the nodes on the level above will also have the same value, since the nodes above have a value depedant only on the hash of the nodes below</figcaption></figure><p>Aha! When a merkle tree is empty, it looks like the <strong>all the nodes on each level have the same value</strong>. The value of the nodes on each level of an empty merkle tree are known as the <strong>Zero Hashes</strong> and are often denoted as <strong><em>Zₙ</em></strong> where <strong>n</strong> = <strong>TreeHeight</strong> - <strong>Level</strong>.</p><p>Since we know that the bottom nodes have a value <em>Z</em>₀ = 0, lets write an expression for <strong><em>Zₙ.</em></strong></p><p>Recalling the formula for computing the value of node in a merkle tree that we learned in the previous installment:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qPWpXvMRe-Xkby7QHmDLAQ.png" /><figcaption>The formula for computing the value of a merkle tree node</figcaption></figure><p>We can just plug-in <strong>zero</strong> for the leaves and use the <strong>level-wise</strong> <strong>self-similarity</strong> that we observed in the tree to write an expression for <strong><em>Zₙ</em></strong>:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fimgur.com%2Fa%2FpGKwEcN%2Fembed%3Fpub%3Dtrue%26ref%3Dhttps%253A%252F%252Fembed.ly%26w%3D900&amp;display_name=Imgur&amp;url=https%3A%2F%2Fimgur.com%2Fa%2FpGKwEcN&amp;image=https%3A%2F%2Fi.imgur.com%2FaqmRZOT.jpg%3Ffbplay&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=imgur" width="900" height="587" frameborder="0" scrolling="no"><a href="https://medium.com/media/fd991db30496aa782d2bcdf59df87b11/href">https://medium.com/media/fd991db30496aa782d2bcdf59df87b11/href</a></iframe><p>Hence we can say that, for an empty merkle tree with height = <strong>TreeHeight</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/896/1*OzGHPpmUkOHV_VtKCvJ_mQ.png" /></figure><p>Great, now let’s demonstrate our knowledge of the zero hashes by writing a TypeScript function that computes the array of zero hashes for a giventreeHeight:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1395f2a1a34e7114b3fad1e65ef97a8e/href">https://medium.com/media/1395f2a1a34e7114b3fad1e65ef97a8e/href</a></iframe><p>Running this code, we get the output:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FJjeGydx%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DJjeGydx&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FJjeGydx&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FJjeGydx-512.jpg%3Fversion%3D1686813629&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/8aa1fdaa0d9b9a8df9d3d06687be242c/href">https://medium.com/media/8aa1fdaa0d9b9a8df9d3d06687be242c/href</a></iframe><p>Great! Now that we have written a function to compute the zero hashes, we can write a function to obtain the value of any node in an empty merkle tree using our zero hashes:</p><pre>function getNodeValue(treeHeight: number, level: number): MerkleNodeValue{<br>  const zeroHashes = computeZeroHashes(treeHeight);<br>  return zeroHashes[treeHeight-level];<br>}</pre><h3>Implementing a ZMT Database</h3><p>We now know how to compute the value any node in an empty merkle tree, let’s use our knowledge to write our efficient merkle tree database.</p><p>In the previous installment of our series, we already proved that, when you update a leaf in a merkle tree, <strong>only the nodes alone the leaf’s merkle path will change</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ht2LhmpjNBKEwBfo0xK6mw.png" /></figure><p>Using this principle, we make a merkle tree database which only stores all the nodes in the tree that aren’t zero hashes, and follows the procedure:</p><p>To find the value of node N(level, index):</p><ul><li>Check if it exists in the database.<strong> If it does, return the value</strong>.</li><li><strong>If it does not exist, return the corresponding zero hash for the node’s level</strong></li><li>If you want to update a node, first set the leaf, compute the leaf’s merkle path, and then <strong>save all the new node values in the merkle path to the database</strong></li></ul><p>In this way, we will create a database which only needs to store the non-zero hash nodes, perfect for our large, sparse merkle trees!</p><p>Let’s start by writing a simple data store which follows our rule for getting nodes from the datastore (return if it exists, else fallback to a zero hash):</p><pre>class NodeStore {<br>  nodes: {[id: string]: MerkleNodeValue};<br>  height: number;<br>  zeroHashes: MerkleNodeValue[];<br>  constructor(height: number){<br>    this.nodes = {};<br>    this.height = height;<br>    this.zeroHashes = computeZeroHashes(height);<br>  }<br>  contains(level: number, index: number): boolean {<br>    // check if the node exists in the data store<br>    return Object.hasOwnProperty.call(this.nodes, level+&quot;_&quot;+index);<br>  }<br>  set(level: number, index: number, value: MerkleNodeValue){<br>    // set the value of the node in the data store<br>    this.nodes[level+&quot;_&quot;+index] = value;<br>  }<br>  get(level: number, index: number): MerkleNodeValue {<br>    if(this.contains(level, index)){<br>      // if the node is in the datastore, return it<br>      return this.nodes[level+&quot;_&quot;+index];<br>    }else{<br>      // if the node is NOT in the data store, return the correct zero hash for the node&#39;s level<br>      return this.zeroHashes[this.height-level];<br>    }<br>  }<br>}</pre><p>Great! Now we can write a ZeroMerkleTree class which uses the NodeStore:</p><pre>class ZeroMerkleTree {<br>  height: number;<br>  nodeStore: NodeStore;<br>  constructor(height: number){<br>    this.height = height;<br>    this.nodeStore = new NodeStore(height);<br>  }<br>}</pre><p>Following what we learned in the previous installment, we know that we can compute the merkle path of a node by dividing the index by 2 each time we go up a level in the merkle path:</p><pre>function getMerklePathOfNode(level, index){<br>  const merklePath = [];<br>  for(let currentLevel=level;currentLevel&gt;=0;currentLevel--){<br>    merklePath.push({<br>      level: currentLevel,<br>      index: index,<br>    });<br>    index = Math.floor(index/2);<br>  }<br>  return merklePath;<br>}</pre><p>Applying this knowledge, lets write a setLeaf function which sets a leaf in the tree and updates its merkle path according to the same logic as our getMerklePathOfNode function and the even-odd hashing rule for siblings:</p><pre>class ZeroMerkleTree {<br>  height: number;<br>  nodeStore: NodeStore;<br>  constructor(height: number){<br>    this.height = height;<br>    this.nodeStore = new NodeStore(height);<br>  }<br>  setLeaf(index: number, value: MerkleNodeValue){<br>    // start traversing the leaf&#39;s merkle path at the leaf node<br>    let currentIndex = index;<br>    let currentValue = value;<br>    // don&#39;t set the root (level = 0) in the loop, as it has no sibling<br>    for(let level = this.height; level &gt; 0; level--){<br>      // set the current node in the tree<br>      this.nodeStore.set(level, currentIndex, currentValue);<br>      if(currentIndex % 2 === 0){<br>        // if the current index is even, then it has a sibling on the right (same level, index = currentIndex+1)<br>        const rightSibling = this.nodeStore.get(level, currentIndex+1);<br>        currentValue = hash(currentValue, rightSibling);<br>      }else{<br>        // if the current index is odd, then it has a sibling on the left (same level, index = currentIndex-1)<br>        const leftSibling = this.nodeStore.get(level, currentIndex-1);<br>        currentValue = hash(leftSibling, currentValue);<br>      }<br>      // set current index to the index of the parent node<br>      currentIndex = Math.floor(currentIndex/2);<br>    }<br>    // set the root node (level = 0, index = 0) to current value<br>    this.nodeStore.set(0, 0, currentValue);<br>  }<br>}</pre><p>Great! Let’s check if its working by adding a getRoot function which returns the merkle root</p><pre>class ZeroMerkleTree {<br>...<br>  getRoot(): MerkleNodeValue {<br>    return this.nodeStore.get(0, 0);<br>  }<br>...<br>}</pre><p>Putting everything together we get:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/accbaa16a8997f8c274c40ab320641d1/href">https://medium.com/media/accbaa16a8997f8c274c40ab320641d1/href</a></iframe><p>Running this function yields the result:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FdyQGzYx%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DdyQGzYx&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FdyQGzYx&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FdyQGzYx-512.jpg%3Fversion%3D1686813753&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/6ce908031f4be98d71a59acd0cf8d356/href">https://medium.com/media/6ce908031f4be98d71a59acd0cf8d356/href</a></iframe><pre>[example2] the root is: 7e286a6721a66675ea033a4dcdec5abbdc7d3c81580e2d6ded7433ed113b7737</pre><p>We can see if we got everything right by comparing it with the merkle root we calculated from a tree with the same leaves in our <a href="https://medium.com/@carterfeldman/a-hackers-guide-to-layer-2-merkle-trees-from-scratch-f682fc31bced#c21e">previous installment</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_ciLDCCmdDdKjgc-46RxNQ.png" /><figcaption><strong>The root matches!</strong></figcaption></figure><p>A perfect match 🎉, now we can make it even more useful by returning a <strong>delta merkle proof</strong> whenever we call setLeaf.</p><p>Recalling our previous definition of delta merkle proofs we will need the following data:</p><ul><li>index of the leaf (index)</li><li>the siblings of the leaf’s merkle path (siblings)</li><li>the root before the leaf was changed (oldRoot)</li><li>the value of the leaf before it was changed (oldValue)</li><li>the root after the leaf is changed (newRoot)</li><li>the new value of the leaf (newValue)</li></ul><pre>type MerkleNodeValue = string;<br>interface IDeltaMerkleProof {<br>  index: number;<br>  siblings: MerkleNodeValue[];<br>  oldRoot: MerkleNodeValue;<br>  oldValue: MerkleNodeValue;<br>  newRoot: MerkleNodeValue;<br>  newValue: MerkleNodeValue;<br>}</pre><p>To do this, we can modify the start of our setLeaf function to record the value of the oldRoot and oldValue before we make our update + add a siblings array to keep track of the siblings of the leaf’s merkle path:</p><pre>...<br>  setLeaf(index: number, value: MerkleNodeValue): IDeltaMerkleProof{<br>    // get the old root and old value for the delta merkle proof<br>    const oldRoot = this.nodeStore.get(0, 0);<br>    const oldValue = this.nodeStore.get(this.height, index);<br>    // siblings array for delta merkle proof<br>    const siblings: MerkleNodeValue[] = [];<br>...</pre><p>Then, we can modify the body of the loop where we hash the node on the merkle path with its sibling to also push the siblings value to our siblings array:</p><pre>...<br>  setLeaf(index: number, value: MerkleNodeValue): IDeltaMerkleProof{<br>    ...<br>    for(let level = this.height; level &gt; 0; level--){<br>      // set the current node in the tree<br>      this.nodeStore.set(level, currentIndex, currentValue);<br><br>      if(currentIndex % 2 === 0){<br>        // if the current index is even, then it has a sibling on the right (same level, index = currentIndex+1)<br>        const rightSibling = this.nodeStore.get(level, currentIndex+1);<br>        currentValue = hash(currentValue, rightSibling);<br>        // add the right sibling to the siblings array<br>        siblings.push(rightSibling); // &lt;---- HERE<br>      }else{<br>        // if the current index is odd, then it has a sibling on the left (same level, index = currentIndex-1)<br>        const leftSibling = this.nodeStore.get(level, currentIndex-1);<br>        currentValue = hash(leftSibling, currentValue);<br>        // add the left sibling to the siblings array<br>        siblings.push(leftSibling); // &lt;---- HERE<br>      }<br>      // set current index to the index of the parent node<br>      currentIndex = Math.floor(currentIndex/2);<br>    }<br>...</pre><p>Then at the end of the function, we just need to use our new variables to return a delta merkle proof at the end of our setLeaf function:</p><pre>setLeaf(index: number, value: MerkleNodeValue): IDeltaMerkleProof{<br>    // get the old root and old value for the delta merkle proof<br>    const oldRoot = this.nodeStore.get(0, 0);<br>    const oldValue = this.nodeStore.get(this.height, index);<br>    // siblings array for delta merkle proof<br>    const siblings: MerkleNodeValue[] = [];<br><br>    // start traversing the leaf&#39;s merkle path at the leaf node<br>    let currentIndex = index;<br>    let currentValue = value;<br>    // don&#39;t set the root (level = 0) in the loop, as it has no sibling<br>    for(let level = this.height; level &gt; 0; level--){<br>      // set the current node in the tree<br>      this.nodeStore.set(level, currentIndex, currentValue);<br>      if(currentIndex % 2 === 0){<br>        // if the current index is even, then it has a sibling on the right (same level, index = currentIndex+1)<br>        const rightSibling = this.nodeStore.get(level, currentIndex+1);<br>        currentValue = hash(currentValue, rightSibling);<br>        // add the right sibling to the siblings array<br>        siblings.push(rightSibling);<br>      }else{<br>        // if the current index is odd, then it has a sibling on the left (same level, index = currentIndex-1)<br>        const leftSibling = this.nodeStore.get(level, currentIndex-1);<br>        currentValue = hash(leftSibling, currentValue);<br>        // add the left sibling to the siblings array<br>        siblings.push(leftSibling);<br>      }<br>      // set current index to the index of the parent node<br>      currentIndex = Math.floor(currentIndex/2);<br>    }<br>    // set the root node (level = 0, index = 0) to current value<br>    this.nodeStore.set(0, 0, currentValue);<br>    return { // &lt;--- HERE<br>      index,<br>      siblings,<br>      oldRoot,<br>      oldValue,<br>      newValue: value,<br>      newRoot: currentValue,<br>    };<br>  }</pre><p>Putting everything together, we can copy over our <em>verifyDeltaMerkleProof</em> from the <a href="https://medium.com/@carterfeldman/a-hackers-guide-to-layer-2-merkle-trees-from-scratch-f682fc31bced#2cdc">previous installment</a> to check to make sure the delta merkle proofs are valid:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/983c7039ae38ab9c933a9113348d9e2d/href">https://medium.com/media/983c7039ae38ab9c933a9113348d9e2d/href</a></iframe><p>Let’s run the javascript and see if it works:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FpoQgrgp%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DpoQgrgp&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FpoQgrgp&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FpoQgrgp-512.jpg%3Fversion%3D1686813854&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/1b6126fd10b69356bccc1128614cf540/href">https://medium.com/media/1b6126fd10b69356bccc1128614cf540/href</a></iframe><pre>[example3] delta merkle proof for index 0 is valid<br>[example3] delta merkle proof for index 1 is valid<br>[example3] delta merkle proof for index 2 is valid<br>[example3] delta merkle proof for index 3 is valid<br>[example3] delta merkle proof for index 4 is valid<br>[example3] delta merkle proof for index 5 is valid<br>[example3] delta merkle proof for index 6 is valid<br>[example3] delta merkle proof for index 7 is valid<br>[example3] the delta merkle proofs are:<br>[<br>  {<br>    &quot;index&quot;: 0,<br>    &quot;siblings&quot;: [<br>      &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;,<br>      &quot;f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b&quot;,<br>      &quot;db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71&quot;<br>    ],<br>    &quot;oldRoot&quot;: &quot;c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c&quot;,<br>    &quot;oldValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;,<br>    &quot;newValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;,<br>    &quot;newRoot&quot;: &quot;f06e424318b067ae608de0ef0035e9f48a2658cc59e7f94f9f94600b2a36eac6&quot;<br>  },<br>  ... /* omitted for brevity, check codepen for full result */<br>]</pre><p>Success! All of the delta merkle proofs are valid!</p><h3>Chain Delta Merkle Proofs</h3><p>Along with verifying delta merkle proofs, another important check that our zkVM will have to perform is known as a <strong>chain delta merkle proof</strong> verification.</p><p><strong>A chain delta merkle proof verification ensures that a series of delta merkle proofs are sequential</strong>, meaning that you have provided <strong>all</strong> the proofs for the <strong>state transition</strong> from the first delta merkle proof’s oldRoot to the last delta merkle proof in the series’s newRoot.</p><p>We can perform this verification by asserting that, for each delta merkle proof in the series, the oldRoot of the current proof equals the newRoot of the previous proof:</p><pre>...<br>for(let i=0;i&lt;deltaMerkleProofs.length;i++){<br>  const deltaProof = deltaMerkleProofs[i];<br>  if(!verifyDeltaMerkleProof(deltaProof)){ //first verify the proof<br>    console.error(&quot;[example4] ERROR: delta merkle proof for index &quot;+deltaProof.index+&quot; is INVALID&quot;);<br>    throw new Error(&quot;invalid delta merkle proof&quot;);<br>  }else if(i&gt;0 &amp;&amp; deltaProof.oldRoot !== deltaMerkleProofs[i-1].newRoot){ // &lt;------ [HERE]<br>    // the previous proof&#39;s new root should be the same as this proof&#39;s old root<br>    console.error(<br>      &quot;[example4] ERROR: delta merkle proof for index &quot;+deltaProof.index +<br>      &quot; has a different old root than the previous delta merkle proof&#39;s new root&quot;<br>  );<br>    throw new Error(&quot;delta merkle proof root sequence mismatch&quot;);<br>  }else{<br>    console.log(&quot;[example4] delta merkle proof for index &quot;+deltaProof.index+&quot; is valid&quot;);<br>  }<br>}<br>...</pre><p>Let’s update our code to include a chain delta merkle proof verification and check to make sure our code is working properly:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4a2ffc7dbd71c3ea2f61bb892affe2ba/href">https://medium.com/media/4a2ffc7dbd71c3ea2f61bb892affe2ba/href</a></iframe><p>Running example4, we get the result:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FyLQeoOv%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DyLQeoOv&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FyLQeoOv&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FyLQeoOv-512.jpg%3Fversion%3D1686813976&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/f353c000b1eed15abcf73ebcc6d4153b/href">https://medium.com/media/f353c000b1eed15abcf73ebcc6d4153b/href</a></iframe><pre>[example4] delta merkle proof for index 0 is valid<br>[example4] delta merkle proof for index 1 is valid<br>[example4] delta merkle proof for index 2 is valid<br>[example4] delta merkle proof for index 3 is valid<br>[example4] delta merkle proof for index 4 is valid<br>[example4] delta merkle proof for index 5 is valid<br>[example4] delta merkle proof for index 6 is valid<br>[example4] delta merkle proof for index 7 is valid<br>[example4] the delta merkle proofs are:<br>[<br>... /* omitted for brevity, check codepen for full result */<br>]</pre><p>Great! No errors 🎉, we have proves the changes we made are valid (delta merkle proof) and sequential (chain delta merkle proof).</p><h3>Finishing up our ZMT Database</h3><p>The final import function we will want to implement is a getLeaf function which returns the value of a leaf and an accompanying merkle proof of its inclusion within the tree. To do this, we just need to grab the value of the leaf, and then walk its merkle path to obtain the siblings and root using the same strategy we used for computing the delta merkle proof in setLeaf:</p><pre>class ZeroMerkleTree {<br>...<br>  getLeaf(index: number): IMerkleProof {<br>    // siblings array for merkle proof<br>    const siblings: MerkleNodeValue[] = [];<br>    // get value for the merkle proof<br>    const value = this.nodeStore.get(this.height, index);<br><br>    // start traversing the leaf&#39;s merkle path at the leaf node<br>    let currentIndex = index;<br>    let currentValue = value;<br>    // don&#39;t set the root (level = 0) in the loop, as it has no sibling<br>    for(let level = this.height; level &gt; 0; level--){<br>      if(currentIndex % 2 === 0){<br>        // if the current index is even, then it has a sibling on the right (same level, index = currentIndex+1)<br>        const rightSibling = this.nodeStore.get(level, currentIndex+1);<br>        currentValue = hash(currentValue, rightSibling);<br>        // add the right sibling to the siblings array<br>        siblings.push(rightSibling);<br>      }else{<br>        // if the current index is odd, then it has a sibling on the left (same level, index = currentIndex-1)<br>        const leftSibling = this.nodeStore.get(level, currentIndex-1);<br>        currentValue = hash(leftSibling, currentValue);<br>        // add the left sibling to the siblings array<br>        siblings.push(leftSibling);<br>      }<br>      // set current index to the index of the parent node<br>      currentIndex = Math.floor(currentIndex/2);<br>    }<br>    // current value is the root<br>    const root = currentValue;<br>    return {<br>      root,<br>      siblings,<br>      index,<br>      value,<br>    };<br>  }<br>...</pre><p>We can now put everything together to and use the verifyMerkleProof function we wrote in the <a href="https://medium.com/@carterfeldman/a-hackers-guide-to-layer-2-merkle-trees-from-scratch-f682fc31bced#ce75">previous installment</a> to verify the merkle proofs generated by getLeaf (<a href="https://gist.github.com/cf/5ae6a374a936a7034a1ac6c850732b84?file=example5.ts#file-example5-ts">full code gist</a>):</p><pre>/* see https://gist.github.com/cf/1b04459735c0b86dda56714205566c97 */<br>...<br>function example5(){<br>  const leavesToSet = [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;, // 1<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;, // 7<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;, // 4<br>    &quot;0000000000000000000000000000000000000000000000000000000000000002&quot;, // 2<br>    &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;, // 0<br>    &quot;0000000000000000000000000000000000000000000000000000000000000006&quot;, // 6<br>  ];<br>  const tree = new ZeroMerkleTree(3);<br>  const deltaMerkleProofs = leavesToSet.map((leaf, index)=&gt;tree.setLeaf(index, leaf));<br>  // verify the delta merkle proofs<br>  for(let i=0;i&lt;deltaMerkleProofs.length;i++){<br>    const deltaProof = deltaMerkleProofs[i];<br>    if(!verifyDeltaMerkleProof(deltaProof)){<br>      console.error(&quot;[example5] ERROR: delta merkle proof for index &quot;+deltaProof.index+&quot; is INVALID&quot;);<br>      throw new Error(&quot;invalid delta merkle proof&quot;);<br>    }else if(i&gt;0 &amp;&amp; deltaProof.oldRoot !== deltaMerkleProofs[i-1].newRoot){<br>      // the previous proof&#39;s new root should be the same as this proof&#39;s old root<br>      console.error(<br>        &quot;[example5] ERROR: delta merkle proof for index &quot;+deltaProof.index +<br>        &quot; has a different old root than the previous delta merkle proof&#39;s new root&quot;<br>    );<br>      throw new Error(&quot;delta merkle proof root sequence mismatch&quot;);<br>    }else{<br>      console.log(&quot;[example5] delta merkle proof for index &quot;+deltaProof.index+&quot; is valid&quot;);<br>    }<br>  }<br>  // don&#39;t print the delta merkle proofs to avoid clutter<br>  //console.log(&quot;[example5] the delta merkle proofs are:\n&quot;+JSON.stringify(deltaMerkleProofs, null, 2));<br>  // verify each leaf&#39;s merkle proof<br>  for(let i=0;i&lt;leavesToSet.length;i++){<br>    const proof = tree.getLeaf(i);<br>    if(!verifyMerkleProof(proof)){<br>      console.error(&quot;[example5] ERROR: merkle proof for index &quot;+proof.index+&quot; is INVALID&quot;);<br>      throw new Error(&quot;invalid merkle proof&quot;);<br>    }else if(proof.value !== leavesToSet[i]){<br>      console.error(&quot;[example5] ERROR: merkle proof for index &quot;+proof.index+&quot; has the wrong value&quot;);<br>      throw new Error(&quot;merkle proof value mismatch&quot;);<br>    }else{<br>      console.log(&quot;[example5] merkle proof for index &quot;+proof.index+&quot; is valid&quot;);<br>    }<br>    console.log(&quot;merkle proof for index &quot;+proof.index+&quot;: &quot;+JSON.stringify(proof, null, 2));<br>  }<br>}<br>example5();</pre><p>Running the code we get the result:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FExOPvyG%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DExOPvyG&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FExOPvyG&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FExOPvyG-512.jpg%3Fversion%3D1686814104&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/77bc5ce6884890c454f5de7a002370d2/href">https://medium.com/media/77bc5ce6884890c454f5de7a002370d2/href</a></iframe><pre>[example5] delta merkle proof for index 0 is valid<br>[example5] delta merkle proof for index 1 is valid<br>[example5] delta merkle proof for index 2 is valid<br>[example5] delta merkle proof for index 3 is valid<br>[example5] delta merkle proof for index 4 is valid<br>[example5] delta merkle proof for index 5 is valid<br>[example5] delta merkle proof for index 6 is valid<br>[example5] delta merkle proof for index 7 is valid<br>[example5] merkle proof for index 0 is valid<br>merkle proof for index 0: {<br>  &quot;root&quot;: &quot;7e286a6721a66675ea033a4dcdec5abbdc7d3c81580e2d6ded7433ed113b7737&quot;,<br>  &quot;siblings&quot;: [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;,<br>    &quot;6b0e4bcd4368ba74e6a99ee69334c2593bcae1170d77048854d228664218c56b&quot;,<br>    &quot;81b1e323f0e91a785dfd155817e09949a7d66fe8fdc4f31f39530845e88ab63c&quot;<br>  ],<br>  &quot;index&quot;: 0,<br>  &quot;value&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;<br>}<br>[example5] merkle proof for index 1 is valid<br>... /* excluded for brevity, check the codepen above for the full result */</pre><p>Fantastic!</p><p><strong>We have now written a merkle tree database which we can use with our zkVM</strong> 🎉🎉🎉</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/494/1*7UCM-lpud6yv4jFeJG5V_g.gif" /></figure><p>To show off our new super-powered merkle tree, lets create a merkle tree of height 50, which has 1,125,899,906,842,624 leaves, and set values for the 1337th and 999,999,999,999th node (<a href="https://gist.github.com/cf/5ae6a374a936a7034a1ac6c850732b84?file=example6.ts#file-example6-ts">link to code</a>)</p><p>At 64 bytes of storage per leaf, our previous implementation would need <strong>64 petabytes of RAM</strong> just to store the tree (not even modern super computers are capable of this), yet with our new ZeroMerkleTree implementation we can do this in the blink of an eye on laptop and only consume 6400 bytes:</p><pre>// for the whole code check the gist at https://gist.github.com/cf/9df9b18cd5877fb4fc5c8b31a3acf77d<br>...<br>function example6(){<br>  const tree = new ZeroMerkleTree(50);<br>  const deltaA = tree.setLeaf(999999999999,&quot;0000000000000000000000000000000000000000000000000000000000000008&quot;);<br>  const deltaB = tree.setLeaf(1337,&quot;0000000000000000000000000000000000000000000000000000000000000007&quot;);<br>  const proofA = tree.getLeaf(999999999999);<br>  const proofB = tree.getLeaf(1337);<br>  console.log(&quot;verifyDeltaMerkleProof(deltaA): &quot;+verifyDeltaMerkleProof(deltaA));<br>  console.log(&quot;verifyDeltaMerkleProof(deltaB): &quot;+verifyDeltaMerkleProof(deltaB));<br>  console.log(&quot;deltaA.newRoot === deltaB.oldRoot: &quot;+(deltaA.newRoot === deltaB.oldRoot));<br>  console.log(&quot;verifyMerkleProof(proofA): &quot;+verifyMerkleProof(proofA));<br>  console.log(&quot;verifyMerkleProof(proofB): &quot;+verifyMerkleProof(proofB));<br>  console.log(&quot;proofA: &quot;+JSON.stringify(proofA, null, 2));<br>  console.log(&quot;proofB: &quot;+JSON.stringify(proofB, null, 2));<br>}<br>example6();</pre><p>Which prints the result:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FjOQWLVa%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DjOQWLVa&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FjOQWLVa&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FjOQWLVa-512.jpg%3Fversion%3D1686814348&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/ba7beff7da00c5f18c5e5eb6688ebb21/href">https://medium.com/media/ba7beff7da00c5f18c5e5eb6688ebb21/href</a></iframe><pre>verifyDeltaMerkleProof(deltaA): true<br>verifyDeltaMerkleProof(deltaB): true<br>deltaA.newRoot === deltaB.oldRoot: true<br>verifyMerkleProof(proofA): true<br>verifyMerkleProof(proofB): true<br>proofA: {<br>  &quot;root&quot;: &quot;40db8b6edad868d911c8b9aea2692ee80b2e87ac407b8d1a5efe30419e843991&quot;,<br>  &quot;siblings&quot;: <br>   ... /* omitted for brevity, check the code pen for the full result */<br>  ],<br>  &quot;index&quot;: 999999999999,<br>  &quot;value&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000008&quot;<br>}<br>proofB: {<br>  &quot;root&quot;: &quot;40db8b6edad868d911c8b9aea2692ee80b2e87ac407b8d1a5efe30419e843991&quot;,<br>  &quot;siblings&quot;: [<br>    ... /* omitted for brevity, check the code pen for the full result */<br>  ],<br>  &quot;index&quot;: 1337,<br>  &quot;value&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;<br>}</pre><p>We have climbed the impossible mountain, and built a merkle tree ready for our zkVM, all thanks to ZeroHashes and our ZMT 💪</p><h3>Append Only Merkle Trees</h3><p>Our ZMT implementation is perfect for our zkVM use case, but when we are building on legacy blockchains (EVM/zkEVM), storage can be expensive to the point where even our ZMT will prove too costly to make sense for use cases like <strong>verifiable event logs</strong>.</p><p>We will touch on verifiable event logs in more detail in a future installment where we explore trustless cross chain bridges, but for now it is sufficient to know that these write-only merkle trees are critical for trustlessly bridging data &amp; assets from layer 1 to layer 2.</p><h3>The Verifiable Event Log</h3><p>The basic theory behind a verifiable event log is that we want to allow a smart contract to record events in the leaves of a merkle tree. Whenever a new event occurs, we append a leaf to the merkle tree (set the next non-zero leaf in the merkle tree to the hash of the event) and calculate the updated root. You can then process these events off-chain using zero knowledge proofs or a layer 2, and submit proofs which prove you have sync’d all the events up to the latest event log root, thus providing trustless data transfer from layer 1 to layer 2.</p><p>The most important property of use case is that the smart contract only needs to write leaves to the merkle tree and never has to read back the previous event leaves (we only really care about reading the latest root).</p><p>With our understanding of the <strong>zero hashes</strong> and some cleverness, it turns out that <strong>we will be able to build an append-only merkle tree of height N which only needs to store O(log(N)) hashes on chain</strong>!</p><p>As we did before, let’s get started by visualizing the changes in the merkle tree when we append leaves and see if we can find any patterns.</p><p>In the animation below, we visualize the delta merkle proof results from appending a leaf where:</p><ul><li>the <strong>purple</strong> node represents the <strong>leaf</strong> being appending</li><li>the <strong>blue</strong> nodes represent the leaf’s <strong>merkle path</strong></li><li>the <strong>red</strong> nodes represent the <strong>siblings</strong></li></ul><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fimgur.com%2Fa%2Fq09RZjl%2Fembed%3Fpub%3Dtrue%26ref%3Dhttps%253A%252F%252Fembed.ly%26w%3D900&amp;display_name=Imgur&amp;url=https%3A%2F%2Fimgur.com%2Fa%2Fq09RZjl&amp;image=https%3A%2F%2Fi.imgur.com%2FZRijTV2.jpg%3Ffbplay&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=imgur" width="900" height="581" frameborder="0" scrolling="no"><a href="https://medium.com/media/21cd1bb19272ddd991ec2181f61d50df/href">https://medium.com/media/21cd1bb19272ddd991ec2181f61d50df/href</a></iframe><p>Recall that our goal is to be able to some how compute the new merkle root while storing the minimum amount of data possible. We also know from our previous installment that the information required to compute a merkle tree’s root is known as a <strong>merkle proof</strong> and contains the following information</p><ul><li>index (index of the leaf)</li><li>value (value of the leaf)</li><li>siblings (siblings of the leaf’s merkle path)</li></ul><p>In the case of our append only merkle tree, the index and value will be passed to the function, so the only thing we would be missing for our calculation is a method for calculating the siblings for the next merkle proof using the data stored in our contract. If we used the ZMT implementation from the previous section, we would have to store all the nodes in the tree on-chain, which is undesirable.</p><p>One way we might go about tackling the problem is storing the previous node’s merkle proof, and somehow writing a function which computes the new siblings from the previous nodes merkle proof.</p><p>If we could do that, we could accomplish our goal of O(log(n)) storage by repeating the procedure below each time we append a leaf to the tree:</p><ol><li>Read the previous merkle proof from the smart contract storage, and increment the index by 1</li><li>Somehow calculate the new siblings for the leaf we are appending using the zero hashes and the previous merkle proof</li><li>Compute the new merkle root using the new siblings and the value we are appending, and then overwrite the old merkle proof with the newly appended leaf’s merkle proof to the smart contract storage</li></ol><p>This of course begs the question: is it possible to compute the siblings of the next leaf in our append only merkle tree using only the zero hashes and previous merkle proof?</p><p>Let’s examine the animation once again, but this time let’s pay attention to how the siblings change as each leaf is appended to the tree. We will still color all the sibling nodes red, but this time we will make the sibling’s text yellow if it was not a sibling for the previous node’s merkle proof:</p><p>Starting to see the pattern? The key to cracking this puzzle is remembering that the siblings are just the adjacent nodes to the leaf’s merkle path, so when transitioning from leaf N to leaf N+1:</p><ul><li>On the levels where the merkle paths of leaf N and leaf N+1 are the same, you can reuse the sibling from leaf N’s merkle proof in leaf N+1’s merkle proof</li><li>On the levels where the merkle paths of leaf N and leaf N+1 are the different, you need to somehow compute what new sibling is.</li></ul><p>Great! So for the levels where the merkle path doesn’t change, we know to just reuse the sibling from the previous leaf’s proof, but what about the second case?</p><p>Let’s examine some more append transition to get some intuition:</p><p>Notice that, because it is an append-only merkle tree, our leaf index is always increasing, and therefore our path is always moving to the right.</p><p>Let’s write down what we know about the relationship between Leaf[n] and Leaf[n+1]’s merkle paths/siblings on a tree with height H and then try to deduce a formula from the facts we know:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1jm-0plDluusU_bJrbwgyQ.png" /><figcaption><em>Sorry for the image embed, medium doesn’t support LaTeX so I can’t inline this</em></figcaption></figure><p>Aha! So the only information we will need to generate a merkle proof for Leaf[n+1] is:</p><ul><li>the zero hashes for the tree</li><li>Leaf[n]’s siblings/value</li><li>Leaf[n]’s merkle path (we can compute this using the siblings and value!)</li><li>Leaf[n+1]’s value</li></ul><p>This is very cool, it looks like all we need to store when building our append only merkle tree is latest leaf’s merkle proof!</p><p>Let’s write our implementation based on the facts we wrote down above:</p><pre>class AppendOnlyMerkleTree {<br>  height: number;<br>  lastProof: IMerkleProof;<br>  zeroHashes: MerkleNodeValue[];<br>  constructor(height: number){<br>    this.height = height;<br>    this.zeroHashes = computeZeroHashes(height);<br>    // create a dummy proof of all zero hashes for initialization (before we append any leaves, we know all the siblings will be zero hashes because it is an empty tree)<br>    this.lastProof = {<br>      root: this.zeroHashes[this.height],<br>      siblings: this.zeroHashes.slice(0, this.height),<br>      index: -1,<br>      value: this.zeroHashes[this.height],<br>    };<br>  }<br>  appendLeaf(leafValue: string): IDeltaMerkleProof {<br>    const oldMerklePath = computeMerklePathFromProof(this.lastProof.siblings, this.lastProof.index, this.lastProof.value);<br>    // get the old root and old value for the delta merkle proof<br>    const oldRoot = this.lastProof.root;<br>    // the old value will always be empty, thats why its an append only tree :P<br>    const oldValue = &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;;<br>    const prevIndex = this.lastProof.index;<br>    // append only tree = new index is always the previous index + 1<br>    const newIndex = prevIndex+1;<br>    // keep track of the old siblings so we can use them for our delta merkle proof<br>    const oldSiblings =this.lastProof.siblings;<br>    const siblings: MerkleNodeValue[] = [];<br>    let multiplier = 1;<br>    for(let level=0;level&lt;this.height;level++){<br>      // get the index of the previous leaf&#39;s merkle path node on the current level<br>      const prevLevelIndex = Math.floor(prevIndex/multiplier);<br>      // get the index of the new leaf&#39;s merkle path node on the current level<br>      const newLevelIndex = Math.floor(newIndex/multiplier);<br><br>      if(newLevelIndex===prevLevelIndex){<br>        // if the merkle path node index on this level DID NOT change, we can reuse the old sibling<br>        siblings.push(oldSiblings[level]);<br>      }else{<br>        // if the merkle path node index on this level DID change, we need to check if the new merkle path node index is a left or right hand node<br>        if(newLevelIndex%2===0){<br>          // if the new merkle path node index is even, the new merkle path node is a left hand node,<br>          // so merkle path node&#39;s sibling is a right hand node,<br>          // therefore our sibling has an index greater than our merkle path node,<br>          // so the sibling must be a zero hash<br>          // QED<br>          siblings.push(this.zeroHashes[level]);<br>        }else{<br>          // if the new merkle path node is odd, then its sibling has an index one less than it, so its sibling must be the previous merkle path node on this level<br>          siblings.push(oldMerklePath[level]);<br>        }<br>      }<br>      multiplier = multiplier * 2;<br>    }<br>    const newRoot = computeMerkleRootFromProof(siblings, newIndex, leafValue);<br>    this.lastProof = {<br>      root: newRoot,<br>      siblings: siblings,<br>      index: newIndex,<br>      value: leafValue,<br>    };<br>    return {<br>      index: this.lastProof.index,<br>      siblings,<br>      oldRoot,<br>      oldValue,<br>      newRoot,<br>      newValue: leafValue,<br>    };<br>  }<br>}</pre><p>Looks good, let’s test it out using our previous verify delta merkle proof function to make sure it is working correctly:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7db55f16d9df02c517431f4825fe1dee/href">https://medium.com/media/7db55f16d9df02c517431f4825fe1dee/href</a></iframe><p>When we run example7, we get the output:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FoNQbeGN%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DoNQbeGN&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FoNQbeGN&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FoNQbeGN-512.jpg%3Fversion%3D1686815090&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/8d898a06c8bf36fe486762c98d4c2160/href">https://medium.com/media/8d898a06c8bf36fe486762c98d4c2160/href</a></iframe><pre>verifyDeltaMerkleProof(deltaA): true<br>verifyDeltaMerkleProof(deltaB): true<br>deltaA.newRoot === deltaB.oldRoot: true<br>deltaA: {<br>  &quot;index&quot;: 0,<br>  &quot;siblings&quot;: [<br>... /* omitted for brevity, check the code pen for the full result */<br>  ],<br>  &quot;oldRoot&quot;: &quot;e833d7a67160e68bf4c9044a53077df2727ad00cf36f4949c7b681a912140cbb&quot;,<br>  &quot;oldValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;,<br>  &quot;newRoot&quot;: &quot;bfe0338f3c07c1ff64514ccde5d0e4535b88c9093454b29de1590414c721abb1&quot;,<br>  &quot;newValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000008&quot;<br>}<br>deltaB: {<br>  &quot;index&quot;: 1,<br>  &quot;siblings&quot;: [<br>... /* omitted for brevity, check the code pen for the full result */<br>  ],<br>  &quot;oldRoot&quot;: &quot;bfe0338f3c07c1ff64514ccde5d0e4535b88c9093454b29de1590414c721abb1&quot;,<br>  &quot;oldValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;,<br>  &quot;newRoot&quot;: &quot;3b6c4c5cf467972101c5236a32eb2f5e23c66fab942352d1e7003660f83f66b2&quot;,<br>  &quot;newValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;<br>}<br>verifyDeltaMerkleProof(delta[0]): true<br>verifyDeltaMerkleProof(delta[1]): true<br>verifyDeltaMerkleProof(delta[2]): true<br>verifyDeltaMerkleProof(delta[3]): true<br>verifyDeltaMerkleProof(delta[4]): true<br>verifyDeltaMerkleProof(delta[5]): true<br>verifyDeltaMerkleProof(delta[6]): true<br>verifyDeltaMerkleProof(delta[7]): true<br>verifyDeltaMerkleProof(delta[8]): true<br>verifyDeltaMerkleProof(delta[9]): true<br>verifyDeltaMerkleProof(delta[10]): true<br>verifyDeltaMerkleProof(delta[11]): true<br>verifyDeltaMerkleProof(delta[12]): true<br>verifyDeltaMerkleProof(delta[13]): true<br>verifyDeltaMerkleProof(delta[14]): true<br>verifyDeltaMerkleProof(delta[15]): true<br>verifyDeltaMerkleProof(delta[16]): true<br>verifyDeltaMerkleProof(delta[17]): true<br>verifyDeltaMerkleProof(delta[18]): true<br>verifyDeltaMerkleProof(delta[19]): true<br>verifyDeltaMerkleProof(delta[20]): true<br>verifyDeltaMerkleProof(delta[21]): true<br>verifyDeltaMerkleProof(delta[22]): true<br>verifyDeltaMerkleProof(delta[23]): true<br>verifyDeltaMerkleProof(delta[24]): true<br>verifyDeltaMerkleProof(delta[25]): true<br>verifyDeltaMerkleProof(delta[26]): true<br>verifyDeltaMerkleProof(delta[27]): true<br>verifyDeltaMerkleProof(delta[28]): true<br>verifyDeltaMerkleProof(delta[29]): true<br>verifyDeltaMerkleProof(delta[30]): true<br>verifyDeltaMerkleProof(delta[31]): true<br>verifyDeltaMerkleProof(delta[32]): true<br>verifyDeltaMerkleProof(delta[33]): true<br>verifyDeltaMerkleProof(delta[34]): true<br>verifyDeltaMerkleProof(delta[35]): true<br>verifyDeltaMerkleProof(delta[36]): true<br>verifyDeltaMerkleProof(delta[37]): true<br>verifyDeltaMerkleProof(delta[38]): true<br>verifyDeltaMerkleProof(delta[39]): true<br>verifyDeltaMerkleProof(delta[40]): true<br>verifyDeltaMerkleProof(delta[41]): true<br>verifyDeltaMerkleProof(delta[42]): true<br>verifyDeltaMerkleProof(delta[43]): true<br>verifyDeltaMerkleProof(delta[44]): true<br>verifyDeltaMerkleProof(delta[45]): true<br>verifyDeltaMerkleProof(delta[46]): true<br>verifyDeltaMerkleProof(delta[47]): true<br>verifyDeltaMerkleProof(delta[48]): true<br>verifyDeltaMerkleProof(delta[49]): true</pre><p>Success!</p><p>Since we are only storing one merkle proof in our class, we only need O(log(n)) storage for our append only merkle tree 🎉</p><h3>Spiderman Proofs</h3><p>Sometimes we want to update whole sections of a tree:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Pg16AvoPgDCTjYvMf3xT1w.png" /></figure><p>Using our ZMT implementation, we can already do this <strong>by updating each leaf, one-by-one</strong>, but perhaps there is a better way to go about updating batches of leaves.</p><p>Let’s first try the tree highlighted in red on the the left:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R0w65DAAjX7xzoRDuck10g.png" /><figcaption>We want to replace the top sub tree with the bottom tree</figcaption></figure><p>Notice that the sub tree with root N(1,0) in our big tree looks the same as our updated smaller tree on the bottom, except with different values.</p><p>If we also look carefully at the big tree on top, the nodes above the sub-tree we want to modify form a tiny height = 1 tree of their own:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3Jupk_rIqBX4JuV1BqRwhw.png" /></figure><p>Great! We know how to update the leaves of a merkle tree, let’s start building a delta merkle proof from the information we know.</p><ul><li>We know that the node we want to update is N(1,0), so the index of the delta merkle proof will be 1.</li><li>We know that the siblings of the delta merkle proof will be [N(1,1)] because N(1,0) is the only node on the merkle path, and its sibling is N(1,1)</li><li>We know that the old root of the delta merkle proof will be our old N(0,0)</li><li>We know that our old value is the value of N(1,0) because that’s the “leaf” we want to update</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*56Keh913GEyiTHkMgnSX6A.png" /></figure><p>Now all we have to do is update the leaf, and apply the rules for delta merkle proofs that we learned in the previous installment:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u9HHJelhMUEwjXyp_ScUqQ.png" /></figure><p>Fantastic! We now know how to efficiently replace sections of our merkle tree and generate delta merkle proofs to prove the validity of our changes.</p><p>For the rest of the sub-trees we want to replace, we can just update them in succession using the same rules:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MNJHd6ATEw4pETgHSgWdkw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lt2tS0RGm8pbNRDKy5ziqw.png" /></figure><p>For now, we won’t write the code in TypeScript for this operation, as we will only need to write this logic in our arithmetic circuit for reasons we will explore in a future installment.</p><h3>Conclusion</h3><p>Great work if you have made it this far, you now know all the information about merkle trees that will be needed for constructing our zkVM!</p><p>In our next installment, we will write our first zero knowledge circuits using plonky2, and begin implementing our zkVM!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4lo50nGr-s-jZFTv.png" /></figure><p>Thanks to our sponsors <a href="https://oas.gg">OAS</a> &amp; QED, you can follow OAS on twitter <a href="https://twitter.com/OASNetwork">@OASNetwork</a></p><p><em>about the author</em> — follow <a href="https://twitter.com/cmpeq">@cmpeq</a> on twitter or check out my <a href="https://medium.com/@carterfeldman/about">about page</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ujG7iYK9cPYmlKfitX_eZw.png" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d612ea846016" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Hackers Guide to Layer 2: Merkle Trees from Scratch]]></title>
            <link>https://medium.com/@carterfeldman/a-hackers-guide-to-layer-2-merkle-trees-from-scratch-f682fc31bced?source=rss-44247fb3ab1d------2</link>
            <guid isPermaLink="false">https://medium.com/p/f682fc31bced</guid>
            <category><![CDATA[merkle-tree]]></category>
            <category><![CDATA[blockchain-development]]></category>
            <category><![CDATA[zero-knowledge-proofs]]></category>
            <category><![CDATA[zksnark]]></category>
            <category><![CDATA[blockchain]]></category>
            <dc:creator><![CDATA[Carter Feldman]]></dc:creator>
            <pubDate>Tue, 30 May 2023 15:43:13 GMT</pubDate>
            <atom:updated>2023-07-22T17:03:29.111Z</atom:updated>
            <content:encoded><![CDATA[<p><em>In this tutorial, we will implement merkle trees, merkle proofs, and delta merkle proofs in JavaScript from first principles.</em></p><blockquote>This is the first weekly installment of <strong><em>A Hackers Guide to Layer 2: Building a zkVM from Scratch.</em></strong></blockquote><blockquote><em>Follow </em><a href="https://twitter.com/cmpeq"><strong><em>@cmpeq</em></strong></a><em> on twitter to stay up to date with future installments.</em></blockquote><blockquote><strong><em>What I cannot create, I do not understand.<br></em></strong><em>Richard Feynman</em></blockquote><h3>Introduction</h3><p>Merkle trees are one of the most important tools in any would be layer 2 developer’s toolbox. In the spirit of Feynman’s approach to understanding a topic, we will implement Merkle Trees from scratch and build <strong>deep Merkle Tree intuition</strong> that will be essential later in our layer 2 development journey.</p><h3>Inventing Merkle Trees: Data Integrity</h3><p>Let’s imagine Jack wants to send a 400 gigabyte file to his friend Sally over an unreliable internet connection. It will take hours for the file to send, and Sally wants to make sure that the file she gets isn’t corrupted or changed while it is being transferred.</p><p>One way we might try to solve this is by having Jack create a digital signature of the whole file, and then have Sally verify the signature when the file transfer is complete. Unfortunately, however, digital signature technology has a property that: <strong>the longer the data you want to sign, the slower it takes to generate a digital signature</strong>, so it is impractical for Jack digitally sign the whole file.</p><p>They can get around this problem by taking advantage of digital fingerprint (also known as cryptographic hashing) technology. Taking the hash (or digital fingerprint) of a large file generates <strong>a short 32 byte number </strong>to represent the data that was hashed.<strong> Most importantly, if you change even a single byte of the large file, its fingerprint will also change</strong>:</p><ol><li>Jack sends the hash, the signature and the file to Sally</li><li>When Sally receives the file, she computes the hash on her machine, and then checks to see if the hash matches the one that Jack digitally signed.</li></ol><p>This is all well and good, but since the transfer can take hours and it is very likely that something gets corrupted during the transfer, Sally keeps on finding that the hash she computes is different than the one signed by Jack, and<strong> Jack has to send the 400 GB file all over again</strong> (very slow).</p><p>One way to fix this would be to have Jack first<strong> split the file into two pieces (A and B) and hash each piece. </strong>That way the size of any data that has to be resent is 200 GB instead of 400 GB.</p><p>If Jack is really clever, he can make it so he still only has to compute one digital signature, by signing the <strong>Hash(Hash(A)+Hash(B))</strong> (called the <strong>Root</strong>). This way Sally knows the hashes of A and B weren’t modified during the transfer and can be confident that the file she receives is the same as Jack’s original file. <strong>If any data in A or B is modified, Hash(Hash(A)+Hash(B)) will not match Jack’s original signature.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/385/1*bIdd_lR_-PjAN6jL4ry_yw@2x.png" /><figcaption>Split the file into two pieces, hash each piece and then hash the combination of the two pieces’ hashes</figcaption></figure><p>In fact, we can split the file into even smaller pieces to make the size of the any data that has to be resent much smaller:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VKcMpp7vMfMkXBo_HSZ7qg@2x.png" /><figcaption>If Jack splits the file into 8 pieces, he would create a structure like this and sign N(0,0)</figcaption></figure><p>This business of splitting up data and recursively hashing is called a building a <strong>Merkle Tree.</strong></p><p>Like in our example, the basic principle of a Merkle Tree is:<br><strong>if you modify the value of any of the nodes at the bottom</strong> which represent our dataset,<strong> the value of the node at the top will also change</strong>.</p><p>The nodes at the bottom which contain our dataset are known as <strong>leaves</strong> and the node at the top (aka. N(0,0)) is known as the <strong>Merkle Root</strong>.</p><p>For convenience, we can identify each node N with two numbers:</p><ol><li>The <strong>level</strong> of the node (the top node is on level 0, and the nodes below it has level = 1, etc.)</li><li>The <strong>index</strong> of the node (for the index, we just count from left to right with the node furthest left having index = 0)</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MgHle7vVKF6sWBMOHk9fJg@2x.png" /><figcaption>The Merkle Tree Cheat Sheet</figcaption></figure><p>Using our node definition, we can also say that every node <strong>N(level, index)</strong>, has two <strong>children</strong> <strong>N(level+1, index*2)</strong> and <strong>N(level+1, index*2+1). </strong>This is to reflect that each level has twice as many nodes as the level above it.</p><p>In addition, using our previous rule of computing Hash(Hash(A), Hash(B)), we can define the value of each non-leaf node as:<strong><br>N(level, index) = Hash(N(level+1, index*2), N(level+1, index*2+1))</strong></p><p>We can also define the <strong>height </strong>of the merkle tree as the <strong>distance from the leaves from the merkle root</strong> (ie. count the lines in the path between the leaf nodes and the root node at the top).<strong> The leaves of the Merkle Tree always have a level number equal to the height of the tree</strong>.</p><h3>Coding a Merkle Tree</h3><p>Now that we have invented the Merkle Tree, we can demonstrate our new found knowledge by writing a JavaScript implementation.</p><p>First we will need to select a function to calculate our digital fingerprints. We can use the common <strong>sha256</strong> hashing function to do the job of computing <strong>Hash(childA, childB):</strong></p><pre>const shajs = require(&quot;sha.js&quot;); // npm install --save sha.js<br><br>function hash(leftNode, rightNode) {<br>  return shajs(&quot;sha256&quot;)<br>    .update(leftNode + rightNode, &quot;hex&quot;)<br>    .digest(&quot;hex&quot;);<br>}</pre><p>With a hash function in hand, we can write a simple class which computes the merkle root given a dataset of leaves and the corresponding tree height:</p><pre>const shajs = require(&quot;sha.js&quot;); // npm install --save sha.js<br><br>function hash(leftNode, rightNode) {<br>  return shajs(&quot;sha256&quot;)<br>    .update(leftNode + rightNode, &quot;hex&quot;)<br>    .digest(&quot;hex&quot;);<br>}<br><br>class MerkleTree {<br>  constructor(height, leaves){<br>    this.height = height;<br>    this.leaves = leaves;<br>  }<br>  N(level, index){<br>    if(level === this.height){<br>      // if level == height, the node is a leaf, <br>      // so just return the value from our dataset<br>      return this.leaves[index];<br>    }else{<br>      // if the node is not a leaf, use our definition:<br>      // N(level, index) = Hash(N(level+1, index*2), N(level+1, index*2+1))<br>      return hash(<br>        this.N(level+1, 2*index),<br>        this.N(level+1, 2*index+1),<br>      );<br>    }<br>  }<br>  getRoot(){<br>    // the node N(0,0) is always the root node<br>    return this.N(0,0);<br>  }<br>}</pre><p>Let’s test out our merkle tree implementation with a tree that has <br>leaves = [1, 3, 3, 7, 4, 2, 0, 6] and a height of 3:</p><figure><img alt="a tree that with leaves = [1, 3, 3, 7, 4, 2, 0, 6] and a height of 3" src="https://cdn-images-1.medium.com/max/1024/1*5JOf6Sf59krEVTYJA2ZVRA@2x.png" /><figcaption>a tree that with leaves = [1, 3, 3, 7, 4, 2, 0, 6] and a height of 3</figcaption></figure><p>In our JavaScript file, we can write each leaf as a hexadecimal string, create a new instance of MerkleTree, and call the getRoot function to compute the root:</p><pre>function example1(){<br>  const height = 3;<br>  const leaves = [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;, // 1<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;, // 7<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;, // 4<br>    &quot;0000000000000000000000000000000000000000000000000000000000000002&quot;, // 2<br>    &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;, // 0<br>    &quot;0000000000000000000000000000000000000000000000000000000000000006&quot;, // 6<br>  ];<br>  const tree = new MerkleTree(height, leaves);<br>  console.log(&quot;[EX_1] the merkle tree&#39;s root is: &quot;+tree.getRoot());<br>}<br>example1();</pre><p>If we run this code, we will get the output:</p><pre>[EX_1] the merkle tree&#39;s root is: 7e286a6721a66675ea033a4dcdec5abbdc7d3c81580e2d6ded7433ed113b7737</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FrNqbrvG%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DrNqbrvG&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FrNqbrvG&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FrNqbrvG-512.jpg%3Fversion%3D1685262170&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/0d92824247b81da85a83af9e469b8e9a/href">https://medium.com/media/0d92824247b81da85a83af9e469b8e9a/href</a></iframe><p>If we change any of the leaves (for example, changing the 2 to a 9), we should also see that the root changes:</p><pre>function example2(){<br>  const height = 3;<br>  const leaves = [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;, // 1<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;, // 7<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;, // 4<br>    &quot;0000000000000000000000000000000000000000000000000000000000000002&quot;, // 2<br>    &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;, // 0<br>    &quot;0000000000000000000000000000000000000000000000000000000000000006&quot;, // 6<br>  ];<br>  const tree = new MerkleTree(height, leaves);<br>  console.log(&quot;[EX_2] before root is: &quot;+tree.getRoot());<br>  // change the 2 to a 9<br>  tree.leaves[5] = &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;;<br>  console.log(&quot;[EX_2] after root is: &quot;+tree.getRoot());<br>}<br>example2();</pre><p>If we run example2(), we will see the output:</p><pre>[EX_2] before root is: 7e286a6721a66675ea033a4dcdec5abbdc7d3c81580e2d6ded7433ed113b7737<br>[EX_2] after root is: e74ae221cf067dc8e88195f5b30bfda60dc224a8cd5554bc6345c87ad8c6f925</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FjOeRpKM%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DjOeRpKM&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FjOeRpKM&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FjOeRpKM-512.jpg%3Fversion%3D1685262336&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/492cb785d78eb0727ac37cc5071f05c0/href">https://medium.com/media/492cb785d78eb0727ac37cc5071f05c0/href</a></iframe><p>Success! The root before the change matches our original root because it had the same dataset, and the after root has updated because we changed one of the leaves in the dataset.</p><p>Let’s look deeper into why changing one of the leaves changes the root:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LGV0j-CGtiPY0GFmsUBUyQ@2x.png" /><figcaption>The effects from changing leaf N(3,5) from 2 to 9 bubbles up the tree</figcaption></figure><ul><li>Since we changed N(3,5), its parent <strong>N(2,2)</strong> changes because N(2,2) is defined as N(2,2) = Hash(N(3,4), <strong>N(3,5)</strong>)</li><li>Since N(2,2) changed, its parent <strong>N(1,1)</strong> changes because N(1,1) is defined as N(1,1) = Hash(<strong>N(2,2)</strong>, N(2,3))</li><li>Since N(1,1) changed, its parent <strong>N(0,0)</strong> changes because N(0,0) is defined as N(0,0) = Hash(N(1,0), <strong>N(1,1)</strong>)</li></ul><p>The nodes that change when you update a leaf, are called the node’s <strong>merkle path. </strong>So in this case, we could say the merkle path of N(3,5) is<strong> [N(3,5), N(2,2), N(1,1), N(0,0)]</strong>.</p><p><strong>To compute the merkle path of a node, we just recursively list out the ancestors of the node.</strong> (ancestors = parent of node, parent of parent, parent of parent of parent…).</p><p>Since we know from before that every node <strong>N(level, index)</strong>, has two <strong>children</strong> <strong>N(level+1, index*2)</strong> and <strong>N(level+1, index*2+1)</strong>, we can say that the parent of a node N(level, index) is <strong>N(level-1, floor(index/2))</strong>.</p><p>Using this knowledge, we can write a JavaScript function to calculate the merkle path of a node and check our earlier observation with N(3,5):</p><pre>function getMerklePathOfNode(level, index){<br>  const merklePath = [];<br>  for(let currentLevel=level;currentLevel&gt;=0;currentLevel--){<br>    merklePath.push({<br>      level: currentLevel,<br>      index: index,<br>    });<br>    index = Math.floor(index/2);<br>  }<br>  return merklePath;<br>}<br><br>console.log(getMerklePathOfNode(3,5))</pre><p>When we run it, the log prints [N(3,5), N(2,2), N(1,1), N(0,0)]:</p><pre>[<br>  { level: 3, index: 5 },<br>  { level: 2, index: 2 },<br>  { level: 1, index: 1 },<br>  { level: 0, index: 0 }<br>]</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FoNaOMya%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DoNaOMya&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FoNaOMya&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FoNaOMya-512.jpg%3Fversion%3D1685262466&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/b48da88fbd7c86d5e0df7fd70b1519b4/href">https://medium.com/media/b48da88fbd7c86d5e0df7fd70b1519b4/href</a></iframe><p>In practice, we often exclude the root node from our returned merkle path as every node’s merkle path contains it, so we can rewrite our function to return the all nodes in the merkle path except the root (level&gt;0):</p><pre>function getMerklePathOfNode(level, index){<br>  const merklePath = [];<br>  for(let currentLevel=level;currentLevel&gt;0;currentLevel--){<br>    merklePath.push({<br>      level: currentLevel,<br>      index: index,<br>    });<br>    index = Math.floor(index/2);<br>  }<br>  return merklePath;<br>}</pre><h3>Siblings</h3><p>Before we continue it is important that we talk about the concept of left handed and right handed nodes on a merkle tree.</p><ul><li>We say that a node is <strong>left handed</strong> if it appears in the tree on the left side of its parent.</li><li>We say that a node is <strong>right handed</strong> if it appears in the tree on the right side of its parent.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*us0rVsCO4_kp4RTHku2pnw@2x.png" /></figure><p>Given our original definition of the merkle tree where we say that every non-leaf node N(level, index) has a left child and a right child:</p><ul><li>Left Child: N(level+1, 2*index)</li><li>Right Child: N(level+1, 2*index + 1)</li></ul><p>Given this definition, we can see that all left handed nodes have an even index (2*x), and right handed nodes have an odd index (2*x+1).</p><p>Therefore:</p><ul><li>If a node N(level, index) has an even index, we say that its opposite handed sibling is N(level, index+1).</li><li>If a node N(level, index) has an odd index, we say that its opposite handed sibling is N(level, index-1)</li></ul><p>Let’s write a JavaScript function to return the sibling of any node we specify on a tree:</p><pre>function getSiblingNode(level, index){<br>  // if node is the root, it has no sibling<br>  if(level === 0){<br>    throw new Error(&quot;the root does not have a sibling&quot;)<br>  }else if(index % 2 === 0){<br>    // if node is even, its sibling is at index+1<br>    return {level: level, index: index+1};<br>  }else{<br>    // if node is odd, its sibling is at index-1<br>    return {level: level, index: index-1};<br>  }<br>}</pre><h3>Merkle Proofs</h3><p>We often want to prove the <strong>inclusion</strong> of a certain leaf in a merkle tree with a known root. Let’s imagine Bob wants to prove to his friend Todd that the value 9 exists at leaf N(3,5) on a tree with a known merkle root R.</p><p>The most naive way for Bob to do this is:</p><ol><li>Bob gives Todd a list of all the leaves</li><li>Todd uses our MerkleTree class to calculate N(0,0)</li><li>Todd compares N(0,0) with R to make sure they are the same</li></ol><p>For large trees, however, this is not very practical. If Bob wants to prove the value of a leaf in a tree with 1,000,000 leaves, Bob must send all 1,000,000 leaves to Todd, and Todd must do 999,999 computations to calculate the root.</p><p>If we look back at our merkle path diagram, however, there is a faster way we can do this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TV4kjZX3Ey3Jt37pNhMlxA@2x.png" /><figcaption>The <strong>siblings</strong> of N(3,5)’s merkle path are highlighted in <strong>blue</strong></figcaption></figure><p>In our diagram we can see that Todd can calculate the root if Bob gives him: the <strong>value</strong> of the leaf, the <strong>index</strong> of the leaf and the <strong>siblings</strong> of the non-root nodes on the leaf’s merkle path (N(3,4), N(2,3) and N(1,0)):</p><p><strong>N(0,0) = Hash(N(1,0), Hash(Hash(N(3,4), 9), N(2,3)))</strong></p><p>Using this method, for a tree with N leaves, Todd only needs to compute log(N) computations, so if the tree has 1,000,00 leaves, he only needs to perform 20 computations instead of instead of 999,999 computations!</p><p>The value, index and siblings together are known as a merkle proof, as they are the key ingredients required to prove a leaf’s existence in a merkle tree with a known root.</p><p>Let’s first write a function the calculate the siblings of a given node’s merkle path:</p><p>Let’s update our MerkleTree class to include a getMerkleProof function:</p><pre>class MerkleTree {<br>// ...<br>getMerkleProof(level, index){<br><br>    // get the value of the leaf node<br>    const leafValue = this.N(level, index);<br><br>    // get the levels and indexes of the nodes on the leaf&#39;s merkle path<br>    const merklePath = getMerklePathOfNode(level, index);<br><br>    // get the levels and indexes of the siblings of the nodes on the merkle path<br>    const merklePathSiblings = merklePath.map((node) =&gt; {<br>      return getSiblingNode(node.level, node.index);<br>    });<br><br>    // get the values of the sibling nodes<br>    const siblingValues = merklePathSiblings.map((node) =&gt; {<br>      return this.N(node.level, node.index);<br>    });<br><br>    return {<br>      root: this.getRoot(), // the root we claim to be our tree&#39;s root<br>      siblings: siblingValues, // the siblings of our leaf&#39;s merkle path<br>      index: index, // the index of our leaf<br>      value: leafValue, // the value of our leaf<br>    };<br>  }<br>}</pre><p>We can also write a function which calculates the merkle root using the proof’s siblings, index and value:</p><pre>function computeMerkleRootFromProof(siblings, index, value){<br>  // start our merkle node path at the leaf node<br>  let merklePathNodeValue = value;<br>  let merklePathNodeIndex = index;<br><br>  // we follow the leaf&#39;s merkle path up to the root, <br>  // computing the merkle path&#39;s nodes using the siblings provided as we go alone<br>  for(let i=0;i&lt;siblings.length;i++){<br>    const merklePathNodeSibling = siblings[i];<br><br>    if(merklePathNodeIndex%2===0){<br>      // if the current index of the node on our merkle path is even:<br>      // * merklePathNodeValue is the left hand node,<br>      // * merklePathNodeSibling is the right hand node<br>      // * parent node&#39;s value is hash(merklePathNodeValue, merklePathNodeSibling)<br>      merklePathNodeValue = hash(merklePathNodeValue, merklePathNodeSibling);<br>    }else{<br>      // if the current index of the node on our merkle path is odd:<br>      // * merklePathNodeSibling is the left hand node<br>      // * merklePathNodeValue is the right hand node,<br>      // * parent node&#39;s value is hash(merklePathNodeSibling, merklePathNodeValue)<br>      merklePathNodeValue = hash(merklePathNodeSibling, merklePathNodeValue);<br>    }<br><br>    // using our definition, the parent node of our path node is N(level-1, floor(index/2))<br>    merklePathNodeIndex = Math.floor(merklePathNodeIndex/2);<br>  }<br>  return merklePathNodeValue;<br>}</pre><p>Finally, let’s write a function which verifies the merkle proof:</p><pre>function verifyMerkleProof(proof){<br>  return proof.root === computeMerkleRootFromProof(proof.siblings, proof.index, proof.value);<br>}</pre><p>Putting it all together, we now have a class which can generate a merkle proof of inclusion that can be verified in O(log(n)) time 🎉</p><pre>const shajs = require(&quot;sha.js&quot;); // npm install --save sha.js<br><br>function hash(leftNode, rightNode) {<br>  return shajs(&quot;sha256&quot;)<br>    .update(leftNode + rightNode, &quot;hex&quot;)<br>    .digest(&quot;hex&quot;);<br>}<br>function getSiblingNode(level, index){<br>  // if node is the root, it has no sibling<br>  if(level === 0){<br>    throw new Error(&quot;the root does not have a sibling&quot;)<br>  }else if(index % 2 === 0){<br>    // if node is even, its sibling is at index+1<br>    return {level: level, index: index+1};<br>  }else{<br>    // if node is odd, its sibling is at index-1<br>    return {level: level, index: index-1};<br>  }<br>}<br>function getMerklePathOfNode(level, index){<br>  const merklePath = [];<br>  for(let currentLevel=level;currentLevel&gt;0;currentLevel--){<br>    merklePath.push({<br>      level: currentLevel,<br>      index: index,<br>    });<br>    index = Math.floor(index/2);<br>  }<br>  return merklePath;<br>}<br>class MerkleTree {<br>  constructor(height, leaves){<br>    this.height = height;<br>    this.leaves = leaves;<br>  }<br>  N(level, index){<br>    if(level === this.height){<br>      // if level == height, the node is a leaf, <br>      // so just return the value from our dataset<br>      return this.leaves[index];<br>    }else{<br>      // if the node is not a leaf, use our definition:<br>      // N(level, index) = Hash(N(level+1, index*2), N(level+1, index*2+1))<br>      return hash(<br>        this.N(level+1, 2*index),<br>        this.N(level+1, 2*index+1),<br>      );<br>    }<br>  }<br>  getRoot(){<br>    // the node N(0,0) is always the root node<br>    return this.N(0,0);<br>  }<br>  getMerkleProof(level, index){<br><br>    // get the value of the leaf node<br>    const leafValue = this.N(level, index);<br><br>    // get the levels and indexes of the nodes on the leaf&#39;s merkle path<br>    const merklePath = getMerklePathOfNode(level, index);<br><br>    // get the levels and indexes of the siblings of the nodes on the merkle path<br>    const merklePathSiblings = merklePath.map((node) =&gt; {<br>      return getSiblingNode(node.level, node.index);<br>    });<br><br>    // get the values of the sibling nodes<br>    const siblingValues = merklePathSiblings.map((node) =&gt; {<br>      return this.N(node.level, node.index);<br>    });<br><br>    return {<br>      root: this.getRoot(), // the root we claim to be our tree&#39;s root<br>      siblings: siblingValues, // the siblings of our leaf&#39;s merkle path<br>      index: index, // the index of our leaf<br>      value: leafValue, // the value of our leaf<br>    };<br>  }<br>}<br>function computeMerkleRootFromProof(siblings, index, value){<br>  // start our merkle node path at the leaf node<br>  let merklePathNodeValue = value;<br>  let merklePathNodeIndex = index;<br><br>  // we follow the leaf&#39;s merkle path up to the root, <br>  // computing the merkle path&#39;s nodes using the siblings provided as we go alone<br>  for(let i=0;i&lt;siblings.length;i++){<br>    const merklePathNodeSibling = siblings[i];<br><br>    if(merklePathNodeIndex%2===0){<br>      // if the current index of the node on our merkle path is even:<br>      // * merklePathNodeValue is the left hand node,<br>      // * merklePathNodeSibling is the right hand node<br>      // * parent node&#39;s value is hash(merklePathNodeValue, merklePathNodeSibling)<br>      merklePathNodeValue = hash(merklePathNodeValue, merklePathNodeSibling);<br>    }else{<br>      // if the current index of the node on our merkle path is odd:<br>      // * merklePathNodeSibling is the left hand node<br>      // * merklePathNodeValue is the right hand node,<br>      // * parent node&#39;s value is hash(merklePathNodeSibling, merklePathNodeValue)<br>      merklePathNodeValue = hash(merklePathNodeSibling, merklePathNodeValue);<br>    }<br><br>    // using our definition, the parent node of our path node is N(level-1, floor(index/2))<br>    merklePathNodeIndex = Math.floor(merklePathNodeIndex/2);<br>  }<br>  return merklePathNodeValue;<br>}<br>function verifyMerkleProof(proof){<br>  return proof.root === computeMerkleRootFromProof(proof.siblings, proof.index, proof.value);<br>}<br>function example4(){<br>  const height = 3;<br>  const leaves = [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;, // 1<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;, // 7<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;, // 4<br>    &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;, // 9<br>    &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;, // 0<br>    &quot;0000000000000000000000000000000000000000000000000000000000000006&quot;, // 6<br>  ];<br>  const tree = new MerkleTree(height, leaves);<br>  console.log(&quot;[EX_4] the root is: &quot;+tree.getRoot());<br>  const merkleProofOfN3_5 = tree.getMerkleProof(3,5);<br><br>  console.log(&quot;[EX_4] the merkle proof of N(3,5):\n&quot; + JSON.stringify(merkleProofOfN3_5, null, 2));<br><br>  console.log(&quot;[EX_4] computed root: &quot;+computeMerkleRootFromProof(merkleProofOfN3_5.siblings, merkleProofOfN3_5.index, merkleProofOfN3_5.value));<br>  console.log(&quot;[EX_4] verify merkle proof: &quot;+verifyMerkleProof(merkleProofOfN3_5));<br>}<br><br>example4();</pre><p>This prints:</p><pre>[EX_4] the root is: e74ae221cf067dc8e88195f5b30bfda60dc224a8cd5554bc6345c87ad8c6f925<br>[EX_4] the merkle proof of N(3,5):<br>{<br>  &quot;root&quot;: &quot;e74ae221cf067dc8e88195f5b30bfda60dc224a8cd5554bc6345c87ad8c6f925&quot;,<br>  &quot;siblings&quot;: [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;,<br>    &quot;deb2a27a00dbc46e3a59096a8d07d2b97f950158886411ff6a9c9ab9623dada6&quot;,<br>    &quot;2f4d3e941b602c50347af3f5c809a28737c27c7ce460e77b10739875ef957aa7&quot;<br>  ],<br>  &quot;index&quot;: 5,<br>  &quot;value&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;<br>}<br>[EX_4] computed root: e74ae221cf067dc8e88195f5b30bfda60dc224a8cd5554bc6345c87ad8c6f925<br>[EX_4] verify merkle proof: true</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FMWPRBBm%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DMWPRBBm&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FMWPRBBm&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FMWPRBBm-512.jpg%3Fversion%3D1685262589&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/ea6b7699f372747758de4d9180cc3ad8/href">https://medium.com/media/ea6b7699f372747758de4d9180cc3ad8/href</a></iframe><h3>Delta Merkle Proofs</h3><p>If we have merkle tree with root A, and we generate a merkle proof that has siblings</p><p>In addition to proving that a certain leaf exists in a merkle tree, we will often want to prove the result of modifying a specific leaf in a tree from one value to another. This is called a <strong>delta merkle proof</strong> (delta means change).</p><p>In particular, delta merkle proofs prove:</p><ul><li><strong>A leaf exists at index I with value X in a tree with merkle root A</strong></li><li><strong>If you change the value of the leaf with index I from X to Y without modifying any other leaves in the tree, the new merkle root of the tree is B</strong></li></ul><p>We already know how to prove “A leaf exists at index I with value X in a tree with merkle root A”; we can just call the <strong>getMerkleProof</strong> function on our contract.</p><p>If we then modify the leaf, let’s say from 9 to 8, let’s call <strong>getMerkleProof</strong> again and see what happens:</p><pre>function example5(){<br>  const height = 3;<br>  const leaves = [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;, // 1<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;, // 7<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;, // 4<br>    &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;, // 9<br>    &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;, // 0<br>    &quot;0000000000000000000000000000000000000000000000000000000000000006&quot;, // 6<br>  ];<br>  const tree = new MerkleTree(height, leaves);<br>  const beforeMerkleProofOfN3_5 = tree.getMerkleProof(3,5);<br>  console.log(&quot;[EX_5] the merkle proof of N(3,5) before change:\n&quot; + JSON.stringify(beforeMerkleProofOfN3_5, null, 2));<br>  // change 9 to 8<br>  tree.leaves[5] = &quot;0000000000000000000000000000000000000000000000000000000000000008&quot;;<br><br>  const afterMerkleProofOfN3_5 = tree.getMerkleProof(3,5);<br>  console.log(&quot;[EX_5] the merkle proof of N(3,5) after change:\n&quot; + JSON.stringify(afterMerkleProofOfN3_5, null, 2));<br>}<br>example5();</pre><p>This prints:</p><pre>[EX_5] the merkle proof of N(3,5) before change:<br>{<br>  &quot;root&quot;: &quot;e74ae221cf067dc8e88195f5b30bfda60dc224a8cd5554bc6345c87ad8c6f925&quot;,<br>  &quot;siblings&quot;: [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;,<br>    &quot;deb2a27a00dbc46e3a59096a8d07d2b97f950158886411ff6a9c9ab9623dada6&quot;,<br>    &quot;2f4d3e941b602c50347af3f5c809a28737c27c7ce460e77b10739875ef957aa7&quot;<br>  ],<br>  &quot;index&quot;: 5,<br>  &quot;value&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;<br>}<br>[EX_5] the merkle proof of N(3,5) after change:<br>{<br>  &quot;root&quot;: &quot;99ba6856d11cd514be35ed291e86ae7957ec43d6b83270f32aaa50a8a25dc5cc&quot;,<br>  &quot;siblings&quot;: [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;,<br>    &quot;deb2a27a00dbc46e3a59096a8d07d2b97f950158886411ff6a9c9ab9623dada6&quot;,<br>    &quot;2f4d3e941b602c50347af3f5c809a28737c27c7ce460e77b10739875ef957aa7&quot;<br>  ],<br>  &quot;index&quot;: 5,<br>  &quot;value&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000008&quot;<br>}</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FQWZPBVE%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DQWZPBVE&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FQWZPBVE&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FQWZPBVE-512.jpg%3Fversion%3D1685262823&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/d942c1312d844fc8c54407f99192295d/href">https://medium.com/media/d942c1312d844fc8c54407f99192295d/href</a></iframe><p>Aha!</p><p>Notice that the <strong>siblings are the same</strong> in the before and after proof’s!</p><p>Remember that when we update a leaf, it only changes the nodes on the leaf’s merkle path, and <strong>since by definition the leaf’s merkle path does not include any siblings, the before and after proofs have the same siblings</strong>!</p><p>If we look deeper, the fact that both proofs provide the same siblings and index, verifying both proofs prove that we have only modified the leaf with index I:</p><figure><img alt="Diagram showing a merkle path" src="https://cdn-images-1.medium.com/max/1024/1*objSgfyn1H9jaQVRoujqNg@2x.png" /><figcaption>Only the orange nodes on the merkle path change, so the siblings (white) stay the same</figcaption></figure><p>Therefore, our delta merkle proof needs to provide:</p><ol><li>The leaf’s index (the same for both before and after proofs)</li><li>The leaf’s siblings (the same for both before and after proofs)</li><li>The leaf’s old value (before the change)</li><li>The tree’s old root (the root of the tree before the change)</li><li>The leaf’s new value (after we change the value of the leaf)</li><li>The tree’s new root (after we change the value of the leaf)</li></ol><p>We can implement a getDeltaMerkleProof function to do this for us:</p><pre>class MerkleTree {<br>  // ...<br>  getDeltaMerkleProof(level, index, newValue){<br>    // compute the root of the leaf at index I<br>    const oldLeafProof = this.getMerkleProof(level, index);<br>    // compute the merkle root from the proof, replacing the leaf&#39;s value with the new value<br>    const newRoot = computeMerkleRootFromProof(oldLeafProof.siblings, index, newValue);<br><br>    // return the data from the old proof and the new proof<br>    return {<br>      index: index,<br>      siblings: oldLeafProof.siblings,<br><br>      oldRoot: oldLeafProof.root,<br>      oldValue: oldLeafProof.value,<br>      <br>      newRoot: newRoot,<br>      newValue: newValue,<br>    }<br>  }<br>}</pre><p>To verify the delta merkle proof, we can split the data of the delta merkle proofs into a newProof and oldProof which share the same siblings/index, and verify the proofs using our existing verifyMerkleProof function:</p><pre>function verifyDeltaMerkleProof(deltaMerkleProof){<br>  // split the delta merkle proof into a before and after merkle proof, reusing the same siblings and index<br>  const oldProof = {<br>    // reuse the same siblings for both old and new<br>    siblings: deltaMerkleProof.siblings, <br>    // reuse the same index for both old and new<br>    index: deltaMerkleProof.index,<br><br>    root: deltaMerkleProof.oldRoot,<br>    value: deltaMerkleProof.oldValue,<br>  };<br><br>  const newProof = {<br>    // reuse the same siblings for both old and new<br>    siblings: deltaMerkleProof.siblings, <br>    // reuse the same index for both old and new<br>    index: deltaMerkleProof.index,<br><br>    root: deltaMerkleProof.newRoot,<br>    value: deltaMerkleProof.newValue,<br>  };<br>  return verifyMerkleProof(oldProof) &amp;&amp; verifyMerkleProof(newProof);<br>}</pre><p>Let’s combine this together and see if it works:</p><pre>const shajs = require(&quot;sha.js&quot;); // npm install --save sha.js<br><br>function hash(leftNode, rightNode) {<br>  return shajs(&quot;sha256&quot;)<br>    .update(leftNode + rightNode, &quot;hex&quot;)<br>    .digest(&quot;hex&quot;);<br>}<br>function getSiblingNode(level, index){<br>  // if node is the root, it has no sibling<br>  if(level === 0){<br>    throw new Error(&quot;the root does not have a sibling&quot;)<br>  }else if(index % 2 === 0){<br>    // if node is even, its sibling is at index+1<br>    return {level: level, index: index+1};<br>  }else{<br>    // if node is odd, its sibling is at index-1<br>    return {level: level, index: index-1};<br>  }<br>}<br>function getMerklePathOfNode(level, index){<br>  const merklePath = [];<br>  for(let currentLevel=level;currentLevel&gt;0;currentLevel--){<br>    merklePath.push({<br>      level: currentLevel,<br>      index: index,<br>    });<br>    index = Math.floor(index/2);<br>  }<br>  return merklePath;<br>}<br>class MerkleTree {<br>  constructor(height, leaves){<br>    this.height = height;<br>    this.leaves = leaves;<br>  }<br>  N(level, index){<br>    if(level === this.height){<br>      // if level == height, the node is a leaf, <br>      // so just return the value from our dataset<br>      return this.leaves[index];<br>    }else{<br>      // if the node is not a leaf, use our definition:<br>      // N(level, index) = Hash(N(level+1, index*2), N(level+1, index*2+1))<br>      return hash(<br>        this.N(level+1, 2*index),<br>        this.N(level+1, 2*index+1),<br>      );<br>    }<br>  }<br>  getRoot(){<br>    // the node N(0,0) is always the root node<br>    return this.N(0,0);<br>  }<br>  getMerkleProof(level, index){<br><br>    // get the value of the leaf node<br>    const leafValue = this.N(level, index);<br><br>    // get the levels and indexes of the nodes on the leaf&#39;s merkle path<br>    const merklePath = getMerklePathOfNode(level, index);<br><br>    // get the levels and indexes of the siblings of the nodes on the merkle path<br>    const merklePathSiblings = merklePath.map((node) =&gt; {<br>      return getSiblingNode(node.level, node.index);<br>    });<br><br>    // get the values of the sibling nodes<br>    const siblingValues = merklePathSiblings.map((node) =&gt; {<br>      return this.N(node.level, node.index);<br>    });<br><br>    return {<br>      root: this.getRoot(), // the root we claim to be our tree&#39;s root<br>      siblings: siblingValues, // the siblings of our leaf&#39;s merkle path<br>      index: index, // the index of our leaf<br>      value: leafValue, // the value of our leaf<br>    };<br>  }<br>  getDeltaMerkleProof(level, index, newValue){<br>    // compute the root of the leaf at index I<br>    const oldLeafProof = this.getMerkleProof(level, index);<br>    // compute the merkle root from the proof, replacing the leaf&#39;s value with the new value<br>    const newRoot = computeMerkleRootFromProof(oldLeafProof.siblings, index, newValue);<br><br>    // return the data from the old proof and the new proof<br>    return {<br>      index: index,<br>      siblings: oldLeafProof.siblings,<br><br>      oldRoot: oldLeafProof.root,<br>      oldValue: oldLeafProof.value,<br>      <br>      newRoot: newRoot,<br>      newValue: newValue,<br>    }<br>  }<br>}<br>function computeMerkleRootFromProof(siblings, index, value){<br>  // start our merkle node path at the leaf node<br>  let merklePathNodeValue = value;<br>  let merklePathNodeIndex = index;<br><br>  // we follow the leaf&#39;s merkle path up to the root, <br>  // computing the merkle path&#39;s nodes using the siblings provided as we go alone<br>  for(let i=0;i&lt;siblings.length;i++){<br>    const merklePathNodeSibling = siblings[i];<br><br>    if(merklePathNodeIndex%2===0){<br>      // if the current index of the node on our merkle path is even:<br>      // * merklePathNodeValue is the left hand node,<br>      // * merklePathNodeSibling is the right hand node<br>      // * parent node&#39;s value is hash(merklePathNodeValue, merklePathNodeSibling)<br>      merklePathNodeValue = hash(merklePathNodeValue, merklePathNodeSibling);<br>    }else{<br>      // if the current index of the node on our merkle path is odd:<br>      // * merklePathNodeSibling is the left hand node<br>      // * merklePathNodeValue is the right hand node,<br>      // * parent node&#39;s value is hash(merklePathNodeSibling, merklePathNodeValue)<br>      merklePathNodeValue = hash(merklePathNodeSibling, merklePathNodeValue);<br>    }<br><br>    // using our definition, the parent node of our path node is N(level-1, floor(index/2))<br>    merklePathNodeIndex = Math.floor(merklePathNodeIndex/2);<br>  }<br>  return merklePathNodeValue;<br>}<br>function verifyMerkleProof(proof){<br>  return proof.root === computeMerkleRootFromProof(proof.siblings, proof.index, proof.value);<br>}<br>function verifyDeltaMerkleProof(deltaMerkleProof){<br>  // split the delta merkle proof into a before and after merkle proof, reusing the same siblings and index<br>  const oldProof = {<br>    // reuse the same siblings for both old and new<br>    siblings: deltaMerkleProof.siblings, <br>    // reuse the same index for both old and new<br>    index: deltaMerkleProof.index,<br><br>    root: deltaMerkleProof.oldRoot,<br>    value: deltaMerkleProof.oldValue,<br>  };<br><br>  const newProof = {<br>    // reuse the same siblings for both old and new<br>    siblings: deltaMerkleProof.siblings, <br>    // reuse the same index for both old and new<br>    index: deltaMerkleProof.index,<br><br>    root: deltaMerkleProof.newRoot,<br>    value: deltaMerkleProof.newValue,<br>  };<br>  return verifyMerkleProof(oldProof) &amp;&amp; verifyMerkleProof(newProof);<br>}<br>function example6(){<br>  const height = 3;<br>  const leaves = [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000001&quot;, // 1<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000003&quot;, // 3<br>    &quot;0000000000000000000000000000000000000000000000000000000000000007&quot;, // 7<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;, // 4<br>    &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;, // 9<br>    &quot;0000000000000000000000000000000000000000000000000000000000000000&quot;, // 0<br>    &quot;0000000000000000000000000000000000000000000000000000000000000006&quot;, // 6<br>  ];<br>  const tree = new MerkleTree(height, leaves);<br><br>  const deltaMerkleProofOfN3_5 = tree.getDeltaMerkleProof(3,5, &quot;0000000000000000000000000000000000000000000000000000000000000008&quot;);<br>  console.log(&quot;[EX_6] delta merkle proof of changing N(3,5) from 9 to 8:\n&quot; + JSON.stringify(deltaMerkleProofOfN3_5, null, 2));<br>  console.log(&quot;[EX_6] verify delta merkle proof: &quot;+verifyDeltaMerkleProof(deltaMerkleProofOfN3_5));<br>}<br><br>example6();</pre><p>Running this code prints:</p><pre>[EX_6] delta merkle proof of changing N(3,5) from 9 to 8:<br>{<br>  &quot;index&quot;: 5,<br>  &quot;siblings&quot;: [<br>    &quot;0000000000000000000000000000000000000000000000000000000000000004&quot;,<br>    &quot;deb2a27a00dbc46e3a59096a8d07d2b97f950158886411ff6a9c9ab9623dada6&quot;,<br>    &quot;2f4d3e941b602c50347af3f5c809a28737c27c7ce460e77b10739875ef957aa7&quot;<br>  ],<br>  &quot;oldRoot&quot;: &quot;e74ae221cf067dc8e88195f5b30bfda60dc224a8cd5554bc6345c87ad8c6f925&quot;,<br>  &quot;oldValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000009&quot;,<br>  &quot;newRoot&quot;: &quot;99ba6856d11cd514be35ed291e86ae7957ec43d6b83270f32aaa50a8a25dc5cc&quot;,<br>  &quot;newValue&quot;: &quot;0000000000000000000000000000000000000000000000000000000000000008&quot;<br>}<br>[EX_6] verify delta merkle proof: true</pre><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fembed%2Fpreview%2FpoxBZOR%3Fdefault-tabs%3Djs%252Cresult%26height%3D600%26host%3Dhttps%253A%252F%252Fcodepen.io%26slug-hash%3DpoxBZOR&amp;display_name=CodePen&amp;url=https%3A%2F%2Fcodepen.io%2Fqedprotocol%2Fpen%2FpoxBZOR&amp;image=https%3A%2F%2Fshots.codepen.io%2Fusername%2Fpen%2FpoxBZOR-512.jpg%3Fversion%3D1685262895&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codepen" width="800" height="600" frameborder="0" scrolling="no"><a href="https://medium.com/media/0fba69f89b9c686954e509cf1b8df741/href">https://medium.com/media/0fba69f89b9c686954e509cf1b8df741/href</a></iframe><p>Success! We have now invented the core concepts behind merkle trees, merkle proofs and delta merkle proofs.</p><p>More importantly, with this intuition we can use our full understanding of how merkle proofs work to implement variants of these proofs to create useful and efficient data stores for our layer 2 protocol.</p><p>In the next tutorial, we will build an efficient merkle tree database for large sparse merkle trees, write an append only merkle tree that only uses O(log(N)) storage, and investigate some merkle proof variants that allow us to merge trees and prove historical state.</p><figure><img alt="OAS and the QED Protocol" src="https://cdn-images-1.medium.com/max/1024/1*Ye4GSHapN4KfetWxMxJ8iA.png" /><figcaption>Thanks to our sponsors OAS &amp; <a href="https://qedprotocol.com">QED</a>, you can follow OAS on twitter <a href="https://twitter.com/OASNetwork">@OASNetwork</a></figcaption></figure><p><em>about the author</em> — follow <a href="https://twitter.com/cmpeq">@cmpeq</a> on twitter or check out my <a href="https://medium.com/@carterfeldman/about">about page</a>.</p><p>Vist QED at <a href="https://qedprotocol.com">https://qedprotocol.com</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f682fc31bced" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>