Goal. Measure the time to generate 100,000 presignatures with DKLs23 and with CGGMP21, and how that time changes with machine size, concurrency and geography.
Setup. Three GCP n2d-standard VMs at 4, 16 and 32 vCPU, one per party, in a 2-of-3 configuration. Concurrency (c) is the number of presignatures being generated in parallel; each protocol was run at the concurrency that gave it the highest throughput (DKLs23: c=50 on 4 vCPU, c=200 on 16 and 32 vCPU; CGGMP21: c=50 on all sizes). Same harness and same load generator for both protocols. All figures on this page are same-region.
Result. On 32 vCPUs, DKLs23 built the 100,000-presignature pool in 2 minutes 22 seconds at 200 parallel sessions; CGGMP21 took 2 hours 36 minutes at 50 parallel sessions, which was its fastest setting — beyond that it only queues. Both completed all 100,000 with zero failures. DKLs23 is 66× faster, and the gap holds at every machine size tested.
Full data and method: repo (private — request access) · exhaustive DKG and DSG benchmarks.
Measured answer · 100,000 presignatures
100,000 presignatures: 2 min 22 s with DKLs23, 2 h 36 min with CGGMP21
Three n2d-standard-32 VMs, one per party, same harness and load generator for both protocols. DKLs23 ran at 200 parallel sessions; CGGMP21 at 50, its fastest setting. Both completed 100,000 of 100,000 presignatures with zero errors. The ratio is 66×. For reference, a single CGGMP21 session (c=1) would take about 73 hours.
Bandwidth never binds: one presign costs ≈120 KiB, so a 1 Gbit/s link carries ≈1019 presigns/s — far above what the CPUs deliver (≈706/s sustained in the 100k pool; 449/s peak in a 45 s sweep cell). For CGGMP21, c=50 is already the sweet spot: past ~c=400 latency only grows, and from ~c=800 requests start failing.
Time to build a 100k pool
One 100,000-presignature run per size, each protocol at its best setting
Every point is a complete 100,000-presignature run at that VM size. DKLs23: 2 min 22 s to 11 min 17 s. CGGMP21: 2 h 36 min to 20 h 46 min.
View values & sources
| vCPU | Protocol | Concurrency (c) | Wall-clock |
|---|---|---|---|
| 4 | DKLs23 | c=50 | 11m 17s |
| 4 | CGGMP21 | c=50 | 20h 46m 12s |
| 16 | DKLs23 | c=200 | 5m 16s |
| 16 | CGGMP21 | c=50 | 5h 11m 00s |
| 32 | DKLs23 | c=200 | 2m 22s |
| 32 | CGGMP21 | c=50 | 2h 36m 02s |
Sources: DKLs23 sustained 100k pools (2026-09-15, one per size, best setting); CGGMP21 measured 100k pools (c=50, one per VM size, 2026-09-12). X settings are equally spaced.
CGGMP21's build time is nearly independent of concurrency: its throughput reaches a plateau by c=50 and does not rise further, so running it at DKLs23's setting would only lengthen its queue. DKLs23's build time does depend on concurrency. Under either convention the ratio stays between roughly 55× and 110×.
| vCPU | DKLs23 | CGGMP21 | Ratio | Basis |
|---|---|---|---|---|
| 4 | 11m 17s | 20h 46m 12s | 110× | measured |
| 16 | 5m 35s | 5h 11m 00s | 56× | measured |
| 32 | 2m 49s | 2h 36m 02s | 55× | measured |
Sources: DKLs23 measured 100k pools at c=50 (results/harvest/presig-dkls100k-{4,16}-dkls-c50.csv, presig-dkls100000-32-dkls-c50.csv); CGGMP21 measured 100k pools at c=50 (results/harvest/bulk-cggmp-100k-{4,16,32}.json). Every run completed 100,000/100,000 with zero errors.
Why the gap is this large
A CGGMP21 presignature is built from Paillier encryption over 3072-bit numbers, plus the zero-knowledge proofs that go with it; across the three party VMs one presignature consumes about 5.9 core-seconds of CPU. A DKLs23 presignature is built from elliptic-curve operations and oblivious transfer, and consumes about 0.05 core-seconds on the same basis. The two also respond differently to load: DKLs23 keeps getting faster as you add sessions and cores, while CGGMP21 stops improving around 10 to 50 parallel sessions — past that it holds roughly two-thirds of the machine busy and every extra session only lengthens the queue. Measured end to end, the wall-clock gap is 66×. Bandwidth is not a factor either way — CGGMP21 actually sends about 28% fewer bytes per presignature, but at these rates neither protocol comes close to filling a 1 Gbit/s link.
Scenario explorer
Model your setup
An exact match to a measured pool is shown as measured; anything projected from a short sampling run is labelled estimated with its source.
RAM 64 GiB (4 GiB per vCPU)
DKLs23 vs CGGMP21 — benchmark comparison
Reference charts across the measured scenarios stay synchronized with the vCPU control. Each chart identifies its conditions and sources. Solid lines show measurements.
Throughput by VM size
Same region · sustained 100k pool · 100,000 ÷ wall-clock
Sustained pool throughput, not a short sweep cell: each point is a full 100,000-presignature run divided by its wall-clock. Labels give the DKLs23/CGGMP21 ratio at that size.
View values & sources
| vCPU | Protocol | Concurrency | Wall-clock | presigns/s | Errors |
|---|---|---|---|---|---|
| 4 | DKLs23 | c=50 | 11m 17s | 147.6 | 0 |
| 4 | CGGMP21 | c=50 | 20h 46m 12s | 1.3 | 0 |
| 16 | DKLs23 | c=200 | 5m 16s | 316.4 | 0 |
| 16 | CGGMP21 | c=50 | 5h 11m 00s | 5.4 | 0 |
| 32 | DKLs23 | c=200 | 2m 22s | 706.3 | 0 |
| 32 | CGGMP21 | c=50 | 2h 36m 02s | 10.7 | 0 |
Sources: DKLs23 results/harvest/presig-dkls100k-4-dkls-c50.csv, presig-dkls100k-16-dkls-c200.csv, presig-dkls100000-32-dkls-c200.csv; CGGMP21 results/harvest/bulk-cggmp-100k-4.json, bulk-cggmp-100k-16.json, bulk-cggmp-100k-32.json. Every run completed 100,000/100,000 with zero errors. X settings are equally spaced.
| vCPU | DKLs23 | CGGMP21 |
|---|---|---|
| 4 | 163.21 | 1.34 |
| 16 | 448.92 | 5.27 |
| 32 | 341.43 | 10.52 |
Source: 2026-09-15 single-boot sweeps (results/harvest/presig-highc2-*.csv); CGGMP21 at 4/16 vCPU uses the 2026-09-11 cells. Only zero-error cells. Each protocol may peak at a different concurrency. These 45-second cells chose the concurrency for the sustained pools above; they are not pool throughput.
Throughput vs concurrency (c)
Same region · 16 vCPU · measured requests per second
View values & sources
| concurrency (c) | DKLs23 | CGGMP21 |
|---|---|---|
| 1 | 16.32 | 0.38 |
| 1 | Not measured | 0.5 |
| 10 | 114.19 | 1.34 |
| 50 | 163.21 | 1.32 |
| 100 | 163.13 | 1.31 |
| 200 | 161.13 | 1.27 |
| 400 | 163.09 | 1.31 |
| 800 | Not measured | 1.3 |
Sources: 2026-09-15 single-boot sweeps (results/harvest/presig-highc2-{4,16,32}-{dkls,cggmp}-c*.csv); c=1 anchors as in the latency chart. Throughput is requests_per_sec. X settings are equally spaced.
View values & sources
| concurrency (c) | DKLs23 | CGGMP21 |
|---|---|---|
| 1 | 15.7 | 0.38 |
| 1 | Not measured | 0.51 |
| 10 | 127.09 | 4.32 |
| 50 | 376.54 | 5.12 |
| 100 | 396.14 | 5.19 |
| 200 | 448.92 | 5.2 |
| 400 | 407.17 | 5.27 |
| 800 | 8 errors | 414 errors |
| 1600 | 1333 errors | 1273 errors |
Sources: 2026-09-15 single-boot sweeps (results/harvest/presig-highc2-{4,16,32}-{dkls,cggmp}-c*.csv); c=1 anchors as in the latency chart. Throughput is requests_per_sec. X settings are equally spaced.
View values & sources
| concurrency (c) | DKLs23 | CGGMP21 |
|---|---|---|
| 1 | 15.33 | 0.38 |
| 1 | Not measured | 0.51 |
| 10 | 117.62 | 4.82 |
| 50 | 304.46 | 10.36 |
| 100 | 341.43 | 10.44 |
| 150 | 1 errors | 10.44 |
| 200 | 213.21 | 10.51 |
| 250 | 254.04 | 10.46 |
| 400 | 213.28 | 10.52 |
| 800 | 261.18 | 31 errors |
| 1200 | 248.54 | 265 errors |
| 1400 | 262.77 | 396 errors |
| 1600 | 164.63 | 594 errors |
| 2000 | 179.93 | 1198 errors |
Sources: 2026-09-15 single-boot sweeps (results/harvest/presig-highc2-{4,16,32}-{dkls,cggmp}-c*.csv); c=1 anchors as in the latency chart. Throughput is requests_per_sec. X settings are equally spaced.
DKLs23 peaks near c=100-250 and then declines; CGGMP21 is flat to c=400 and starts failing around c=800.
Latency under concurrency (c)
Same region · 16 vCPU · median per-presign latency
View values & sources
| concurrency (c) | DKLs23 | CGGMP21 |
|---|---|---|
| 1 | 61.28 | 2,646.16 |
| 1 | Not measured | 1,968.6 |
| 10 | 86.99 | 7,485.6 |
| 50 | 311.65 | 36,855.91 |
| 100 | 619.4 | 69,821.84 |
| 200 | 1,225.41 | 130,745.32 |
| 400 | 2,448.58 | 175,497.7 |
| 800 | Not measured | 350,000 |
Sources: 2026-09-15 single-boot sweep CSVs (results/harvest/presig-highc2-*.csv) plus the c=1 anchors (2026-09-11 DKLS pools; CGGMP21 sequential reference). Cells with <1% failures are shown with their error count; heavier failure breaks the line.
View values & sources
| concurrency (c) | DKLs23 | CGGMP21 |
|---|---|---|
| 1 | 63.69 | 2,658.29 |
| 1 | Not measured | 1,964.8 |
| 10 | 66.98 | 2,245.9 |
| 50 | 100.24 | 9,442.84 |
| 100 | 172.19 | 17,538.52 |
| 200 | 348.71 | 32,126.5 |
| 400 | 697.4 | 61,723.56 |
| 800 | 8 errors | 414 errors |
| 1600 | 1333 errors | 1273 errors |
Sources: 2026-09-15 single-boot sweep CSVs (results/harvest/presig-highc2-*.csv) plus the c=1 anchors (2026-09-11 DKLS pools; CGGMP21 sequential reference). Cells with <1% failures are shown with their error count; heavier failure breaks the line.
View values & sources
| concurrency (c) | DKLs23 | CGGMP21 |
|---|---|---|
| 1 | 65.25 | 2,640.98 |
| 1 | Not measured | 1,961.9 |
| 10 | 66.24 | 2,078.7 |
| 50 | 79.76 | 4,712.44 |
| 100 | 141.58 | 9,168.65 |
| 150 | 1 errors | 12,994.85 |
| 200 | 267.62 | 17,514.75 |
| 250 | 344.32 | 18,711.2 |
| 400 | 547.53 | 34,943.41 |
| 800 | 1,094.89 | 31 errors |
| 1200 | 2,146.67 | 265 errors |
| 1400 | 1,894.93 | 396 errors |
| 1600 | 9,232.86 | 594 errors |
| 2000 | 576.79 | 1198 errors |
Sources: 2026-09-15 single-boot sweep CSVs (results/harvest/presig-highc2-*.csv) plus the c=1 anchors (2026-09-11 DKLS pools; CGGMP21 sequential reference). Cells with <1% failures are shown with their error count; heavier failure breaks the line.
More concurrency only adds queueing: CGGMP21 latency grows linearly and fails past ~c=400, while DKLs23 holds flat until its knee near c=100-200.
Consistency check: throughput × latency ≈ concurrency
All three VM sizes and both protocols · dashed line is y = x
By Little's law, the number of presignatures in flight equals throughput multiplied by latency. Points on the diagonal confirm that the harness holds the configured concurrency with no hidden queue on either side, so the latency and throughput figures on this page describe the protocols, not the test rig.
View values & sources
| vCPU | Protocol | Concurrency (c) | presigns/s | mean latency (s) | median latency (s) | throughput × mean | Ratio to c |
|---|---|---|---|---|---|---|---|
| 4 | CGGMP21 | c=10 | 1.25 | 7.751 | 7.810 | 9.7 | 0.97× |
| 4 | CGGMP21 | c=50 | 1.32 | 36.856 | 36.856 | 48.6 | 0.97× |
| 4 | CGGMP21 | c=100 | 1.31 | 69.323 | 69.822 | 91.1 | 0.91× |
| 4 | CGGMP21 | c=200 | 1.27 | 125.075 | 130.745 | 158.4 | 0.79× |
| 4 | CGGMP21 | c=400 | 1.31 | 186.172 | 175.498 | 244.4 | 0.61× — outside ±30% |
| 4 | DKLs23 | c=10 | 114.19 | 0.087 | 0.087 | 10.0 | 1.00× |
| 4 | DKLs23 | c=50 | 163.21 | 0.306 | 0.312 | 49.9 | 1.00× |
| 4 | DKLs23 | c=100 | 163.13 | 0.610 | 0.619 | 99.6 | 1.00× |
| 4 | DKLs23 | c=200 | 161.13 | 1.227 | 1.225 | 197.8 | 0.99× |
| 4 | DKLs23 | c=400 | 163.09 | 2.392 | 2.449 | 390.1 | 0.98× |
| 16 | CGGMP21 | c=10 | 4.32 | 2.271 | 2.246 | 9.8 | 0.98× |
| 16 | CGGMP21 | c=50 | 5.12 | 9.374 | 9.443 | 48.0 | 0.96× |
| 16 | CGGMP21 | c=100 | 5.19 | 17.945 | 17.539 | 93.1 | 0.93× |
| 16 | CGGMP21 | c=200 | 5.20 | 33.349 | 32.127 | 173.4 | 0.87× |
| 16 | CGGMP21 | c=400 | 5.27 | 55.394 | 61.724 | 291.8 | 0.73× |
| 16 | DKLs23 | c=10 | 127.09 | 0.079 | 0.067 | 10.0 | 1.00× |
| 16 | DKLs23 | c=50 | 376.54 | 0.133 | 0.100 | 49.9 | 1.00× |
| 16 | DKLs23 | c=100 | 396.14 | 0.252 | 0.172 | 99.7 | 1.00× |
| 16 | DKLs23 | c=200 | 448.92 | 0.443 | 0.349 | 198.9 | 0.99× |
| 16 | DKLs23 | c=400 | 407.17 | 0.972 | 0.697 | 396.0 | 0.99× |
| 32 | CGGMP21 | c=10 | 4.70 | 2.105 | 2.100 | 9.9 | 0.99× |
| 32 | CGGMP21 | c=50 | 10.36 | 4.700 | 4.712 | 48.7 | 0.97× |
| 32 | CGGMP21 | c=100 | 10.44 | 9.127 | 9.169 | 95.3 | 0.95× |
| 32 | CGGMP21 | c=150 | 10.44 | 13.370 | 12.995 | 139.5 | 0.93× |
| 32 | CGGMP21 | c=200 | 10.51 | 17.226 | 17.515 | 181.0 | 0.91× |
| 32 | CGGMP21 | c=250 | 10.46 | 21.169 | 18.711 | 221.4 | 0.89× |
| 32 | CGGMP21 | c=400 | 10.52 | 30.835 | 34.943 | 324.2 | 0.81× |
| 32 | DKLs23 | c=10 | 117.62 | 0.085 | 0.066 | 10.0 | 1.00× |
| 32 | DKLs23 | c=50 | 304.46 | 0.164 | 0.080 | 49.9 | 1.00× |
| 32 | DKLs23 | c=100 | 341.43 | 0.292 | 0.142 | 99.6 | 1.00× |
| 32 | DKLs23 | c=200 | 213.21 | 0.932 | 0.268 | 198.7 | 0.99× |
| 32 | DKLs23 | c=250 | 254.04 | 0.977 | 0.344 | 248.1 | 0.99× |
| 32 | DKLs23 | c=400 | 213.28 | 1.769 | 0.548 | 377.3 | 0.94× |
| 32 | DKLs23 | c=800 | 261.18 | 2.944 | 1.095 | 769.0 | 0.96× |
| 32 | DKLs23 | c=1200 | 248.54 | 4.538 | 2.147 | 1,127.9 | 0.94× |
| 32 | DKLs23 | c=1400 | 262.77 | 5.019 | 1.895 | 1,318.9 | 0.94× |
| 32 | DKLs23 | c=1600 | 164.63 | 8.788 | 9.233 | 1,446.8 | 0.90× |
| 32 | DKLs23 | c=2000 | 179.93 | 7.253 | 0.577 | 1,305.0 | 0.65× — outside ±30% |
Source: 2026-09-15 single-boot sweeps (results/harvest/presig-highc2-*.csv), zero-error cells. Little's law uses mean time in system, so the product uses avg_latency_ms; the median is shown alongside for reference. 2 of 38 cells fall outside ±30%: 32 vCPU DKLs23 c=2000 at 0.65×; 4 vCPU CGGMP21 c=400 at 0.61×.
Compute vs bandwidth per transaction — DKLs23 vs CGGMP21
| Protocol | Operation | CPU per tx (3 VMs) | Bandwidth per tx (one-way, aggregate over parties) | Sustained rate (100k pool) | Traffic at sustained rate |
|---|---|---|---|---|---|
| DKLs23 | Presigning | ~0.05 core-s | ~120 KiB | ~706 presigns/s | ~0.69 Gbit/s |
| DKLs23 | One online signing round | ~7.5 ms p50 | ~4.4 KB | — | — |
| CGGMP21 | Presigning | ~5.9 core-s | ~88 KiB | ~10.7 presigns/s | ~7.6 Mbit/s |
| CGGMP21 | One online signing round | ~26 ms p50 | ~4–7 KB | — | — |
Bandwidth (CGGMP21 / DKLs23) ≈ 0.72×: CGGMP21 sends ≈28% fewer bytes per presignature. Both online rounds are small (4–7 KB) and fast (10s of ms). The headline comparison on this page is measured wall-clock, not a compute ratio.
How to read the numbers
- time* — estimated pool time: no run at that exact setting; derived from measured rates (method in the cell tooltip). See footnote for uncertainty.
- not measured — no run exists for that combination; we show no number instead of a guess.
- failed — the sustained pool run finished with request errors; unusable at that concurrency.
- single-use — presignatures are single-use: every signature consumes one, so the pool must be refilled at least as fast as you sign.
* Estimated pool times are computed, not measured: a measured single-run rate for that protocol and concurrency, scaled to the selected size; the derivation of every cell is in its tooltip. Repeats of identical settings on this fleet varied by up to ±20% between boots (DKLS c=100 at 32 vCPU: 294–427 s), so treat estimates as ±20% intervals and read them as orders of magnitude. Cells without * are measured runs and carry their exact duration.
What was benchmarked
- DKLs23: Silence Laboratories' production implementation of the protocol from “Threshold ECDSA in Three Rounds” (Doerner, Kondi, Lee, shelat; IEEE S&P 2024). Paper: eprint.iacr.org/2023/765. Implementation: github.com/silence-laboratories/dkls23.
- CGGMP21: the LFDT-Lockness implementation of “UC Non-Interactive, Proactive,
Threshold ECDSA with Identifiable Aborts” (Canetti, Gennaro, Goldfeder, Makriyannis, Peled;
CCS 2020, revised 2024).
Paper: eprint.iacr.org/2021/060.
Implementation: github.com/LFDT-Lockness/cggmp21
at commit
b870883, with Paillier primes cached at process start-up so key setup is excluded from presign timings. - Harness: our benchmark driver runs off the party VMs and is identical for both protocols
(commit
7328920). Raw CSVs and the harness are in github.com/silence-laboratories/threshold-bench (private; access on request). Summary report: silence-laboratories.github.io/threshold-bench-report.
Get in touch
MPC infrastructure for Institution Grade Digital Assets
Book a discussion call© Silence Laboratories