kxco-post-quantum 1.3.0 → 1.4.0
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/CHANGELOG.md +374 -292
- package/CONFORMANCE.md +180 -0
- package/LICENSE +202 -202
- package/MIGRATION.md +143 -0
- package/README.md +215 -178
- package/SECURITY.md +41 -41
- package/THREAT-MODEL.md +194 -0
- package/package.json +124 -106
- package/src/derive.d.ts +20 -20
- package/src/derive.js +47 -47
- package/src/index.d.ts +13 -8
- package/src/index.js +24 -17
- package/src/kid.d.ts +21 -21
- package/src/kid.js +53 -53
- package/src/ml-dsa-87.d.ts +81 -0
- package/src/ml-dsa-87.js +127 -0
- package/src/ml-dsa.d.ts +78 -78
- package/src/ml-dsa.js +102 -102
- package/src/ml-kem-1024.d.ts +59 -0
- package/src/ml-kem-1024.js +90 -0
- package/src/ml-kem.d.ts +49 -49
- package/src/ml-kem.js +61 -61
- package/src/slh-dsa.d.ts +72 -72
- package/src/slh-dsa.js +106 -106
- package/src/webhook.d.ts +119 -119
- package/src/webhook.js +135 -135
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Migration guide
|
|
2
|
+
|
|
3
|
+
How to move an existing system onto post-quantum signatures and key
|
|
4
|
+
establishment with this package, and how to move between versions of it.
|
|
5
|
+
|
|
6
|
+
Two separate problems, in order:
|
|
7
|
+
|
|
8
|
+
1. [Choosing a parameter set](#choosing-a-parameter-set)
|
|
9
|
+
2. [Migrating a classical system](#migrating-a-classical-system) — RSA or ECDSA today
|
|
10
|
+
3. [Migrating between versions](#migrating-between-versions) of this package
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Choosing a parameter set
|
|
15
|
+
|
|
16
|
+
| You need | Use | Why |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Signatures, general use | `mlDsa` (ML-DSA-65) | Category 3. Fast, 3309-byte signatures. The default. |
|
|
19
|
+
| Signatures, Category 5 required | `mlDsa87` (ML-DSA-87) | When a counterparty specifies Category 5 or names ML-DSA-87. Signatures are 4627 bytes. |
|
|
20
|
+
| Signatures, no lattice assumption | `slhDsa` (SLH-DSA-SHA2-192s) | Hash-based, so it does not share ML-DSA's underlying assumption. Signatures are 16 KB and signing takes seconds. |
|
|
21
|
+
| Key establishment | `mlKem` (ML-KEM-768) | Category 3, matching ML-DSA-65. |
|
|
22
|
+
| Key establishment, Category 5 required | `mlKem1024` (ML-KEM-1024) | Public key and ciphertext are 1568 bytes each. The shared secret stays 32 bytes. |
|
|
23
|
+
|
|
24
|
+
**On CNSA 2.0.** It names ML-DSA-87 and ML-KEM-1024, and both are available
|
|
25
|
+
here. Availability is not compliance: CNSA 2.0 compliance is a property of a
|
|
26
|
+
deployment, and picking `mlDsa87` for one call path does not confer it on a
|
|
27
|
+
system whose other signatures, identities and anchors are Category 3. Use the
|
|
28
|
+
Category 5 sets when someone asks for the parameter set. Do not use their
|
|
29
|
+
presence as the basis for a compliance statement.
|
|
30
|
+
|
|
31
|
+
Two points that catch people out:
|
|
32
|
+
|
|
33
|
+
**Sizes are the migration cost, not speed.** An ML-DSA-65 signature is 3309
|
|
34
|
+
bytes against 64 for Ed25519, roughly 50 times larger. Anything with a size
|
|
35
|
+
limit on the signature field needs looking at before anything else: database
|
|
36
|
+
columns, cookie and header size limits, QR codes, embedded firmware slots,
|
|
37
|
+
protocol frames with a fixed-width length. Signing and verification are fast
|
|
38
|
+
enough that throughput is rarely the blocker.
|
|
39
|
+
|
|
40
|
+
**SLH-DSA is slow enough to change designs.** Signing with a `...s` (small)
|
|
41
|
+
parameter set takes on the order of seconds. It suits infrequent, high-value
|
|
42
|
+
signatures such as firmware releases or root attestations. It does not suit
|
|
43
|
+
per-request signing. The `...f` (fast) sets trade signature size for speed.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Migrating a classical system
|
|
48
|
+
|
|
49
|
+
### Do not swap. Add, then remove.
|
|
50
|
+
|
|
51
|
+
The failure mode is replacing RSA or ECDSA with ML-DSA in one release, then
|
|
52
|
+
finding that data signed before the switch no longer verifies, or that a peer
|
|
53
|
+
you do not control has not migrated. Run both, then retire the old one.
|
|
54
|
+
|
|
55
|
+
**Stage 1: verify both, sign classically.** Deploy verification for ML-DSA
|
|
56
|
+
alongside your existing scheme. Nothing signs with it yet. This is the cheap
|
|
57
|
+
stage and it proves your storage and transport handle the larger signatures
|
|
58
|
+
before anything depends on them.
|
|
59
|
+
|
|
60
|
+
**Stage 2: sign both.** Attach both signatures. Consumers verify whichever they
|
|
61
|
+
support. Both must be bound to the same message, and a consumer that verifies
|
|
62
|
+
only one must not be able to be tricked into treating a valid classical
|
|
63
|
+
signature as covering a message the PQ signature does not, so sign identical
|
|
64
|
+
bytes with both.
|
|
65
|
+
|
|
66
|
+
**Stage 3: require both.** Verification fails unless both are present and valid.
|
|
67
|
+
This is the point at which you have post-quantum security and have not yet lost
|
|
68
|
+
compatibility. It is a good place to stay for a long time.
|
|
69
|
+
|
|
70
|
+
**Stage 4: drop the classical signature.** Only once every producer and consumer
|
|
71
|
+
is on stage 3, and only once you no longer need to verify anything signed before
|
|
72
|
+
stage 2.
|
|
73
|
+
|
|
74
|
+
For key establishment the same shape applies, and the hybrid step is not
|
|
75
|
+
optional in the same way: combine the ML-KEM shared secret with a classical
|
|
76
|
+
ECDH secret through a KDF so the session is secure if *either* holds. Do not
|
|
77
|
+
use a raw ML-KEM shared secret as a key. Run it through HKDF with a context
|
|
78
|
+
label, which is what `deriveSeed` is for.
|
|
79
|
+
|
|
80
|
+
### Store what you will need later
|
|
81
|
+
|
|
82
|
+
Migrations stall on missing metadata, not on cryptography. From stage 1, record
|
|
83
|
+
alongside every signature:
|
|
84
|
+
|
|
85
|
+
- **Which algorithm and parameter set** produced it. Do not infer it from
|
|
86
|
+
signature length; ML-DSA-65 and some SLH-DSA sets are distinguishable by
|
|
87
|
+
length today but that is not a property to depend on.
|
|
88
|
+
- **Which key** produced it. The KXCO ID from `kid` identifies a public key
|
|
89
|
+
compactly and is safe to publish.
|
|
90
|
+
- **The context string**, if any. A signature made under a context does not
|
|
91
|
+
verify without it, so a lost context is a lost signature.
|
|
92
|
+
|
|
93
|
+
Adding these fields later means backfilling them for data you can no longer
|
|
94
|
+
attribute.
|
|
95
|
+
|
|
96
|
+
### Test the negative cases
|
|
97
|
+
|
|
98
|
+
The interop matrix in this repository tests that a tampered signature is
|
|
99
|
+
rejected, for the reason that a verifier which returns true unconditionally
|
|
100
|
+
passes every positive test. Your integration deserves the same check: assert
|
|
101
|
+
that a flipped bit fails, that a signature under the wrong context fails, and
|
|
102
|
+
that a signature from the wrong key fails. Do this before stage 3, not after.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Migrating between versions
|
|
107
|
+
|
|
108
|
+
The `CHANGELOG.md` is authoritative. The notes below cover the changes that
|
|
109
|
+
require action rather than a version bump.
|
|
110
|
+
|
|
111
|
+
### 1.2.x to 1.3.0
|
|
112
|
+
|
|
113
|
+
**The FIPS 204 / FIPS 205 context parameter arrived.** `sign` and `verify` take
|
|
114
|
+
an optional `{ context }`. Existing calls that pass no context are unaffected:
|
|
115
|
+
no context means the empty context, which is what earlier versions produced.
|
|
116
|
+
|
|
117
|
+
A context is part of the signature. Adding one to a signing call invalidates
|
|
118
|
+
verification by any caller that does not pass the same context, so roll context
|
|
119
|
+
adoption out to verifiers first, exactly as in stage 1 above.
|
|
120
|
+
|
|
121
|
+
**The backend moved to `@noble/post-quantum` 0.7.0**, exact-pinned. If your
|
|
122
|
+
project also depends on `@noble/post-quantum` directly, align it. Two copies of
|
|
123
|
+
a cryptographic backend in one dependency tree is a hazard worth removing, and
|
|
124
|
+
`falcon.js` and `hybrid.js` from that release are not part of what this package
|
|
125
|
+
tests or supports.
|
|
126
|
+
|
|
127
|
+
### Upgrading across any version
|
|
128
|
+
|
|
129
|
+
1. Read `CHANGELOG.md` for the versions you are skipping, not just the target.
|
|
130
|
+
2. Run your own negative tests, above, against the new version.
|
|
131
|
+
3. Verify a signature produced by the old version with the new one, and the
|
|
132
|
+
reverse. This is the check that catches an encoding change.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Verifying what you deploy
|
|
137
|
+
|
|
138
|
+
Release integrity is covered in [SECURITY.md](SECURITY.md); conformance evidence,
|
|
139
|
+
including the cross-implementation matrix, is in
|
|
140
|
+
[CONFORMANCE.md](CONFORMANCE.md). Before putting a key in production, read
|
|
141
|
+
[THREAT-MODEL.md](THREAT-MODEL.md), specifically the section on where the
|
|
142
|
+
residual risk sits. It will tell you whether the key should be in this library
|
|
143
|
+
at all or in an HSM.
|
package/README.md
CHANGED
|
@@ -1,178 +1,215 @@
|
|
|
1
|
-
# kxco-post-quantum
|
|
2
|
-
|
|
3
|
-
Post-quantum cryptography primitives for the KXCO stack.
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/kxco-post-quantum)
|
|
6
|
-
[](https://github.com/KnightsbridgeAIQ/kxco-post-quantum/actions/workflows/ci.yml)
|
|
7
|
-
[](https://www.npmjs.com/package/kxco-post-quantum)
|
|
6
|
+
[](https://github.com/KnightsbridgeAIQ/kxco-post-quantum/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/KnightsbridgeAIQ/kxco-post-quantum/actions/workflows/conformance.yml)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
ML-DSA-65 (FIPS 204) and SLH-DSA-SHA2-192s (FIPS 205) signatures, ML-KEM-768 (FIPS 203) key encapsulation, and key fingerprinting utilities. Category 5 sets ML-DSA-87 and ML-KEM-1024 are also available. Wraps [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum). All other `kxco-pq-*` packages depend on this one.
|
|
11
|
+
|
|
12
|
+
**Evidence, not adjectives:**
|
|
13
|
+
|
|
14
|
+
- [CONFORMANCE.md](./CONFORMANCE.md) — NIST ACVP vectors for FIPS 203/204/205, and a cross-implementation interop matrix against Bouncy Castle and two pure-Python implementations. 134 interop checks, both directions, with negative controls. Reproducible: `npm run conformance:acvp`, `npm run conformance:interop`.
|
|
15
|
+
- [THREAT-MODEL.md](./THREAT-MODEL.md) — what this defends against and what it does not. Read the side-channel section before deciding where a signing key lives.
|
|
16
|
+
- [MIGRATION.md](./MIGRATION.md) — moving an RSA or ECDSA system across, and moving between versions of this package.
|
|
17
|
+
- [SECURITY.md](./SECURITY.md) — reporting, and release integrity.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install kxco-post-quantum
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Node.js 20.19+. ESM-only.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { mlDsa, mlKem, slhDsa, fingerprint, kidEquals } from 'kxco-post-quantum'
|
|
35
|
+
|
|
36
|
+
// ML-DSA-65 — sign and verify
|
|
37
|
+
const { publicKey, secretKey } = mlDsa.keypairFromMaster(masterSecret, 'signing-v1')
|
|
38
|
+
const sig = mlDsa.sign(secretKey, 'hello')
|
|
39
|
+
const ok = mlDsa.verify(publicKey, 'hello', sig) // true
|
|
40
|
+
|
|
41
|
+
// SLH-DSA-SHA2-192s — hash-based signatures (same API shape as mlDsa)
|
|
42
|
+
const slh = slhDsa.keypairFromMaster(masterSecret, 'signing-v1')
|
|
43
|
+
const slhSig = slhDsa.sign(slh.secretKey, 'hello')
|
|
44
|
+
const slhOk = slhDsa.verify(slh.publicKey, 'hello', slhSig) // true
|
|
45
|
+
|
|
46
|
+
// Key fingerprint
|
|
47
|
+
const kid = fingerprint(publicKey) // e.g. '4a7c9e2f1b3d5680'
|
|
48
|
+
kidEquals(kid, kid) // true (constant-time)
|
|
49
|
+
|
|
50
|
+
// ML-KEM-768 — key encapsulation
|
|
51
|
+
const kemKeys = mlKem.keypairFromMaster(masterSecret, 'encryption-v1')
|
|
52
|
+
const { ciphertext, sharedSecret } = mlKem.encapsulate(kemKeys.publicKey)
|
|
53
|
+
const recovered = mlKem.decapsulate(ciphertext, kemKeys.secretKey)
|
|
54
|
+
// sharedSecret and recovered are the same 32 bytes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`masterSecret` is a `Buffer` or `Uint8Array` with at least 16 bytes of entropy (typically 32–64 bytes from an env var or KMS).
|
|
58
|
+
|
|
59
|
+
### Category 5 parameter sets
|
|
60
|
+
|
|
61
|
+
`mlDsa87` (ML-DSA-87) and `mlKem1024` (ML-KEM-1024) have the same API as `mlDsa`
|
|
62
|
+
and `mlKem`, one security category higher. Reach for them when a counterparty
|
|
63
|
+
specifies Category 5 or names the parameter set. The KXCO default stays
|
|
64
|
+
Category 3.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { mlDsa87, mlKem1024 } from 'kxco-post-quantum'
|
|
68
|
+
|
|
69
|
+
const { publicKey, secretKey } = mlDsa87.keypairFromMaster(masterSecret, 'signing-v1')
|
|
70
|
+
const sig = mlDsa87.sign(secretKey, 'hello') // 4627 bytes, 9254 hex chars
|
|
71
|
+
mlDsa87.verify(publicKey, 'hello', sig) // true
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| | Category 3 (default) | Category 5 |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| Signatures | `mlDsa` — pk 1952, sig 3309 | `mlDsa87` — pk 2592, sig 4627 |
|
|
77
|
+
| Key encapsulation | `mlKem` — pk 1184, ct 1088 | `mlKem1024` — pk 1568, ct 1568 |
|
|
78
|
+
|
|
79
|
+
The two sets do not mix, deliberately. Default derivation info differs, so one
|
|
80
|
+
master yields unrelated keys for each; and a signature from one set does not
|
|
81
|
+
verify under the other. Sizes are the migration cost, so check any fixed-width
|
|
82
|
+
signature or key field before mixing sets in one system.
|
|
83
|
+
|
|
84
|
+
**CNSA 2.0 names ML-DSA-87 and ML-KEM-1024, and supporting them is not a CNSA
|
|
85
|
+
2.0 compliance claim.** Compliance is a property of a deployment, not of an
|
|
86
|
+
available function. See [CONFORMANCE.md](./CONFORMANCE.md).
|
|
87
|
+
|
|
88
|
+
### Context strings (FIPS 204 / FIPS 205)
|
|
89
|
+
|
|
90
|
+
`sign` and `verify` take an optional context string, at most 255 bytes. A
|
|
91
|
+
signature made under a context does not verify without it, or under a different
|
|
92
|
+
one.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const sig = mlDsa.sign(secretKey, 'hello', { context: 'kxco-nexus-v1' })
|
|
96
|
+
|
|
97
|
+
mlDsa.verify(publicKey, 'hello', sig, { context: 'kxco-nexus-v1' }) // true
|
|
98
|
+
mlDsa.verify(publicKey, 'hello', sig) // false
|
|
99
|
+
mlDsa.verify(publicKey, 'hello', sig, { context: 'other-v1' }) // false
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The parameter is optional and defaults to no context, so every existing call
|
|
103
|
+
site is unaffected. An empty context is identical to omitting it. `slhDsa` takes
|
|
104
|
+
the same option.
|
|
105
|
+
|
|
106
|
+
**Context separates at the signature level; `keypairFromMaster(master, info)`
|
|
107
|
+
separates at the key level.** They are complementary. Use a context when one key
|
|
108
|
+
legitimately signs for several purposes and you need a signature from one
|
|
109
|
+
purpose to be unusable in another. Use a distinct derived key when the purposes
|
|
110
|
+
should not share a key at all.
|
|
111
|
+
|
|
112
|
+
Strings are encoded as UTF-8, so the 255-byte limit is bytes and not
|
|
113
|
+
characters. Over-length or wrongly typed input throws (`RangeError` /
|
|
114
|
+
`TypeError`) rather than returning `false`, because that is a caller bug and not
|
|
115
|
+
a failed verification:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
mlDsa.sign(secretKey, 'hello', 'kxco-nexus-v1') // throws TypeError
|
|
119
|
+
// (needs { context: ... })
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That last case is worth guarding: without the throw it would silently sign with
|
|
123
|
+
*no* context and produce a valid-looking signature carrying none of the intended
|
|
124
|
+
separation.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## API
|
|
129
|
+
|
|
130
|
+
### `mlDsa` — ML-DSA-65 (NIST FIPS 204)
|
|
131
|
+
|
|
132
|
+
| Export | Signature | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'ml-dsa-65-v1'`. |
|
|
135
|
+
| `sign` | `(secretKey, message) → string` | Signs a message. Returns a hex-encoded signature (6618 chars). |
|
|
136
|
+
| `verify` | `(publicKey, message, sigHex) → boolean` | Verifies a hex-encoded signature. Returns `false` on any failure. |
|
|
137
|
+
| `ml_dsa65` | raw primitive | The underlying `@noble/post-quantum` primitive, re-exported. |
|
|
138
|
+
|
|
139
|
+
`publicKey` is 1952 bytes. `secretKey` is 4032 bytes. `message` accepts `Buffer`, `Uint8Array`, or `string`.
|
|
140
|
+
|
|
141
|
+
### `slhDsa` — SLH-DSA-SHA2-192s (NIST FIPS 205)
|
|
142
|
+
|
|
143
|
+
Hash-based, stateless signatures. Security Category 3 (matching ML-DSA-65), but security rests only on the SHA-2 hash function — no lattice or number-theoretic assumptions. Use this as a conservative hedge alongside `mlDsa`. Tradeoff: signatures are ~5× larger (16224 vs 3309 bytes) and signing is slower.
|
|
144
|
+
|
|
145
|
+
| Export | Signature | Description |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'slh-dsa-sha2-192s-v1'`. |
|
|
148
|
+
| `sign` | `(secretKey, message) → string` | Signs a message. Returns a hex-encoded signature (32448 chars). |
|
|
149
|
+
| `verify` | `(publicKey, message, sigHex) → boolean` | Verifies a hex-encoded signature. Returns `false` on any failure. |
|
|
150
|
+
| `slh_dsa_sha2_192s` | raw primitive | The underlying `@noble/post-quantum` primitive, re-exported. |
|
|
151
|
+
|
|
152
|
+
`publicKey` is 48 bytes. `secretKey` is 96 bytes. `message` accepts `Buffer`, `Uint8Array`, or `string`.
|
|
153
|
+
|
|
154
|
+
### `mlKem` — ML-KEM-768 (NIST FIPS 203)
|
|
155
|
+
|
|
156
|
+
| Export | Signature | Description |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'ml-kem-768-v1'`. |
|
|
159
|
+
| `encapsulate` | `(publicKey) → { ciphertext, sharedSecret }` | Generates a shared secret and ciphertext to send to the key holder. |
|
|
160
|
+
| `decapsulate` | `(ciphertext, secretKey) → Buffer` | Recovers the shared secret from a ciphertext. Returns 32 bytes. |
|
|
161
|
+
| `ml_kem768` | raw primitive | The underlying `@noble/post-quantum` primitive, re-exported. |
|
|
162
|
+
|
|
163
|
+
`publicKey` is 1184 bytes. `ciphertext` is 1088 bytes. `sharedSecret` is 32 bytes.
|
|
164
|
+
|
|
165
|
+
### `fingerprint(publicKey)` → `string`
|
|
166
|
+
|
|
167
|
+
First 16 hex characters of SHA-256 of the public key. Stable for the lifetime of the key. Accepts raw bytes or a hex string.
|
|
168
|
+
|
|
169
|
+
### `kidEquals(a, b)` → `boolean`
|
|
170
|
+
|
|
171
|
+
Constant-time comparison of two kid strings. Use this when comparing user-supplied input — not `===`.
|
|
172
|
+
|
|
173
|
+
### `deriveSeed(master, info, length)` → `Buffer`
|
|
174
|
+
|
|
175
|
+
HKDF-SHA-512 derivation. `master` must be at least 16 bytes. `info` is a required domain-separation string. Returns `length` bytes.
|
|
176
|
+
|
|
177
|
+
### `webhook` — hybrid HMAC + ML-DSA-65 delivery signing
|
|
178
|
+
|
|
179
|
+
Low-level helpers for the KXCO hybrid webhook pattern: `envelope`, `hmacHex`, `verifyHmac`, `pqSign`, `verifyPq`, `signDelivery`, `verifyDelivery`. HMAC-SHA-256 gives symmetric verification with no library dependency; ML-DSA-65 adds non-repudiation over the same `${timestamp}.${body}` envelope. The full identity/credential surface lives in `kxco-pq-sdk`.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## What this does NOT do
|
|
184
|
+
|
|
185
|
+
- No identity credentials or verifiable claims (those are in `kxco-pq-sdk`)
|
|
186
|
+
- No relay, transport, or network layer
|
|
187
|
+
- No key storage or KMS integration
|
|
188
|
+
- No FIPS 140-3 module validation (the algorithms are FIPS-standardised; the module is not validated)
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Part of the KXCO stack
|
|
193
|
+
|
|
194
|
+
`kxco-post-quantum` is the primitive layer. Everything else builds on it:
|
|
195
|
+
|
|
196
|
+
- **`kxco-pq-sdk`** — identity credentials, webhook signing, verifiable claims
|
|
197
|
+
- Other `kxco-pq-*` packages — domain-specific integrations
|
|
198
|
+
|
|
199
|
+
Install this package directly when you need ML-DSA or ML-KEM without the rest of the identity stack.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Security
|
|
204
|
+
|
|
205
|
+
Cryptographic operations delegate entirely to [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum) and [`@noble/hashes`](https://github.com/paulmillr/noble-hashes) — this package does not reimplement any NIST primitive. `@noble/hashes` falls under Cure53's 2023 audit of the `@noble` ecosystem (`ciphers`, `curves`, `hashes`); `@noble/post-quantum` was **not** in that audit's scope and has been self-audited by its maintainer. See [AUDIT.md](./AUDIT.md) for the full posture.
|
|
206
|
+
|
|
207
|
+
To report a vulnerability: [open a private security advisory](https://github.com/KnightsbridgeAIQ/kxco-post-quantum/security/advisories/new) or email **john@knightsbridgelaw.com**. Acknowledgement within 2 business days, triage decision within 5. Full policy, including safe harbour for good-faith research: <https://kxco.ai/security>.
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
Apache-2.0. See [LICENSE](./LICENSE).
|
|
212
|
+
|
|
213
|
+
## Maintainers
|
|
214
|
+
|
|
215
|
+
Shayne Heffernan and John Heffernan — [KXCO by Knightsbridge](https://kxco.ai)
|
package/SECURITY.md
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
## Reporting a vulnerability
|
|
4
|
-
Email **john@knightsbridgelaw.com**. Do not open public issues for security reports.
|
|
5
|
-
PGP key available on request. We credit reporters in `CHANGELOG.md` unless they
|
|
6
|
-
request otherwise.
|
|
7
|
-
|
|
8
|
-
Acknowledgement within **2 business days**. Triage decision within **5 business days**.
|
|
9
|
-
|
|
10
|
-
Full policy: <https://kxco.ai/security>
|
|
11
|
-
|
|
12
|
-
## Safe harbour
|
|
13
|
-
If you make a good-faith effort to comply with this policy, we will treat your
|
|
14
|
-
research as authorised, and we will not pursue or support legal action against
|
|
15
|
-
you. Good faith means: do not access, modify, exfiltrate, or destroy data that
|
|
16
|
-
is not yours; use test accounts where possible; do not degrade service for
|
|
17
|
-
others; and stop and report as soon as you have established that a
|
|
18
|
-
vulnerability exists. This cannot bind third parties, and it does not cover
|
|
19
|
-
extortion, data sale, or public disclosure ahead of the window below.
|
|
20
|
-
|
|
21
|
-
## Scope
|
|
22
|
-
In scope:
|
|
23
|
-
- Cryptographic correctness of the wrappers in this package
|
|
24
|
-
- Constant-time guarantees on signature/HMAC comparison
|
|
25
|
-
- Replay-window enforcement in `webhook.verify`
|
|
26
|
-
- HKDF domain separation in `derive`
|
|
27
|
-
- Kid fingerprint collision behaviour
|
|
28
|
-
|
|
29
|
-
Out of scope (report upstream to https://github.com/paulmillr/noble-post-quantum):
|
|
30
|
-
- Bugs in the underlying ML-DSA-65, ML-KEM-768, SLH-DSA-SHA2-192s, or HKDF primitives
|
|
31
|
-
|
|
32
|
-
## Algorithms used
|
|
33
|
-
- ML-DSA-65 — NIST FIPS 204 (lattice signatures)
|
|
34
|
-
- ML-KEM-768 — NIST FIPS 203 (key encapsulation)
|
|
35
|
-
- SLH-DSA-SHA2-192s — NIST FIPS 205 (hash-based signatures)
|
|
36
|
-
- HMAC-SHA-256
|
|
37
|
-
- HKDF-SHA-512 (RFC 5869)
|
|
38
|
-
|
|
39
|
-
## Disclosure
|
|
40
|
-
We follow coordinated disclosure with a 90-day default window.
|
|
41
|
-
For actively-exploited issues we ship a patch release within 48 hours.
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
Email **john@knightsbridgelaw.com**. Do not open public issues for security reports.
|
|
5
|
+
PGP key available on request. We credit reporters in `CHANGELOG.md` unless they
|
|
6
|
+
request otherwise.
|
|
7
|
+
|
|
8
|
+
Acknowledgement within **2 business days**. Triage decision within **5 business days**.
|
|
9
|
+
|
|
10
|
+
Full policy: <https://kxco.ai/security>
|
|
11
|
+
|
|
12
|
+
## Safe harbour
|
|
13
|
+
If you make a good-faith effort to comply with this policy, we will treat your
|
|
14
|
+
research as authorised, and we will not pursue or support legal action against
|
|
15
|
+
you. Good faith means: do not access, modify, exfiltrate, or destroy data that
|
|
16
|
+
is not yours; use test accounts where possible; do not degrade service for
|
|
17
|
+
others; and stop and report as soon as you have established that a
|
|
18
|
+
vulnerability exists. This cannot bind third parties, and it does not cover
|
|
19
|
+
extortion, data sale, or public disclosure ahead of the window below.
|
|
20
|
+
|
|
21
|
+
## Scope
|
|
22
|
+
In scope:
|
|
23
|
+
- Cryptographic correctness of the wrappers in this package
|
|
24
|
+
- Constant-time guarantees on signature/HMAC comparison
|
|
25
|
+
- Replay-window enforcement in `webhook.verify`
|
|
26
|
+
- HKDF domain separation in `derive`
|
|
27
|
+
- Kid fingerprint collision behaviour
|
|
28
|
+
|
|
29
|
+
Out of scope (report upstream to https://github.com/paulmillr/noble-post-quantum):
|
|
30
|
+
- Bugs in the underlying ML-DSA-65, ML-KEM-768, SLH-DSA-SHA2-192s, or HKDF primitives
|
|
31
|
+
|
|
32
|
+
## Algorithms used
|
|
33
|
+
- ML-DSA-65 — NIST FIPS 204 (lattice signatures)
|
|
34
|
+
- ML-KEM-768 — NIST FIPS 203 (key encapsulation)
|
|
35
|
+
- SLH-DSA-SHA2-192s — NIST FIPS 205 (hash-based signatures)
|
|
36
|
+
- HMAC-SHA-256
|
|
37
|
+
- HKDF-SHA-512 (RFC 5869)
|
|
38
|
+
|
|
39
|
+
## Disclosure
|
|
40
|
+
We follow coordinated disclosure with a 90-day default window.
|
|
41
|
+
For actively-exploited issues we ship a patch release within 48 hours.
|