Ledger Wallet™ just got a major upgrade.

Take control today

Embedded Software | 07/06/2026

Post-Quantum Cryptography In The Ledger SDK

Learn how Ledger has built two new security tools into its SDK replacing older security methods with ones that quantum computers can't crack.

Before You Dive In

  • The threat is real, but not here yet: A powerful enough quantum computer could break the math that protects most digital security today, including crypto wallets. It doesn’t exist yet, but the industry is preparing now so defenses are ready when it does.
  • Ledger has added two new algorithms in its SDK: These are called ML-KEM and ML-DSA. They replace older security methods with ones that even quantum computers can’t crack.
  • ML-KEM is for sharing secrets securely: Think of it like a lockbox: the recipient publishes an open lock (public key), the sender puts a secret inside and snaps it shut (encapsulation), and only the recipient can open it (decapsulation). Nobody else, including a quantum computer, can read what’s inside.
  • ML-DSA is for proving something is genuine: It works like a digital signature on a document: it proves a message came from who it says it came from, and hasn’t been tampered with. It’s the quantum-safe replacement for the signature method most blockchains use today.
  • The tradeoff is size, not security: These new methods are secure against future threats, but their keys and signatures are much larger than the old ones, sometimes 30–50x bigger. That’s a known cost of quantum resistance.
  • Ledger’s devices are small and have limited memory: So the implementation required clever engineering. Rather than loading everything into memory at once, the code rebuilds what it needs piece by piece, staying within tight hardware limits without cutting any security corners.
  • One important caveat the document flags openly: the current version doesn’t yet include protection against physical attacks (like someone probing the chip while it runs). That’s noted as future work.
  • The output is bit-for-bit identical to the global NIST standard: This means Ledger’s implementation passes the same test vectors as the reference, just using less RAM


ML-KEM & ML-DSA | FIPS 203 / FIPS 204
Implementation Guide | Ledger Embedded OS 
June 2026

The Quantum Threat: Why It Matters

Today’s public-key cryptography relies on mathematical problems that are computationally infeasible for classical computers to solve: factoring large integers (RSA), or computing discrete logarithms on elliptic curves (ECDSA, ECDH). A sufficiently powerful quantum computer running Shor’s algorithm would solve these problems in polynomial time, breaking the security guarantees that protect wallets, transactions, and key derivation schemes.

In 2016 NIST ran a multi-year competition to standardize quantum-resistant algorithms, culminating in the publication of FIPS 203 (ML-KEM) and FIPS 204 (ML-DSA) in August 2024. Ledger’s Embedded OS team has implemented both standards in the SDK.

What changes for applications?

Both ML-KEM and ML-DSA expose a familiar API structure, but the underlying concepts differ from their classical counterparts in important ways.

ML-KEM is the more conceptually different one. Key generation looks similar to ECDH, you produce a public/private key pair, but the exchange mechanism is fundamentally different. In ECDH, both parties contribute a key pair and compute a shared secret symmetrically. In ML-KEM, only the recipient generates a key pair; the sender runs encapsulation, which takes the recipient’s public key and produces both a ciphertext and a shared secret in one shot. The recipient then runs decapsulation on that ciphertext to recover the same shared secret. There is no back-and-forth scalar multiplication: the sender and recipient play asymmetric roles.

ML-DSA maps more naturally onto ECDSA: key generation produces a signing key and a verification key, sign takes a message and secret key and produces a signature, and verify takes the signature, message, and public key and returns a pass/fail. The call signatures will feel familiar. What changes is everything underneath: the parameters, the hash functions involved and the mathematical structure.


What does ML-KEM replace?

ML-KEM (Module-Lattice Key Encapsulation Mechanism) replaces key-exchange protocols like ECDH. Its goal is to let two parties derive a shared secret over an untrusted channel. It does not directly encrypt data: it produces a symmetric key, which you then use with AES for instance.

What does ML-DSA replace?

ML-DSA (Module-Lattice Digital Signature Algorithm) replaces ECDSA. It produces a signature over a message that any holder of the corresponding public key can verify, without revealing the secret key.

From ECC to Lattices: A Gentle Comparison

Elliptic curve cryptography is built on a one-way function: scalar multiplication on a curve is fast to compute forward but infeasible to reverse. On a quantum computer, that reversal is easy via Shor’s algorithm.

Lattice-based cryptography uses a completely different hard problem: adding small noise to a structured system of linear equations over integer rings makes the system hard to solve, even for quantum computers. This is the Learning With Errors (LWE) problem.

POST-QUANTUM CRYPTOGRAPHY IN THE LEDGER SDK
ECC vs. Module-Lattice: the trapdoor comparison

Key Size Comparison

ALGORITHMSECURITY LEVELPUBLIC KEYSECRET KEYCIPHERTEXT / SIGQUANTUM SAFE
P-256 / ECDH128-bit64 B32 B64 BNo
P-256 / ECDSA128-bit64 B32 B64 BNo
ML-KEM-512L1 (~128)800 B1 632 B768 B (ct)Yes
ML-KEM-768L3 (~192)1 184 B2 400 B1 088 B (ct)Yes
ML-KEM-1024L5 (~256)1 568 B3 168 B1 568 B (ct)Yes
ML-DSA-44L21 312 B2 560 B2 420 B (sig)Yes
ML-DSA-65L31 952 B4 032 B3 309 B (sig)Yes
ML-DSA-87L52 592 B4 896 B4 627 B (sig)Yes


ML-KEM Algorithm Construction (FIPS 203)

ML-KEM is built from two layers. The inner layer is an IND-CPA PKE scheme (K-PKE) constructed over Module-LWE. The outer layer applies a Fujisaki-Okamoto (FO) transform to lift it to IND-CCA2 security, which is the standard required for a KEM.

The IND-CPA Core: K-PKE

K-PKE works in the polynomial ring Zq[X]/(X^n+1) with n=256, q=3329. The module dimension k varies by parameter set. Key generation expands a 32-byte seed via SHA3-512 into a public seed ρ and a noise seed σ, then samples the matrix A (via SHAKE-128) and secret/error vectors s and e following the centered binomial distribution (CBD) with SHAKE-256.

POST-QUANTUM CRYPTOGRAPHY IN THE LEDGER SDK
K-PKE KeyGen: seed expands into matrix A and secret vectors s, e; public key t = As + e

Building Blocks

BUILDING BLOCKROLE IN ML-KEMPRIMITIVE
NTTFast polynomial multiplication in Zq[X]/(X^n+1)In-place Cooley-Tukey, zeta table
Montgomery / BarrettModular reduction during NTT and base multiplicationCustom 16-bit arithmetic
CBD (eta=2,3)Sample small-norm secret and error polynomialsSHAKE-256 PRF
Rejection samplingSample uniform polynomials for matrix ASHAKE-128 XOF
Compression/DecompressionQuantize polynomial coefficients for compact ciphertextBit-packing, du/dv parameters
H, G, JHash functions for FO transform, key derivationSHA3-256, SHA3-512, SHAKE-256
FO TransformLift IND-CPA to IND-CCA2Re-encryption + CT comparison

The FO Transform & IND-CCA2 Decapsulation

The outer KEM layer performs decapsulation in a constant-time sequence: it re-encrypts the decrypted plaintext and performs a constant-time comparison against the received ciphertext. On mismatch, it outputs a pseudorandom value from an implicit-reject seed rather than an error, to prevent oracle attacks.

POST-QUANTUM CRYPTOGRAPHY IN THE LEDGER SDK
ECC vs. Module-Lattice: the trapdoor comparison

Parameter Sets

ML-KEM-512k:  2η₁:  3η₂:  2du/dv:  10/4pk:  800 Bct:  768 Blevel:  NIST 1ML-KEM-768k:  3η₁:  2η₂:  2du/dv:  10/4pk:  1 184 Bct:  1 088 Blevel:  NIST 3ML-KEM-1024k:  4η₁:  2η₂:  2du/dv:  11/5pk:  1 568 Bct:  1 568 Blevel:  NIST 5


ML-DSA Algorithm Construction (FIPS 204)

ML-DSA is a Fiat-Shamir with aborts scheme over Module-LWE and Module-SIS. Its signature security rests on the hardness of finding short vectors in a lattice: there is no known quantum speedup for this problem.

Signing At A High Level

POST-QUANTUM CRYPTOGRAPHY IN THE LEDGER SDK
ML-DSA signing: Fiat-Shamir with aborts. On norm bound failure, nonce κ++ and restart. Up to 814 attempts.

Building Blocks

BUILDING BLOCKROLE IN ML-DSAPRIMITIVE
NTT (32-bit)Fast polynomial multiplication over Zq, q=8380417In-place Cooley-Tukey, zeta table
Montgomery (64-bit)Reduce int64 intermediate products mod qMLDSA_POLY_montgomery_reduce
Power2RoundSplit t into (t1, t0) at bit D=13 for pk compressionArithmetic shift + masking
Decompose / HighBits / LowBitsRounding for hint computationModulo gamma2 arithmetic
MakeHint / UseHintCarry hints for verifier reconstruction of w1Bit vector, omega-bounded
SampleInBallGenerate sparse +-1 challenge polynomialSHAKE-256 + rejection
ExpandMaskSample masking vector y from seedSHAKE-256, gamma1-range
ExpandAExpand matrix A from rhoSHAKE-128 uniform rejection

Parameter Sets

ML-DSA-44k/l:  4/4η:  2τ/β:  39/78γ₁:  2^17pk/sk:  1312/2560 Bsig:  2420 BML-DSA-65k/l:  6/5η:  4τ/β:  49/196γ₁:  2^19pk/sk:  1952/4032 Bsig:  3309 BML-DSA-87 (opt-in)k/l:  8/7η:  2τ/β:  60/120γ₁:  2^19pk/sk:  2592/4896 Bsig:  4627 B
ML-DSA-87 is not enabled by default. It requires HAVE_MLDSA_87 but without the low-RAM optimization, key generation and signing with ML-DSA-87 exceeds the stack budget on LNX devices. The flag HAVE_MLDSA_OPTIMIZATION must be enabled to use ML-DSA-87.

Implementation Deep-dive

Ledger devices use resource-constrained secure elements. Both algorithms were implemented with a primary goal: keep peak stack usage within at most 16 kB (see Application available memory) without sacrificing correctness or security. Without optimizations, ML-DSA signing would exceed this budget on larger parameter sets. This section walks through the key implementation choices and how they compare to the NIST reference implementations.

NTT Implementation

Both algorithms use a from-scratch in-place Cooley-Tukey NTT over their respective polynomial rings, sharing the same loop structure but with different field parameters.

PARAMETERML-KEMML-DSA
Modulus q3 3298 380 417
Coefficient typeint16_tint32_t
Montgomery operandint32_t → int16_tint64_t → int32_t
Montgomery inverse QINV62209 (q^-1 mod 2^16)q^-1 mod 2^32
Inv-NTT scale value1441 = 128^-1 * 2^16 mod q41978 = mont^2 / 256
Inv-NTT scale placementPre-scale pass (before butterflies)Post-scale pass (after butterflies)
Barrett constantsV=20159, shift=26reduce32 via arithmetic shift

Inv-NTT deviation vs. NIST Reference (ML-KEM)

The Ledger and NIST implementations of the inverse NTT differ in where the 1/128 scale factor is applied. The NIST reference folds it into a post-scaling pass after all butterfly layers. The Ledger implementation applies it as a pre-scaling pass before the butterfly loop. Because NTT is a linear transform, f · INTT(x) = INTT(f · x), so both orderings are mathematically equivalent and produce bit-identical output.

The pre-scaling choice simplifies the inner loop: the scale multiplication is isolated in its own pass and the butterfly loop only handles additions and zeta multiplications, without needing to special-case the final layer. Barrett reduction is applied on the addition arm of each butterfly to prevent coefficient overflow. ML-DSA’s inverse NTT follows the NIST Dilithium reference more closely, applying the scale factor f = 41978 as a final post-pass.

Hash Functions & XOF

Rather than bundling an independent Keccak implementation, both algorithms reuse the existing Ledger SDK SHAKE and SHA3 primitives via cx_sha3_256_hash, cx_sha3_512_hash, cx_shake128_hash, and cx_shake256_hash. For multi-part hashing operations (e.g., H(tr || M′)), the streaming API is used to avoid allocating a large concatenated buffer.

Stack Optimizations vs. the NIST Reference

ML-KEM: Row-By-Row Matrix Expansion

The NIST reference generates the entire k×k matrix A before computing the product A·s. The Ledger implementation never materializes A: each row is generated on-the-fly, the inner product for that row is accumulated, and the row is immediately discarded. Only one polyvec (one row) lives on the stack at a time.

// Optimization: Generate matrix A row-by-row

// to avoid storing full k×k polymat in RAM</span>

polyvec a_row = {0};   // ONE row at a time

for (uint32_t i = 0U; i < p->k; i++) {

    MLKEM_POLYMAT_gen_matrix_row(&a_row, publicseed, 0, (uint8_t)i, p->k);

    MLKEM_POLYVEC_basemul_acc_montgomery(&pkpv.vec[i], &a_row, &skpv, p->k);

}

SDK file: cx_mlkem_indcpa.c

ML-KEM decapsulation: Stack Union

Decapsulation requires both a re-encryption buffer ct2 and a temporary buffer tmp, but these are needed in sequential phases. A union overlaps them to avoid double-counting their sizes on the stack:

// ct2 is used first (re-encryption),
// then tmp overwrites it (implicit-reject derivation).
union {
    uint8_t ct2[MLKEM1024_CIPHERTEXTBYTES];
    uint8_t tmp[MLKEM_SYMBYTES + MLKEM1024_CIPHERTEXTBYTES];
} u;

SDK file: cx_mlkem.c

ML-DSA: the HAVE_MLDSA_OPTIMIZATION Flag

ML-DSA signing is significantly more memory-intensive than ML-KEM. When HAVE_MLDSA_OPTIMIZATION is enabled, a dedicated low-RAM layer (cx_mldsa_lowram.c) replaces the reference approach. The optimization techniques are taken from ZKnox.

POST-QUANTUM CRYPTOGRAPHY IN THE LEDGER SDK
Low-RAM optimization techniques in cx_mldsa_lowram.c

ML-DSA KeyGen: On-the-Fly s1, s2, A

In the NIST reference, keygen holds full secret vectors s1, s2, and matrix A simultaneously. The Ledger implementation recomputes each polynomial on-the-fly and packs it directly into the output buffers, holding only 3 scratch polynomials at any time:

// Only 3 scratch polys held at once.
// s1[j] is re-expanded each time it's needed,
// then packed once (first row only).
for (uint8_t i = 0; i < p->k; i++) {
    for (uint8_t j = 0; j < p->l; j++) {
        MLDSA_SAMPLE_eta(&tC, rhoprime, j, p->eta);  // expand s1[j]
        if (i == 0) MLDSA_PACK_polyeta(&sk[...], &tC, p->eta); // pack once
        MLDSA_POLY_ntt(&tC);
        MLDSA_SAMPLE_uniform(&tB, rho, nonce);       // expand A[i][j]
        MLDSA_POLY_pointwise_montgomery(&tA, &tB, &tC, j == 0);
    }
    // tA holds row i of A*s1 — invert NTT, add s2, split t
}

SDK file: cx_mldsa_internal.c

Application Available Memory

The application available SRAM memory includes the bss and the stack.

PRODUCTSRAM
Ledger Nano X28kB
Ledger Nano S Plus32kB
Flex36kB
Stax36kB
Ledger Nano Gen540kB

Security Considerations

CONCERNML-KEMML-DSA
Constant-time CT comparecx_constant_time_eq + MLKEM_UTIL_ct_cmovN/A (sign/verify)
Implicit reject oracleAlways return CX_OK; output is J(z,ct)
Sensitive zeroizationexplicit_bzero on sensitive variablesexplicit_bzero on sensitive variables
Enc key modulus checkMLKEM_UTIL_check_ek: all pk coefficients verified < q (FIPS 203 §7.2)
Abort boundUp to 814 attempts (p < 2^-256 of failure)

Side-channel Security: Current State & Roadmap

⚠  NO SIDE-CHANNEL COUNTERMEASURES IN V1
The current implementation provides only algorithmic security: constant-time comparison paths and systematic zeroization of sensitive intermediates. It does NOT include hardware countermeasures against fault injection and side-channel attacks. such as arithmetic masking or execution shuffling in addition to random delay. A future v2 will add shuffling (random permutation of polynomial operations) and arithmetic masking (splitting secret coefficients into randomized shares) as countermeasures.
DEVIATION FROM NIST REFERENCE 
The NIST FIPS 203/204 reference implementations keep full polynomial vectors and matrices in memory simultaneously. The Ledger SDK trades compute (re-expanding polynomials on each pass) for stack. This is the correct trade-off for a device with a limited stack budget. The output is bit-for-bit identical to the NIST test vectors.

SDK API usage

ML-KEM

Include lcx_mlkem.h. Buffer sizes are available as macros or via MLKEM_PARAM[param] at runtime.

Key Generation

#include "lcx_mlkem.h"


uint8_t pk[MLKEM768_PUBLICKEYBYTES];
uint8_t sk[MLKEM768_SECRETKEYBYTES];


cx_err_t err = MLKEM_crypto_kem_keypair(
    pk, sizeof(pk),
    sk, sizeof(sk),
    MLKEM_768
);


Encapsulation (Sender)

uint8_t ct[MLKEM768_CIPHERTEXTBYTES];
uint8_t ss[MLKEM_SSBYTES];  // 32-byte shared secret


cx_err_t err = MLKEM_crypto_kem_enc(
    ct, sizeof(ct),
    ss, sizeof(ss),
    pk, sizeof(pk),
    MLKEM_768
);
// ss is now a 32-byte shared secret; use with AES


Decapsulation (Recipient)

uint8_t ss_recv[MLKEM_SSBYTES];


cx_err_t err = MLKEM_crypto_kem_dec(
    ss_recv, sizeof(ss_recv),
    ct,      ct_len,
    sk,      sizeof(sk),
    MLKEM_768
);
// Always returns CX_OK — compare ss_recv with sender's ss
// at the application layer. A mismatch means the ciphertext
// was tampered with; ss_recv will be pseudorandom.

⚠  DECAPSULATION ALWAYS RETURNS CX_OKPer the FO transform, MLKEM_crypto_kem_dec returns CX_OK even when ciphertext authentication fails. The shared secret in ss will be a pseudorandom value derived from the implicit-reject seed. Always verify the shared secret matches your expected value at the application layer: do not rely on the return code for integrity.
ML-KEM key exchange flow: recipient generates key pair, sender encapsulates, recipient decapsulates

ML-DSA

Include lcx_mldsa.h. The API adds a ctx/ctx_len context string parameter per FIPS 204 §5.2, and a sig_actual_len output for the actual signature length written.


Key Generation

#include "lcx_mldsa.h"


uint8_t pk[MLDSA65_PUBLICKEYBYTES];
uint8_t sk[MLDSA65_SECRETKEYBYTES];


cx_err_t err = MLDSA_keygen(
    pk, sizeof(pk), sk, sizeof(sk), MLDSA_65
);


Signing

uint8_t sig[MLDSA65_SIGBYTES];
size_t  sig_len = 0;


cx_err_t err = MLDSA_sign(
    sig, sizeof(sig), &sig_len,
    msg, msg_len,
    ctx, ctx_len,     // ctx may be NULL if ctx_len == 0
    sk,  sizeof(sk),
    MLDSA_65
);
// sig_len holds actual bytes written


Verification

cx_err_t err = MLDSA_verify(
    sig, sig_len,
    msg, msg_len,
    ctx, ctx_len,
    pk,  sizeof(pk),
    MLDSA_65
);
if (err == CX_OK) {
    // signature is valid
} else {
    // CX_INVALID_PARAMETER or CX_SIGNATURE_INVALID
}

HashML-DSA (Pre-hash Variant)

For large messages that cannot be hashed on-device in a single pass, use MLDSA_sign_prehash / MLDSA_verify_prehash. The caller is responsible for computing the digest before calling. This is incompatible with some use cases like clear-signing.

uint8_t ph_m[32]; // Pre-hash
cx_sha3_256_hash(msg, msg_len, ph_m);  // caller hashes


cx_err_t err = MLDSA_sign_prehash(
    sig, sizeof(sig), &sig_len,
    ph_m, sizeof(ph_m),
    ctx, ctx_len,
    sk,  sizeof(sk),
    MLDSA_PREHASH_SHA3_256,
    MLDSA_65
);

CHOOSING A PARAMETER SET
For most use cases, ML-KEM-768 (NIST Level 3) and ML-DSA-65 (NIST Level 3) offer a good balance between security and performance on constrained hardware. ML-DSA-87 is only available when HAVE_MLDSA_OPTIMIZATION is enabled.

Sandra RASOAMIARAMANANA
Engineering Manager & Cryptographer


References


To learn more about the potential threat of quantum computing on hardware wallets read a previous article on side-channel attacks on post quantum cryptography.

Read this in-depth analysis by the Ledger Donjon to understand how blockchain systems and hardware wallets must adapt to new signature algorithms and security constraints.

Stay in touch

Announcements can be found in our blog. Press contact:
[email protected]

Subscribe to our
newsletter

New coins supported, blog updates and exclusive offers directly in your inbox


Your email address will only be used to send you our newsletter, as well as updates and offers. You can unsubscribe at any time using the link included in the newsletter. Learn more about how we manage your data and your rights.