<?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://juliakrizanova.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://juliakrizanova.github.io/" rel="alternate" type="text/html" /><updated>2026-10-01T06:03:11+00:00</updated><id>https://juliakrizanova.github.io/feed.xml</id><title type="html">Júlia Križanová</title><entry><title type="html">Comparing heap allocators for zkEVM</title><link href="https://juliakrizanova.github.io/blogs/zisk-allocator-comparison.html" rel="alternate" type="text/html" title="Comparing heap allocators for zkEVM" /><published>2026-06-25T00:00:00+00:00</published><updated>2026-06-25T00:00:00+00:00</updated><id>https://juliakrizanova.github.io/blogs/zisk-allocator-comparison</id><content type="html" xml:base="https://juliakrizanova.github.io/blogs/zisk-allocator-comparison.html"><![CDATA[<p><a href="https://github.com/0xPolygonHermez/zisk">ZisK</a> exposes four allocators as Cargo features in the <a href="https://github.com/0xPolygonHermez/zisk/blob/main/ziskos/entrypoint/Cargo.toml">ziskos entrypoint</a>. Other allocators exist in the codebase but they <a href="https://github.com/0xPolygonHermez/zisk/blob/main/ziskos/entrypoint/src/alloc/mod.rs#L33-L37">are disabled</a>.</p>

<p>For evaluating the options I used the ZisK emulator <code class="language-plaintext highlighter-rouge">ziskemu</code>, which runs guest execution (without generating a proof) and accumulates AIR-cost. The cost derived from the execution trace translates to the proving complexity. While we ultimately care about proving time, using the emulator to measure AIR-costs is more practical.</p>

<h2 id="allocator-options">Allocator options</h2>

<p>All allocators can be selected via Cargo feature:</p>

<p><strong>bump</strong> (default): The simplest possible allocator. Keeps a pointer to the next free byte and advances it on each allocation. Memory is never reclaimed.</p>

<p><strong>dlmalloc</strong> (<a href="https://gee.cs.oswego.edu/dl/html/malloc.html">explainer</a>): Each allocated block carries a small header with its size and a flag. When a block is freed, dlmalloc adds it to the free list and checks neighbors. If adjacent blocks are also free, it merges them into a larger block so future allocations can reuse the space.</p>

<p><strong>talc</strong>: Similar mechanism to dlmalloc with merging of adjacent free blocks, but organizes free blocks into buckets.</p>

<p><strong>tlsf</strong> (<a href="https://ricefields.me/2024/04/20/tlsf-allocator.html">explainer</a>): Instead of searching a list, it uses a two-level bitmap to jump directly to a free block of the right size. Amortized O(1) alloc/free.</p>

<h2 id="how-are-the-costs-calculated">How are the costs calculated</h2>

<p>ZisK is a collection of specialised modules much like a modern CPU. Each module (state machine) has its own execution trace. Every instruction the guest executes fills rows in one or more traces. The emulator sequentially executes each instruction, looks up per-op cost constants, and accumulates into buckets:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">MAIN</code>: Cost of processing, proportional to the number of instructions executed. Every instruction contributes one row here regardless of what it does. This is the baseline before any operation-specific overhead.</li>
  <li><code class="language-plaintext highlighter-rouge">OPCODES</code>: Additional rows for the operation itself, on top of MAIN. These cover simple operations (add, sub, mul, shifts, bitwise). For example, a multiplication fills MAIN with one row and OPCODES with the multiplication cost.</li>
  <li><code class="language-plaintext highlighter-rouge">PRECOMPILES</code>: Specialised circuits for operations too expensive to express as RISC-V sequences. An example for precompiles is a hash function call.</li>
  <li><code class="language-plaintext highlighter-rouge">MEMORY</code>: Additional rows for memory reads and writes, including extra overhead for non-aligned accesses.</li>
  <li><code class="language-plaintext highlighter-rouge">BASE</code>: Fixed overhead independent of the guest program. For example the overhead needed for lookup tables.</li>
</ul>

<p>Total cost = <code class="language-plaintext highlighter-rouge">MAIN + OPCODES + PRECOMPILES + MEMORY + BASE</code>. Per-op costs are defined in <a href="https://github.com/0xPolygonHermez/zisk/blob/main/core/src/zisk_ops_costs.rs"><code class="language-plaintext highlighter-rouge">zisk_ops_costs.rs</code></a>. The current memory limit is set to 512MB in <a href="https://github.com/0xPolygonHermez/zisk/blob/main/core/src/mem.rs#L111">mem.rs</a>.</p>

<h2 id="50-mainnet-blocks">50 Mainnet blocks</h2>

<p>The first setting where I evaluated the allocator is on the mainnet blocks 24949787 - 24949836, by using <a href="https://github.com/eth-act/zkevm-benchmark-workload">zkevm-benchmark-workload</a> to run the <code class="language-plaintext highlighter-rouge">ZisK v0.16.1</code>. I used the locally compiled ethrex stateless client from commit <code class="language-plaintext highlighter-rouge">b112f94</code>.</p>

<table>
  <thead>
    <tr>
      <th>Mainnet cost: ethrex b112f94 ZisK v0.16.1</th>
      <th>bump</th>
      <th>dlmalloc</th>
      <th>talc</th>
      <th>tlsf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Min gas block 14M: <code class="language-plaintext highlighter-rouge">block 24949797</code></td>
      <td>24.71B</td>
      <td>26.27B</td>
      <td>26.52B</td>
      <td>27.53B</td>
    </tr>
    <tr>
      <td>Max gas block 56M: <code class="language-plaintext highlighter-rouge">block 24949791</code></td>
      <td>77.71B</td>
      <td>83.18B</td>
      <td>83.86B</td>
      <td>87.24B</td>
    </tr>
    <tr>
      <td>Average over all blocks</td>
      <td>45.44B</td>
      <td>48.61B</td>
      <td>49.02B</td>
      <td>51.06B</td>
    </tr>
  </tbody>
</table>

<p><strong>In all cases it holds: <code class="language-plaintext highlighter-rouge">bump &lt; dlmalloc &lt; talc &lt; tlsf</code></strong>. I wanted to check min and max gas block to see if there is noticeable difference in the costs, but it does not seem to be the case. The delta for bump and dlmalloc is consistent across the three rows (6%, 6.5%, 6.5%). We can also take a look at the distribution of the costs across the state machines.</p>

<p><img src="/assets/images/blogs/sm_breakdown.png" alt="sm_breakdown" /></p>

<p>As expected BASE and PRECOMPILES are identical across all allocators. <strong>The largest difference is in the MAIN costs</strong> (+9% to 16%). No matter if the allocator performs comparison or traversal to evaluate the free space, each of these steps adds complexity to the MAIN state machine. The difference on MEMORY is less significant (+5% to 8%). The allocators store some additional data in form of a header or metadata but this overhead is relatively small. <em>The measurements in a table format can be viewed in the last section of the blog.</em></p>

<h2 id="eest-fixtures">EEST Fixtures</h2>

<p>The current mainnet (Fusaka) <a href="https://eips.ethereum.org/EIPS/eip-7935">block gas limit is 60M</a>. The next Glamsterdam fork <a href="https://ethereum.org/roadmap/glamsterdam/#state-creation-gas-cost-increase">raises it to 200M</a>. How does this affect zkEVMs? Keep in mind that, average case performance matters less than worst-case. A single “prover killer” block that crashes on memory or exceeds the 12s block time is enough to halt proof generation.</p>

<p>I used fixtures generated using the <a href="https://github.com/ethereum/execution-spec-tests">EEST benchmark suite</a> for 100M gas limit. To parse the generated tests for the <code class="language-plaintext highlighter-rouge">zkevm-benchmark-workload</code> I used <a href="https://github.com/eth-act/zkevm-benchmark-workload/tree/master/crates/witness-generator-cli">witness-generator-cli</a>. <em>The exact commands are listed in the last section.</em></p>

<h3 id="100m-fixtures">100M fixtures</h3>

<p><img src="/assets/images/blogs/air_cost_comparison.png" alt="air_cost_comparison" /></p>

<p>In these EEST tests the bump allocator still performs the best however, <strong>we see the first OOM</strong>, which is a problem. The tests like ADD and MULMOD, which do not make many allocations, unsurprisingly, do not show much difference in the total cost. But other tests like SSTORE or SLOAD show more variety.</p>

<p>Aside from that, we can also measure the memory usage. Or more precisely the highest address touched from the <a href="https://github.com/0xPolygonHermez/zisk/pull/1141">PR1141</a> on ZisK. In this measurement <strong><code class="language-plaintext highlighter-rouge">keccak_max_permutation</code> OOMs</strong>, and there are other tests like <code class="language-plaintext highlighter-rouge">modexp</code> and <code class="language-plaintext highlighter-rouge">sha256</code> that also allocate a lot. This suggests that for higher gas limits they might also fail.</p>

<p>The memory usage across dlmalloc, talc, and tlsf is similar. Since memory is cleared between blocks, the choice between these three should come down to AIR-cost performance rather than memory footprint.</p>

<p><img src="/assets/images/blogs/ram_usage.png" alt="ram_usage" /></p>

<h3 id="allocator-comparison">Allocator comparison</h3>

<p>Across <em>almost all</em> of the tests we are seeing <code class="language-plaintext highlighter-rouge">bump &lt; dlmalloc &lt; talc &lt; tlsf</code> in terms of the AIR-costs. The profiling data also show the costs of the function calls. The average for the above 100M EEST tests is the following:</p>

<table>
  <thead>
    <tr>
      <th>Compare allocator function calls</th>
      <th>bump</th>
      <th>dlmalloc</th>
      <th>talc</th>
      <th>tlsf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>alloc</td>
      <td>too small, not recorded</td>
      <td>1.32%</td>
      <td>1.89%</td>
      <td>2.37%</td>
    </tr>
    <tr>
      <td>free</td>
      <td>0%</td>
      <td>0.85%</td>
      <td>1.13%</td>
      <td>1.83%</td>
    </tr>
  </tbody>
</table>

<p>Bump does the least work resulting in lowest costs. After that dlmalloc is the second candidate. On native hardware, talc and tlsf can win through cache locality or fast bit operations. These advantages do not translate from standard computation to zkVM.</p>

<p>Without getting into too many allocator details, let’s look into tests with higher gas where the memory limits are a bigger problem. The following tests take long to compute, so I will compare just dlmalloc and bump.</p>

<h3 id="200m-fixtures">200M fixtures</h3>

<p>As expected the tests mentioned above (<code class="language-plaintext highlighter-rouge">modexp</code>, <code class="language-plaintext highlighter-rouge">sha256</code>) fail for the 200M gas limit. It is possible to avoid the OOM by increasing the memory limit for the bump allocator on a <a href="https://github.com/benbencik/zisk/tree/test/increase-mem-limit">fork of ZisK</a>. This prevents the crashes and is even more efficient on the <code class="language-plaintext highlighter-rouge">modexp</code> test.</p>

<p>However, these results cover only a small subset of 200M tests. Running all of the 200M tests is just too heavy to do locally. It is very possible that there exist some tests which fail even for the 1.5GB memory limit with bump allocator.</p>

<p><img src="/assets/images/blogs/air_cost_comparison_2.png" alt="air_cost_comparison_2" /></p>

<p>Below we compare the memory usage, where dlmalloc is consistently lower, making it safer for potential future L1 inclusion.</p>

<p><img src="/assets/images/blogs/ram_usage_200M.png" alt="ram_usage_200M" /></p>

<h2 id="conclusion">Conclusion</h2>

<p>The trivial bump allocator performs better on AIR cost in almost all cases, however from just the few tests tried there are OOMs and more can be seen at <a href="https://eth-act.github.io/zkevm-benchmark-runs/benchmarks/">zkevm-benchmark-runs</a>. From the measured cases <strong>the most viable option seems to use dlmalloc.</strong></p>

<p><br /></p>

<hr />

<p><br /></p>

<h2 id="notes">Notes</h2>

<h3 id="useful-commands">Useful commands</h3>

<p>Building ELF file</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">--rm</span> <span class="se">\</span>
   <span class="nt">-v</span> <span class="s2">"</span><span class="nv">$PWD</span><span class="s2">"</span>:/ere-guests <span class="se">\</span>
   <span class="nt">-v</span> <span class="s2">"</span><span class="nv">$PWD</span><span class="s2">/output"</span>:/output <span class="se">\</span>
   <span class="nt">-v</span> <span class="s2">"&lt;path to ethrex&gt;"</span>:/ethrex <span class="se">\</span>
   <span class="nt">-v</span> <span class="s2">"&lt;path to zisk&gt;"</span>:/zisk <span class="se">\</span>
   <span class="s2">"ghcr.io/eth-act/ere/ere-compiler-zisk:0.12.1"</span> <span class="se">\</span>
   <span class="nt">--compiler-kind</span> rust-customized <span class="se">\</span>
   <span class="nt">--guest-dir</span> /ere-guests/bin/stateless-validator-ethrex/zisk <span class="se">\</span>
   <span class="nt">--output-dir</span> /output <span class="se">\</span>
   <span class="nt">--elf-name</span> stateless-validator-ethrex-zisk.elf
</code></pre></div></div>

<p>Generating EEST fixtures</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> uv run fill <span class="se">\</span>
  <span class="nt">--gas-benchmark-values</span> &lt;gas limit&gt; <span class="nt">--fork</span> Prague <span class="se">\</span>
  <span class="nt">--output</span> &lt;output name&gt; <span class="nt">--clean</span> <span class="nt">-n</span> auto <span class="se">\</span>
  <span class="nt">-k</span> <span class="s2">"&lt;test name&gt;"</span> <span class="se">\</span>
  tests/benchmark/compute
</code></pre></div></div>

<p>Converting EEST fixtures to inputs:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">EF_TEST_TRIE</span><span class="o">=</span>default cargo run <span class="nt">--release</span> <span class="nt">-p</span> witness-generator-cli <span class="nt">--</span> <span class="se">\</span>
  <span class="nt">-o</span> &lt;output file&gt; <span class="se">\</span>
  tests <span class="nt">--eest-fixtures-path</span> &lt;input files&gt;
</code></pre></div></div>

<p>Running ziskemu</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">RUST_LOG</span><span class="o">=</span>info <span class="nv">RAYON_NUM_THREADS</span><span class="o">=</span>2 cargo run <span class="nt">--release</span> <span class="nt">-p</span> ere-hosts <span class="se">\ </span>
  <span class="nt">--</span> <span class="nt">--zisk-profile</span> <span class="nt">--zkvms</span> zisk <span class="nt">--bin-path</span> &lt;path to ere-guests&gt;/output <span class="se">\ </span>
  stateless-validator <span class="nt">--execution-client</span> ethrex <span class="se">\</span>
  <span class="nt">--input-folder</span> &lt;path to generated inputs&gt;
</code></pre></div></div>

<h3 id="data-tables">Data tables</h3>

<table>
  <thead>
    <tr>
      <th>Cost distribution mainnet: ethrex b112f94 ZisK v0.16.1</th>
      <th>bump</th>
      <th>dlmalloc</th>
      <th>talc</th>
      <th>tlsf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAIN</code></td>
      <td>27.0B</td>
      <td>29.5B</td>
      <td>29.7B</td>
      <td>31.3B</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PRECOMPILES</code></td>
      <td>7.8B</td>
      <td>7.8B</td>
      <td>7.8B</td>
      <td>7.8B</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">OPCODES</code></td>
      <td>6.8B</td>
      <td>7.2B</td>
      <td>7.5B</td>
      <td>8.0B</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MEMORY</code></td>
      <td>3.5B</td>
      <td>3.8B</td>
      <td>3.7B</td>
      <td>3.7B</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">BASE</code></td>
      <td>293.6M</td>
      <td>293.6M</td>
      <td>293.6M</td>
      <td>293.6M</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>Cost by allocator (100M): ethrex 81484be ZisK v0.16.1</th>
      <th>bump</th>
      <th>dlmalloc</th>
      <th>talc</th>
      <th>tlsf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>arithmetic[opcode_ADD-benchmark]</td>
      <td>157.82B</td>
      <td>157.83B</td>
      <td>157.83B</td>
      <td>157.84B</td>
    </tr>
    <tr>
      <td>arithmetic[opcode_MULMOD-benchmark]</td>
      <td>116.54B</td>
      <td>116.55B</td>
      <td>116.55B</td>
      <td>116.56B</td>
    </tr>
    <tr>
      <td>ecrecover[ecrecover-benchmark]</td>
      <td>78.36B</td>
      <td>80.22B</td>
      <td>80.74B</td>
      <td>82.55B</td>
    </tr>
    <tr>
      <td>keccak_max_permutations[benchmark]</td>
      <td>OOM</td>
      <td>470.79B</td>
      <td>470.88B</td>
      <td>470.90B</td>
    </tr>
    <tr>
      <td>modexp[mod_1024_exp_2-benchmark]</td>
      <td>577.20B</td>
      <td>588.51B</td>
      <td>592.20B</td>
      <td>594.76B</td>
    </tr>
    <tr>
      <td>sha256[benchmark]</td>
      <td>41.44B</td>
      <td>41.45B</td>
      <td>41.50B</td>
      <td>41.55B</td>
    </tr>
    <tr>
      <td>storage_access_cold_benchmark[SLOAD-benchmark]</td>
      <td>34.12B</td>
      <td>37.81B</td>
      <td>39.20B</td>
      <td>43.656</td>
    </tr>
    <tr>
      <td>storage_access_cold_benchmark[SSTORE_new-benchmark]</td>
      <td>420.88B</td>
      <td>430.70B</td>
      <td>435.47B</td>
      <td>443.26B</td>
    </tr>
    <tr>
      <td>storage_access_warm_benchmark[SLOAD-benchmark]</td>
      <td>90.140B</td>
      <td>90.12B</td>
      <td>90.13B</td>
      <td>90.19B</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>MEM usage by allocator (100M): ethrex 81484be ZisK v0.16.1</th>
      <th>bump</th>
      <th>dlmalloc</th>
      <th>talc</th>
      <th>tlsf</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>arithmetic[opcode_ADD-benchmark]</td>
      <td>0.48%</td>
      <td>0.46%</td>
      <td>0.45%</td>
      <td>0.45%</td>
    </tr>
    <tr>
      <td>arithmetic[opcode_MULMOD-benchmark]</td>
      <td>0.48%</td>
      <td>0.46%</td>
      <td>0.45%</td>
      <td>0.45%</td>
    </tr>
    <tr>
      <td>ecrecover[ecrecover-benchmark]</td>
      <td>1.73%</td>
      <td>0.46%</td>
      <td>0.45%</td>
      <td>0.45%</td>
    </tr>
    <tr>
      <td>keccak_max_permutations[benchmark]</td>
      <td>100%</td>
      <td>0.49%</td>
      <td>0.45%</td>
      <td>0.50%</td>
    </tr>
    <tr>
      <td>modexp[mod_1024_exp_2-benchmark]</td>
      <td>91.09%</td>
      <td>0.46%</td>
      <td>0.45%</td>
      <td>0.45%</td>
    </tr>
    <tr>
      <td>sha256[benchmark]</td>
      <td>50.49%</td>
      <td>0.55%</td>
      <td>0.53%</td>
      <td>0.53%</td>
    </tr>
    <tr>
      <td>storage_access_cold_benchmark[SLOAD-benchmark]</td>
      <td>7.83%</td>
      <td>3.51%</td>
      <td>3.19%</td>
      <td>3.65%</td>
    </tr>
    <tr>
      <td>storage_access_cold_benchmark[SSTORE_new-benchmark]</td>
      <td>0.49%</td>
      <td>0.47%</td>
      <td>0.46%</td>
      <td>0.46%</td>
    </tr>
    <tr>
      <td>storage_access_warm_benchmark[SLOAD-benchmark]</td>
      <td>0.56%</td>
      <td>0.46%</td>
      <td>0.45%</td>
      <td>0.45%</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>Cost by allocator (200M): ethrex 81484be ZisK v0.16.1</th>
      <th>bump 512MB Mem</th>
      <th>bump 1536MB Mem</th>
      <th>dlmalloc</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>mod_arithmetic[opcode_MULMOD-mod_bits_191-benchmark]</td>
      <td>2.375T</td>
      <td>2.375T</td>
      <td>2.375T</td>
    </tr>
    <tr>
      <td>ecrecover[ecrecover-benchmark]</td>
      <td>156.34B</td>
      <td>156.36B</td>
      <td>160.06B</td>
    </tr>
    <tr>
      <td>keccak_max_permutations[benchmark]</td>
      <td>OOM</td>
      <td>945.27B</td>
      <td>941.48B</td>
    </tr>
    <tr>
      <td>mcopy[fixed_src_dst_True-copy_size_0-mem_size_1048576-benchmark]</td>
      <td>335.36B</td>
      <td>335.36B</td>
      <td>335.37B</td>
    </tr>
    <tr>
      <td>test_modexp[mod_1024_exp_2-benchmark]</td>
      <td>OOM</td>
      <td>1.15T</td>
      <td>1.17T</td>
    </tr>
    <tr>
      <td>sha256[benchmark]</td>
      <td>OOM</td>
      <td>82.37B</td>
      <td>82.40B</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th>Absolute MEM usage by allocator (200M): ethrex 81484be ZisK v0.16.1</th>
      <th>bump MEM 1536MB (B)</th>
      <th>dlmalloc (B)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>mod_arithmetic[opcode_MULMOD-mod_bits_191-benchmark]</td>
      <td>2,547,923</td>
      <td>2,424,832</td>
    </tr>
    <tr>
      <td>ecrecover[ecrecover-benchmark]</td>
      <td>15,836,691</td>
      <td>2,424,832</td>
    </tr>
    <tr>
      <td>keccak_max_permutations[benchmark]</td>
      <td>1,067,406,171</td>
      <td>2,818,048</td>
    </tr>
    <tr>
      <td>mcopy[fixed_src_dst_True-copy_size_0-mem_size_1048576-benchmark]</td>
      <td>4,123,571</td>
      <td>4,128,768</td>
    </tr>
    <tr>
      <td>test_modexp[mod_1024_exp_2-benchmark]</td>
      <td>967,632,603</td>
      <td>2,424,832</td>
    </tr>
    <tr>
      <td>sha256[benchmark]</td>
      <td>535,117,379</td>
      <td>3,080,192</td>
    </tr>
  </tbody>
</table>]]></content><author><name></name></author><category term="zkEVM" /><category term="Ethereum" /><category term="memory" /><summary type="html"><![CDATA[Do we ever need to free?]]></summary></entry><entry><title type="html">Tuning compiler flags for zkVMs</title><link href="https://juliakrizanova.github.io/blogs/compiler-optimization-zkevm.html" rel="alternate" type="text/html" title="Tuning compiler flags for zkVMs" /><published>2026-05-28T00:00:00+00:00</published><updated>2026-05-28T00:00:00+00:00</updated><id>https://juliakrizanova.github.io/blogs/compiler-optimization-zkevm</id><content type="html" xml:base="https://juliakrizanova.github.io/blogs/compiler-optimization-zkevm.html"><![CDATA[<p>A recent publication <a href="https://arxiv.org/pdf/2508.17518">Evaluating Compiler Optimization Impacts on zkVM Performance</a>, <a href="https://www.youtube.com/watch?v=fSMaBewbD0Y&amp;t=1238s">presented at ZKProof 8</a>, motivated me to test compiler optimizations on the proposed zkEVMs. In this work the compiler is no longer optimizing for the number of instructions on the processor but for the execution trace rows in the AIR table. While the standard optimizations help, we can still tweak the parameters a bit more to push them to better results. As an outcome it was possible to <strong>reduce the costs by 10%</strong> for ethrex stateless validator running in the ZisK zkVM.</p>

<h2 id="computation-model">Computation model</h2>

<p>We are compiling for architecture <code class="language-plaintext highlighter-rouge">riscv64im_zicclsm</code>, which is a bare-metal target. There is no operating system, no standard library and you need to bring your own memory management. Every operation and branch must eventually be proven in the resulting proof of computation.</p>

<p>This instruction set is deliberately simple. There are no extensions like vectorized instructions, but we do have precompiles for computing hash functions. The <code class="language-plaintext highlighter-rouge">Zicclsm</code> feature adds support for misaligned loads and stores, more info about why we need it can be found in <a href="https://github.com/eth-act/zkvm-standards/blob/main/standards/riscv-target/target.md#zicclsm-extension">zkVM standards</a>.</p>

<p>Looking at the <a href="https://github.com/0xPolygonHermez/zisk/blob/main/core/src/zisk_ops_costs.rs">ZisK costs table</a> this is also different from conventional CPU. Arithmetic multiplication and division have the same cost. The compiler sometimes rewrites operations using bitwise tricks that are fast on real hardware, but this is counterproductive here. Every instruction pays a fixed cost and multiplication replaced by a series of bitwise operations is strictly worse.</p>

<h2 id="existing-findings-and-constraints">Existing findings and constraints</h2>

<p>The mentioned paper correctly identified the most relevant LLVM passes. For the beneficial optimizations it is possible to increase their impact by adjusting the parameters. However it is not easy to disable passes that regress the performance. The previous work patched the LLVM source code to turn off such optimizations, but we will limit ourselves to not doing this for the Ethereum (future) mainnet. If every team developing a zkVM has their own custom compiler the reasoning about the whole system is more complicated.</p>

<p>One clear outcome from the paper is that, applying the <code class="language-plaintext highlighter-rouge">opt-level=3</code> optimizations significantly helps. At this setting, LLVM applies almost every optimization available to it. The tricky part is that some of those passes are harmful for zkVM workloads, and there is no clean <code class="language-plaintext highlighter-rouge">--disable-pass &lt;name&gt;</code> flag in the <a href="https://llvm.org/docs/NewPassManager.html">New Pass Manager</a>.</p>

<p>I tried to enumerate the default passes using <code class="language-plaintext highlighter-rouge">--print-pipeline-passes</code>, strip the problematic ones, and feed the remainder back manually. However, when benchmarked, that approach regressed. The set of passes LLVM applies internally is apparently not equivalent to the list I fed back. Determining the exact passes <a href="https://groups.google.com/g/llvm-dev/c/heZUV3rIuxk">seems to be hard</a>. So manually reconstructing the pass pipeline is not a reliable approach.</p>

<p>That being said, there are a few passes that expose a specific <a href="https://github.com/llvm/llvm-project/blob/main/llvm/lib/Transforms/Scalar/LICM.cpp#L120-L122">disable option</a> flag, but this is more of an exception. Alternatively it is possible to disable some passes by forcing thresholds to zero.</p>

<h2 id="search-for-optimal-parameters">Search for optimal parameters</h2>

<p>Okay so, this effectively leaves us with <a href="https://doc.rust-lang.org/rustc/codegen-options/index.html">codegen options</a> and <code class="language-plaintext highlighter-rouge">llvm-args</code>. There are way <a href="https://clang.llvm.org/docs/ClangCommandLineReference.html">too many knobs</a> that we can tweak. I picked the flags, specified in the <a href="https://github.com/thomasgassmann/zkvm-compiler-optimizations/blob/main/zkbench/tune/common.py#L262-L291">paper reference implementation</a>, and fed that to an Optuna optimizer commonly used to find hyperparameters in machine learning. The following parameters provided the best results:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--inline-threshold=4749
--inline-instr-cost=1
--inline-memaccess-cost=1
--inline-call-penalty=12
--unroll-threshold=378
--disable-licm-promotion
--licm-versioning-max-depth-threshold=0
--memdep-block-number-limit=2510
--memdep-block-scan-limit=98
--jump-threading-threshold=16
--jump-threading-implication-search-threshold=6
--max-speculation-depth=0
--max-uses-for-sinking=119
--available-load-scan-limit=23
--bonus-inst-threshold=5
--max-num-inline-blocks=6
--loop-interchange-threshold=2
--early-ifcvt-limit=29
--loop-distribute-scev-check-threshold=0
--loop-load-elimination-scev-check-threshold=7
--max-dependences=6
--max-nested-scalar-reduction-interleave=2
</code></pre></div></div>

<p>The results have a few interesting points. In the <a href="https://arxiv.org/pdf/2508.17518">previous work</a> it is already known that inlining is the most impactful optimization. That is why the optimizer set the threshold to roughly 21 times <a href="https://llvm.org/doxygen/InlineCost_8cpp_source.html">the default</a>. Related flags (<code class="language-plaintext highlighter-rouge">--inline-instr-cost</code>, <code class="language-plaintext highlighter-rouge">--inline-memaccess-cost</code>, <code class="language-plaintext highlighter-rouge">--inline-call-penalty</code>) also push the optimizer to prioritize inlining. The flag <code class="language-plaintext highlighter-rouge">--max-speculation-depth</code> is pushed all the way to zero because we do not have any branch predictor. Beyond that, the memory dependence limits (<code class="language-plaintext highlighter-rouge">--memdep-block-number-limit</code>, <code class="language-plaintext highlighter-rouge">--available-load-scan-limit</code>) are adjusted to give LLVM more space for identifying redundant operations.</p>

<p>A few of the other remaining passes lower the thresholds to effectively disable features that the zkVM does not possess. The flag <code class="language-plaintext highlighter-rouge">--disable-licm-promotion</code> was added manually because the previous study found that licm was the most harmful pass.</p>

<h3 id="codegen-options">Codegen options</h3>

<p>The existing infrastructure in <a href="https://github.com/eth-act/ere-guests">ere-guests</a> already provides:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">codegen-units = 1</code>: compiles everything as a single unit, giving LLVM full visibility across the codebase for cross-function optimizations like inlining</li>
  <li><code class="language-plaintext highlighter-rouge">lto = "fat"</code>: performs link-time optimization across the entire dependency tree</li>
  <li><code class="language-plaintext highlighter-rouge">panic=abort</code>: crashes immediately on error, this eliminates the unwinding logic that would otherwise appear in the trace</li>
</ul>

<p>I also tested optimising for size with <code class="language-plaintext highlighter-rouge">opt-level=s</code>, by reasoning that larger memory means a larger trace to prove. However, size-optimized builds underperformed compared to <code class="language-plaintext highlighter-rouge">opt-level=3</code>, suggesting that release passes are still better. Besides that, a popular feature is also <code class="language-plaintext highlighter-rouge">target-cpu=native</code>, but that is irrelevant, because we are running a VM not on a native CPU and it already has a specified target.</p>

<h2 id="results">Results</h2>

<p>The profiling was done using <a href="https://github.com/eth-act/zkevm-benchmark-workload">zkevm-benchmark-workload</a> on mainnet blocks 24949787 - 24949811 and the results show a consistent reduction of the AIR-costs by something over 10%. The average cost per block on the baseline is 44.22B, and the optimized version 39.54B.</p>

<p><img src="/assets/images/blogs/block_costs_comparison.png" alt="block_costs_comparison.png" /></p>

<h2 id="unexplored-directions">Unexplored directions</h2>

<p>One could write custom LLVM passes with <a href="https://crates.io/crates/llvm-plugin">llvm-plugin</a>. This would allow writing passes tailored to zkVM cost models without forking the compiler, since plugins load at compile time into an unmodified binary. This task seems to be quite complex, and potential improvement was unclear. Besides that it is also possible to give the compiler more context about the program with <a href="https://doc.rust-lang.org/rustc/profile-guided-optimization.html">Profile Guided Optimization</a>. Since we are compiling for bare-metal, this makes the instrumentation more complicated. For sure it is possible to make this work but the optimizations are calibrated for standard CPU cost models and in this case I did not have a reasonable estimate for benefit on the zkVM.</p>

<hr />

<p>As an outcome, it is possible to get at least 10% improvement on top of <code class="language-plaintext highlighter-rouge">opt-level=3</code>, which is very reasonable for no-code change. But as the paper highlighted, the best approach is to use an optimizer to find the most suitable parameters for the specific zkVM.</p>]]></content><author><name></name></author><category term="zkEVM" /><category term="Ethereum" /><summary type="html"><![CDATA[How far can we get with no-code change]]></summary></entry><entry><title type="html">Ethereum cryptographic garden</title><link href="https://juliakrizanova.github.io/blogs/ethereum-crypto-garden.html" rel="alternate" type="text/html" title="Ethereum cryptographic garden" /><published>2025-12-09T00:00:00+00:00</published><updated>2025-12-09T00:00:00+00:00</updated><id>https://juliakrizanova.github.io/blogs/ethereum-crypto-garden</id><content type="html" xml:base="https://juliakrizanova.github.io/blogs/ethereum-crypto-garden.html"><![CDATA[<blockquote>
  <p>This is a user-friendly overview of cryptographic primitives of Ethereum. This post does not describe the algorithms, but rather give and overview and useful references for detailed description.</p>
</blockquote>

<p><img src="/assets/images/blogs/ethereum_crypto_garden_smaller.jpg" alt="Ethereum Crypto Garden" />
<em>…cover image is generated by AI</em></p>

<p>At the time of writing, Ethereum processes over a million transactions daily. The combined market capitalization sits at hundreds of billions of dollars, with tens of billions more locked in smart contracts (TVL). That is a lot of trust and value placed on a single protocol. But what exactly secures this value? In this post, we will look at the cryptographic primitives that make Ethereum secure. We will cover the use of algorithms <strong>ECDSA, BLS, keccak256</strong> and <strong>KZG</strong>. Moreover, we will take a look at how is Ethereum positioned again the quantum computers.</p>

<h2 id="used-primitives">Used primitives</h2>

<h3 id="ecdsa">ECDSA</h3>
<p><strong>ECDSA</strong> stands for the <em>Elliptic Curve Digital Signature Algorithm</em>. It is a variant of the Digital Signature Algorithm (DSA) which uses <em>Elliptic Curve Cryptography</em> (ECC) rather than the modular exponentiation found in earlier schemes like RSA. The advantage over RSA is that ECDSA can achieve the same level of security with a much smaller key size.</p>

<p>ECDSA is used to authorize state transitions initiated by network users (EOAs). In other words, it allows you to prove you are the owner of a wallet without revealing your private key. For every public key, there exists a corresponding private key. The private key is used to generate a signature, and the public key is sufficient to verify that the signature was generated by the owner.</p>

<p>Every time a user sends a transaction from an EOA address, they generate an ECDSA signature along with the transaction data to prove that they own the private key for that respective wallet. The core concept is that only the owner of the private key can generate a valid signature, but anyone can verify it. For each transaction, before submitting it to the execution layer, the node checks the validity of the signature. The user authorizes a transaction in the following way:</p>
<ol>
  <li><strong>Data preparation:</strong> the account nonce is attached to the transaction data (recipient, amount, …) to prevent double-spending</li>
  <li><strong>Hashing:</strong> the transaction data is hashed using <code class="language-plaintext highlighter-rouge">keccak256</code> to create a message digest</li>
  <li><strong>Signature Generation:</strong> apply the ECDSA algorithm on the message digest and private key to generate the resulting signature</li>
</ol>

<p>Ethereum relies on public key recovery. Unlike standard verification where the output of the verification is <code class="language-plaintext highlighter-rouge">true/false</code>, Ethereum transactions provide the signature, and the network recovers the public key from the signature. If that recovered address matches the <code class="language-plaintext highlighter-rouge">from</code> field in the transaction, the transaction is considered valid.</p>

<p>The security of ECDSA relies on the <strong>Elliptic Curve Discrete Logarithm Problem</strong> (ECDLP). This problem currently secures the vast majority of online communication. The algorithm relies on an algebraic structure where the elements are points on an elliptic curve. To understand this better take a look at my <a href="https://www.maya-zk.com/blog/math-toolkit">math toolkit post</a>. It is widely believed that calculating the discrete logarithm in this structure is infeasible because no we do not know any algorithm that would be significantly faster than brute-force. The specific curve used is <code class="language-plaintext highlighter-rouge">secp256k1</code> which is the same curve used in Bitcoin. The ECDLP is also referred to as a one way function because in this structure it is easy to perform exponentiation (multiplicative notation) but hard to compute the logarithm. For a deeper look into how this structure works check the blog post <a href="https://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/">“Elliptic Curve Cryptography: a gentle introduction”</a> by Andrea Corbellini.</p>

<h3 id="bls-signature-scheme">BLS signature scheme</h3>
<p>This is a different type of scheme with a property that allows signature aggregation. While ECDSA is used at the execution level this scheme is useful in proof of stake where validators attest to blocks. Ethereum has hundreds of thousands of validators who vote on the proposed blocks and the blocks need a supermajority to get finalized. The BLS signature aggregation is useful for attestations in Ethereum’s consensus layer, where validators vote on blocks every epoch. Checking all of the signatures from validators for each block would be computationally expensive to say the least.</p>

<p>This signature algorithm is fairly similar to ECDSA but the underlying curve <code class="language-plaintext highlighter-rouge">BLS12-381</code> has <em>“special pairing properties”</em>. This is well described in the blog <a href="https://hackmd.io/@benjaminion/bls12-381">BLS12-381 For The Rest Of Us</a>. The pairing describes a bilinear mapping of a sort. The important thing is that this maps points from a source group to a target group and this property is used in the verification procedure.</p>

<p>In this case aggregation means that all signatures over the same message or vote can be checked with a single verification and the size of the signature is not related to the number of signers. This is because the whole algebraic structure has linearity and the public keys can be summed up into a new key. To the scheme this aggregate key is not different from any other key.</p>

<p>Besides this usage there is also RANDAO which is the source of randomness in Ethereum. For example, a block proposer is randomly selected and the randomness is determined by this mechanism. The usage of BLS is crucial here because BLS signatures are unique and deterministic. Unlike ECDSA where you can create many valid signatures for one message using different random nonces, a BLS signature is fixed for a given key and message. This prevents the block proposer from manipulating the randomness by trying different signatures until they get a favorable result. If you are interested in how this works here is a really nice explanation in <a href="https://eth2book.info/latest/part2/building_blocks/randomness/">the ethbook</a></p>

<p>The hardness of the BLS signatures comes from the Computational Diffie Hellman assumption and Pairing Inversion. The first assumes that even if you know the public points corresponding to secret numbers it is hard to compute the point corresponding to their product. The second states that the pairing function itself acts as a one way function meaning you cannot reverse the mapping to recover the original inputs from the target group.</p>

<h3 id="keccak-256-hash-function">Keccak-256 hash function</h3>
<p>Now we get to the hash function of choice. We have already seen this in the ECDSA section where the signature is created from the message digest, where the digest is generated by the hash function. This is only one of many places where the hash function is used. The hash function takes arbitrary length input and generates a 256-bit output deterministically.</p>

<p>The prime concept in blockchain is that each block has a hash that is in a way dependent on the hash of the previous block. This chain of dependencies makes it possible to easily check if the finalized data has been modified. If any data in a previous block were modified, its hash would change, and all subsequent blocks would become invalid. This mechanism gives a blockchain immutability. Likewise we utilize hashes on transactions to make sure that the transaction details were not modified.</p>

<p>Another important use is <strong>address derivation</strong>. User addresses (EOAs) are derived by taking the Keccak-256 hash of a public key and keeping only the last few bytes. Contract addresses are similarly derived using Keccak-256 over a combination of the creator’s address and nonce. This ensures that addresses are deterministic, unique, and infeasible to predict or reverse-engineer in a meaningful way.</p>

<p>Another important part is the connection to Merkle trees. The data is split into pieces, hashed, and arranged into a tree, and a root hash commits to the entire set of data in the leaves of the tree. This is used to commit to Ethereum’s entire state. These roots act as compact commitments to millions of accounts and transactions and allow nodes to prove the inclusion or absence of data using Merkle proofs. Moreover, the <code class="language-plaintext highlighter-rouge">keccak256</code> hash is used in EVM to handle smart contract storage locations, but you get the point…the hash function is a crucial building block.</p>

<p>From a security perspective, it is designed to satisfy the standard assumptions for cryptographic hash functions:</p>
<ul>
  <li><strong>Preimage resistance:</strong> given a hash output $y$, it is computationally infeasible to find an input $x$ such that $h(x) = y$</li>
  <li><strong>Second preimage resistance:</strong> given an input $x$, it is infeasible to find a different input $x’$ such that $h(x) = h(x’)$</li>
  <li><strong>Collision resistance:</strong> it is infeasible to find <strong><em>any</em></strong> two distinct inputs $x, x’$ such that $h(x) = h(x’)$</li>
</ul>

<p>Probabilistically from the <a href="https://en.wikipedia.org/wiki/Birthday_problem">birthday paradox</a>, we would need to try approximately $2^{128}$ hashes in order to get a collision, which is beyond feasible. Now where does this function come from, how does it work, and why do we trust it? <code class="language-plaintext highlighter-rouge">Keccak256</code> is closely related to the standardized <code class="language-plaintext highlighter-rouge">SHA-3</code>. This is the winning proposal for NIST’s standardized hash function competition where leading cryptographers submitted their proposals for an optimal hash function. Generally there is a consensus that this is safe to use. To get more information on the proposal you can directly check the <a href="https://keccak.team/keccak.html">keccak team website</a>.</p>

<h3 id="kzg-commitment-scheme">KZG commitment scheme</h3>
<p>The KZG commitment scheme is one of the main components in Ethereum’s scaling roadmap. Specifically <em>Danksharding</em> provides a way to verify that large chunks of data have been published without requiring every node to process that data permanently.</p>

<p>To understand why KZG is necessary, we briefly explain how Layer 2 (L2) scaling works. L2s execute transactions off-chain to save costs. However, these transactions are secure only if an independent observer can verify that they are not fraudulent. If a Layer 2 sequencer claims, <em>“I processed $n$ transactions and the new state is $X$,”</em> but refuses to show the actual transaction data, no one can verify if they are telling the truth. Data availability guarantees that the transaction data is public so that honest actors can audit the state and dispute it if necessary. To get deeper into danksharding check the <a href="https://a16zcrypto.com/posts/article/an-overview-of-danksharding-and-a-proposal-for-improvement-of-das/">a16z post on danksharding</a>.</p>

<p>Traditionally, data integrity is enforced using Merkle trees. While secure, Merkle proofs grow logarithmically $\mathcal{O}(\log(n))$ with the size of the data. KZG allows committing to an entire blob using a <strong>single, constant-size commitment</strong>, and later verifying parts of that blob with <strong>constant-size proofs</strong>, independent of the blob size.</p>

<p>To oversimplify, KZG represents the data blob as a mathematical polynomial $f(x)$. The commitment is essentially the evaluation of this polynomial at a specific secret value. Later, when a verifier wants to check that a certain piece of data belongs to the committed blob, the prover generates a small <em>opening proof</em> showing that the polynomial really evaluates to a certain value at a given point. The verifier can check this with a constant number of pairing operations. If even a single coefficient of the polynomial was modified, it would be computationally infeasible to create a valid proof for the original commitment. So if the data was changed, it would be almost impossible to construct a valid proof for the original commitment. For a clear description of KZG see <a href="https://www.maya-zk.com/blog/kzg">my detailed write-up</a> from a different blog.</p>

<p>When an L2 is acting honestly, the flow is as follows:</p>
<ol>
  <li><strong>Batching:</strong> L2 transactions are batched into a “blob”</li>
  <li><strong>Commitment:</strong> the polynomial commitment to the blob is calculated</li>
  <li><strong>Submission:</strong> the transaction (containing the blob and the commitment) is sent to Ethereum</li>
  <li><strong>Verification:</strong> Ethereum nodes verify that the blob data matches the commitment. The network guarantees the data is available for a temporary period</li>
  <li><strong>Finalization:</strong> The state proposed by the L2 is tentatively accepted. Because the data is guaranteed to be available, other actors can download it, re-execute the transactions, and raise a dispute.</li>
</ol>

<p>The KZG polynomial commitment scheme has multiple security properties which assume the ECDLP mentioned before and also a variant of the stong Diffie-Hellman Assumption, which is more specific than computational Diffie-Hellman Assumption for ECDSA.</p>

<h3 id="summary">Summary</h3>

<h4 id="bits-of-security">Bits of security</h4>
<p>Talk about the economic security as well at cryptographic security.</p>

<h4 id="hardness-of-the-internet">Hardness of the internet</h4>
<p>Before we sum up the primitives used in Ethereum, we make a quick detour to the traditional internet. The <strong>Transport Layer Security (TLS)</strong> is the cryptographic protocol that essentially secures the communication over the Internet. The protocol is used heavily in email, instant messaging, but most importantly, <strong>HTTPS</strong>. HTTPS is the implementation of TLS encryption layered on top of the traditional HTTP protocol.</p>

<p>Every time you see <code class="language-plaintext highlighter-rouge">https://</code> in your browser, that communication is secured with TLS. In the most recent version, <strong>TLS 1.3</strong>, there has been a shift towards exclusively using Elliptic Curve Cryptography. From <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-1.2">the specification</a> of comparing TLS 1.2 to TLS 1.3:</p>

<blockquote>
  <p>“Elliptic curve algorithms are now in the base spec, and new signature algorithms, such as EdDSA, are included…”</p>
</blockquote>

<p>There are multiple supported curves. If you want, you can check the specification yourself in <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.7">section 4.2.7</a>. Without getting tangled up in the details on which specific curve is being used where, we can oversimplify and claim that the ECDLP is the hardness problem of choice for internet communication. Now for an experiment, try going to your internet banking and check the connection you have to the banking server. Most probably this is going to be <code class="language-plaintext highlighter-rouge">https</code> that is relying on elliptic curve cryptography. From this, you can see that if people can break the hardness assumptions, there is not only a problem with Ethereum but also with your bank.</p>

<p>Now to sum it up, Ethereum relies on the ECDLP along with Computational and Strong Diffie-Hellman Assumptions. The <strong>Strong Diffie-Hellman assumption</strong> is more specific or rather mathematically a stronger assumption than the computational variant.</p>

<p>Now there is also the hash function, right? There is a slight difference between <code class="language-plaintext highlighter-rouge">keccak256</code> and the accepted SHA-3, however this does not affect the security. While SHA-2 still remains very commonly used, the security of SHA-3 is assumed to be higher. These hash functions use entirely different constructions and while SHA-2 remains the most popular, SHA-3 is more secure.</p>

<table>
  <thead>
    <tr>
      <th>Primitive</th>
      <th>Primary Use Case</th>
      <th>Underlying Structure</th>
      <th>Hardness Assumption</th>
      <th>Key Properties</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>ECDSA</strong></td>
      <td>Transaction authorization (EOAs), wallet ownership proof</td>
      <td><code class="language-plaintext highlighter-rouge">secp256k1</code> elliptic curve</td>
      <td>ECDLP</td>
      <td>Public key recovery, deterministic per nonce</td>
    </tr>
    <tr>
      <td><strong>BLS Signatures</strong></td>
      <td>Validator attestations, RANDAO</td>
      <td><code class="language-plaintext highlighter-rouge">BLS12-381</code> curve with pairing properties</td>
      <td>ECDLP + Computational Diffie-Hellman, Pairing Inversion</td>
      <td>Signature aggregation, deterministic and unique</td>
    </tr>
    <tr>
      <td><strong>KZG Commitments</strong></td>
      <td>Data availability, blob verification (EIP-4844)</td>
      <td>Polynomial commitments over elliptic curves</td>
      <td>ECDLP + Strong Diffie-Hellman</td>
      <td>Constant-size commitments and proofs</td>
    </tr>
    <tr>
      <td><strong>Keccak-256</strong></td>
      <td>Block hashing, address derivation, Merkle trees, state roots</td>
      <td>Sponge construction (SHA-3 family)</td>
      <td>Preimage, second preimage, collision resistance</td>
      <td>Deterministic 256-bit output, one-way function</td>
    </tr>
  </tbody>
</table>

<p>To sum it up, if these problems are broken, the internet has a problem and with it goes Ethereum. For now we can overgeneralize to say that the hardness of Ethereum is actually breaking the internet:)</p>

<h2 id="post-quantum-future">Post-quantum future</h2>
<p>Okay great, now one question that is going to come up in this conversation is what about quantum computers. The truth is that Ethereum is not post-quantum secure. The cryptographic primitives we discussed, ECDSA, BLS signatures, and KZG commitments, all rely on hardness assumptions that a sufficiently powerful quantum computer could break. In the roadmap highlighted by Vitalik, the transition to post-quantum schemes is planned for the last phase, <a href="https://vitalik.eth.limo/general/2024/10/29/futures6.html"><em>the Splurge</em></a>.</p>

<p>There is a development team working on Lean Consensus that aims to ship post-quantum security among other improvements. Justin Drake and his team have been developing this lean Ethereum consensus protocol that should address quantum vulnerabilities while also improving efficiency and simplicity. You can follow their progress and technical specifications at <a href="https://leanroadmap.org/">leanroadmap.org</a>.</p>

<h3 id="starks">STARKs</h3>
<p>reed-colomon proximity conjecture 
hash based things etc etc</p>

<h3 id="quantum-emergency">Quantum emergency</h3>

<p>However, there has already been <a href="https://ethresear.ch/t/how-to-hard-fork-to-save-most-users-funds-in-a-quantum-emergency/18901">some discussion</a> on Ethereum research forums about what to do in case of an overnight quantum breakthrough. The scenario being planned for is one where quantum computers suddenly become capable of breaking ECDLP, which would allow attackers to derive private keys from public keys and steal funds from any address that has ever made a transaction. In this emergency case, there would be a handbrake mechanism in the form of a hard fork. The proposed recovery mechanism is as follows:</p>
<ol>
  <li><strong>Freeze and rollback:</strong> The chain undergoes a hard fork that rolls back to the state before the chain was affected</li>
  <li><strong>Disable EOAs:</strong> The fork disables all traditional transaction types. You can no longer send ETH by simply signing with your private key because the attacker can forge this.</li>
  <li><strong>Prove ownership:</strong> Users cannot safely reveal their private key. However, they <em>can</em> prove they know the seed phrase that generated their address. A new transaction type is added where users submit a <strong>STARK proof</strong>. This proof mathematically demonstrates: <em>“I know the seed phrase that hashes to this address”</em> without revealing the seed phrase itself.</li>
  <li><strong>Migration:</strong> Successful proof submission moves the user’s funds into a new wallet equipped with a quantum-safe signature scheme</li>
</ol>

<hr />

<p>In this article we explored the cryptographic primitives securing Ethereum: ECDSA for transactions, BLS signatures for consensus, KZG commitments for data availability, and Keccak-256 for hashing. These all rely on the similar hardness assumptions protecting the internet you use every day for everything. Breaking Ethereum means breaking the internet. Looking ahead, teams of really smart people are working on new cryptography to future-proof against tomorrow’s computers. It seems that the security of the protocol has a bright future.</p>

<p>Thanks for reading. In case I am spreading any misinformation, feel free to contact me.</p>

<blockquote>
  <p>This post was written as an outcome of the <a href="https://nxbn.ethereum.foundation/scholars">Ethereum Devconnect Scholars</a> programme. Huge thanks to Ethereum Foundation for making it possible for me to attend the Devconnect 2025&lt;3</p>
</blockquote>

<p><img src="/assets/images/blogs/devconnect25-scholars.jpg" alt="Devconnect Scholars 2025" /></p>]]></content><author><name></name></author><category term="Ethereum" /><category term="Non-Technical" /><summary type="html"><![CDATA[A user-friendly overview of cryptographic primitives of Ethereum.]]></summary></entry><entry><title type="html">PlonK deconstructed</title><link href="https://juliakrizanova.github.io/blogs/plonk-deconstructed.html" rel="alternate" type="text/html" title="PlonK deconstructed" /><published>2024-03-13T00:00:00+00:00</published><updated>2024-03-13T00:00:00+00:00</updated><id>https://juliakrizanova.github.io/blogs/plonk-deconstructed</id><content type="html" xml:base="https://juliakrizanova.github.io/blogs/plonk-deconstructed.html"><![CDATA[<p>This blog-series was written in collaboration with Maya-ZK. You can find it on their page:</p>

<ol>
  <li><a href="https://www.maya-zk.com/blog/plonk-overview">Overview</a></li>
  <li><a href="https://www.maya-zk.com/blog/arithmetization">Arithmetization</a></li>
  <li><a href="https://www.maya-zk.com/blog/math-toolkit">Math Preliminaries</a></li>
  <li><a href="https://www.maya-zk.com/blog/kzg">KZG</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-setup">Setup</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-round1">Round 1</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-round2">Round 2</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-round3">Round 3</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-round4">Round 4</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-round5">Round 5</a></li>
  <li><a href="https://www.maya-zk.com/blog/plonk-verifier">Verification</a></li>
</ol>]]></content><author><name></name></author><category term="deep-dive" /><category term="zkSNARK" /><category term="algebra" /><summary type="html"><![CDATA[11-part blog series explaining the internals of the PlonK protocol.]]></summary></entry></feed>