making post-quantum signatures faster
technology, open source

making post-quantum signatures faster

post-quantum cryptography, digital signatures, performance, ML-DSA, SLH-DSA
Leon Acosta
Leon Acostaoct 12, 2026 · 5 min read

I wanted to understand how much of the cost of post-quantum signatures comes from the algorithms, and how much comes from the way we implement them

that became a practical exercise across six open-source libraries: noble, liboqs, OpenSSL, CIRCL, RustCrypto and Bouncy Castle. I measured key generation, signing and verification, tried alternative implementations, and kept asking the same questions: does it still produce the same result? does the gain survive a new key? what happens to memory?

the code, measurements and reports are public in pqc-signatures. this post explains what I was trying to learn, what held up, and what I sent back to the projects

why I started

performance affects whether a cryptographic implementation is practical to deploy. signing may happen occasionally or thousands of times per second. verification may run on a server, in a browser or on a constrained device. the cost matters differently in each case

I was interested in the implementations people can actually use: C, Go, Rust, Java and JavaScript. a useful result would be a small change that makes an existing library do less unnecessary work, with evidence that someone else can inspect

the main targets were ML-DSA, standardized in FIPS 204, and SLH-DSA, standardized in FIPS 205. the comparison also includes supported Falcon implementations. I was optimizing implementations of existing schemes. the changes keep their parameter sets and encoded key and signature sizes

how I approached it

I started with a shared measurement contract, reproducible inputs and reference outputs. key generation, signing and verification needed separate measurements: making a key cheaper is a different result from making verification cheaper

the first harness was not the final word. reviewing it exposed measurement and validation limitations, which are retained in the harness audit. later patch experiments used their own pinned source versions, correctness checks and timing boundaries. I kept that distinction rather than treating every old result as equally reliable

for the candidate changes, I checked exact outputs under matching inputs and signing randomness, valid and tampered signatures, malformed inputs, ownership of buffers and keys, and the repositories' native tests where applicable. fixed randomness was test material for reproducibility, not advice for production randomness

then I tried to make the improvements disappear. I tested fresh keys, changed run order, compared individual parts of a patch, and checked runtime or library versions for selected candidates. one followup used ten distinct keys per parameter set with the changed implementation first. later Bouncy Castle experiments used balanced orders and separate JVM processes. these were different experiments with different limits, not one universal benchmark protocol

I also used AI agents to investigate alternatives in parallel and help with harnesses, testing, analysis and documentation. the benchmark runs themselves were serialized to avoid agents competing for the same CPU. the public repository discloses that assistance and retains source revisions, scripts and raw outputs. these are supporting engineering checks, not an independent cryptographic security certification

what improved, and what I submitted

the figures below compare each patch with its own baseline on the recorded machine and configuration. they describe submitted changes, not features that have necessarily shipped. PR statuses were checked on October 12, 2026

liboqs — PR #2625, open. the largest gain came from routing SLH-DSA SHA-2 compression through OpenSSL's optimized transforms when that backend is available. signing and verification were roughly 4–5× faster than the portable baseline in the tested configuration. that is an implementation/backend improvement; it does not make every liboqs algorithm 5× faster. the low-level OpenSSL API deprecation is a real maintenance consideration, and I also tested whether a smaller patch could preserve the gain

CIRCL — PR #708, open. SLH-DSA repeatedly hashes a public seed prefix. saving that hash state within one operation avoids rebuilding it for subsequent hashes. the recorded signing and verification improvements were about 1.32–1.43×. this adds some allocation, so the later GC measurements matter alongside the timing result

OpenSSL — PR #33196, open. the same kind of public-prefix reuse helps the SHA-512 path used by SLH-DSA SHA2-192/256. signing and verification gains were about 1.07–1.20×, with additional context memory. state duplication, cleanup and ownership are part of the change, not details outside it

RustCrypto — PR #1461, open. ML-DSA key construction was rebuilding values it had already computed to populate the existing verification-key cache. reusing those values made construction about 1.17–1.18× faster. construction plus signing improved more modestly. signing with an existing key and verification were unchanged; it would be misleading to present this as a general signing or verification speedup

noble — PR #56, closed without merging. accumulating ML-DSA polynomial products by row reduced temporary arrays and redundant reductions. the repeated study recorded roughly 1.09–1.19× warmed signing and verification, with smaller or mixed startup results. the maintainer closed the proposal over the complexity-versus-benefit tradeoff. a measured improvement does not settle whether a patch belongs in a library

Bouncy Castle — draft on my fork. replacing temporary BigInteger work in SLH-DSA FORS decoding reduced whole-verification allocation by 2.8–7.3%, but did not establish a general latency improvement. this is a draft on my personal fork, not an upstream submission

the later Bouncy Castle work went further: three parallel investigations tested 11 alternatives across 1,464 fresh JVM processes. a small ML-DSA public-constant table produced about 5–6% less signing time on Java 25, with about 6.3% confirmed for ML-DSA-44 and -65 on Java 17 using a new key corpus. its cost is a 1 KiB static array payload plus its header. the combined arithmetic changes had larger regressions in some cases, so the smaller change is the more credible candidate for further review. those experiments and their losing cases are public; they are not another submitted upstream PR

the questions behind the numbers

“is this only faster because the same key is reused?” was one of the most useful questions in the exercise

operation-local reuse is different from a persistent cache. saving a public hash prefix or reusing a scratch buffer inside one signature can help even when the next key is completely different. RustCrypto's existing verification-key cache is another case again: that patch makes its construction cheaper. the reports state which work is inside and outside each timer

memory also changed the interpretation. some Bouncy Castle buffer variants reduced allocated bytes by around 61–77% while making particular operations slower. fewer bytes allocated does not automatically mean less live memory, a smaller process, shorter GC pauses or lower latency. I kept those metrics separate and retained the regressions

I also wanted actual times between libraries, before and after the changes. the absolute-time comparison now covers six libraries and 17 exact parameter sets. it keeps SHA2 separate from SHAKE, and fast SLH-DSA sets separate from small-signature sets. those saved observations come from different protocols, so they are useful reference points, not a controlled fastest-library ranking. a fair ranking still needs equivalent setup boundaries, inputs and warmup

the reports

I published the evidence so the patches can be questioned and reproduced, including the ones that were slower or not accepted

the report links above point to a fixed revision so the evidence behind this post remains readable as the work evolves. the ongoing reports PR tracks subsequent changes

what I wanted from this exercise was something useful to hand back: a smaller amount of work for an implementation to perform, the same cryptographic outputs, and enough evidence for a maintainer to decide whether the tradeoff is worth it