Skip to content

ML-DSA

Header: #include <cryptopp/mldsa.h> | Namespace: CryptoPP
Since: cryptopp-modern 2026.3.0
Thread Safety: Signer/Verifier instances are not thread-safe; use one per thread. If sharing key bytes, load them into separate instances per thread.

ML-DSA (Module-Lattice Digital Signature Algorithm) is a NIST post-quantum cryptographic standard (FIPS 204) based on CRYSTALS-Dilithium. It provides digital signatures resistant to attacks by quantum computers.

Key Features

  • Post-quantum secure - Resistant to attacks by quantum computers
  • EUF-CMA secure - Existential unforgeability under chosen-message attack
  • Probabilistic signing - Uses RNG with rejection sampling; signature output may vary
  • 3 parameter sets - Security levels 2, 3, and 5
  • Efficient - Fast lattice-based operations with NTT

Parameter Sets

Parameter SetSecurity LevelPublic KeySecret KeySignature
ML-DSA-442 (128-bit)1312 bytes2560 bytes2420 bytes
ML-DSA-653 (192-bit)1952 bytes4032 bytes3309 bytes
ML-DSA-875 (256-bit)2592 bytes4896 bytes4627 bytes

Recommendation: Use ML-DSA-65 for most applications (good balance of security and performance).


Quick Example

#include <cryptopp/mldsa.h>
#include <cryptopp/osrng.h>
#include <cryptopp/secblock.h>
#include <string>
#include <iostream>

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool rng;

    // === Generate key pair and create signer ===
    MLDSASigner<MLDSA_65> signer(rng);

    // === Create verifier from signer's public key ===
    MLDSAVerifier<MLDSA_65> verifier(signer);

    // === Sign a message ===
    std::string message = "Hello, post-quantum world!";
    SecByteBlock signature(signer.SignatureLength());

    size_t sigLen = signer.SignMessage(
        rng,
        (const byte*)message.data(),
        message.size(),
        signature.begin()
    );

    // === Verify the signature ===
    bool valid = verifier.VerifyMessage(
        (const byte*)message.data(),
        message.size(),
        signature.begin(),
        sigLen
    );

    std::cout << "Signature valid: " << (valid ? "YES" : "NO") << std::endl;

    return 0;
}

Usage Guidelines

Do:

  • Use ML-DSA-65 for most applications (NIST Level 3, 192-bit security)
  • Store private keys securely using SecByteBlock
  • Consider hybrid signatures combining ML-DSA with Ed25519 for defence in depth
  • Use ML-DSA-87 for high-security applications (government, long-term documents)

Avoid:

  • Using ML-DSA-44 unless size constraints require it (Level 2 is minimum security)
  • Modifying signatures or messages (verification will fail)
  • Exposing private keys in logs or error messages
  • Using the same key pair across different security domains

When to Use ML-DSA

Use ML-DSA when:

  • You need post-quantum secure digital signatures
  • Signing code, certificates, or long-lived documents
  • Preparing for “harvest now, decrypt later” threats
  • Building post-quantum secure authentication systems

Consider SLH-DSA instead when:

  • You want hash-based security (no lattice assumptions)
  • Conservative security requirements demand different mathematical basis
  • Larger signatures are acceptable for your use case

Consider Ed25519 instead when:

  • Quantum computers are not a concern for your threat model
  • Minimum signature size is critical (64 bytes vs 3309+ bytes)
  • Maximum verification speed is required

Class: MLDSASigner

Sign messages using a private key.

Template Parameter

template <class PARAMS>
struct MLDSASigner;

PARAMS is one of: MLDSA_44, MLDSA_65, MLDSA_87

Constants

CRYPTOPP_CONSTANT(SECRET_KEYLENGTH = PARAMS::SECRET_KEY_SIZE);
CRYPTOPP_CONSTANT(PUBLIC_KEYLENGTH = PARAMS::PUBLIC_KEY_SIZE);
CRYPTOPP_CONSTANT(SIGNATURE_LENGTH = PARAMS::SIGNATURE_SIZE);

Constructors

Default Constructor

MLDSASigner();

Create an empty signer. Use AccessKey().GenerateRandom(rng, params) to generate a key.

Constructor with RNG (Generate New Key Pair)

MLDSASigner(RandomNumberGenerator &rng);

Generate a new key pair.

Example:

AutoSeededRandomPool rng;
MLDSASigner<MLDSA_65> signer(rng);
// Ready to sign messages

Constructor with Private Key

MLDSASigner(const byte *privateKey, size_t privateKeyLen);

Load an existing private key.

Example:

// Load stored private key
SecByteBlock storedKey = /* loaded from storage */;
MLDSASigner<MLDSA_65> signer(storedKey.begin(), storedKey.size());

Methods

SignMessage (Convenience Method)

size_t SignMessage(RandomNumberGenerator &rng,
                   const byte *message, size_t messageLen,
                   byte *signature) const;

Sign a message in one call (inherited from PK_Signer).

Parameters:

  • rng - Random number generator
  • message - Message to sign
  • messageLen - Length of message
  • signature - Output buffer for signature (SignatureLength() bytes)

Returns: Actual signature length

Example:

std::string message = "Document to sign";
SecByteBlock signature(signer.SignatureLength());
size_t sigLen = signer.SignMessage(rng,
    (const byte*)message.data(), message.size(),
    signature.begin());

SignatureLength

size_t SignatureLength() const;

Get the signature size in bytes.

GetKey / AccessKey

const MLDSAPrivateKey<PARAMS>& GetKey() const;
MLDSAPrivateKey<PARAMS>& AccessKey();

Access the private key.

AlgorithmName

std::string AlgorithmName() const;

Get the algorithm name (e.g., “ML-DSA-65”). Useful for logging and runtime identification.


Class: MLDSAVerifier

Verify signatures using a public key.

Template Parameter

template <class PARAMS>
struct MLDSAVerifier;

PARAMS is one of: MLDSA_44, MLDSA_65, MLDSA_87

Constants

CRYPTOPP_CONSTANT(PUBLIC_KEYLENGTH = PARAMS::PUBLIC_KEY_SIZE);
CRYPTOPP_CONSTANT(SIGNATURE_LENGTH = PARAMS::SIGNATURE_SIZE);

Constructors

Default Constructor

MLDSAVerifier();

Create an empty verifier. Use AccessKey().SetPublicKey() to set the key.

Constructor from Signer

MLDSAVerifier(const MLDSASigner<PARAMS> &signer);

Create a verifier from a signer’s public key.

Example:

MLDSASigner<MLDSA_65> signer(rng);
MLDSAVerifier<MLDSA_65> verifier(signer);

Constructor with Public Key

MLDSAVerifier(const byte *publicKey, size_t publicKeyLen);

Load a public key.

Example:

SecByteBlock publicKey = /* received from signer */;
MLDSAVerifier<MLDSA_65> verifier(publicKey.begin(), publicKey.size());

Methods

VerifyMessage (Convenience Method)

bool VerifyMessage(const byte *message, size_t messageLen,
                   const byte *signature, size_t signatureLen) const;

Verify a signature in one call (inherited from PK_Verifier).

Parameters:

  • message - Original message
  • messageLen - Length of message
  • signature - Signature to verify
  • signatureLen - Length of signature

Returns: true if signature is valid, false otherwise

Example:

bool valid = verifier.VerifyMessage(
    (const byte*)message.data(), message.size(),
    signature.begin(), signature.size());

if (valid) {
    std::cout << "Signature verified!" << std::endl;
}

SignatureLength

size_t SignatureLength() const;

Get the expected signature size in bytes.

GetKey / AccessKey

const MLDSAPublicKey<PARAMS>& GetKey() const;
MLDSAPublicKey<PARAMS>& AccessKey();

Access the public key.

AlgorithmName

std::string AlgorithmName() const;

Get the algorithm name (e.g., “ML-DSA-65”).


Class: MLDSAPrivateKey

Holds the secret key material for signing.

Methods

GetAlgorithmID

OID GetAlgorithmID() const;

Get the algorithm OID for this key type. Used in ASN.1/DER encoding.

GenerateRandom

void GenerateRandom(RandomNumberGenerator &rng, const NameValuePairs &params);

Generate a new random key pair.

Example:

MLDSAPrivateKey<MLDSA_65> privateKey;
privateKey.GenerateRandom(rng, g_nullNameValuePairs);

GetPublicKeyBytePtr / GetPublicKeySize

const byte* GetPublicKeyBytePtr() const;
size_t GetPublicKeySize() const;

Get the embedded public key.

GetPrivateKeyBytePtr / GetPrivateKeySize

const byte* GetPrivateKeyBytePtr() const;
size_t GetPrivateKeySize() const;

Get the private key bytes.

Save / Load

void Save(BufferedTransformation &bt) const;
void Load(BufferedTransformation &bt);

Serialize/deserialize in ASN.1 DER format (PKCS#8 OneAsymmetricKey).


Class: MLDSAPublicKey

Holds the public key material for verification.

Methods

SetPublicKey

void SetPublicKey(const byte *key, size_t len);

Set the public key bytes.

GetPublicKeyBytePtr / GetPublicKeySize

const byte* GetPublicKeyBytePtr() const;
size_t GetPublicKeySize() const;

Get the public key bytes.

Save / Load

void Save(BufferedTransformation &bt) const;
void Load(BufferedTransformation &bt);

Serialize/deserialize in ASN.1 DER format (X.509 SubjectPublicKeyInfo).


Key Serialization

Requires: #include <cryptopp/files.h> for file I/O, #include <cstring> for std::memcpy.

Note: This implementation buffers the entire message before signing/verification. For very large messages, consider hashing first and signing the digest.

Saving Keys (Raw Bytes)

// Generate key pair
MLDSASigner<MLDSA_65> signer(rng);
const auto& privKey = signer.GetKey();

// Save private key (includes secret and public parts)
SecByteBlock privateKeyBytes(privKey.GetPrivateKeySize());
std::memcpy(privateKeyBytes.begin(),
            privKey.GetPrivateKeyBytePtr(),
            privateKeyBytes.size());

// Save public key (for distribution)
SecByteBlock publicKeyBytes(privKey.GetPublicKeySize());
std::memcpy(publicKeyBytes.begin(),
            privKey.GetPublicKeyBytePtr(),
            publicKeyBytes.size());

Loading Keys (Raw Bytes)

// Load private key (for signing)
MLDSASigner<MLDSA_65> signer(
    privateKeyBytes.begin(), privateKeyBytes.size());

// Load public key only (for verification)
MLDSAVerifier<MLDSA_65> verifier(
    publicKeyBytes.begin(), publicKeyBytes.size());

Saving Keys (ASN.1 DER)

// Save private key to file
FileSink privFile("mldsa_private.der");
signer.GetKey().Save(privFile);

// Save public key to file
FileSink pubFile("mldsa_public.der");
MLDSAPublicKey<MLDSA_65> pubKey;
pubKey.SetPublicKey(signer.GetKey().GetPublicKeyBytePtr(),
                    signer.GetKey().GetPublicKeySize());
pubKey.Save(pubFile);

Loading Keys (ASN.1 DER)

// Load private key from file
FileSource privFile("mldsa_private.der", true);
MLDSASigner<MLDSA_65> signer;
signer.AccessKey().Load(privFile);

// Load public key from file
FileSource pubFile("mldsa_public.der", true);
MLDSAVerifier<MLDSA_65> verifier;
verifier.AccessKey().Load(pubFile);

Convenience Type Aliases

// ML-DSA-44 (Level 2, 128-bit security)
typedef MLDSASigner<MLDSA_44> MLDSA_44_Signer;
typedef MLDSAVerifier<MLDSA_44> MLDSA_44_Verifier;

// ML-DSA-65 (Level 3, 192-bit security)
typedef MLDSASigner<MLDSA_65> MLDSA_65_Signer;
typedef MLDSAVerifier<MLDSA_65> MLDSA_65_Verifier;

// ML-DSA-87 (Level 5, 256-bit security)
typedef MLDSASigner<MLDSA_87> MLDSA_87_Signer;
typedef MLDSAVerifier<MLDSA_87> MLDSA_87_Verifier;

Example using convenience types:

MLDSA_65_Signer signer(rng);
MLDSA_65_Verifier verifier(signer);

Hybrid Signature Example

For defence in depth, combine ML-DSA with classical signatures:

#include <cryptopp/mldsa.h>
#include <cryptopp/xed25519.h>
#include <cryptopp/sha3.h>

struct HybridSignature {
    SecByteBlock ed25519Sig;  // 64 bytes
    SecByteBlock mldsaSig;    // 3309 bytes for ML-DSA-65
};

HybridSignature HybridSign(RandomNumberGenerator& rng,
                           const ed25519Signer& ed25519Signer,
                           const MLDSASigner<MLDSA_65>& mldsaSigner,
                           const byte* message, size_t messageLen)
{
    HybridSignature sig;

    // Ed25519 signature
    sig.ed25519Sig.resize(64);
    ed25519Signer.SignMessage(rng, message, messageLen, sig.ed25519Sig.begin());

    // ML-DSA signature
    sig.mldsaSig.resize(mldsaSigner.SignatureLength());
    mldsaSigner.SignMessage(rng, message, messageLen, sig.mldsaSig.begin());

    return sig;
}

bool HybridVerify(const ed25519Verifier& ed25519Verifier,
                  const MLDSAVerifier<MLDSA_65>& mldsaVerifier,
                  const byte* message, size_t messageLen,
                  const HybridSignature& sig)
{
    // Both signatures must be valid
    bool ed25519Valid = ed25519Verifier.VerifyMessage(
        message, messageLen, sig.ed25519Sig.begin(), sig.ed25519Sig.size());

    bool mldsaValid = mldsaVerifier.VerifyMessage(
        message, messageLen, sig.mldsaSig.begin(), sig.mldsaSig.size());

    return ed25519Valid && mldsaValid;
}

Context Strings

FIPS 204 supports an optional context string (up to 255 bytes) that binds a signature to a specific application or protocol. This prevents cross-protocol signature reuse.

Context strings are set on the message accumulator before signing or verifying:

#include <cryptopp/mldsa.h>
#include <cryptopp/osrng.h>
#include <cryptopp/secblock.h>
#include <string>

using namespace CryptoPP;

AutoSeededRandomPool rng;
MLDSASigner<MLDSA_65> signer(rng);
MLDSAVerifier<MLDSA_65> verifier(signer);

std::string message = "Important document";
const byte ctx[] = "myapp-v1";

// Sign with context
auto* signAcc = signer.NewSignatureAccumulator(rng);
auto* mldsaSignAcc = dynamic_cast<MLDSA_MessageAccumulator<MLDSA_65>*>(signAcc);
mldsaSignAcc->SetContext(ctx, sizeof(ctx) - 1);
mldsaSignAcc->Update((const byte*)message.data(), message.size());

SecByteBlock signature(signer.SignatureLength());
size_t sigLen = signer.SignAndRestart(rng, *signAcc, signature.begin());
delete signAcc;

// Verify with same context
auto* verAcc = verifier.NewVerificationAccumulator();
auto* mldsaVerAcc = dynamic_cast<MLDSA_MessageAccumulator<MLDSA_65>*>(verAcc);
mldsaVerAcc->SetContext(ctx, sizeof(ctx) - 1);
mldsaVerAcc->Update((const byte*)message.data(), message.size());
verifier.InputSignature(*verAcc, signature.begin(), sigLen);

bool valid = verifier.VerifyAndRestart(*verAcc);
delete verAcc;
// valid == true (context matches)

Notes:

  • Context must be identical for signing and verification; a mismatch will cause verification to fail
  • Empty context (the default) is valid and interoperable with implementations that don’t set a context
  • Maximum context length is 255 bytes; SetContext throws InvalidArgument if exceeded

Indicative Performance

OperationML-DSA-44ML-DSA-65ML-DSA-87
Key Generation~100 µs~150 µs~200 µs
Signing~200 µs~300 µs~400 µs
Verification~100 µs~150 µs~200 µs

Illustrative figures only. Performance varies by platform; benchmark in your target environment.

Notes:

  • Signing uses rejection sampling, so times can vary
  • Verification is typically faster than signing
  • ML-DSA-87 provides highest security but largest signatures

Security Considerations

  1. Private Key Protection - Store private keys using SecByteBlock which zeros memory on destruction. Never log or expose private key bytes.

  2. Key Lifetime - Consider key rotation policies. Long-lived signing keys have increased exposure risk.

  3. Signature Verification - Always verify signatures before trusting signed data. Invalid signatures should be treated as a security event.

  4. Quantum Timeline - ML-DSA is designed for “harvest now, decrypt later” threats. Consider migrating now for long-lived signatures (code signing, certificates).

  5. Hybrid Mode - For critical applications, combine ML-DSA with Ed25519 or ECDSA. Security is maintained as long as either algorithm remains secure.

  6. Side Channels - The implementation uses constant-time operations where feasible. Avoid timing measurements on signing operations.


Object Identifiers (OIDs)

Library-provided OIDs for ASN.1/DER encoding:

Parameter SetOIDAccessor
ML-DSA-442.16.840.1.101.3.4.3.17ASN1::id_ml_dsa_44()
ML-DSA-652.16.840.1.101.3.4.3.18ASN1::id_ml_dsa_65()
ML-DSA-872.16.840.1.101.3.4.3.19ASN1::id_ml_dsa_87()

Usage:

// Get OID from key object
OID oid = privateKey.GetAlgorithmID();

// Get OID statically from parameter set
OID oid = MLDSA_65::StaticAlgorithmOID();

// Compare OIDs
if (oid == ASN1::id_ml_dsa_65()) {
    // ML-DSA-65 key
}

Specification Compliance

This implementation follows NIST FIPS 204 (August 2024):

  • Key Generation: Uses SHAKE256 for matrix and secret vector expansion
  • Signing: Implements rejection sampling loop with proper hint generation
  • Verification: Reconstructs commitment and validates challenge hash
  • Primitives: SHAKE256 for all XOF operations, SHA-3 for hashing
  • Parameters: All three parameter sets (44, 65, 87) match FIPS 204 specifications

FIPS 204 Internal Parameters:

Parameter Setkletagamma1gamma2omega
ML-DSA-444422^17(q-1)/8880
ML-DSA-656542^19(q-1)/3255
ML-DSA-878722^19(q-1)/3275

See Also