# X.509 PKI Pipeline (x509bench)
End-to-end X.509 PKI pipeline: CA self-signing, subscriber certificate
issuance (parallel), CRL construction and signing, OCSP response
construction and signing (parallel), database persistence, and signature
verification. A fresh ephemeral CA is generated for each batch size so
serial numbers always start at 1.
All results are from: Lenovo ThinkPad P1 Gen 5, 12th Gen i7-12800H, 64 GB RAM,
Linux 6.15.8-200.fc42.x86_64. Release build, mimalloc global allocator, Rayon
thread pool (16 logical cores). Run date: 2026-04-09.
## Running the Benchmark
```bash
# OpenSSL backend (default)
cargo build --release -p synta-bench --features bench-x509-sqlite --bin x509bench
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ecdsa-p256 --db x509bench-openssl-ecdsa-p256.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ed25519 --db x509bench-openssl-ed25519.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ml-dsa-44 --db x509bench-openssl-ml-dsa-44.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ml-dsa-65 --db x509bench-openssl-ml-dsa-65.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo rsa2048 --db x509bench-openssl-rsa2048.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo rsa3072 --db x509bench-openssl-rsa3072.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo rsa4096 --db x509bench-openssl-rsa4096.sqlite
# NSS backend (cert/CRL/OCSP signing and verification route through NSS)
cargo build --release -p synta-bench --features bench-x509-sqlite-nss --bin x509bench
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ecdsa-p256 --db x509bench-nss-ecdsa-p256.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ed25519 --db x509bench-nss-ed25519.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ml-dsa-44 --db x509bench-nss-ml-dsa-44.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo ml-dsa-65 --db x509bench-nss-ml-dsa-65.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo rsa2048 --db x509bench-nss-rsa2048.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo rsa3072 --db x509bench-nss-rsa3072.sqlite
./target/release/x509bench bench --sizes 1,2,4,8,16,32,64,128,256,512,1024 --ca-key-algo rsa4096 --db x509bench-nss-rsa4096.sqlite
```
Each table below shows both the OpenSSL and NSS backends side by side at
batch=64 and batch=1024. Database operations (insert/read) are unaffected by
backend choice and are shown for completeness. Throughput is items/second ÷ 1000.
## Results: ECDSA P-256
**Configuration:** ECDSA P-256 for both CA and subscriber keys.
| `ca_self_sign` | 0.06 ms | 17.9 | 0.08 ms | 12.3 | 0.19 ms | 5.2 | 0.26 ms | 3.9 | 1 cert/batch |
| `cert_gen` | 0.63 ms | 102.2 | 5.94 ms | 172.4 | 1.86 ms | 34.5 | 25.27 ms | 40.5 | Rayon parallel |
| `db_insert_certs` | 0.29 ms | 219.1 | 2.30 ms | 446.0 | 0.18 ms | 365.5 | 2.56 ms | 399.4 | SQLite WAL |
| `cert_verify` | 0.71 ms | 90.2 | 8.52 ms | 120.2 | 2.77 ms | 23.1 | 45.57 ms | 22.5 | Rayon parallel |
| `db_read_certs` | 0.07 ms | 915.6 | 0.76 ms | 1,344 | 0.07 ms | 937.0 | 0.79 ms | 1,299 | SQLite read |
| `crl_build` | 0.05 ms | 20.8 | 0.60 ms | 1.7 | 0.05 ms | 20.4 | 0.70 ms | 1.4 | 1 CRL covering N serials |
| `crl_sign` | 0.08 ms | 13.3 | 0.70 ms | 1.4 | 0.23 ms | 4.4 | 0.99 ms | 1.0 | 1 CRL/batch |
| `db_insert_crl` | 0.06 ms | 17.5 | 0.20 ms | 5.1 | 0.05 ms | 18.7 | 0.20 ms | 5.0 | SQLite WAL |
| `crl_verify` | 0.10 ms | 10.0 | 0.15 ms | 6.6 | 0.43 ms | 2.3 | 0.52 ms | 1.9 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 52.6 | 0.04 ms | 25.3 | 0.02 ms | 54.1 | 0.03 ms | 31.0 | SQLite read |
| `ocsp_build` | 0.18 ms | 349.7 | 0.61 ms | 1,668 | 0.10 ms | 628.3 | 0.61 ms | 1,685 | Rayon; TBS DER only |
| `ocsp_sign` | 0.27 ms | 240.2 | 3.48 ms | 294.7 | 1.61 ms | 39.6 | 21.77 ms | 47.0 | Rayon parallel |
| `db_insert_ocsp` | 0.29 ms | 222.3 | 2.10 ms | 488.5 | 0.18 ms | 360.5 | 2.46 ms | 416.0 | SQLite WAL |
| `ocsp_verify` | 0.81 ms | 78.6 | 8.91 ms | 115.0 | 3.35 ms | 19.1 | 46.65 ms | 22.0 | Rayon parallel |
| `db_read_ocsp` | 0.11 ms | 569.8 | 0.72 ms | 1,426 | 0.10 ms | 648.1 | 1.34 ms | 766.8 | SQLite read |
## Results: Ed25519
**Configuration:** Ed25519 for both CA and subscriber keys.
| `ca_self_sign` | 0.04 ms | 25.9 | 0.05 ms | 20.1 | 0.08 ms | 13.1 | 0.08 ms | 11.8 | 1 cert/batch |
| `cert_gen` | 0.79 ms | 81.0 | 10.74 ms | 95.3 | 1.88 ms | 34.0 | 15.98 ms | 64.1 | Rayon parallel |
| `db_insert_certs` | 0.17 ms | 382.3 | 2.16 ms | 473.2 | 0.56 ms | 113.7 | 2.33 ms | 440.4 | SQLite WAL |
| `cert_verify` | 0.62 ms | 102.5 | 9.47 ms | 108.1 | 1.90 ms | 33.7 | 8.36 ms | 122.4 | Rayon parallel |
| `db_read_certs` | 0.09 ms | 693.2 | 0.74 ms | 1,383 | 0.17 ms | 385.3 | 0.68 ms | 1,515 | SQLite read |
| `crl_build` | 0.06 ms | 15.8 | 0.81 ms | 1.2 | 0.10 ms | 9.9 | 0.64 ms | 1.6 | 1 CRL covering N serials |
| `crl_sign` | 0.11 ms | 8.7 | 0.77 ms | 1.3 | 0.27 ms | 3.7 | 0.85 ms | 1.2 | 1 CRL/batch |
| `db_insert_crl` | 0.09 ms | 11.8 | 0.19 ms | 5.3 | 0.10 ms | 10.2 | 0.16 ms | 6.2 | SQLite WAL |
| `crl_verify` | 0.14 ms | 7.2 | 0.18 ms | 5.6 | 0.06 ms | 16.6 | 0.15 ms | 6.8 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 41.1 | 0.04 ms | 24.1 | 0.02 ms | 60.2 | 0.03 ms | 29.1 | SQLite read |
| `ocsp_build` | 0.10 ms | 644.7 | 0.73 ms | 1,396 | 0.17 ms | 369.8 | 0.46 ms | 2,249 | Rayon; TBS DER only |
| `ocsp_sign` | 0.29 ms | 218.3 | 4.24 ms | 241.5 | 0.80 ms | 79.9 | 11.01 ms | 93.0 | Rayon parallel |
| `db_insert_ocsp` | 0.16 ms | 393.3 | 1.97 ms | 520.1 | 0.30 ms | 210.3 | 2.04 ms | 502.1 | SQLite WAL |
| `ocsp_verify` | 0.64 ms | 99.5 | 10.38 ms | 98.6 | 1.86 ms | 34.4 | 8.12 ms | 126.1 | Rayon parallel |
| `db_read_ocsp` | 0.07 ms | 944.8 | 0.75 ms | 1,373 | 0.27 ms | 234.3 | 0.73 ms | 1,407 | SQLite read |
## Results: ML-DSA-44
**Configuration:** ML-DSA-44 for both CA and subscriber keys.
ML-DSA-44 certificates are ~4,069 bytes DER; OCSP responses with ML-DSA-44 signatures
are roughly 2,700 bytes each.
| `ca_self_sign` | 0.49 ms | 2.0 | 0.28 ms | 3.5 | 0.27 ms | 3.7 | 0.41 ms | 2.4 | 1 cert/batch |
| `cert_gen` | 5.37 ms | 11.9 | 68.00 ms | 15.1 | 11.63 ms | 5.5 | 198.11 ms | 5.2 | Rayon parallel |
| `db_insert_certs` | 1.14 ms | 56.1 | 22.26 ms | 46.0 | 1.16 ms | 55.0 | 27.04 ms | 37.9 | SQLite WAL |
| `cert_verify` | 0.92 ms | 69.7 | 10.94 ms | 93.6 | 0.94 ms | 68.1 | 15.21 ms | 67.3 | Rayon parallel |
| `db_read_certs` | 0.13 ms | 477.9 | 2.14 ms | 479.4 | 0.28 ms | 229.7 | 2.11 ms | 484.8 | SQLite read |
| `crl_build` | 0.04 ms | 22.8 | 0.72 ms | 1.4 | 0.10 ms | 9.8 | 0.62 ms | 1.6 | 1 CRL covering N serials |
| `crl_sign` | 1.21 ms | 0.8 | 1.40 ms | 0.7 | 0.81 ms | 1.2 | 1.15 ms | 0.9 | 1 CRL/batch |
| `db_insert_crl` | 0.07 ms | 14.9 | 1.48 ms | 0.7 | 0.09 ms | 11.7 | 1.09 ms | 0.9 | SQLite WAL |
| `crl_verify` | 0.11 ms | 9.5 | 0.22 ms | 4.5 | 0.13 ms | 7.9 | 0.30 ms | 3.3 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 50.7 | 0.04 ms | 24.9 | 0.02 ms | 52.8 | 0.04 ms | 26.4 | SQLite read |
| `ocsp_build` | 0.15 ms | 438.8 | 0.55 ms | 1,870 | 0.07 ms | 909.9 | 0.44 ms | 2,310 | Rayon; TBS DER only |
| `ocsp_sign` | 4.70 ms | 13.6 | 49.58 ms | 20.7 | 11.34 ms | 5.6 | 169.52 ms | 6.0 | Rayon parallel |
| `db_insert_ocsp` | 0.65 ms | 98.0 | 18.90 ms | 54.2 | 0.59 ms | 108.3 | 13.89 ms | 73.7 | SQLite WAL |
| `ocsp_verify` | 0.94 ms | 67.9 | 11.95 ms | 85.7 | 0.86 ms | 74.3 | 12.47 ms | 82.1 | Rayon parallel |
| `db_read_ocsp` | 0.12 ms | 554.6 | 1.15 ms | 886.7 | 0.11 ms | 593.5 | 1.16 ms | 880.3 | SQLite read |
## Results: ML-DSA-65
**Configuration:** ML-DSA-65 for both CA and subscriber keys.
ML-DSA-65 certificates are ~5,521 bytes DER; OCSP responses with ML-DSA-65 signatures
are roughly 3,700 bytes each, compared to ~400 bytes for ECDSA P-256.
| `ca_self_sign` | 0.31 ms | 3.2 | 0.32 ms | 3.2 | 0.71 ms | 1.4 | 0.85 ms | 1.2 | 1 cert/batch |
| `cert_gen` | 10.89 ms | 5.9 | 102.26 ms | 10.0 | 20.64 ms | 3.1 | 313.03 ms | 3.3 | Rayon parallel |
| `db_insert_certs` | 1.27 ms | 50.3 | 36.88 ms | 27.8 | 1.60 ms | 40.0 | 32.75 ms | 31.3 | SQLite WAL |
| `cert_verify` | 1.38 ms | 46.3 | 15.53 ms | 65.9 | 2.85 ms | 22.4 | 24.11 ms | 42.5 | Rayon parallel |
| `db_read_certs` | 0.20 ms | 328.1 | 2.44 ms | 418.9 | 0.57 ms | 113.2 | 2.79 ms | 366.8 | SQLite read |
| `crl_build` | 0.05 ms | 19.9 | 0.70 ms | 1.4 | 0.06 ms | 17.9 | 0.72 ms | 1.4 | 1 CRL covering N serials |
| `crl_sign` | 2.03 ms | 0.5 | 1.23 ms | 0.8 | 1.45 ms | 0.7 | 1.39 ms | 0.7 | 1 CRL/batch |
| `db_insert_crl` | 0.07 ms | 14.5 | 1.10 ms | 0.9 | 0.09 ms | 11.7 | 1.54 ms | 0.7 | SQLite WAL |
| `crl_verify` | 0.18 ms | 5.6 | 0.30 ms | 3.3 | 0.29 ms | 3.4 | 0.59 ms | 1.7 | 1 CRL/batch |
| `db_read_crl` | 0.03 ms | 38.1 | 0.05 ms | 22.0 | 0.04 ms | 25.9 | 0.06 ms | 17.0 | SQLite read |
| `ocsp_build` | 0.34 ms | 185.8 | 0.57 ms | 1,785 | 0.25 ms | 261.1 | 0.95 ms | 1,074 | Rayon; TBS DER only |
| `ocsp_sign` | 7.61 ms | 8.4 | 86.98 ms | 11.8 | 20.30 ms | 3.2 | 299.44 ms | 3.4 | Rayon parallel |
| `db_insert_ocsp` | 0.70 ms | 92.0 | 22.52 ms | 45.5 | 0.61 ms | 104.2 | 15.71 ms | 65.2 | SQLite WAL |
| `ocsp_verify` | 1.62 ms | 39.5 | 16.92 ms | 60.5 | 1.55 ms | 41.3 | 28.89 ms | 35.4 | Rayon parallel |
| `db_read_ocsp` | 0.16 ms | 402.9 | 1.24 ms | 828.1 | 0.16 ms | 407.4 | 1.19 ms | 861.3 | SQLite read |
## Results: RSA-2048
**Configuration:** RSA-2048 for both CA and subscriber keys.
`cert_gen` includes RSA key pair generation for each subscriber certificate
(~200–400 ms/key pair single-threaded), which dominates the batch time.
| `ca_self_sign` | 0.77 ms | 1.3 | 0.86 ms | 1.2 | 4.55 ms | 0.22 | 4.32 ms | 0.23 | 1 cert/batch |
| `cert_gen` | 397.48 ms | 0.16 | 5,615 ms | 0.18 | 588.26 ms | 0.11 | 8,427 ms | 0.12 | Rayon; incl. key gen |
| `db_insert_certs` | 0.28 ms | 228 | 33.83 ms | 30 | 0.59 ms | 109 | 15.73 ms | 65 | SQLite WAL |
| `cert_verify` | 0.79 ms | 81 | 4.51 ms | 227 | 1.53 ms | 42 | 11.75 ms | 87 | Rayon parallel |
| `db_read_certs` | 0.08 ms | 775 | 1.07 ms | 961 | 0.12 ms | 521 | 1.68 ms | 608 | SQLite read |
| `crl_build` | 0.05 ms | 21 | 0.68 ms | 1.5 | 0.07 ms | 13 | 1.11 ms | 0.90 | 1 CRL covering N serials |
| `crl_sign` | 0.54 ms | 1.9 | 1.26 ms | 0.79 | 2.65 ms | 0.38 | 4.02 ms | 0.25 | 1 CRL/batch |
| `db_insert_crl` | 0.06 ms | 18 | 1.01 ms | 0.99 | 0.08 ms | 12 | 1.18 ms | 0.85 | SQLite WAL |
| `crl_verify` | 0.05 ms | 20 | 0.11 ms | 9.0 | 0.08 ms | 12 | 0.12 ms | 8.5 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 57 | 0.04 ms | 22 | 0.03 ms | 35 | 0.05 ms | 20 | SQLite read |
| `ocsp_build` | 0.08 ms | 777 | 0.64 ms | 1,610 | 0.29 ms | 218 | 0.82 ms | 1,253 | Rayon; TBS DER only |
| `ocsp_sign` | 5.53 ms | 12 | 88.26 ms | 12 | 54.42 ms | 1.2 | 749.29 ms | 1.4 | Rayon parallel |
| `db_insert_ocsp` | 0.20 ms | 318 | 2.59 ms | 396 | 0.33 ms | 194 | 2.63 ms | 389 | SQLite WAL |
| `ocsp_verify` | 0.36 ms | 176 | 4.67 ms | 219 | 1.25 ms | 51 | 13.06 ms | 78 | Rayon parallel |
| `db_read_ocsp` | 0.07 ms | 871 | 1.05 ms | 975 | 0.17 ms | 371 | 1.73 ms | 593 | SQLite read |
## Results: RSA-3072
**Configuration:** RSA-3072 for both CA and subscriber keys.
RSA-3072 key generation takes roughly 4× longer per key than RSA-2048.
| `ca_self_sign` | 7.77 ms | 0.13 | 2.62 ms | 0.38 | 9.28 ms | 0.11 | 10.02 ms | 0.100 | 1 cert/batch |
| `cert_gen` | 1,657 ms | 0.039 | 37,919 ms | 0.027 | 2,022 ms | 0.032 | 30,444 ms | 0.034 | Rayon; incl. key gen |
| `db_insert_certs` | 0.64 ms | 100 | 25.41 ms | 40 | 0.50 ms | 127 | 5.40 ms | 189 | SQLite WAL |
| `cert_verify` | 1.00 ms | 64 | 8.06 ms | 127 | 1.23 ms | 52 | 8.37 ms | 122 | Rayon parallel |
| `db_read_certs` | 0.11 ms | 560 | 1.08 ms | 945 | 0.18 ms | 360 | 0.94 ms | 1,088 | SQLite read |
| `crl_build` | 0.05 ms | 20 | 0.90 ms | 1.1 | 0.09 ms | 12 | 0.66 ms | 1.5 | 1 CRL covering N serials |
| `crl_sign` | 1.84 ms | 0.54 | 3.14 ms | 0.32 | 9.20 ms | 0.11 | 5.55 ms | 0.18 | 1 CRL/batch |
| `db_insert_crl` | 0.09 ms | 11 | 0.50 ms | 2.0 | 0.10 ms | 9.8 | 0.14 ms | 7.2 | SQLite WAL |
| `crl_verify` | 0.11 ms | 9.3 | 0.15 ms | 6.7 | 0.14 ms | 7.2 | 0.11 ms | 9.3 | 1 CRL/batch |
| `db_read_crl` | 0.03 ms | 29 | 0.05 ms | 19 | 0.04 ms | 26 | 0.03 ms | 32 | SQLite read |
| `ocsp_build` | 0.33 ms | 193 | 0.95 ms | 1,073 | 0.39 ms | 165 | 0.66 ms | 1,553 | Rayon; TBS DER only |
| `ocsp_sign` | 20.90 ms | 3.1 | 315.05 ms | 3.3 | 95.27 ms | 0.67 | 1,074 ms | 0.95 | Rayon parallel |
| `db_insert_ocsp` | 0.48 ms | 132 | 5.90 ms | 174 | 0.35 ms | 185 | 2.97 ms | 344 | SQLite WAL |
| `ocsp_verify` | 1.17 ms | 55 | 9.46 ms | 108 | 1.42 ms | 45 | 8.93 ms | 115 | Rayon parallel |
| `db_read_ocsp` | 0.19 ms | 340 | 1.08 ms | 946 | 0.13 ms | 496 | 0.83 ms | 1,237 | SQLite read |
## Results: RSA-4096
**Configuration:** RSA-4096 for both CA and subscriber keys.
RSA-4096 key generation dominates: ~1–2 s/key pair single-threaded.
| `ca_self_sign` | 5.36 ms | 0.19 | 4.51 ms | 0.22 | 13.71 ms | 0.073 | 15.20 ms | 0.066 | 1 cert/batch |
| `cert_gen` | 4,251 ms | 0.015 | 74,762 ms | 0.014 | 4,032 ms | 0.016 | 69,943 ms | 0.015 | Rayon; incl. key gen |
| `db_insert_certs` | 0.81 ms | 79 | 7.76 ms | 132 | 0.40 ms | 160 | 5.54 ms | 185 | SQLite WAL |
| `cert_verify` | 10.78 ms | 5.9 | 12.49 ms | 82 | 1.18 ms | 54 | 12.34 ms | 83 | Rayon parallel |
| `db_read_certs` | 0.23 ms | 278 | 1.37 ms | 749 | 0.11 ms | 601 | 1.05 ms | 978 | SQLite read |
| `crl_build` | 0.07 ms | 14 | 0.84 ms | 1.2 | 0.05 ms | 21 | 0.70 ms | 1.4 | 1 CRL covering N serials |
| `crl_sign` | 6.82 ms | 0.15 | 5.87 ms | 0.17 | 8.10 ms | 0.12 | 10.25 ms | 0.098 | 1 CRL/batch |
| `db_insert_crl` | 0.18 ms | 5.6 | 0.18 ms | 5.7 | 0.06 ms | 16 | 0.13 ms | 7.5 | SQLite WAL |
| `crl_verify` | 0.19 ms | 5.2 | 0.18 ms | 5.5 | 0.13 ms | 7.5 | 0.17 ms | 5.8 | 1 CRL/batch |
| `db_read_crl` | 0.06 ms | 16 | 0.05 ms | 21 | 0.02 ms | 46 | 0.03 ms | 35 | SQLite read |
| `ocsp_build` | 6.59 ms | 9.7 | 0.92 ms | 1,119 | 0.29 ms | 223 | 0.60 ms | 1,709 | Rayon; TBS DER only |
| `ocsp_sign` | 78.67 ms | 0.81 | 844.06 ms | 1.2 | 111.96 ms | 0.57 | 2,253 ms | 0.45 | Rayon parallel |
| `db_insert_ocsp` | 0.61 ms | 104 | 5.37 ms | 191 | 0.27 ms | 240 | 3.74 ms | 274 | SQLite WAL |
| `ocsp_verify` | 3.58 ms | 18 | 18.14 ms | 56 | 1.20 ms | 53 | 16.42 ms | 62 | Rayon parallel |
| `db_read_ocsp` | 0.30 ms | 212 | 1.33 ms | 768 | 0.11 ms | 565 | 1.08 ms | 947 | SQLite read |
## Backend Comparison: OpenSSL vs NSS (batch=1024)
Signing and verification operations only. Database operations are identical
across backends. Ratio > 1 means NSS is slower; ratio < 1 means NSS is faster.
| ECDSA P-256 | `cert_gen` (sign) | 5.94 ms | 25.27 ms | **4.3× slower** |
| ECDSA P-256 | `cert_verify` | 8.52 ms | 45.57 ms | **5.3× slower** |
| ECDSA P-256 | `crl_sign` | 0.70 ms | 0.99 ms | **1.4× slower** |
| ECDSA P-256 | `crl_verify` | 0.15 ms | 0.52 ms | **3.5× slower** |
| ECDSA P-256 | `ocsp_sign` | 3.48 ms | 21.77 ms | **6.3× slower** |
| ECDSA P-256 | `ocsp_verify` | 8.91 ms | 46.65 ms | **5.2× slower** |
| Ed25519 | `cert_gen` (sign) | 10.74 ms | 15.98 ms | **1.5× slower** |
| Ed25519 | `cert_verify` | 9.47 ms | 8.36 ms | **0.88× (NSS faster)** |
| Ed25519 | `crl_sign` | 0.77 ms | 0.85 ms | 1.1× |
| Ed25519 | `crl_verify` | 0.18 ms | 0.15 ms | 0.83× (NSS faster) |
| Ed25519 | `ocsp_sign` | 4.24 ms | 11.01 ms | **2.6× slower** |
| Ed25519 | `ocsp_verify` | 10.38 ms | 8.12 ms | **0.78× (NSS faster)** |
| ML-DSA-44 | `cert_gen` (sign) | 68.00 ms | 198.11 ms | **2.9× slower** |
| ML-DSA-44 | `cert_verify` | 10.94 ms | 15.21 ms | **1.4× slower** |
| ML-DSA-44 | `ocsp_sign` | 49.58 ms | 169.52 ms | **3.4× slower** |
| ML-DSA-44 | `ocsp_verify` | 11.95 ms | 12.47 ms | 1.0× (equal) |
| ML-DSA-65 | `cert_gen` (sign) | 102.26 ms | 313.03 ms | **3.1× slower** |
| ML-DSA-65 | `cert_verify` | 15.53 ms | 24.11 ms | **1.6× slower** |
| ML-DSA-65 | `ocsp_sign` | 86.98 ms | 299.44 ms | **3.4× slower** |
| ML-DSA-65 | `ocsp_verify` | 16.92 ms | 28.89 ms | **1.7× slower** |
| RSA-2048 | `cert_gen` (incl. keygen) | 5,615 ms | 8,427 ms | **1.5× slower** |
| RSA-2048 | `cert_verify` | 4.51 ms | 11.75 ms | **2.6× slower** |
| RSA-2048 | `crl_sign` | 1.26 ms | 4.02 ms | **3.2× slower** |
| RSA-2048 | `crl_verify` | 0.11 ms | 0.12 ms | ≈ equal |
| RSA-2048 | `ocsp_sign` | 88.26 ms | 749.29 ms | **8.5× slower** |
| RSA-2048 | `ocsp_verify` | 4.67 ms | 13.06 ms | **2.8× slower** |
| RSA-3072 | `cert_gen` (incl. keygen) | 37,919 ms | 30,444 ms | **0.8× (NSS faster)** |
| RSA-3072 | `cert_verify` | 8.06 ms | 8.37 ms | ≈ equal |
| RSA-3072 | `crl_sign` | 3.14 ms | 5.55 ms | **1.8× slower** |
| RSA-3072 | `crl_verify` | 0.15 ms | 0.11 ms | **0.7× (NSS faster)** |
| RSA-3072 | `ocsp_sign` | 315.05 ms | 1,074 ms | **3.4× slower** |
| RSA-3072 | `ocsp_verify` | 9.46 ms | 8.93 ms | ≈ equal |
| RSA-4096 | `cert_gen` (incl. keygen) | 74,762 ms | 69,943 ms | ≈ equal |
| RSA-4096 | `cert_verify` | 12.49 ms | 12.34 ms | ≈ equal |
| RSA-4096 | `crl_sign` | 5.87 ms | 10.25 ms | **1.7× slower** |
| RSA-4096 | `crl_verify` | 0.18 ms | 0.17 ms | ≈ equal |
| RSA-4096 | `ocsp_sign` | 844.06 ms | 2,253 ms | **2.7× slower** |
| RSA-4096 | `ocsp_verify` | 18.14 ms | 16.42 ms | **0.9× (NSS faster)** |
## Algorithm Comparison: OpenSSL Backend (batch=1024)
`cert_gen` for RSA keys includes subscriber key pair generation and dominates;
all other algorithms generate keys at CA setup time only.
| `ca_self_sign` | 0.08 ms | 0.05 ms | 0.86 ms | 4.51 ms | 0.28 ms | 0.32 ms |
| `cert_gen` | 5.94 ms | 10.74 ms | 5,615 ms† | 74,762 ms† | 68.00 ms | 102.26 ms |
| `cert_verify` | 8.52 ms | 9.47 ms | 4.51 ms | 12.49 ms | 10.94 ms | 15.53 ms |
| `crl_sign` | 0.70 ms | 0.77 ms | 1.26 ms | 5.87 ms | 1.40 ms | 1.23 ms |
| `crl_verify` | 0.15 ms | 0.18 ms | 0.11 ms | 0.18 ms | 0.22 ms | 0.30 ms |
| `ocsp_sign` | 3.48 ms | 4.24 ms | 88.26 ms | 844.06 ms | 49.58 ms | 86.98 ms |
| `ocsp_verify` | 8.91 ms | 10.38 ms | 4.67 ms | 18.14 ms | 11.95 ms | 16.92 ms |
| `db_insert_certs` | 2.30 ms | 2.16 ms | 33.83 ms | 7.76 ms | 22.26 ms | 36.88 ms |
| `db_insert_ocsp` | 2.10 ms | 1.97 ms | 2.59 ms | 5.37 ms | 18.90 ms | 22.52 ms |
| `ocsp_build` | 0.61 ms | 0.73 ms | 0.64 ms | 0.92 ms | 0.55 ms | 0.57 ms |
† RSA `cert_gen` includes RSA key pair generation per subscriber certificate.
## Analysis
### Backend: OpenSSL vs NSS
**NSS signing overhead** is significant across all algorithms. The NSS backend routes
every signing operation through the PKCS#11 interface via `SEC_SignData` (RSA, ECDSA,
ML-DSA) or `PK11_Sign` (Ed25519), which includes per-operation token lookup and
mechanism dispatch. For ECDSA P-256, this adds roughly 4–6× overhead over OpenSSL's
direct `EVP_DigestSign` path. For ML-DSA-44/65, the overhead is 2.9–3.4× — smaller
in relative terms because ML-DSA signing itself is expensive, but still significant in
absolute time.
**Ed25519 verification is the exception**: `cert_verify`, `crl_verify`, and `ocsp_verify`
are all *faster* under NSS (8.36 ms vs 9.47 ms at batch=1024 for cert_verify). NSS
verifies Ed25519 via `PK11_Verify`, which dispatches directly to the softokn
`CKM_EDDSA` mechanism without the `VFY_VerifyDataWithAlgorithmID` OID-lookup path
that fails for Ed25519 due to a duplicate OID registration in NSS's internal table.
The `PK11_Verify` path is slightly more efficient than OpenSSL's multi-step
`EVP_DigestVerify` for Ed25519.
**ECDSA P-256 and ML-DSA verification** is slower under NSS. `VFY_VerifyDataWithAlgorithmID`
routes through the PKCS#11 `CKF_VERIFY` path, adding per-verification overhead compared
to OpenSSL's direct EVP layer.
**The x509bench signing overhead** for NSS reflects per-certificate signer
initialization: each `cert_gen` task imports the private key via
`PK11_ImportDERPrivateKeyInfoAndReturnKey` before signing. Reusing a single
`NssSigner` across multiple certificates in the same batch would eliminate this
overhead. The signing operations themselves (ECDSA P-256 at ~4 µs/sign,
ML-DSA-65 at ~90 µs/sign) are comparable between backends; the extra latency
is PKCS#11 setup cost, not cryptographic computation.
### OpenSSL Backend: ECDSA P-256
`cert_gen` and `ocsp_sign` use Rayon parallel iteration across all logical
cores. Throughput rises from 102.2 K/s to 172.4 K/s between batch=64 and
batch=1024 as the thread pool becomes fully saturated. `cert_verify` shows
the same pattern (90.2 → 120.2 K/s).
`crl_build` and `crl_sign` always cover exactly one CRL per batch, regardless
of batch size. The CRL TBS DER grows proportionally with the number of revoked
serial entries, so throughput falls from 13.3 K/s (batch=64) to 1.4 K/s
(batch=1024) — all growth is in DER encoding plus P-256 signing of the larger
TBS blob.
`ocsp_build` (TBS DER construction only) is roughly 2× faster than `ocsp_sign`
at batch=1024 (294.7 K/s vs 1,668 K/s for build; the build throughput is higher
because it is pure ASN.1 encoding with no crypto). `ocsp_verify` at batch=1024
reaches 115 K/s as full Rayon parallelism is achieved.
SQLite inserts use a single `prepare()` before the transaction loop so the
SQL parse cost is paid once per batch rather than once per row.
### OpenSSL Backend: Ed25519
Ed25519 signing is comparable to ECDSA P-256 in throughput (95.3 vs 172.4 K/s
at batch=1024 for cert_gen) but uses a single-shot `EVP_DigestSign` without
pre-hashing, which limits parallelism benefits for small batches. Verification
throughput (108.1 K/s) is similar to ECDSA P-256 (120.2 K/s).
### OpenSSL Backend: ML-DSA-44 and ML-DSA-65
**Signing is the dominant cost.** `cert_gen` at batch=1024 is 11.5× slower for
ML-DSA-44 (68 ms vs 5.94 ms for ECDSA P-256) and 17.2× slower for ML-DSA-65
(102 ms). ML-DSA signing involves large polynomial matrix operations that stress
the L2/L3 cache, limiting effective Rayon parallelism across 16 cores.
**Verification is competitive.** `cert_verify` is only 1.3–1.8× slower than ECDSA
P-256 at batch=1024. ML-DSA verification avoids the expensive randomized nonce
generation required by signing.
**`ocsp_sign` is the most extreme outlier**: 49.58 ms (ML-DSA-44) and 86.98 ms
(ML-DSA-65) at batch=1024, vs 3.48 ms for ECDSA P-256. Each OCSP response
requires one ML-DSA signing operation, and 1024 parallel signs saturate cache
heavily.
**Database throughput is I/O-bound.** ML-DSA-44 certificates are ~4 KB and
ML-DSA-65 certificates are ~5.5 KB each, vs ~700 bytes for ECDSA P-256.
SQLite WAL write time scales roughly with byte volume.
`ocsp_build` (pure DER encoding, no crypto) is similarly fast for all algorithms
(0.55–0.73 ms at batch=1024) because synta's encoder splices the ML-DSA signature
BIT STRING as a zero-copy `BitStringRef` slice.
### OpenSSL Backend: RSA-2048, RSA-3072, RSA-4096
**RSA `cert_gen` is dominated by key pair generation**, not by signing. Each
subscriber certificate requires a fresh RSA key pair: ~200–400 ms for RSA-2048,
~1–2 s for RSA-3072, and ~2–4 s for RSA-4096, single-threaded. Rayon parallelizes
across 16 cores, but the absolute batch times remain extreme (5.6 s, 37.9 s, and
74.8 s at batch=1024 for RSA-2048/3072/4096 respectively under OpenSSL). These
numbers are not comparable to other algorithms for signing performance — they
measure key generation speed.
**RSA verification is fast** due to the small public exponent (e=65537). `cert_verify`
at batch=1024 is 4.51 ms for RSA-2048 and 12.49 ms for RSA-4096 — faster than
ECDSA P-256 (8.52 ms) and competitive with Ed25519 (9.47 ms). The single modular
exponentiation with e=65537 (17 squarings) is much cheaper than the ECDSA scalar
point multiplication.
**RSA `ocsp_sign` is the most expensive non-keygen operation.** Each OCSP response
requires one RSA private-key operation (full modular exponentiation with the private
exponent d). At batch=1024, `ocsp_sign` takes 88 ms (RSA-2048), 315 ms (RSA-3072),
and 844 ms (RSA-4096). The cost grows roughly as O(key_bits²·³) — consistent with
the ~9.6× increase from RSA-2048 to RSA-4096.
**NSS overhead is highly asymmetric for RSA.** For RSA-2048, `ocsp_sign` is 8.5×
slower under NSS (749 ms vs 88 ms). The private-key operation itself takes only
~80 µs at 2048 bits, so the PKCS#11 per-call setup cost — token lookup, mechanism
dispatch, `C_Sign` call — represents a large fraction of the total. At RSA-4096,
where the private-key operation takes ~800 µs, the same PKCS#11 overhead is
proportionally smaller, reducing the ratio to 2.7× (2,253 ms vs 844 ms).
**NSS `cert_gen` (including key generation) is unexpectedly comparable or faster**
for RSA-3072 and RSA-4096. RSA key generation uses OpenSSL directly (not routed
through `NssSigner`), so the backend choice does not affect key generation time.
The minor variance (RSA-3072: 30 s NSS vs 38 s OpenSSL) reflects Rayon scheduling
randomness across long-running tasks — not a real backend difference.
**`crl_verify` and `ocsp_verify` are comparable or NSS-faster** for RSA-3072/4096
because RSA public verification (small-exponent) takes ~100–700 µs per cert, making
the PKCS#11 overhead negligible relative to the computation.
**Database performance scales with DER blob size.** RSA-2048 certificates are ~800
bytes (smaller than ML-DSA-44), so `db_insert_certs` is fast (33 ms at batch=1024
despite more certs than ML-DSA batches reach). RSA-4096 certificates are ~1.7 KB,
doubling that cost. OCSP response DER is smaller for RSA (~300 bytes) than for
ML-DSA-65 (~3.7 KB), so `db_insert_ocsp` is comparable to ECDSA P-256 for RSA.