kryptos/mldsa

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

ML-DSA (formerly CRYSTALS-Dilithium) provides post-quantum digital signatures based on lattice problems believed to resist both classical and quantum attacks. Conventional schemes like ECDSA and Ed25519 rely on elliptic-curve discrete logarithms, which a sufficiently powerful quantum computer could break via Shor’s algorithm. ML-DSA is designed for systems that need long-term security guarantees.

Trade-offs

Post-quantum security comes at a cost. ML-DSA public keys and signatures are significantly larger than their classical counterparts:

AlgorithmPublic KeySignature
Ed2551932 B64 B
ECDSA P-25664 B64 B
ML-DSA-441,312 B2,420 B
ML-DSA-651,952 B3,309 B
ML-DSA-872,592 B4,627 B

All of these use a 32-byte private key. For ML-DSA that is the seed this module stores and exports; the expanded signing key held in memory is larger (2,560 to 4,896 B) but is never serialized.

These larger sizes affect wire overhead, certificate sizes, handshake latency, and storage. ML-DSA-44 targets roughly NIST Security Level 2 (comparable to AES-128), ML-DSA-65 targets Level 3, and ML-DSA-87 targets Level 5. Choose the smallest parameter set that meets your security requirements. ML-DSA-44 is appropriate for most applications.

Key representations

FIPS 204 defines two private key representations: a compact 32-byte seed and the fully expanded key material. Following the CFRG security considerations, this module treats the seed as the canonical representation and never exposes expanded keys. Seed generation guarantees well-formed key material, whereas an externally supplied expanded key could be malformed. The pyca/cryptography library takes the same seed-only approach.

Context strings

FIPS 204 defines an optional context string parameter for signing and verification. Neither Erlang/OTP nor Node.js expose this parameter in their crypto APIs, so this module always uses the default empty context string.

Example

import kryptos/mldsa

let #(private_key, public_key) = mldsa.generate_key_pair(mldsa.Mldsa44)
let message = <<"hello world":utf8>>
let signature = mldsa.sign(private_key, message)
let valid = mldsa.verify(public_key, message:, signature:)
// valid == True

Types

ML-DSA parameter sets.

pub type ParameterSet {
  Mldsa44
  Mldsa65
  Mldsa87
}

Constructors

  • Mldsa44

    ML-DSA-44. 1312-byte public keys, 2420-byte signatures.

  • Mldsa65

    ML-DSA-65. 1952-byte public keys, 3309-byte signatures.

  • Mldsa87

    ML-DSA-87. 2592-byte public keys, 4627-byte signatures.

An ML-DSA private key.

pub type PrivateKey

An ML-DSA public key.

pub type PublicKey

Values

pub fn from_der(
  der: BitArray,
) -> Result(#(PrivateKey, PublicKey), Nil)

Imports an ML-DSA private key from DER-encoded data.

The key must carry the 32-byte seed. ExpandedKey-only encodings, which omit the seed, are rejected with Error(Nil).

pub fn from_pem(
  pem: String,
) -> Result(#(PrivateKey, PublicKey), Nil)

Imports an ML-DSA private key from PEM-encoded data.

The key must carry the 32-byte seed. ExpandedKey-only encodings, which omit the seed, are rejected with Error(Nil).

pub fn from_seed(
  param: ParameterSet,
  seed: BitArray,
) -> Result(#(PrivateKey, PublicKey), Nil)

Creates an ML-DSA key pair from a 32-byte seed.

Returns Error(Nil) if the seed is not exactly 32 bytes.

pub fn generate_key_pair(
  param: ParameterSet,
) -> #(PrivateKey, PublicKey)

Generates a new ML-DSA key pair.

pub fn key_size(param: ParameterSet) -> Int

Returns the public key size in bytes for the given parameter set.

pub fn parameter_set(key: PrivateKey) -> ParameterSet

Returns the parameter set for an ML-DSA private key.

pub fn private_key_to_seed(
  key: PrivateKey,
) -> Result(BitArray, Nil)

Exports the 32-byte seed from an ML-DSA private key.

pub fn public_key_from_bytes(
  param: ParameterSet,
  public_bytes: BitArray,
) -> Result(PublicKey, Nil)

Imports a public key from its FIPS 204 raw byte encoding (rho || t1_packed), without any ASN.1 wrapping. Use public_key_from_pem or public_key_from_der for SPKI-encoded input.

Returns Error(Nil) if the bytes are the wrong size for the parameter set.

pub fn public_key_from_der(
  der: BitArray,
) -> Result(PublicKey, Nil)

Imports an ML-DSA public key from DER-encoded data.

The key must be in SPKI format.

pub fn public_key_from_pem(pem: String) -> Result(PublicKey, Nil)

Imports an ML-DSA public key from PEM-encoded data.

The key must be in SPKI format.

pub fn public_key_from_private_key(key: PrivateKey) -> PublicKey

Derives the public key from an ML-DSA private key.

pub fn public_key_parameter_set(key: PublicKey) -> ParameterSet

Returns the parameter set for an ML-DSA public key.

pub fn public_key_to_bytes(key: PublicKey) -> BitArray

Exports a public key to its FIPS 204 raw byte encoding (rho || t1_packed), without any ASN.1 wrapping. Use public_key_to_pem or public_key_to_der for SPKI-encoded output.

pub fn public_key_to_der(key: PublicKey) -> BitArray

Exports an ML-DSA public key to DER format.

The key is exported in SPKI format.

pub fn public_key_to_pem(key: PublicKey) -> String

Exports an ML-DSA public key to PEM format.

The key is exported in SPKI format.

pub fn sign(
  private_key: PrivateKey,
  message: BitArray,
) -> BitArray

Signs a message using ML-DSA.

ML-DSA includes built-in hashing. No separate hash algorithm parameter is needed.

pub fn signature_size(param: ParameterSet) -> Int

Returns the signature size in bytes for the given parameter set.

pub fn supported() -> Bool

Returns whether the current runtime supports ML-DSA operations.

ML-DSA requires Erlang/OTP 28 or later, or Node.js 24 or later. On older runtimes this returns False and ML-DSA operations are unavailable.

pub fn to_der(key: PrivateKey) -> BitArray

Exports an ML-DSA private key to DER format.

The key is exported as seed-form PKCS#8.

pub fn to_pem(key: PrivateKey) -> String

Exports an ML-DSA private key to PEM format.

The key is exported as seed-form PKCS#8.

pub fn verify(
  public_key: PublicKey,
  message message: BitArray,
  signature signature: BitArray,
) -> Bool

Verifies an ML-DSA signature against a message.

✨ Search Document