Skip to content

Benchmarks ​

Proof generation is the most expensive client-side operation. These measurements use the bench harness, which runs the same WorkerProver and prover worker entry point (@lelantos-org/sdk/workers/prover) as a wallet.

ParameterValue
Date2026-10-07
CircuitTRANSACT_4X6, Merkle depth 11, ~33 MB zkey
NetworkHTTPS over LAN
Methodone warm-up run, then median of 5 timed runs from lelantos:prover:wasm logs

WASM prover (browser) ​

DeviceThreadsTotalWitnessGroth16Prepare
macOS · Chrome 15416233 ms76 ms156 ms292 ms · cold
iPhone · Safari 18.541021 ms75 ms946 ms3039 ms · cold
iPhone · Safari 18.541008 ms78 ms931 ms173 ms · warm
  • Threads is the size of the thread pool actually created.
  • Prepare is artifact download and parsing, measured outside the proof timing. Warm means the zkey was already in the Cache API; cold means it was downloaded.
  • The Mac has 16 cores and 32 GB of memory; the iPhone has 4 cores.

Observations:

  • Witness generation is single-threaded and takes 75–78 ms on both devices.
  • Groth16 is 67% of total time at 16 threads and 93% at 4 threads.
  • A page without cross-origin isolation gets no thread pool, and the SDK uses snarkjs instead. That configuration is not measured here.
  • On the iPhone, cold artifact preparation (3.0 s) exceeds proving time; with the artifacts in the Cache API it takes 173 ms. It occurs once per session; wallet.warmProver() (or prover: { warmup: "eager" }) and the Cache API move it out of the first transaction. Over the public internet, download time depends on bandwidth.

Native prover (relayer) ​

The relayer proves natively on a larger circuit. Only the cost breakdown has been measured:

  • Multi-scalar multiplications: 86% of proving time. The six 2^17 FFTs: 14%.
  • taceo-groth16 is ~1.7× faster than ark-groth16, both at 113k constraints on 16 threads and on the 4x6 zkey.

These figures are not comparable with the WASM table: the circuit and arithmetic backend differ.

Measuring in your application ​

Enable debug logging for the prover namespaces to record the same phases:

ts
import { 
configureLogging
,
consoleSink
} from "@lelantos-org/sdk";
configureLogging
({
level
: "debug",
sink
:
consoleSink
(),
namespaces
: ["lelantos:prover:*"],
});

The effective field on lelantos:prover:wasm records the thread count. If timings are far above the table, check it first: a failed thread-pool initialization is the usual cause.

Next ​