ML-DSA-87 cryptography
npm install @theqrl/mldsa87Post-quantum digital signatures using ML-DSA-87 (FIPS 204).
This package implements the ML-DSA-87 signature scheme at NIST security level 5 (AES-256 equivalent). It follows the FIPS 204 standard and is recommended for new implementations.
``bash`
npm install @theqrl/mldsa87
`javascript
import {
cryptoSignKeypair,
cryptoSign,
cryptoSignOpen,
CryptoPublicKeyBytes,
CryptoSecretKeyBytes,
} from '@theqrl/mldsa87';
// Generate keypair
const pk = new Uint8Array(CryptoPublicKeyBytes); // 2592 bytes
const sk = new Uint8Array(CryptoSecretKeyBytes); // 4896 bytes
cryptoSignKeypair(null, pk, sk); // null = random seed
// Sign a message (uses default "ZOND" context)
const message = new TextEncoder().encode('Hello, quantum world!');
const signedMessage = cryptoSign(message, sk, false); // false = deterministic
// Verify and extract
const extracted = cryptoSignOpen(signedMessage, pk);
if (extracted === undefined) {
throw new Error('Invalid signature');
}
console.log(new TextDecoder().decode(extracted)); // "Hello, quantum world!"
`
ML-DSA-87 supports a context parameter for domain separation (FIPS 204 feature). This allows the same keypair to be used safely across different applications.
`javascript
// With custom context
const ctx = new TextEncoder().encode('my-app-v1');
const signed = cryptoSign(message, sk, false, ctx);
const extracted = cryptoSignOpen(signed, pk, ctx);
// Context must match for verification
cryptoSignOpen(signed, pk); // undefined - wrong context (default "ZOND")
cryptoSignOpen(signed, pk, ctx); // message - correct context
`
The default context is "ZOND" (for QRL Zond network compatibility). Context can be 0-255 bytes.
| Constant | Value | Description |
|----------|-------|-------------|
| CryptoPublicKeyBytes | 2592 | Public key size in bytes |CryptoSecretKeyBytes
| | 4896 | Secret key size in bytes |CryptoBytes
| | 4627 | Signature size in bytes |SeedBytes
| | 32 | Seed size for key generation |
#### cryptoSignKeypair(seed, pk, sk)
Generate a keypair from a seed.
- seed: Uint8Array(32) or null for randompk
- : Uint8Array(2592) - output buffer for public keysk
- : Uint8Array(4896) - output buffer for secret keyseed
- Returns: The seed used (useful when is null)
#### cryptoSign(message, sk, randomized, context?)
Sign a message (combined mode: returns signature || message).
- message: Uint8Array or string - message bytes; if string, it must be hex only (optional 0x, even length). Plain-text strings are not accepted.sk
- : Uint8Array(4896) - secret keyrandomized
- : boolean - true for hedged signing, false for deterministiccontext
- : Uint8Array (optional) - context string, 0-255 bytes. Default: "ZOND"Uint8Array
- Returns: containing signature + message
#### cryptoSignOpen(signedMessage, pk, context?)
Verify and extract message from signed message.
- signedMessage: Uint8Array - output from cryptoSign()pk
- : Uint8Array(2592) - public keycontext
- : Uint8Array (optional) - must match signing contextundefined
- Returns: Original message if valid, if verification fails
#### cryptoSignSignature(sig, message, sk, randomized, context?)
Create a detached signature.
- sig: Uint8Array(4627) - output buffer for signaturemessage
- : Uint8Array or string - message bytes; if string, it must be hex only (optional 0x, even length). Plain-text strings are not accepted.sk
- : Uint8Array(4896) - secret keyrandomized
- : boolean - true for hedged, false for deterministiccontext
- : Uint8Array (optional) - context string, 0-255 bytes0
- Returns: on success
#### cryptoSignVerify(sig, message, pk, context?)
Verify a detached signature.
- sig: Uint8Array(4627) - signature to verifymessage
- : Uint8Array or string - original message bytes; if string, it must be hex only (optional 0x, even length). Plain-text strings are not accepted.pk
- : Uint8Array(2592) - public keycontext
- : Uint8Array (optional) - must match signing contexttrue
- Returns: if valid, false otherwise
Note: To sign or verify plain text, convert it to bytes (e.g., new TextEncoder().encode('Hello')). String inputs are interpreted as hex only.
#### zeroize(buffer)
Zero out sensitive data (best-effort, see security notes).
#### isZero(buffer)
Check if buffer is all zeros (constant-time).
Both this library and go-qrllib process ML-DSA-87 seeds identically. Raw seeds produce matching keys:
`javascript`
// Same seed produces same keys in both implementations
cryptoSignKeypair(seed, pk, sk);
Verified against the pq-crystals reference implementation.
| Feature | ML-DSA-87 | Dilithium5 |
|---------|-----------|------------|
| Standard | FIPS 204 | CRYSTALS Round 3 |
| Signature size | 4627 bytes | 4595 bytes |
| Context parameter | Supported | Not supported |
| Use case | New implementations | go-qrllib compatibility |
Use @theqrl/dilithium5 only if you need compatibility with existing QRL infrastructure.
See SECURITY.md for important information about:
- JavaScript memory security limitations
- Constant-time verification
- Secure key handling recommendations
- Node.js: 18.20+, 20.x, or 22.x (requires globalThis.crypto.getRandomValues`)
- Browsers: Web Crypto API and ES2020 (BigInt) -- Chrome 67+, Firefox 68+, Safari 14+, Edge 79+
- Full TypeScript definitions included
MIT
- Main documentation
- FIPS 204 - ML-DSA specification
- go-qrllib - Go implementation
- QRL Website