Files
nostr_quantum_preparation/research/pq_js_packages_survey.md
T

12 KiB

Post-Quantum Cryptography: JavaScript Package Survey

⚠️ Research document. A library survey; not an implementation spec.

Executive Summary

We do NOT need to write our own implementations. There are mature, well-maintained JavaScript packages for all NIST-standardized post-quantum algorithms. The standout recommendation is @noble/post-quantum — a pure JavaScript implementation of all FIPS 203/204/205 algorithms, from the highly respected noble cryptography family (335 GitHub stars, MIT licensed, self-audited, actively maintained).

For our PQ Nostr web app, @noble/post-quantum covers everything we need: ML-DSA (signatures), SLH-DSA (signatures), ML-KEM (encryption key agreement), and hybrid KEMs (X-Wing). No WASM required, works in browsers natively.


NIST-Standardized PQ Algorithms (FIPS)

Algorithm FIPS Standard Type Use Case Status
ML-KEM (Kyber) FIPS 203 KEM (key encapsulation) Encryption key agreement Finalized 2024
ML-DSA (Dilithium) FIPS 204 Digital signature Authentication Finalized 2024
SLH-DSA (SPHINCS+) FIPS 205 Digital signature Authentication (conservative) Finalized 2024
FN-DSA (Falcon) FIPS 206 Digital signature Authentication (compact) Still draft

Key and Signature Sizes

Variant Public key Secret key Signature / Ciphertext
ML-KEM-512 800 B 1632 B 768 B
ML-KEM-768 1184 B 2400 B 1088 B
ML-KEM-1024 1568 B 3168 B 1568 B
ML-DSA-44 1312 B 2560 B 2420 B
ML-DSA-65 1952 B 4032 B 3309 B
ML-DSA-87 2592 B 4896 B 4627 B
Falcon-512 897 B 1281 B ~666 B
Falcon-1024 1793 B 2305 B ~1280 B
SLH-DSA-128f 32 B 64 B 17088 B
SLH-DSA-128s 32 B 64 B 7856 B
SLH-DSA-192f 48 B 96 B 35664 B
SLH-DSA-192s 48 B 96 B 16224 B
SLH-DSA-256f 64 B 128 B 49856 B
SLH-DSA-256s 64 B 128 B 29792 B

npm: https://www.npmjs.com/package/@noble/post-quantum GitHub: https://github.com/paulmillr/noble-post-quantum Version: 0.6.1 | License: MIT | Stars: 335

Why This Is the Best Choice

  1. Covers all algorithms we need: ML-KEM, ML-DSA, SLH-DSA, Falcon, and hybrid KEMs (X-Wing, KitchenSink) — all in one package
  2. Pure JavaScript — no WASM, no native bindings, works in any browser and Node.js
  3. From the noble family — Paul Miller's noble libraries (noble-hashes, noble-curves, noble-ciphers, noble-secp256k1) are the most trusted JS crypto libraries, used widely across the ecosystem
  4. Tree-shakeable — only the algorithms you import are bundled (16KB gzipped for everything)
  5. Self-audited at v0.6.1 (April 2026)
  6. Deterministic key generation from seed — keygen(seed) accepts an optional seed, which is exactly what we need for deriving PQ keys from a BIP39 seed phrase
  7. Actively maintained — last updated June 2026
  8. No external dependencies beyond noble-hashes and noble-curves (same author, same trust model)

API Examples

ML-DSA-65 (Dilithium) — for PQ signatures

import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';

// Deterministic keygen from seed (for our seed-phrase derivation)
const seed = new Uint8Array(32); // derived from BIP39 seed via HKDF
const keys = ml_dsa65.keygen(seed);

// Sign
const msg = new TextEncoder().encode('link statement');
const sig = ml_dsa65.sign(msg, keys.secretKey);

// Verify
const isValid = ml_dsa65.verify(sig, msg, keys.publicKey);

SLH-DSA-128s (SPHINCS+) — conservative hash-based signatures

import { slh_dsa_sha2_128s } from '@noble/post-quantum/slh-dsa.js';

const keys = slh_dsa_sha2_128s.keygen(seed);
const sig = slh_dsa_sha2_128s.sign(msg, keys.secretKey);
const isValid = slh_dsa_sha2_128s.verify(sig, msg, keys.publicKey);

ML-KEM-768 (Kyber) — for PQ encryption key agreement

import { ml_kem768 } from '@noble/post-quantum/ml-kem.js';

// Alice generates keys
const aliceKeys = ml_kem768.keygen(seed);

// Bob encapsulates a shared secret for Alice
const { cipherText, sharedSecret: bobShared } = ml_kem768.encapsulate(aliceKeys.publicKey);

// Alice decapsulates
const aliceShared = ml_kem768.decapsulate(cipherText, aliceKeys.secretKey);
// aliceShared === bobShared

X-Wing (Hybrid KEM — ML-KEM-768 + X25519)

import { XWing } from '@noble/post-quantum/hybrid.js';

const keys = XWing.keygen();
const { cipherText, sharedSecret } = XWing.encapsulate(keys.publicKey);
const decapSecret = XWing.decapsulate(cipherText, keys.secretKey);

Speed Benchmarks (Apple M4, operations/sec)

Primitive Keygen Signing Verification Shared secret
ML-KEM-768 4661 — — 4089
ML-DSA-65 669 271 565 —
Falcon-512 14 749 2160 —
SLH-DSA-SHA2-192f 235 8 159 —
SLH-DSA-SHA2-128s ~5 <1 ~1000 —
x/ed25519 (pre-quantum) 12648 6157 1255 1981

Key takeaways for our use case:

  • ML-DSA-65 signing: ~3.7ms per signature — fast enough for a one-time key-link event
  • ML-KEM-768 keygen + encapsulate: ~0.4ms — instant
  • SLH-DSA is slow (117ms-2900ms per signature depending on variant) but it's a one-time cost for the key-link event
  • There's an experimental WASM branch with 80% faster ML-KEM, 30% faster ML-DSA, 15x faster SLH-DSA-SHAKE

Security Notes

  • "The library has not been independently audited yet" (self-audited at v0.6.1)
  • No constant-time guarantees (side-channel attacks possible) — acceptable for our use case since PQ private keys are used in the browser, not on a server
  • Uses crypto.getRandomValues for randomness (browser CSPRNG)

Alternative Packages

@oqs/liboqs-js — WASM bindings to liboqs

Pros:

  • Wraps liboqs (the reference implementation from Open Quantum Safe project)
  • Supports the widest range of algorithms: ML-KEM, ML-DSA, Falcon, Classic McEliece, FrodoKEM, NTRU, HQC, CROSS, Mayo, UOV, SNOVA
  • WASM performance may be better for some algorithms

Cons:

  • Very new (created Feb 2026, only 11 stars)
  • WASM adds complexity (loading .wasm files, larger bundle)
  • Less established than noble
  • License is "NOASSERTION" (liboqs has its own license, not standard MIT)

When to use: If we need algorithms not in noble (e.g., Classic McEliece, FrodoKEM), or if WASM performance is critical.

mlkem (crystals-kyber-js)

Pros:

  • Focused, well-maintained ML-KEM implementation
  • Pure TypeScript, no dependencies
  • Used by @hpke/hybridkem-x-wing for HPKE integration
  • "The fastest pure TypeScript ML-KEM implementation"

Cons:

  • Only ML-KEM (no signature algorithms)
  • Would need a separate package for ML-DSA/SLH-DSA

When to use: If we only need ML-KEM for encryption and want a focused, tested library.

@hpke/hybridkem-x-wing

HPKE (Hybrid Public Key Encryption) module for X-Wing (ML-KEM-768 + X25519 hybrid KEM). Part of the hpke-js family. Useful if we want to use the HPKE framework for PQ encryption, which is the IETF standard for hybrid key exchange.

dilithium-crystals-js

Dilithium implementation for Node.js and browsers. Older, less maintained than noble.

@stenvault/pqc-wasm

ML-KEM-768 + ML-DSA-65 WASM wrapper (RustCrypto compiled to WebAssembly). New, untested.

pqclean

Node.js bindings for PQClean (all PQ implementations). Node-only, not browser-compatible.


What We Need for the PQ Nostr Web App

Need Algorithm Package Status
PQ signature (primary) ML-DSA-65 @noble/post-quantum ✅ Ready
PQ signature (conservative) SLH-DSA-128s @noble/post-quantum ✅ Ready
PQ encryption key agreement ML-KEM-768 @noble/post-quantum ✅ Ready
Hybrid KEM (PQ + classical) X-Wing (ML-KEM-768 + X25519) @noble/post-quantum ✅ Ready
Deterministic keygen from seed All above @noble/post-quantum ✅ keygen(seed) supported
Browser compatibility All above @noble/post-quantum ✅ Pure JS, no WASM needed
BIP39 seed phrase Mnemonic → seed @scure/bip39 or similar ✅ Separate package needed
{
  "dependencies": {
    "@noble/post-quantum": "^0.6.1",
    "@noble/curves": "^2.2.0",
    "@noble/hashes": "^2.2.0",
    "@scure/bip39": "^1.5.0",
    "@scure/bip32": "^1.6.0",
    "nostr-tools": "^2.10.0"
  }
}
  • @noble/post-quantum — PQ algorithms (ML-DSA, SLH-DSA, ML-KEM, X-Wing)
  • @noble/curves — secp256k1 for NIP-01 signing and NIP-06 key derivation
  • @noble/hashes — SHA-256, HKDF, etc.
  • @scure/bip39 — BIP39 mnemonic generation and validation
  • @scure/bip32 — BIP32 HD wallet derivation (for NIP-06)
  • nostr-tools — Nostr event creation, relay communication, NIP-46

All of these are from the same trust ecosystem (noble / scure family by Paul Miller and Alex Obukhov).


PQ Key Derivation from BIP39 Seed

One critical detail: @noble/post-quantum's keygen(seed) functions accept a seed parameter for deterministic key generation. The seed length varies by algorithm:

Algorithm Seed length
ML-KEM-768 64 bytes
ML-DSA-65 32 bytes
SLH-DSA-128s 32 bytes (or 64 for some variants)
Falcon-512 48 bytes

Our derivation scheme from BIP39 seed:

import { hkdf } from '@noble/hashes/hkdf';
import { sha512 } from '@noble/hashes/sha512';

// BIP39 seed (64 bytes from PBKDF2-HMAC-SHA512)
const bip39Seed = ...; // 64 bytes

// Derive PQ key seeds using HKDF with algorithm-specific labels
const mlDsaSeed  = hkdf(sha512, bip39Seed, undefined, 'nostr-pq-ml-dsa-65',  32);
const slhDsaSeed = hkdf(sha512, bip39Seed, undefined, 'nostr-pq-slh-dsa-128s', 32);
const mlKemSeed  = hkdf(sha512, bip39Seed, undefined, 'nostr-pq-ml-kem-768',  64);

// Generate PQ keypairs deterministically
const mlDsaKeys  = ml_dsa65.keygen(mlDsaSeed);
const slhDsaKeys = slh_dsa_sha2_128s.keygen(slhDsaSeed);
const mlKemKeys  = ml_kem768.keygen(mlKemSeed);

This means: same seed phrase → same PQ keys, every time. The user can recover their PQ keys by entering their seed phrase in any implementation of this scheme.


Summary: No Custom Implementations Needed

Question Answer
Do we need to write PQ crypto implementations? No — @noble/post-quantum covers all algorithms
Do we need WASM? No — pure JS is fast enough for our one-time key-link event
Are the packages maintained? Yes — noble-post-quantum updated June 2026, 335 stars
Are they audited? Self-audited at v0.6.1. No independent audit yet.
Do they support deterministic keygen from seed? Yes — keygen(seed) for all algorithms
Do they work in browsers? Yes — pure JS, uses crypto.getRandomValues
What's the bundle size? 16KB gzipped for all algorithms (tree-shakeable)
What do we need to write ourselves? The NIP-QR event builder, key derivation scheme (HKDF labels), relay publishing, OTS client, UI