TLS already moved. A stock Node.js client on current releases negotiates the hybrid post-quantum key exchange X25519MLKEM768 with no options set, and Chrome, Edge and Firefox do the same. What your application signs, issues and stores has not moved. Documents, logins, webhooks, API tokens, file envelopes and the records a firm expects to open in 2035 are still protected by keys that a sufficiently large quantum computer could break. For the policy case and the deadlines in full, read the Live Trading News article. This post shows how to move that layer with kxco-post-quantum, KXCO's free Apache-2.0 library, what the evidence for it shows, and where the free line ends.
The dates are written down
Executive Order 14412, signed on 22 June 2026, moves federal high value assets and high impact systems to post-quantum key establishment by 31 December 2030. It moves the same systems to post-quantum signatures by 31 December 2031. OMB M-26-15, issued two days later, is specific about the application layer. It says "API gateways and application workloads must be configured to issue and validate PQC-signed tokens", and "Secure software development practices must mandate the use of PQC-agile libraries for all new applications". The UK's National Cyber Security Centre has set 2028, 2031 and 2035 as its milestones.
Transport and application fail on different clocks. A recorded TLS session can be broken later if its key exchange was classical, which is why the hybrid groups shipped in TLS first. A signature is checked at the moment of use. A document signed in 2026 with a classical key has to stay unforgeable in 2032, when a regulator, a counterparty or a court asks whether it still means what it meant. Key establishment therefore comes first, at the end of 2030, and signatures follow at the end of 2031.
What the library ships
NIST published three post-quantum standards in August 2024: FIPS 203 (ML-KEM), FIPS 204 (ML-DSA) and FIPS 205 (SLH-DSA). kxco-post-quantum ships all three:
- ML-DSA-87 for every new signing key, and ML-DSA-65 so keys issued before the change keep working;
- ML-KEM-768, the parameter set inside the hybrid TLS group, and ML-KEM-1024;
- SLH-DSA-SHA2-192s for hash-based signatures.
ML-DSA-87 and ML-KEM-1024 are the sets the NSA's CNSA 2.0 FAQ names "for all classification levels". They share one API with the category 3 sets, so moving between them is a change of import, not a change of architecture.
The library does not reimplement the mathematics. On Node 24 and later it runs in OpenSSL 3.5. On Node 22 and in browsers it runs in a JavaScript implementation. Both produce identical bytes on the wire, so a signature made on one backend verifies on the other. The current release, 1.10.0, requires Node.js 22.12 or later and is ESM-only.
Install and sign
npm install kxco-post-quantumKeys are derived from a master secret by label, so you can name and rotate keys without a second key store. A context string binds a signature to its purpose, so a signature made for a document does not verify as a login.
import { randomBytes } from 'node:crypto'
import { mlDsa87, fingerprint, kidEquals } from 'kxco-post-quantum'
// In production the master secret comes from your KMS or HSM, never from code.
const master = randomBytes(32)
const { publicKey, secretKey } = mlDsa87.keypairFromMaster(master, 'docs-signing-87-v1')
const sig = mlDsa87.sign(secretKey, 'invoice 2026-0412', { context: 'kxco-docs-v1' })
console.log(mlDsa87.verify(publicKey, 'invoice 2026-0412', sig, { context: 'kxco-docs-v1' })) // true
console.log(mlDsa87.verify(publicKey, 'invoice 2026-0412', sig, { context: 'kxco-login-v1' })) // false
const kid = fingerprint(publicKey)
console.log(kidEquals(kid, fingerprint(publicKey))) // true, compared in constant time
Tokens at the gateway
M-26-15 asks gateways and workloads to issue and validate post-quantum signed tokens. The jws module writes compact JSON Web Signatures under the algorithm names ML-DSA-87 and ML-DSA-65, so the token travels the same path your gateway, identity provider and partner verifier already parse. The verifier resolves the algorithm from an allowlist, never from the token, and fails closed.
import { randomBytes } from 'node:crypto'
import { mlDsa87, fingerprint } from 'kxco-post-quantum'
import { signJws, verifyJws, decodeJwsHeader } from 'kxco-post-quantum/jws'
const key = mlDsa87.keypairFromMaster(randomBytes(32), 'api-tokens-87-v1')
const kid = fingerprint(key.publicKey)
const token = signJws({ sub: 'client-42', scope: 'payments:read' }, key.secretKey, { kid })
console.log(decodeJwsHeader(token).alg) // ML-DSA-87, because the key decides the set
const result = verifyJws(token, key.publicKey, { alg: 'ML-DSA-87', kid })
console.log(result.valid) // true
An existing ML-DSA-65 key still signs as ML-DSA-65, because the key's size selects the set, and a token signed by the previous release verifies unchanged. New keys take ML-DSA-87 with no option set.
Webhooks a merchant can prove
A webhook signed only with HMAC proves that someone holding the shared secret sent it, and the platform holds that secret too. The hybrid pattern adds an ML-DSA signature over the same bytes, so a receiver can prove which platform sent each delivery.
import { randomBytes } from 'node:crypto'
import { mlDsa87, fingerprint, webhook } from 'kxco-post-quantum'
const platform = mlDsa87.keypairFromMaster(randomBytes(32), 'webhooks-87-v1')
const kid = fingerprint(platform.publicKey)
const hmacSecret = randomBytes(32)
const rawBody = JSON.stringify({ event: 'payment.settled', id: 'pay_1001' })
// Sender
const headers = webhook.signDelivery({ rawBody, hmacSecret, pqSecretKey: platform.secretKey, pqKid: kid })
// Receiver: header names in lower case, and the body exactly as received
const received = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]))
const r = webhook.verifyDelivery({ headers: received, rawBody, hmacSecret, pqPublicKey: platform.publicKey, pinnedKid: kid })
console.log(r.hmacOk && r.pqOk && r.timestampOk && r.kidOk) // true
For Express, Fastify, Hono, Cloudflare Workers and Vercel adapters, install kxco-post-quantum-webhook.
Keys at rest, in seed form
An ML-DSA secret key is several kilobytes. The seed it is grown from is 32 bytes. Store the seed and expand it on use. The package writes AKP JSON Web Keys and PKCS#8 in the seed form, and reading a key back gives the same public key.
import { randomBytes } from 'node:crypto'
import { mlDsa87, seed, fingerprint } from 'kxco-post-quantum'
const key = mlDsa87.keypairFromMaster(randomBytes(32), 'records-87-v1')
console.log(key.seed.length) // 32
const jwk = seed.exportJwk('ML-DSA-87', key, { kid: fingerprint(key.publicKey) })
console.log(jwk.kty, jwk.alg) // AKP ML-DSA-87
const der = seed.exportSeedPkcs8('ML-DSA-87', key.seed)
const back = seed.keypairFromSeed('ML-DSA-87', seed.importSeedPkcs8(der).seed)
console.log(Buffer.compare(back.publicKey, key.publicKey) === 0) // true
Key establishment for data that must stay private
Executive Order 14412 puts key establishment first, at the end of 2030. ML-KEM gives two parties a shared secret that a later quantum computer cannot recover from a recording. Use the shared secret to key AES-256-GCM, or install kxco-pq-vault to encrypt files to one recipient or many.
import { randomBytes } from 'node:crypto'
import { mlKem1024 } from 'kxco-post-quantum'
const recipient = mlKem1024.keypairFromMaster(randomBytes(32), 'records-kem-1024-v1')
const { ciphertext, sharedSecret } = mlKem1024.encapsulate(recipient.publicKey)
const opened = mlKem1024.decapsulate(ciphertext, recipient.secretKey)
console.log(Buffer.compare(sharedSecret, opened) === 0) // true
Require the native backend, or pin one
The library picks its implementation at import. Where a control says the cryptography must run inside a named module, make that a requirement rather than a hope. backend() reports which implementation is doing the maths, and requireNativeBackend throws ERR_KXCO_PQ_BACKEND on the JavaScript path. Operators can enforce the same rule without touching application code by setting KXCO_PQ_REQUIRE_NATIVE=1.
import { backend, requireNativeBackend } from 'kxco-post-quantum'
console.log(backend().kind) // openssl on Node 24 and later
requireNativeBackend(['ML-DSA-87', 'ML-KEM-1024']) // throws on the JavaScript backend
A bill of materials in one command
Executive Order 14412 gives CISA 270 days, to 19 March 2027, to publish the minimum elements of a cryptographic bill of materials. You do not have to wait to start one. kxco-pq-scan reads your lockfile and writes a CycloneDX 1.6 CBOM:
npx kxco-pq-scan --cbom > cbom.jsonAdd eslint-plugin-kxco-pq at the same time. It fails the build when code reaches past the wrapper to a raw primitive, so a later change cannot quietly drop the algorithm choice.
The evidence, and what it is not
The package has been run against 2,103 of NIST's ACVP test vectors, according to its conformance file. 1,793 passed and none failed. The other 310 are pre-hash pairings the library refuses because the hash is weaker than the parameter set, such as SHA2-256 with ML-DSA-87, and each refusal is recorded with its reason. The vectors are pinned by digest to a commit of NIST's ACVP server, so the corpus cannot drift under a release.
It is also cross-checked in both directions against liboqs in C, Bouncy Castle in Java and the Python reference implementations. 225 checks passed and none failed, on both backends. CI subsamples the slow sets on every push, runs the full suite weekly, and records which run it was.
Every release since 1.4.1 carries SLSA provenance and a CycloneDX SBOM. Each release also carries a CycloneDX 1.6 CBOM, and the test suite fails if the source uses an algorithm the CBOM does not declare. Dependencies are pinned to exact versions, and a bump of the primitive has to regenerate the conformance and interoperability evidence clean. The OpenSSF Scorecard stands at 9.2 out of 10, from its scan of 9 October 2026.
This is algorithm-level conformance against NIST's published vectors, run by the project and reproducible by anyone with the repository. It is not a CAVP certificate or a FIPS 140-3 validation, and none is claimed. Using the CNSA 2.0 parameter sets does not by itself make a deployment compliant with CNSA 2.0, because compliance is a property of the deployment.
What is free, and what KXCO operates
The licence covers the code. Using it commercially, forking it and shipping it inside a product are free. Signing and verification work offline, with no licence check and nothing that phones home. A signature issued today verifies in ten years without a KXCO server, a network or a licence. To check a signature without the signer's stack, a counterparty can use kxco-verify, a separate browser verifier that does not import the signing library.
A signature proves that a key signed. It does not prove the key is still trusted now, that it has not been revoked, or that it has not been rotated since. That answer needs someone to run a registry and stand behind it. KXCO sells that operated part:
- a hosted key registry that answers whether a key is active, revoked or rotated;
- live revocation, a verification mode that checks the registry at verify time and fails closed;
- a meta-transaction relay that validates your signed intent, pays the fee and submits it, so you hold no token and run no node;
- on-chain anchoring as an operated service;
- support with an availability commitment and a named contact.
The README states the terms: "Priced in USD, per seat, per year. No tokens, no nodes and no wallets." The KXCO name and its marks are not covered by Apache-2.0. Describing your product as "built on kxco-post-quantum" is fine.
Where to start
- Install kxco-post-quantum and sign one thing you already sign today: a webhook, a token or a document.
- Add the lint rule and the scan, so the algorithm choice and the bill of materials are enforced in CI.
- Move stored data that must stay confidential past 2030 behind ML-KEM.
- Decide which keys need an answer about the present, and contact KXCO when you want the hosted registry, live revocation, the relay or support. Commercial terms are with [email protected].
Frequently asked questions
What does TLS already protect, and what does it leave out?
Current Node.js releases, Chrome, Edge and Firefox negotiate the hybrid X25519MLKEM768 key exchange by default, which protects traffic recorded today against later decryption. TLS does not cover what the application signs, issues and stores: documents, logins, webhooks, API tokens, file envelopes and long-lived records.
When must US federal systems move to post-quantum cryptography?
Executive Order 14412 of 22 June 2026 sets 31 December 2030 for post-quantum key establishment and 31 December 2031 for post-quantum signatures in high value assets and high impact systems. OMB M-26-15 requires PQC-agile libraries for all new applications and post-quantum signed tokens at API gateways.
Which algorithms are in kxco-post-quantum?
It implements ML-DSA-87 and ML-DSA-65 from FIPS 204, ML-KEM-768 and ML-KEM-1024 from FIPS 203, and SLH-DSA-SHA2-192s from FIPS 205. ML-DSA-87 is the default for new signing keys, and ML-DSA-87 with ML-KEM-1024 are the sets CNSA 2.0 names.
How do you sign a JSON Web Token with ML-DSA-87 in Node.js?
Install kxco-post-quantum, derive an ML-DSA-87 key and call signJws from kxco-post-quantum/jws. The token header carries the algorithm name ML-DSA-87, and verifyJws checks it against a pinned algorithm and key identifier and fails closed.
Is kxco-post-quantum FIPS 140-3 validated?
No. According to its conformance file, it has been run against 2,103 of NIST's ACVP test vectors with 1,793 passed, none failed and 310 weaker pairings refused, and cross-checked against liboqs, Bouncy Castle and the Python reference implementations. That is algorithm-level conformance, not a CAVP certificate or a FIPS 140-3 validation.
What does KXCO charge for?
The library is free under Apache-2.0, and signing and verification work offline. KXCO charges for the operated services: a hosted key registry, live revocation, a meta-transaction relay, on-chain anchoring and support, priced in US dollars per seat per year, with no token, node or wallet.