15 KiB
Dilithium → ML-DSA migration guide
Background
The post-quantum signature algorithm originally implemented in wolfSSL
under the pre-standardization name Dilithium was standardized by NIST
as ML-DSA (Module-Lattice-based Digital Signature Algorithm) — FIPS
204 in 2024. This release renames the wolfSSL implementation of that
algorithm to its standardized name, mirroring the earlier Kyber → ML-KEM
migration in wc_mlkem.{h,c}.
For application code written against the legacy dilithium_key /
wc_dilithium_* / wc_Dilithium_* API there is no immediate change
required: a temporary compatibility shim translates the legacy names
into the canonical ones at compile time. The shim will be removed in a
future release; new code should adopt the canonical names directly.
What changed
File renames
| Old path | New path |
|---|---|
wolfcrypt/src/dilithium.c |
wolfcrypt/src/wc_mldsa.c |
wolfssl/wolfcrypt/dilithium.h |
wolfssl/wolfcrypt/wc_mldsa.h |
The legacy <wolfssl/wolfcrypt/dilithium.h> path is now a thin shim
that #includes wc_mldsa.h and provides macro / inline aliases for
the legacy API.
Symbol renames
| Old | New |
|---|---|
dilithium_key |
MlDsaKey |
wc_dilithium_params |
MlDsaParams |
wc_dilithium_* (lifecycle / sizing) |
wc_MlDsaKey_* |
wc_Dilithium_* (DER encode / decode) |
wc_MlDsaKey_* |
internal lower-case dilithium_* helpers |
mldsa_* |
DILITHIUM_* algorithm-parameter macros |
MLDSA_* (matches MLKEM_* in wc_mlkem.h) |
DILITHIUM_LEVEL{2,3,5}_*_SIZE, ML_DSA_LEVEL{2,3,5}_*_SIZE, DILITHIUM_ML_DSA_{44,65,87}_*_SIZE |
WC_MLDSA_{44,65,87}_*_SIZE |
DEBUG_DILITHIUM |
DEBUG_MLDSA |
The three legacy size-constant families
(DILITHIUM_LEVEL{2,3,5}_*_SIZE, ML_DSA_LEVEL{2,3,5}_*_SIZE,
DILITHIUM_ML_DSA_{44,65,87}_*_SIZE) remain reachable through the
dilithium.h shim as #define-style aliases for the canonical
WC_MLDSA_{44,65,87}_*_SIZE family — eight spellings per parameter
set (KEY_SIZE, PRV_KEY_SIZE, PUB_KEY_SIZE, SIG_SIZE,
PRV_KEY_DER_SIZE, PUB_KEY_DER_SIZE, BOTH_KEY_DER_SIZE,
BOTH_KEY_PEM_SIZE). All of them are gated on
!defined(WOLFSSL_NO_DILITHIUM_LEGACY_NAMES).
The WC_ML_DSA_{44,65,87} / WC_ML_DSA_{44,65,87}_DRAFT / WC_ML_DSA_DRAFT
public level identifiers and the PARAMS_ML_DSA_{44,65,87}_*
per-parameter-set internal constants intentionally keep their
underscored ML_DSA_ spelling — the level identifiers are established
public names and the PARAMS_* family is internal-only, so neither
benefits from a rename.
The WOLFSSL_NO_ML_DSA_{44,65,87} parameter-set disable gates are
likewise kept in their underscored form (matching the
WOLFSSL_NO_ML_KEM_{512,768,1024} spelling in wc_mlkem.h).
The 16 sign / verify / import / DER-decode entry points were also
re-ordered to put the MlDsaKey* first (matching the FIPS 204 / ML-KEM
convention used by wc_MlKemKey_*). The legacy parameter order is
preserved through static-inline wrapper functions in the shim header,
so legacy call sites compile unchanged.
wc_MlDsaKey_Init is a 3-argument function (MlDsaKey*, void* heap,
int devId) matching wc_MlKemKey_Init. The legacy 1-argument
wc_dilithium_init(key) is mapped through the shim to
wc_MlDsaKey_Init(key, NULL, INVALID_DEVID).
Build-gate renames
| Old | New |
|---|---|
HAVE_DILITHIUM |
WOLFSSL_HAVE_MLDSA |
WOLFSSL_DILITHIUM_* (~25) |
WOLFSSL_MLDSA_* |
WC_DILITHIUM_CACHE_* |
WC_MLDSA_CACHE_* |
WC_DILITHIUM_FIXED_ARRAY |
WC_MLDSA_FIXED_ARRAY |
WC_DILITHIUMKEY_TYPE_DEFINED |
WC_MLDSAKEY_TYPE_DEFINED |
The Autotools / CMake configure switches gain canonical aliases:
| Legacy | Canonical |
|---|---|
--enable-dilithium |
--enable-mldsa |
WOLFSSL_DILITHIUM |
WOLFSSL_MLDSA |
Both spellings remain valid; the canonical form is recommended for new projects.
The configure summary echoes ML-DSA: yes rather than DILITHIUM: yes.
Public error-code rename
The error-code enumerator in wolfssl/error-ssl.h was renamed:
| Legacy | Canonical | Numeric value |
|---|---|---|
DILITHIUM_KEY_SIZE_E |
MLDSA_KEY_SIZE_E |
-453 (unchanged) |
The numeric value is unchanged, so any code that compares against the
literal -453 (or stores the value) continues to work. Code that
references the symbol by name is covered by a legacy #define DILITHIUM_KEY_SIZE_E MLDSA_KEY_SIZE_E alias, gated on
!defined(WOLFSSL_NO_DILITHIUM_LEGACY_NAMES). The error string returned
by wolfSSL_ERR_reason_error_string is now "Wrong key size for ML-DSA.".
Public ASN.1 / OID identifier renames
The pre-standardization LEVEL2/3/5 spellings of the ML-DSA public ASN.1
key-type, certificate-type, and OID enumerators were renamed to match
the FIPS 204 parameter-set numbers (44 / 65 / 87), and to match the
existing WC_MLDSA_{44,65,87}_*_SIZE / BENCH_ML_DSA_{44,65,87}_SIGN
spellings:
| Legacy | Canonical | Defined in |
|---|---|---|
ML_DSA_LEVEL{2,3,5}_TYPE |
ML_DSA_{44,65,87}_TYPE |
wolfssl/wolfcrypt/asn_public.h (enum CertType) |
ML_DSA_LEVEL{2,3,5}_KEY |
ML_DSA_{44,65,87}_KEY |
wolfssl/wolfcrypt/asn.h (cert-gen key type) |
ML_DSA_LEVEL{2,3,5}k |
ML_DSA_{44,65,87}k |
wolfssl/wolfcrypt/oid_sum.h (enum Key_Sum) |
CTC_ML_DSA_LEVEL{2,3,5} |
CTC_ML_DSA_{44,65,87} |
wolfssl/wolfcrypt/oid_sum.h (enum Ctc_SigType) |
All four families keep their numeric values (e.g. ML_DSA_44k is still
431), so ABI is preserved. Source-level back-compat for unmigrated
consumers is provided by #define-style legacy aliases next to each
enum, gated on !defined(WOLFSSL_NO_DILITHIUM_LEGACY_NAMES) (the same
gate as the rest of the dilithium.h shim — see header comment in
<wolfssl/wolfcrypt/dilithium.h> for the gate's full coverage).
The DILITHIUM_LEVEL{2,3,5}k / CTC_DILITHIUM_LEVEL{2,3,5} /
DILITHIUM_LEVEL{2,3,5}_TYPE / DILITHIUM_LEVEL{2,3,5}_KEY
pre-standardization (NIST PQC round 3) enumerators are intentionally
not renamed: they identify a distinct draft-era OID surface and
coexist with the FIPS 204 entries in the same enum. For the same reason
the "Dilithium Level {2,3,5}" OID-name labels in
wolfssl_object_info[] (src/ssl.c) are kept under the Dilithium name
and coexist with parallel "ML-DSA {44,65,87}" rows.
The PEM header / footer markers used by wc_MlDsaKey_* PEM
import/export ("-----BEGIN ML_DSA_LEVEL2 PRIVATE KEY-----", etc.) are
intentionally unchanged — the string contents are a serialization
format and renaming them would break PEM files written by older
wolfSSL. The C identifier names (BEGIN_ML_DSA_LEVEL{2,3,5}_PRIV,
END_*) are likewise unchanged.
OpenSSL compatibility
The OpenSSL-compat enum value WC_EVP_PKEY_DILITHIUM and macro
EVP_PKEY_DILITHIUM are unchanged in this release. Aligning them with
OpenSSL 3.5+'s actual NID_ML_DSA_* values is planned for a follow-up
commit.
How to migrate (when you are ready)
The temporary shim accepts both legacy and canonical names indefinitely until it is removed. To migrate a consumer to canonical:
- Replace
#include <wolfssl/wolfcrypt/dilithium.h>with#include <wolfssl/wolfcrypt/wc_mldsa.h>. - Replace
dilithium_keywithMlDsaKey. - Replace each
wc_dilithium_*/wc_Dilithium_*call with thewc_MlDsaKey_*form, swapping arguments to put the key first for the 16 affected entry points. - Replace
HAVE_DILITHIUM/WOLFSSL_DILITHIUM_*/WC_DILITHIUM_*build-gate references with the canonical names.
Migration can be done file by file; the two spellings interoperate at the link level (the shim's static-inline wrappers call into the canonical exported symbols).
To suppress the legacy aliases (e.g. to surface stale references during migration), define one or both of:
WOLFSSL_NO_DILITHIUM_LEGACY_NAMES— suppresses the legacydilithium_key/wc_dilithium_*/wc_Dilithium_*macro / inline aliases, theML_DSA_LEVEL{2,3,5}*/CTC_ML_DSA_LEVEL{2,3,5}/DILITHIUM_KEY_SIZE_Eenum aliases, and the legacy size-constant family (DILITHIUM_LEVEL{2,3,5}_*_SIZE,ML_DSA_LEVEL{2,3,5}_*_SIZE,DILITHIUM_ML_DSA_{44,65,87}_*_SIZE).WOLFSSL_NO_DILITHIUM_LEGACY_GATES— suppresses the bidirectional sub-config gate translations (legacyWOLFSSL_DILITHIUM_*/WC_DILITHIUM_*↔ canonicalWOLFSSL_MLDSA_*/WC_MLDSA_*). The parent gate (HAVE_DILITHIUM↔WOLFSSL_HAVE_MLDSA) forward arm is always active so that builds using only the legacy parent name still compile the canonical implementation file; the reverse arm honors this opt-out.
In-tree consumers have been migrated to the canonical names in this
release, so a build that defines WOLFSSL_NO_DILITHIUM_LEGACY_NAMES
(with or without WOLFSSL_NO_DILITHIUM_LEGACY_GATES) compiles cleanly
and make check passes.
Internal API note (no back-compat aliases)
A handful of identifiers that were defined only in wolfSSL-internal
headers (no presence in dilithium.h, no public-API surface) were
renamed in place without a backwards-compatibility alias. They
affect downstream code only if it reached into wolfssl/internal.h or
similar internal headers:
| Legacy | Canonical | Defined in |
|---|---|---|
DILITHIUM_SA_MAJOR, DILITHIUM_LEVEL{2,3,5}_SA_{MAJOR,MINOR} |
MLDSA_SA_MAJOR, MLDSA_{44,65,87}_SA_{MAJOR,MINOR} |
wolfssl/internal.h |
SIG_DILITHIUM |
SIG_MLDSA |
wolfssl/internal.h |
dilithium_level{2,3,5}_sa_algo (enum SignatureAlgorithm) |
mldsa_{44,65,87}_sa_algo |
wolfssl/internal.h |
dilithium_sign (enum ClientCertificateType) |
mldsa_sign |
wolfssl/internal.h |
MIN_DILITHIUMKEY_SZ |
MIN_MLDSAKEY_SZ |
wolfssl/internal.h |
minDilithiumKeySz (struct field on WOLFSSL_CTX, WOLFSSL_CERT_MANAGER, Options) |
minMlDsaKeySz |
wolfssl/internal.h |
haveDilithiumSig (bitfield on WOLFSSL_CTX, Options) |
haveMlDsaSig |
wolfssl/internal.h |
peerDilithiumKey, peerDilithiumKeyPresent (WOLFSSL) |
peerMlDsaKey, peerMlDsaKeyPresent |
wolfssl/internal.h |
HYBRID_*_DILITHIUM_LEVEL*_SA_MINOR |
HYBRID_*_MLDSA_{44,65,87}_SA_MINOR |
src/tls13.c (file-local) |
dilithium (union field on SignatureCtx::key) |
mldsa |
wolfssl/wolfcrypt/asn.h |
dilithium_test (test-driver entry point) |
mldsa_test |
wolfcrypt/test/test.{c,h} |
bench_dilithium_level{2,3,5}_{key,pubkey,sig} |
bench_mldsa_{44,65,87}_{key,pubkey,sig} |
wolfssl/certs_test.h, wolfcrypt/benchmark/benchmark.c |
bench_dilithiumKeySign |
bench_mldsaKeySign |
wolfcrypt/benchmark/benchmark.{c,h} |
BENCH_DILITHIUM_LEVEL{2,3,5}_SIGN |
BENCH_ML_DSA_{44,65,87}_SIGN (legacy macros deleted as redundant duplicates) |
wolfcrypt/benchmark/benchmark.c |
The benchmark CLI options -dilithium_level{2,3,5} are retained as
deprecated aliases for -ml-dsa-{44,65,87} and will be removed
alongside the dilithium.h shim.
Test coverage
The canonical ML-DSA API is exercised by tests/api/test_mldsa.c
(~24 test_mldsa_* functions), wolfcrypt/test/test.c::mldsa_test,
and the TLS / X.509 paths in tests/api.c that exercise ML-DSA
end-to-end. These run under all build configurations including builds
that suppress the legacy alias surface.
The legacy-name shim itself is covered by
tests/api/test_mldsa_legacy.c::test_mldsa_legacy_shim, a single
focused regression test combining three layers of check:
- Compile-time
wc_static_assertover every alias spelling — all three size-constant families (LEVEL, DILITHIUM_LEVEL, DILITHIUM_ML_DSA) at all 8 spellings per parameter set, every public enum alias, the error-code alias, and the FIPS 204 algorithm-parameter macros. - Typed function-pointer assignments without casts that bind each
symbol-form alias (
wc_dilithium_init_ex,wc_dilithium_free, …) to a pointer with the canonical signature, so a signature drift in the shim trips a build error. - Compile-time invocation of every arg-reordering macro under
if (0)so the compiler type-checks the macro expansion in every configuration (including verify-only builds where the runtime smoke test below is skipped). - Runtime make-key / sign / verify / export / import / DER round-trip driving the arg-reordering macros with valid inputs; a same-type arg swap (which the compile-time invocation can't catch) shows up as a verification or import failure.
The runtime portion requires both sign and verify; in a verify-only
build it skips and the compile-time layers carry the coverage. A
same-type arg swap on the verify side specifically is then caught only
by the canonical KAT-driven verify tests in
test_mldsa.c::test_mldsa_verify_*_kats, which always run.
The whole file becomes a TEST_SKIPPED stub when
WOLFSSL_NO_DILITHIUM_LEGACY_NAMES is defined.
ABI note
The library's exported linkage symbols are renamed: the .so /
.dylib / .dll now exports wc_MlDsaKey_* instead of
wc_dilithium_*. Applications that linked dynamically against the
legacy symbol names need to either recompile against the legacy header
path (the shim's static-inline wrappers resolve to the new symbols at
compile time) or switch their sources to the canonical names. Source
code that includes <wolfssl/wolfcrypt/dilithium.h> continues to build
without modification.