kxco-post-quantum 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AUDIT.md ADDED
@@ -0,0 +1,110 @@
1
+ # Audit Posture
2
+
3
+ **Status as of v1.0.1 release (2026-05-21).** Self-attested. No third-party audit of this wrapper library has been performed yet. This document exists to make our posture **legible to reviewers** so the right questions get asked of the right party.
4
+
5
+ If you are doing institutional due diligence, read this end-to-end before the README.
6
+
7
+ ---
8
+
9
+ ## 1. What has been audited (upstream)
10
+
11
+ **`@noble/post-quantum@0.2.1`** — the underlying NIST primitives we wrap — has been independently audited.
12
+
13
+ | Auditor | Year | Scope | Report |
14
+ |---|---|---|---|
15
+ | **Cure53** | 2024 | `@noble/post-quantum` cryptographic primitives | https://github.com/paulmillr/noble-post-quantum#security |
16
+
17
+ This audit covers the actual cryptographic operations: ML-DSA-65 sign/verify, ML-KEM-768 keygen/encapsulate/decapsulate, the constant-time properties, the test vector compliance with NIST's reference outputs.
18
+
19
+ When you `npm install kxco-post-quantum`, the audited code is what runs the math. This wrapper does not reimplement the primitives.
20
+
21
+ The exact upstream we pin:
22
+
23
+ ```
24
+ @noble/post-quantum@0.2.1
25
+ integrity: sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==
26
+ ```
27
+
28
+ ## 2. What has NOT been audited (this wrapper)
29
+
30
+ The integration patterns in this package — `pqSigner` derivation, kid fingerprinting, webhook envelope construction, hybrid HMAC + ML-DSA signing, timestamp replay enforcement — have not been independently audited.
31
+
32
+ What we have done:
33
+
34
+ - **Internal review** by the KXCO engineering team and KXCO Cybersecurity (lead: Sean O'Coiligh, 30+ years cybersecurity, formerly led Offensive Cyber at the DTCC Cyber Threat Fusion Center)
35
+ - **Reproducible test vectors** in `test/vectors.json` covering every primitive — anyone running `npm test` gets the same outputs the maintainers see
36
+ - **Production deployment** across the KXCO platform (KnightsVault, KXCO Bank, KnightsBot, The Exchequer, Armature L1) since November 2025
37
+ - **Public verifiable proof** of the signing identity at https://chain.kxco.ai/wallet/api/.well-known/kxco-pq-pubkey — anyone can fetch the kid, install this library, and verify signatures from the production fleet
38
+
39
+ What we have **not** done:
40
+
41
+ - ❌ Engaged a third-party auditor for this wrapper
42
+ - ❌ Held a public security review window with bug bounty
43
+ - ❌ Obtained CMVP FIPS 140-3 module certification
44
+ - ❌ Submitted to ENISA / NCSC / BSI evaluation schemes
45
+
46
+ ## 3. Audit roadmap
47
+
48
+ | Milestone | Target | Owner |
49
+ |---|---|---|
50
+ | Engage external auditor for wrapper integration patterns | Q3 2026 | KXCO Engineering |
51
+ | Public bug bounty programme | Q4 2026 | KXCO Security |
52
+ | Apply for FIPS 140-3 CMVP validation of a cryptographic module deployment using this library + an HSM | 2027 | KXCO Compliance |
53
+ | NIST PQC Workshop presentation (production lessons) | When workshop opens for 2026/27 | Shayne Heffernan + Sean O'Coiligh |
54
+
55
+ The exact dates depend on engineering and budget capacity. The order is committed.
56
+
57
+ ## 4. Reproducibility checks (run these yourself)
58
+
59
+ You do not have to trust us. Run these to verify:
60
+
61
+ ```bash
62
+ # Clone and install
63
+ git clone https://github.com/JackKXCO/kxco-post-quantum
64
+ npm install
65
+
66
+ # Run the full test suite — primitives + vectors
67
+ npm test
68
+
69
+ # Run vector verification only
70
+ npm run test:vectors
71
+
72
+ # Fetch the live production platform key and verify offline
73
+ curl https://chain.kxco.ai/wallet/api/.well-known/kxco-pq-pubkey
74
+ ```
75
+
76
+ Expected: `npm test` reports `✓ All 29 checks pass — library output matches pinned vectors bit-for-bit.`
77
+
78
+ ## 5. Threat model summary
79
+
80
+ See [SECURITY.md](./SECURITY.md) for the full threat model. In short:
81
+
82
+ - **In scope:** quantum signature non-repudiation, quantum-safe KEM, webhook forgery resistance, replay rejection, body tamper detection, wrong-key rejection.
83
+ - **Out of scope:** master secret storage (use KMS/HSM), TLS termination (use OpenSSL 3.5+), receiving raw bodies byte-for-byte (use `express.raw` or equivalent), key rotation procedures (caller's responsibility).
84
+
85
+ ## 6. Bug-finding signals
86
+
87
+ If you are evaluating this library, look at:
88
+
89
+ - **Test coverage:** 9 functional tests + 29 vector checks = 38 distinct assertions covering every export
90
+ - **Code size:** ~280 source lines across 6 modules — small enough to review end-to-end in an afternoon
91
+ - **Dependency surface:** one runtime dependency (`@noble/post-quantum`), itself audited
92
+ - **Determinism:** every output is reproducible from inputs — no hidden state, no globals beyond a lazy cache, no network
93
+ - **API stability:** v1.0 commits to the public surface listed in CHANGELOG.md
94
+
95
+ ## 7. Reviewer checklist
96
+
97
+ For institutional reviewers, the smallest version of "did they actually do the work":
98
+
99
+ - [ ] `npm view kxco-post-quantum dist.signatures` returns a signed package
100
+ - [ ] `npm test` passes after fresh clone + install
101
+ - [ ] `npm run test:vectors` matches the pinned vectors
102
+ - [ ] `curl https://chain.kxco.ai/wallet/api/.well-known/kxco-pq-pubkey` returns a valid ML-DSA-65 public key
103
+ - [ ] The kid (`kid` field above) matches `fingerprint()` of the returned `publicKey` field
104
+ - [ ] An outbound webhook from `chain.kxco.ai` verifies with `webhook.verifyDelivery` against the pinned kid
105
+
106
+ All six are reproducible without any cooperation from KXCO. That's the standard we hold ourselves to.
107
+
108
+ ---
109
+
110
+ **Contact:** security@kxco.ai for vulnerability reports. audit@kxco.ai for due-diligence and review requests.
package/CHANGELOG.md ADDED
@@ -0,0 +1,82 @@
1
+ # Changelog
2
+
3
+ All notable changes to `kxco-post-quantum` are documented here. This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ## [1.0.2] — 2026-05-21
6
+
7
+ ### Changed
8
+ - Repository URL on the npm package now points to `github.com/JackKXCO/kxco-post-quantum`. No code change.
9
+
10
+ ## [1.0.1] — 2026-05-21
11
+
12
+ Substance additions to the v1.0 release. No API changes. Same code on the cryptographic path; auditability and reproducibility infrastructure expanded.
13
+
14
+ ### Added
15
+ - `AUDIT.md` — self-attested audit posture, including explicit roadmap for third-party review (Q3 2026), bug bounty (Q4 2026), and FIPS 140-3 CMVP application (2027)
16
+ - `test/vectors.json` — 29 deterministic test vectors pinning every primitive's output bit-for-bit
17
+ - `test/run-vectors.js` — runner that verifies the library's output against `vectors.json` (exit code 0 = match)
18
+ - `test/generate-vectors.js` — vector generator (run only when the library's output behaviour changes)
19
+ - `npm test` — now runs both functional tests and vector verification
20
+ - `npm run test:vectors` — vector check only
21
+ - Production deployment proof: the wallet at `chain.kxco.ai` now imports `kxco-post-quantum` directly via npm. Real-world download count starts here.
22
+
23
+ ### Changed
24
+ - `SECURITY.md` — sharpened with explicit threat model, in-scope vs out-of-scope responsibilities, pinned `@noble/post-quantum@0.2.1` integrity hash, Cure53 audit reference, FIPS posture clarification, replay-window and side-channel limitations
25
+ - `README.md` — added "Used in production at" section with file refs to the live deployment
26
+
27
+ ### Why a patch and not a feature release
28
+ No API surface changes. Same exports, same signatures, same behaviour. The diff is documentation, vectors, and infrastructure that lets reviewers verify the library without trusting the publisher.
29
+
30
+ ## [1.0.0] — 2026-05-21
31
+
32
+ First stable release. The public API below is committed to: future minor and patch releases will not break it without a major version bump.
33
+
34
+ ### Committed public API
35
+
36
+ ```js
37
+ import {
38
+ mlDsa, // namespace
39
+ mlKem, // namespace
40
+ deriveSeed,
41
+ fingerprint,
42
+ kidEquals,
43
+ webhook, // namespace
44
+ } from 'kxco-post-quantum'
45
+ ```
46
+
47
+ | Export | Signature | Notes |
48
+ |---|---|---|
49
+ | `mlDsa.keypairFromMaster(master, info='ml-dsa-65-v1')` | → `{ publicKey: Buffer, secretKey: Buffer }` | Deterministic. Stable across versions. |
50
+ | `mlDsa.sign(secretKey, message)` | → hex string (6618 chars) | `message` accepts Buffer or string |
51
+ | `mlDsa.verify(publicKey, message, sigHex)` | → boolean | Catches exceptions, returns false |
52
+ | `mlDsa.ml_dsa65` | re-export | Raw `@noble/post-quantum` primitive |
53
+ | `mlKem.keypairFromMaster(master, info='ml-kem-768-v1')` | → `{ publicKey, secretKey }` | Deterministic |
54
+ | `mlKem.encapsulate(publicKey)` | → `{ ciphertext, sharedSecret, cipherText }` | `cipherText` (camelCase) alias for noble compatibility |
55
+ | `mlKem.decapsulate(ciphertext, secretKey)` | → Buffer (32 bytes) | |
56
+ | `mlKem.ml_kem768` | re-export | Raw primitive |
57
+ | `deriveSeed(master, info, length)` | → Buffer | HKDF-SHA-512 with empty salt; requires `master.length ≥ 16` |
58
+ | `fingerprint(publicKey)` | → 16-hex string | First 8 bytes of SHA-256(pubkey) |
59
+ | `kidEquals(a, b)` | → boolean | Constant-time string compare |
60
+ | `webhook.envelope(timestamp, rawBody)` | → Buffer | `timestamp + "." + rawBody` |
61
+ | `webhook.hmacHex(secret, timestamp, rawBody)` | → hex string | HMAC-SHA-256 over envelope |
62
+ | `webhook.verifyHmac(secret, timestamp, rawBody, sigHeader)` | → boolean | Constant-time. Accepts `sha256=` prefix. |
63
+ | `webhook.pqSign(secretKey, timestamp, rawBody)` | → `ml-dsa-65=<hex>` | Ready-to-send header value |
64
+ | `webhook.verifyPq(publicKey, timestamp, rawBody, sigHeader)` | → boolean | Accepts `ml-dsa-65=` prefix |
65
+ | `webhook.signDelivery({ rawBody, hmacSecret, pqSecretKey, pqKid, event?, deliveryId? })` | → HTTP header map | Full sender helper |
66
+ | `webhook.verifyDelivery({ headers, rawBody, hmacSecret?, pqPublicKey?, pinnedKid?, windowSeconds=300 })` | → `{ hmacOk, pqOk, timestampOk, kidOk }` | Receiver helper. Either signature can be omitted to skip that check. |
67
+
68
+ ### What "committed" means
69
+ We will not break any of the above signatures in a 1.x release. We will not change algorithm choices in a 1.x release. We will not change the envelope format `${timestamp}.${rawBody}` in a 1.x release. We may add new exports; existing ones won't move.
70
+
71
+ ### Verified at release
72
+ - 9/9 functional tests pass (sign/verify, encapsulate/decapsulate, hybrid delivery, replay rejection, tamper rejection)
73
+ - 29/29 test vectors pass
74
+ - Underlying primitives: `@noble/post-quantum@0.2.1` (audited by Cure53, 2024)
75
+ - Node.js 18+ ESM-only
76
+
77
+ ### Initial release notes
78
+ ESM-only. Node.js 18+. FIPS-aligned (implements FIPS 203/204 algorithms). Not CMVP-validated as a module. TLS guidance: use OpenSSL 3.5+ for `X25519MLKEM768` hybrid at the edge — this package does not handle TLS.
79
+
80
+ ## [0.1.0] — 2026-05-21
81
+
82
+ Initial pre-release. Same surface as 1.0.0; promoted to 1.0.0 after verifying clean install and round-trip on a fresh environment.
package/README.md CHANGED
@@ -9,6 +9,22 @@
9
9
 
10
10
  ---
11
11
 
12
+ ## Used in production at
13
+
14
+ This is not a sample library. It runs in production at KXCO across multiple products. You can verify this yourself without any cooperation from us:
15
+
16
+ ```bash
17
+ # Fetch the live platform PQ identity key from production
18
+ curl https://chain.kxco.ai/wallet/api/.well-known/kxco-pq-pubkey
19
+
20
+ # Returns a JSON document with alg=ML-DSA-65, the public key (3904 hex chars),
21
+ # and a kid fingerprint. The kid is fingerprint(publicKey) using this library.
22
+ ```
23
+
24
+ The wallet at `chain.kxco.ai` imports `kxco-post-quantum` directly from npm. The platform identity signing module ([src/lib/pqSigner.js in kxco-bank](https://chain.kxco.ai/wallet/dev-docs)) delegates to `mlDsa.keypairFromMaster`, `mlDsa.sign`, and `fingerprint` from this package. Every outbound webhook from the KXCO platform is signed using `webhook.signDelivery`. You can pin the kid returned above, install this library, and verify any webhook from the production fleet offline.
25
+
26
+ Other KXCO products on the same package: KnightsVault (institutional custody), KnightsBot (universal trading — every order signed), The Exchequer (compliance intelligence), Armature L1 (the permissioned chain underneath everything).
27
+
12
28
  ## What this is
13
29
 
14
30
  A higher-level package that wraps [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) — the audited, dependency-free TypeScript reference implementation of NIST's August 2024 post-quantum standards — with the integration patterns we run in production:
@@ -183,6 +199,31 @@ Constant-time string compare. Use this when comparing user-supplied kids.
183
199
  - **Enforce timestamp windows.** Defaults to 5 minutes. Set lower for higher-security paths.
184
200
  - **Constant-time compare strings.** Use `kidEquals` and `verifyHmac`, not `===`.
185
201
 
202
+ ## Reproducibility
203
+
204
+ Every public output is pinned in `test/vectors.json`. Run them:
205
+
206
+ ```bash
207
+ git clone https://github.com/JackKXCO/kxco-post-quantum
208
+ cd kxco-post-quantum
209
+ npm install
210
+ npm test # 9 functional tests + 29 vector checks
211
+ npm run test:vectors # vectors only
212
+ ```
213
+
214
+ Expected output: `✓ All 29 checks pass — library output matches pinned vectors bit-for-bit.`
215
+
216
+ If any check fails on a release version, file an issue. The vectors are the tripwire for cryptographic regressions.
217
+
218
+ ## Audit posture
219
+
220
+ See [AUDIT.md](./AUDIT.md) for the full statement. Short version:
221
+
222
+ - **Underlying primitives** (`@noble/post-quantum@0.2.1`) — audited by Cure53, 2024
223
+ - **This wrapper** — no third-party audit yet. Roadmap: external audit Q3 2026, public bug bounty Q4 2026, FIPS 140-3 CMVP application 2027
224
+ - **Internal review** — KXCO Engineering + Cybersecurity (lead: Sean O'Coiligh, ex-DTCC Offensive Cyber)
225
+ - **Production deployment** — live at chain.kxco.ai since 2025-11
226
+
186
227
  ## References
187
228
 
188
229
  - [NIST FIPS 204 — Module-Lattice-Based Digital Signature Standard](https://csrc.nist.gov/pubs/fips/204/final)
package/SECURITY.md CHANGED
@@ -1,29 +1,94 @@
1
1
  # Security Policy
2
2
 
3
- ## Reporting
3
+ ## Reporting a vulnerability
4
4
 
5
- Email `security@kxco.ai` with details. Do not open public issues for security reports.
5
+ Email **security@kxco.ai** with details. Do not open public GitHub issues for vulnerabilities.
6
6
 
7
- We acknowledge within 48 hours. Critical findings are triaged within 5 business days.
7
+ - Acknowledgement within 48 hours
8
+ - Initial triage of critical findings within 5 business days
9
+ - Coordinated disclosure: we patch first, credit the reporter, publish a CVE / GHSA
8
10
 
9
11
  ## Scope
10
12
 
11
- This package wraps `@noble/post-quantum`. Vulnerabilities in the underlying NIST primitives or `@noble/post-quantum` should be reported to that project upstream. This package's scope is the integration patterns: derivation, envelope construction, kid generation, hybrid signing, verification.
13
+ This package's surface is the **integration patterns**: deterministic key derivation, kid fingerprinting, envelope construction, hybrid HMAC + ML-DSA-65 webhook signing, replay-window enforcement. Vulnerabilities in the **underlying NIST primitives** belong upstream.
14
+
15
+ | Layer | Where it lives | Where to report |
16
+ |---|---|---|
17
+ | ML-DSA-65 / ML-KEM-768 algorithm bugs | `@noble/post-quantum` | https://github.com/paulmillr/noble-post-quantum/security |
18
+ | HKDF-SHA-512 / HMAC-SHA-256 / SHA-256 | Node.js `crypto` module | https://github.com/nodejs/node/security |
19
+ | Integration patterns (this library) | this repo | security@kxco.ai |
12
20
 
13
21
  ## Cryptographic posture
14
22
 
15
- - **Algorithms:** NIST FIPS 203 (ML-KEM-768), NIST FIPS 204 (ML-DSA-65). Both Security Category 3.
16
- - **FIPS module validation:** not held. Algorithms are FIPS-standardised; the module is not CMVP-certified.
17
- - **Side channels:** the underlying `@noble/post-quantum` aims for constant-time execution. This package adds constant-time string compares for kid and HMAC headers. Higher-assurance deployments should run cryptographic operations inside a FIPS 140-3 HSM.
18
- - **Randomness:** all operations use Node's `crypto` module CSPRNG.
23
+ ### Algorithms and standards
24
+
25
+ | Primitive | Standard | Security level |
26
+ |---|---|---|
27
+ | ML-DSA-65 (Dilithium3) | [NIST FIPS 204](https://csrc.nist.gov/pubs/fips/204/final) | Category 3 (≈ AES-192) |
28
+ | ML-KEM-768 (Kyber768) | [NIST FIPS 203](https://csrc.nist.gov/pubs/fips/203/final) | Category 3 (≈ AES-192) |
29
+ | HKDF-SHA-512 | [NIST SP 800-56C](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf), [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869) | n/a (KDF) |
30
+ | HMAC-SHA-256 | [FIPS 198-1](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf) | Post-quantum safe as MAC |
31
+ | SHA-256 (for kid) | [FIPS 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) | Truncated to 8 bytes |
32
+
33
+ ### Pinned upstream
34
+
35
+ This package is pinned to:
36
+
37
+ ```
38
+ @noble/post-quantum@0.2.1
39
+ integrity: sha512-ImgfMp9notXSEocz464o1AefYfFWEkkszKMGO+ZiTn73yIBFeNyEHKQUMS+SheJwSNymldSts6YyVcQDjcnVVg==
40
+ ```
41
+
42
+ `@noble/post-quantum` was independently audited by **Cure53** in 2024. Audit report: https://github.com/paulmillr/noble-post-quantum#security
43
+
44
+ ### FIPS posture (be honest)
45
+
46
+ - ✅ Algorithms implemented are NIST FIPS-standardised (203, 204).
47
+ - ❌ This module is **not** FIPS 140-3 CMVP-validated. There is no FIPS module certificate.
48
+ - ⚠️ "FIPS-aligned" is the correct way to describe this package; "FIPS-certified" is not.
49
+
50
+ For deployments where FIPS 140-3 module validation is mandatory, run cryptographic operations inside a validated HSM (AWS CloudHSM, Thales Luna, YubiHSM 2, etc.) and use this library only for envelope construction, kid generation, and verification of incoming signatures.
51
+
52
+ ## Threat model
53
+
54
+ What this library defends against and what it does not.
55
+
56
+ ### In scope
57
+
58
+ - ✅ **Quantum adversary against signature non-repudiation** — ML-DSA-65 is the FIPS 204 standard for this.
59
+ - ✅ **Quantum adversary against confidentiality of new sessions** — ML-KEM-768 (FIPS 203) for KEM; combine with AES-256-GCM (or equivalent AEAD) for the symmetric envelope.
60
+ - ✅ **Forgery of webhook deliveries** — hybrid HMAC + ML-DSA defends against both classical and quantum forgery. ML-DSA adds non-repudiation HMAC cannot give.
61
+ - ✅ **Replay attacks** — `webhook.verifyDelivery` enforces a default 5-minute timestamp window.
62
+ - ✅ **Tampering with body** — both signatures cover the exact `timestamp + "." + raw_body` envelope.
63
+ - ✅ **Wrong-key acceptance** — `pinnedKid` check fails fast before any signature verification.
64
+
65
+ ### Out of scope (caller's responsibility)
66
+
67
+ - ❌ **Master secret storage**. Keep `KXCO_KEY_MASTER` (or equivalent) in environment variables, a KMS, or an HSM. This library does not store secrets.
68
+ - ❌ **Key rotation procedures**. Rotating the master changes every derived keypair; downstream consumers must refresh their pinned kid.
69
+ - ❌ **TLS / transport encryption**. Use hybrid `X25519MLKEM768` at the public edge — outside this library's scope.
70
+ - ❌ **Receiving raw bodies byte-for-byte**. If you re-stringify JSON before verifying, you will mangle the signature input. Use Express `express.raw({ type: 'application/json' })` or equivalent.
71
+ - ❌ **Domain separation hygiene**. If you reuse the same `info` string for signing and encryption keypairs, you create a cross-protocol attack surface. Use distinct `info` per purpose.
72
+ - ❌ **Constant-time comparison of secrets outside this library**. We provide `kidEquals` and `verifyHmac`; if you compare HMAC tags yourself with `===`, that's on you.
73
+
74
+ ### Acknowledged limitations
75
+
76
+ - **Default replay window is 300 seconds**. Configurable via `windowSeconds`. Tighten for high-security paths.
77
+ - **`fingerprint` uses 8 bytes (16 hex)**. SHA-256 truncation. Collision-resistant for the scale of keys in use today (one platform key, occasionally rotated). If you anticipate billions of distinct keys, extend to a longer prefix.
78
+ - **Side-channel posture**. `@noble/post-quantum` targets constant-time execution; this wrapper adds constant-time string comparisons. Higher-assurance deployments should run operations inside a hardware boundary.
79
+ - **`signDelivery` calls `Date.now()`**. If your system clock is wrong, verifiers will reject your deliveries. Run NTP.
80
+
81
+ ## Reproducibility
82
+
83
+ Test vectors in `test/vectors.json` pin the library's outputs bit-for-bit. Anyone can run `npm test` to verify the library produces identical outputs to the recorded vectors. If a future release changes any output, the run-vectors check fails — this is a deliberate tripwire on cryptographic regressions.
19
84
 
20
- ## Known limitations
85
+ ## Disclosure history
21
86
 
22
- - Public-edge TLS is hybrid X25519MLKEM768, not pure-PQ. This is correct posture for 2026; pure-PQ TLS will be appropriate once the IETF finalises the relevant drafts.
23
- - Replay window default is 5 minutes. Reduce for tighter security.
24
- - Master secret rotation is the caller's responsibility — this library does not manage rotation.
87
+ None yet. This is a v1 release. Vulnerabilities will be disclosed here as GHSA advisories with CVE numbers where applicable.
25
88
 
26
- ## Audit status
89
+ ## Versioning and support
27
90
 
28
- - Underlying primitives (`@noble/post-quantum`) — audited by Cure53 (2024).
29
- - This wrapper — internal review only at present. External audit planned.
91
+ - We support the latest minor release on the latest major (currently 1.x).
92
+ - Critical security fixes are backported to the previous minor for 6 months.
93
+ - Major version bumps (2.x → 3.x) only occur when the cryptographic primitives or signed envelope format changes; minor bumps add features without breaking the API.
94
+ - `npm install kxco-post-quantum` always pulls the supported version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kxco-post-quantum",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Production-tested post-quantum cryptography patterns: deterministic key derivation, hybrid webhook signing, and kid fingerprinting. Built on @noble/post-quantum. Used in production at KXCO.",
5
5
  "keywords": [
6
6
  "post-quantum",
@@ -21,10 +21,10 @@
21
21
  "homepage": "https://kxco.ai",
22
22
  "repository": {
23
23
  "type": "git",
24
- "url": "https://github.com/kxco/post-quantum.git"
24
+ "url": "https://github.com/JackKXCO/kxco-post-quantum.git"
25
25
  },
26
26
  "bugs": {
27
- "url": "https://github.com/kxco/post-quantum/issues"
27
+ "url": "https://github.com/JackKXCO/kxco-post-quantum/issues"
28
28
  },
29
29
  "type": "module",
30
30
  "main": "./src/index.js",
@@ -38,9 +38,13 @@
38
38
  },
39
39
  "files": [
40
40
  "src/",
41
+ "test/vectors.json",
42
+ "test/run-vectors.js",
41
43
  "README.md",
42
44
  "LICENSE",
43
- "SECURITY.md"
45
+ "SECURITY.md",
46
+ "AUDIT.md",
47
+ "CHANGELOG.md"
44
48
  ],
45
49
  "engines": {
46
50
  "node": ">=18"
@@ -49,6 +53,8 @@
49
53
  "@noble/post-quantum": "^0.2.1"
50
54
  },
51
55
  "scripts": {
52
- "test": "node --test test/"
56
+ "test": "node --test test/basic.test.js && node test/run-vectors.js",
57
+ "test:vectors": "node test/run-vectors.js",
58
+ "generate:vectors": "node test/generate-vectors.js > test/vectors.json"
53
59
  }
54
60
  }
@@ -0,0 +1,155 @@
1
+ // Run-vectors — verifies every entry in test/vectors.json against the library.
2
+ //
3
+ // Anyone can `git clone && npm install && node test/run-vectors.js` and see
4
+ // the same bit-for-bit outputs. This is the reproducibility claim.
5
+ //
6
+ // Exit code 0 = all pass. Exit code 1 = at least one mismatch.
7
+
8
+ import { readFileSync } from 'node:fs'
9
+ import { createHash } from 'node:crypto'
10
+ import { fileURLToPath } from 'node:url'
11
+ import { dirname, join } from 'node:path'
12
+
13
+ import {
14
+ mlDsa, mlKem, deriveSeed, fingerprint, webhook,
15
+ } from '../src/index.js'
16
+
17
+ const here = dirname(fileURLToPath(import.meta.url))
18
+ const vectors = JSON.parse(readFileSync(join(here, 'vectors.json'), 'utf8'))
19
+
20
+ const sha256hex = (b) => createHash('sha256').update(b).digest('hex')
21
+
22
+ let pass = 0
23
+ let fail = 0
24
+ const failures = []
25
+
26
+ function check(group, name, expected, actual) {
27
+ const ok = expected === actual ||
28
+ (typeof expected === 'boolean' && expected === actual) ||
29
+ (typeof expected === 'number' && expected === actual)
30
+ if (ok) {
31
+ pass++
32
+ } else {
33
+ fail++
34
+ failures.push({ group, name, expected, actual })
35
+ }
36
+ }
37
+
38
+ // deriveSeed
39
+ for (const v of vectors.deriveSeed) {
40
+ const master = Buffer.from(v.master_hex, 'hex')
41
+ const got = deriveSeed(master, v.info, v.length).toString('hex')
42
+ check('deriveSeed', v.name, v.expect_hex, got)
43
+ }
44
+
45
+ // mlDsa keypairFromMaster — pin SHA-256 of pub + secret bytes
46
+ for (const v of vectors.mlDsa_keypairFromMaster) {
47
+ const master = Buffer.from(v.master_hex, 'hex')
48
+ const kp = mlDsa.keypairFromMaster(master, v.info)
49
+ check('mlDsa.keypairFromMaster', v.name + ' [publicKey_sha256]', v.publicKey_sha256, sha256hex(kp.publicKey))
50
+ check('mlDsa.keypairFromMaster', v.name + ' [secretKey_sha256]', v.secretKey_sha256, sha256hex(kp.secretKey))
51
+ check('mlDsa.keypairFromMaster', v.name + ' [publicKey_bytes]', v.publicKey_bytes, kp.publicKey.length)
52
+ check('mlDsa.keypairFromMaster', v.name + ' [secretKey_bytes]', v.secretKey_bytes, kp.secretKey.length)
53
+ }
54
+
55
+ // mlDsa sign round-trip
56
+ for (const v of vectors.mlDsa_sign_roundtrip) {
57
+ const master = Buffer.from(v.master_hex, 'hex')
58
+ const kp = mlDsa.keypairFromMaster(master, v.info)
59
+ const sig = mlDsa.sign(kp.secretKey, v.message_utf8)
60
+ const verified = mlDsa.verify(kp.publicKey, v.message_utf8, sig)
61
+ check('mlDsa.sign_roundtrip', v.name + ' [verify]', v.expect_verify, verified)
62
+ check('mlDsa.sign_roundtrip', v.name + ' [sig_hex_length]', v.expect_sig_hex_length, sig.length)
63
+ }
64
+
65
+ // mlKem keypairFromMaster
66
+ for (const v of vectors.mlKem_keypairFromMaster) {
67
+ const master = Buffer.from(v.master_hex, 'hex')
68
+ const kp = mlKem.keypairFromMaster(master, v.info)
69
+ check('mlKem.keypairFromMaster', v.name + ' [publicKey_sha256]', v.publicKey_sha256, sha256hex(kp.publicKey))
70
+ check('mlKem.keypairFromMaster', v.name + ' [secretKey_sha256]', v.secretKey_sha256, sha256hex(kp.secretKey))
71
+ check('mlKem.keypairFromMaster', v.name + ' [publicKey_bytes]', v.publicKey_bytes, kp.publicKey.length)
72
+ check('mlKem.keypairFromMaster', v.name + ' [secretKey_bytes]', v.secretKey_bytes, kp.secretKey.length)
73
+ }
74
+
75
+ // mlKem encapsulate round-trip
76
+ for (const v of vectors.mlKem_encapsulate_roundtrip) {
77
+ const master = Buffer.from(v.master_hex, 'hex')
78
+ const kp = mlKem.keypairFromMaster(master, v.info)
79
+ const { ciphertext, sharedSecret } = mlKem.encapsulate(kp.publicKey)
80
+ const recovered = mlKem.decapsulate(ciphertext, kp.secretKey)
81
+ check('mlKem.encapsulate_roundtrip', v.name + ' [secret_bytes]', v.expect_sharedSecret_bytes, sharedSecret.length)
82
+ check('mlKem.encapsulate_roundtrip', v.name + ' [ct_bytes]', v.expect_ciphertext_bytes, ciphertext.length)
83
+ check('mlKem.encapsulate_roundtrip', v.name + ' [recovered_eq]', sha256hex(sharedSecret), sha256hex(recovered))
84
+ }
85
+
86
+ // fingerprint
87
+ for (const v of vectors.fingerprint) {
88
+ let input
89
+ if (v.input_hex) input = Buffer.from(v.input_hex, 'hex')
90
+ else if (v.input_utf8) input = Buffer.from(v.input_utf8, 'utf8')
91
+ else if (v.input_source && v.input_source.includes('mlDsa.keypairFromMaster')) {
92
+ input = mlDsa.keypairFromMaster(Buffer.from('00'.repeat(32), 'hex'), 'platform-v1').publicKey
93
+ }
94
+ const got = fingerprint(input)
95
+ check('fingerprint', v.name, v.expect_kid, got)
96
+ }
97
+
98
+ // webhook envelope
99
+ for (const v of vectors.webhook_envelope) {
100
+ const got = webhook.envelope(v.timestamp, v.body_utf8).toString('hex')
101
+ check('webhook.envelope', v.name, v.expect_envelope_hex, got)
102
+ }
103
+
104
+ // webhook hmac
105
+ for (const v of vectors.webhook_hmac) {
106
+ const got = webhook.hmacHex(v.secret_utf8, v.timestamp, v.body_utf8)
107
+ check('webhook.hmac', v.name, v.expect_hmac_hex, got)
108
+ }
109
+
110
+ // webhook hybrid round-trip with FRESH timestamp (vector's timestamp is stale on purpose; we use Date.now() here)
111
+ for (const v of vectors.webhook_hybrid_roundtrip) {
112
+ const master = Buffer.from(v.master_hex, 'hex')
113
+ const kp = mlDsa.keypairFromMaster(master, v.info)
114
+ const kid = fingerprint(kp.publicKey)
115
+ const freshTs = Math.floor(Date.now() / 1000).toString()
116
+ const headers = webhook.signDelivery({
117
+ rawBody: v.body_utf8,
118
+ hmacSecret: v.hmac_secret_utf8,
119
+ pqSecretKey: kp.secretKey,
120
+ pqKid: kid,
121
+ })
122
+ const lower = Object.fromEntries(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]))
123
+ const r = webhook.verifyDelivery({
124
+ headers: lower,
125
+ rawBody: v.body_utf8,
126
+ hmacSecret: v.hmac_secret_utf8,
127
+ pqPublicKey: kp.publicKey,
128
+ pinnedKid: kid,
129
+ })
130
+ check('webhook.hybrid_roundtrip', v.name + ' [hmac_ok]', v.expect_hmac_ok, r.hmacOk)
131
+ check('webhook.hybrid_roundtrip', v.name + ' [pq_ok]', v.expect_pq_ok, r.pqOk)
132
+ check('webhook.hybrid_roundtrip', v.name + ' [ts_ok]', v.expect_timestamp_ok_when_fresh, r.timestampOk)
133
+ }
134
+
135
+ // Report
136
+ console.log('')
137
+ console.log(`kxco-post-quantum test vectors`)
138
+ console.log(`Generated: ${vectors.generated_at}`)
139
+ console.log(`Library: ${vectors.library}`)
140
+ console.log(`Underlying: ${vectors.underlying_primitive}`)
141
+ console.log('')
142
+
143
+ if (fail === 0) {
144
+ console.log(`✓ All ${pass} checks pass — library output matches pinned vectors bit-for-bit.`)
145
+ process.exit(0)
146
+ } else {
147
+ console.log(`✗ ${fail} failed, ${pass} passed.`)
148
+ for (const f of failures) {
149
+ console.log(`\n GROUP: ${f.group}`)
150
+ console.log(` NAME: ${f.name}`)
151
+ console.log(` EXPECTED: ${f.expected}`)
152
+ console.log(` ACTUAL: ${f.actual}`)
153
+ }
154
+ process.exit(1)
155
+ }
@@ -0,0 +1,137 @@
1
+ {
2
+ "version": "1.0",
3
+ "generated_at": "2026-05-20T18:58:39.432Z",
4
+ "description": "Deterministic test vectors for kxco-post-quantum. Run test/run-vectors.js to verify these match the library output bit-for-bit.",
5
+ "library": "kxco-post-quantum",
6
+ "underlying_primitive": "@noble/post-quantum",
7
+ "notes": [
8
+ "ML-DSA-65 signatures are randomized — we cannot pin signature bytes. We pin keypair hashes and verify round-trip.",
9
+ "ML-KEM encapsulation is randomized — we pin keypair hashes and verify decapsulate round-trip in the runner.",
10
+ "All hex is lowercase. All UTF-8 strings are explicit."
11
+ ],
12
+ "deriveSeed": [
13
+ {
14
+ "name": "zero master, info=role-a, length=32",
15
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
16
+ "info": "role-a",
17
+ "length": 32,
18
+ "expect_hex": "873c6de3e076daede530be45f1f7953a29fec20648557429d2607e08c3ad8d7c"
19
+ },
20
+ {
21
+ "name": "zero master, info=role-b, length=32 (different info → different seed)",
22
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
23
+ "info": "role-b",
24
+ "length": 32,
25
+ "expect_hex": "80f852c16846b2a44c64c7742cd8df83f9b1a98f969dd0496904c3bdd08cd8cc"
26
+ },
27
+ {
28
+ "name": "0x11 master, info=platform-v1, length=64",
29
+ "master_hex": "1111111111111111111111111111111111111111111111111111111111111111",
30
+ "info": "platform-v1",
31
+ "length": 64,
32
+ "expect_hex": "a9df8abe6efa342d56b911f8eeea67bb553d675e4411ea046b9aca2fa409fcc2b92166b6117606627faed0a66f890403ad2fa6d9d3dadc69bd5559f3da92eec3"
33
+ }
34
+ ],
35
+ "mlDsa_keypairFromMaster": [
36
+ {
37
+ "name": "zero master, info=platform-v1",
38
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
39
+ "info": "platform-v1",
40
+ "publicKey_sha256": "dc29825b2f9cc9f1671ca499c27d013c2b4ac86e1c6ce365f40556ec82916e57",
41
+ "publicKey_bytes": 1952,
42
+ "secretKey_sha256": "fad19856458d742f62d79dbd772cfaea7ad2e432111a9663d159b079d15f51eb",
43
+ "secretKey_bytes": 4032
44
+ },
45
+ {
46
+ "name": "0xff master, info=test",
47
+ "master_hex": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
48
+ "info": "test",
49
+ "publicKey_sha256": "b5efe3602d7d87e29ce642ff44a682ded5408e43b965b2a22e5aedec0d8ab9aa",
50
+ "publicKey_bytes": 1952,
51
+ "secretKey_sha256": "abd46a2431e7e82249e798e7ca634785ba1f43e052e4649a4e0e02536db47770",
52
+ "secretKey_bytes": 4032
53
+ }
54
+ ],
55
+ "mlDsa_sign_roundtrip": [
56
+ {
57
+ "name": "sign known message with keypair from zero master; verify true",
58
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
59
+ "info": "platform-v1",
60
+ "message_utf8": "hello kxco",
61
+ "expect_verify": true,
62
+ "expect_sig_hex_length": 6618
63
+ }
64
+ ],
65
+ "mlKem_keypairFromMaster": [
66
+ {
67
+ "name": "zero master, info=platform-v1",
68
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
69
+ "info": "platform-v1",
70
+ "publicKey_sha256": "8393aadd8e9070c8b43b65a1e05eb43f9c9e7e32ba7880261a2cbcabda58c188",
71
+ "publicKey_bytes": 1184,
72
+ "secretKey_sha256": "8d21f316efc198ba3cab8e7c9270f57efe351bed4d0f66c66f6d454d4282423e",
73
+ "secretKey_bytes": 2400
74
+ }
75
+ ],
76
+ "mlKem_encapsulate_roundtrip": [
77
+ {
78
+ "name": "encapsulate to keypair from zero master; decapsulate recovers same secret",
79
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
80
+ "info": "platform-v1",
81
+ "expect_sharedSecret_bytes": 32,
82
+ "expect_ciphertext_bytes": 1088
83
+ }
84
+ ],
85
+ "fingerprint": [
86
+ {
87
+ "name": "fingerprint of 32 zero bytes",
88
+ "input_hex": "0000000000000000000000000000000000000000000000000000000000000000",
89
+ "expect_kid": "66687aadf862bd77"
90
+ },
91
+ {
92
+ "name": "fingerprint of 32 0xff bytes",
93
+ "input_hex": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
94
+ "expect_kid": "af9613760f72635f"
95
+ },
96
+ {
97
+ "name": "fingerprint of UTF-8 string 'kxco'",
98
+ "input_utf8": "kxco",
99
+ "expect_kid": "00932b8f81e33fa2"
100
+ },
101
+ {
102
+ "name": "fingerprint of ML-DSA-65 platform pubkey from zero master",
103
+ "input_source": "mlDsa.keypairFromMaster(0x00*32, \"platform-v1\").publicKey",
104
+ "expect_kid": "dc29825b2f9cc9f1"
105
+ }
106
+ ],
107
+ "webhook_envelope": [
108
+ {
109
+ "name": "envelope concatenation",
110
+ "timestamp": "1748000000",
111
+ "body_utf8": "{\"event\":\"payment.settled\",\"amount\":1000}",
112
+ "expect_envelope_hex": "313734383030303030302e7b226576656e74223a227061796d656e742e736574746c6564222c22616d6f756e74223a313030307d"
113
+ }
114
+ ],
115
+ "webhook_hmac": [
116
+ {
117
+ "name": "HMAC-SHA-256 over envelope",
118
+ "secret_utf8": "shared-secret-bytes",
119
+ "timestamp": "1748000000",
120
+ "body_utf8": "{\"event\":\"payment.settled\",\"amount\":1000}",
121
+ "expect_hmac_hex": "63ed31450be6c8771c2eb9a92888aecd814431360ac06aaac95faa94cf5bd665"
122
+ }
123
+ ],
124
+ "webhook_hybrid_roundtrip": [
125
+ {
126
+ "name": "sign + verify with platform keypair from zero master",
127
+ "master_hex": "0000000000000000000000000000000000000000000000000000000000000000",
128
+ "info": "platform-v1",
129
+ "timestamp": "1748000000",
130
+ "body_utf8": "{\"event\":\"payment.settled\",\"amount\":1000}",
131
+ "hmac_secret_utf8": "shared-secret-bytes",
132
+ "expect_hmac_ok": true,
133
+ "expect_pq_ok": true,
134
+ "expect_timestamp_ok_when_fresh": true
135
+ }
136
+ ]
137
+ }