<?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://blog.nearone.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.nearone.org/" rel="alternate" type="text/html" /><updated>2026-03-30T11:45:33+00:00</updated><id>https://blog.nearone.org/feed.xml</id><title type="html">Near One Blog</title><subtitle>Near One is the tech research and development team driving innovation of key NEAR Protocol infrastructure.</subtitle><entry><title type="html">Benchmarking Threshold Signature Schemes</title><link href="https://blog.nearone.org/r&d/2026/03/29/benchmarking-models.html" rel="alternate" type="text/html" title="Benchmarking Threshold Signature Schemes" /><published>2026-03-29T22:00:00+00:00</published><updated>2026-03-29T22:00:00+00:00</updated><id>https://blog.nearone.org/r&amp;d/2026/03/29/benchmarking-models</id><content type="html" xml:base="https://blog.nearone.org/r&amp;d/2026/03/29/benchmarking-models.html"><![CDATA[<p><a href="https://docs.near.org/chain-abstraction/chain-signatures">NEAR Chain Signatures</a> enables signing and executing transactions across multiple blockchain protocols. The technology relies heavily on <a href="https://en.wikipedia.org/wiki/Threshold_cryptosystem">threshold cryptography</a>, using signature schemes such as <a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf">ECDSA and EdDSA</a>. This blog post explores how newly integrated schemes can help scale our systems, demonstrating their impact through practical benchmarking techniques and results.<!--more--></p>

<p>Threshold signing allows multiple parties, each holding a secret key share, to jointly compute a signature over a specific message. Such a technology guarantees cryptographically the possibility of generating valid signatures <strong>if and only if</strong>
at least $t$ participants actively take part in the signing process, where $t$ is the system’s threshold parameter.
In other words, any $t-1$ malicious parties would fail to generate a valid signature without the inclusion of one honest signer.</p>

<p>Our system maintains a pool of eight signers. For each protocol instance, a subset of five signers is selected to participate, and operations require approval from all five participants (i.e., a 5-of-5 configuration).
As we scale, we plan to increase the total number of available signers while preserving strong security guarantees and high performance.</p>

<p>A preliminary performance assessment revealed that the primary bottlenecks stem from the complexity of our deployed ECDSA scheme (based on <a href="https://github.com/cronokirby/cait-sith">Cait-Sith implementation</a>), which we refer to as OT-Based ECDSA. This protocol requires more than eleven computation-heavy communication rounds and exhibits quadratic communication overhead with respect to the number of active participants. We split this scheme (as well as all other schemes)
into two phases: an offline phase and an online phase. The offline phase is a precomputation stage that occurs before the message is known to the protocol participants. During this phase, participants collaboratively generate cryptographic assets that will be needed in the online phase. Once the message becomes known to the signers, the online phase begins, and signatures can be produced and delivered with minimal delay, since all necessary computations have already been performed in advance. In this blog post, all the schemes mentioned require a <strong>presigning protocol (Presign)</strong>, which is part of the offline phase, and a <strong>signing protocol (Sign)</strong>, which belongs to the online phase. Additionally, the OT-Based ECDSA scheme requires a <strong>Two Triple Generation</strong> protocol during the offline phase, which must be executed before the presigning protocol.</p>

<p>To address the OT-Based ECDSA performance limitations, we <a href="https://github.com/near/mpc/tree/main/crates/threshold-signatures">implemented</a> and evaluated a more efficient ECDSA construction based on [<a href="https://eprint.iacr.org/2020/501">DJNPO20</a>], which we refer to as Robust ECDSA.
To quantify the expected improvements, we developed a dedicated benchmarking methodology.</p>

<p>This blog post presents our approach, assumptions, and findings and shows results for the OT-Based ECDSA, Robust ECDSA and (for completeness) our EdDSA implementation based on FROST [<a href="https://eprint.iacr.org/2020/852">KG20</a>].</p>

<h2 id="benchmarking">Benchmarking</h2>

<p>Prior to this work, we had multiple threshold signature schemes implemented, but no systematic way to compare their practical performance. A key challenge in this setting is that these protocols are inherently distributed, yet benchmarking them on multiple machines is costly and difficult to control. As a result, our goal was to design benchmarking methodologies that run on a <strong>single</strong> machine while still providing results that are representative of real-world deployments.</p>

<p>To address this gap, we designed two benchmarking approaches based on Rust’s <a href="https://docs.rs/criterion/latest/criterion/">Criterion</a> framework to measure computation time. To ensure a fair comparison between the implemented schemes, we evaluated them under a shared invariant: the security level, defined as the maximum number of malicious parties tolerated.</p>

<p>Importantly, for a fixed maximum number of malicious parties, different schemes may require different numbers of active participants. Our methodology accounts for this distinction, enabling an apples-to-apples comparison at an equivalent security level. Naturally, derandomizing our algorithms was a necessary precondition for producing consistent results, thereby improving the reliability of our benchmarks by eliminating protocol-induced randomness.</p>

<p>Our first approach measures the end-to-end execution time of each scheme by running all participants on a single machine. We refer to this as the naive technique. While straightforward and fast to implement, this method does not fully reflect realistic deployment conditions and may therefore produce results that are only partially representative. In the following section, we describe how this technique operates in practice and explain why we classify it as “naive.”</p>

<p>We then designed and implemented a more representative, advanced technique. This approach captures a snapshot of the communication during protocol execution and subsequently replays the protocol from the perspective of a single participant using the recorded data.
By doing so, we are able to isolate and measure the per-participant computational cost, while still accounting for network latency and communication volume. The advantage of this approach is that it provides a more accurate approximation of real-world performance without requiring a fully distributed deployment for each benchmark run.</p>

<p>All benchmarks were executed on a laptop equipped with an AMD Ryzen 7 7730U processor with Radeon Graphics and 16 GB of RAM with a clock speed ranging between 3.2-4.3 GHz. Each experiment was run for a minimum of 15 iterations to ensure statistically meaningful results.</p>

<h2 id="naive-technique">Naive Technique</h2>

<p>A straightforward way to benchmark our schemes is to execute the entire protocol with all participants running side by side and then analyze the aggregated results. We refer to this approach as naive for several reasons:</p>

<ol>
  <li>
    <p><strong>Sequential execution distorts computation costs.</strong><br />
Participants are executed sequentially in a single environment. When combined with the quadratic or cubic communication complexity of some protocols, this makes it difficult to isolate per-participant computation time.</p>
  </li>
  <li>
    <p><strong>Potential unfairness across schemes.</strong><br />
Different signature schemes may require different numbers of active participants to tolerate the same maximum number of malicious parties. In our naive benchmarking approach, all participants are executed sequentially on a single machine, causing the total runtime to scale with the number of participants. This can introduce bias when comparing schemes, as protocols requiring more participants may appear slower, even though in a real distributed deployment these participants would operate in parallel.</p>
  </li>
  <li>
    <p><strong>Overcounting communication costs.</strong><br />
When a participant broadcasts a message to all others, the same send operation is effectively measured multiple times, artificially inflating the perceived communication overhead.</p>
  </li>
</ol>

<p>The table below reports the results of our Criterion benchmarks for Robust ECDSA and OT-Based ECDSA, with the maximum number of malicious parties fixed at 6.</p>

<table>
  <caption style="caption-side: bottom; font-weight: bold; padding-bottom: 0.5em;">
    Naive benchmarking of ECDSA threshold signing schemes. <br />
    Maximum number of malicious parties: 6
  </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th rowspan="2" style="text-align:center;">Parties</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">7</td>
      <td style="text-align:center;">1.4237 s</td>
      <td style="text-align:center;">1.4626 ms</td>
      <td style="text-align:center;">191.82 µs</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">13</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">66.060 ms</td>
      <td style="text-align:center;">278.13 µs</td>
    </tr>
  </tbody>
</table>

<p>In this configuration, Robust ECDSA requires 13 active participants to tolerate 6 malicious parties, whereas the OT-Based scheme requires only 7. Despite the higher number of participants, Robust ECDSA demonstrates substantially faster offline phase performance by eliminating the costly “Two Triples Generation” phase.</p>

<h2 id="advanced-technique">Advanced Technique</h2>

<p>A more accurate benchmarking approach is the <strong>snap-then-simulate</strong> method. In this setup, the protocol is executed with a single real participant (the coordinator), while all other participants are simulated.</p>

<p>We study protocol scalability by increasing the number of simulated participants within this framework. Although only one participant runs as a real process, the simulator emulates the interactions of all other parties with the real participant. As the number of participants grows, the real participant must handle a proportionally larger workload: it processes more messages, performs more computation, and sends/receives more data over the network. This allows us to faithfully capture how the per-participant computational and communication costs scale with the total number of participants.</p>

<p>More specifically, we first enabled derandomization of our algorithms during benchmarking to ensure reproducibility. Next, we implemented a function that executes a protocol with all participants and records all exchanged messages in a dictionary. We then developed the simulator logic, including a function that allows the simulator to respond to a real participant in a simplified, dummy manner using the previously captured snapshot data.</p>

<p>During the second (simulated) run, we benchmarked the real participant’s performance using Criterion. Additionally, we enabled measurement of the data volume received by each participant throughout the protocol execution.</p>

<h3 id="why-is-this-technique-better-than-the-naive-one">Why is this technique better than the naive one?</h3>

<ol>
  <li>
    <p><strong>Fair benchmarking across protocols.</strong><br />
Even when one scheme requires more participants than another, this method focuses on the performance of a single real participant rather than measuring all participants simultaneously.</p>
  </li>
  <li>
    <p><strong>Accurate representation of $O(n^2)$ communication costs.</strong><br />
By simulating all-but-one participants, we avoid artificially inflating communication complexity. This reduces the protocol from $O(n^2)$ to $O(n)$ for benchmarking purposes, allowing a clearer focus on the real participant’s computation and communication.</p>
  </li>
  <li>
    <p><strong>Easy measurement of data transmitted.</strong><br />
The snap-then-simulate approach makes it straightforward to track the amount of data sent and received by each participant during a protocol run.</p>
  </li>
</ol>

<p><br /></p>

<h3 id="results--analysis">Results &amp; Analysis</h3>

<p>In this section, we present selected benchmark results. The tables below show the time required for a single participant (or coordinator, where applicable) to complete each protocol. These measurements were obtained using the advanced snap-then-simulate benchmarking technique, providing a more accurate and representative view of per-participant performance.</p>

<table>
  <caption style="caption-side: bottom; font-weight: bold; padding-bottom: 0.5em;">
    Advanced benchmarking of both ECDSA and FROST threshold signing schemes.<br />
    Maximum number of malicious parties: 6 &nbsp;&nbsp;|&nbsp;&nbsp; Network Latency: 0 ms
  </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th rowspan="2" style="text-align:center;">Parties</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">7</td>
      <td style="text-align:center;">198.95 ms</td>
      <td style="text-align:center;">206.52 µs</td>
      <td style="text-align:center;">111.76 µs</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">13</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">4.90 ms</td>
      <td style="text-align:center;">114.63 µs</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>FROST</strong></td>
      <td style="text-align:center;">7</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">419.23 µs</td>
      <td style="text-align:center;">348.94 µs</td>
    </tr>
  </tbody>
</table>

<p>Note that in the naive benchmarking, the time reported for <strong>Two Triples Gen</strong> and <strong>Presign</strong> roughly corresponds to the time measured in the advanced setting multiplied by the number of active participants, i.e., 
\(\text{time}_{\text{naive}} \approx \text{time}_{\text{advanced}} \times \text{number\_of\_participants}\)</p>

<p>For a higher number of tolerated malicious parties, the measured results are as follows:</p>

<table>
  <caption style="caption-side: bottom; font-weight: bold; padding-bottom: 0.5em;">
    Advanced benchmarking of both ECDSA and FROST threshold signing schemes.<br />
    Maximum number of malicious parties: 15 &nbsp;&nbsp;|&nbsp;&nbsp; Network Latency: 0 ms
  </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th rowspan="2" style="text-align:center;">Parties</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">16</td>
      <td style="text-align:center;">544.94 ms</td>
      <td style="text-align:center;">257.05 µs</td>
      <td style="text-align:center;">119.65 µs</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">31</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">24.56 ms</td>
      <td style="text-align:center;">129.45 µs</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>FROST</strong></td>
      <td style="text-align:center;">16</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">964.76 µs</td>
      <td style="text-align:center;">590.34 µs</td>
    </tr>
  </tbody>
</table>

<p>The Robust ECDSA offline phase is <strong>40× faster</strong> than that of OT-based ECDSA when the maximum number of malicious parties is 6, and <strong>22× faster</strong> when the maximum rises to 15. We attribute this difference primarily to the increasing number of active participants required in the Robust ECDSA setting which relies on honest majority assumptions.</p>

<p><br /></p>

<h3 id="latency">Latency</h3>

<p>Because the computation time of both schemes is relatively small, adding network latency has a proportionally larger impact, effectively dominating the total measured time. As a result, the observed performance under latency depends primarily on the number of communication rounds each scheme requires. The table below provides the number of rounds per protocol run:</p>

<table>
  <caption style="caption-side: bottom; font-weight: bold; padding-bottom: 0.5em;">
    Number of rounds of threshold signing schemes. <br />
    <span style="font-size: 0.85em; font-weight: normal; font-style: italic;">
    *Note: The OT-Based ECDSA Two Triples generation scheme requires more than 8 rounds of communication to complete. This is an estimation that gives a good idea of the network latency cost on the benchmarking.
    </span>
  </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">8*</td>
      <td style="text-align:center;">2</td>
      <td style="text-align:center;">1</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">3</td>
      <td style="text-align:center;">1</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>FROST</strong></td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">1</td>
      <td style="text-align:center;">1</td>
    </tr>
  </tbody>
</table>

<p>Our implementation does not yet support explicitly adding latency to the communication but we
are currently working on this feature. However, when accounting for network latency,
the total execution time can be estimated using the following formula:
$\text{latency} \times \text{rounds} + \text{computation}$.</p>

<p>For example, we have the following results:</p>

<table>
  <caption style="caption-side: bottom; font-weight: bold; padding-bottom: 0.5em;">
    Advanced benchmarking of both ECDSA and FROST threshold signing schemes.<br />
    Maximum number of malicious parties: 15 &nbsp;&nbsp;|&nbsp;&nbsp; Network Latency: 100 ms
  </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th rowspan="2" style="text-align:center;">Parties</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">16</td>
      <td style="text-align:center;">1.344 s</td>
      <td style="text-align:center;">200.25 ms</td>
      <td style="text-align:center;">100.11 ms</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">31</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">324.56 ms</td>
      <td style="text-align:center;">100.12 ms</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>FROST</strong></td>
      <td style="text-align:center;">16</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">100.96 ms</td>
      <td style="text-align:center;">100.59 ms</td>
    </tr>
  </tbody>
</table>

<p>Notice that the Robust ECDSA offline phase is roughly <strong>4.7×</strong> faster than the OT-Based ECDSA offline phase.
In fact, as network latency increases, the ratio approaches <strong>3.3×</strong>, reflecting the ratio of communication rounds (10 vs 3).</p>

<p><br /></p>

<h3 id="bandwidth">Bandwidth</h3>

<p>In real systems, scalability is often limited by network bandwidth. To explore this, we measured the total amount of data received by a real participant during a snap-then-simulate protocol run. Inferring data per individual round from the snapshots would require a major refactor of our cryptographic communication channels—a complex task that could introduce breaking changes in our deployed product. As a result, computing per-participant, per-round data is ongoing work and is not included in this post. Instead, we report the total data received over the entire protocol execution.</p>

<p>For protocols that distinguish a coordinator from regular participants, we specifically measured the data received by the coordinator. As expected, the coordinator receives more data than other participants, since it is the only party capable of producing a signature. Across our benchmark runs, the amount of data sent was stable, with zero variance across iterations.</p>

<p>Additionally, the reported sizes are expressed in bytes and include the metadata (e.g., receiver ID, session number, etc.) sizes.
The measurements reflect the raw data transmitted by the protocol itself, including application-level metadata such as the sender, receiver, and session identifiers. They do not include transport-layer overhead such as TLS or TCP headers, which would slightly increase the total byte count in a real deployment.</p>

<table>
    <caption style="caption-side: bottom; font-size: 0.9em; font-weight: bold; padding-bottom: 0.5em;">
    Data received by the real participant when the system is configured with 6 malicious parties and no latency.
    The unit of measurement is bytes.<br />
    </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th rowspan="2" style="text-align:center;">Parties</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">7</td>
      <td style="text-align:center;">595260</td>
      <td style="text-align:center;">1416</td>
      <td style="text-align:center;">557</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">13</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">6387</td>
      <td style="text-align:center;">1096</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>FROST</strong></td>
      <td style="text-align:center;">7</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">918</td>
      <td style="text-align:center;">609</td>
    </tr>
  </tbody>
</table>

<table>
    <caption style="caption-side: bottom; font-size: 0.9em; font-weight: bold; padding-bottom: 0.5em;">
    Data received by the real participant when the system is configured with 15 malicious parties and no latency.
    The unit of measurement is bytes.<br />
    </caption>
  <thead>
    <tr>
      <th rowspan="2" style="text-align:center;">Scheme</th>
      <th rowspan="2" style="text-align:center;">Parties</th>
      <th colspan="2" style="text-align:center;">Offline Phase</th>
      <th style="text-align:center;">Online Phase</th>
    </tr>
    <tr>
      <th style="text-align:center;">Two Triples Generation</th>
      <th style="text-align:center;">Presign</th>
      <th style="text-align:center;">Sign</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align:center;"><strong>OT-Based ECDSA</strong></td>
      <td style="text-align:center;">16</td>
      <td style="text-align:center;">2088966</td>
      <td style="text-align:center;">3485</td>
      <td style="text-align:center;">1360</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>Robust ECDSA</strong></td>
      <td style="text-align:center;">31</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">15986</td>
      <td style="text-align:center;">2752</td>
    </tr>
    <tr>
      <td style="text-align:center;"><strong>FROST</strong></td>
      <td style="text-align:center;">16</td>
      <td style="text-align:center;">N/A</td>
      <td style="text-align:center;">2274</td>
      <td style="text-align:center;">1526</td>
    </tr>
  </tbody>
</table>

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

<p>The primary goal of this work was to determine whether the Robust ECDSA scheme can replace OT-based ECDSA as a more scalable and efficient alternative. Our benchmarking results confirm that it can: Robust ECDSA outperforms OT-based ECDSA across all measured dimensions — latency, communication rounds, and bandwidth. With 15 maximum malicious parties and 100 ms of latency, the Robust ECDSA offline phase is approximately 4.7× faster than OT-based ECDSA and transmits 130× less data over the network. We found no scenario in which OT-based ECDSA holds an advantage.</p>

<p>It is important to note that FROST is not directly comparable to the ECDSA schemes, as it implements EdDSA — a fundamentally different signature algorithm operating over a different curve. The choice between ECDSA and EdDSA is dictated by the target blockchain’s requirements, not by performance alone. We include FROST in our benchmarks to provide a complete picture of the threshold signing implementations available in our system. That said, FROST’s results are noteworthy: with only one communication round in the offline phase, it achieves 3–15× lower latency than both ECDSA variants while maintaining comparable online-phase performance and low bandwidth usage. For chains that support EdDSA, FROST is the most lightweight option available.</p>

<p>Overall, both Robust ECDSA and FROST exhibit reasonable bandwidth usage as the number of participants grows, demonstrating that our system is well-positioned to scale further with an increasing number of active signers.</p>

<hr />]]></content><author><name>Simon Rastikian</name></author><category term="R&amp;D" /><summary type="html"><![CDATA[NEAR Chain Signatures enables signing and executing transactions across multiple blockchain protocols. The technology relies heavily on threshold cryptography, using signature schemes such as ECDSA and EdDSA. This blog post explores how newly integrated schemes can help scale our systems, demonstrating their impact through practical benchmarking techniques and results.]]></summary></entry><entry><title type="html">Confidential Key Derivation in the MPC Network</title><link href="https://blog.nearone.org/research/2026/02/23/mpc-ckd.html" rel="alternate" type="text/html" title="Confidential Key Derivation in the MPC Network" /><published>2026-02-23T15:00:00+00:00</published><updated>2026-02-23T15:00:00+00:00</updated><id>https://blog.nearone.org/research/2026/02/23/mpc-ckd</id><content type="html" xml:base="https://blog.nearone.org/research/2026/02/23/mpc-ckd.html"><![CDATA[<p>Imagine an application running inside a Trusted Execution Environment (TEE)
that needs to encrypt its storage. If the app migrates to a different machine or
reboots, it must recover the exact same encryption key, without ever having
stored it. How can such an app obtain a deterministic, confidential key from a
decentralized network, without any single party ever seeing it?
<!--more--></p>

<p>This is the problem that <strong>confidential key derivation (CKD)</strong> solves. In this
post we describe how we designed and implemented CKD as a new feature of NEAR
Protocol’s
<a href="https://docs.near.org/chain-abstraction/chain-signatures">Chain Signatures</a>,
an implementation of threshold signature schemes secured by a Multi-Party
Computation (MPC) network. Chain Signatures allow smart contracts and accounts
on the NEAR blockchain to securely authorize transactions on external,
heterogeneous blockchains without relying on centralized custody or wrapping
assets.</p>

<p>At its core, CKD turns our MPC network into a decentralized key management
service (KMS). It offers authenticated clients the ability to derive
cryptographic keys for various purposes, such as data-at-rest encryption. These
keys are available to any authenticated client at any time and are protected
against corruption of individual nodes. The keys supplied by the system are
deterministic and remain confidential by using public key encryption during the
generation process. Determinism is a critical property, particularly for
TEE-based applications, where consistent key derivation is essential.</p>

<h1 id="confidential-key-derivation-ckd-">Confidential Key Derivation (CKD) <!-- markdownlint-disable-line MD025 --></h1>

<p>The confidential key derivation feature is an extension of the current MPC
system. At its core it uses threshold
<a href="https://en.wikipedia.org/wiki/BLS_digital_signature">BLS signatures</a>. On a high
level, it provides authenticated applications with deterministic secrets.
Deterministic means that the same application may request the same secret at
different points in time. Confidential means that the secret itself is never
revealed to any entity other than the application itself, not even to individual
nodes in the MPC network.</p>

<h2 id="applications">Applications</h2>

<p>Our main use-case for CKD is to allow any app running inside a
<a href="https://en.wikipedia.org/wiki/Trusted_execution_environment">TEE</a> (for example,
<a href="https://www.intel.com/content/www/us/en/developer/tools/trust-domain-extensions/overview.html">Intel TDX</a>)
to have a deterministically derived key that is unique to the app and not
specific to the TEE. The app can derive the exact same key as many times as it
desires even when running on a different TEE. To obtain the key, the app can
leverage the CKD functionality in the NEAR protocol blockchain. This key can be
used, for example, to encrypt its storage, so that it can be decrypted later in
another machine while remaining confidential.</p>

<p>More generally, creating a service that can supply confidential deterministic
keys in the NEAR blockchain unlocks a massive range of decentralized
possibilities. For example, it can be used to enable
<a href="https://en.wikipedia.org/wiki/Time-lock_puzzle">time-lock encryption</a>. A
smart contract could encrypt data under a key that is only derivable once
certain on-chain conditions are met, such as a deadline passing or a
governance vote concluding. Until then, not even the MPC nodes can decrypt the
data. This enables sealed-bid auctions, delayed reveals, and scheduled
disclosures without a trusted third party.</p>

<p>Another example is the generation of
<a href="https://en.wikipedia.org/wiki/Verifiable_random_function">verifiable randomness</a>
(private or public). Because the derived key is deterministic and verifiable,
it can serve as a source of unbiased randomness: no single party can
influence the output, and anyone can verify it was computed correctly. This is
useful for decentralized applications that require fair randomness, such as
games, lotteries, and leader election protocols.</p>

<h2 id="problem">Problem</h2>

<p>Informally, confidential keys satisfy the following properties:</p>

<ul>
  <li>The <em>TEE app</em> should be able to recover the confidential key
without having to store it directly, which is very useful in the case of
crashes or reboots</li>
  <li>The key should never leave the <em>TEE app</em> that requested it. This entails that
the key can only be determined by the expected receiver. In particular, neither
an <em>MPC node</em> nor a blockchain observer should be able to deduce such a key.</li>
  <li>Each <em>TEE app</em> should obtain a unique confidential key</li>
  <li>Correctness assurance. The <em>TEE app</em> should be able to check that the received key
was computed correctly</li>
</ul>

<p>Formally, these keys must be:</p>

<ul>
  <li>Deterministically extractable, depending only on the <em>TEE app</em>’s authenticated identifier
$\texttt{app\_id}$ and MPC Public Key $\texttt{pk}$</li>
  <li>Confidential</li>
  <li>Verifiable</li>
</ul>

<h3 id="attacker-model">Attacker Model</h3>

<p>These are the attacker models we consider throughout this blog post:</p>

<ul>
  <li>
    <p><em>MPC nodes</em> might be malicious, and try to obtain app’s secrets. In our
$t$-of-$n$ threshold setting, where $n$ is the total number of nodes and $t$
is the minimum number required to reconstruct a secret, up to $t-1$ nodes
might be malicious.</p>
  </li>
  <li>
    <p><em>App developer</em> might be malicious. It may attempt to obtain secrets belonging
to other applications or to extract secrets from the <em>MPC network</em>.</p>
  </li>
  <li>
    <p><em>App developer</em> colluding with up to $t-1$ malicious <em>MPC nodes</em>. This is the
most general attacker, as it subsumes the two previous cases.</p>
  </li>
</ul>

<h3 id="possible-solution-with-encrypted-signatures">Possible Solution with Encrypted Signatures</h3>

<p>One approach to solve the problem is to encrypt signatures over some message,
and use the signatures to obtain a secret. As we require determinism, not every
possible threshold signature scheme is fit for this purpose. Before CKD was
implemented, the <em>MPC network</em> supported only threshold
<a href="https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm">ECDSA</a>
and <a href="https://en.wikipedia.org/wiki/EdDSA">EdDSA</a> signatures, none of which are
deterministic. Given that a deterministic signature scheme can be used as a
<a href="https://en.wikipedia.org/wiki/Pseudorandom_function_family">PRF</a> to generate
random coins, we use <a href="https://en.wikipedia.org/wiki/BLS_digital_signature">BLS</a>
for this purpose. Furthermore, these signatures are very efficient to compute in
our threshold setting.</p>

<h2 id="system-overview">System Overview</h2>

<pre><code class="language-mermaid">flowchart TD
    TEEapp[TEE app] --&gt;|"`1\. send (attestation, A)`"| DevC["Developer contract
    (verifies attestation)"]
    subgraph Blockchain
    DevC --&gt;|"`2\. request key (A)`"| MPCC[MPC contract]
    end
    MPCC --&gt;|"`3\. compute key (app_id, A)`"| MPCN[MPC Network]
    MPCN --&gt;|"`4\. return (es)`"| MPCC --&gt;|"`5\. return (es)`"| DevC --&gt;|"`6\. return (es)`"| TEEapp
</code></pre>

<p>In this section we explain our solution in detail. While building our solution,
we converged to a protocol similar to
<a href="https://eprint.iacr.org/2023/616">VetKeys</a>, even though we were not aware of
this result. For simplicity we will describe a reduced version of the system,
shown in the diagram above. The details of the full system can be found in
<a href="https://github.com/near/threshold-signatures/blob/main/docs/confidential_key_derivation/confidential_key_derivation.md">docs</a>.</p>

<p>The system contains mainly four components: <em>TEE app</em>, <em>Developer contract</em>,
<a href="https://nearblocks.io/address/v1.signer"><em>MPC contract</em></a> and <em>MPC network</em>. The
<em>TEE app</em> wishes to use the CKD functionality to obtain confidential keys. To
obtain a key in encrypted form, the <em>TEE app</em> sends a public key and an
attestation proof to the <em>Developer contract</em>, which verifies that the app is
running inside a secure environment through
<a href="https://en.wikipedia.org/wiki/Trusted_Computing#Remote_attestation">remote attestation</a>.
Upon successful verification, the <em>Developer contract</em> calls the <em>MPC contract</em>
to request the key derivation. The <em>MPC contract</em> identifies the caller by its
account id, which serves as a unique, authenticated identifier for the
application, denoted by $\texttt{app\_id}$. The <em>MPC contract</em> then signals the
<em>MPC network</em> to compute the corresponding key, encrypted by the public key of
the <em>TEE app</em>. Once the <em>MPC network</em> is done computing the key in encrypted
form, one of its members, which we call coordinator, publishes this value in the
blockchain. Next, the encrypted key is returned to the <em>TEE app</em> that requested
it. Note that in the full system the <em>Developer contract</em> can also pass an
arbitrary key derivation string alongside the request, so that it can derive as
many distinct keys as needed.</p>

<p>Next we will dive into the protocol details, but before that we need to define a
few elliptic curve concepts and some notation.</p>

<h3 id="elliptic-curve-notation">Elliptic Curve Notation</h3>

<p>Elliptic curves over finite fields are mathematical objects with many
applications in cryptography. An elliptic curve is a set of points in two
dimensions, where the point coordinates are scalars in a finite field. The curve
points satisfy a polynomial equation, such as $y^2 = x^3 + 4$ for the
<a href="https://hackmd.io/@benjaminion/bls12-381">BLS12-381 curve</a>, and
form a group with respect to the point addition operation. The group order is a
prime $q$.</p>

<p>For the rest of this blog post, we use the following notation:</p>

<ul>
  <li>Lower case variables, such as $x$ and $y$, denote scalars modulo group order
$q$</li>
  <li>Upper case variables, such as $G$ and $Y$, denote elliptic curve points. $G$
will be reserved for the generator of the curve</li>
  <li>$H$ is a hash to curve function defined in
<a href="https://www.rfc-editor.org/rfc/rfc9380.html">RFC 9380</a>, which translates
bytes to <a href="https://en.wikipedia.org/wiki/Elliptic_curve">elliptic curve</a> points
for which computing the discrete log is hard</li>
  <li>$ Y \gets y \cdot G $ is the scalar multiplication of $y$ by the group generator $G$</li>
  <li>$y \gets^{$} \mathbb{Z}_q $ is scalar $y$ picked uniformly at random from $ \mathbb{Z}_q $</li>
</ul>

<h3 id="mpc-network-notation">MPC Network Notation</h3>

<ul>
  <li>$\texttt{msk}$: secret key of the <em>MPC network</em></li>
  <li>$\texttt{pk} \gets \texttt{msk} \cdot G$: public key of <em>MPC network</em></li>
  <li>$x_i \in \mathbb{Z}_q$: private key share of node $i$, for $i = 1, \ldots, n$</li>
  <li>$\lambda_i$:
<a href="https://en.wikipedia.org/wiki/Lagrange_polynomial">Lagrange polynomial</a>
coefficients. By definition we have $\sum_{i=1}^{n}{\lambda_i \cdot x_i} = \texttt{msk}$</li>
</ul>

<h3 id="protocol-steps">Protocol Steps</h3>

<ul>
  <li>The <em>TEE app</em> generates a fresh elliptic curve
<a href="https://en.wikipedia.org/wiki/ElGamal_encryption">ElGamal</a> key pair $(a, A)$
and requests a key from the <em>Developer contract</em>. This request includes a
remote attestation proof</li>
  <li>The <em>Developer contract</em> verifies the <em>TEE app</em> is correctly being executed
inside a TEE, by verifying the remote attestation proof. Upon successful
verification, it forwards the request to the <em>MPC contract</em></li>
  <li>The <em>MPC contract</em> sets $\texttt{app\_id}$ equal to the account id of the
caller (the <em>Developer contract</em>), which serves as a unique, authenticated
identifier for the application, and approves the request to be handled by the
<em>MPC Network</em></li>
  <li>
    <p><em>MPC node</em> $i$ receives a new CKD request $(\texttt{app\_id}, A)$ and computes:</p>

\[\begin{aligned}
&amp; y_i  \gets^{\$} \mathbb{Z}_q \\
&amp; Y_i \gets y_i \cdot G_1 \\
&amp; S_i \gets x_i \cdot H(\texttt{pk}, \texttt{app\_id}) \\
&amp; C_i \gets  S_i + y_i \cdot A \\
\end{aligned}\]
  </li>
</ul>

<p>Notice that this is basically computing a BLS signature of
$H(\texttt{pk}, \texttt{app\_id})$ using the node’s private share $x_i$ as private key, and
encrypting the signature using ElGamal with the <em>TEE app</em>’s public key $A$.</p>

<ul>
  <li><em>MPC node</em> $i$ sends $(\lambda_i \cdot Y_i, \lambda_i \cdot C_i)$ to coordinator</li>
  <li>
    <p>The coordinator adds the received pairs together and computes the encrypted
secret $\texttt{es}$:</p>

\[\begin{aligned}
&amp; Y \gets λ_1 \cdot Y_1 + \ldots + λ_n \cdot Y_n \\
&amp; C \gets λ_1 \cdot C_1 + \ldots + λ_n \cdot C_n = \texttt{msk} \cdot H(\texttt{pk}, \texttt{app\_id}) + a \cdot Y \\
&amp; \texttt{es} \gets (Y, C)
\end{aligned}\]
  </li>
  <li>Coordinator sends $\texttt{es}$ to <em>TEE app</em> on-chain through the <em>MPC contract</em></li>
  <li>
    <p><em>TEE app</em> obtains $\texttt{es} = (Y, C)$ and computes the secret (which is a
BLS signature):</p>

\[\texttt{key} \gets C + (- a) \cdot Y = \texttt{msk} \cdot H(\texttt{pk}, \texttt{app\_id})\]
  </li>
  <li>Then checks its correctness with respect to the MPC network public key
$\texttt{pk}$</li>
</ul>

<p>The choices related to the last step above, in which the <em>TEE app</em> verifies
whether the secret obtained is correct, are explained in more depth in the next
section.</p>

<h2 id="verifiability">Verifiability</h2>

<p>As mentioned in the <a href="#problem">Problem</a> section, achieving verifiability is a desirable
feature for such a system. In this section we first show that it is actually
necessary, as the system above without any verification step is insecure. Then
we explain how we currently achieve private verifiability, and how we plan
to achieve public verifiability in the near future.</p>

<p>We consider two definitions of verifiability:</p>

<ul>
  <li>private verifiability: the <em>TEE app</em> is able to verify that the received
confidential key is correct with respect to the MPC public key</li>
  <li>public verifiability: anyone, for example the MPC contract or any blockchain
observer, should be able to verify that the encrypted key is correct, even
without being able to decrypt it</li>
</ul>

<p>To show why verifiability is needed, we first consider the case where no
verification of the resulting confidential key (or its encryption) is done by
any protocol party.</p>

<h3 id="no-verifiability">No Verifiability</h3>

<p>If upon reception of the encrypted secret $\texttt{es}$ the <em>TEE app</em> does not
execute any verification step, the following attack by the coordinator is
possible. Consider the honest coordinator output:</p>

\[\texttt{es} = (Y, C) \gets (y \cdot G, \texttt{msk} \cdot H(\texttt{pk}, \texttt{app\_id}) + a \cdot Y)\]

<p>If instead the coordinator computes:</p>

\[\begin{aligned}
  &amp; y  \gets \text{arbitrary scalar} \\
  &amp; Y \gets y \cdot G \\
  &amp; C \gets \text{arbitrary curve point} \\
  \end{aligned}\]

<p>Upon reception of $(Y, C)$, the <em>TEE app</em> would compute its confidential key as:</p>

\[\texttt{key} \gets C + (-a) \cdot Y = C + (-y) \cdot A\]

<p>This is a value that is already known by the coordinator, breaking the
confidentiality of the obtained key.</p>

<h3 id="private-verifiability">Private Verifiability</h3>

<p>In our current system, upon reception of $\texttt{es}$, the <em>TEE app</em> first decrypts
the key:</p>

<p>$\texttt{key} \gets C + (- a) \cdot Y$ and then verifies it is equal to the
expected value $\texttt{msk} \cdot H(\texttt{pk}, \texttt{app\_id})$ by checking its validity
as a BLS signature of the message $H(\texttt{pk}, \texttt{app\_id})$.</p>

<p>In a nutshell, this verification leverages the
<a href="https://en.wikipedia.org/wiki/Pairing">pairing function</a> $e$ associated with
elliptic curves used for BLS signatures:</p>

\[e(\texttt{key},G_2) = e(H(\texttt{pk}, \texttt{app\_id}), \texttt{pk})\]

<p>This thwarts the attack explained above because the coordinator has no way to
ensure the maliciously constructed $\texttt{key}$ is a correct BLS signature for
$H(\texttt{pk}, \texttt{app\_id})$.</p>

<h3 id="public-verifiability">Public Verifiability</h3>

<p>Achieving public verifiability (with minor modifications) is also possible. This
is desirable, because it would allow the <em>MPC contract</em> to detect if something
has gone wrong during the distributed computation by the <em>MPC network</em>. It would
also guarantee that if the <em>TEE app</em> obtains a response, then this response is
correct. One possible variant is the following:</p>

<ul>
  <li>app public key:</li>
</ul>

\[(A_1, A_2) \gets (a \cdot G_1, a \cdot G_2)\]

<ul>
  <li>coordinator output:</li>
</ul>

\[(C, Y_1, Y_2) \gets (\texttt{msk} \cdot H(\texttt{pk}, \texttt{app\_id})
  + a \cdot Y_1, y \cdot G_1, y \cdot G_2)\]

<ul>
  <li><em>MPC contract</em> verification:</li>
</ul>

\[\begin{aligned}
&amp; e(Y_1, G_2) = e(G_1, Y_2) \\
&amp; e(C, G_2) = e(A_1, Y_2) \cdot e(H(\texttt{pk}, \texttt{app\_id}), \texttt{pk}) \\
\end{aligned}\]

<p>The first check ensures that $Y_1$ and $Y_2$ are consistent, i.e. that the
coordinator used the same scalar $y$ in both groups. The second check verifies
that $C$ is a valid ElGamal encryption of the correct BLS signature under the
app’s public key, without needing to decrypt it. Together, these prevent the
coordinator attack described earlier, since the contract can reject malformed
responses before they reach the <em>TEE app</em>.</p>

<ul>
  <li><em>TEE app</em> key decryption and verification:</li>
</ul>

\[\texttt{key} \gets C + (-a) \cdot Y_1\]

\[e(\texttt{key},G_2) = e(H(\texttt{pk}, \texttt{app\_id}), \texttt{pk})\]

<p>This is the same private verification as before: the <em>TEE app</em> decrypts the
ElGamal ciphertext and confirms the result is a valid BLS signature. With
public verifiability, this step serves as a redundant safety check, since the
contract has already verified the encrypted form.</p>

<h2 id="performance-considerations">Performance Considerations</h2>

<p>CKD is lightweight by design. Each key derivation requires a single round of
communication between the MPC nodes and the coordinator: every node computes
one scalar multiplication and one hash-to-curve operation on its private share,
and the coordinator aggregates the results with simple point additions. There is
no multi-round interactive protocol or heavy on-chain computation involved. The
on-chain footprint is limited to storing and forwarding the encrypted secret
$\texttt{es}$, which consists of just two elliptic curve points. Verification
on the TEE side requires a single pairing check. As a result, CKD adds minimal
overhead on top of the existing MPC infrastructure.</p>

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

<p>In this post we introduced confidential key derivation (CKD), a new feature of
NEAR’s MPC network that turns it into a decentralized key management service.
CKD provides authenticated applications with deterministic, confidential keys
by combining threshold BLS signatures with ElGamal encryption, ensuring that
no single MPC node ever sees the derived key. We showed why verifiability is
essential by demonstrating a concrete coordinator attack, and explained how
private verifiability thwarts it. Public verifiability, which would allow
on-chain verification of encrypted keys without decryption, is planned as a
next step.</p>

<p>For the full protocol specification, including details on key resharing and
threshold management, see the
<a href="https://github.com/near/threshold-signatures/blob/main/docs/confidential_key_derivation/confidential_key_derivation.md">CKD documentation</a>.
If you are interested in building on top of CKD, check out the
<a href="https://github.com/near/mpc">mpc</a> repository.</p>

<h2 id="appendix-implementation">Appendix: Implementation</h2>

<p>For those of you who usually say: <strong>Talk is cheap. Show me the code.</strong> Here is
a simple implementation that shows how the confidential key is computed using
our <a href="https://github.com/near/threshold-signatures">threshold-signatures</a> crate.</p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="cd">//!```cargo</span>
<span class="cd">//! [dependencies]</span>
<span class="cd">//! rand_core = { version = "0.6.4", features = ["getrandom"] }</span>
<span class="cd">//! threshold-signatures = { git = "https://github.com/near/threshold-signatures", rev = "01315cf9fef7064a0b529582c41ffad5f122d584"}</span>
<span class="cd">//!```</span>
<span class="k">use</span> <span class="nn">rand_core</span><span class="p">::</span><span class="n">OsRng</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">threshold_signatures</span><span class="p">::</span><span class="nn">blstrs</span><span class="p">::{</span><span class="n">pairing</span><span class="p">,</span> <span class="n">Gt</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">threshold_signatures</span><span class="p">::</span><span class="nn">confidential_key_derivation</span><span class="p">::{</span>
    <span class="nn">ciphersuite</span><span class="p">::{</span><span class="n">hash_to_curve</span><span class="p">,</span> <span class="n">Field</span><span class="p">,</span> <span class="n">G1Projective</span><span class="p">,</span> <span class="n">G2Projective</span><span class="p">,</span> <span class="n">Group</span><span class="p">},</span>
    <span class="n">AppId</span><span class="p">,</span> <span class="n">Scalar</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">fn</span> <span class="nf">e</span><span class="p">(</span><span class="n">p1</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G1Projective</span><span class="p">,</span> <span class="n">p2</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G2Projective</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">Gt</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">p1</span> <span class="o">=</span> <span class="n">p1</span><span class="nf">.into</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">p2</span> <span class="o">=</span> <span class="n">p2</span><span class="nf">.into</span><span class="p">();</span>
    <span class="nf">pairing</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">p2</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">gen_keypair_G1</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span><span class="n">Scalar</span><span class="p">,</span> <span class="n">G1Projective</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="nn">Scalar</span><span class="p">::</span><span class="nf">random</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">OsRng</span><span class="p">);</span>
    <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nn">G1Projective</span><span class="p">::</span><span class="nf">generator</span><span class="p">()</span> <span class="o">*</span> <span class="n">x</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">gen_keypair_G2</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="p">(</span><span class="n">Scalar</span><span class="p">,</span> <span class="n">G2Projective</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="nn">Scalar</span><span class="p">::</span><span class="nf">random</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">OsRng</span><span class="p">);</span>
    <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="nn">G2Projective</span><span class="p">::</span><span class="nf">generator</span><span class="p">()</span> <span class="o">*</span> <span class="n">x</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">H</span><span class="p">(</span><span class="n">pk</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G2Projective</span><span class="p">,</span> <span class="n">app_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">AppId</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">G1Projective</span> <span class="p">{</span>
    <span class="nf">hash_to_curve</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="n">pk</span><span class="nf">.to_compressed</span><span class="p">()</span><span class="nf">.as_ref</span><span class="p">(),</span> <span class="n">app_id</span><span class="nf">.as_bytes</span><span class="p">()]</span><span class="nf">.concat</span><span class="p">())</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">verify</span><span class="p">(</span><span class="n">pk</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G2Projective</span><span class="p">,</span> <span class="n">app_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">AppId</span><span class="p">,</span> <span class="n">sig</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G1Projective</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
    <span class="nf">e</span><span class="p">(</span><span class="n">sig</span><span class="p">,</span> <span class="o">&amp;</span><span class="nn">G2Projective</span><span class="p">::</span><span class="nf">generator</span><span class="p">())</span> <span class="o">==</span> <span class="nf">e</span><span class="p">(</span><span class="o">&amp;</span><span class="nf">H</span><span class="p">(</span><span class="n">pk</span><span class="p">,</span> <span class="n">app_id</span><span class="p">),</span> <span class="n">pk</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">ckd</span><span class="p">(</span><span class="n">app_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">AppId</span><span class="p">,</span> <span class="n">A</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G1Projective</span><span class="p">,</span> <span class="n">msk</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">Scalar</span><span class="p">,</span> <span class="n">pk</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">G2Projective</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="p">(</span><span class="n">G1Projective</span><span class="p">,</span> <span class="n">G1Projective</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">Y</span><span class="p">)</span> <span class="o">=</span> <span class="nf">gen_keypair_G1</span><span class="p">();</span>
    <span class="p">(</span><span class="nf">H</span><span class="p">(</span><span class="n">pk</span><span class="p">,</span> <span class="n">app_id</span><span class="p">)</span> <span class="o">*</span> <span class="n">msk</span> <span class="o">+</span> <span class="n">A</span> <span class="o">*</span> <span class="n">y</span><span class="p">,</span> <span class="n">Y</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">msk</span><span class="p">,</span> <span class="n">pk</span><span class="p">)</span> <span class="o">=</span> <span class="nf">gen_keypair_G2</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">app_id</span> <span class="o">=</span> <span class="nn">AppId</span><span class="p">::</span><span class="nf">try_new</span><span class="p">(</span><span class="s">b"example-app.testnet"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">A</span><span class="p">)</span> <span class="o">=</span> <span class="nf">gen_keypair_G1</span><span class="p">();</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">Y</span><span class="p">)</span> <span class="o">=</span> <span class="nf">ckd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">app_id</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">A</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">msk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pk</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">sig</span> <span class="o">=</span> <span class="n">C</span> <span class="o">-</span> <span class="n">Y</span> <span class="o">*</span> <span class="n">a</span><span class="p">;</span>
    <span class="nd">assert!</span><span class="p">(</span><span class="nf">verify</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">app_id</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sig</span><span class="p">));</span>
    <span class="nd">println!</span><span class="p">(</span><span class="s">"Computation ended correctly!"</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>]]></content><author><name>Reynaldo Gil Pons</name></author><category term="research" /><summary type="html"><![CDATA[Imagine an application running inside a Trusted Execution Environment (TEE) that needs to encrypt its storage. If the app migrates to a different machine or reboots, it must recover the exact same encryption key, without ever having stored it. How can such an app obtain a deterministic, confidential key from a decentralized network, without any single party ever seeing it?]]></summary></entry><entry><title type="html">Scaling near to 1M TPS</title><link href="https://blog.nearone.org/announcement/2025/12/11/scaling-near-to-1m-tps.html" rel="alternate" type="text/html" title="Scaling near to 1M TPS" /><published>2025-12-11T22:00:00+00:00</published><updated>2025-12-11T22:00:00+00:00</updated><id>https://blog.nearone.org/announcement/2025/12/11/scaling-near-to-1m-tps</id><content type="html" xml:base="https://blog.nearone.org/announcement/2025/12/11/scaling-near-to-1m-tps.html"><![CDATA[<p>We achieved 1M TPS on NEAR after many optimizations targeted at increasing throughput and scaling up the number of shards, only using commodity hardware. Below is a detailed look at the techniques that made it possible.<!--more--></p>

<p>Authors: <a href="mailto:andrea@nearone.org">Andrea Spurio</a>, <a href="mailto:jan.ciolek@nearone.org">Jan Malinowski</a>, <a href="mailto:svyatoslav.savenko@nearone.org">Slava Savenko</a>, <a href="mailto:darioush.jalali@nearone.org">Darioush Jalali</a></p>

<h2 id="background-on-near-protocol-stateless-validation-and-sharding">Background on NEAR protocol, stateless validation, and sharding</h2>

<p>NEAR uses a <strong>sharded, leader-based blockchain</strong> where each block contains the outputs of multiple <strong>shards</strong>, and each shard processes a disjoint portion of the state. Blocks are produced in a fixed sequence of <strong>block producers</strong>, while <strong>chunk producers</strong> (one per shard per height) assemble the transactions and state transitions for their shard into “chunks.” A block is essentially a container for these chunks plus receipts that move between shards. Cross-shard communication is fully asynchronous: when a transaction in shard A produces a receipt for shard B, that receipt is routed via the next block and executed by shard B in its future chunk. This design avoids global synchronous execution and keeps the system horizontally scalable as shards are added.</p>

<p>To make such a sharded chain verifiable by ordinary nodes, NEAR supports <strong>stateless validation</strong>: instead of requiring validators to store the full state, each chunk includes <strong>Merkle proofs (state witnesses)</strong> that show which parts of the state were read or written. A validating node only needs to verify signatures, apply the transactions over the provided witness, and check that the resulting state root matches the one committed by the chunk producer. This allows NEAR to retain strong security even when individual validators are only assigned to a single shard at a time.</p>

<h2 id="goal-setup-and-focus-of-benchmark">Goal, setup, and focus of benchmark</h2>

<p>The focus of this benchmark is to demonstrate NEAR protocol’s scalability via sharding on commodity hardware. In this benchmark, we demonstrate this by using the same <code class="language-plaintext highlighter-rouge">neard</code> client reference <a href="https://github.com/near/nearcore/tree/d178e1830b062b407c270e8f8045753fd41cd081">implementation</a> codebase that runs on mainnet (under different configuration and network setup), to run validator nodes of a high-throughput chain, reaching a peak of 1M native token transfers per second.</p>

<p><em>We considered cloud hardware that fits in a $1k / month budget for compute and storage. We believe this budget is not excessive in a way that is harmful to decentralization. Our estimates for network traffic at 64 MB/s is $4 / hr using on-demand cloud provider rates. Validators may run on dedicated hardware or use cloud provider service agreements to further reduce costs.</em></p>

<p>We were able to reach that target using 70 shards and the setup detailed below:</p>

<h3 id="configuration-of-network-and-nodes">Configuration of network and nodes</h3>

<ul>
  <li>140 nodes, assigned to 70 shards. Both nodes assigned to the same shard have the chunk producer role, i.e., alternate turns in producing chunks for their assigned shard.</li>
  <li>Nodes assigned across three regions:
    <ul>
      <li>us-central1 - 47 nodes</li>
      <li>us-east1 - 47 nodes</li>
      <li>us-east4 - 46 nodes</li>
    </ul>
  </li>
  <li>All nodes using:
    <ul>
      <li>GCP <a href="https://docs.cloud.google.com/compute/docs/general-purpose-machines#c4dhighmem">c4d-highmem-16</a> (8-core CPUs)</li>
      <li>200 GB hyperdisk-balanced (440 MB/s, 4200 IOPS) as a boot disk</li>
      <li>400 GB hyperdisk-balanced (1200 MB/s, 20000 IOPS) as an attached disk</li>
    </ul>
  </li>
</ul>

<h3 id="a-note-about-mandates-and-network-security-in-sharded-stateless-validation">A note about “mandates” and network security in sharded stateless validation</h3>

<p>In stateless validation, a block producer must include endorsement from a sufficient amount of stake (that have validated the state witness proving the new chunk is valid) in order to include a chunk in a block.</p>

<p>NEAR uses a concept called <a href="https://github.com/near/NEPs/blob/master/neps/nep-0509.md#chunk-validators-selection"><strong>mandates</strong></a>, which represents a tradeoff between the number of witnesses each validator must validate and the safety of the network.</p>

<p>In this work, we choose a number of mandates such that the amount of witnesses validated by each node is the same as it is on the current top 20% mainnet network validators (by stake).</p>

<p>In other words, more nodes are needed to have acceptable security guarantees on a 70 shard network (this allows an increase in the number of mandates). Note that these additional nodes do not need to track state, therefore they need less powerful hardware compared to chunk producer nodes. Starting such large networks could easily have a justifiable cost to scale an actual blockchain, however the cost is prohibitive for benchmarking. Therefore, we study how the nodes of such a chain would operate under a computationally equivalent load that provides security for mainnet (each node ends up validating state witnesses for on average 5 shards).</p>

<p>We refer the reader to additional references around selecting secure committees:</p>

<ul>
  <li><a href="https://medium.com/logos-network/sharding-how-many-shards-are-safe-bc361c487083">Sharding: How many shards are safe?</a></li>
  <li><a href="https://github.com/near/nearcore/blob/a9557047d1bd45da0d06cf6b880fea6487c35e20/chain/epoch-manager/src/validator_selection.rs#L245-L257">Nearcore source code for mandate selection</a></li>
  <li><a href="https://blog.nearone.org/research/2025/12/11/random-sampling-of-nears-validators.html">Random Sampling of NEAR’s Validators</a></li>
</ul>

<h3 id="workload">Workload</h3>

<ul>
  <li>Native token transfers,</li>
  <li>Account selection:
    <ul>
      <li>Source accounts are selected from the shard of the producer using the <a href="https://en.wikipedia.org/wiki/Zipf%E2%80%93Mandelbrot_law">Zipf-Mandelbrot</a> distribution.</li>
      <li>The target accounts pool is assembled using all accounts from all the shards randomly shuffled (on each load generator independently) to avoid shards skew. The Zipf-Mandelbrot distribution is afterwards used to select the target accounts for each particular transaction.</li>
      <li>Distribution parameters for the source and target accounts selection are: shift=2.7, exponent=1.0. For the 1M accounts in 70 shards with 2 producers per shard that results in the following probabilities:
        <ul>
          <li>~3.5% for the most frequent source account on each load producer.</li>
          <li>~2% for the most frequent target account on each load producer.</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Total number of accounts: 1M</li>
</ul>

<h3 id="running-the-benchmark">Running the benchmark</h3>

<p>To run the benchmark, first we create the accounts and access keys, and then gradually increase traffic over 6 minutes before applying the maximum load. During the initial period, we keep the traffic low as the chain initially has a few chunk/block misses while nodes are coming online.</p>

<p>Chunk producer nodes generate and sign the transactions: this simplified our benchmarking setup and allowed us to exclude RPC and focus on validator throughput.
In the steady state, we apply a constant load and the system operates at 0.5 block/s without chunk/block misses. Peer traffic reaches around 64 MB/s in send and receive.</p>

<table>
  <tbody>
    <tr>
      <td><a href="/assets/2025-12-11-scaling-near-to-1m-tps/image1.png" target="blank"><img src="/assets/2025-12-11-scaling-near-to-1m-tps/image1.png" alt="Main benchmark - Transactions per second" /></a></td>
      <td><a href="/assets/2025-12-11-scaling-near-to-1m-tps/image2.png" target="blank"><img src="/assets/2025-12-11-scaling-near-to-1m-tps/image2.png" alt="Main benchmark - Blocks per second" /></a></td>
      <td><a href="/assets/2025-12-11-scaling-near-to-1m-tps/image3.png" target="blank"><img src="/assets/2025-12-11-scaling-near-to-1m-tps/image3.png" alt="Main benchmark - Peer traffic" /></a></td>
    </tr>
  </tbody>
</table>

<h2 id="optimizations">Optimizations</h2>

<p>The following is a list of the more impactful and large scale optimizations the team worked on. We also worked on many other smaller scale optimizations, which are not detailed here.</p>

<h3 id="early-transaction-preparation">Early transaction preparation</h3>

<p>In normal operation, the chunk producer starts ordering transactions from the pool when it’s time to produce the chunk. This allows for inclusion of transactions that were submitted up to that moment, however delaying preparation of transactions until the last moment adds to the overall block latency.
We implemented a change that prepares transactions as soon as the prior chunk’s post-state is computed, trading off inclusion of most recent transactions in favor of higher chain throughput. This behavior is controlled by the <code class="language-plaintext highlighter-rouge">enable_early_prepare_transactions</code> configuration option, and can be enabled on a per-chunk producer basis.</p>

<h3 id="persisting-less-to-disk">Persisting less to disk</h3>

<p>In this benchmark, we focused on validator performance. By default, and out of convenience, <code class="language-plaintext highlighter-rouge">neard</code> persists a lot of data to RocksDB for each block. We found that large portions of this data was not needed for validators, and added configuration options to allow disabling writing of those (with sensible defaults – to avoid impacting existing node operator flows).</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">save_tx_outcomes</code>: Transaction outcomes are used by RPC nodes to respond to status queries, but not needed for validators.</li>
  <li><code class="language-plaintext highlighter-rouge">save_state_changes</code>: StateChanges column is used by RPC nodes to respond to queries and by the Rosetta indexer, but not needed for validators.</li>
  <li><code class="language-plaintext highlighter-rouge">save_untracked_partial_chunks_parts</code>: PartialChunks are saved in order to answer requests from nodes that are catching up with the chain; disabling this option avoids writing PartialChunks in the database, and keeps them only in the memory cache.</li>
</ul>

<h3 id="ed25519-batch-signature-verification">Ed25519 batch signature verification</h3>

<p>NEAR blockchain natively supports ED25519 and SECP256K1 signatures. Since mostly ED25519 signatures are used, we implemented an optimization to verify those signatures in batches of 128, with graceful fallback to verifying individual signatures.</p>

<p>Batched signature verification uses around 40% less cpu resources. However this translated to only around 7-10% improvement in the overall benchmark.</p>

<h3 id="deserialized-column-cache">Deserialized column cache</h3>

<p>We found the NEAR implementation performs frequent reads of data such as recent chunks, blocks, and block headers. This would not only read through RocksDB but would also deserialize the item on each access! To mitigate this, we implemented a <a href="https://github.com/near/nearcore/blob/d178e1830b062b407c270e8f8045753fd41cd081/core/store/src/deserialized_column.rs">column-level cache</a> for some of these frequently referenced columns, based on profiling observations.</p>

<h3 id="rocksdb-tuning">RocksDB Tuning</h3>

<p>To improve database throughput under write-heavy workloads, we implemented the following RocksDB optimizations:</p>

<ul>
  <li>Pipelined Writes: enabled pipelined write path to reduce write stall latency, allowing write operations to overlap more efficiently.</li>
  <li>WAL Tuning: configured WAL (Write-Ahead Log) to sync roughly every 1 MiB, smoothing I/O patterns and reducing latency spikes from large fsync bursts.</li>
  <li>Compaction Tuning: relaxed L0 compaction triggers so background compaction interferes less with foreground writes, and increased target file sizes to reduce the number of SST files. Added compaction read-ahead to help sequential reads during compaction.</li>
  <li>Memtable Configuration: increased memtable memory budget and write buffer count to absorb write bursts and reduce flush frequency.</li>
  <li>Column Family Tuning: allocated more resources to write-heavy column families with larger memory budgets, more sub-compaction jobs for parallelism and bigger write buffers.</li>
</ul>

<h3 id="tcp-tuning">TCP Tuning</h3>

<p>To reduce latency in state witness distribution and chunk distribution, we implemented several Linux TCP stack optimizations:</p>

<ul>
  <li>BBR Congestion Control: we switched to Google’s BBR algorithm paired with Fair Queueing (fq). BBR models network capacity based on bandwidth and round-trip time rather than packet loss, achieving higher throughput and lower latency.</li>
  <li>Path MTU Discovery: enabled to automatically discover the optimal MTU along network paths, preventing packet fragmentation and reducing overhead.</li>
  <li>Connection Handling: Increased the SYN backlog to 8096 to queue more incoming connection requests during traffic bursts.</li>
  <li>Buffer Tuning: for benchmarks, we significantly increased socket buffer sizes and raised the network device backlog, allowing the kernel to handle high-volume data transfers without dropping packets.</li>
</ul>

<h3 id="actor-separation">Actor separation</h3>

<p><code class="language-plaintext highlighter-rouge">neard</code> is implemented using an actor-style programming: a collection of <strong>independent actors</strong>, each with its own state and a mailbox for incoming messages. Actors communicate exclusively by sending asynchronous messages. However, in practice individual actors can become bottlenecks, for example:</p>

<ul>
  <li>some actors cannot make progress while waiting for a response from another actor,</li>
  <li>an actor takes on too many responsibilities, and ends up handling large amounts of work on the critical path.</li>
</ul>

<p>Some examples where we identified and solved this type of issue:</p>

<ul>
  <li><a href="https://github.com/near/nearcore/pull/13942">Creating state witnesses on a thread pool to avoid blocking a critical actor</a></li>
  <li><a href="https://github.com/near/nearcore/pull/13892">Separate actor for state witnesses</a></li>
  <li><a href="https://github.com/near/nearcore/pull/13259">Moving chunk-endorsement processing to a multi-threaded actor</a></li>
</ul>

<h3 id="lock-contention--trie-witness-recording-optimizations">Lock contention &amp; trie witness recording optimizations</h3>

<ul>
  <li>Avoiding serialization and hashing nodes when not needed,</li>
  <li>Using dashmap to reduce lock contention in recorder,</li>
  <li>Removed unnecessary tracking of “visited nodes” for state witness – this was taking place due to re-using code from “state sync part verification” logic.</li>
</ul>

<h2 id="solutions-for-some-problems-we-encountered">Solutions for some problems we encountered</h2>

<h3 id="visualizing-opentelemetry-traces-traviz">Visualizing OpenTelemetry traces: Traviz</h3>

<p>We used OpenTelemetry traces to analyze node performance. The traces allow us to view exactly how the node operates - when it receives and sends data, when it computes things, and how different nodes relate to each other. Being able to visually analyze all operations in the network was invaluable in understanding what the critical path looks like and which optimizations can be implemented to improve performance.</p>

<p><code class="language-plaintext highlighter-rouge">neard</code> exports tracing data in an OpenTelemetry compatible format. A few mature tools such as <a href="https://grafana.com/docs/tempo/latest/">Tempo</a> and <a href="https://www.jaegertracing.io/">Jaeger</a> are available in this domain, however they mostly focus on a hierarchical structure for traces (e.g., to break down calls to micro services).
This was not enough to help us figure out bottlenecks in a distributed system such as a multi-shard blockchain network. Because of that we developed an in-house trace visualization tool - <a href="https://github.com/jancionear/traviz">Traviz</a>. It allows us to view the data exactly the way we need, and has custom NEAR-specific features which make analyzing the NEAR flow easier. We’re planning to publish another blog post with more details about traviz.</p>

<p><a href="/assets/2025-12-11-scaling-near-to-1m-tps/image4.png" target="_blank"><img src="/assets/2025-12-11-scaling-near-to-1m-tps/image4.png" alt="Traviz screenshot" /></a></p>

<h3 id="consistently-measuring-benchmark-results-and-monitoring-regressions">Consistently measuring benchmark results and monitoring regressions</h3>

<p>To find out the maximum throughput the chain can support we have disabled/adjusted some safeguards supporting the chain stability in the prod. Those include the compute-cost and witness size caps for example that are used to limit the chunk size. While doing so we were not sure precisely which way the chain would break under the traffic exceeding its capacity. It turns out the chain reacts by exponentially increasing the block time and chunk size leading to missing blocks and severe drop in performance. Initial measurements involved running the series of experiments with the varying load to find out the optimal TPS volume. That was extremely human- and hardware- resources consuming, and resulted in noisy (+- 10%) measurements at irregular occasions. Finding the source of regressions (that did happen) used to be a rather painful procedure.</p>

<p>To improve the situation we implemented a <a href="https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller">proportional–integral–derivative</a> controller (with some fine-tuning to better represent the system’s response) that modifies the load TPS to target the desired block rate. Experiments are fully reproducible with very low variance, allowing to catch the deviations of ~1%. That allowed us to fully automate the benchmarks and add them as periodic CI jobs, with the perf measurements and telemetry saved as artifacts for each run.</p>

<p>For the continuous benchmarking we are targeting the block production time of 1.5s.
We found that it represents the throughput close to its maximum values while still keeping the chain in the stable operational range. The nightly jobs are run using the 4-shards chain as a minimal representative sample, and we are running the more expensive 20-shards scenario weekly. The CI setup also provides a convenient way to run the reproducible, self-documenting experiments with a few mouse clicks.</p>

<p>This proved to be useful not only for experiments run by the performance optimization team but also allowed us to catch and quickly bisect several performance regressions introduced by the others.</p>

<h2 id="scalability">Scalability</h2>

<p>Additionally, we report the following throughput capacity for smaller shards, keeping the same number of total accounts. Currently, mainnet is operating with 9 shards, and these experiments provide an idea about the shorter term scaling potential of the NEAR network:</p>

<p><a href="/assets/2025-12-11-scaling-near-to-1m-tps/image5.png" target="_blank"><img src="/assets/2025-12-11-scaling-near-to-1m-tps/image5.png" alt="Transactions per second vs shard count" /></a></p>

<h2 id="impact-of-optimizations">Impact of optimizations</h2>

<p>To specifically highlight the impact of the optimizations, we compared the throughput of 4-shard and 20-shard networks, prior to the optimization work using a commit from master on Mar 25, 2025. The results show around 3.5x improvement (graph included for 20-shard comparison).</p>

<p><a href="/assets/2025-12-11-scaling-near-to-1m-tps/image6.png" target="_blank"><img src="/assets/2025-12-11-scaling-near-to-1m-tps/image6.png" alt="Before and after TPS comparison - 20 shards" /></a></p>

<h2 id="reproducibility">Reproducibility</h2>

<p>To ensure the community and industry can verify these results, we are releasing the following deliverables:</p>

<ul>
  <li><a href="https://github.com/near/one-million-tps">Open-source benchmark scripts</a></li>
  <li>Public Grafana dashboards with 1M TPS runs: <a href="https://grafana.nearone.org/public-dashboards/99de1913abcd42979c2966a763f9c7dc?from=2025-12-03T13:07:28.000Z&amp;to=2025-12-03T14:17:55.000Z&amp;timezone=utc">Run 1</a>, <a href="https://grafana.nearone.org/public-dashboards/99de1913abcd42979c2966a763f9c7dc?from=2025-12-03T14:53:54.000Z&amp;to=2025-12-03T16:01:39.000Z&amp;timezone=utc">Run 2</a>, <a href="https://grafana.nearone.org/public-dashboards/99de1913abcd42979c2966a763f9c7dc?from=2025-12-03T18:04:52.000Z&amp;to=2025-12-03T19:12:18.000Z&amp;timezone=utc">Run 3</a></li>
  <li><a href="https://github.com/near/nearcore/tree/d178e1830b062b407c270e8f8045753fd41cd081">Commit hash of the benchmarked code</a></li>
</ul>

<p>These materials enable researchers, auditors, validators, and the wider blockchain ecosystem to reproduce the benchmark and validate these performance claims.</p>

<h2 id="future-work">Future work</h2>

<h3 id="optimistic-execution-and-separating-consensus-from-execution">Optimistic execution and separating consensus from execution</h3>

<p>Many parts of the code could be optimized with optimistic execution. Currently execution and consensus are interleaved - the network processes some data, then runs consensus to agree on the state and then processes some more data. It’d be more efficient to run execution in parallel with consensus instead of running them sequentially. This can be achieved using “optimistic” execution which runs on unconfirmed state before the consensus agrees on it.</p>

<p>Separating consensus and execution is the leading theme in the upcoming <a href="https://docs.google.com/document/d/1q5l2G0_wFgENqR2Cf3D4OWyaEHUlNhsG_OtCJ8IfrXU/edit?tab=t.0#heading=h.x03p5p5rp92p">SPICE</a> upgrade. In SPICE chunks will be produced without waiting for the previous ones to execute, allowing consensus and execution to run at the same time and improving performance. Some optimistic execution might still be needed to achieve maximum possible throughput.</p>

<h3 id="further-reducing-storage-redundancies">Further reducing storage redundancies</h3>

<p><code class="language-plaintext highlighter-rouge">neard</code> still stores state in formats that have redundancies. For example, the <code class="language-plaintext highlighter-rouge">State</code> column stores reference counted trie nodes in by hash, and the reference count changes are stored in a separate column (<code class="language-plaintext highlighter-rouge">TrieChanges</code>), to enable garbage collection.</p>

<p>Since the time that <a href="https://github.com/near/NEPs/blob/master/neps/nep-0509.md">stateless validation</a> was introduced, most validators operate using <a href="https://docs.google.com/document/d/1_X2z6CZbIsL68PiFvyrasjRdvKA_uucyIaDURziiH2U/edit?tab=t.0#heading=h.1lngmw1jnvgu">in-memory state</a>. RPC nodes still rely on the <code class="language-plaintext highlighter-rouge">State</code> column for responding to queries. In the future, RPC nodes may be sharded and also use in-memory state, which could allow further optimizations.</p>

<h3 id="benchmarking-fungible-token-transfers">Benchmarking fungible token transfers</h3>

<p>In this benchmark, we focused on native token transfers to focus on scalability of the chain independent of the contract runtime. Benchmarking fungible token transfers and optimizing the contract runtime remains as future work.</p>

<h2 id="benchmarking-simplifications">Benchmarking simplifications</h2>

<p>This benchmark focuses on showing NEAR protocol’s scaling potential and providing a validator implementation backed by the same reference client protocol that runs NEAR mainnet today. We want to highlight areas we considered out of scope.</p>

<p>Further work is required to improve the following areas necessary to operate an entire blockchain ecosystem at levels of throughput similar to those demonstrated in this work:</p>

<ul>
  <li><strong>RPC &amp; archival support:</strong> Nodes that operate as “RPC nodes”, with the purpose of responding to queries about blocks and state, persist more data to disk than is needed for validators to operate. Archival nodes persist even more data to disk, including all historical state and block information.</li>
  <li><strong>State sync part generation:</strong> Currently in the NEAR protocol, validators divide the state into “parts” and persist parts to disk at the beginning of an epoch. These parts are then served to new nodes who join the network later. We exclude the creation of these parts from the benchmark. In a high throughput chain, theoretically storing these parts could be a specialized role.</li>
  <li><strong>Gas limits and congestion control:</strong> Normally, gas limits are set based on usage of mainnet. High-throughput benchmarks involve chunk sizes significantly larger than those observed on mainnet, which impacts gas limits and congestion control. These parameters should be adjusted alongside actual network usage growth.</li>
  <li><strong>Partial Chunk persistence</strong>: When the option <code class="language-plaintext highlighter-rouge">save_untracked_partial_chunks_parts</code> is set to false, nodes will persist chunk parts only for the shard they track. All other part-owner nodes (the subset of nodes responsible for providing chunk availability guarantees) will only store partial chunks in a configurable-length in-memory cache.
The downside of this configuration is that a node needing to request a chunk may not find any suitable peer able to respond successfully. This can happen if the chunk is too old thus out of the cache horizon, or if the other peer was restarted recently and its in-memory cache is fresh. A node that cannot fetch a required chunk will be unable to catch up to the latest block. This is not an issue in the benchmark because nodes are not restarted and no node is lagging behind the tip of the chain.</li>
  <li><strong>Data center location:</strong> In this benchmark, we placed nodes across three data centers in “us-central” and “us-east”. Further distributing nodes geographically improves decentralization, however it may impact the network throughput as a result of increased networking latency.</li>
</ul>]]></content><author><name></name></author><category term="announcement" /><summary type="html"><![CDATA[We achieved 1M TPS on NEAR after many optimizations targeted at increasing throughput and scaling up the number of shards, only using commodity hardware. Below is a detailed look at the techniques that made it possible.]]></summary></entry><entry><title type="html">Random Sampling of NEAR’s Validators</title><link href="https://blog.nearone.org/research/2025/12/11/random-sampling-of-nears-validators.html" rel="alternate" type="text/html" title="Random Sampling of NEAR’s Validators" /><published>2025-12-11T13:00:00+00:00</published><updated>2025-12-11T13:00:00+00:00</updated><id>https://blog.nearone.org/research/2025/12/11/random-sampling-of-nears-validators</id><content type="html" xml:base="https://blog.nearone.org/research/2025/12/11/random-sampling-of-nears-validators.html"><![CDATA[<p>NEAR’s current approach is sufficient for the current number of shards
and validators, but it can easily become inefficient if the number of
validators grows. This blog post presents a new, simple and efficient
way of selecting NEAR’s validators and analyzes its safety and
liveness guarantees.<!--more--></p>

<p>The NEAR blockchain system uses a mechanism called <a href="https://github.com/near/NEPs/blob/master/neps/nep-0509.md">stateless
validation</a>
to ensure that all transactions are executed correctly. When a NEAR
node processes a sequence of transactions and computes a new
blockchain state, it produces a <em>state witness</em> alongside the new
state. The state witness is a piece of data containing all the
information necessary for anyone to verify (“<em>validate</em>”) the
transition from the old blockchain state to the new one. The NEAR node
then distributes this state witness to a set of other nodes that have
the role of <em>validators</em>. Validators verify the state witness and vote
if the state transition represented by the witness should be accepted
as valid. Their votes (a.k.a. <em>endorsements</em>) are required for the
next NEAR block to be produced.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/validation-scheme.svg" alt="Image: Validation scheme" /></p>

<p>The validators are called stateless because they do not need any prior
knowledge of the blockchain state to verify the correctness of the
state transition - the state witness is all they need. We are not
going to dive into the details of how state witnesses work - they can
be found in the corresponding
<a href="https://github.com/near/NEPs/blob/master/neps/nep-0509.md">documentation</a>. What
we are interested in now is how the validators are selected.</p>

<p>To become a validator, a node needs to lock some NEAR tokens as
stake. It is then allowed to vote on (i.e., endorse) NEAR’s state
transitions and receives rewards for doing so. We make the common
assumption that strictly less than one third of all the stake belongs
to faulty (potentially malicious) validators. Building only on this
assumption, we need to ensure the following with high probability
despite potential malicious attacks.</p>

<ul>
  <li><strong>Safety</strong>: The system does not accept an incorrect state transition.</li>
  <li><strong>Liveness</strong>: The system does accept correct state transitions.</li>
  <li><strong>Efficiency</strong>: The system does not spend too many resources on the validation process,
                even with many validators and many state transitions to be verified.</li>
  <li><strong>Fairness</strong>: The rewards of correct validators are proportional to their stakes.</li>
</ul>

<p>NEAR is a sharded blockchain system that splits its state into
multiple parts called <em>shards</em>. A separate state transition happens in
every shard with each block. If a transaction touches the state in two
(or more) shards, it is split into two (or more) separate (though
related) state transitions. Making every validator verify every state
transition is, unfortunately, not efficient. Therefore, only a limited
subset of validators must be responsible for validating each state
transition.</p>

<h2 id="how-near-currently-samples-validators">How NEAR currently samples validators</h2>

<p>NEAR’s current implementation splits each validator’s stake into a
variable number of parts and, at each block, assigns each such part to
exactly one random shard. The owner of the stake part then becomes
responsible for verifying and voting on that shard’s state
transition. If the validator consistently performs this task, it
receives a corresponding reward.</p>

<p>This method ensures that each of NEAR’s shards is assigned validators
with approximately the same amount of stake, intuitively making each
shard similarly secure (as it is “backed by” a similar amount of
stake). Note that non-sharded blockchain systems do not need to deal
with this concern, as they consist of only a single shard.</p>

<p>Our current approach is sufficient for the number of shards and
validators NEAR has at the time of writing of this article, but it has
several drawbacks. While it does ensure fairness, it can easily become
inefficient if the number of validators grows. Since each validator is
involved in the voting process at each block, the system might need to
process unnecessarily many votes and transmit large amounts of state
witness data. We will not discuss the details of the current
validation mechanism here (the interested reader can read more in the
<a href="https://github.com/near/NEPs/blob/master/neps/nep-0509.md">NEAR Enhancement Proposal
509</a>). It
involves variably sized parts of validators’ stake, stake-weighted
endorsements, and the safety and liveness guarantees are difficult to
formally analyze. It suffices to say that the above considerations
bring us to designing a new method of validator selection we are now
going to present.</p>

<h2 id="new-way-of-sampling-validators">New way of sampling validators</h2>

<p>The basic idea is that, instead of assigning each validator to a
random shard, we take each shard and assign some randomly selected set
of validators to it (while adjusting the selection probability
according to the validators’ stake). As we will see later, this subtle
detail simplifies the reasoning about the system and allows us to
easily quantify the safety and liveness guarantees. In a nutshell, for
each state transition, we randomly<span id="footnote1-anchor"><a href="#footnote1">$^1$</a></span>
select a small subset of validators that we call the <em>validator
sample</em>. The probability of choosing a validator to be included in the
sample is proportional to its stake and a validator can be chosen more
than once. If enough validators in the sample endorse (vote for) the
state transition, we consider the state transition validated.</p>

<p>We pick the validators for a sample with replacement. Validators that
have been chosen multiple times have the corresponding number of
votes. For instance, if a validator has been included in a sample 3
times, then its endorsement is also counted 3 times. Validators’
endorsements are not weighted by stake, as their stake is already
taken into account in the selection probability.</p>

<p>To ensure fairness, we keep rewarding the validators proportionally to
their stake (and thus also proportionally to the rate of their
endorsements) if they consistently keep voting for correct state
transitions.</p>

<h3 id="safety-and-liveness-requirements">Safety and liveness requirements</h3>

<p>Intuitively, we say: “If a sufficiently large subset (that we call a
quorum) of a small representative sample of validators endorse a state
transition, then most likely all correct validators would have
endorsed it.” The word “representative” is very important here. It
means that the sample’s proportion of correct and faulty validators
will be similar to that of the overall validator set.  Now let us
formalize this intuition.</p>

<p>Let us translate “sufficiently large subset of a small sample” to “
$q$ out of $s$ endorsements”. Furthermore, “most likely” shall be
“with probability at least $(1-\beta)$”. In other words, we need to
choose our validator sample of size $s$ such that the probability that
$q$ out of those $s$ validators endorse an incorrect state transition
is upper-bounded by a security parameter $β$. Note that this
corresponds precisely to the safety requirement defined above.</p>

<p>Conversely, for liveness, we want to ensure that the sample contains
enough validators voting for the state transition so it gets
accepted. That is, for some small parameter $\gamma$, we need the
probability that less than $q$ out of $s$ votes are cast for the
correct state transition to be at most $\gamma$.  We know that correct
validators will always endorse a correct state transition and never an
incorrect one. If $f’$ is the relative fraction of faulty validators
in a sample (making $(1-f’)$ the fraction of correct ones), we can
express the above as follows.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/failure-probabilities.svg" alt="Image: Failure probabilities" /></p>

<p>We are free to choose the $\beta$ and $\gamma$ parameters according to
our needs (within some constraints that we discuss later). For
example, if we consider a system with 50 shards, a block rate of 3
blocks/second, a separate validator set for each state transition, and
an expected time to a safety violation of 1 million years, we obtain
$\beta$ = 1 / (1’000’000 years · 365 days · 24 hours · 3600 seconds ·
3 blocks · 50 shards) = 1 / (4.73·1015 samples) = 2.11·10-16.</p>

<h3 id="calculating-failure-probabilities">Calculating failure probabilities</h3>

<p>Let us dive deeper into the representativeness of the sample. We are
choosing (with replacement) $s$ validators for the sample, where a
validator’s chance of being selected is proportional to its
stake. This is equivalent to sampling from the stake itself and
including the chosen stake’s owner in the sample. Our assumption is
that at most $f = ⅓$ of all the stake may be owned by faulty
validators, as depicted below (darts represent randomly selected parts
of the stake)</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/stake-sampling-darts.png" alt="Image: Stake sampling" /></p>

<p>Now it is easy to see that selecting a validator is a Bernoulli trial
with the probability of the validator being faulty $f = ⅓$. That makes
the number of times we select a faulty validator (denoted by $F$) in a
sample of size $s$ a binomially distributed random variable: $F ~
Bin(s, f)$. To obtain the relative fraction of faulty validators $f’$,
we divide by the sample size $s$ and obtain $f’ = F/s$, and thus $F =
f’·s ~ Bin(s, f)$. We can perform an analogous calculation for the
distribution of the number of correct validators with probability
$(1-f) = ⅔$ of selecting a correct validator, leading to $(s - F) =
(1-f’)·s ~ Bin(s, 1-f)$.</p>

<p>Let us focus on the number of faulty validators $f’·s ~ Bin(s,
f)$. Since the expected value of a binomial distribution is $s·f$, the
expected fraction of faulty validators in a sample will always be $s·f
/ s = f = ⅓$. What changes with sample size $s$ is only the variance
(represented by the color gradient in the figure below). The bigger
the sample size, the less likely $f’$ is to deviate much from $f$.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/sample-size.svg" alt="Image: Sample size" /></p>

<p>If we fix the acceptable safety violation probability $\beta$ and a
sample size $s$, we can compute the minimal quorum size $q$ that
satisfies our safety requirements by evaluating the binomial
distribution<span id="footnote2-anchor"><a href="#footnote2">$^2$</a></span>. For example, in order to bound the safety violation
probability by $\beta = 2.11·10^{-16}$ in a sample of $s = 50$
validators, we need to wait until $q = 45$ of them endorse the same
state transition. If we had a larger sample of, say, 75 validators, we
would require 60 endorsements to achieve the same safety violation
probability. Note that, in a larger sample, we also need a larger
absolute number, but a smaller relative number of endorsements (89% vs
79% in the above examples). The plot below shows possible combinations
of sample sizes ($s$) and quorum sizes ($q$) for a tolerable safety
violation probability $\beta = 2.11·10^{-16}$.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/possible-sample-and-quorum-sizes.svg" alt="Image: Possible sample and quorum sizes" /></p>

<p>We see that if we use a small sample, we need all of its votes. As the
sample grows, we can afford to “lose” some votes. Why does the plot
start at a sample size of 33 validators? Because, if we only used 32
or fewer validators, we could not be sufficiently ($\beta =
2.11·10^{-16}$) sure that a state transition is correct even if all
those validators endorsed it. There would still be a probability of
$(⅓)^{32} ≈ 5.4·10^{-16} &gt; \beta$ that all those 32 validators are
faulty.</p>

<h3 id="safety-vs-liveness-trade-off">Safety vs. liveness trade-off</h3>

<p>One might be tempted to say that the best choice is to stick with a
small sample and a relatively large (but absolutely small)
quorum. After all, a smaller validator sample means a smaller
communication and computation overhead for the same safety
guarantees. This has an important drawback, however: if we ever get
even slightly unlucky with our sample, we can easily get stuck, since
even a (absolutely) very small (but relatively large) number of faulty
validators can block any state transition from being accepted. This is
a symptom of a general trade-off: every choice we make regarding
$\beta$, s, and q also determines the liveness violation probability
$\gamma$. In a small sample, the quorum size required to achieve
safety might be prohibitive for liveness. Increasing the sample size
allows for a smaller relative quorum, decreasing the liveness
violation probability $\gamma$ while maintaining safety.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/small-vs-large-sample.svg" alt="Image: Small vs. large sample" /></p>

<p>We can also turn things around and start by fixing $\gamma$ to ensure
the desired liveness. However, the resulting maximal allowed quorum
size might prohibit us from guaranteeing safety with a sufficiently
large $\beta$. We can thus say that the smaller the sample, the
tougher the trade-off between safety and liveness.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/beta-vs-gamma.png" alt="Image: Beta vs. Gamma" /></p>

<p>In practice (i.e. in the NEAR system), safety and liveness
requirements are not quite symmetric. A safety violation corresponds
to accepting an invalid state transition, e.g., minting or
transferring an arbitrary amount of tokens. This situation is very
hard to meaningfully recover from and we need to make sure it simply
does not occur. A liveness violation in the context of a single state
transition, on the other hand, can be dealt with more easily. For
example, if a state transition is not accepted by some time-out, we
can try again, potentially even using a different, perhaps more
conservative validation strategy. This manifests as a performance
glitch which might be inconvenient, but, especially if rare, not
critical. We can therefore say that for NEAR, we can tune the safety
vs. liveness trade-off very much in favor of a low $\beta$ and a
relatively larger $\gamma$.</p>

<h3 id="defending-against-an-adaptive-adversary">Defending against an adaptive adversary</h3>

<p>What was said above would suffice against a static adversary that has
to choose the corrupted validators ahead of time. However, an adaptive
adversary may wait until we select our validator sample and then
corrupt a quorum of just that sample. If we rely on small validator
samples, there is no defense against an adaptive adversary without
making additional assumptions. Even corrupting the whole sample would
fit in the adversary’s budget of ⅓ of the total corruptible
stake. Therefore, to deter even an adaptive adversary, we make an
additional assumption: a validator can only be dynamically corrupted
if doing so doesn’t make it lose money. We then make it expensive to
perform an adaptive attack through the following two measures:</p>

<ol>
  <li>A validator that endorses an incorrect state transition or
consistently fails to endorse a correct one loses its stake.</li>
  <li>We make sure that any quorum of validators in a sample always has
some minimal cumulative amount of stake.</li>
</ol>

<p>We call the cumulative amount of stake of a quorum in a sample the
<em>strength</em> of that sample. There are many subsets of a sample that
constitute a quorum with different amounts of stake and we always look
at the one with the smallest stake. We assume that an adaptive
adversary corrupting a quorum of validators in a sample would need to
at least refund the stake to the corrupted validators to make them
collude. The strength of a sample then corresponds to the price of an
adaptive attack.</p>

<p>We introduce another security parameter $\alpha$ representing the
required minimal strength of a sample. When picking a sample, we make
sure that its strength is at least $\alpha$ – i.e., that the total
stake of every quorum is at least $\alpha$. It is difficult to devise
a probability distribution for the sample strength because it depends
on how the stake is distributed among the validators (e.g. - “all
validators have equal stake” or “most validators have small stake and
few have a large one”, etc.). We do show, however, what the sample
strength looks like for different quorum sizes in several simulated
example scenarios in the figure below.</p>

<p><img src="/assets/2025-12-11-random-sampling-of-nears-validators/stake-distribution-in-a-sample.svg" alt="Image: Stake distribution in a sample" /></p>

<p>In the uniform stake distribution, all validators have the same
stake. In the pseudo-exponential distribution, the first validator has
10% of the total stake, the second has 10% of the rest (i.e. 9% of the
total stake), etc. The last validator, however, has all the remaining
stake (that is where the “pseudo” comes from). The “actual”
distribution corresponds to the assignment of stake to validators on
the NEAR mainnet in September 2025. The standard deviation is not
shown, as it is at least an order of magnitude smaller than the value
itself.</p>

<p>Note that the uniform sample has less than ⅓ of the stake despite its
size being more than ⅓ of the total number of validators. This is
because it does not contain 111 distinct nodes - some validators have
been picked multiple times, but their stake counts only once.  The key
takeaway from the above picture is that non-uniform stake
distributions tend to lead to stronger samples. This is because very
“rich” validators have a high probability of being chosen and drive
the sample strength up.</p>

<p>We are slightly hand-wavy about the analysis of the sample strength
since, at the end of the day, it is relatively easy to satisfy
$\alpha$ whenever we are creating a validator sample in practice. We
can simply choose the sample to satisfy $\beta$ and $\gamma$ and then,
if necessary, keep increasing the sample size $s$ and quorum $q$ until
$\alpha$ is satisfied too. While it might impact the performance, it
will never compromise safety or liveness.</p>

<h2 id="in-a-nutshell">In a nutshell</h2>

<p>We have seen how to randomly choose a sample of validators to vote on
the correctness of NEAR state transitions. We also discussed how to
quantify the safety and liveness guarantees we obtain based on the
parameters of the sample:</p>

<ul>
  <li>$s$: sample size</li>
  <li>$q$: quorum size required to accept a state transition</li>
</ul>

<p>In essence, for a given sample size $s$, the quorum size $q$ is the
knob we can turn to determine where on the safety-liveness trade-off
we want to be. Increasing $q$ strengthens safety but weakens liveness
and vice versa. The sample size $s$ determines how “tough” this
trade-off is: if $s$ is too small, we only have bad options. The
larger $s$, the more favorable options we have to choose from (using
$q$). There is no free lunch, however, and we need to pay for these
better options by an increased computation and communication overhead
associated with larger samples.</p>

<p>To deal with an adaptive adversary that can corrupt validators on the
fly, we use the concept of sample strength to quantify the cost of an
adaptive corruption. We can then set acceptable limits on</p>

<ul>
  <li>$\alpha$: The minimal cost of an adaptive corruption of a validator sample</li>
  <li>$\beta$: The safety violation probability</li>
  <li>$\gamma$: Liveness violation probability</li>
</ul>

<p>and choose a sample that respects all these limits. To achieve this, we can proceed as follows:</p>

<ol>
  <li>Pick acceptable security parameters $\alpha$, $\beta$, and $\gamma$.</li>
  <li>Pick sample parameters s and q based on $\beta$ and $\gamma$.</li>
  <li>Draw a sample of size s by choosing a random validator s times,
with the probability of choosing a validator proportional to its stake.</li>
  <li>While the sample strength is less than $\alpha$, keep adding validators.</li>
</ol>

<p>The first two steps only need to be performed once and only the last
two steps are executed for each sample. A practical alternative to
point (4.) is to simply pick an all new sample of size s and hope it
will be stronger. We might tend to do this if most of the samples we
draw are strong enough and this happens rarely, since it technically
shortens the expected time until a safety violation happens. What we
buy for it is a more consistent performance where no sample exceeds
the size $s$.</p>

<p>Lastly, it is important to note that we have always been considering
the worst-case scenario, namely one third of the stake being actually
controlled by faulty validators throughout the lifetime of the
system. The computed failure probabilities are upper bounds that are
tight only in the worst case. In practice, the actual amount of stake
controlled by faulty validators stays well below the assumed limit
($f$) the vast majority of the time. Therefore, the real-world failure
probabilities can also be expected to remain significantly below
$\beta$ and $\gamma$.</p>

<hr />

<p><span id="footnote1">$^1$</span>We use the entropy provided by the Near protocol at each block as
the source of randomness. Whenever we perform “random” sampling, we
use pseudo-randomness derived from this entropy. (<a href="#footnote1-anchor">back to text</a>)</p>

<p><span id="footnote2">$^2$</span>For details on how we compute the exact values, have a look at
the <a href="https://github.com/matejpavlovic/validator-sampling">code</a>. (<a href="#footnote2-anchor">back to text</a>)</p>]]></content><author><name>Matej Pavlovic</name></author><category term="research" /><summary type="html"><![CDATA[NEAR’s current approach is sufficient for the current number of shards and validators, but it can easily become inefficient if the number of validators grows. This blog post presents a new, simple and efficient way of selecting NEAR’s validators and analyzes its safety and liveness guarantees.]]></summary></entry><entry><title type="html">Enhancing NEAR Tokenomics: Version 2.9.0 Halving Upgrade</title><link href="https://blog.nearone.org/announcement/2025/10/21/enhancing-near-tokenomics.html" rel="alternate" type="text/html" title="Enhancing NEAR Tokenomics: Version 2.9.0 Halving Upgrade" /><published>2025-10-21T13:30:00+00:00</published><updated>2025-10-21T13:30:00+00:00</updated><id>https://blog.nearone.org/announcement/2025/10/21/enhancing-near-tokenomics</id><content type="html" xml:base="https://blog.nearone.org/announcement/2025/10/21/enhancing-near-tokenomics.html"><![CDATA[<p>To support a more sustainable tokenomic model and strengthen incentive alignment across the ecosystem, nearcore version 2.9.0 reduces the maximum annual inflation rate of NEAR Protocol from 5% to 2.5%. This upgrade will become effective once 80% of stake of block producing validators choose to adopt it. For more details on upgrading your mainnet node, please see the <a href="https://github.com/near/nearcore/releases/tag/2.9.0">2.9.0 release notes</a>.<!--more--></p>

<p>This initial halving upgrade is a principles-driven and adaptive approach to maintain sustainability of issuance around the NEAR token, while also laying the foundation for a long-term and systematic inflation strategy. This upgrade is based on community feedback and a broadly supported initial proposal across the NEAR ecosystem. Going forward, House of Stake will play a critical role in the decision-making process around the economic parameters for the NEAR ecosystem.</p>

<h2 id="timeline-and-details">Timeline and Details</h2>

<p>If the vote passes by end of the epoch that includes 01:00:00 UTC on Tuesday, October 28, 2025, the protocol is expected to upgrade to version 81 approximately 7-8 hours after that voting epoch ends.</p>

<p>Conversely, if the vote does not succeed within the designated timeframe, the voting window may remain open for up to an additional 23 days. In this scenario, the protocol upgrade to version 81 is expected to occur in the epoch following the one during which more than 80% of stake has been upgraded.</p>

<p>There will not be any forking of NEAR Protocol because such an upgrade will not be effective until the threshold has been reached.</p>

<p>As inflation is reduced to ca. 2.5%, staking rewards will accordingly change to 4.75%, assuming 50% of the total supply remains staked. To support this transition, MetaPool, LiNEAR, HOT_DAO, and Gauntlet have proposed two complementary programs strengthen incentive alignment across validators and House of Stake governance participants:</p>

<ol>
  <li><strong><a href="https://gov.near.org/t/hsp-002-validator-support-program/41687">Proposal – HSP-002</a></strong>: Support smaller validators to ensure network decentralization</li>
  <li><strong><a href="https://gov.near.org/t/hsp-003-venear-holder-rewards-program/41688/1">Proposal – HSP-003</a></strong>: Increase rewards for veNEAR holders to reward early governance participation</li>
</ol>

<p>Proposal HSP-002 is contingent on NEAR Protocol successfully reducing its inflation to 2.5%, and HSP-003 assumes HSP-002 has been approved and implemented. To read the full proposals, discuss them with the wider NEAR community, and gain governing power, <a href="https://gov.houseofstake.org/info">join House of Stake today</a>.</p>

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

<p>NEAR’s initial inflation rate, set over five years ago before mainnet launch, was designed to bootstrap an active validator set. Today, the network has over 300 active validators and uses a stateless validation security model, increasing cost-efficiency and eliminating the need to reward actors for monitoring and ensuring the security of shards. This halving upgrade and two complementary proposals aim to transition NEAR toward a more sustainable inflation model, while ensuring network decentralization and long-term incentive alignment across validators and NEAR token holders.</p>

<p>With NEAR now facilitating multi-billion dollar volumes and generating new sources of fees through NEAR Intents and future AI products, these economic enhancements help ensure the sustainable growth of the protocol for the long term and create economic opportunity for as many actors across the NEAR ecosystem as possible.</p>

<p>To support these enhancements, please <a href="https://github.com/near/nearcore/releases/tag/2.9.0">upgrade your mainnet node</a> today and <a href="https://gov.houseofstake.org/proposals">vote on the proposals</a> in House of Stake.</p>]]></content><author><name></name></author><category term="announcement" /><summary type="html"><![CDATA[To support a more sustainable tokenomic model and strengthen incentive alignment across the ecosystem, nearcore version 2.9.0 reduces the maximum annual inflation rate of NEAR Protocol from 5% to 2.5%. This upgrade will become effective once 80% of stake of block producing validators choose to adopt it. For more details on upgrading your mainnet node, please see the 2.9.0 release notes.]]></summary></entry><entry><title type="html">Mainnet Decreased Block Production Rate</title><link href="https://blog.nearone.org/postmortem/2025/07/07/mainnet-decreased-block-production-rate.html" rel="alternate" type="text/html" title="Mainnet Decreased Block Production Rate" /><published>2025-07-07T09:37:58+00:00</published><updated>2025-07-07T09:37:58+00:00</updated><id>https://blog.nearone.org/postmortem/2025/07/07/mainnet-decreased-block-production-rate</id><content type="html" xml:base="https://blog.nearone.org/postmortem/2025/07/07/mainnet-decreased-block-production-rate.html"><![CDATA[<p>NEAR mainnet briefly slowed on deployment of new smart contracts after the validator set grew past 256: the broadcast routine that erasure‑codes contract byte‑code for all validators relied on a Reed–Solomon library hard‑capped at 256 recipients, so the chunk producer that included a <code class="language-plaintext highlighter-rouge">DeployContract</code> transaction crashed during the final “code distribution” step, though the finished chunk still propagated. The Near One team released a hot‑fix that simply truncated the recipient list, which worked for any nodes that upgraded to adopt the fix, but the fix didn’t account for the creation of a Reed-Solomon encoder on un-upgraded nodes, which caused any un‑upgraded node to crash when it tried to forward those contract parts, temporarily enlarging the blast radius; block throughput dipped to ~0.1 blocks/s for about half an hour and then recovered as most validators adopted the patch.<!--more--></p>

<p><a href="/assets/1751897466-blockrate.png" target="_blank"><img src="/assets/1751897466-blockrate.png" alt="Blockrate chart" /></a></p>

<h2 id="timeline-and-details">Timeline and Details</h2>

<p><em>July 3rd, 2:30 AM, UTC</em><br />
First report of a validator node crashing.</p>

<p><em>July 3rd, 2:48 AM, UTC</em><br />
Second report, the same node has crashed again.</p>

<p><em>July 3rd, 2:54 AM, UTC</em><br />
Initial assessment confirming the number of parts has to be over 256. At this point the chain is progressing and chunks are produced fine, some nodes occasionally crash and restart. Contract deployment is not a frequent action, thus the impact is limited.</p>

<p><em>July 3rd, 08:11 AM, UTC</em><br />
A domain expert reviews the issue and identifies the root cause.</p>

<p><em>July 3rd, 08:27 AM, UTC</em><br />
A quick fix is ready and sent for review. The fix disables contract code distribution if the number of recipients is over 256.</p>

<p><em>July 3rd, 10:08 AM, UTC</em><br />
A concern is raised that not sending the contract code to the validators may create performance issues as they would need to fetch the contract code during state witness validation, slowing down this step. Considering the frequency of contract deployment the performance impact was overestimated.</p>

<p><em>July 3rd, 10:35 AM, UTC</em><br />
Another fix is ready, truncating the list of recipients if it is longer than 256. This fix would prevent the chunk producer from crashing. The team does not realize that the recipients would be crashing instead if they are not applying the same fix.</p>

<p><em>July 3rd, 3:41 PM, UTC</em><br />
After merging and testing the fix, the 2.6.5 release is ready.</p>

<p><em>July 3rd, 5:44 PM, UTC</em><br />
2.6.5 is published and announced as <code class="language-plaintext highlighter-rouge">CODE_YELLOW_MAINNET</code>.</p>

<p><em>July 4th, 11:30 PM, UTC</em><br />
As the validators adopt the version 2.6.5 they no longer crash upon contract deployment. Unfortunately, non-upgraded receiving validators start crashing. The Mainnet block rate drops to 0.15 (1 block in 7 seconds), the network performance degrades.</p>

<p><em>July 4th, 7:17 AM, UTC</em><br />
2.6.5 is adopted by 73% of the validators, the block rate is back to normal. During the incident the block rate went down to almost 0.1 for 30 minutes (1 block in 10 seconds).</p>

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

<p>The original issue did not cause significant impact and was correctly assessed as <code class="language-plaintext highlighter-rouge">CODE_YELLOW</code>. Unfortunately we have overestimated potential performance impact of a simple fix and opted for a more complex one trying to preserve the optimized behavior, which caused a network degradation. At this point we should have upgraded to <code class="language-plaintext highlighter-rouge">CODE_RED</code> to accelerate the adoption of the patch.</p>

<p>NEAR One’s release testing includes a gradual rollout of a new version and could have caught the issue, but the full suite takes days to run and does not have hundreds of validators.</p>

<p>Going forward we will be taking a more careful approach towards mainnet hotfixes, opting for the safest option possible and making sure the changes are properly tested. That being said, dealing with Mainnet incidents is extremely hard due to the time pressure and severity of potential consequences.</p>

<p>Finally, we would like to highlight that this issue has been triggered due to progressing decentralization of NEAR and growing number of participating validators.</p>]]></content><author><name>Anton Astafiev</name></author><category term="postmortem" /><summary type="html"><![CDATA[NEAR mainnet briefly slowed on deployment of new smart contracts after the validator set grew past 256: the broadcast routine that erasure‑codes contract byte‑code for all validators relied on a Reed–Solomon library hard‑capped at 256 recipients, so the chunk producer that included a DeployContract transaction crashed during the final “code distribution” step, though the finished chunk still propagated. The Near One team released a hot‑fix that simply truncated the recipient list, which worked for any nodes that upgraded to adopt the fix, but the fix didn’t account for the creation of a Reed-Solomon encoder on un-upgraded nodes, which caused any un‑upgraded node to crash when it tried to forward those contract parts, temporarily enlarging the blast radius; block throughput dipped to ~0.1 blocks/s for about half an hour and then recovered as most validators adopted the patch.]]></summary></entry></feed>