
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
ML-DSA implementation using WebAssembly in a single JS file based on mldsa-native
ML-DSA, a post-quantum digital signature algorithm in WebAssembly.
This package provides a WebAssembly-based implementation of ML-DSA, based on mldsa-native. It exposes a modern, WebCrypto-compatible API for key generation, signing, and verification, all bundled in a single JavaScript file with the WASM module inlined.
mldsa with crypto.subtle and burn this package).dist/mldsa.js ES module (no external .wasm files needed).Use it as a stopgap solution until the WebCrypto API supports ML-DSA natively.
Demo: https://dchest.github.io/mldsa-wasm/
[!CAUTION] Beta version. CONTAINS CRYPTOGRAPHY! Use at your own risk.
CryptoKey returned by this module's generateKey and importKey has the same prototype as WebCrypto's CryptoKey, but cannot be cloned with structuredClone, so you cannot, for example, save them to IndexedDB, pass them to a worker, or use wrapKey on them, without exporting. You can only use them with this library's methods. Cloning is deliberately disabled to prevent compatibility issues with the future WebCrypto API (e.g., you saved an mldsa-wasm key to IndexedDB, and then switched to the native WebCrypto API, which has its own internal key format and cannot deserialize it).async to be compatible and to be able to load the WASM module without a separate initialization call), are done synchronously, instead of being fully asynchronous like in the WebCrypto API. You may consider it an improvement.pkcs8 import only supports the seed format of private keys (as nature intended).npm install mldsa-wasm
import mldsa from "mldsa-wasm";
// Generate key pair
const keyPair = await mldsa.generateKey({ name: "ML-DSA-65" }, true, [
"sign",
"verify",
]);
const { publicKey, privateKey } = keyPair;
// Sign a message
const message = new TextEncoder().encode("Hello, world!");
const signature = await mldsa.sign({ name: "ML-DSA-65" }, privateKey, message);
// Verify a signature
const isValid = await mldsa.verify(
{ name: "ML-DSA-65" },
publicKey,
signature,
message
);
console.log("Signature is valid:", isValid); // true
// Sign a message with context (maximum 255 bytes)
const context = new TextEncoder().encode("MyApp v1.0");
const signatureWithContext = await mldsa.sign(
{ name: "ML-DSA-65", context },
privateKey,
message
);
// Verify a signature with context
const isValidWithContext = await mldsa.verify(
{ name: "ML-DSA-65", context },
publicKey,
signatureWithContext,
message
);
You can export and import ML-DSA keys in several formats. Here are some examples:
// Export public key as raw bytes
const rawPublicKey = await mldsa.exportKey("raw-public", publicKey);
// rawPublicKey is an ArrayBuffer
// Export private key as a seed
const rawSeed = await mldsa.exportKey("raw-seed", privateKey);
// rawSeed is an ArrayBuffer
// Export public key as JWK
const jwkPublic = await mldsa.exportKey("jwk", publicKey);
// jwkPublic is a JsonWebKey object
// Import a public key from raw bytes
const importedPublicKey = await mldsa.importKey(
"raw-public",
rawPublicKey,
{ name: "ML-DSA-65" },
true, // extractable
["verify"]
);
// Import a private key from seed
const importedPrivateKey = await mldsa.importKey(
"raw-seed",
rawSeed,
{ name: "ML-DSA-65" },
false, // not extractable
["sign"]
);
// Import a public key from JWK
const importedJwkPublicKey = await mldsa.importKey(
"jwk",
jwkPublic,
{ name: "ML-DSA-65" },
false,
["verify"]
);
SPKI and PKCS8 formats are also supported.
All API methods are asynchronous and return Promises. See Modern Algorithms in the Web Cryptography API for details.
Types are given for ML-DSA-65, but the same methods work for ML-DSA-44 and ML-DSA-87:
mldsa.generateKey(algorithm, extractable, usages){ name: "ML-DSA-65" } or "ML-DSA-65"boolean (for private key)"sign", "verify"{ publicKey, privateKey } (both are CryptoKey)mldsa.exportKey(format, key)"raw-public", "raw-seed", "jwk", "pkcs8" or "spki"CryptoKeyArrayBuffer or JsonWebKeymldsa.importKey(format, keyData, algorithm, extractable, usages)"raw-public", "raw-seed", "jwk", "pkcs8" or "spki"ArrayBuffer, typed array, or JsonWebKey{ name: "ML-DSA-65" } or "ML-DSA-65"booleanCryptoKeymldsa.sign(algorithm, key, data){ name: "ML-DSA-65", context? } or "ML-DSA-65"CryptoKeyArrayBuffer or typed array (data to sign)ArrayBuffer (signature)mldsa.verify(algorithm, key, signature, data){ name: "ML-DSA-65", context? } or "ML-DSA-65"CryptoKeyArrayBuffer or typed arrayArrayBuffer or typed array (original data)boolean (true if signature is valid)mldsa.getPublicKey(key, usages)CryptoKey"verify")CryptoKeymldsa._isSupportedCryptoKey(key)Non-spec method to check if a CryptoKey was created by this library.
You can use it to distinguish WebCrypto's native keys from mldsa-wasm keys.
CryptoKeybooleanCryptoKey: Internal key object, not compatible with WebCrypto's CryptoKey."sign", "verify""raw-public", "raw-seed", "jwk", "pkcs8", "spki"Once the WebCrypto API supports ML-DSA natively (assuming the draft ships as-is), just switch mldsa to crypto.subtle and use the native API directly.
Since the WebCrypto API draft is still evolving, this library may need updates to keep up with changes in the spec. The updates are not guaranteed (but I will try to keep up), and they may break compatibility with previous versions.
npm install to install dev dependencies (esbuild, typescript, and vitest).src/mldsa-native/.git submodule update --init --recursive
npm run build
src/build/wasm-module.js (WASM inlined).src/build/wasm-module.js and src/mldsa.ts using esbuild, resulting in dist/mldsa.js.types/mldsa.d.ts by running tsc.dist/mldsa.js.types/mldsa.d.ts.Fupply fain fufurity. The whole WASM module is a scary-looking opaque encoded blob, compiled by me from the code I got from GitHub (apparently used by AWS' Cryptography library and other popular projects), npm-installed by you from the internets. I made this library for my project and happily share it with you.
Nobody checks every line of code they npm install, instead they like to check checkboxes.
Here are some checkboxes:
mldsa-native is included as a git submodule instead of importing it directly into the source.mldsa-native code.package.json.If your company wants to pay to get some other checkboxes from me, please contact me directly.
FAQs
ML-DSA implementation using WebAssembly in a single JS file based on mldsa-native
We found that mldsa-wasm demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.