Post-quantum cost measurements: 2026-10-14, ARM build machine
Post-quantum cost measurements on an aarch64 machine, with raw data.
This page reproduces docs/benchmarks/2026-10-14_arm/README.md from qanum-core v0.9.0 (release tag). Links to files that are not published on this site are shown as plain text until the source repository is public.
Contents
Produced by examples/pq_bench on a development build that predates the
first public release. The constants were set from
these numbers afterwards: the “Consensus VU” column shows the values at
measurement time (SLH-DSA-128s 1200, SLH-DSA-256s 3500); today they are
1600 and 3600 (src/constants.rs). The .jsonl files are the raw output; this page
is a summary generated from them. Every number below can be recomputed from
the files; the reduction rules are pinned by tests/pq_bench_report.rs.
Machine and method
| |
Gold core |
Silver core |
| File |
gold-core6.jsonl |
silver-core2.jsonl |
Core (taskset -c) |
6 (CPU part 0xd0d) |
2 (CPU part 0x805) |
| Max frequency at start |
2.42 GHz |
1.80 GHz |
| Run time |
123 s |
445 s |
- Machine: the development machine, an aarch64 big.LITTLE SoC (Gold: implementer
0x41, part 0xd0d;
Silver: implementer 0x51, part 0x805), Linux 4.19.113-27166950,
governor schedutil. Compiler rustc 1.98.1 (48a229cea 2026-09-01); liboqs through
oqs 0.11.0 / oqs-sys 0.11.0+liboqs-0.16.0-rc1 (git rev c6fd1a6, from Cargo.lock).
The -rc1 is upstream’s package label, not the library: that rev vendors liboqs
0.16.0 plus 11 later commits (2966c35), which reports 0.16.0. These runs predate
the liboqs_linked field of meta; the version is held by
crypto::tests::linked_liboqs_is_the_pinned_release.
- One core, one job: each run pinned with
taskset, one heavy job on the
machine at a time, under nice -n 10; rayon_threads = 1, so block
validation ran on one thread. Load and per-core frequency before and after
each run are in the .env.txt files.
- Command:
QANUM_BENCH_COMMIT=<commit> taskset -c <core> target/release/examples/pq_bench --out <file>.jsonl
(cargo build --release --locked --example pq_bench first).
- Statistics: each sample is one operation timed on its own; tables show
the median and, in brackets, the 95th percentile.
Signature verification and Verify Units
Through OqsVerifier::verify_suite (the node’s check, lengths included), in
A/B/A/B/A rounds (ML-DSA-44 / ML-DSA-87, then SLH-DSA-128s / SLH-DSA-256s).
Estimate = ceil(100 × median / median of ML-DSA-44).
| Suite |
Gold median (rounds) |
Silver median (rounds) |
VU estimate Gold / Silver |
Consensus VU |
Covered |
| ML-DSA-44 |
59.0 µs |
190.6 µs-191.2 µs |
100 / 100 |
100 |
yes |
| ML-DSA-87 |
166.6 µs |
544.4 µs-546.3 µs |
283 / 286 |
290 |
yes |
| SLH-DSA-128s |
861.0 µs-867.6 µs |
3.00 ms-3.01 ms |
1466 / 1576 |
1200 |
no |
| SLH-DSA-256s |
1.88 ms-1.89 ms |
6.66 ms-6.67 ms |
3200 / 3492 |
3500 |
yes |
- ML-DSA-87 and SLH-DSA-256s, the suites in use, stay within their constants
(SLH-DSA-256s by 8 units on Silver).
- SLH-DSA-128s costs more than its constant of 1200 at the time, as an
earlier measurement already showed (ratio 15.0-15.7). Its constant was
raised to 1600 afterwards.
- Drift between rounds of the same suite: at most 77 basis points (0.77 %);
ML-DSA-44 control at the end of the run vs the start: 53 bp (Gold), 44 bp (Silver).
Key generation and signing (what a wallet pays)
| Suite |
Keygen Gold |
Keygen Silver |
Sign Gold |
Sign Silver |
| ML-DSA-44 |
59.5 µs (61.6 µs) |
203.1 µs (247.9 µs) |
147.1 µs (355.4 µs) |
502.6 µs (1.23 ms) |
| ML-DSA-87 |
172.2 µs (182.9 µs) |
594.3 µs (706.9 µs) |
315.1 µs (739.0 µs) |
1.15 ms (2.83 ms) |
| SLH-DSA-128s |
116 ms (120 ms) |
400 ms (401 ms) |
899 ms (911 ms) |
3,095 ms (3,104 ms) |
| SLH-DSA-256s |
109 ms (112 ms) |
387 ms (390 ms) |
1,424 ms (1,425 ms) |
5,193 ms (5,202 ms) |
ML-KEM-1024 (P2P handshake), through OqsMlKem1024
| Operation |
Gold |
Silver |
| keygen |
46.6 µs (48.2 µs) |
159.6 µs (190.5 µs) |
| encapsulate |
41.3 µs (41.5 µs) |
141.9 µs (169.0 µs) |
| decapsulate |
48.0 µs (51.7 µs) |
161.0 µs (204.9 µs) |
cold: the global signature cache emptied before each sample, so the
signature is really verified. warm: the transaction is already in the cache
(as for a block whose transactions were seen in the mempool).
| Spend |
No crypto Gold / Silver |
Cold Gold / Silver |
Warm Gold / Silver |
| P2PQK ML-DSA-87 |
1.67 µs (1.72 µs) / 7.24 µs (7.45 µs) |
200.9 µs (217.4 µs) / 696.5 µs (823.4 µs) |
22.3 µs (22.4 µs) / 78.6 µs (84.1 µs) |
| P2RKPQ main ML-DSA-87 |
1.67 µs (1.72 µs) / 7.29 µs (7.55 µs) |
200.7 µs (217.7 µs) / 692.7 µs (811.8 µs) |
22.4 µs (22.5 µs) / 79.0 µs (87.0 µs) |
| P2RKPQ recovery SLH-DSA-256s |
3.59 µs (4.27 µs) / 14.7 µs (15.1 µs) |
1.94 ms (1.98 ms) / 7.07 ms (7.19 ms) |
86.1 µs (88.4 µs) / 297.0 µs (351.3 µs) |
Blocks filled up to the first consensus limit
Through validate_block_no_crypto and validate_block_with_verifier: no PoW
check and no sparse UTXO cache (the UTXO set holds only the spent outputs),
one thread.
| Block |
Spends |
Limit reached |
Verify units |
Auth bytes |
| P2PQK ML-DSA-87 |
415 |
auth_bytes |
120,350 / 200,000 |
2,998,806 / 3,000,000 |
| P2RKPQ recovery SLH-DSA-256s |
57 |
verify_units |
199,500 / 200,000 |
1,702,264 / 3,000,000 |
| Block |
No crypto Gold / Silver |
Cold Gold / Silver |
Warm Gold / Silver |
| P2PQK ML-DSA-87 |
66.2 ms (67.1 ms) / 249 ms (253 ms) |
152 ms (153 ms) / 553 ms (556 ms) |
76.2 ms (76.9 ms) / 283 ms (287 ms) |
| P2RKPQ recovery SLH-DSA-256s |
13.9 ms (14.6 ms) / 48.8 ms (50.0 ms) |
125 ms (126 ms) / 446 ms (448 ms) |
18.7 ms (19.1 ms) / 65.4 ms (67.5 ms) |
- A block full of ML-DSA-87 spends reaches the auth-bytes limit first, at
60 % of the verify-unit budget.
- The worst case for verification is a block of SLH-DSA-256s recoveries, which
reaches the verify-unit limit. Its cold validation took less time than
that of the full ML-DSA-87 block, on both cores.
- The target block interval is 75 s.
Limits of these numbers
- One machine (an ARM big.LITTLE SoC), one core per run; not a server and
not x86. The older figures in
docs/BENCHMARK-PLAN.md (EPYC VPS, 2026-09-17,
ML-DSA-44 only) are not comparable.
- The block figures leave out the sparse UTXO cache that the running node
uses; the non-crypto part of a real node differs.
- Frequency scaling was left to the governor (
schedutil); the maximum
frequency of the Gold cores changed during the day (see .env.txt).