<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://bensepanski.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://bensepanski.github.io/" rel="alternate" type="text/html" /><updated>2025-06-25T02:53:44+00:00</updated><id>https://bensepanski.github.io/feed.xml</id><title type="html">Benjamin Sepanski</title><subtitle>SSE at Veridise</subtitle><author><name>Benjamin Sepanski</name><email>ben.sepanski@veridise.com</email></author><entry><title type="html">Mastering o1js on Mina: Four key strategies for secure development</title><link href="https://bensepanski.github.io/posts/2025/02/19/Mastering-o1js-on-Mina-Four-key-strategies-for-secure-development/" rel="alternate" type="text/html" title="Mastering o1js on Mina: Four key strategies for secure development" /><published>2025-02-19T00:00:00+00:00</published><updated>2025-02-19T00:00:00+00:00</updated><id>https://bensepanski.github.io/posts/2025/02/19/Mastering-o1js-on-Mina-Four-key-strategies-for-secure-development</id><content type="html" xml:base="https://bensepanski.github.io/posts/2025/02/19/Mastering-o1js-on-Mina-Four-key-strategies-for-secure-development/"><![CDATA[<p>For the original (and properly formatted) publication, see <a href="https://medium.com/veridise/mastering-o1js-on-mina-four-key-strategies-for-secure-development-fff3a3f4f6d1">Veridise’s post on medium</a>.</p>

<hr />

<p>If you’re building on the Mina blockchain, this blog post is a must-read.</p>

<p>After completing an extensive <a href="https://www.o1labs.org/blog/the-o1js-audit">security audit of the o1js v1 library</a> — spanning 39 person-weeks with 4 security analysts — we’ve gained a comprehensive understanding of the framework. We’re eager to share our insights with the community.</p>

<p>To help you develop securely with the o1js TypeScript library, we’ve distilled our findings into four topics. In this post, we highlight common pitfalls, share real-world examples of vulnerabilities, and provide actionable guidance to ensure your projects harness o1js effectively and securely.</p>

<h1 id="writing-zk-circuits-with-o1js-and-typescript">Writing ZK Circuits with o1js and TypeScript</h1>

<p>o1js brings cutting-edge technology to developers, enabling them to write ZK circuits in TypeScript and seamlessly deploy them to the Mina blockchain. While o1js abstracts much of the complexity of zero-knowledge proofs, it also introduces unique challenges and anti-patterns that developers must carefully navigate.</p>

<p>In this post, we dive into four illustrative examples:</p>

<ul>
  <li>Example #1: Trust the Types</li>
  <li>Example #2: It’s still ZK under the hood: No conditional data flow!</li>
  <li>Example #3. Account Updates: Mastering on-chain state management</li>
  <li>Example #4: Don’t get DoS’ed: Actions &amp; Reducers</li>
</ul>

<p>Let’s dive in!</p>

<h1 id="example-1-trust-the-types">Example #1: Trust the Types</h1>

<p>A <code class="language-plaintext highlighter-rouge">UInt64</code> type is a supposed to represent a value in the range [0, 2⁶⁴), i.e. 0, 1, 2, 3, …, up to 18,446,744,073,709,551,615</p>

<p><code class="language-plaintext highlighter-rouge">UInt64.assertLessThan()</code> allows you to assert that a certain value <code class="language-plaintext highlighter-rouge">x</code> is less than another value</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Provable.runAndCheck(() =&gt; {\
  // Introduce a UInt64 variable in the program\
  let x = Provable.witness(UInt64, () =&gt; {return new UInt64(100n);});\
  // Prove the variable is at most 2**48\
  x.assertLessThan(new UInt64(2n**48n));\
})
</code></pre></div></div>

<p>In the above program, all we prove is that <code class="language-plaintext highlighter-rouge">x</code> is some number less than 2⁴⁸. We set <code class="language-plaintext highlighter-rouge">x</code> to <code class="language-plaintext highlighter-rouge">100</code> in our generator, but anyone can change the witness generator. More specifically, <em>anything inside the </em><code class="language-plaintext highlighter-rouge">*Provable.witness*</code><em> function is not proven! </em><code class="language-plaintext highlighter-rouge">*x*</code><em> can have any value, so long as it satisfies the constraints!</em></p>

<p>But where are constraints defined?</p>

<p><code class="language-plaintext highlighter-rouge">Provable.witness(UInt64, ...)</code> adds constraints defined in <code class="language-plaintext highlighter-rouge">UInt64.check()</code> to ensure that verification won’t succeed unless <code class="language-plaintext highlighter-rouge">x</code> is in the range [0, 2⁶⁴)</p>

<p><code class="language-plaintext highlighter-rouge">x.assertLessThan(new UInt64(2n**48n))</code> then asserts that <code class="language-plaintext highlighter-rouge">x</code> is in the range [0, 2⁴⁸)</p>

<p>So far so good….</p>

<p>Now a clever user looks at this and might think: “Why check <code class="language-plaintext highlighter-rouge">x</code> is in [0, 2⁶⁴) if we are going to check <code class="language-plaintext highlighter-rouge">x</code> is in [0, 2⁴⁸) anyway? We can just do the second check!”</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// BUG IN CODE: DO NOT USE\
Provable.runAndCheck(() =&gt; {\
  // Introduce an unconstrained variable in the program\
  let xAsField = Provable.exists(1, () =&gt; {return new UInt64(100n);});\
  // Unsafe cast it to a UInt64. This adds no constraints\
  let x = UInt64.Unsafe.from(xAsField);\
  // Prove the variable is at most 2**48\
  x.assertLessThan(new UInt64(2n**48n));\
})
</code></pre></div></div>

<p>While this looks innocuous, it is actually under-constrained! We can use values for <code class="language-plaintext highlighter-rouge">x</code> which are much larger than the input field.</p>

<p>Why?</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">UInt64.assertLessThan()</code> assumes that <em>we already know </em><code class="language-plaintext highlighter-rouge">*x &lt; 2**64*</code><em>.</em> Under the hood, it then asserts that 2⁴⁸ - x is in the range [0, 2⁶⁴). Remember that x is <em>really</em> a field element, so all arithmetic occurs modulo <code class="language-plaintext highlighter-rouge">p</code> for <a href="https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/">some large </a><code class="language-plaintext highlighter-rouge">[p](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/)</code>, and we can think of the range [0, p) as all possible values of <code class="language-plaintext highlighter-rouge">x</code>!</li>
</ul>

<p>The implementation has the following cases:</p>

<ol>
  <li>If x ∈ [0, 2⁴⁸), then 0 ≤ 2⁴⁸ — x &lt; 2⁴⁸ &lt; 2⁶⁴, so the check passes ✅</li>
  <li>If x ∈ [2⁴⁸, p + 2⁴⁸ — 2⁶⁴), then 2⁶⁴ ≤ 2⁴⁸ — x, so the check fails p + 2⁴⁸ — 2⁶⁴ ≈ p ≈ 2²⁵⁴ (note p + 2⁴⁸ — 2⁶⁴ ≈ p ≈ 2²⁵⁴ ) ✅ (just like we want)</li>
  <li>If x ∈ [p + 2⁴⁸ — 2⁶⁴, p) (i.e. the 2⁶⁴ — 2⁴⁸ — 1 <em>largest elements in the field, much larger than 2⁴⁸), </em>then the computation overflows all the way back into the range 0 ≤ 2⁴⁸ — x &lt; 2⁴⁸! ❌ This is bad: the check will pass but x is much larger than 2⁴⁸</li>
</ol>

<p>This is why we can’t cheat: we <em>need</em> the constraint x ∈ 2⁶⁴ from <code class="language-plaintext highlighter-rouge">Provable.witness(UInt64, ...)</code> to eliminate this 3rd “bad case”.</p>

<h1 id="example-2-its-still-zk-under-the-hood-no-conditional-data-flow">Example #2: It’s still ZK under the hood: No conditional data flow!</h1>

<p>One common mistake we’ve seen across ecosystems comes from a limitation of ZK itself: the circuit’s control flow is entirely static.</p>

<p>No if/else</p>

<p>How can we do computations then? Conditional data flow.</p>

<p>Instead of</p>

<p>if case1 {<br />
  x = f(y)<br />
}<br />
else {<br />
  x = g(y)<br />
}</p>

<p>We have to compute <code class="language-plaintext highlighter-rouge">f(y)</code>, compute <code class="language-plaintext highlighter-rouge">g(y)</code>, and then set</p>

<p>x = case1 ? f(y) : g(y)</p>

<p>Of course, in <code class="language-plaintext highlighter-rouge">o1js</code> this would look like</p>

<p>x = Provable.if(<br />
  case1,<br />
  MyProvableType, // tell o1js the type to use<br />
  x,<br />
  y<br />
)</p>

<p>How can this cause issues? We often want to do computations which <em>might</em>succeed. A classical example is division: division-by-zero is undefined.</p>

<p>Suppose we are implementing a simple vault. The vault holds funds. You can deposit funds equaling 1% of the currently managed funds, and the vault will mint you a number of tokens equal to 1% of the currently existing tokens.</p>

<p>For example, imagine the supply of vault <code class="language-plaintext highlighter-rouge">Token</code>s is <code class="language-plaintext highlighter-rouge">1000 Token</code>, and the vault is holding <code class="language-plaintext highlighter-rouge">100 USDC</code>. If I deposit <code class="language-plaintext highlighter-rouge">1 USDC</code>, the vault will mint me <code class="language-plaintext highlighter-rouge">1 USDC * 1000 Token / 100 USDC = 10 Token</code>. If I deposit <code class="language-plaintext highlighter-rouge">10 USDC</code>, the vault will mint <code class="language-plaintext highlighter-rouge">10 USDC * 1000 Token / 100 USDC = 100 Token</code>.</p>

<p>Ignoring decimals, this might be written simply as</p>

<p>amountToMint = totalSupply.mul(depositedAmount).div(managedFunds)</p>

<p>But what about the starting case? Suppose there are <em>no</em> funds and we are the first depositor. Commonly, we will set some fixed ratio: e.g. mint 1 token per initial USDC deposited. A first attempt at implementing this might be the below:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// BUG IN CODE: DO NOT USE\
amountToMint = Provable.if(\
  managedFunds.equals(new UInt64(0n)),\
  UInt64,                               // Output type\
  depositedAmount,\
  totalSupply.mul(depositedAmount).div(managedFunds)\
)
</code></pre></div></div>

<p>This looks like it will work: if no funds are currently managed, <code class="language-plaintext highlighter-rouge">amountToMint</code>is <code class="language-plaintext highlighter-rouge">depositedAmount</code>. Otherwise, we compute the ratio of tokens to managed funds.</p>

<p>The problem is simple: we <em>always compute</em><code class="language-plaintext highlighter-rouge">totalSupply.mul(depositedAmount).div(managedFunds)</code>, even when <code class="language-plaintext highlighter-rouge">managedFunds</code> is equal to zero. To guarantee its correctness, <code class="language-plaintext highlighter-rouge">UInt64.div()</code> will cause an assertion failure when the denominator is zero.</p>

<p>This may seem not so bad: we’ll catch it during testing. The problem is, it isn’t always so obvious. For example, what if the above contract starts with a non-zero amount of funds/total supply set up before the vault was opened to the public? Then this issue will only manifest if the <code class="language-plaintext highlighter-rouge">managedFunds</code> reaches 0, at which point it can never be deposited into again.</p>

<p>A more serious (but analogous) example could prevent the last user from withdrawing from the vault.</p>

<h1 id="example-3-account-updates-mastering-on-chain-state-management">Example #3: Account Updates: Mastering on-chain state management</h1>

<p>While o1js looks a lot like other popular smart contract languages, there are some important differences.</p>

<p>Each <code class="language-plaintext highlighter-rouge">@method</code> creates an <code class="language-plaintext highlighter-rouge">AccountUpdate</code>: A single object representing a change of on-chain state. These <code class="language-plaintext highlighter-rouge">AccountUpdate</code>s have preconditions on the state to ensure valid access. For example, if I am decreasing a smart contract Mina balance by <code class="language-plaintext highlighter-rouge">10</code>, the node must validate the account has at least 10 Mina—even if I have a proof that I executed the smart contract correctly.</p>

<p>Why? Remember that ZK is “state-less:” it has no hard drive, disk, or memory. All you prove is that, for some inputs which only you know, you did the correct computation.</p>

<p>When dealing with on-chain values, we need to prove that we used the actual on-chain values, and not just some random numbers! How? We output the “private-input” as “public preconditions” of the <code class="language-plaintext highlighter-rouge">AccountUpdate</code>. The node can then check the public preconditions</p>

<p>With state, we do this via the <code class="language-plaintext highlighter-rouge">getAndRequireEquals()</code> function. o1js will cause an error if you call <code class="language-plaintext highlighter-rouge">get()</code> without <code class="language-plaintext highlighter-rouge">getAndRequireEquals()</code>. It will now also call an error if you <code class="language-plaintext highlighter-rouge">getAndRequireEquals()</code> with two different values, due to an issue reported by Veridise (see <a href="https://github.com/o1-labs/o1js/pull/1712">#1712 Assert Preconditions are not already set when trying to set their values</a>)</p>

<p>Let’s take a simple example.</p>

<p>// BUG IN CODE: DO NOT USE<br />
export class Pool extends SmartContract {<br />
  @state(Boolean) paused = State<Boolean>();</Boolean></p>

<p>@method function mint(amount: UInt64): UInt64 {<br />
    this.paused.get().assertFalse(“Pool paused!”);<br />
    // Minting logic<br />
  }<br />
}</p>

<p>The above snippet is intended to prevent minting when the protocol is paused. In actuality, it just proves that the prover set <code class="language-plaintext highlighter-rouge">paused</code> to <code class="language-plaintext highlighter-rouge">False</code> in their local environment when generating the proof. To ensure that the network validates this assumption, the code should instead use <code class="language-plaintext highlighter-rouge">getAndRequireEquals()</code>. This way, the assumption <code class="language-plaintext highlighter-rouge">paused = False</code> is included in the <code class="language-plaintext highlighter-rouge">AccountUpdate</code> as part of the proof, forcing the network node to validate that the on-chain protocol is not paused.</p>

<p>export class Pool extends SmartContract {<br />
  @state(Boolean) paused = State<Boolean>();</Boolean></p>

<p>@method function mint(amount: UInt64): UInt64 {<br />
    this.paused.getAndRequireEquals().assertFalse(“Pool paused!”);<br />
    // Minting logic<br />
  }<br />
}</p>

<h1 id="example-4-dont-get-dosed-actions--reducers">Example #4: Don’t get DoS’ed: Actions &amp; Reducers</h1>

<p>Preconditions can cause problems when concurrent accesses are occurring. Say you and I are both incrementing a counter. Our <code class="language-plaintext highlighter-rouge">AccountUpdate</code> will have two important parts:</p>

<ul>
  <li>A precondition that the current counter value is the old value <code class="language-plaintext highlighter-rouge">x</code></li>
  <li>A new value for the counter: <code class="language-plaintext highlighter-rouge">x+1</code></li>
</ul>

<p>If you and I both call this function when <code class="language-plaintext highlighter-rouge">x = 3</code>, we <em>both have a precondition </em><code class="language-plaintext highlighter-rouge">*x=3*</code><em>!</em> That means whichever one of us is executed second will have our <code class="language-plaintext highlighter-rouge">AccountUpdate</code> fail, since after the first person goes, <code class="language-plaintext highlighter-rouge">x = 4 != 3</code></p>

<p>How can we fix this? We queue up actions.</p>

<p>Mina has a feature called <a href="https://www.google.com/search?client=safari&amp;rls=en&amp;q=actions+and+reducers+mina&amp;ie=UTF-8&amp;oe=UTF-8">actions &amp; reducers</a>. You can submit an “Action”, which gets put in a queue. Later, users can call a “reducer” which calls a function on those actions</p>

<p>Let’s look at an example taken from <a href="https://github.com/o1-labs/o1js/blob/main/src/examples/zkapps/reducer/reducer-composite.ts">reducer-composite.ts</a>:</p>

<p>class MaybeIncrement extends Struct({<br />
  isIncrement: Bool,<br />
  otherData: Field,<br />
}) {}<br />
const INCREMENT = { isIncrement: Bool(true), otherData: Field(0) };</p>

<p>class Counter extends SmartContract {<br />
  // the “reducer” field describes a type of action that we can dispatch, and reduce later<br />
  reducer = Reducer({ actionType: MaybeIncrement });</p>

<p>// on-chain version of our state. it will typically lag behind the<br />
  // version that’s implicitly represented by the list of actions<br />
  @state(Field) counter = State<Field>();\
  // helper field to store the point in the action history that our on-chain state is at\
  @state(Field) actionState = State<Field>();</Field></Field></p>

<p>@method async incrementCounter() {<br />
    this.reducer.dispatch(INCREMENT);<br />
  }<br />
  @method async dispatchData(data: Field) {<br />
    this.reducer.dispatch({ isIncrement: Bool(false), otherData: data });<br />
  }</p>

<p>@method async rollupIncrements() {<br />
    // get previous counter &amp; actions hash, assert that they’re the same as on-chain values<br />
    let counter = this.counter.getAndRequireEquals();<br />
    let actionState = this.actionState.getAndRequireEquals();</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// compute the new counter and hash from pending actions\
let pendingActions = this.reducer.getActions({\
  fromActionState: actionState,\
});

let newCounter = this.reducer.reduce(\
  pendingActions,\
  // state type\
  Field,\
  // function that says how to apply an action\
  (state: Field, action: MaybeIncrement) =&gt; {\
    return Provable.if(action.isIncrement, state.add(1), state);\
  },\
  counter,\
  { maxUpdatesWithActions: 10 }\
);

// update on-chain state\
this.counter.set(newCounter);\
this.actionState.set(pendingActions.hash);\   }\ }
</code></pre></div></div>

<p>In this code, <code class="language-plaintext highlighter-rouge">incrementCounter()</code> dispatches an action to the queue requesting an increment. <code class="language-plaintext highlighter-rouge">dispatchData()</code> adds a queue with some other unrelated data.</p>

<p>Anyone can process the entire queue by calling <code class="language-plaintext highlighter-rouge">rollupIncrements()</code>. This will go through the whole queue, incrementing once for each submitted (but unprocessed) request to increment.</p>

<p>Note:</p>

<ul>
  <li>The contract is responsible for managing the <code class="language-plaintext highlighter-rouge">actionState</code> field, which tracks “where we are in the queue.” In particular, <code class="language-plaintext highlighter-rouge">this.actionState</code> tracks what parts of the queue have been <em>processed</em>, while the Mina node automatically tracks what actions have been <em>submitted</em> via <code class="language-plaintext highlighter-rouge">dispatch</code></li>
</ul>

<p>Suppose that, instead of just incrementing by one, the user provided a number to add (e.g. an account balance change).</p>

<p>class MaybeIncrement extends Struct({<br />
  isIncrement: Bool,<br />
  otherData: Field,<br />
  amount: UInt64<br />
}) {}</p>

<p>// function that says how to apply an action<br />
(state: Field, action: MaybeIncrement) =&gt; {<br />
  return Provable.if(action.isIncrement, state.add(action.amount), state);<br />
},</p>

<p>A malicious user could submit several large <code class="language-plaintext highlighter-rouge">amount</code>s, e.g.</p>

<p>{<br />
  isIncrement: new Bool(0),<br />
  otherData: new Field(0),<br />
  amount: new UInt64(UInt64.MAX),<br />
}</p>

<p>Action submission will work smoothly, but this single action can permanently prevent all other actions from being processed!</p>

<p>Using this simplified example, the only way to process actions is in a single, large batch. Using more complex constructions like the batch reducer, or a custom rollup proof can get around this issue, but at the time of audit, only simple examples were made available for us to review.</p>

<p>If you decide to use the actions and reducer pattern, the reducer must be <em>guaranteed to succeed</em> once an action is submitted. This means that any arithmetic must be inspected carefully, actions must be strictly validated at submission, and must be canonicalized (see this <a href="https://github.com/o1-labs/o1js/pull/1759">PR #1759 Canonical representation of provable types</a>, which was developed as a solution to an issue raised during the Veridise audit).</p>

<p>To see what other solutions the O(1) community is working on, <a href="https://medium.com/zknoid/mina-action-reducers-guide-writing-our-own-reducers-81802287776f">check out this article from zkNoid.</a></p>

<h1 id="closing-thoughts">Closing thoughts</h1>

<p>o1js empowers developers to build cutting-edge zero-knowledge applications on the Mina blockchain, leveraging the simplicity of TypeScript to create and deploy ZK circuits.</p>

<p>However, this power comes with responsibilities. Developers must be vigilant about potential vulnerabilities, from respecting type constraints and avoiding under-constrained circuits to managing state updates effectively and mitigating concurrency risks.</p>

<p>We hope the examples and strategies shared in this blog provide a solid foundation for developing securely on Mina. The challenges and pitfalls of working with zero-knowledge circuits can be intricate, but with careful attention to detail and adherence to best practices, they can be navigated successfully.</p>

<h1 id="author">Author</h1>

<p>Ben Sepanski, Chief Security Officer at Veridise</p>

<h1 id="want-to-learn-more-about-veridise">Want to learn more about Veridise?</h1>

<table>
  <tbody>
    <tr>
      <td><a href="https://twitter.com/VeridiseInc">Twitter</a> </td>
      <td> <a href="https://www.linkedin.com/company/veridise/">LinkedIn</a> </td>
      <td> <a href="https://github.com/Veridise">Github</a> </td>
      <td> <a href="https://veridise.com/request-audit/">Request Audit</a></td>
    </tr>
  </tbody>
</table>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Ffff3a3f4f6d1&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fmastering-o1js-on-mina-four-key-strategies-for-secure-development-fff3a3f4f6d1&amp;source=---footer_actions--fff3a3f4f6d1---------------------bookmark_footer------------------"></a></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—post_publication_info–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<h2 id="published-inveridise">Published in Veridise</h2>

<p>](https://medium.com/veridise?source=post_page—post_publication_info–fff3a3f4f6d1—————————————)</p>

<p><a href="https://medium.com/veridise/followers?source=post_page---post_publication_info--fff3a3f4f6d1---------------------------------------">62 Followers</a></p>

<p>-<a href="https://medium.com/veridise/mastering-o1js-on-mina-four-key-strategies-for-secure-development-fff3a3f4f6d1?source=post_page---post_publication_info--fff3a3f4f6d1---------------------------------------">Last published 4 days ago</a></p>

<p>Our mission in to harden blockchain security with formal methods. We write about blockchain security, zero-knowledge proofs, and our bug discoveries.</p>

<p>Follow</p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/@veridise?source=post_page—post_author_info–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<h2 id="written-byveridise">Written by Veridise</h2>

<p>](https://medium.com/@veridise?source=post_page—post_author_info–fff3a3f4f6d1—————————————)</p>

<p><a href="https://medium.com/@veridise/followers?source=post_page---post_author_info--fff3a3f4f6d1---------------------------------------">277 Followers</a></p>

<p>-<a href="https://medium.com/@veridise/following?source=post_page---post_author_info--fff3a3f4f6d1---------------------------------------">3 Following</a></p>

<p>Hardening blockchain security with formal methods. We write about blockchain &amp; zero-knowledge proof security. Contact us for industry-leading security audits.</p>

<p>Follow</p>

<h2 id="no-responses-yet">No responses yet</h2>

<p><a href="https://policy.medium.com/medium-rules-30e5502c4eb4?source=post_page---post_responses--fff3a3f4f6d1---------------------------------------"></a></p>

<p>[</p>

<p>What are your thoughts?</p>

<p>](https://medium.com/m/signin?operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fmastering-o1js-on-mina-four-key-strategies-for-secure-development-fff3a3f4f6d1&amp;source=—post_responses–fff3a3f4f6d1———————respond_sidebar——————)</p>

<p>Cancel</p>

<p>Respond</p>

<p>Respond</p>

<p>Also publish to my profile</p>

<h2 id="more-fromveridiseand-veridise">More from Veridise and Veridise</h2>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*uwizzJfUnbne-5Y-pXBo2A.png" alt="Zero Knowledge for Dummies: Introduction to ZK Proofs" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-0———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>In</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-0———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>by</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/@veridise?source=post_page—author_recirc–fff3a3f4f6d1—-0———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>[</p>

<h2 id="zero-knowledge-for-dummies-introduction-to-zk-proofs">Zero Knowledge for Dummies: Introduction to ZK Proofs</h2>

<h3 id="do-you-have-zero-knowledge-about-zero-knowledge-do-you-want-to-learn-more-about-it-youre-in-the-right-place-and-we-have-cookies">Do you have zero knowledge about zero knowledge? Do you want to learn more about it? You’re in the right place and we have cookies.</h3>

<p>](https://medium.com/veridise/zero-knowledge-for-dummies-introduction-to-zk-proofs-29e3fe9604f1?source=post_page—author_recirc–fff3a3f4f6d1—-0———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>Aug 24, 2023</p>

<p>[</p>

<p>150</p>

<p>1</p>

<p>](https://medium.com/veridise/zero-knowledge-for-dummies-introduction-to-zk-proofs-29e3fe9604f1?source=post_page—author_recirc–fff3a3f4f6d1—-0———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F29e3fe9604f1&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fzero-knowledge-for-dummies-introduction-to-zk-proofs-29e3fe9604f1&amp;source=---author_recirc--fff3a3f4f6d1----0-----------------bookmark_preview----d84e01f0_b292_493f_bbea_aac71074a8f8--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*7n6YZB61hMFV4kZrj8Azjw.png" alt="Zero Knowledge for Dummies: Demystifying ZK Circuits" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-1———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>In</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-1———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>by</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/@veridise?source=post_page—author_recirc–fff3a3f4f6d1—-1———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>[</p>

<h2 id="zero-knowledge-for-dummies-demystifying-zk-circuits">Zero Knowledge for Dummies: Demystifying ZK Circuits</h2>

<h3 id="zk-circuits-are-the-magic-tools-that-enable-zk-proofs">ZK circuits are the “magic tools” that enable ZK proofs</h3>

<p>](https://medium.com/veridise/zero-knowledge-for-dummies-demystifying-zk-circuits-c140a64c6ed3?source=post_page—author_recirc–fff3a3f4f6d1—-1———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>Jan 19, 2024</p>

<p>[</p>

<p>27</p>

<p>](https://medium.com/veridise/zero-knowledge-for-dummies-demystifying-zk-circuits-c140a64c6ed3?source=post_page—author_recirc–fff3a3f4f6d1—-1———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fc140a64c6ed3&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fzero-knowledge-for-dummies-demystifying-zk-circuits-c140a64c6ed3&amp;source=---author_recirc--fff3a3f4f6d1----1-----------------bookmark_preview----d84e01f0_b292_493f_bbea_aac71074a8f8--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*7kkTlG2Ah7C3RzsphuyMjQ.jpeg" alt="Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-2———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>In</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-2———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>by</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/@veridise?source=post_page—author_recirc–fff3a3f4f6d1—-2———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>[</p>

<h2 id="highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained">Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained</h2>

<h3 id="in-2024-the-veridise-team-conducted-a-comprehensive-security-audit-of-o1js-a-crucial-typescript-library-that-powers-zero-knowledge">In 2024, the Veridise team conducted a comprehensive security audit of o1js, a crucial TypeScript library that powers zero-knowledge…</h3>

<p>](https://medium.com/veridise/highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681?source=post_page—author_recirc–fff3a3f4f6d1—-2———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>Feb 3</p>

<p><a href="https://medium.com/veridise/highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681?source=post_page---author_recirc--fff3a3f4f6d1----2---------------------d84e01f0_b292_493f_bbea_aac71074a8f8--------------"></a></p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F2f5708f13681&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fhighlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681&amp;source=---author_recirc--fff3a3f4f6d1----2-----------------bookmark_preview----d84e01f0_b292_493f_bbea_aac71074a8f8--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*fcK7LTcLreBHGYykLcggcw.jpeg" alt="Recursive SNARKs and Incrementally Verifiable Computation (IVC)" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-3———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>In</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—-3———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>by</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/@veridise?source=post_page—author_recirc–fff3a3f4f6d1—-3———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>[</p>

<h2 id="recursive-snarks-and-incrementally-verifiable-computation-ivc">Recursive SNARKs and Incrementally Verifiable Computation (IVC)</h2>

<h3 id="part-i-recursive-snarks-and-incrementally-verifiable-computation">PART I: Recursive SNARKs and Incrementally Verifiable Computation</h3>

<p>](https://medium.com/veridise/introduction-to-nova-and-zk-folding-schemes-4ef717574484?source=post_page—author_recirc–fff3a3f4f6d1—-3———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p>Jul 27, 2023</p>

<p>[</p>

<p>70</p>

<p>](https://medium.com/veridise/introduction-to-nova-and-zk-folding-schemes-4ef717574484?source=post_page—author_recirc–fff3a3f4f6d1—-3———————d84e01f0_b292_493f_bbea_aac71074a8f8————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F4ef717574484&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fintroduction-to-nova-and-zk-folding-schemes-4ef717574484&amp;source=---author_recirc--fff3a3f4f6d1----3-----------------bookmark_preview----d84e01f0_b292_493f_bbea_aac71074a8f8--------------"></a></p>

<p>[</p>

<p>See all from Veridise</p>

<p>](https://medium.com/@veridise?source=post_page—author_recirc–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>See all from Veridise</p>

<p>](https://medium.com/veridise?source=post_page—author_recirc–fff3a3f4f6d1—————————————)</p>

<h2 id="recommended-from-medium">Recommended from Medium</h2>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/0*dY_bMLLwDsM7qxqQ" alt="Animation in react native" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*FZQmGhGfj4f4gHYh3e3Olg@2x.jpeg" alt="Ankit" /></p>

<p>](https://medium.com/@mohantaankit2002?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<p>Ankit</p>

<p>](https://medium.com/@mohantaankit2002?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<h2 id="making-react-native-animations-buttery-smooth-on-budget-phones">Making React Native Animations Buttery Smooth on Budget Phones</h2>

<h3 id="got-a-beautiful-animation-that-turns-into-a-slideshow-on-low-end-devices-lets-fix-that-without-compromising-your-apps-wow-factor">Got a beautiful animation that turns into a slideshow on low-end devices? Let’s fix that without compromising your app’s wow factor.</h3>

<p>](https://medium.com/@mohantaankit2002/making-react-native-animations-buttery-smooth-on-budget-phones-f6ff3d4215bd?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>Feb 15</p>

<p><a href="https://medium.com/@mohantaankit2002/making-react-native-animations-buttery-smooth-on-budget-phones-f6ff3d4215bd?source=post_page---read_next_recirc--fff3a3f4f6d1----0---------------------ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Ff6ff3d4215bd&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2F%40mohantaankit2002%2Fmaking-react-native-animations-buttery-smooth-on-budget-phones-f6ff3d4215bd&amp;source=---read_next_recirc--fff3a3f4f6d1----0-----------------bookmark_preview----ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*3xKF0UIKrDYxA7qVq4rsew.png" alt="Mastering React Native in 2025: Real Talk from a Developer's Perspective" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*U-kjsW7IZUobnoy1gAp1UQ.png" alt="Stackademic" /></p>

<p>](https://medium.com/stackademic?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>In</p>

<p>[</p>

<p>Stackademic</p>

<p>](https://medium.com/stackademic?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>by</p>

<p>[</p>

<p>Abvhishek kumar</p>

<p>](https://medium.com/@abvhishekkumaar?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<h2 id="mastering-react-native-in-2025-real-talk-from-a-developers-perspective">Mastering React Native in 2025: Real Talk from a Developer’s Perspective</h2>

<h3 id="react-native-has-been-a-go-to-choice-for-cross-platform-mobile-development-for-a-while-now-and-in-2025-its-more-powerful-and-flexible">React Native has been a go-to choice for cross-platform mobile development for a while now, and in 2025, it’s more powerful and flexible…</h3>

<p>](https://medium.com/stackademic/mastering-react-native-in-2025-real-talk-from-a-developers-perspective-96aa64910a20?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>6d ago</p>

<p>[</p>

<p>2</p>

<p>1</p>

<p>](https://medium.com/stackademic/mastering-react-native-in-2025-real-talk-from-a-developers-perspective-96aa64910a20?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F96aa64910a20&amp;operation=register&amp;redirect=https%3A%2F%2Fblog.stackademic.com%2Fmastering-react-native-in-2025-real-talk-from-a-developers-perspective-96aa64910a20&amp;source=---read_next_recirc--fff3a3f4f6d1----1-----------------bookmark_preview----ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<h2 id="lists">Lists</h2>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*I1q8VWkZEXxUyv2B4umMzQ.png" alt="An illustration of a Featured story push notification for a publication follower." /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/0*oC1WfZBerS8zD1aW.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/da:true/resize:fill:96:96/0*ayla5Doy42EwGrWu" alt="" /></p>

<h2 id="staff-picks">Staff picks</h2>

<p>817 stories-1632 saves</p>

<p>](https://medium.com/@MediumStaff/list/staff-picks-c7bc6e1ee00f?source=post_page—read_next_recirc–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*4zC5ohNcmVDb1NXmzCvmNA.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*0dul7hn9LeV7U2XLVPvYYw.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*oO7uwYs0NMWV7B4mUCuoIw.png" alt="" /></p>

<h2 id="stories-to-help-you-level-up-at-work">Stories to Help You Level-Up at Work</h2>

<p>19 stories-942 saves</p>

<p>](https://medium.com/@MediumStaff/list/stories-to-help-you-levelup-at-work-faca18b0622f?source=post_page—read_next_recirc–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*VQhBEVqZRXlxFvFVqyTYVA.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*XRekBY0_j_KEo2_lK_SrkQ.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*c2stHqktjG8_XTqkC-bkfw.jpeg" alt="" /></p>

<h2 id="self-improvement-101">Self-Improvement 101</h2>

<p>20 stories-3313 saves</p>

<p>](https://medium.com/@MediumForTeams/list/selfimprovement-101-3c62b6cb0526?source=post_page—read_next_recirc–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*HWxGot_WiEOZ0fkB_ee2gg.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*kAzwx9sMsEYm0bMYDTa-lQ.jpeg" alt="" /></p>

<p><img src="https://miro.medium.com/v2/resize:fill:96:96/1*GGLm6Juo_CxQAKEM-mAWfw.jpeg" alt="" /></p>

<h2 id="productivity-101">Productivity 101</h2>

<p>20 stories-2787 saves</p>

<p>](https://medium.com/@MediumForTeams/list/productivity-101-f09f1aaf38cd?source=post_page—read_next_recirc–fff3a3f4f6d1—————————————)</p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/0*zQIIg7HiXOW75M4S" alt="A laptop, 2 phones, notebook, and other things" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*FZEG_DxaZ4g-w10VST7WGg.jpeg" alt="Andrew Zuo" /></p>

<p>](https://medium.com/@impure?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<p>Andrew Zuo</p>

<p>](https://medium.com/@impure?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<h2 id="go-is-a-poorly-designed-language-actually">Go Is A Poorly Designed Language, Actually</h2>

<h3 id="i-read-an-article-titled-go-is-a-well-designed-language-actually-hard-disagree-i-originally-liked-go-because-it-did-simplify-things-i">I read an article titled Go is a Well-Designed Language, Actually. Hard disagree. I originally liked Go because it did simplify things. I…</h3>

<p>](https://medium.com/@impure/go-is-a-poorly-designed-language-actually-a8ec508fc2ed?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>4d ago</p>

<p>[</p>

<p>263</p>

<p>24</p>

<p>](https://medium.com/@impure/go-is-a-poorly-designed-language-actually-a8ec508fc2ed?source=post_page—read_next_recirc–fff3a3f4f6d1—-0———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2Fa8ec508fc2ed&amp;operation=register&amp;redirect=https%3A%2F%2Fandrewzuo.com%2Fgo-is-a-poorly-designed-language-actually-a8ec508fc2ed&amp;source=---read_next_recirc--fff3a3f4f6d1----0-----------------bookmark_preview----ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*GXomu5teh5riMgkvNvTLiw.jpeg" alt="MacOS: Building the Perfect Development Machine in 2025" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*5D9oYBd58pyjMkV_5-zXXQ.jpeg" alt="Level Up Coding" /></p>

<p>](https://medium.com/gitconnected?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>In</p>

<p>[</p>

<p>Level Up Coding</p>

<p>](https://medium.com/gitconnected?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>by</p>

<p>[</p>

<p>Promise Chukwuenyem</p>

<p>](https://medium.com/@promisepreston?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<h2 id="macos-building-the-perfect-development-machine-in-2025">MacOS: Building the Perfect Development Machine in 2025</h2>

<h3 id="how-i-transformed-my-macbook-into-a-development-powerhouse">How I Transformed My MacBook Into a Development Powerhouse</h3>

<p>](https://medium.com/gitconnected/from-linux-to-mac-building-the-perfect-development-machine-in-2025-14db582f239f?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>Jan 13</p>

<p>[</p>

<p>351</p>

<p>3</p>

<p>](https://medium.com/gitconnected/from-linux-to-mac-building-the-perfect-development-machine-in-2025-14db582f239f?source=post_page—read_next_recirc–fff3a3f4f6d1—-1———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F14db582f239f&amp;operation=register&amp;redirect=https%3A%2F%2Flevelup.gitconnected.com%2Ffrom-linux-to-mac-building-the-perfect-development-machine-in-2025-14db582f239f&amp;source=---read_next_recirc--fff3a3f4f6d1----1-----------------bookmark_preview----ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*7kkTlG2Ah7C3RzsphuyMjQ.jpeg" alt="Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—read_next_recirc–fff3a3f4f6d1—-2———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>In</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/veridise?source=post_page—read_next_recirc–fff3a3f4f6d1—-2———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>by</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/@veridise?source=post_page—read_next_recirc–fff3a3f4f6d1—-2———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<h2 id="highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-1">Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained</h2>

<h3 id="in-2024-the-veridise-team-conducted-a-comprehensive-security-audit-of-o1js-a-crucial-typescript-library-that-powers-zero-knowledge-1">In 2024, the Veridise team conducted a comprehensive security audit of o1js, a crucial TypeScript library that powers zero-knowledge…</h3>

<p>](https://medium.com/veridise/highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681?source=post_page—read_next_recirc–fff3a3f4f6d1—-2———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>Feb 3</p>

<p><a href="https://medium.com/veridise/highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681?source=post_page---read_next_recirc--fff3a3f4f6d1----2---------------------ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F2f5708f13681&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fhighlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681&amp;source=---read_next_recirc--fff3a3f4f6d1----2-----------------bookmark_preview----ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1358/1*CblrYTeERV5wSbWrKeUwdw.jpeg" alt="&quot;Bitcoin is Going to Zero Within a Decade.&quot;" /></p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:40:40/1*6sOhW3xrimX5kCDlGd27ZA.png" alt="Crypto with Lorenzo" /></p>

<p>](https://medium.com/@cryptowithlorenzo?source=post_page—read_next_recirc–fff3a3f4f6d1—-3———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<p>Crypto with Lorenzo</p>

<p>](https://medium.com/@cryptowithlorenzo?source=post_page—read_next_recirc–fff3a3f4f6d1—-3———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>[</p>

<h2 id="bitcoin-is-going-to-zero-within-a-decade">“Bitcoin is Going to Zero Within a Decade.”</h2>

<h3 id="breaking-down-the-arguments-made-by-an-economics-expert">Breaking down the arguments made by an economics expert.</h3>

<p>](https://medium.com/@cryptowithlorenzo/bitcoin-is-going-to-zero-5562122f5481?source=post_page—read_next_recirc–fff3a3f4f6d1—-3———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p>Feb 5</p>

<p>[</p>

<p>390</p>

<p>36</p>

<p>](https://medium.com/@cryptowithlorenzo/bitcoin-is-going-to-zero-5562122f5481?source=post_page—read_next_recirc–fff3a3f4f6d1—-3———————ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3————–)</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F5562122f5481&amp;operation=register&amp;redirect=https%3A%2F%2Fcryptowithlorenzo.medium.com%2Fbitcoin-is-going-to-zero-5562122f5481&amp;source=---read_next_recirc--fff3a3f4f6d1----3-----------------bookmark_preview----ebb31a75_5f5f_46b7_8d3d_a06f39dfabc3--------------"></a></p>

<p>[</p>

<p>See more recommendations</p>

<p>](https://medium.com/?source=post_page—read_next_recirc–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Help</p>

<p>](https://help.medium.com/hc/en-us?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Status</p>

<p>](https://medium.statuspage.io/?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>About</p>

<p>](https://medium.com/about?autoplay=1&amp;source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Careers</p>

<p>](https://medium.com/jobs-at-medium/work-at-medium-959d1a85284e?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Press</p>

<p>](mailto:pressinquiries@medium.com)</p>

<p>[</p>

<p>Blog</p>

<p>](https://blog.medium.com/?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Privacy</p>

<p>](https://policy.medium.com/medium-privacy-policy-f03bf92035c9?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Terms</p>

<p>](https://policy.medium.com/medium-terms-of-service-9db0094a1e0f?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Text to speech</p>

<p>](https://speechify.com/medium?source=post_page—–fff3a3f4f6d1—————————————)</p>

<p>[</p>

<p>Teams</p>

<p>](https://medium.com/business?source=post_page—–fff3a3f4f6d1—————————————)ridise%2Fhighlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681&amp;source=post_page—top_nav_layout_nav———————–global_nav——————)</p>

<p><img src="https://miro.medium.com/v2/resize:fill:64:64/1*dmbNkD5D-u45r44go_cf0g.png" alt="" /></p>

<h1 id="highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2">Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained</h1>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:88:88/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/@veridise?source=post_page—byline–2f5708f13681—————————————)</p>

<p>[</p>

<p><img src="https://miro.medium.com/v2/resize:fill:48:48/1*LwZDepFjUwzEVNUrgIFXeA.png" alt="Veridise" /></p>

<p>](https://medium.com/veridise?source=post_page—byline–2f5708f13681—————————————)</p>

<p><a href="https://medium.com/@veridise?source=post_page---byline--2f5708f13681---------------------------------------">Veridise</a></p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fsubscribe%2Fuser%2Fd54583b14487&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fhighlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681&amp;user=Veridise&amp;userId=d54583b14487&amp;source=post_page-d54583b14487--byline--2f5708f13681---------------------post_header------------------">Follow</a></p>

<p>Published in</p>

<p>[</p>

<p>Veridise</p>

<p>](https://medium.com/veridise?source=post_page—byline–2f5708f13681—————————————)</p>

<p>10 min read</p>

<p>Feb 3, 2025</p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2F_%2Fbookmark%2Fp%2F2f5708f13681&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fhighlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681&amp;source=---header_actions--2f5708f13681---------------------bookmark_footer------------------"></a></p>

<p><a href="https://medium.com/m/signin?actionUrl=https%3A%2F%2Fmedium.com%2Fplans%3Fdimension%3Dpost_audio_button%26postId%3D2f5708f13681&amp;operation=register&amp;redirect=https%3A%2F%2Fmedium.com%2Fveridise%2Fhighlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681&amp;source=---header_actions--2f5708f13681---------------------post_audio_button------------------"></a></p>

<p><img src="https://miro.medium.com/v2/resize:fit:1400/1*7kkTlG2Ah7C3RzsphuyMjQ.jpeg" alt="" /></p>

<p>In 2024, the Veridise team conducted a comprehensive security audit of <a href="https://www.o1labs.org/o1js">o1js</a>, a crucial TypeScript library that powers zero-knowledge application development on the <a href="https://minaprotocol.com/">Mina blockchain</a>.</p>

<p>The security assessment spanned 39 person-weeks, with four security analysts working over a period of 13 weeks. The audit strategy combined tool-assisted analysis of the source code by Veridise engineers with extensive manual, line-by-line code reviews.</p>

<p>In this blog post, we’ll dive into three of the most intriguing vulnerabilities uncovered during our audit. These issues are particularly noteworthy because they span different layers of the cryptographic stack, ranging from low-level field arithmetic to high-level protocol design. What unites them all is their relation to range checks.</p>

<p>To make the findings easier to follow and understand, we’ve simplified the bugs into illustrative examples. Full reporting on the actual vulnerabilities can be found in the full audit report.</p>

<h1 id="working-together-with-o1labs">Working together with o1Labs</h1>

<p>Our collaboration with o1Labs was both productive and engaging. We had weekly meetings and maintained constant communication via Slack.</p>

<p>Most of our interactions were with Gregor and Florian, who were highly active and deeply involved. They worked closely with us to enhance our understanding of the system and even identified some of the bugs independently, working in parallel with our team.</p>

<p>They frequently shared detailed insights through long Slack threads, and were responsive to any queries from our auditors. Their deep knowledge of the codebase allowed them to efficiently guide us to the areas our auditors needed to focus on.</p>

<p>A highlight of the collaboration was Gregor’s incredibly thorough writeups on optimizations. These were invaluable in helping us navigate and comprehend complex circuits, such as emulated field arithmetic and multi-scalar multiplication. His detailed explanations were helpful in our ability to follow and address these intricate components.</p>

<h1 id="three-vulnerabilities-veridise-fixed-in-o1js">Three vulnerabilities Veridise fixed in o1js</h1>

<h1 id="1-importance-of-type-checks-range-validation-in-zero-knowledge-circuits">1) Importance of type checks: range validation in zero-knowledge circuits</h1>

<p>Among the high-severity vulnerabilities we discovered in o1js was a subtle but dangerous flaw in how circuits which validate ECDSA signatures or manipulate foreign curve points are verified. This bug (V-O1J-VUL-006) highlights how missing range checks can undermine the security of cryptographic protocols.</p>

<p>As an overview, in o1js, data is often decomposed into smaller components for processing. A common example is <strong>bit decomposition</strong>, where a number is broken down into its binary representation:</p>

<p>For instance:</p>

<ul>
  <li>The number <code class="language-plaintext highlighter-rouge">3</code> can be written as <code class="language-plaintext highlighter-rouge">1 + 1 * 2</code>, which is encoded as <code class="language-plaintext highlighter-rouge">[1, 1]</code>.</li>
  <li>The number <code class="language-plaintext highlighter-rouge">7</code> can be written as <code class="language-plaintext highlighter-rouge">1 + 1 * 2 + 1 * 4</code>, encoded as <code class="language-plaintext highlighter-rouge">[1, 1, 1]</code>.</li>
</ul>

<p>This same decomposition concept can be applied to larger bases. For example, in o1js, instead of base <code class="language-plaintext highlighter-rouge">2</code>, you might use <code class="language-plaintext highlighter-rouge">2^88</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[1, 1, 1]</code> in this context represents:</li>
  <li><code class="language-plaintext highlighter-rouge">1 + 1 * 2^88 + 1 * (2^88)^2 = 1 + 1 * 2^88 + 1 * 2^176</code>.</li>
</ul>

<h1 id="the-problem">The problem:</h1>

<p>The decomposition is only well-defined (or unique) if each component in the representation remains within a specified range.</p>

<h1 id="bit-decomposition-example">Bit decomposition example</h1>

<p>In bit decomposition, each entry must be either <code class="language-plaintext highlighter-rouge">0</code> or <code class="language-plaintext highlighter-rouge">1</code>. If this condition is violated, ambiguities arise.</p>

<p>For instance, <code class="language-plaintext highlighter-rouge">7</code> could be decomposed in multiple ways:<code class="language-plaintext highlighter-rouge">[1, 3, 0]</code> or <code class="language-plaintext highlighter-rouge">[1, 1, 1]</code>.</p>

<p>This happens because you can “borrow” from higher components. Example:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">1 + 1 * 2 + 1 * 4</code> can also be expressed as:</li>
  <li><code class="language-plaintext highlighter-rouge">1 + (1 + 2) * 2 + (2 - 2) * 4 = 1 + 3 * 2</code>.</li>
</ul>

<h1 id="larger-bases-eg288">Larger Bases (e.g., <code class="language-plaintext highlighter-rouge">2^88</code>)</h1>

<p>For larger bases like <code class="language-plaintext highlighter-rouge">2^88</code>, each entry must satisfy:<code class="language-plaintext highlighter-rouge">0 ≤ entry &lt; 2^88</code>.</p>

<p>Without this constraint, similar ambiguity occurs: You can “add” or “subtract” between components to create alternate decompositions.</p>

<h1 id="specific-bug-details">Specific bug details</h1>

<p>In this case, a custom type was represented using <strong>three limbs</strong>, each of size <code class="language-plaintext highlighter-rouge">2^88</code>.</p>

<p>However, there was <strong>no check</strong> to ensure that the limbs were actually within the range <code class="language-plaintext highlighter-rouge">[0, 2^88 - 1]</code>.</p>

<h1 id="impact-and-the-fix">Impact and the fix:</h1>

<p>An attacker can manipulate the values of these limbs and carefully choose values that <strong>overflow</strong> during subsequent computations.</p>

<p>This creates opportunities for cryptographic exploits and undermines the integrity of the protocol.</p>

<p>The root cause of this vulnerability — and similar ones — is <strong>missing range checks</strong>. Ensuring proper type and range validation is critical to maintaining the security and correctness of zero-knowledge circuits.</p>

<h1 id="2-hash-collisions-in-merkle-map-key-to-index-mapping">2) Hash collisions in merkle map key-to-index mapping</h1>

<p>The basic idea of bug V-O1J-VUL-002 is that a mapping is being stored in a Merkle tree with more leaves than keys in the map.</p>

<h1 id="the-problem-1">The problem</h1>

<p><code class="language-plaintext highlighter-rouge">key</code>s are limited to 254 bits, so they lie within the range <code class="language-plaintext highlighter-rouge">[0, 2**254)</code>. However, the Merkle tree has more <code class="language-plaintext highlighter-rouge">index</code> es than there are unique <code class="language-plaintext highlighter-rouge">key</code> s!</p>

<p>This means some <code class="language-plaintext highlighter-rouge">index</code> es in the tree must map to the <strong>same </strong><code class="language-plaintext highlighter-rouge">**key**</code>.</p>

<p>In fact, it is straightforward to determine which <code class="language-plaintext highlighter-rouge">indexes</code> share the same <code class="language-plaintext highlighter-rouge">key</code>—there are trillions of possibilities.</p>

<h1 id="simplified-example--exploit">Simplified example &amp; exploit</h1>

<p>Suppose a <code class="language-plaintext highlighter-rouge">key</code> is an address and a <code class="language-plaintext highlighter-rouge">value</code> indicates whether the address is blacklisted (<code class="language-plaintext highlighter-rouge">true</code>) or not (<code class="language-plaintext highlighter-rouge">false</code>).</p>

<p>A single <code class="language-plaintext highlighter-rouge">key</code> might correspond to <strong>two distinct indexes</strong> in the Merkle tree: At one index, the value stored is <code class="language-plaintext highlighter-rouge">true</code>. At another index, the value stored is <code class="language-plaintext highlighter-rouge">false</code> (an edge case overlooked by developers).</p>

<p>The attacker can <strong>choose which index to use</strong>, enabling them to exploit the system. Naturally, the attacker will select the index with the value advantageous to them.</p>

<p>To summarize, the core issue is that instead of a one-to-one relationship between <code class="language-plaintext highlighter-rouge">keys</code> and <code class="language-plaintext highlighter-rouge">indexes</code>, some <code class="language-plaintext highlighter-rouge">keys</code> correspond to multiple <code class="language-plaintext highlighter-rouge">indexes</code>. This allows attackers to exploit the ambiguity and choose the mapping that benefits them.</p>

<h1 id="impact-and-the-fix-1">Impact and the fix</h1>

<p>As shown in the above proof of concept, a user may prove that some entries of a MerkleMap are empty, even after they have been set. This can have critical consequences, such as user could prove their address is not in a blacklist, or that a certain nullifier is not in a Merkle tree.</p>

<p>We recommended a range-check on to prevent this overflow.</p>

<p>While this high-level overview omits some details, it captures the essence of the vulnerability. Full description of the bug can be found in the audit report, PDF page 15.</p>

<h1 id="3-the-hidden-dangers-of-hash-inversion-in-zk-circuits">3) The hidden dangers of hash inversion in ZK circuits</h1>

<p>The core concept in the first bug (V-O1J-VUL-001, PDF page 15) revolves around <em>multiscalar multiplication</em> and a bug in a <em>compress-select-decompress</em>process. This bug would have enabled attackers to have control over the output of the multi-scalar computation.</p>

<p>In this blog post, we’re giving a simplified example of the bug for easier comprehension and readability. The actual bug specific to o1js can be studied in the audit report.</p>

<h1 id="what-are-multiscalar-multiplications">What are multiscalar multiplications?</h1>

<p>At a high level, multiscalar multiplication essentially means performing multiple multiplications and adding the results together. However, instead of multiplying numbers, in this context one is usually dealing with cryptographic constructs called elliptic curve points.</p>

<p>Multiscalar multiplication (MSM) is usually implemented as a specialized operation designed to make common calculations faster and more efficient.</p>

<p>Rather than describe the full context, this blog will focus on a particular step in the multi-scalar multiplication algorithm. At this step, o1js needs to choose between two possible values for an array. For details on how this fits into the larger MSM algorithm, see <a href="https://crypto.stackexchange.com/questions/99975/strauss-shamir-trick-on-ec-multiplication-by-scalar">here</a>. For the purposes of this blog, just know that if an attacker can control which of the two possible arrays is chosen, they can potentially alter the output of standard cryptographic algorithms like <a href="https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm">ECDSA</a>.</p>

<h1 id="challenges-in-zk-circuits-there-are-no-if-else-statements">Challenges in ZK Circuits: there are no if-else statements</h1>

<p>ZK circuits lack traditional control flow structures like <code class="language-plaintext highlighter-rouge">if-else</code> statements. Instead, both options (e.g., left and right) must be computed, and the desired option is selected by multiplying it by <code class="language-plaintext highlighter-rouge">1</code>, while the undesired option is multiplied by <code class="language-plaintext highlighter-rouge">0</code>.</p>

<p>This approach can be inefficient, especially when the options involve large datasets. For example, if Option 1 sets 10 values (e.g., <code class="language-plaintext highlighter-rouge">1, 2, 3, ... 10</code>) and Option 2 sets another 10 values (e.g., <code class="language-plaintext highlighter-rouge">11, 12, 13, ... 20</code>), the circuit essentially computes both options in full and pays the computational cost for each, even though only one is used. This inefficiency is the problem o1js aims to optimize.</p>

<h1 id="optimization-approach">Optimization Approach</h1>

<ol>
  <li>Compression: Instead of working with all 10 values directly, the values are first hashed together. This creates a single “commitment” value representing the 10 values.</li>
  <li>Decision: Both the left and right options are compressed into single hashes. The algorithm then decides between these compressed options.</li>
  <li>Decompression: After the decision is made, the chosen compressed value is decompressed back into the original 10 values.</li>
</ol>

<p>By compressing the data first, the circuit avoids the overhead of making the decision 10 times. Instead, it only makes the decision once, based on the compressed hashes.</p>

<h1 id="the-core-issue--compress-select-decompress-process">The core issue — Compress-select-decompress process</h1>

<p>The key vulnerability lies in the <strong>compress-select-decompress</strong> process. In ZK circuits, decompression must reliably produce the exact original values from the compressed hash. Any mismatch here could lead to attackers gaining control of the execution.</p>

<h1 id="why-this-can-fail">Why this can fail</h1>

<p>Compression functions typically handle data as <code class="language-plaintext highlighter-rouge">1s</code> and <code class="language-plaintext highlighter-rouge">0s</code>. These binary representations might correspond to scalars, field elements, or booleans, or other types.</p>

<p>If the same binary data (<code class="language-plaintext highlighter-rouge">1s</code> and <code class="language-plaintext highlighter-rouge">0s</code>) can be decoded into multiple possible values, the decompression process might yield incorrect or unexpected results. This ambiguity creates a potential exploit.</p>

<h1 id="compression-is-not-injective">Compression is not injective</h1>

<p>In simple terms, the issue in the o1js code arises from an optimization routine that is not <em>injective</em>. Injective means that the decoding process can produce multiple valid solutions, leading to potential vulnerabilities.</p>

<p>Here’s how the original optimization process works:</p>

<ol>
  <li><strong>Compression:</strong></li>
</ol>

<ul>
  <li>The values <code class="language-plaintext highlighter-rouge">[a0, a1]</code> are compressed into a single hash: <code class="language-plaintext highlighter-rouge">hash(encode([a0, a1]))</code>.</li>
  <li>Similarly, <code class="language-plaintext highlighter-rouge">[b0, b1]</code> is compressed: <code class="language-plaintext highlighter-rouge">hash(encode([b0, b1]))</code>.</li>
</ul>

<p><strong>2. “Switch” step:</strong></p>

<ul>
  <li>The algorithm selects one of the two compressed options and assigns it to <code class="language-plaintext highlighter-rouge">choice</code>.</li>
</ul>

<p><strong>3. Decompression:</strong></p>

<ul>
  <li>The algorithm verifies that the decompressed result matches the chosen hash:<code class="language-plaintext highlighter-rouge">assert(hash(encode([r0, r1])) == choice)</code>.</li>
</ul>

<p><strong>4. Result Extraction:</strong></p>

<ul>
  <li>After decompression, <code class="language-plaintext highlighter-rouge">[r0, r1]</code> are obtained as the resulting values.</li>
</ul>

<p>The encoding process lacks the necessary property to guarantee that decoding and re-encoding will always yield the same result. In other words:</p>

<p><code class="language-plaintext highlighter-rouge">decode(encode([r0, r1])) == choice</code></p>

<p>does not consistently hold true. This means that the same encoded value can be decoded into multiple different outputs, introducing ambiguity.</p>

<p>Such behavior creates a vulnerability, as the system may fail to reliably match the original values after decompression. This non-injective encoding undermines the security and correctness of the algorithm.</p>

<h1 id="lets-understand-this-with-an-example-encoding-function">Let’s understand this with an example encoding function</h1>

<p>Consider an array of length 2, where each value is a boolean (0 or 1). Possible values for the array are:<code class="language-plaintext highlighter-rouge">[0, 0]</code>, <code class="language-plaintext highlighter-rouge">[0, 1]</code>, <code class="language-plaintext highlighter-rouge">[1, 0]</code>, or <code class="language-plaintext highlighter-rouge">[1, 1]</code></p>

<p>We define the following:</p>

<ul>
  <li><strong>Option 0:</strong> <code class="language-plaintext highlighter-rouge">[a0, a1]</code></li>
  <li><strong>Option 1:</strong> <code class="language-plaintext highlighter-rouge">[b0, b1]</code></li>
  <li><strong>Selector variable:</strong> <code class="language-plaintext highlighter-rouge">s</code>, which can be <code class="language-plaintext highlighter-rouge">0</code> or <code class="language-plaintext highlighter-rouge">1</code></li>
  <li><strong>Goal:</strong> Store the correct value into the result <code class="language-plaintext highlighter-rouge">[r0, r1]</code></li>
</ul>

<h1 id="the-slow-way">The slow way</h1>

<p>For each element in the result:</p>

<p>Compute <code class="language-plaintext highlighter-rouge">r0</code> using:<code class="language-plaintext highlighter-rouge">r0 := (1-s) * a0 + s * b0</code></p>

<p><strong>Why does this work?</strong></p>

<ul>
  <li>When <code class="language-plaintext highlighter-rouge">s = 0</code>:<code class="language-plaintext highlighter-rouge">r0 := (1 - 0) * a0 + 0 * b0 = a0</code></li>
  <li>When <code class="language-plaintext highlighter-rouge">s = 1</code>:<code class="language-plaintext highlighter-rouge">r0 := 0 * a0 + 1 * b0 = b0</code></li>
</ul>

<p>Similarly, compute <code class="language-plaintext highlighter-rouge">r1</code> using:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">r1 := (1-s) * a1 + s * b1</code></li>
</ul>

<p>This computation is sometimes called “switching” between a and b.</p>

<p>This approach works but can be <strong>slow</strong>, as it requires repeating the computation for each element. With an array of length two, this is not so bad. But as the arrays get longer and longer, the repeated computation can take a toll.</p>

<h1 id="bad-optimization">Bad optimization</h1>

<p>Instead of directly switching, the optimization uses an <strong>encoding function</strong>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">encode([a0, a1]) = a0 + 2 * a1</code></li>
  <li><code class="language-plaintext highlighter-rouge">encode([b0, b1]) = b0 + 2 * b1</code></li>
</ul>

<p>This encoding maps the arrays uniquely:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[0, 0] → 0 + 2 * 0 = 0</code></li>
  <li><code class="language-plaintext highlighter-rouge">[0, 1] → 0 + 2 * 1 = 2</code></li>
  <li><code class="language-plaintext highlighter-rouge">[1, 0] → 1 + 2 * 0 = 1</code></li>
  <li><code class="language-plaintext highlighter-rouge">[1, 1] → 1 + 2 * 1 = 3</code></li>
</ul>

<p>This seems fine so far, as the mappings are unique.</p>

<h1 id="optimization-process">Optimization process</h1>

<ol>
  <li><strong>Compression:</strong> Compress both options into single values:</li>
</ol>

<p>cA := a0 + 2 * a1<br />
cB := b0 + 2 * b1</p>

<ol>
  <li><strong>Switch:</strong> Use the selector <code class="language-plaintext highlighter-rouge">s</code> to choose between the compressed values:</li>
</ol>

<p>cR := (1-s) * cA + s * cB</p>

<ol>
  <li><strong>Decompression:</strong> Generate <code class="language-plaintext highlighter-rouge">r0, r1</code> from the witness generator and add a constraint to ensure the decompressed values match:</li>
</ol>

<p>r0 + 2 * r1 = cR</p>

<h1 id="example-exploit">Example exploit</h1>

<p>The issue lies in the <strong>decompression</strong> step. The witness generator is not constrained to produce valid boolean values (0 or 1) for <code class="language-plaintext highlighter-rouge">r0</code> and <code class="language-plaintext highlighter-rouge">r1</code>.</p>

<p>This means that instead of <code class="language-plaintext highlighter-rouge">[r0, r1]</code> being restricted to <code class="language-plaintext highlighter-rouge">[0, 1]</code>, they can take arbitrary values.</p>

<p>An attacker could choose <code class="language-plaintext highlighter-rouge">r0 = 1,000,000</code> and <code class="language-plaintext highlighter-rouge">r1 = -500,000</code>. Let’s compute the compressed value:</p>

<p>cR = r0 + 2 * r1 = 1,000,000 + 2 * (-500,000) = 1,000,000 - 1,000,000 = 0</p>

<p>This satisfies the constraint <code class="language-plaintext highlighter-rouge">r0 + 2 * r1 = cR</code>, but clearly, the values <code class="language-plaintext highlighter-rouge">[r0, r1]</code> do not represent valid boolean values.</p>

<h1 id="root-cause">Root cause</h1>

<p>During compression, the original arrays <code class="language-plaintext highlighter-rouge">[a0, a1]</code> and <code class="language-plaintext highlighter-rouge">[b0, b1]</code> were constrained to boolean values (0 or 1).</p>

<p>However, during decompression, the original code failed to enforce these constraints, allowing the witness generator to produce invalid values.</p>

<p>This lack of constraints makes the encoding <strong>non-injective</strong>, meaning the decompressed values can correspond to multiple possible outputs, enabling attackers to exploit the system.</p>

<h1 id="impact-and-the-fix-2">Impact and the fix</h1>

<p>The vulnerability arises because the decompression step lacks proper constraints, breaking the injective property of the encoding-decoding process. To fix this, the system must enforce that <code class="language-plaintext highlighter-rouge">r0</code> and <code class="language-plaintext highlighter-rouge">r1</code> remain within their valid range (0 or 1) during decompression.</p>

<h1 id="final-remarks">Final remarks</h1>

<p>o1js has a very active set of contributors and is very interested in security. They have worked hard to bring ZK technology to TypeScript developers, and overcome a set of unique challenges in bringing smart contract logic to the Mina blockchain.</p>

<p>We found out it remarkably straightforward to prototype proof-of-concept applications using the TypeScript library. This ease of use extends to identifying under-constrained bugs and testing circuits in ways that developers might not have anticipated.</p>

<p>If you’re planning to develop dapps on the Mina blockchain using TypeScript, stay tuned — our upcoming blog post will provide insights and best practices for secure development, drawing from our auditing experience.</p>

<h1 id="full-audit-report">Full audit report</h1>

<p>Download the full o1js <a href="https://veridise.com/audits-archive/company/o1-labs/o1-labs-o1js-2024-08-27/">security audit report here.</a></p>

<p><strong>Author</strong>:<br />
Ben Sepanski, Chief Security Officer at Veridise</p>

<p>Editor: Mikko Ikola, VP of Marketing</p>]]></content><author><name>Benjamin Sepanski</name><email>ben.sepanski@veridise.com</email></author><category term="Mina" /><category term="ZK" /><category term="o1js" /><category term="Secure development" /><summary type="html"><![CDATA[For the original (and properly formatted) publication, see Veridise’s post on medium.]]></summary></entry><entry><title type="html">Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained</title><link href="https://bensepanski.github.io/posts/2025/02/03/Highlights-from-the-Veridise-o1js-v1-audit/" rel="alternate" type="text/html" title="Highlights from the Veridise o1js v1 audit: Three zero-knowledge security bugs explained" /><published>2025-02-03T00:00:00+00:00</published><updated>2025-02-03T00:00:00+00:00</updated><id>https://bensepanski.github.io/posts/2025/02/03/Highlights-from-the-Veridise-o1js-v1-audit</id><content type="html" xml:base="https://bensepanski.github.io/posts/2025/02/03/Highlights-from-the-Veridise-o1js-v1-audit/"><![CDATA[<p>For the original (and properly formatted) publication, see <a href="https://medium.com/veridise/highlights-from-the-veridise-o1js-v1-audit-three-zero-knowledge-security-bugs-explained-2f5708f13681">Veridise’s post on medium</a>.</p>

<hr />

<p>In 2024, the Veridise team conducted a comprehensive security audit of <a href="https://www.o1labs.org/o1js">o1js</a>, a crucial TypeScript library that powers zero-knowledge application development on the <a href="https://minaprotocol.com/">Mina blockchain</a>.</p>

<p>The security assessment spanned 39 person-weeks, with four security analysts working over a period of 13 weeks. The audit strategy combined tool-assisted analysis of the source code by Veridise engineers with extensive manual, line-by-line code reviews.</p>

<p>In this blog post, we’ll dive into three of the most intriguing vulnerabilities uncovered during our audit. These issues are particularly noteworthy because they span different layers of the cryptographic stack, ranging from low-level field arithmetic to high-level protocol design. What unites them all is their relation to range checks.</p>

<p>To make the findings easier to follow and understand, we’ve simplified the bugs into illustrative examples. Full reporting on the actual vulnerabilities can be found in the full audit report.</p>

<h1 id="working-together-with-o1labs">Working together with o1Labs</h1>

<p>Our collaboration with o1Labs was both productive and engaging. We had weekly meetings and maintained constant communication via Slack.</p>

<p>Most of our interactions were with Gregor and Florian, who were highly active and deeply involved. They worked closely with us to enhance our understanding of the system and even identified some of the bugs independently, working in parallel with our team.</p>

<p>They frequently shared detailed insights through long Slack threads, and were responsive to any queries from our auditors. Their deep knowledge of the codebase allowed them to efficiently guide us to the areas our auditors needed to focus on.</p>

<p>A highlight of the collaboration was Gregor’s incredibly thorough writeups on optimizations. These were invaluable in helping us navigate and comprehend complex circuits, such as emulated field arithmetic and multi-scalar multiplication. His detailed explanations were helpful in our ability to follow and address these intricate components.</p>

<h1 id="three-vulnerabilities-veridise-fixed-in-o1js">Three vulnerabilities Veridise fixed in o1js</h1>

<h1 id="1-importance-of-type-checks-range-validation-in-zero-knowledge-circuits">1) Importance of type checks: range validation in zero-knowledge circuits</h1>

<p>Among the high-severity vulnerabilities we discovered in o1js was a subtle but dangerous flaw in how circuits which validate ECDSA signatures or manipulate foreign curve points are verified. This bug (V-O1J-VUL-006) highlights how missing range checks can undermine the security of cryptographic protocols.</p>

<p>As an overview, in o1js, data is often decomposed into smaller components for processing. A common example is <strong>bit decomposition</strong>, where a number is broken down into its binary representation:</p>

<p>For instance:</p>

<ul>
  <li>The number <code class="language-plaintext highlighter-rouge">3</code> can be written as <code class="language-plaintext highlighter-rouge">1 + 1 * 2</code>, which is encoded as <code class="language-plaintext highlighter-rouge">[1, 1]</code>.</li>
  <li>The number <code class="language-plaintext highlighter-rouge">7</code> can be written as <code class="language-plaintext highlighter-rouge">1 + 1 * 2 + 1 * 4</code>, encoded as <code class="language-plaintext highlighter-rouge">[1, 1, 1]</code>.</li>
</ul>

<p>This same decomposition concept can be applied to larger bases. For example, in o1js, instead of base <code class="language-plaintext highlighter-rouge">2</code>, you might use <code class="language-plaintext highlighter-rouge">2^88</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[1, 1, 1]</code> in this context represents:</li>
  <li><code class="language-plaintext highlighter-rouge">1 + 1 * 2^88 + 1 * (2^88)^2 = 1 + 1 * 2^88 + 1 * 2^176</code>.</li>
</ul>

<h1 id="the-problem">The problem:</h1>

<p>The decomposition is only well-defined (or unique) if each component in the representation remains within a specified range.</p>

<h1 id="bit-decomposition-example">Bit decomposition example</h1>

<p>In bit decomposition, each entry must be either <code class="language-plaintext highlighter-rouge">0</code> or <code class="language-plaintext highlighter-rouge">1</code>. If this condition is violated, ambiguities arise.</p>

<p>For instance, <code class="language-plaintext highlighter-rouge">7</code> could be decomposed in multiple ways:<code class="language-plaintext highlighter-rouge">[1, 3, 0]</code> or <code class="language-plaintext highlighter-rouge">[1, 1, 1]</code>.</p>

<p>This happens because you can “borrow” from higher components. Example:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">1 + 1 * 2 + 1 * 4</code> can also be expressed as:</li>
  <li><code class="language-plaintext highlighter-rouge">1 + (1 + 2) * 2 + (2 - 2) * 4 = 1 + 3 * 2</code>.</li>
</ul>

<h1 id="larger-bases-eg288">Larger Bases (e.g., <code class="language-plaintext highlighter-rouge">2^88</code>)</h1>

<p>For larger bases like <code class="language-plaintext highlighter-rouge">2^88</code>, each entry must satisfy:<code class="language-plaintext highlighter-rouge">0 ≤ entry &lt; 2^88</code>.</p>

<p>Without this constraint, similar ambiguity occurs: You can “add” or “subtract” between components to create alternate decompositions.</p>

<h1 id="specific-bug-details">Specific bug details</h1>

<p>In this case, a custom type was represented using <strong>three limbs</strong>, each of size <code class="language-plaintext highlighter-rouge">2^88</code>.</p>

<p>However, there was <strong>no check</strong> to ensure that the limbs were actually within the range <code class="language-plaintext highlighter-rouge">[0, 2^88 - 1]</code>.</p>

<h1 id="impact-and-the-fix">Impact and the fix:</h1>

<p>An attacker can manipulate the values of these limbs and carefully choose values that <strong>overflow</strong> during subsequent computations.</p>

<p>This creates opportunities for cryptographic exploits and undermines the integrity of the protocol.</p>

<p>The root cause of this vulnerability — and similar ones — is <strong>missing range checks</strong>. Ensuring proper type and range validation is critical to maintaining the security and correctness of zero-knowledge circuits.</p>

<h1 id="2-hash-collisions-in-merkle-map-key-to-index-mapping">2) Hash collisions in merkle map key-to-index mapping</h1>

<p>The basic idea of bug V-O1J-VUL-002 is that a mapping is being stored in a Merkle tree with more leaves than keys in the map.</p>

<h1 id="the-problem-1">The problem</h1>

<p><code class="language-plaintext highlighter-rouge">key</code>s are limited to 254 bits, so they lie within the range <code class="language-plaintext highlighter-rouge">[0, 2**254)</code>. However, the Merkle tree has more <code class="language-plaintext highlighter-rouge">index</code> es than there are unique <code class="language-plaintext highlighter-rouge">key</code> s!</p>

<p>This means some <code class="language-plaintext highlighter-rouge">index</code> es in the tree must map to the <strong>same </strong><code class="language-plaintext highlighter-rouge">**key**</code>.</p>

<p>In fact, it is straightforward to determine which <code class="language-plaintext highlighter-rouge">indexes</code> share the same <code class="language-plaintext highlighter-rouge">key</code>—there are trillions of possibilities.</p>

<h1 id="simplified-example--exploit">Simplified example &amp; exploit</h1>

<p>Suppose a <code class="language-plaintext highlighter-rouge">key</code> is an address and a <code class="language-plaintext highlighter-rouge">value</code> indicates whether the address is blacklisted (<code class="language-plaintext highlighter-rouge">true</code>) or not (<code class="language-plaintext highlighter-rouge">false</code>).</p>

<p>A single <code class="language-plaintext highlighter-rouge">key</code> might correspond to <strong>two distinct indexes</strong> in the Merkle tree: At one index, the value stored is <code class="language-plaintext highlighter-rouge">true</code>. At another index, the value stored is <code class="language-plaintext highlighter-rouge">false</code> (an edge case overlooked by developers).</p>

<p>The attacker can <strong>choose which index to use</strong>, enabling them to exploit the system. Naturally, the attacker will select the index with the value advantageous to them.</p>

<p>To summarize, the core issue is that instead of a one-to-one relationship between <code class="language-plaintext highlighter-rouge">keys</code> and <code class="language-plaintext highlighter-rouge">indexes</code>, some <code class="language-plaintext highlighter-rouge">keys</code> correspond to multiple <code class="language-plaintext highlighter-rouge">indexes</code>. This allows attackers to exploit the ambiguity and choose the mapping that benefits them.</p>

<h1 id="impact-and-the-fix-1">Impact and the fix</h1>

<p>As shown in the above proof of concept, a user may prove that some entries of a MerkleMap are empty, even after they have been set. This can have critical consequences, such as user could prove their address is not in a blacklist, or that a certain nullifier is not in a Merkle tree.</p>

<p>We recommended a range-check on to prevent this overflow.</p>

<p>While this high-level overview omits some details, it captures the essence of the vulnerability. Full description of the bug can be found in the audit report, PDF page 15.</p>

<h1 id="3-the-hidden-dangers-of-hash-inversion-in-zk-circuits">3) The hidden dangers of hash inversion in ZK circuits</h1>

<p>The core concept in the first bug (V-O1J-VUL-001, PDF page 15) revolves around <em>multiscalar multiplication</em> and a bug in a <em>compress-select-decompress</em>process. This bug would have enabled attackers to have control over the output of the multi-scalar computation.</p>

<p>In this blog post, we’re giving a simplified example of the bug for easier comprehension and readability. The actual bug specific to o1js can be studied in the audit report.</p>

<h1 id="what-are-multiscalar-multiplications">What are multiscalar multiplications?</h1>

<p>At a high level, multiscalar multiplication essentially means performing multiple multiplications and adding the results together. However, instead of multiplying numbers, in this context one is usually dealing with cryptographic constructs called elliptic curve points.</p>

<p>Multiscalar multiplication (MSM) is usually implemented as a specialized operation designed to make common calculations faster and more efficient.</p>

<p>Rather than describe the full context, this blog will focus on a particular step in the multi-scalar multiplication algorithm. At this step, o1js needs to choose between two possible values for an array. For details on how this fits into the larger MSM algorithm, see <a href="https://crypto.stackexchange.com/questions/99975/strauss-shamir-trick-on-ec-multiplication-by-scalar">here</a>. For the purposes of this blog, just know that if an attacker can control which of the two possible arrays is chosen, they can potentially alter the output of standard cryptographic algorithms like <a href="https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm">ECDSA</a>.</p>

<h1 id="challenges-in-zk-circuits-there-are-no-if-else-statements">Challenges in ZK Circuits: there are no if-else statements</h1>

<p>ZK circuits lack traditional control flow structures like <code class="language-plaintext highlighter-rouge">if-else</code> statements. Instead, both options (e.g., left and right) must be computed, and the desired option is selected by multiplying it by <code class="language-plaintext highlighter-rouge">1</code>, while the undesired option is multiplied by <code class="language-plaintext highlighter-rouge">0</code>.</p>

<p>This approach can be inefficient, especially when the options involve large datasets. For example, if Option 1 sets 10 values (e.g., <code class="language-plaintext highlighter-rouge">1, 2, 3, ... 10</code>) and Option 2 sets another 10 values (e.g., <code class="language-plaintext highlighter-rouge">11, 12, 13, ... 20</code>), the circuit essentially computes both options in full and pays the computational cost for each, even though only one is used. This inefficiency is the problem o1js aims to optimize.</p>

<h1 id="optimization-approach">Optimization Approach</h1>

<ol>
  <li>Compression: Instead of working with all 10 values directly, the values are first hashed together. This creates a single “commitment” value representing the 10 values.</li>
  <li>Decision: Both the left and right options are compressed into single hashes. The algorithm then decides between these compressed options.</li>
  <li>Decompression: After the decision is made, the chosen compressed value is decompressed back into the original 10 values.</li>
</ol>

<p>By compressing the data first, the circuit avoids the overhead of making the decision 10 times. Instead, it only makes the decision once, based on the compressed hashes.</p>

<h1 id="the-core-issue--compress-select-decompress-process">The core issue — Compress-select-decompress process</h1>

<p>The key vulnerability lies in the <strong>compress-select-decompress</strong> process. In ZK circuits, decompression must reliably produce the exact original values from the compressed hash. Any mismatch here could lead to attackers gaining control of the execution.</p>

<h1 id="why-this-can-fail">Why this can fail</h1>

<p>Compression functions typically handle data as <code class="language-plaintext highlighter-rouge">1s</code> and <code class="language-plaintext highlighter-rouge">0s</code>. These binary representations might correspond to scalars, field elements, or booleans, or other types.</p>

<p>If the same binary data (<code class="language-plaintext highlighter-rouge">1s</code> and <code class="language-plaintext highlighter-rouge">0s</code>) can be decoded into multiple possible values, the decompression process might yield incorrect or unexpected results. This ambiguity creates a potential exploit.</p>

<h1 id="compression-is-not-injective">Compression is not injective</h1>

<p>In simple terms, the issue in the o1js code arises from an optimization routine that is not <em>injective</em>. Injective means that the decoding process can produce multiple valid solutions, leading to potential vulnerabilities.</p>

<p>Here’s how the original optimization process works:</p>

<ol>
  <li><strong>Compression:</strong></li>
</ol>

<ul>
  <li>The values <code class="language-plaintext highlighter-rouge">[a0, a1]</code> are compressed into a single hash: <code class="language-plaintext highlighter-rouge">hash(encode([a0, a1]))</code>.</li>
  <li>Similarly, <code class="language-plaintext highlighter-rouge">[b0, b1]</code> is compressed: <code class="language-plaintext highlighter-rouge">hash(encode([b0, b1]))</code>.</li>
</ul>

<p><strong>2. “Switch” step:</strong></p>

<ul>
  <li>The algorithm selects one of the two compressed options and assigns it to <code class="language-plaintext highlighter-rouge">choice</code>.</li>
</ul>

<p><strong>3. Decompression:</strong></p>

<ul>
  <li>The algorithm verifies that the decompressed result matches the chosen hash:<code class="language-plaintext highlighter-rouge">assert(hash(encode([r0, r1])) == choice)</code>.</li>
</ul>

<p><strong>4. Result Extraction:</strong></p>

<ul>
  <li>After decompression, <code class="language-plaintext highlighter-rouge">[r0, r1]</code> are obtained as the resulting values.</li>
</ul>

<p>The encoding process lacks the necessary property to guarantee that decoding and re-encoding will always yield the same result. In other words:</p>

<p><code class="language-plaintext highlighter-rouge">decode(encode([r0, r1])) == choice</code></p>

<p>does not consistently hold true. This means that the same encoded value can be decoded into multiple different outputs, introducing ambiguity.</p>

<p>Such behavior creates a vulnerability, as the system may fail to reliably match the original values after decompression. This non-injective encoding undermines the security and correctness of the algorithm.</p>

<h1 id="lets-understand-this-with-an-example-encoding-function">Let’s understand this with an example encoding function</h1>

<p>Consider an array of length 2, where each value is a boolean (0 or 1). Possible values for the array are:<code class="language-plaintext highlighter-rouge">[0, 0]</code>, <code class="language-plaintext highlighter-rouge">[0, 1]</code>, <code class="language-plaintext highlighter-rouge">[1, 0]</code>, or <code class="language-plaintext highlighter-rouge">[1, 1]</code></p>

<p>We define the following:</p>

<ul>
  <li><strong>Option 0:</strong> <code class="language-plaintext highlighter-rouge">[a0, a1]</code></li>
  <li><strong>Option 1:</strong> <code class="language-plaintext highlighter-rouge">[b0, b1]</code></li>
  <li><strong>Selector variable:</strong> <code class="language-plaintext highlighter-rouge">s</code>, which can be <code class="language-plaintext highlighter-rouge">0</code> or <code class="language-plaintext highlighter-rouge">1</code></li>
  <li><strong>Goal:</strong> Store the correct value into the result <code class="language-plaintext highlighter-rouge">[r0, r1]</code></li>
</ul>

<h1 id="the-slow-way">The slow way</h1>

<p>For each element in the result:</p>

<p>Compute <code class="language-plaintext highlighter-rouge">r0</code> using:<code class="language-plaintext highlighter-rouge">r0 := (1-s) * a0 + s * b0</code></p>

<p><strong>Why does this work?</strong></p>

<ul>
  <li>When <code class="language-plaintext highlighter-rouge">s = 0</code>:<code class="language-plaintext highlighter-rouge">r0 := (1 - 0) * a0 + 0 * b0 = a0</code></li>
  <li>When <code class="language-plaintext highlighter-rouge">s = 1</code>:<code class="language-plaintext highlighter-rouge">r0 := 0 * a0 + 1 * b0 = b0</code></li>
</ul>

<p>Similarly, compute <code class="language-plaintext highlighter-rouge">r1</code> using:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">r1 := (1-s) * a1 + s * b1</code></li>
</ul>

<p>This computation is sometimes called “switching” between a and b.</p>

<p>This approach works but can be <strong>slow</strong>, as it requires repeating the computation for each element. With an array of length two, this is not so bad. But as the arrays get longer and longer, the repeated computation can take a toll.</p>

<h1 id="bad-optimization">Bad optimization</h1>

<p>Instead of directly switching, the optimization uses an <strong>encoding function</strong>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">encode([a0, a1]) = a0 + 2 * a1</code></li>
  <li><code class="language-plaintext highlighter-rouge">encode([b0, b1]) = b0 + 2 * b1</code></li>
</ul>

<p>This encoding maps the arrays uniquely:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[0, 0] → 0 + 2 * 0 = 0</code></li>
  <li><code class="language-plaintext highlighter-rouge">[0, 1] → 0 + 2 * 1 = 2</code></li>
  <li><code class="language-plaintext highlighter-rouge">[1, 0] → 1 + 2 * 0 = 1</code></li>
  <li><code class="language-plaintext highlighter-rouge">[1, 1] → 1 + 2 * 1 = 3</code></li>
</ul>

<p>This seems fine so far, as the mappings are unique.</p>

<h1 id="optimization-process">Optimization process</h1>

<ol>
  <li><strong>Compression:</strong> Compress both options into single values:</li>
</ol>

<p>cA := a0 + 2 * a1<br />
cB := b0 + 2 * b1</p>

<ol>
  <li><strong>Switch:</strong> Use the selector <code class="language-plaintext highlighter-rouge">s</code> to choose between the compressed values:</li>
</ol>

<p>cR := (1-s) * cA + s * cB</p>

<ol>
  <li><strong>Decompression:</strong> Generate <code class="language-plaintext highlighter-rouge">r0, r1</code> from the witness generator and add a constraint to ensure the decompressed values match:</li>
</ol>

<p>r0 + 2 * r1 = cR</p>

<h1 id="example-exploit">Example exploit</h1>

<p>The issue lies in the <strong>decompression</strong> step. The witness generator is not constrained to produce valid boolean values (0 or 1) for <code class="language-plaintext highlighter-rouge">r0</code> and <code class="language-plaintext highlighter-rouge">r1</code>.</p>

<p>This means that instead of <code class="language-plaintext highlighter-rouge">[r0, r1]</code> being restricted to <code class="language-plaintext highlighter-rouge">[0, 1]</code>, they can take arbitrary values.</p>

<p>An attacker could choose <code class="language-plaintext highlighter-rouge">r0 = 1,000,000</code> and <code class="language-plaintext highlighter-rouge">r1 = -500,000</code>. Let’s compute the compressed value:</p>

<p>cR = r0 + 2 * r1 = 1,000,000 + 2 * (-500,000) = 1,000,000 - 1,000,000 = 0</p>

<p>This satisfies the constraint <code class="language-plaintext highlighter-rouge">r0 + 2 * r1 = cR</code>, but clearly, the values <code class="language-plaintext highlighter-rouge">[r0, r1]</code> do not represent valid boolean values.</p>

<h1 id="root-cause">Root cause</h1>

<p>During compression, the original arrays <code class="language-plaintext highlighter-rouge">[a0, a1]</code> and <code class="language-plaintext highlighter-rouge">[b0, b1]</code> were constrained to boolean values (0 or 1).</p>

<p>However, during decompression, the original code failed to enforce these constraints, allowing the witness generator to produce invalid values.</p>

<p>This lack of constraints makes the encoding <strong>non-injective</strong>, meaning the decompressed values can correspond to multiple possible outputs, enabling attackers to exploit the system.</p>

<h1 id="impact-and-the-fix-2">Impact and the fix</h1>

<p>The vulnerability arises because the decompression step lacks proper constraints, breaking the injective property of the encoding-decoding process. To fix this, the system must enforce that <code class="language-plaintext highlighter-rouge">r0</code> and <code class="language-plaintext highlighter-rouge">r1</code> remain within their valid range (0 or 1) during decompression.</p>

<h1 id="final-remarks">Final remarks</h1>

<p>o1js has a very active set of contributors and is very interested in security. They have worked hard to bring ZK technology to TypeScript developers, and overcome a set of unique challenges in bringing smart contract logic to the Mina blockchain.</p>

<p>We found out it remarkably straightforward to prototype proof-of-concept applications using the TypeScript library. This ease of use extends to identifying under-constrained bugs and testing circuits in ways that developers might not have anticipated.</p>

<p>If you’re planning to develop dapps on the Mina blockchain using TypeScript, stay tuned — our upcoming blog post will provide insights and best practices for secure development, drawing from our auditing experience.</p>

<h1 id="full-audit-report">Full audit report</h1>

<p>Download the full o1js <a href="https://veridise.com/audits-archive/company/o1-labs/o1-labs-o1js-2024-08-27/">security audit report here.</a></p>

<p><strong>Author</strong>:<br />
Ben Sepanski, Chief Security Officer at Veridise</p>

<p>Editor: Mikko Ikola, VP of Marketing</p>]]></content><author><name>Benjamin Sepanski</name><email>ben.sepanski@veridise.com</email></author><category term="Mina" /><category term="ZK" /><category term="o1js" /><category term="audit" /><summary type="html"><![CDATA[For the original (and properly formatted) publication, see Veridise’s post on medium.]]></summary></entry><entry><title type="html">Deploying and Debugging Remotely with Intellij</title><link href="https://bensepanski.github.io/posts/2021/10/13/Deploying-and-Debugging-Remotely-with-Intellij/" rel="alternate" type="text/html" title="Deploying and Debugging Remotely with Intellij" /><published>2021-10-13T00:00:00+00:00</published><updated>2021-10-13T00:00:00+00:00</updated><id>https://bensepanski.github.io/posts/2021/10/13/Deploying-and-Debugging-Remotely-with-Intellij</id><content type="html" xml:base="https://bensepanski.github.io/posts/2021/10/13/Deploying-and-Debugging-Remotely-with-Intellij/"><![CDATA[<p>In this blog post I will show you how to sync an <a href="https://www.jetbrains.com/idea/">Intellij IDEA</a> project with a remote server (called <em>deploying</em> the project to a server), and how to debug remote runs of that project.</p>

<p><strong><em>EDIT Jan 3, 2022</em></strong><em>: Note that this process is only available for the Intellij IDEA Ultimate edition, not the Community edition.</em></p>

<h2 id="related-guides">Related Guides</h2>

<p>Intellij has its own guides on these topics, check them out here:</p>
<ul>
  <li>Intellij <a href="https://www.jetbrains.com/help/idea/tutorial-deployment-in-product.html#before">Deployment Guide</a></li>
  <li>Intellij <a href="https://www.jetbrains.com/help/idea/tutorial-remote-debug.html">Remote Debug Guide</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p><a href="https://www.jetbrains.com/idea/">Intellij IDEA</a> is an incredibly powerful IDE. If you’re anything like me, it’s become an essential component of any Java program you write. However, compiling and running large applications on my laptop gets frustratingly slow. Since I have access to bigger and better machines, I want to compile and run on those remote servers. However, I need several things before this actually improves my workflow:</p>

<ol>
  <li>Keeping the local and remote in sync should be easy <em>without</em> using git</li>
  <li>Debugging applications running on the remote should be push-button</li>
  <li>All file-editing should be performed locally (I want the power of Intellij without having to set up X-forwarding, etc.)</li>
</ol>

<p>(1) and (3) can be achieving using <a href="#deployment"><em>deployment</em></a>: setting up a remote clone of a project that Intellij syncs in the background. (2) can be achieved using <a href="#remote-debug">remote debug</a>. 
In the rest of the blog, I’ll show you how to set this up using an example project.</p>

<h2 id="setup">Setup</h2>

<p>I’m running Intellij 2021.2.1. Any recent version of Intellij should work. You’ll need to first set up SSH for your remote server, which I’ll call <code class="language-plaintext highlighter-rouge">remote</code>. For instance, you should be able to successfully run</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh remoteUserName@remote 
</code></pre></div></div>
<p>I’ll assume that your SSH key is located in <code class="language-plaintext highlighter-rouge">~/.ssh/id_rsa</code>. Password-based authentication is a similar process.</p>

<p>For this example, we’ll start by making a new Java project using the <a href="https://maven.apache.org/">Maven</a> build system.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/01-Intellij-Create-Project.png" alt="" /></p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/02-Maven.png" alt="" /></p>

<p>Now we’re ready to set up for deployment!</p>

<h2 id="deployment">Deployment</h2>

<p>First, open the deployment configuration by going to <code class="language-plaintext highlighter-rouge">Tools</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Deployment</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Configuration</code></p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/03-Configuration-Menu.png" alt="" /></p>

<p>Click <code class="language-plaintext highlighter-rouge">+</code>, and add an SFTP server. Choose whatever name you want, it doesn’t matter.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/04-Add-SFTP.png" alt="" /></p>

<p>If you already have an SSH configuration setup on Intellij for your desired server, go ahead and select it. Otherwise, let’s set one up!
Click the three dots next to SSH configuration to get started:</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/05-Three-Dots.png" alt="" /></p>

<p>Enter the host, your remote username, and select your authentication type. I’m going to assume you’re using a password-protected private key in <code class="language-plaintext highlighter-rouge">~/.ssh/id_rsa</code>. Only change the port from 22 (or set the local port) if you know what you’re doing!</p>

<p>Once you’re done, press “Test Connection” to make sure it works.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/06-SSH-Config.png" alt="" /></p>

<p>You can set the “root” directory if you wish. This sets what Intellij perceives as the root
directory of the remote server (not the root directory of your remote project, we’ll set that later).
If you do set the root, just remember that the file mappings are relative to the root you set.</p>

<p>Once you’re done, press OK and make sure your remote is in bold on the left menu. If it is not, select it and press the check mark to make it the default configuration.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/07-Set-Default.png" alt="" /></p>

<p>Finally, we need to set up the file mappings. 
On your remote, pick some path where you want your remote to be stored. I’m going to use <code class="language-plaintext highlighter-rouge">~/intellijRemotes/&lt;projectName&gt;</code>.
Create that directory.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>myRemoteUserName@remote&gt; <span class="nb">mkdir</span> ~/intellijRemotes/IntellijRemoteExample
</code></pre></div></div>

<p>Click on “Mappings”, and copy the path to the deployed project on your remote.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/08-Mapping.png" alt="" /></p>

<p>Press OK, and now you’re good to go!
What exactly does that mean?</p>

<ul>
  <li>Any file you save locally will be automatically uploaded to your remote.</li>
  <li>You can upload, download, or sync any file or directory in you project by
    <ol>
      <li>Right-clicking the file or directory</li>
      <li>Clicking “deployment”</li>
      <li>Selecting either upload, download, or sync</li>
    </ol>
  </li>
</ul>

<p>Look over some options by going to <code class="language-plaintext highlighter-rouge">Tool</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Deployment</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Options</code></p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/09-Options.png" alt="" /></p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Delete target items when source ones do not exist
    <ul>
      <li>This is useful to avoid confusing errors where you’ve deleted a file locally, but Intellij does not delete the remote copy. I’d recommend setting this as long as you’re not using the remote to backup files.</li>
    </ul>
  </li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Create empty directories
    <ul>
      <li>Have Intellij create empty directories when you upload. This is helpful if you’re outputting things to an empty directory and want it to be created on the remote when you create it locally.</li>
    </ul>
  </li>
</ul>

<p>You can also exclude items by names/patterns at this menu. Another place you can exclude specific paths for specific remotes is by clicking <code class="language-plaintext highlighter-rouge">Tools</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Deployment</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Configuration</code> and selecting “Excluded Paths”.</p>

<p>Note that you can create multiple remotes by repeating this process! Intellij only automatically uploads changes to the default. All other uploads, downloads, and syncs have to be manual.</p>

<h2 id="remote-debug">Remote Debug</h2>

<p>Intellij’s debugger is one of its most powerful features. There’s no reason you should lose that just because you want to run your program remotely.</p>

<p>First, we’re going to build a <a href="https://www.jetbrains.com/help/idea/run-debug-configuration.html">configuration</a> that will help us connect to our remote application. Start by clicking “Add configuration.”</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/10-Configuration.png" alt="" /></p>

<p>Click the <code class="language-plaintext highlighter-rouge">+</code> on the top left, and select “Remote JVM Debug”.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/11-Remote-JVM-Debug.png" alt="" /></p>

<p>Name the configuration whatever you want. Enter the host name and whatever port you want to use to connect. If you have several maven projects/sub-projects, make sure to select the correct module classpath!</p>

<p>I usually use port 8000, but all that matters is that TCP connections can be made from your local IP address to your remote at that port. If you have issues, you can use <a href="https://www.acronis.com/en-us/articles/telnet/">this guide</a> to figure out which ports are open.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/12-Debug-Setup.png" alt="" /></p>

<p>Next, you’ll want to copy the “Command line arguments for remote JVM.” You’re going to need these arguments later.</p>

<p><img src="/files/posts/2021-10-13-Deploying-and-Debugging-Remotely-with-Intellij/13-JVM-Args.png" alt="" /></p>

<p>Once you’re done, press “OK”.</p>

<p>Now, SSH into your remote, and run your application using the arguments you copied.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> myUserName@localhost&gt; ssh myRemoteUserName@remote
 myRemoteUserName@Remote&gt; java \
     -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 \
     -jar myApp.jar
</code></pre></div></div>
<p>You should see this as output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Listening for transport dt_socket at address: 8000
</code></pre></div></div>
<p>Now go back to the local Intellij instance and run the configuration you just created! It should connect and start debugging like normal.</p>

<p>Note that all terminal output from your application will appear on your remote terminal, not on the local Intellij terminal. However, the Debugger tab will work as usual.</p>

<h2 id="common-pitfalls">Common Pitfalls</h2>

<p>I hope this tutorial was helpful for you! Before I let you go, I’d like to warn you of a couple pitfalls that I commonly ran into when I first started using this setup.</p>

<ol>
  <li>Intellij only syncs changes made by saving/deleting files. If you switch git branches, you’ll need to manually sync with the remote.</li>
  <li>Sometimes, the Intellij remote debug application continues running after the remote application has stopped. Make sure to manually stop it locally to avoid processes clogging up your remote machine. If necessary, use <code class="language-plaintext highlighter-rouge">htop</code> on the remote to check for and kill these processes.</li>
  <li>If you use multiple remote deployments, only one can be the default at a time. You’ll have to manually sync other changes.</li>
  <li>If you click <code class="language-plaintext highlighter-rouge">Tools</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Deployment</code> <code class="language-plaintext highlighter-rouge">&gt;</code> <code class="language-plaintext highlighter-rouge">Upload to ...</code> (resp. <code class="language-plaintext highlighter-rouge">Download</code>, <code class="language-plaintext highlighter-rouge">Sync</code>) it will only <code class="language-plaintext highlighter-rouge">Upload</code> (resp. <code class="language-plaintext highlighter-rouge">Download</code>, <code class="language-plaintext highlighter-rouge">Sync</code>) the file which is currently open. To <code class="language-plaintext highlighter-rouge">Upload</code> (resp. <code class="language-plaintext highlighter-rouge">Download</code>, <code class="language-plaintext highlighter-rouge">Sync</code>) the entire project, you need to right-click on the directory from the <code class="language-plaintext highlighter-rouge">Project</code> tab.</li>
</ol>]]></content><author><name>Benjamin Sepanski</name><email>ben.sepanski@veridise.com</email></author><category term="Intellij" /><category term="Debug" /><category term="Deploy" /><category term="Remote" /><category term="Sync" /><summary type="html"><![CDATA[In this blog post I will show you how to sync an Intellij IDEA project with a remote server (called deploying the project to a server), and how to debug remote runs of that project.]]></summary></entry><entry><title type="html">Denali: A Goal-directed Superoptimizer</title><link href="https://bensepanski.github.io/posts/2021/03/10/Denali-a-goal-directed-superoptimizer/" rel="alternate" type="text/html" title="Denali: A Goal-directed Superoptimizer" /><published>2021-03-10T00:00:00+00:00</published><updated>2021-03-10T00:00:00+00:00</updated><id>https://bensepanski.github.io/posts/2021/03/10/Denali-a-goal-directed-superoptimizer</id><content type="html" xml:base="https://bensepanski.github.io/posts/2021/03/10/Denali-a-goal-directed-superoptimizer/"><![CDATA[<p>This blog post was written for <a href="https://www.cs.utexas.edu/~bornholt/">Dr. James Bornholt</a>’s <a href="https://www.cs.utexas.edu/~bornholt/courses/cs395t-21sp/">CS 395T: Systems Verification and Synthesis, Spring 2021</a>. It summarizes the context and contributions of the paper <a href="https://dl.acm.org/doi/10.1145/543552.512566">Denali: A Goal-directed Superoptimizer</a>, written by <a href="https://rjoshi.org/bio/index.html">Dr. Rajeev Joshi</a>, <a href="https://en.wikipedia.org/wiki/Greg_Nelson_(computer_scientist)">Dr. Greg Nelson</a>, and <a href="http://people.csail.mit.edu/randall/">Dr. Keith Randall</a>.</p>

<p>None of the technical ideas discussed in this blog are my own, they are summaries/explanations based on the referenced works.</p>

<h1 id="denali-a-goal-directed-superoptimizer">Denali: A Goal-directed Superoptimizer</h1>

<p>Tdoday’s paper is <a href="https://dl.acm.org/doi/10.1145/543552.512566">Denali: A Goal-directed Superoptimizer</a>. At the time of its publication (2002), it was one of the first <strong>superoptimizer</strong>s: a code generator which seeks to find truly optimal code. This is a dramatically different approach from traditional compiler optimizations, and is usually specific to efficiency-critical straight-line kernels written at the assembly level.</p>

<h2 id="background">Background</h2>

<h3 id="what-is-superoptimization">What <em>is</em> Super🦸optimization?</h3>

<p>Plenty of compilers are <em>optimizing compilers</em>. However, in the strictest sense of the word, they don’t really find an <em>optimal</em> translation. They just find one that, according to some heuristics, ought to improve upon a naive translation. Why? Finding optimal translations is, in general, undecidable. Even for simplified, decidable versions of the problem, it is prohibitively time consuming to insert into any mortal programmer’s build-run-debug development cycle.</p>

<p>However, sometimes it is worth the effort to find a <em>truly optimal</em> solution. To disambiguate between these two “optimization” procedures, we use the term <em>superoptimization</em> when we are seeking a “truly optimal” solution. Superoptimization is an offline procedure and typically targets straight-line sequences of machine code inside critical loops.</p>

<p>With a few simplifying assumptions, the shortest straight-line code is the fastest. Consequently, we seek the shortest program.</p>

<h3 id="superoptimization-the-pre-denali-era-beginning-of-time--2002">Super🦸optimization: The Pre-Denali Era (Beginning of Time – 2002)</h3>

<p><a href="https://en.wikipedia.org/wiki/Alexia_Massalin">Alexia Massalin</a> coined the term “<a href="https://en.wikipedia.org/wiki/Superoptimization">superoptimization</a>” in her 1987 paper <a href="https://dl.acm.org/doi/10.1145/36177.36194">Superoptimizer – A look at the Smallest Program</a>. Massalin used a (pruned) exhaustive search to find the shortest implementation of various straight line computations in the <a href="https://en.wikipedia.org/wiki/Motorola_68000#Instruction_set_details">68000 instruction set</a>. For instance, she found the shortest programs to compute the signum function, absolute value, max/min, and others. Her goal was to identify unintuitive idioms in these shortest programs so that performance engineers could use them in practice.</p>

<p>While Massalin’s technique was powerful, it did not scale well (the shortest programs were at most 13 instructions long in Massalin’s paper). Moreover, the output programs were not automatically verified to be equivalent to the input programs. They are instead highly likely to be equivalent, and must be verified by hand.</p>

<p>Granlund \&amp; Kenner <a href="https://dl.acm.org/doi/10.1145/143103.143146">followed up on Massalin’s work</a> in 1992 with the <a href="https://www.gnu.org/software/superopt/">GNU Superoptimizer</a>. They integrated a variation of Massalin’s superoptimizer into GCC to eliminate branching.</p>

<p>Until 2002, research in superoptimizers seemed to stall. Judging by citations during that period, most researchers considered Massalin’s work to fit inside the field of optimizing compilers. These researchers viewed superoptimization as a useful engineering tool, but of little theoretical interest or scalability. Rather, superoptimization was seen as an interesting application of brute-force search. Massalin and the GNU Superoptimizer seemed to become a token citation in the optimizing compiler literature.</p>

<h2 id="the-denali-approach">The Denali Approach</h2>

<h3 id="goal-directed-vs-brute-force">Goal-Directed vs. Brute Force</h3>

<p>Massalin’s superoptimizer relies on brute-force search: enumerate candidate programs until you find the desired program. Given the massive size of any modern instruction set, this does not scale well. However, since we want the shortest program, we have to rely on some kind of brute-force search. Denali’s insight is that Massalin’s search algorithm was enumerating <em>all</em> candidate programs, instead of only enumerating <em>relevant</em> candidate programs.</p>

<p>Denali users specify their desired program as a set of (memory location, expression to evaluate) pairs.  For instance, (<em>%rdi</em>, <em>2 * %rdi</em>) is the program which doubles the value of <em>%rdi</em>.</p>

<p>Denali’s algorithm only “enumerates” candidate programs which it can prove are equivalent to the desired program. For efficiency, it stores this enumeration in a compact graph structure called an <a href="#e-graphs">E-Graph</a>, then searches the E-Graph using a SAT solver.</p>

<h3 id="e-graphs">E-Graphs</h3>

<h4 id="what-is-an-e-graph">What is an E-Graph?</h4>

<p>An E-Graph is used to represent expressions. For instance, a literal 4 or a register value <em>%rdi</em> is represented as a node with no children.</p>

<p><img src="/files/posts/2021-03-10-Denali-a-goal-directed-superoptimizer/egraph-rdi-and-4.svg?token=AIEHSZA5TESJ3BC6LBD6FELAGFZ3O" alt="" /></p>

<p>The expression <em>%rdi * 4</em> is represented as a node ‘<em>*</em>’ whose first child represents <em>%rdi</em> and whose second child represents 4.</p>

<p><img src="/files/posts/2021-03-10-Denali-a-goal-directed-superoptimizer/egraph-rdi-times-4.svg?token=AIEHSZACZTJLTY7GVZSQQUDAGF2EM" alt="" /></p>

<p>Bigger expressions are represented just like you would think. For instance, the expression <em>%rdi * 4 + 1</em> would be represented as</p>

<p><img src="/files/posts/2021-03-10-Denali-a-goal-directed-superoptimizer/egraph-rdi-times-4-plus-1.svg?token=AIEHSZH4LPUKAMS5CIODF5TAGF3AO" alt="" /></p>

<p>So far, this just looks like an <a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">Abstract Syntax Tree</a>. E-Graphs are distinguished from ASTs by the ability to represent <strong>multiple equivalent expressions</strong>. Suppose we wish to add the equivalence 4<span style="color:blue">=</span>2**2 to our E-graph. We do this by adding a special <em><span style="color:blue">equivalence edge</span></em></p>

<p><img src="/files/posts/2021-03-10-Denali-a-goal-directed-superoptimizer/egraph-rdi-times-4-plus-1-with-exp.svg?token=AIEHSZB7XJBWPFALUQVHFJDAGF4SC" alt="" /></p>

<p>Since there is no machine exponentiation instruction, this does not look useful at first. However, now we can add a further <span style="color:blue">equivalence edge</span> based on the fact that <em>%rdi « 2 <span style="color:blue">=</span> %rdi * 2**2 <span style="color:blue">=</span> %rdi * * 4</em>.</p>

<p><img src="/files/posts/2021-03-10-Denali-a-goal-directed-superoptimizer/egraph-rdi-times-4-plus1-with-shift.svg?token=AIEHSZBXN3ND7R2LHKI3VYTAGF6EU" alt="" /></p>

<p>Since E-Graphs represent <strong>A=B</strong> by keeping both <strong>A</strong> and <strong>B</strong> around, they can become quite massive.</p>

<h4 id="how-do-we-build-e-graphs">How do we build E-Graphs?</h4>

<p>We can use proof rules to repeatedly grow the E-Graph and/or add <span style="color:blue">equivalence edge</span>s. If we keep applying our proof rules until our graph stops changing, then we’ve deduced all the ways we can provably compute our expression (relative to our proof rules). For instance, in the previous example we had only three proof rules:</p>

<ol>
  <li>4 = 2**2</li>
  <li>x * 2**n = x « n</li>
  <li>If a = b, and b = c, then a = c</li>
</ol>

<p>If we add more proof rules, we may be able to deduce faster ways to compute our expression.</p>

<h4 id="other-uses-of-e-graphs">Other uses of E-Graphs</h4>

<p>An early variant of E-Graphs is described in Greg Nelson’s (one of the Denali authors) <a href="http://scottmcpeak.com/nelson-verification.pdf">Ph.D. Thesis</a>. These were used by Nelson in the automated theorem prover <a href="https://www.hpl.hp.com/techreports/2003/HPL-2003-148.pdf">Simplify</a> for equational reasoning. Since then, search over E-graphs via the <a href="https://people.eecs.berkeley.edu/~necula/Papers/NelsonOppenCong.pdf">congruence closure</a> algorithm is used by many modern SMT solveres for reasoning about equality of uninterpreted functions (it is even taught in <a href="https://www.cs.utexas.edu/~isil/">Dr. Isil Dillig</a>’s <a href="https://www.cs.utexas.edu/~isil/cs389L/lecture11-6up.pdf">CS 389L course</a> here at UT!). For example, the <a href="https://github.com/Z3Prover/z3">Z3 SMT solver</a> <a href="https://github.com/Z3Prover/z3/blob/830f314a3f32ce896fcf93fd40666d4a390fc330/src/ast/euf/euf_enode.h#L30">implements an E-graph</a>, and the <a href="https://github.com/CVC4/CVC4">CVC4 Solver</a> <a href="https://github.com/CVC4/CVC4/blob/e4fd524b02054a3ac9724f184e55a983cb6cb6b9/src/theory/uf/equality_engine.h#L50">implements an incremental congruence closure</a>.</p>

<h3 id="search-over-e-graphs">Search over E-Graphs</h3>

<p>Nodes in an E-Graph that are connected by an <span style="color:blue">equivalence edge</span> represent expressions that are equivalent according to the proof rules. Therefore, we only need to evaluate one of the nodes. Denali can use a SAT solver to figure out the optimal choice of nodes. Their encoding is not too complicated.</p>

<p>The basic idea of the encoding is as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>For each machine instruction node T,
    L(i, T) = { 1    T starts executing on cycle i 
              { 0    otherwise
</code></pre></div></div>
<p>Then, all we have to do is add constraints so that</p>
<ul>
  <li>Exactly one instruction starts executing per cycle.</li>
  <li>Each instruction’s arguments are available when the instruction gets executed.</li>
  <li>Some node equivalent to the root node gets computed.</li>
</ul>

<p>Now we can find the shortest program encoded in our E-Graph by constraining the SAT solver to look for a program of length 1, then length 2, then length 3, …. until we find a solution.</p>

<h2 id="impact-of-the-denali-superoptimizer">Impact of the Denali Superoptimizer</h2>

<h3 id="preliminary-results">Preliminary Results</h3>

<p>The Denali paper presents several preliminary results. For the <a href="https://en.wikipedia.org/wiki/DEC_Alpha#:~:text=Alpha%2C%20originally%20known%20as%20Alpha,set%20computer%20(CISC)%20ISA.">Alpha instruction set architecture</a>, they are able to generate some programs of length up to 31 instructions. For comparison, the GNU superoptimizer is unable to generate (near) optimal instructions sequences of length greater than 5.</p>

<p>However, in addition to Denali’s built-in architectural axioms, the programmers specify program-specific axioms in their examples. This trades off automation for the ability to generate longer (near) optimal instruction sequences.</p>

<h3 id="superoptimization-the-post-denali-era-2002--present-day">Super🦸optimization: The Post-Denali Era (2002 – Present Day)</h3>

<p>Denali demonstrated that, for small programs, it is possible to generate provably equivalent, (nearly) optimal code. Since then, there has been a lot of interest in superoptimization. Here are some projects/papers that have popped up since Denali.</p>

<ul>
  <li><a href="https://arxiv.org/pdf/1711.04422.pdf">Souper</a> is an <a href="https://github.com/google/souper">open-source</a> project that extracts straight-line code from the LLVM IR and applies superoptimization. It uses caching so that it can be run online (2017).
    <ul>
      <li>SMT-based goal-directed search</li>
      <li>Maintained by <a href="https://www.google.com">Google</a>, jointly developed by researchers at Google, <a href="https://www.nvidia.com/en-us/">NVIDIA</a>, <a href="https://www.tno.nl/en/">TNO</a>, <a href="https://www.microsoft.com/en-us/">Microsoft</a>, <a href="https://www.ses.com/">SES</a>, and the University of Utah.</li>
    </ul>
  </li>
  <li><a href="https://github.com/KTH/slumps">slumps</a> is based on souper and targets web assembly (2020).</li>
  <li><a href="http://stoke.stanford.edu/">STOKE</a> is a superoptimizer for x86 (2013) (which we will be <a href="https://www.cs.utexas.edu/~bornholt/courses/cs395t-21sp/schedule/">reading on March 22</a> after spring break)
    <ul>
      <li>Uses stochastic enumerative search.</li>
      <li>Is still maintained and open source at <a href="https://github.com/StanfordPL/stoke">StanfordPL/stoke</a>.</li>
    </ul>
  </li>
  <li><a href="https://www.embecosm.com/about/">embecosm</a>, a compiler research group, is developing <a href="https://www.embecosm.com/services/superoptimization/#Related-Services">GSO 2.0</a> (2015)</li>
  <li>There has been research into <a href="https://theory.stanford.edu/~aiken/publications/papers/asplos06.pdf">automatic peephole optimizations</a> (2006).</li>
</ul>

<p>However, while there is active industry and research interest in the <em>problem</em> that Denali presented (finding a provably equivalent, near-optimal translation), most modern approaches (e.g. souper) rely on SMT-based synthesis techniques. Denali’s methods of superoptimization seem to have largely fallen by the wayside. Part of this is because Denali’s provably (almost) optimal program relies on a set of user-specified axioms, and is only optimal with respect to those axioms. Part of the appeal of an SMT solver is standardized theories for certain objects and operations.</p>

<p>Both enumerative search (e.g. STOKE) and goal-directed search (e.g. souper) are used today. In addition, Denali’s general notion of specification (a set of symbolic input-output pairs) is still used, with various project-specific modifications. Projects still rely on (often hand-written) heuristics to measure the cost/cycle-count of candidate programs.</p>

<h1 id="discussion-questions">Discussion Questions</h1>

<ul>
  <li>As computation times are increasingly memory-bound, does superoptimization run into concerns with Amdahl’s law?</li>
  <li>SMT solvers are powerful tools, but incredibly general-purpose. What types of computations are likely to be compute-bound, and can we use that domain-specific knowledge to make superoptimization faster?</li>
  <li>Superoptimization seems naturally related to <a href="https://en.wikipedia.org/wiki/Automatic_vectorization">automated vectorization</a>. However, people seem to treat the two problems as separate. Is there any reason automated vectorization might make superoptimization much more difficult?</li>
  <li><a href="https://github.com/flame/blis">BLIS</a> is a framework for creating <a href="http://www.netlib.org/blas/">BLAS</a> implementations for new architectures by only implementing small, finite computations kernels. Can BLIS be combined with superoptimization to automatically generate BLAS libraries for specific architectures?</li>
</ul>

<h1 id="references">References</h1>

<p>All graphs were built using <a href="https://graphviz.org/">graphviz</a>. The example E-Graph in section <a href="#What-is-an-E-Graph">What is an E-Graph?</a> is based on the example in today’s paper.</p>]]></content><author><name>Benjamin Sepanski</name><email>ben.sepanski@veridise.com</email></author><category term="superoptimization" /><category term="E-graphs" /><category term="Denali" /><summary type="html"><![CDATA[This blog post was written for Dr. James Bornholt’s CS 395T: Systems Verification and Synthesis, Spring 2021. It summarizes the context and contributions of the paper Denali: A Goal-directed Superoptimizer, written by Dr. Rajeev Joshi, Dr. Greg Nelson, and Dr. Keith Randall.]]></summary></entry></feed>