# Classic vs Post-Quantum Crypto
ECDSA P-256 vs ML-DSA-65 across the full batch-size sweep (1–1024) on the
same machine. Both use the same algorithm for CA and subscriber keys.
ML-DSA-65 certificates are ~5.5 KB vs ~900 B for P-256.
`cert_gen` builds TBS once using the cached `sig_alg_der`, signs with
`sign_tbs_erased`, and assembles — the TBS DER (which embeds the ~2.5 KB
ML-DSA-65 SPKI) is encoded exactly once per certificate.
> **Note:** figures below were collected on 2026-04-16 using the
> native-ossl 0.1.1 OpenSSL backend with lazy PKCS#8 serialisation and
> cached EC SPKI headers. Each data point is the mean over ≥ 20 seconds of
> wall-clock measurement (hundreds to tens-of-thousands of repetitions).
## cert_gen — certificate issuance (subscriber keygen + CA sign)
| 1 | 0.048 | 20.9 K | 0.880 | 1.1 K | 18.3× |
| 2 | 0.129 | 15.5 K | 1.259 | 1.6 K | 9.8× |
| 4 | 0.161 | 24.9 K | 1.736 | 2.3 K | 10.8× |
| 8 | 0.207 | 38.6 K | 2.709 | 3.0 K | 13.1× |
| 16 | 0.256 | 62.4 K | 4.660 | 3.4 K | 18.2× |
| 32 | 0.341 | 93.9 K | 7.335 | 4.4 K | 21.5× |
| 64 | 0.472 | 135.5 K | 10.193 | 6.3 K | 21.6× |
| 128 | 0.863 | 148.3 K | 18.351 | 7.0 K | 21.3× |
| 256 | 1.711 | 149.6 K | 31.650 | 8.1 K | 18.5× |
| 512 | 3.139 | 163.1 K | 81.211 | 6.3 K | 25.9× |
| 1024 | 5.911 | 173.2 K | 117.406 | 8.7 K | 19.9× |
## cert_verify — certificate signature verification
| 1 | 0.086 | 11.6 K | 0.157 | 6.4 K | 1.8× |
| 4 | 0.158 | 25.4 K | 0.304 | 13.2 K | 1.9× |
| 16 | 0.317 | 50.4 K | 0.544 | 29.4 K | 1.7× |
| 64 | 1.288 | 49.7 K | 1.618 | 39.6 K | 1.3× |
| 256 | 3.896 | 65.7 K | 5.570 | 46.0 K | 1.4× |
| 1024 | 9.508 | 107.7 K | 24.867 | 41.2 K | 2.6× |
## ocsp_sign — OCSP response signing (N parallel responses)
| 1 | 0.030 | 33.1 K | 0.692 | 1.4 K | 23.1× |
| 4 | 0.090 | 44.5 K | 1.590 | 2.5 K | 17.7× |
| 16 | 0.183 | 87.2 K | 3.897 | 4.1 K | 21.3× |
| 64 | 0.319 | 200.4 K | 8.571 | 7.5 K | 26.9× |
| 256 | 1.261 | 203.1 K | 25.256 | 10.1 K | 20.0× |
| 1024 | 3.311 | 309.2 K | 98.292 | 10.4 K | 29.7× |
## ocsp_verify — OCSP response verification
| 1 | 0.079 | 12.6 K | 0.151 | 6.6 K | 1.9× |
| 4 | 0.157 | 25.4 K | 0.301 | 13.3 K | 1.9× |
| 16 | 0.345 | 46.4 K | 0.488 | 32.8 K | 1.4× |
| 64 | 1.282 | 49.9 K | 1.524 | 42.0 K | 1.2× |
| 256 | 3.815 | 67.1 K | 5.553 | 46.1 K | 1.5× |
| 1024 | 9.887 | 103.6 K | 21.786 | 47.0 K | 2.2× |
## crl_sign — CRL signing (1 CRL per batch, N revoked serials)
| 1 | 0.026 | 37.8 K | 0.707 | 1.4 K | 27.2× |
| 4 | 0.043 | 23.4 K | 0.782 | 1.3 K | 18.2× |
| 16 | 0.084 | 11.9 K | 0.956 | 1.0 K | 11.4× |
| 64 | 0.188 | 5.3 K | 0.978 | 1.0 K | 5.2× |
| 256 | 0.271 | 3.7 K | 1.179 | 0.8 K | 4.3× |
| 1024 | 0.694 | 1.4 K | 1.592 | 0.6 K | 2.3× |
## db_insert_certs / db_read_certs — SQLite I/O
| 1 | 0.056 | 0.098 | 1.8× | 0.020 | 0.022 | 1.1× |
| 4 | 0.102 | 0.196 | 1.9× | 0.029 | 0.037 | 1.3× |
| 16 | 0.235 | 0.715 | 3.0× | 0.067 | 0.066 | 1.0× |
| 64 | 0.571 | 1.452 | 2.5× | 0.184 | 0.241 | 1.3× |
| 256 | 1.834 | 10.147 | 5.5× | 0.360 | 0.811 | 2.3× |
| 1024 | 4.786 | 42.235 | 8.8× | 0.730 | 3.931 | 5.4× |
## Analysis
**Signing (cert_gen, ocsp_sign)** shows the largest asymmetry, driven by
ML-DSA-65's expensive lattice sampling on the signing path:
- At **batch=1**, both P-256 and ML-DSA-65 run single-threaded (Rayon is not
involved below the parallelism threshold). P-256 cert_gen (subscriber keygen
+ CA sign) takes 0.048 ms; ML-DSA-65 takes 0.880 ms — an 18.3× ratio. The
P-256 keygen benefits from the cached EC SPKI header (avoiding the OSSL_ENCODER
path) and deferred PKCS#8 serialisation.
- At **small-to-medium batches (2–32)**, the cert_gen ratio stabilises around
10–22× as Rayon begins parallelising subscriber keygen. P-256 saturates the
thread pool at lower batch sizes, reaching ~94 K/s by batch=32.
- At **medium-to-large batches (64–1024)**, P-256 cert_gen throughput continues
to climb (136–173 K/s), while ML-DSA-65 plateaus at 6–9 K/s. The sustained
ratio of ~20× reflects the fundamental cost difference in lattice vs elliptic
curve operations.
- **ocsp_sign** ratios (17–30×) are consistently higher than cert_gen because
ocsp_sign is a pure signing benchmark with no keygen cost to dilute the
ML-DSA-65 overhead.
**Verification (cert_verify, ocsp_verify)** shows a fundamentally different
picture — ML-DSA-65 and P-256 are within 1–2.6× across all batch sizes:
- This reflects a core ML-DSA property: signing requires expensive lattice
sampling (slow), while verification is a single NTT-based matrix-vector
check (fast). ECDSA has more symmetric sign/verify costs.
- At batch=64 cert_verify and batch=64 ocsp_verify, ML-DSA-65 is within 1.2×
of P-256 — Rayon parallelism amortises ML-DSA's slightly higher per-operation
verification cost effectively.
**DER payload size effect (db_insert_certs, db_read_certs):**
- Insert cost scales with DER size: ML-DSA-65 certs are ~6× larger (5.5 KB
vs ~900 B), and the insert ratio grows from 1.8× at batch=1 to ~8.8×
at batch=1024 as WAL write volume dominates.
- Read cost shows a similar but noisier pattern; at batch=16 the ML-DSA-65
reads are marginally faster than P-256 (1.0×) — SQLite page-cache effects.
**CRL signing** ratios narrow significantly at large batches: 27.2× at batch=1
(pure single signing operation) down to 2.3× at batch=1024. At large batches
the TBS DER encoding cost (proportional to the number of revoked serial entries)
grows, increasing the encoding fraction and reducing the signing-time fraction
for P-256 — as the CRL grows, both algorithms spend more time on DER encoding
than on the cryptographic signing step itself.