<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2024-12-19T15:16:57+01:00</updated><id>/feed.xml</id><title type="html">Data Driven Software Systems Research @ Nokia Bell Labs</title><subtitle>The blog of the Data Driven Software Systems Research group at Nokia Bell Labs.</subtitle><entry><title type="html">zkGadgets: fast, re-usable components for common operations in ZKP</title><link href="/2024/07/16/zkgadgets.html" rel="alternate" type="text/html" title="zkGadgets: fast, re-usable components for common operations in ZKP" /><published>2024-07-16T00:00:00+02:00</published><updated>2024-07-16T00:00:00+02:00</updated><id>/2024/07/16/zkgadgets</id><content type="html" xml:base="/2024/07/16/zkgadgets.html"><![CDATA[<p>In <a href="/2024/03/28/challenges-of-zkp.html">a previous post</a>, we looked at the two major challenges developers encounter when developing Zero-Knowledge Proofs: performance and programmability. In this post, we will tackle this by developing a library of “zkGadgets” for Zokrates: a library of common aggregation operations, implemented efficiently for use in ZKPs. <a href="https://github.com/Nokia-Bell-Labs/zkstream/tree/master/zkgadgets">The gadgets are open sourced on GitHub</a>.</p>
<h2 id="what-is-a-zkgadget">What is a zkGadget?</h2>
<p>A <strong>gadget</strong> is a small, specialized, and efficient “proof component” that can be composed and re-used as part of a larger proof. This is inspired by the definition of gadgets by <a href="https://eprint.iacr.org/2019/142">Campanelli et al.</a>.</p>
<p>In our case, a gadget consists of:</p>
<ul>
<li>some computation that runs in the proof,</li>
<li>some computation that happens before the proof (e.g. to pre-compute a value),</li>
<li>some computation outsourced to the verifier (e.g. to verify some condition).</li>
</ul>
<p>Below, we will illustrate this using two examples: calculating the median and the standard deviation.</p>
<h3 id="median-sorting-outside-proof">Median: sorting outside proof</h3>
<p>As a first example, imagine that we want to calculate a median in a ZKP. The prover wants to keep the full input list secret, and only wants to expose the median; the verifier on the other hand needs to be sure the median was calculated correctly. Hence, such a program will typically commit to the input data using a hash and then proceed to calculate the median.</p>
<p>Sorting in the ZKP is prohibitively expensive, as it requires a comparison network. Instead, we developed a gadget that pre-sorts the median outside the proof and just checks whether it was sorted correctly in the proof. Thus, the code in the proof looks like this:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">median</span><span class="o">&lt;</span><span class="n">N</span><span class="o">&gt;</span><span class="p">(</span><span class="n">u64</span><span class="p">[</span><span class="n">N</span><span class="p">]</span> <span class="n">vals</span><span class="p">,</span> <span class="n">u32</span> <span class="n">n</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">u64</span> <span class="p">{</span>
  <span class="k">for</span> <span class="n">u32</span> <span class="n">i</span> <span class="ow">in</span> <span class="mf">1.</span><span class="p">.</span><span class="n">N</span> <span class="p">{</span>
    <span class="k">assert</span><span class="p">(</span><span class="n">vals</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">&lt;=</span> <span class="n">vals</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
  <span class="p">}</span>
  <span class="n">u32</span> <span class="n">i</span> <span class="o">=</span> <span class="n">n</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
  <span class="k">return</span> <span class="p">(</span><span class="n">n</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="err">?</span> <span class="p">(</span><span class="n">vals</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">vals</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">/</span> <span class="mi">2</span> <span class="p">:</span> <span class="n">vals</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This function checks that the input is sorted and then returns the middle result. Note that it is also necessary for the verifier to check whether all values were included (e.g. using message IDs).</p>
<p>We also use the technique of pre-sorting the list to implement functions that take the minimum, maximum, percentiles, etc. We also have a function that returns all distinct elements in a list that relies on the same technique.</p>
<h3 id="standard-deviation-calculating-a-square-root">Standard deviation: calculating a square root</h3>
<p>Another useful function is the standard deviation. This requires calculating the variance and taking the square root. However, calculating a square root in a ZKP is expensive. It is much cheaper to calculate it outside the proof and verify its correctness in the proof, which only requires a multiplication.</p>
<p>The function to calculate the standard deviation then looks like this:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">stddev</span><span class="o">&lt;</span><span class="n">N</span><span class="o">&gt;</span><span class="p">(</span><span class="n">u64</span><span class="p">[</span><span class="n">N</span><span class="p">]</span> <span class="n">vals</span><span class="p">,</span> <span class="n">u64</span> <span class="n">stddev</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">u64</span> <span class="p">{</span>
  <span class="n">u64</span> <span class="n">var</span> <span class="o">=</span> <span class="n">variance</span><span class="p">(</span><span class="n">vals</span><span class="p">);</span>
  <span class="k">assert</span><span class="p">(</span><span class="n">stddev</span> <span class="o">*</span> <span class="n">stddev</span> <span class="o">&lt;=</span> <span class="n">var</span><span class="p">);</span>
  <span class="k">assert</span><span class="p">((</span><span class="n">stddev</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">stddev</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">var</span><span class="p">);</span>
  <span class="k">return</span> <span class="n">stddev</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The prover must pass in the values as well as the standard deviation that was pre-computed. The function then calculates the variance and checks that the pre-computed standard deviation is correct. The standard deviation may be passed in as a private input.</p>
<p>Note that this requires the prover to calculate the variance twice: once outside the proof to then take the square root to get the standard deviation, and again in the proof to prove that the root of the correct value was calculated. Even though the variance is calculated twice, this is still more efficient than calculating a square root in the proof.</p>
<h2 id="gadget-library">Gadget library</h2>
<p>We have developed a library of such gadgets, summarized in the following table. We support all aggregation functions supported by Apache Flink (used by AWS), except those that work on strings or JSON, as well as a few additional functions (median, top/bottom N, any/every).</p>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Type signature</th>
<th><a href="https://cloud.google.com/dataflow/docs/reference/sql/aggregate_functions">Google</a></th>
<th><a href="https://learn.microsoft.com/en-us/stream-analytics-query/aggregate-functions-azure-stream-analytics">Azure</a></th>
<th><a href="https://nightlies.apache.org/flink/flink-docs-release-1.18/docs/dev/table/functions/systemfunctions/#aggregate-functions">Flink</a></th>
<th>Complexity</th>
</tr>
</thead>
<tbody>
<tr>
<td>Count</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>$O(1)$</td>
</tr>
<tr>
<td>Count distinct</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Collect distinct</td>
<td><code>[]u64 -&gt; []u64</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Sum</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Max, min</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Average</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Variance (population, sample)</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Std dev (population, sample)</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Median</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Top N, bottom N</td>
<td><code>[]u64 -&gt; []u64</code></td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Top distinct N, bottom distinct N</td>
<td><code>[]u64 -&gt; []u64</code></td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Rank</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Dense rank</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Percent rank</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Cume dist</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Row number</td>
<td><code>[]u64 -&gt; u32</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Ntile</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Percentile</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>First, last</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>$O(1)$</td>
</tr>
<tr>
<td>Lead, lag</td>
<td><code>[]u64 -&gt; u64</code></td>
<td>❌</td>
<td>❌/✅</td>
<td>✅</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Any, every</td>
<td><code>[]bool -&gt; bool</code></td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>Bitwise AND, OR, XOR</td>
<td><code>[]bitmap -&gt; bitmap</code></td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>$O(n)$</td>
</tr>
<tr>
<td>❌ <del>List agg</del></td>
<td><code>([]string, string) -&gt; string</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>❌ <del>JSON object agg</del></td>
<td><code>[](string, T) -&gt; JSON</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>❌ <del>JSON array agg</del></td>
<td><code>[]T -&gt; JSON</code></td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td></td>
</tr>
</tbody>
</table>
<p>Our library relies on a few tricks:</p>
<ul>
<li><strong>Zero-padded</strong> lists, because they must have a static size.</li>
<li><strong>Sorting</strong> outside proof for median and many others.</li>
<li>As there is no while loop, we always loop over the whole list and only update when needed.</li>
<li>The <strong>square root</strong> (e.g. std dev) is calculated outside the proof and checked in proof.</li>
<li>As ZoKrates only supports <strong>integers</strong>, we use some tricks like delaying divisions until the end (variance) or multiplying percentiles by 100.</li>
</ul>
<h2 id="limitations">Limitations</h2>
<p>There are a few limitations to our approach:</p>
<ul>
<li>We leak some additional data, for instance the order of the values of messages. (If messages have a public ID and a secret value, and they are pre-sorted, the IDs are leaked in the order of the values.) This may not be obvious to the user.</li>
<li>Even though we implement a bunch of statistical functions, they only work on integers as ZoKrates has <strong>no support for floating-point numbers</strong>.</li>
<li>Our current implementation is for ZoKrates. RISC-Zero, Nexus, and Jolt are (more recent) platforms for ZKPs that allows proofs to be implemented Rust and rely on different proof techniques. These support floating-point operations and have quite different performance characteristics. Some of our optimizations will still work in this setting (e.g. pre-sorting), but there may also be new opportunities.</li>
</ul>
<p>Furthermore, in the future we could add some additional gadgets for common streaming operations. In particular <strong>parsing JSON</strong> (or Protobuf or other data formats) would be useful. We could build a small compiler that, from a schema of the message, automatically builds a proof that checks that the message is well-formed and extracts the relevant fields.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In the end, using these gadgets a developer can build an application in ZK, by combining the gadgets that are implemented in an efficient and secure way, and without needing to re-invent the wheel every time.</p>
<p>The gadgets are <a href="https://github.com/Nokia-Bell-Labs/zkstream/tree/master/zkgadgets">open sourced on GitHub</a>.</p>
<p>In the longer term, we could also create a compiler that takes a SQL query (or a dataflow script) and compiles it to a ZKP. This would allow developers to write their queries in a simple, high-level language and then have them executed in a ZKP, without needing to know the intricacies of ZKPs but still benefiting from their confidentiality and integrity properties.</p>
<h2 id="references">References</h2>
<div style="font-size: 90%;">
<ul>
<li>Matteo Campanelli, Dario Fiore, and Anaïs Querol. 2019. <em><a href="https://eprint.iacr.org/2019/142">LegoSNARK: Modular Design and Composition of Succinct Zero-Knowledge Proofs</a></em>. In Proceedings of the 2019 ACM SIGSAC Conference on Computer and Communications Security (CCS ’19).</li>
</ul>
</div>]]></content><author><name>janwillem</name></author><category term="zkp" /><summary type="html"><![CDATA[In a previous post, we looked at the two major challenges developers encounter when developing Zero-Knowledge Proofs: performance and programmability. In this post, we will tackle this by developing a library of “zkGadgets” for Zokrates: a library of common aggregation operations, implemented efficiently for use in ZKPs. The gadgets are open sourced on GitHub.]]></summary></entry><entry><title type="html">The Challenges of Developing Zero-Knowledge Proofs</title><link href="/2024/03/28/challenges-of-zkp.html" rel="alternate" type="text/html" title="The Challenges of Developing Zero-Knowledge Proofs" /><published>2024-03-28T00:00:00+01:00</published><updated>2024-03-28T00:00:00+01:00</updated><id>/2024/03/28/challenges-of-zkp</id><content type="html" xml:base="/2024/03/28/challenges-of-zkp.html"><![CDATA[<p>In this post, we’ll look at the challenges developers encounter when developing Zero-Knowledge Proofs: performance and programmability.</p>
<h2 id="zero-knowledge-proofs">Zero-Knowledge Proofs</h2>
<p>Firstly, let’s briefly repeat what Zero-Knowledge Proofs are. A Zero-Knowledge Proof (ZKP) is a cryptographic technique that allows:</p>
<ul>
<li>a prover to convince to a verifier that it executed a computation correctly (<strong>computational integrity</strong>),</li>
<li>while keeping some inputs confidential (<strong>confidentiality</strong>).</li>
</ul>
<p>For a more detailed description of ZKPs, we refer to <a href="https://vitalik.eth.limo/general/2021/01/26/snarks.html">this blog post by Vitalik Buterin explaining the maths behind zk-SNARKs</a>, <a href="https://ethereum.org/en/zero-knowledge-proofs/">this more high-level article explaining what ZKPs are</a>, and <a href="https://www.youtube.com/watch?v=fOGdb1CTu5c">this YouTube video explaining ZKPs at different levels of abstraction</a>.</p>
<p>In the rest of this post, we will be using the <a href="https://zokrates.github.io/">ZoKrates</a> programming language to create ZKPs. This example program proves knowledge of the factors of a (large) number:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">private</span> <span class="n">field</span> <span class="n">a</span><span class="p">,</span> <span class="n">private</span> <span class="n">field</span> <span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">field</span> <span class="p">{</span>
  <span class="k">assert</span><span class="p">(</span><span class="n">a</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">b</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">);</span>
  <span class="k">return</span> <span class="n">a</span> <span class="o">*</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This program takes in two inputs <code>a</code> and <code>b</code>, which are numbers in a finite field (up to ~255-bit). The keyword  <code>private</code> indicates that these inputs are kept confidential. The program first checks that both <code>a</code> and <code>b</code> are greater than 1 and then returns their product.</p>
<p>After this program is executed, a proof is generated that contains the result of the computation, but not the (private) inputs. This proof can be verified to check that the computation was executed correctly, without revealing the inputs.</p>
<blockquote>
<p>When executing the program with the inputs <code>123456789</code> and <code>987654321</code>, the proof ends up looking like this:</p>
<div style="font-size: 75%; line-height: 1.2;">
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"scheme"</span><span class="p">:</span><span class="w"> </span><span class="s2">"g16"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"curve"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bn128"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"proof"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
   </span><span class="nl">"a"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
     </span><span class="s2">"0x2438520bf50f1ef4f0cbd71ad04069c72e4683b88f5f2becb98bbf7c0c101582"</span><span class="p">,</span><span class="w">
     </span><span class="s2">"0x1940ec3fb0df60726e6bc95e196171e826b00453a106a6085d61d4327ea445cb"</span><span class="w">
   </span><span class="p">],</span><span class="w">
   </span><span class="nl">"b"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
     </span><span class="p">[</span><span class="w">
       </span><span class="s2">"0x2403a661a61aa80b86fe5515547e99e9d8139d5f892c9a29b03f0e2eda82677b"</span><span class="p">,</span><span class="w">
       </span><span class="s2">"0x0cc2321418da1284478f2a9397d279eddecfb631965981c934d065f6c058e2f1"</span><span class="w">
     </span><span class="p">],</span><span class="w">
     </span><span class="p">[</span><span class="w">
       </span><span class="s2">"0x2865222599023b1bb0776c8c5eb23b780d0cf2659d4ccb4e511e2df22b9612f2"</span><span class="p">,</span><span class="w">
       </span><span class="s2">"0x195051b7b0cb3aa2577aa3b99376bd44b8424125db2350015331288cdd2be045"</span><span class="w">
     </span><span class="p">]</span><span class="w">
   </span><span class="p">],</span><span class="w">
   </span><span class="nl">"c"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
     </span><span class="s2">"0x05759e3efb49a3dc8c11cf7fdb4cbd152dd64b1d233f8a9da03a03afac532a4d"</span><span class="p">,</span><span class="w">
     </span><span class="s2">"0x286af8c6919b3053acef0fad338b38bc91e92071d6fe3757cc083d79639e7cf6"</span><span class="w">
   </span><span class="p">]</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
   </span><span class="s2">"0x00000000000000000000000000000000000000000000000001b13114fbff5385"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
</div>
<p>Here, the <code>inputs</code> contains the <em>public</em> inputs and outputs, in this case only the product of the two secret factors (121932631112635269 in hex). Together with the other numbers and a verification key, this forms a proof that can be verified by anyone. The proof is tied to this particular program.</p>
</blockquote>
<p>Similarly, the following ZoKrates program proves that the prover knows the pre-image of a SHA256 hash, without revealing the input (relying on the <code>sha256</code> function of ZoKrates’ standard library):</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">private</span> <span class="n">u8</span><span class="p">[</span><span class="mi">256</span><span class="p">]</span> <span class="nb">input</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">u32</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">sha256</span><span class="p">(</span><span class="nb">input</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In other words, a proof generated by this program shows that the prover knows <em>an</em> input that hashes to a particular hash. This can be used to show that a prover knows a password, without revealing the password.</p>
<p>Hashes are also used to create a <em>commitment</em> to secret data: multiple programs can prove that they are using the same input data, while keeping this data secret, by each calculating and returning the hash. If two programs output the same hash, we know they must have used the same input to the hash, but we cannot deduce this (secret) input because the hash is a one-way function.</p>
<h2 id="performance">Performance</h2>
<p>The program that calculates the SHA256 hash of a 256-byte input is quite <strong>slow</strong>. (The program corresponds to a circuit of 145560 constraints.) On a MacBook Pro with a 2.3 GHz 8-Core Intel Core i9 from 2019:</p>
<ul>
<li>compiling the program takes 19 seconds (this only needs to happen once per program),</li>
<li>performing the set-up takes 8 seconds (this generates keys that are used by prover and verifier, and only needs to happen once per prover–verifier pair),</li>
<li>computing the witness takes <strong>6 seconds</strong> (this actually executes the program, and keeps track of the intermediate values),</li>
<li>generating the proof takes <strong>7 seconds</strong>,</li>
<li>while verification takes <strong>16 <em>milli</em>seconds</strong>.</li>
</ul>
<p>Moreover, the performance characteristics of ZKPs are <strong>non-obvious</strong>. As these programs are compiled to arithmetic circuits that work on a finite field, their performance is quite different from ‘normal’ programs. For example:</p>
<ul>
<li>For an <code>if</code>, both branches are executed. This also means no short-circuiting of <code>&amp;&amp;</code> and <code>||</code>.</li>
<li>Loops must have a static bound and are unrolled.</li>
<li>The performance of different operations can be quite different:
<ul>
<li>Operations are in a finite field, so addition is fast but division is slow. Moreover, overflow checks may be needed.</li>
<li>Operations that work on the types <code>u8</code>, …, <code>u32</code>, <code>u64</code> may need to be emulated using operations on their individual bits.</li>
</ul>
</li>
</ul>
<h2 id="programmability">Programmability</h2>
<p>When programming ZKPs, it’s very easy to make mistakes, which can have severe consequences.</p>
<p>For example, when writing a program to prove knowledge of the factors of a number <code>n</code>, a first attempt might be:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">private</span> <span class="n">field</span> <span class="n">a</span><span class="p">,</span> <span class="n">private</span> <span class="n">field</span> <span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">field</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">a</span> <span class="o">*</span> <span class="n">b</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>However, this program allows the prover to generate a trivial proof by picking <code>a = 1</code> and <code>b = n</code>, hence allowing the prover to generate a valid proof without knowing the factors of <code>n</code>. This <strong>breaks the computational integrity</strong> we expected as verifier.</p>
<p>As another example, the program above that proves knowledge of the pre-image of a SHA256 hash actually contains a mistake (depending on the use case): as the input is not salted, the verifier may be able to guess the input by brute-forcing all possible inputs. This <strong>breaks the confidentiality</strong> that we expected as prover. A better implementation would have been:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">private</span> <span class="n">u8</span><span class="p">[</span><span class="mi">256</span><span class="p">]</span> <span class="nb">input</span><span class="p">,</span> <span class="n">private</span> <span class="n">u8</span><span class="p">[</span><span class="mi">256</span><span class="p">]</span> <span class="n">salt</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">u32</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span> <span class="p">{</span>
  <span class="k">return</span> <span class="n">sha256</span><span class="p">([...</span><span class="nb">input</span><span class="p">,</span> <span class="p">...</span><span class="n">salt</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Such bugs have been observed in practice <a href="https://github.com/0xPARC/zk-bug-tracker">[1]</a>, <a href="https://eprint.iacr.org/2023/547">[2]</a>, appearing in popular projects like the Bulletproofs paper, zcash, and Circom’s standard library. These bugs are hard to find and hard to fix, as they require a deep understanding of the program and the cryptographic properties of the ZKP. Note also that many existing bug detection and static analysis tools focus on finding the first kind of problem (bugs that produce incorrect outputs), and not accidental leaks of confidential information.</p>
<p>Finally, the ZoKrates language (as well as other languages for ZKPs) is quite limited. For instance, there is no support for floating-point numbers or strings. The standard library is also very limited, with support for (1) hashing, (2) operations on elliptic curves, (3) EdDSA signature verification, and (4) some functions for type conversion; but not much else.</p>
<h2 id="conclusion-amp-future-work">Conclusion &amp; Future work</h2>
<p>In this post, we described the main challenges of developing ZKPs: their <em>slow</em> and <em>non-obvious</em> performance, and how easy it is to introduce bugs that break <em>confidentiality</em> or <em>computational integrity</em>.</p>
<p>In <a href="/2024/07/16/zkgadgets.html">a next post</a>, we will introduce our current solutions for these problems, as well as our vision on how to improve this more generally in the future.</p>
<h2 id="references">References</h2>
<div style="font-size: 90%;">
<ul>
<li><a href="https://github.com/0xPARC/zk-bug-tracker">ZK Bug Tracker</a></li>
<li>Junrui Liu, Ian Kretz, Hanzhi Liu, Bryan Tan, Jonathan Wang, Yi Sun, Luke Pearson, Anders Miltner, Işıl Dillig, and Yu Feng. 2023. <em><a href="https://eprint.iacr.org/2023/547">Certifying Zero-Knowledge Circuits with Refinement Types</a></em>.</li>
<li>Shankara Pailoor, Yanju Chen, Franklyn Wang, Clara Rodríguez, Jacob Van Geffen, Jason Morton, Michael Chu, Brian Gu, Yu Feng, and Işıl Dillig. 2023. <em><a href="https://doi.org/10.1145/3591282">Automated Detection of Under-Constrained Circuits in Zero-Knowledge Proofs</a></em>. In Proceedings of Programming Language Design and Implementation (PLDI).</li>
<li>Hongbo Wen, Jon Stephens, Yanju Chen, Kostas Ferles, Shankara Pailoor, Kyle Charbonnet, Işıl Dillig, and Yu Feng. 2023. <em><a href="https://eprint.iacr.org/2023/190">Practical Security Analysis of Zero-Knowledge Proof Circuits</a></em>. In USENIX Security ’24.</li>
<li>Stefanos Chaliasos, Jens Ernstberger, David Theodore, David Wong, Mohammad Jahanara, and Benjamin Livshits. 2024. <em><a href="https://arxiv.org/abs/2402.15293">SoK: What don’t we know? Understanding Security Vulnerabilities in SNARKs</a></em>.</li>
</ul>
</div>]]></content><author><name>janwillem</name></author><category term="zkp" /><summary type="html"><![CDATA[In this post, we’ll look at the challenges developers encounter when developing Zero-Knowledge Proofs: performance and programmability.]]></summary></entry></feed>