Cost of the second signature verifier: 2026-10-22, x86-64 reference VPS
Cost of checking every signature with two independent verifiers, liboqs and qanum-pqverify, on an x86-64 reference VPS, with raw data.
This page reproduces docs/benchmarks/2026-10-22_x86-64_second-verifier/README.md from qanum-core v0.9.2 (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_dual_bench, the same benchmark as
../2026-10-21_arm_second-verifier/,
on the x86-64 machine of
../2026-10-14_x86-64/. It measures what
the node pays for checking every post-quantum signature with two independent
verifiers: liboqs and qanum-pqverify. The qanum-pqverify code is the one
of stage 3 of the ARM runs. The .jsonl files are the raw output; this page
is a summary of those files.
Machine and method
| Core 1 | Core 3 | |
|---|---|---|
| Data | epyc-core1.jsonl |
epyc-core3.jsonl |
| Environment | epyc-core1.env.txt |
epyc-core3.env.txt |
| Run time | 18 s | 18 s |
- Machine: KVM guest, AMD EPYC-Milan, 6 vCPUs, Linux 6.8.0-138-generic.
Shared: a testnet seed node was mining on another vCPU during the runs
(its core is recorded before and after each run in the
.env.txtfiles), and the guest exposes no CPU frequency data. - Build:
cargo build --release --locked --example pq_dual_benchfrom the v0.9.2 release source, Rust 1.98.1 (rust-toolchain.toml; therustcfield of the files is empty becauserustcwas not on the run’sPATH), liboqs 0.16.0 linked,qanum-pqverify0.1.0. liboqs is a distributable build with its x86-64 ML-DSA code enabled (OQS_ENABLE_SIG_ml_dsa_87_x86_64inoqsconfig.h). - Command:
taskset -c <core> target/release/examples/pq_dual_bench --out epyc-core<core>.jsonl, undernice -n 10, one run after the other. - Four ways of checking the same valid signatures, interleaved, three
rounds each, as in the ARM runs:
liboqs(OqsVerifier::verify_suite, the node without a second verifier),qanum-pqverifyalone,dual enforce(DualVerifierin enforce mode: mainnet, regtest) anddual shadow(testnet). - Statistics: median of all samples of the three rounds for valid signatures. Largest spread between rounds of the same way: 643 basis points (core 1) and 595 (core 3), more than on the ARM machine, which ran nothing else.
- No disagreement: each run made 28,868 checks with
qanum-pqverifyand the two verifiers never disagreed. - liboqs matches the earlier run: ML-DSA-87 54.3 to 54.7 µs and SLH-DSA-256s 1.16 to 1.20 ms, against 54.4 to 55.1 µs and 1.16 to 1.24 ms on 2026-10-14.
Valid signatures
Median time per valid signature, core 1 / core 3. The percentage is against
liboqs in the same run.
| Suite | liboqs | qanum-pqverify alone |
dual enforce |
|---|---|---|---|
| ML-DSA-44 | 20.7 µs / 20.7 µs | 64.9 µs / 64.7 µs (314 % / 314 %) | 86.6 µs / 86.7 µs (418 % / 420 %) |
| ML-DSA-87 | 54.7 µs / 54.3 µs | 171.7 µs / 172.1 µs (314 % / 317 %) | 228.5 µs / 226.2 µs (418 % / 417 %) |
| SLH-DSA-128s | 563 µs / 560 µs | 573 µs / 576 µs (102 % / 103 %) | 1.13 ms / 1.14 ms (202 % / 204 %) |
| SLH-DSA-256s | 1.20 ms / 1.16 ms | 1.23 ms / 1.20 ms (103 % / 104 %) | 2.42 ms / 2.36 ms (203 % / 205 %) |
A valid signature costs the sum of both verifiers. dual shadow gives the
same times within 2 % (see the .jsonl files).
- SLH-DSA:
qanum-pqverifyis as fast as liboqs, as on ARM. - ML-DSA:
qanum-pqverifytakes about 3.1 times liboqs here, against about 2 times on ARM. liboqs uses its x86-64 vector code for ML-DSA on this machine;qanum-pqverifystays portable Rust with no assembly or vector code.
Invalid signatures (one byte flipped)
Median per invalid signature, core 1 / core 3.
| Suite | liboqs | dual enforce | dual shadow |
|---|---|---|---|
| ML-DSA-44 | 20.8 µs / 20.2 µs | 22.3 µs / 20.0 µs | 86.2 µs / 84.2 µs |
| ML-DSA-87 | 54.9 µs / 55.5 µs | 55.1 µs / 55.4 µs | 232.5 µs / 227.8 µs |
| SLH-DSA-128s | 546 µs / 539 µs | 545 µs / 535 µs | 1.10 ms / 1.08 ms |
| SLH-DSA-256s | 1.18 ms / 1.17 ms | 1.17 ms / 1.24 ms | 2.42 ms / 2.44 ms |
In enforce mode the second verifier is not called when liboqs rejects, so an invalid signature costs what it cost before. Shadow mode runs both.
Verify Units under enforce mode
Estimate = ceil(100 × median / median of ML-DSA-44), both through
dual enforce. Core 1 / core 3.
| Suite | Estimate | Consensus VU |
|---|---|---|
| ML-DSA-44 | 100 / 100 | 100 |
| ML-DSA-87 | 264 / 261 | 290 |
| SLH-DSA-128s | 1310 / 1313 | 1600 |
| SLH-DSA-256s | 2795 / 2725 | 3600 |
Every constant covers its estimate.
Worst-case block estimate
A block holding as many signatures of one suite as MAX_BLOCK_VERIFY_UNITS
(200,000) allows, times the median of one signature, on one core. This is
an estimate, not a measured block. Block validation spreads signatures over
all cores, so a node with several cores takes less.
| Suite (signatures) | liboqs | dual enforce |
|---|---|---|
| ML-DSA-44 (2000) | 42 ms / 42 ms | 174 ms / 174 ms |
| ML-DSA-87 (689) | 38 ms / 38 ms | 158 ms / 156 ms |
| SLH-DSA-128s (125) | 71 ms / 70 ms | 142 ms / 143 ms |
| SLH-DSA-256s (55) | 66 ms / 64 ms | 134 ms / 130 ms |
On this machine the worst block goes from about 70 ms of signature checks with liboqs alone to about 175 ms with both verifiers.