Module-Lattice-Based Digital Signature Algorithm (ML-DSA)

ML-DSA is the post-quantum digital signature scheme standardized by NIST in FIPS 204. Its keys are in Crypto.PublicKey.MLDSA.

FIPS 204 defines two variants:

  • ML-DSA, where the message is signed directly (recommended).

  • HashML-DSA, where the message is first hashed, and only the resulting digest is signed. This should only be used by applications that cannot process the whole message at once when signing, because it requires a hash function with enough collision resistance.

This module supports both variants, with any of the three parameter sets (ML-DSA-44, ML-DSA-65 and ML-DSA-87).

A signature also depends on a context: up to 255 bytes that separate different protocols or applications using the same key (empty by default). The same context must be used to sign and to verify.

By default, the signature is hedged: it also depends on fresh random bytes, which protects against fault and side-channel attacks. With deterministic=True, it only depends on the key, the context and the message. Both kinds of signatures are verified in the same way.

A sender can use a private key (loaded from a file) to sign a message:

from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa

message = b'I give my permission to order #4355'
key = MLDSA.import_key(open("private_mldsa.pem").read())
signer = mldsa.new(key)
signature = signer.sign(message)

The receiver can use the matching public key to verify the authenticity of the received message:

from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa

message = b'I give my permission to order #4355'
key = MLDSA.import_key(open("public_mldsa.pem").read())
verifier = mldsa.new(key)
try:
    verifier.verify(message, signature)
    print("The message is authentic")
except ValueError:
    print("The message is not authentic")

For HashML-DSA, pass a hash object that has processed the message, instead of the message itself:

from Crypto.Hash import SHA512
from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa

message = b'I give my permission to order #4355'
key = MLDSA.import_key(open("private_mldsa.pem").read())
signer = mldsa.new(key, b'my application')
signature = signer.sign(SHA512.new(message))

The hash function can be SHA-2 (Crypto.Hash.SHA224, Crypto.Hash.SHA256, Crypto.Hash.SHA384, Crypto.Hash.SHA512, also truncated), SHA-3 (Crypto.Hash.SHA3_224, Crypto.Hash.SHA3_256, Crypto.Hash.SHA3_384, Crypto.Hash.SHA3_512) or SHAKE (Crypto.Hash.SHAKE128 and Crypto.Hash.SHAKE256, with 256 and 512 bits of output). To keep the security level of the key, FIPS 204 (Section 5.4) asks for a function with at least 128 bits of collision resistance for ML-DSA-44 (like SHA-256), 192 bits for ML-DSA-65 (like SHA-384) and 256 bits for ML-DSA-87 (like SHA-512).

To process a large message in pieces with ML-DSA (not HashML-DSA), use an MLDSAMuHash. It computes the message representative \(\mu\) of FIPS 204 (external mu) for a given key and context, and it can be passed to MLDSASigScheme.sign() and MLDSASigScheme.verify() instead of the message. The signature is the same as for the whole message:

from Crypto.PublicKey import MLDSA
from Crypto.Signature import mldsa

key = MLDSA.import_key(open("private_mldsa.pem").read())
mu_hash = mldsa.MLDSAMuHash(key, context=b'my application')
for chunk in message_chunks:
    mu_hash.update(chunk)
signature = mldsa.new(key).sign(mu_hash)

# The receiver can verify with the whole message too
mldsa.new(key.public_key(), b'my application').verify(message, signature)

The context belongs to the MLDSAMuHash: the signature object must then be created without one. An MLDSAMuHash only needs the public key: \(\mu\) can also be computed apart from the private key (see MLDSAMuHash.digest()).

ML-DSA and HashML-DSA signatures (FIPS 204)

class Crypto.Signature.mldsa.MLDSAMuHash(key, *, data=None, context=None)

The message representative \(\mu\) of a message, for a given public key and context (FIPS 204, Algorithm 7), computed incrementally.

Pass it to MLDSASigScheme.sign() or MLDSASigScheme.verify() instead of the message: the signature is the same (ML-DSA, not HashML-DSA), but the message can be processed in pieces, or apart from the private key (only the public key is needed).

Parameters:

key (Crypto.PublicKey.MLDSA.MLDSAKey) – The key that signs or verifies (only its public part is used).

Keyword Arguments:
  • data (bytes) – The first piece of the message (optional). The message can also be passed later with update().

  • context (bytes) – Up to 255 bytes of context, as for new(). If None (default), the context is empty.

copy()

Return a copy of this object, to compute \(\mu\) for messages that start with the same data.

Return type:

MLDSAMuHash

digest()

Return the 64-byte message representative \(\mu\) of the message processed so far.

The object can still be updated afterwards.

Return type:

bytes

digest_size = 64

The size of \(\\mu\) in bytes

update(data)

Continue with the next piece of the message.

Parameters:

data (bytes) – The next piece of the message.

Return type:

MLDSAMuHash

class Crypto.Signature.mldsa.MLDSASigScheme(key, context, deterministic, randfunc)

An ML-DSA signature object. Do not instantiate directly. Use Crypto.Signature.mldsa.new().

can_sign()

Return True if this signature object can be used for signing messages.

Return type:

bool

sign(msg_or_hash)

Compute the ML-DSA signature of a message.

Parameters:

msg_or_hash (bytes, an MLDSAMuHash or a hash object) –

One of:

Returns:

2420 bytes for ML-DSA-44, 3309 bytes for ML-DSA-65, and 4627 bytes for ML-DSA-87.

Return type:

The signature as bytes

Raises:

TypeError – if the key is not a private key.

verify(msg_or_hash, signature)

Check if an ML-DSA signature is authentic.

Parameters:
  • msg_or_hash (bytes, an MLDSAMuHash or a hash object) – The message that was signed, as for sign(). A signature of a message (bytes) can also be verified with an MLDSAMuHash of that message, and vice versa.

  • signature (bytes) – The signature to verify.

Raises:

ValueError – if the signature is not authentic.

Return type:

None

Crypto.Signature.mldsa.new(key, context=None, *, deterministic=False, randfunc=None)

Create a signature object MLDSASigScheme that can perform or verify an ML-DSA signature (FIPS 204).

Parameters:
Keyword Arguments:
  • deterministic (boolean) – If False (default), the signature is hedged: it depends on fresh random bytes too (the recommended variant). If True, the signature is deterministic: it only depends on the key, the context and the message. Verification is the same in both cases.

  • randfunc (callable) – The RNG for the hedged signatures: a function that returns the requested number of random bytes. If None, Crypto.Random.get_random_bytes() is used. It cannot be combined with deterministic=True.

Return type:

MLDSASigScheme