synta 0.1.8

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# 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).

ECDSA P-256, Ed25519, ML-DSA-44, and ML-DSA-65 were re-run on 2026-04-16
after the migration from the rust-openssl fork to native-ossl 0.1.1; NSS
figures and RSA figures are from 2026-04-09 (the native-ossl migration affects
only the OpenSSL backend; NSS is unchanged, and RSA key generation dominates
those benchmarks so wrapper differences are negligible).

## 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.

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `ca_self_sign` | 0.03 ms | 29.5 | 0.04 ms | 28.5 | 0.19 ms | 5.2 | 0.26 ms | 3.9 | 1 cert/batch |
| `cert_gen` | 0.79 ms | 81.1 | 10.31 ms | 99.3 | 1.86 ms | 34.5 | 25.27 ms | 40.5 | Rayon parallel |
| `db_insert_certs` | 0.12 ms | 528.7 | 1.62 ms | 633.4 | 0.18 ms | 365.5 | 2.56 ms | 399.4 | SQLite WAL |
| `cert_verify` | 1.56 ms | 41.1 | 8.28 ms | 123.6 | 2.77 ms | 23.1 | 45.57 ms | 22.5 | Rayon parallel |
| `db_read_certs` | 0.22 ms | 287.1 | 0.64 ms | 1,597 | 0.07 ms | 937.0 | 0.79 ms | 1,299 | SQLite read |
| `crl_build` | 0.15 ms | 6.7 | 0.60 ms | 1.7 | 0.05 ms | 20.4 | 0.70 ms | 1.4 | 1 CRL covering N serials |
| `crl_sign` | 0.24 ms | 4.1 | 0.70 ms | 1.4 | 0.23 ms | 4.4 | 0.99 ms | 1.0 | 1 CRL/batch |
| `db_insert_crl` | 0.11 ms | 8.9 | 0.13 ms | 7.6 | 0.05 ms | 18.7 | 0.20 ms | 5.0 | SQLite WAL |
| `crl_verify` | 0.31 ms | 3.3 | 0.15 ms | 6.6 | 0.43 ms | 2.3 | 0.52 ms | 1.9 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 58.2 | 0.03 ms | 31.7 | 0.02 ms | 54.1 | 0.03 ms | 31.0 | SQLite read |
| `ocsp_build` | 0.15 ms | 421.5 | 0.68 ms | 1,518 | 0.10 ms | 628.3 | 0.61 ms | 1,685 | Rayon; TBS DER only |
| `ocsp_sign` | 0.33 ms | 196.5 | 2.65 ms | 386.5 | 1.61 ms | 39.6 | 21.77 ms | 47.0 | Rayon parallel |
| `db_insert_ocsp` | 0.39 ms | 163.3 | 1.59 ms | 643.9 | 0.18 ms | 360.5 | 2.46 ms | 416.0 | SQLite WAL |
| `ocsp_verify` | 2.20 ms | 29.2 | 7.26 ms | 141.1 | 3.35 ms | 19.1 | 46.65 ms | 22.0 | Rayon parallel |
| `db_read_ocsp` | 0.24 ms | 263.8 | 0.65 ms | 1,573 | 0.10 ms | 648.1 | 1.34 ms | 766.8 | SQLite read |

## Results: Ed25519

**Configuration:** Ed25519 for both CA and subscriber keys.

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `ca_self_sign` | 0.07 ms | 14.0 | 0.04 ms | 25.5 | 0.08 ms | 13.1 | 0.08 ms | 11.8 | 1 cert/batch |
| `cert_gen` | 1.16 ms | 55.1 | 9.58 ms | 106.9 | 1.88 ms | 34.0 | 15.98 ms | 64.1 | Rayon parallel |
| `db_insert_certs` | 0.42 ms | 153.6 | 1.88 ms | 543.7 | 0.56 ms | 113.7 | 2.33 ms | 440.4 | SQLite WAL |
| `cert_verify` | 0.94 ms | 68.4 | 8.81 ms | 116.2 | 1.90 ms | 33.7 | 8.36 ms | 122.4 | Rayon parallel |
| `db_read_certs` | 0.14 ms | 475.0 | 0.68 ms | 1,497 | 0.17 ms | 385.3 | 0.68 ms | 1,515 | SQLite read |
| `crl_build` | 0.08 ms | 12.0 | 0.63 ms | 1.6 | 0.10 ms | 9.9 | 0.64 ms | 1.6 | 1 CRL covering N serials |
| `crl_sign` | 0.16 ms | 6.2 | 0.75 ms | 1.3 | 0.27 ms | 3.7 | 0.85 ms | 1.2 | 1 CRL/batch |
| `db_insert_crl` | 0.07 ms | 13.9 | 0.11 ms | 8.9 | 0.10 ms | 10.2 | 0.16 ms | 6.2 | SQLite WAL |
| `crl_verify` | 0.21 ms | 4.7 | 0.17 ms | 5.8 | 0.06 ms | 16.6 | 0.15 ms | 6.8 | 1 CRL/batch |
| `db_read_crl` | 0.03 ms | 30.5 | 0.03 ms | 36.8 | 0.02 ms | 60.2 | 0.03 ms | 29.1 | SQLite read |
| `ocsp_build` | 0.23 ms | 283.2 | 0.70 ms | 1,461 | 0.17 ms | 369.8 | 0.46 ms | 2,249 | Rayon; TBS DER only |
| `ocsp_sign` | 0.42 ms | 151.3 | 3.71 ms | 276.2 | 0.80 ms | 79.9 | 11.01 ms | 93.0 | Rayon parallel |
| `db_insert_ocsp` | 0.20 ms | 313.1 | 1.65 ms | 622.2 | 0.30 ms | 210.3 | 2.04 ms | 502.1 | SQLite WAL |
| `ocsp_verify` | 1.31 ms | 48.9 | 8.53 ms | 120.0 | 1.86 ms | 34.4 | 8.12 ms | 126.1 | Rayon parallel |
| `db_read_ocsp` | 0.21 ms | 301.3 | 0.71 ms | 1,440 | 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.

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `ca_self_sign` | 0.25 ms | 4.1 | 0.37 ms | 2.7 | 0.27 ms | 3.7 | 0.41 ms | 2.4 | 1 cert/batch |
| `cert_gen` | 9.57 ms | 6.7 | 62.59 ms | 16.4 | 11.63 ms | 5.5 | 198.11 ms | 5.2 | Rayon parallel |
| `db_insert_certs` | 0.50 ms | 129.1 | 10.93 ms | 93.7 | 1.16 ms | 55.0 | 27.04 ms | 37.9 | SQLite WAL |
| `cert_verify` | 0.87 ms | 73.2 | 9.48 ms | 108.1 | 0.94 ms | 68.1 | 15.21 ms | 67.3 | Rayon parallel |
| `db_read_certs` | 0.13 ms | 490.4 | 1.97 ms | 520.3 | 0.28 ms | 229.7 | 2.11 ms | 484.8 | SQLite read |
| `crl_build` | 0.08 ms | 12.5 | 0.63 ms | 1.6 | 0.10 ms | 9.8 | 0.62 ms | 1.6 | 1 CRL covering N serials |
| `crl_sign` | 0.89 ms | 1.1 | 1.31 ms | 0.8 | 0.81 ms | 1.2 | 1.15 ms | 0.9 | 1 CRL/batch |
| `db_insert_crl` | 0.04 ms | 24.2 | 0.07 ms | 13.8 | 0.09 ms | 11.7 | 1.09 ms | 0.9 | SQLite WAL |
| `crl_verify` | 0.11 ms | 9.3 | 0.20 ms | 5.1 | 0.13 ms | 7.9 | 0.30 ms | 3.3 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 50.4 | 0.03 ms | 37.1 | 0.02 ms | 52.8 | 0.04 ms | 26.4 | SQLite read |
| `ocsp_build` | 0.17 ms | 379.4 | 0.60 ms | 1,708 | 0.07 ms | 909.9 | 0.44 ms | 2,310 | Rayon; TBS DER only |
| `ocsp_sign` | 4.26 ms | 15.0 | 48.81 ms | 21.0 | 11.34 ms | 5.6 | 169.52 ms | 6.0 | Rayon parallel |
| `db_insert_ocsp` | 0.74 ms | 86.2 | 12.42 ms | 82.5 | 0.59 ms | 108.3 | 13.89 ms | 73.7 | SQLite WAL |
| `ocsp_verify` | 0.78 ms | 82.1 | 9.76 ms | 105.0 | 0.86 ms | 74.3 | 12.47 ms | 82.1 | Rayon parallel |
| `db_read_ocsp` | 0.15 ms | 431.0 | 1.12 ms | 918.3 | 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.

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `ca_self_sign` | 1.21 ms | 0.8 | 1.87 ms | 0.5 | 0.71 ms | 1.4 | 0.85 ms | 1.2 | 1 cert/batch |
| `cert_gen` | 8.51 ms | 7.5 | 102.21 ms | 10.0 | 20.64 ms | 3.1 | 313.03 ms | 3.3 | Rayon parallel |
| `db_insert_certs` | 0.68 ms | 94.7 | 15.73 ms | 65.1 | 1.60 ms | 40.0 | 32.75 ms | 31.3 | SQLite WAL |
| `cert_verify` | 1.44 ms | 44.3 | 15.80 ms | 64.8 | 2.85 ms | 22.4 | 24.11 ms | 42.5 | Rayon parallel |
| `db_read_certs` | 0.17 ms | 370.7 | 2.81 ms | 364.3 | 0.57 ms | 113.2 | 2.79 ms | 366.8 | SQLite read |
| `crl_build` | 0.04 ms | 23.4 | 0.70 ms | 1.4 | 0.06 ms | 17.9 | 0.72 ms | 1.4 | 1 CRL covering N serials |
| `crl_sign` | 1.58 ms | 0.6 | 2.43 ms | 0.4 | 1.45 ms | 0.7 | 1.39 ms | 0.7 | 1 CRL/batch |
| `db_insert_crl` | 0.04 ms | 26.4 | 0.08 ms | 12.7 | 0.09 ms | 11.7 | 1.54 ms | 0.7 | SQLite WAL |
| `crl_verify` | 0.17 ms | 6.0 | 0.26 ms | 3.8 | 0.29 ms | 3.4 | 0.59 ms | 1.7 | 1 CRL/batch |
| `db_read_crl` | 0.02 ms | 42.5 | 0.03 ms | 30.3 | 0.04 ms | 25.9 | 0.06 ms | 17.0 | SQLite read |
| `ocsp_build` | 0.26 ms | 243.0 | 0.46 ms | 2,243 | 0.25 ms | 261.1 | 0.95 ms | 1,074 | Rayon; TBS DER only |
| `ocsp_sign` | 5.39 ms | 11.9 | 79.17 ms | 12.9 | 20.30 ms | 3.2 | 299.44 ms | 3.4 | Rayon parallel |
| `db_insert_ocsp` | 0.35 ms | 183.3 | 10.26 ms | 99.8 | 0.61 ms | 104.2 | 15.71 ms | 65.2 | SQLite WAL |
| `ocsp_verify` | 1.33 ms | 48.1 | 15.73 ms | 65.1 | 1.55 ms | 41.3 | 28.89 ms | 35.4 | Rayon parallel |
| `db_read_ocsp` | 0.14 ms | 466.6 | 1.22 ms | 838.6 | 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.
*Figures from 2026-04-09 (pre-native-ossl migration); RSA keygen dominates and
wrapper-level differences are negligible.*

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `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.
*Figures from 2026-04-09; see RSA-2048 note above.*

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `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.
*Figures from 2026-04-09; see RSA-2048 note above.*

| Operation | OpenSSL B64 | Tput K/s | OpenSSL B1024 | Tput K/s | NSS B64 | Tput K/s | NSS B1024 | Tput K/s | Notes |
|-----------|-------------|----------|---------------|----------|---------|----------|-----------|----------|-------|
| `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.

| Algorithm | Operation | OpenSSL | NSS | Ratio |
|-----------|-----------|---------|-----|-------|
| ECDSA P-256 | `cert_gen` (sign) | 10.31 ms | 25.27 ms | **2.5× slower** |
| ECDSA P-256 | `cert_verify` | 8.28 ms | 45.57 ms | **5.5× 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` | 2.65 ms | 21.77 ms | **8.2× slower** |
| ECDSA P-256 | `ocsp_verify` | 7.26 ms | 46.65 ms | **6.4× slower** |
| Ed25519 | `cert_gen` (sign) | 9.58 ms | 15.98 ms | **1.7× slower** |
| Ed25519 | `cert_verify` | 8.81 ms | 8.36 ms | **0.95× (NSS faster)** |
| Ed25519 | `crl_sign` | 0.75 ms | 0.85 ms | 1.1× |
| Ed25519 | `crl_verify` | 0.17 ms | 0.15 ms | 0.88× (NSS faster) |
| Ed25519 | `ocsp_sign` | 3.71 ms | 11.01 ms | **3.0× slower** |
| Ed25519 | `ocsp_verify` | 8.53 ms | 8.12 ms | **0.95× (NSS faster)** |
| ML-DSA-44 | `cert_gen` (sign) | 62.59 ms | 198.11 ms | **3.2× slower** |
| ML-DSA-44 | `cert_verify` | 9.48 ms | 15.21 ms | **1.6× slower** |
| ML-DSA-44 | `ocsp_sign` | 48.81 ms | 169.52 ms | **3.5× slower** |
| ML-DSA-44 | `ocsp_verify` | 9.76 ms | 12.47 ms | **1.3× slower** |
| ML-DSA-65 | `cert_gen` (sign) | 102.21 ms | 313.03 ms | **3.1× slower** |
| ML-DSA-65 | `cert_verify` | 15.80 ms | 24.11 ms | **1.5× slower** |
| ML-DSA-65 | `ocsp_sign` | 79.17 ms | 299.44 ms | **3.8× slower** |
| ML-DSA-65 | `ocsp_verify` | 15.73 ms | 28.89 ms | **1.8× 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.

| Operation | ECDSA P-256 | Ed25519 | RSA-2048 | RSA-4096 | ML-DSA-44 | ML-DSA-65 |
|-----------|-------------|---------|----------|----------|-----------|-----------|
| `ca_self_sign` | 0.04 ms | 0.04 ms | 0.86 ms | 4.51 ms | 0.37 ms | 1.87 ms |
| `cert_gen` | 10.31 ms | 9.58 ms | 5,615 ms† | 74,762 ms† | 62.59 ms | 102.21 ms |
| `cert_verify` | 8.28 ms | 8.81 ms | 4.51 ms | 12.49 ms | 9.48 ms | 15.80 ms |
| `crl_sign` | 0.70 ms | 0.75 ms | 1.26 ms | 5.87 ms | 1.31 ms | 2.43 ms |
| `crl_verify` | 0.15 ms | 0.17 ms | 0.11 ms | 0.18 ms | 0.20 ms | 0.26 ms |
| `ocsp_sign` | 2.65 ms | 3.71 ms | 88.26 ms | 844.06 ms | 48.81 ms | 79.17 ms |
| `ocsp_verify` | 7.26 ms | 8.53 ms | 4.67 ms | 18.14 ms | 9.76 ms | 15.73 ms |
| `db_insert_certs` | 1.62 ms | 1.88 ms | 33.83 ms | 7.76 ms | 10.93 ms | 15.73 ms |
| `db_insert_ocsp` | 1.59 ms | 1.65 ms | 2.59 ms | 5.37 ms | 12.42 ms | 10.26 ms |
| `ocsp_build` | 0.68 ms | 0.70 ms | 0.64 ms | 0.92 ms | 0.60 ms | 0.46 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 2.5–8× overhead over OpenSSL's
direct `EVP_DigestSign` path. For ML-DSA-44/65, the overhead is 3.1–3.8× — 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 8.81 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 81.1 K/s to 99.3 K/s between batch=64 and
batch=1024 as the thread pool becomes more fully saturated. `cert_verify`
shows a similar pattern (41.1 → 123.6 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 4.1 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) reaches 1,518 K/s at batch=1024 as
full Rayon parallelism is achieved. `ocsp_verify` at batch=1024 reaches
141.1 K/s.

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 (106.9 vs 99.3 K/s
at batch=1024 for cert_gen). Verification throughput (116.2 K/s) is similar
to ECDSA P-256 (123.6 K/s).

### OpenSSL Backend: ML-DSA-44 and ML-DSA-65

**Signing is the dominant cost.** `cert_gen` at batch=1024 is 6.1× slower for
ML-DSA-44 (62.6 ms vs 10.3 ms for ECDSA P-256) and 9.9× 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.1–1.9× 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**: 48.81 ms (ML-DSA-44) and 79.17 ms
(ML-DSA-65) at batch=1024, vs 2.65 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.46–0.68 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.28 ms) and competitive with Ed25519 (8.81 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.