Benchmarking in-browser p256 ECDSA proving systems

Vlad’s grant for Hylé was to benchmark different proof systems for browser proving of ECDSA signatures on the secp256r1 (aka p256) curve. Here are his conclusions.

Benchmarking in-browser p256 ECDSA proving systems

Vlad’s grant for Hylé was to benchmark different proof systems for browser proving of ECDSA signatures on the secp256r1 (aka p256) curve. Here are his conclusions; you can also look at the repo for source code.

Why verify p256 ECDSA signatures?

p256 is a NIST standard. It is used for many real-world cases, including signing passports, national identity cards, passkey/WebAuthn signatures, and several digital signature schemes for digitally signed documents, such as the Portuguese invoice system ATCUD.

Verifying signatures onchain isn’t easy, and proving these verifications can be very expensive (Dubois, 2023; zkZoomer, 2024). Not only that but in the blockchain space, the p256 elliptic curve doesn’t get the love that pairing-friendly curves or even secp256k1 get.

Creating zero-knowledge proofs of secp256r1 ECDSA signatures will allow people to generate statements from signed documents and benefit from selective disclosure. It’s very easy to verify them, in theory… as long as you send the entire document to the verifier.

However, in the majority of cases, these documents hold very sensitive identity data. So, the better way to go is to generate a proof that you can verify a signature for a given document: the document itself remains private while the signature is verified and proven valid.

Relying on a prover means sharing your entire sensitive document with the prover, which defeats the purpose of zero-knowledge, as we explained above. To avoid these risks, you want to use client-side proving; for an optimal user experience, the proving should be done in the browser. Of course, this is easier said than done: most current solutions can’t comply with the inherent computation limits imposed by browsers.

For example, the easiest way to build provable programs is to use a zkVM. However, most of them are very RAM-intensive. On browsers, this is a limiting factor: WASM is 32 bits, so it can’t use more than 4 GB of RAM. Because of this, most zkVMs, including SP1 and risc0, can’t be leveraged.

Three options: sending your sensitive data onchain (yikes), sending your sensitive data to a prover (still requires trusting the prover with the document), or, ideally, proving locally and only sending the proof to another party.
Proving locally is more private and secure than any other option.

Three options: sending your sensitive data onchain (yikes), sending your sensitive data to a prover (still requires trusting the prover with the document), or, ideally, proving locally and only sending the proof to another party.

With all that said, one question remains: what is the best backend for proving a P256 signature client-side?

Understanding the benchmark: methodology

The criteria

The benchmark criteria were:

  • Time to generate the proof
  • RAM usage during the proof generation
  • User experience, especially ease of use

We tested our systems on several devices, with Chrome for desktop on a Macbook M1, Safari for iPhone (16 and Pro), and Chrome for Android on a Samsung Galaxy A23.

What we benchmarked

Before creating the benchmark, we made a long list of possible systems to test, including but not limited to:

  • SP1 (v4, which includes a p256 precompile)
  • Risc0 (v1.2)
  • Noir with UltraHonk and UltraPlonk
  • Halo2
  • Cairo with Stwo

As explained above, SP1 and Risc0 used much too much RAM, making them virtually unusable for client-side p256 proving.

For the p256 proving benchmark, we had four potential front-ends to compare: Noir, Circom, Halo2, and Cairo.

Testing p256 signature proving

Noir

The first thing that strikes us is that Noir is very easy to use.

We can use their JavaScript library directly: we don’t even need to compile to WASM for in-browser proving. They also have a backend library, barretenberg, and precompiles (called black box functions), including one for p256 ECDSA.

Leveraging all this, you only need a single line in your Noir program and a few more lines of JavaScript to prove a signature.

Barretenberg supports two proof systems, UltraPlonk and UltraHonk, so we tested both.

The conclusion is very clear. Use Honk: it’s much faster. Plonk has smaller proofs, but unless you have extreme memory constraints, that improvement is marginal.

On an M1 Macbook Air, we proved a signature in less than 3 seconds with Honk and multithreading enabled.

On mobile, proving is possible under certain conditions. iPhones severely restrict RAM usage per tab, meaning that when you try to prove your signature, you end up with an out-of-memory error. However, on a modern Android smartphone, it takes less than 10 seconds to generate your proof.

We also unsuccessfully explored community-made backends for Noir. When making a proving backend, each black box function needs to be handled explicitly, and none of the existing backends currently support secp256r1_ecdsa(). Some of these alternative backends are still adding black boxes and might eventually support ecdsa on p256; we’re hopeful!

System Time
Noir (UltraHonk, multi-thread) on M1 Macbook 2.06s
Noir (UltraHonk, multi-thread) on Samsung Galaxy A23 Android ~6s
Noir (UltraPlonk, multi-thread) on M1 Macbook 7.96s
Noir (UltraHonk, single thread) on M1 Macbook 8.06s
Noir (UltraPlonk, single thread) on M1 Macbook 33.57s
Noir (UltraHonk) on iPhone 16 or iPhone Pro Out of memory error

Circom

We expected to have trouble writing our constraints in Circom, as it is a much lower-level language than Noir. Fortunately, PSE had already worked on a Circom implementation of p256!

This allowed us to move directly to the testing stage, which yielded disappointing results: the circuit includes more than a million constraints, which no single browser tab can handle, and the experience was short-lived.

Halo2

The good news when starting was that someone else had already already implemented ECDSA signatures on Halo2. Their purpose was to prove WebAuthn and sign Ethereum transactions with passkeys (such as Face ID).

The bad news was that the repo hadn’t been updated in two years. If you try to build it as-is, you get an error due to compiler incompatibilities. We stripped it down to only the required part with Pierre’s help, and by doing that, we got it to compile properly, but only on an older nightly version. Even like this and with heavy reliance on the Halo2 tutorial, we couldn’t get it to compile on WASM.

In the end, we somehow managed to compile it − don’t ask us how because we have no idea. We ran it… and got a runtime error with no further details.

Cairo

Cairo has an elliptic curve library called garaga. It seems very promising, but when we did the benchmark, it didn’t yet have the required functionality to make an ECDSA verification algorithm.

This is no longer true, so feel free to contribute your test results to Vlad’s repo!

Conclusions

The results

The results are very straightforward: zkVMs (SP1 & Risc0) consume too much RAM to run in a browser. Circom is too heavy for a browser, and we couldn’t get the verification and proving running with Halo2 and Cairo. On the other hand, Noir gave us an excellent developer experience and very good running times.

Vlad created a repo for the benchmark, which currently only includes the functioning implementation (in Noir): feel free to contribute to it and add new proof systems. We’d love to turn this into a valuable tool for app builders!

Our takeaways

Our takeaway goes further than the actual tests, though. From this experiment, we derive a strong conviction that the ecosystem is not mature enough: there aren’t enough in-browser proving options outside of Noir and, possibly, Circom.

Where do we go next?

Arithmetics

The problem with proving ECDSA, specifically, is that we get field arithmetic wrong: as long as the scalar field of our proof system and the base field of p256 aren’t the same, we’ll have bulky provers and long proving times.

In our opinion, Spartan-ECDSA has the best approach to date for proving ECDSA on the secp256k1 curve. It uses the twin curve secq256k1 (notice the p is now a q; this is because we usually use Fp and Fq to denote the base and scalar field of an elliptic curve), which allows it to need only around 8k constraints.

A similar curve exists for p256. It’s called TOM-256 and was created to prove P-256 signatures while hiding the public key. As fast as it is, though, the proof system used by TOM-256 is based on Pedersen commitments and doesn’t allow composability.

zkVMs

The industry needs a lightweight zkVM that can run in a browser. Existing zkVMs require 16GB of RAM, which is incompatible with in-browser testing.

In two to five years, hardware and software improvements will completely transform the landscape of zkVMs, and the conversation will be completely different. We’re excited for that time to come!