mldsa-native v2.0.0
Release notes
v2.0.0 is the first stable release of mldsa-native, a secure, fast and portable C90 implementation of ML-DSA derived from the ML-DSA reference implementation.
It carries the version number v2.0.0 rather than v1.0.0 because its public API and configuration are aligned with those of the upcoming v2 release of the sister project mlkem-native.
mldsa-native v2.0.0 offers:
- High maintainability and extensibility through modular frontend/backend design.
- High performance through AArch64 (Neon) and x86_64 (AVX2) assembly backends and the use of the
SLOTHY super-optimizer. - High assurance through memory- and type-safety proofs for the C frontend and backend (CBMC),
functional-correctness and memory-safety proofs for both the AArch64 and x86_64 assembly
backends (HOL Light and s2n-bignum), and extensive constant-time testing.
mldsa-native v2.0.0 is uniformly licensed Apache-2.0 OR MIT OR ISC, giving consumers the choice to use any of these
licenses.
Assurance
v2.0.0 marks the completion of the two formal-verification efforts underpinning mldsa-native: the HOL Light proofs for
the AArch64 (Neon) and x86_64 (AVX2) assembly backends and the CBMC proofs for the C code are now both complete.
- AArch64 and x86_64 assembly proved in HOL Light. Every routine in both the AArch64 (Neon) and x86_64 (AVX2)
backends is proved functionally correct and memory-safe at the object-code level, using
HOL Light and the s2n-bignum verification
infrastructure. All routines except the secret-vector rejection samplers (rej_uniform_eta{2,4}) are additionally
proved to have secret-independent timing (those samplers do have secret-independent timing, but this property is
not yet backed by proof). - CBMC proofs for the C code, establishing memory safety, type safety and absence of various classes of undefined
behaviour for all C code involved in running mldsa-native with its C backend.
See SOUNDNESS.md for more detail on the scope, assumptions and residual risks of the verification
work; it shares its methodology, formal models and trusted computing base with the sister project
mlkem-native.
Breaking changes since v1.0.0-beta2
Between the last beta release (v1.0.0-beta2) and this release, a number of breaking changes to the public API and configuration have been introduced. Consumers upgrading from v1.0.0-beta2 should be aware of the following:
siglenremoved from the signing and verification APIs. Signing functions no longer take asize_t *siglen
out-parameter and verification functions no longer take asize_t siglenin-parameter; signatures are always
MLDSA_BYTES(MLD_CONFIG_PARAMETER_SET)bytes and verification now takes a fixed-size signature array. Drop the
siglenargument at every call site. (#1240)- SUPERCOP-style aliases and constants removed. The
crypto_sign_keypair/crypto_sign_signature/
crypto_sign_verifyaliases, theCRYPTO_SECRETKEYBYTES/CRYPTO_PUBLICKEYBYTES/CRYPTO_BYTESsize macros, and
theMLD_CONFIG_NO_SUPERCOPoption were removed. Use the namespaced API directly and derive sizes from
MLDSA_SECRETKEYBYTES/MLDSA_PUBLICKEYBYTES/MLDSA_BYTES.
(#1248) - SUPERCOP signed-message API removed. The combined signed-message functions (
crypto_sign/crypto_sign_open
and their namespaced equivalents, which produced/consumed signature-prepended messages) were removed. Applications
needing that format should build it on top of the detachedsignature/verifyAPI.
(#1236) MLD_ERR_FAILsplit into specific error codes. Failure conditions that previously all surfaced as
MLD_ERR_FAILnow have dedicated codes:MLD_ERR_INVALID_SIGNATURE(verification rejected the signature),
MLD_ERR_INVALID_KEY(pk_from_skfound the secret key malformed or internally inconsistent),MLD_ERR_PCT_FAIL
(the key-generation pairwise consistency test failed) andMLD_ERR_INVALID_ARG(unsupported pre-hash algorithm or
context string longer than 255 bytes). The previously existing codes keep their values, but callers matching on
MLD_ERR_FAILmust be updated; it is no longer externally observable.
(#1309)- Legacy header-time configuration removed. The deprecated
MLD_CONFIG_API_*macros
(MLD_CONFIG_API_PARAMETER_SET,MLD_CONFIG_API_NAMESPACE_PREFIX,MLD_CONFIG_API_NO_SUPERCOP,
MLD_CONFIG_API_CONSTANTS_ONLY,MLD_CONFIG_API_QUALIFIER) were removed in favour of configuring through the
config file (MLD_CONFIG_PARAMETER_SET,MLD_CONFIG_NAMESPACE_PREFIX,
MLD_CONFIG_CONSTANTS_ONLY,MLD_CONFIG_EXTERNAL_API_QUALIFIER). For multi-level builds, set
MLD_CONFIG_MULTILEVEL_BUILDand do not bake the 44/65/87 suffix into the namespace prefix; the security level is
now appended automatically. (#1206,
#1270) - FIPS-202 backend flags renamed to the
MLD_USE_NATIVE_*convention. Custom FIPS-202 backends must rename the
flags they define:MLD_USE_FIPS202_X1_NATIVE->MLD_USE_NATIVE_FIPS202_X1,MLD_USE_FIPS202_X4_NATIVE->
MLD_USE_NATIVE_FIPS202_X4,MLD_USE_FIPS202_X4_XOR_BYTES_NATIVE->MLD_USE_NATIVE_FIPS202_X4_XOR_BYTES, and
MLD_USE_FIPS202_X4_EXTRACT_BYTES_NATIVE->MLD_USE_NATIVE_FIPS202_X4_EXTRACT_BYTES. Default builds are
unaffected. (#1207) - Capability enum values prefixed with the architecture.
mld_sys_capvalues were renamed
(MLD_SYS_CAP_AVX2->MLD_SYS_CAP_X86_64_AVX2,MLD_SYS_CAP_SHA3->MLD_SYS_CAP_AARCH64_SHA3,
MLD_SYS_CAP_MVE->MLD_SYS_CAP_ARMV81M_MVE). This affects integrators with custom native backends or a custom
capability-check function. (#1208) - Minimum FIPS 204 signing-attempt bound raised from 814 to 821. Builds that set
MLD_CONFIG_MAX_SIGNING_ATTEMPTSexplicitly must now use a value of at least 821; 814 through 820 no longer compile.
The FIPS 204 errata revises
the Repetitions row of Table 1 to 4.36 / 5.14 / 3.91 and, with it, the minimum loop-iteration limit for
ML-DSA.Sign_internalin Table 3 from 814 to 821.
(#1349)
We hope these simplifications make integrations easier for the majority of consumers. Moving forward, APIs and
configuration options not marked as experimental are guaranteed to be stable until a new major version (v3) is
released; additional APIs and configuration options may be introduced at any time.
If the breaking changes above cause any issues for your application, please
open a GitHub issue. If your application could benefit from
additional APIs or configuration options, please open an issue as well.
What's New
Restartable and bounded signing for real-time systems
ML-DSA signing is a rejection-sampling loop: it draws a candidate signature and retries until one passes the
bounds checks. The number of attempts is not known in advance, so a single signing call has an unbounded worst-case
runtime.
For environments with strict timing requirements that is a problem: a signing call cannot be allowed to run for an
arbitrary number of iterations before returning control to the caller.
mldsa-native addresses this with three optional, independent signing hooks around the rejection-sampling loop,
enabled with MLD_CONFIG_SIGN_HOOK_ATTEMPT, MLD_CONFIG_SIGN_HOOK_RESUME and MLD_CONFIG_SIGN_HOOK_FINISH. The
attempt hook runs before each attempt and may pause the operation; signing then returns the new
MLD_ERR_SIGNING_PAUSED code instead of iterating further. At the start of the next call the resume hook is queried
for the attempt to restart from (the one the attempt hook recorded when it paused), so signing continues exactly where
it left off; the finish hook fires with the succeeding attempt once a signature is found. The split, resumed run
produces exactly the same signature as a single uninterrupted call.
To bound per-call runtime, the attempt hook pauses once a per-call budget is spent, and the caller re-invokes signing
in a loop until it completes.
For concurrent or interleaved signers, hold the resume state per caller by enabling MLD_CONFIG_CONTEXT_PARAMETER
instead of a global; each API function then forwards a context argument to the hooks. A complete example is in
examples/restartable_sign.
(#1237)
This feature is experimental: its scope, configuration and hook signatures may change, including after v2.
We would love to hear your feedback on this feature.
API and configuration
MLD_SYS_AARCH64_FAST_SHA3opts non-Apple AArch64 cores with fast SHA3 instructions into the SHA3-instruction
path. (#1254)
Platforms and testing
- Windows MSVC test support (KAT, Wycheproof, allocation-failure and RNG-failure tests).
(#1247) - AVR baremetal / 16-bit platform support. (#1158)
- Zephyr hardware and QEMU test platforms, including NUCLEO-N657X0-Q.
(#1259,
#1269) - ABI checker for AArch64, x86_64 (SysV) and Armv8.1-M+MVE.
(#1195)
Documentation
- FIPS 204 references annotated throughout the code and expanded in the documentation, mapping the implementation
to the standard. - API-CONVENTIONS.md documents the conventions shared by all public functions: return
values, pointer validity (all pointers are assumed valid and non-NULL, except a pointer paired with a length, which
may be NULL when that length is0), and the state of output buffers on error (left either unchanged or fully
zeroized, never holding partially computed data).
What's Changed
- CI: Re-enable Cortex-A76 benchmarks by @willieyz in #1129
- CI: Migrate OpenTitan integration to Pavona by @mkannwischer in #1130
- CI: Add more legacy compiler tests and move to scheduled workflow by @mkannwischer in #1132
- CI: Add gcc16 tests by @mkannwischer in #1133
- CI: Switch HOL-Light proofs to free Github runners by @mkannwischer in #1134
- CI: Check that all HOL-Light .S files are autogenerated by @mkannwischer in #1139
- CI: Split mingw-w64 tests into per-PR and daily sets by @mkannwischer in #1138
- Relax fqmul output bound to 5/4*Q by @hanno-becker in #1131
- Introduce macro abbreviations for offsets into pk/sk/sig buffers by @mkannwischer in #1137
- HOL-Light: Consolidate AArch64 poly_use_hint_32 _CORRECT theorems by @jakemas in #1136
- CI: Move RISC-V runner jobs out of base into a dedicated workflow by @mkannwischer in #1141
- Proofs/Isabelle: Add Neon NTT formalization and conformance harness by @hanno-becker in #1135
- Nix: add isabelle devShell and CI build of the Isabelle proofs by @mkannwischer in #1144
- nix: bump to nixpkgs 26.05 by @mkannwischer in #1149
- HOL-Light: Consolidate AArch64 poly_use_hint_88 _CORRECT theorems by @jakemas in #1152
- Tests: Update Wycheproof pin to 4f5e05f7 (2026-06-04) by @mkannwischer in #1154
- CI: Update dependencies and integration pins by @mkannwischer in #1145
- Deduplicate polyw1_pack in multilevel builds by @mkannwischer in #1142
- Nix: Remove valgrind from default shell and small clean-up by @mkannwischer in #1155
- HOL-Light: add rej_uniform_eta proofs for AArch64 by @jakemas in #1040
- HOL-Light: Document complete AArch64 proof coverage by @mkannwischer in #1161
- Neon NTT: Add chapter on Barrett/Montgomery for even moduli by @hanno-becker in #1162
- x86_64 + HOL-Light: Replace poly_chknorm intrinsics with assembly + HOL-Light proof by @jakemas in #1151
- Use z3_smt_only for proof of poly_ntt_c() by @rod-chapman in #1165
- HOL-Light: Switch to
mld_namespace by @mkannwischer in #1168 - Neon NTT: Minor improvements by @hanno-becker in #1164
- autogen: Allow --force-cross to target specific architectures by @mkannwischer in #1167
- Add clang-tidy to lint with pedantic config by @mkannwischer in #1169
- x86_64: Use SysV calling convention for assembly on Windows by @mkannwischer in #1166
- mldsa_native.h: Fix MLD_TOTAL_ALLOC worst-case for legacy builds by @mkannwischer in #1179
- Fix minor inconsistencies by @mkannwischer in #1171
- CI: Improve workflow robustness by @mkannwischer in #1172
- SLOTHY: Pin to 0.2.2 to speed up CI re-optimization by @mkannwischer in #1176
- x86_64 + HOL-Light: Replace polyz_unpack AVX2 intrinsics with hand-written assembly and HOL-Light proofs by @jakemas in #1182
- mldsa: annotate functions with FIPS 204 algorithm references by @mkannwischer in #1146
- CI: Run RISC-V64 RVV tests as per-VLEN matrix entries by @mkannwischer in #1186
- Add KAT support for MSVC-based tests on Windows by @mkannwischer in #1184
- CI: Trim unnecessary fetch-depth from checkout steps by @mkannwischer in #1192
- CI: Run x86_64 functional tests on an emulated non-AVX2 CPU by @mkannwischer in #1194
- Tests: Add AVR baremetal tests by @mkannwischer in #1158
- ACVP: Allow skipping pre-hash modes unsupported by Python's hashlib by @mkannwischer in #1140
- CI: Build macOS FIPS202 sanitizer tests with clang 22 by @hanno-becker in #1205
- CI: Apply clang 22 macOS sanitizer fix to build_kat too by @hanno-becker in #1209
- Update to tagged build of CBMC 6.10.0 by @rod-chapman in #1210
- Add ABI checker for AArch64, x86_64 (SysV), Armv8.1-M+MVE by @hanno-becker in #1195
- Fail HOL-Light tests on unsupported arch by @fegge in #1232
- Use runtime checks in Wycheproof client by @fegge in #1234
- Remove SUPERCOP signed-message API (crypto_sign/crypto_sign_open) by @mkannwischer in #1236
- CI: Install sqlite3 via apt instead of nix profile by @mkannwischer in #1239
- Update prover choice and/or tactic for long-running proofs by @rod-chapman in #1211
- common: Split context-parameter machinery into context.h by @hanno-becker in #1238
- sign: Rewrite signing loop as
forloop, renamenonce->attempt / kappa. by @hanno-becker in #1246 - Add Windows MSVC test support: rng_fail, alloc, wycheproof by @willieyz in #1247
- sys: Prefix capability enum values with architecture by @mkannwischer in #1208
- FIPS202: Consolidate backend MLD_USE_NATIVE_* flags by @mkannwischer in #1207
- bench: Add Graviton5 (c9g) to EC2 benchmark matrix by @mkannwischer in #1252
- Add
MLD_SYS_AARCH64_FAST_SHA3to signal fast SHA3 instructions by @mkannwischer in #1254 - x86_64: Drop redundant prose headers from AVX2 assembly kernels by @jakemas in #1262
- x86_64 + HOL-Light: Replace poly_decompose AVX2 intrinsics with hand-written assembly and HOL-Light proofs by @jakemas in #1181
- Port: Add Zephyr-based test platform for QEMU MPS boards by @mkannwischer in #1259
- CI: Update GitHub action dependencies by @mkannwischer in #1260
- CI: Add merge queue support by @mkannwischer in #1264
- x86_64 + HOL-Light: Replace poly_use_hint AVX2 intrinsics with hand-written assembly and HOL-Light proofs by @jakemas in #1189
- nix: Pull CBMC 6.10.0 from nixos-unstable binary cache by @mkannwischer in #1261
- Config: Remove legacy configuration by @mkannwischer in #1206
- Remove TRBYTES from public API by @mkannwischer in #1268
- Remove
crypto_sign_*API aliases andMLD_CONFIG_NO_SUPERCOPby @mkannwischer in #1248 - x86_64 + HOL-Light: Replace rej_uniform intrinsics with assembly and HOL-Light CORRECT and MEMSAFE proofs by @jakemas in #1014
- Fetch ACVP/Wycheproof vectors via authenticated GitHub API by @mkannwischer in #1275
- Revert "Remove TRBYTES from public API" by @hanno-becker in #1273
- Fix double level-suffixing of default namespace in multi-level builds by @mkannwischer in #1270
- CI: Authenticate GitHub API requests in more test-running jobs by @mkannwischer in #1279
- Document signing secret-key validity precondition by @fegge in #1229
- Reject MLD_PREHASH_NONE in prehash APIs by @fegge in #1228
- Remove siglen from signing and verification APIs by @mkannwischer in #1240
- Add NUCLEO-N657X0-Q Zephyr hardware platform by @mkannwischer in #1269
- Support randomized Wycheproof signing vectors by @fegge in #1235
- CI: Update liboqs to 0.16.0 by @mkannwischer in #1278
- Add signing hooks for observing signature attempts and implementing restartable signing by @hanno-becker in #1237
- AWS-LC: Patch capability enum to include X86_64 prefix by @mkannwischer in #1283
- CI: Gate merges on HOL-Light proofs by @mkannwischer in #1284
- aarch64: Use array parameters consistently in asm backend by @mkannwischer in #1285
- CI: Track upstream Wycheproof ML-DSA vector updates by @mkannwischer in #1286
- x86_64 + HOL-Light: Replace rej_uniform_eta2/eta4 intrinsics with hand-written assembly and HOL-Light proofs by @jakemas in #1188
- liboqs: Normalize return codes to OQS_STATUS by @mkannwischer in #1298
- SOUNDNESS/README: Use proper markdown citation syntax for Round3_Spec by @hanno-becker in #1293
- Add missing MLD_INTERNAL_API/MLD_EXTERNAL_API declarations by @hanno-becker in #1288
- CBMC: Functional specs for
make_hintand hint unpacking by @mkannwischer in #1287 - README: Mark x86_64 HOL-Light assembly coverage as complete by @mkannwischer in #1297
- CI: Bump Wycheproof vectors to fc24cd5 (2026-07-07) by @mkannwischer in #1290
- AArch64: Add runtime NEON capability gate to native backend by @hanno-becker in #1301
- Document public API conventions by @mkannwischer in #1295
- Add compile-time guard for use of Neon by @hanno-becker in #1305
- Clear ML-DSA outputs on signing failure by @tob-joe in #1242
- CBMC: Use instrumented malloc/free for MLD_ALLOC/MLD_FREE by @hanno-becker in #814
- Backends: Namespace assembly local labels with mld_ prefix by @hanno-becker in #1310
- ACVP: Support FIPS204-tr1 sigGen (v1.1.0.43) by @mkannwischer in #1307
- Split ambiguous MLD_ERR_FAIL into specific error codes by @hanno-becker in #1309
- SOUNDNESS: Fix link to s2n-bignum soundness document by @jakemas in #1325
- Zephyr: apply -O3 across test firmware by @bremoran in #1335
- AWS-LC: Bump integration test to v5.4.0 and fix import by @mkannwischer in #1315
- Fix stale filename reference by @mkannwischer in #1342
- Raise FIPS 204 signing-attempt bound to 821 by @mkannwischer in #1349
- Doc: Various minor fixes by @mkannwischer in #1351
- mldsa_native.h: Hoist domain separation constants above API guard by @mkannwischer in #1354
- Docs: Fix more minor mistakes by @mkannwischer in #1355
- Fix build and coverage under MLD_CONFIG_NO_RANDOMIZED_API by @mkannwischer in #1343
- Add v2.0.0 release notes by @mkannwischer in #1299
- Doc: More documentation fixes by @mkannwischer in #1357
- Doc: Clarify scope and customization of mld_zeroize by @hanno-becker in #1360
- CBMC: Check that outputs are zeroized or unmodified on error by @hanno-becker in #1361
New Contributors
Full Changelog: v1.0.0-beta2...v2.0.0