Skip to main content
This page is for the engineer planning capacity or deciding which predicates to keep. After reading it you will know what each circuit costs, where the time in a batch goes, and how big the artifacts are. Every number below is copied from the repository’s docs/PERFORMANCE.md for version 3.0.0 as shipped. These are single-machine measurements, not a benchmark claim, and native builds on other hardware are expected to differ. The machine used to run the commands in this documentation, a different one, proved the two-subject quickstart batch at about seven seconds per subject.

Hardware

These are Rosetta 2 numbers, not native arm64 numbers. The host is arm64, but the installed Rust toolchain’s host triple was x86_64-apple-darwin, so every binary measured is an x86_64 executable running under translation. A native aarch64-apple-darwin toolchain will be faster, likely substantially, so the figures are an upper bound on prove time for this hardware rather than a measurement of it. Check rustc -vV | grep host before comparing your own numbers. Tested on macOS only; Linux and Windows are untested.

Per-circuit proving and verification

Method: tests/perf_bench.rs in each circuit crate, 5 iterations, median reported.
The same command with trusset-circuit-preimage-knowledge and prod-proofs-preimage, trusset-circuit-tier-threshold and prod-proofs-tier, or trusset-circuit-set-membership and prod-proofs-set reproduces the other rows. The benchmarks are gated behind --ignored so they stay out of the default test suite. set_membership is the most expensive circuit by a wide margin: its trace is 2048 rows because the Merkle walk runs in-AIR at 30 rows per level. tier_threshold is the outlier relative to its trace size: 539 ms for 16 x 32, against 206 ms for the larger age_threshold. The repository attributes it to the constraint composition degree rather than the trace shape, and tracks it in docs/FUTURE_WORK.md. Version 3.0.0 changed nothing inside the circuits, so these figures differ from the 2.1.0 table only by measurement noise.

Full pipeline

Measured on the shipped config.example.json, which produces 9 leaves per subject: 5 proving leaves (1 age_threshold, 3 set_membership, 1 tier_threshold) and 4 commitment_only leaves. Per-leaf times from one run’s output/audit.log: The three set_membership leaves account for 3.74 of the 4.16 seconds. Per-leaf figures in a batch vary more than the isolated benchmarks because they share a warm process. Plan capacity against the isolated medians. Through version 2.1, --skip-proofs ran the full STARK and discarded the bytes. It now runs each circuit’s validate() and skips proving, so predicates are still enforced and the run is fast:

Artifact sizes

One 9-leaf subject under config.example.json: The version 3 manifest grew from 7.9 KB to 17.1 KB for the same nine leaves. That is roughly 1 KB per leaf of descriptor, validity interval, list-version reference and binding digest, plus the issuer and toolchain blocks. That is 7% of a bundle that is 92% proof bytes. An archive is modestly larger than the bundles it contains because proof bytes are base64 inside it, offset by secrets.enc being excluded. Choosing commitment_only for the four identity fields removes roughly 95 KB of proof bytes and 1.4 s of prove time per subject compared with proving them under preimage_knowledge.

Proof parameters

Pinned once in circuits/common/src/proof_options.rs for all four circuits: blowup 16, 27 FRI queries, 16 grinding bits, quadratic extension over Goldilocks, FRI folding 4, FRI remainder max degree 7, linear batching. Unchanged in version 3. The conjectured security is the minimum of three terms, evaluated against the largest workspace trace:
113 bits clears the published floor of 100 that manifest.stark.securityBits advertises. The floor is checked at compile time and again at prover startup.

Memory

Peak prover memory is driven by the set_membership trace and its low-degree extension, so it scales with the largest single leaf rather than with the leaf count. Subjects are processed one at a time, so a 10,000-subject batch has the same peak as a 1-subject batch. An earlier profile on x86_64 hardware reached 112.7 MB at 50 subjects, with a marginal cost of about 0.5 MB per subject. The design budget is 4 GB.

Build

There are no prebuilt binaries. A cold npm install compiles 130 crates from Cargo.lock; on the machine used for the quickstart on these pages it took just under four minutes. Subsequent builds are incremental and take seconds. Every bundle records the SHA-256 of the prover binary that produced it. That is what lets a bundle be traced to a build rather than to “some version of the tool”.

Where to look for slowdowns

  1. set_membership count. Each one costs about 1.1 to 1.5 s. A config with three of them spends 3.7 s of its 4.2 s there. Fields that do not need allowlist membership should not use it.
  2. commitment_only versus preimage_knowledge. The former is free; the latter costs 351 ms and 23 KB per leaf while proving only that the operator knows an opening of its own commitment.
  3. Several predicates on one field. Two age thresholds are two leaves and two proofs. Cheap for age_threshold, expensive for set_membership.
  4. Startup dominates small batches. A 1-subject run is mostly Node cold start plus the build check every npm command performs; a 100-subject batch amortises it.
  5. Check before you prove. npm start -- --dry-run costs about 0.1 s per subject and catches everything the prover would reject.

In the repository