Files
wolfssl/doc/dilithium-to-mldsa-migration.md
T
Tobias Frauenschläger 637c07798a Finalize ML-DSA renaming
2026-05-26 14:54:30 +02:00

277 lines
15 KiB
Markdown

# 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 `#include`s `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:
1. Replace `#include <wolfssl/wolfcrypt/dilithium.h>` with
`#include <wolfssl/wolfcrypt/wc_mldsa.h>`.
2. Replace `dilithium_key` with `MlDsaKey`.
3. Replace each `wc_dilithium_*` / `wc_Dilithium_*` call with the
`wc_MlDsaKey_*` form, swapping arguments to put the key first
for the 16 affected entry points.
4. 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 legacy
`dilithium_key` / `wc_dilithium_*` / `wc_Dilithium_*` macro / inline
aliases, the `ML_DSA_LEVEL{2,3,5}*` / `CTC_ML_DSA_LEVEL{2,3,5}` /
`DILITHIUM_KEY_SIZE_E` enum 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 (legacy `WOLFSSL_DILITHIUM_*` /
`WC_DILITHIUM_*` ↔ canonical `WOLFSSL_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_assert`** over 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.