kxco-post-quantum 1.5.2 → 1.6.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/BENCHMARKS.md +18 -13
- package/CHANGELOG.md +122 -0
- package/CONFORMANCE.md +77 -5
- package/DEPENDENCIES.md +137 -0
- package/LICENCE-PRODUCT.md +90 -0
- package/README.md +72 -6
- package/package.json +29 -11
- package/src/backend.d.ts +16 -0
- package/src/backend.js +48 -0
- package/src/index.d.ts +6 -0
- package/src/index.js +18 -2
- package/src/jws.d.ts +74 -0
- package/src/jws.js +249 -0
- package/src/ml-dsa-87.d.ts +8 -0
- package/src/ml-dsa-87.js +5 -0
- package/src/ml-dsa.d.ts +8 -0
- package/src/ml-dsa.js +5 -0
- package/src/ml-kem-1024.d.ts +8 -0
- package/src/ml-kem-1024.js +5 -0
- package/src/ml-kem.d.ts +8 -0
- package/src/ml-kem.js +5 -0
- package/src/seed.d.ts +91 -0
- package/src/seed.js +394 -0
package/BENCHMARKS.md
CHANGED
|
@@ -85,15 +85,19 @@ Milliseconds. 100 iterations except where the `n` column says otherwise.
|
|
|
85
85
|
|
|
86
86
|
**Two numbers to design around.**
|
|
87
87
|
|
|
88
|
-
**ML-DSA signing has a long tail.** ML-DSA-65 signs in
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
**ML-DSA signing has a long tail, on both backends.** ML-DSA-65 signs in 8.1 ms
|
|
89
|
+
at the median and 40.8 ms at p99 in JavaScript, a factor of 5.1. On OpenSSL it is
|
|
90
|
+
1.3 ms and 4.6 ms, a factor of 3.5. Rejection sampling means an unlucky signature
|
|
91
|
+
does several more rounds, and no backend removes that. Size request budgets from
|
|
92
|
+
p99, not the median, and from the p99 of the backend you will actually run.
|
|
93
|
+
|
|
94
|
+
**SLH-DSA-SHA2-192s signs in seconds, not milliseconds:** about 4.3 s in
|
|
95
|
+
JavaScript and 1.7 s on OpenSSL. That is the set `slhDsa` wraps, and on either
|
|
96
|
+
backend it is not a per-request operation. It suits infrequent, high-value
|
|
97
|
+
signatures such as firmware or root attestations. Verification is cheap, 7.5 ms
|
|
98
|
+
and 1.3 ms respectively, so an SLH-DSA signature is expensive to make and cheap
|
|
99
|
+
to check. The `f` variants trade signature size for signing speed: SHA2-128f
|
|
100
|
+
signs in 98 ms in JavaScript and 69 ms on OpenSSL.
|
|
97
101
|
|
|
98
102
|
## Key encapsulation
|
|
99
103
|
|
|
@@ -155,11 +159,12 @@ collection timing, not evidence that the larger parameter set allocates less.
|
|
|
155
159
|
|
|
156
160
|
- **One machine, one run.** Node v26.1.0 on win32-x64. Absolute numbers move with
|
|
157
161
|
hardware and runtime; the ratios between operations are the portable part.
|
|
158
|
-
- **Not a cross-vendor comparison.**
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
- **Not a cross-vendor comparison.** The two backends here are both ours to
|
|
163
|
+
ship, so the comparison above is between two paths through this package, not
|
|
164
|
+
between this package and someone else's. It says nothing about how either
|
|
165
|
+
compares to a hardware-backed stack.
|
|
161
166
|
- **Reduced samples where marked.** SLH-DSA slow variants take 3 to 20 samples
|
|
162
|
-
rather than 100, because 100 signatures at
|
|
167
|
+
rather than 100, because 100 signatures at seconds each is not a benchmark,
|
|
163
168
|
it is an afternoon. Where n is small, p95 and p99 collapse onto the maximum and
|
|
164
169
|
are reported that way rather than dressed up.
|
|
165
170
|
- **Not a side-channel measurement.** Timing here is throughput, gathered without
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,127 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
|
|
5
|
+
Additive. No existing export changes shape in a way a caller can observe, no
|
|
6
|
+
wire format moves, and the pinned vectors still match bit-for-bit.
|
|
7
|
+
|
|
8
|
+
**Seed-form keys.** FIPS 203 and 204 expand a keypair from a short seed, and
|
|
9
|
+
the expanded private key does not contain the seed it came from. So seed export
|
|
10
|
+
had to start at derivation: `keypairFromMaster` now also returns the seed it
|
|
11
|
+
already derived. Callers destructuring `{ publicKey, secretKey }` see no
|
|
12
|
+
difference. `seed.seedFromMaster` reproduces exactly the same bytes, which is
|
|
13
|
+
what makes seed export possible for keys already in production.
|
|
14
|
+
|
|
15
|
+
New `seed` namespace: `keypairFromSeed`, `seedFromMaster`, `exportJwk`,
|
|
16
|
+
`importJwk`, `exportSeedPkcs8`, `importSeedPkcs8`, for ML-DSA-65, ML-DSA-87,
|
|
17
|
+
ML-KEM-768 and ML-KEM-1024. JWKs are RFC 9964 `kty: "AKP"`, where `priv` is the
|
|
18
|
+
seed; PKCS#8 is the LAMPS seed form, the `[0] IMPLICIT OCTET STRING` CHOICE.
|
|
19
|
+
Both load directly into OpenSSL 3.5 and Node 24+. The existing expanded-key
|
|
20
|
+
export is untouched and old keys verify unchanged.
|
|
21
|
+
|
|
22
|
+
None of that is read off the specification. `test/seed.test.js` asserts the DER
|
|
23
|
+
this package writes is byte-identical to what OpenSSL writes, for every
|
|
24
|
+
parameter set, and that a seed expands to the same public key under both
|
|
25
|
+
backends. Where there is no native backend those tests skip with a reason
|
|
26
|
+
rather than passing quietly, because a green suite on a runtime without OpenSSL
|
|
27
|
+
is not evidence about OpenSSL.
|
|
28
|
+
|
|
29
|
+
An expanded secret key is refused where a seed belongs, and an expanded-form
|
|
30
|
+
PKCS#8 key is refused rather than truncated. Both would otherwise silently
|
|
31
|
+
produce a different keypair: the first 32 bytes of an expanded ML-DSA key are
|
|
32
|
+
not its seed.
|
|
33
|
+
|
|
34
|
+
**Compact JWS.** New `jws` namespace: `signJws`, `verifyJws`,
|
|
35
|
+
`decodeJwsHeader`, using the RFC 9964 algorithm names `ML-DSA-65` and
|
|
36
|
+
`ML-DSA-87`. Format only — no network, no configuration, no licence, and a
|
|
37
|
+
token stays verifiable offline for as long as the holder keeps the public key.
|
|
38
|
+
The algorithm is resolved from an allowlist inside the module rather than from
|
|
39
|
+
the token, so a token cannot name its own verification routine; `crit` and `b64`
|
|
40
|
+
are refused rather than ignored; and the key length must match the declared
|
|
41
|
+
algorithm, so a mixed-up key reports itself instead of looking like a bad
|
|
42
|
+
signature. SLH-DSA is deliberately not offered: FIPS 205 signing takes about a
|
|
43
|
+
second and a half and does not belong on a request path.
|
|
44
|
+
|
|
45
|
+
**A second correction to AUDIT.md.** The v1.1.2 correction fixed a false claim
|
|
46
|
+
that `@noble/post-quantum` was Cure53-audited, and in doing so introduced a
|
|
47
|
+
different inaccuracy: it described a single "Cure53 2023 NDS-01" audit covering
|
|
48
|
+
`@noble/ciphers`, `@noble/curves` and `@noble/hashes`. There is no such
|
|
49
|
+
engagement. Those are three separate audits at three different dates, and this
|
|
50
|
+
repository's own `audit/dependency-review.json` — generated by
|
|
51
|
+
`audit/run-audit.mjs`, not written by hand — recorded them correctly the whole
|
|
52
|
+
time:
|
|
53
|
+
|
|
54
|
+
| Package | Audited by |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `@noble/post-quantum` | maintainer-audited, v0.6.1, Apr 2026 |
|
|
57
|
+
| `@noble/hashes` | Cure53, Jan 2022, v1.0.0 (Ethereum Foundation with Nomic Labs) |
|
|
58
|
+
| `@noble/curves` | Trail of Bits Feb 2023; Kudelski Sep 2023; Cure53 Sep 2024 |
|
|
59
|
+
| `@noble/ciphers` | Cure53, Sep 2024, v1.0.0 (OpenSats) |
|
|
60
|
+
|
|
61
|
+
The load-bearing claim never changed and was correct throughout:
|
|
62
|
+
**`@noble/post-quantum` has never been audited by a third party.** What was
|
|
63
|
+
wrong was the supporting detail, and the wrong wording had been copied into
|
|
64
|
+
sibling repositories before it was caught. AUDIT.md now matches the generated
|
|
65
|
+
record, and the v1.1.2 changelog entry is left as it was written — a released
|
|
66
|
+
entry is a record of what was said at the time, not a place to rewrite it.
|
|
67
|
+
|
|
68
|
+
**An evidence bundle.** `npm run evidence` writes `dist/evidence/`: versions,
|
|
69
|
+
the commit, the toolchain, which backend did the maths, the ACVP and interop
|
|
70
|
+
conformance output, the test suite output, a CycloneDX SBOM, the registry
|
|
71
|
+
signature check, and copies of AUDIT.md, THREAT-MODEL.md, SECURITY.md,
|
|
72
|
+
CONFORMANCE.md, DEPENDENCIES.md and LICENCE-PRODUCT.md.
|
|
73
|
+
|
|
74
|
+
**It is not an audit and it says so three times** — in the manifest's
|
|
75
|
+
`disclaimer` field, in the bundle README, and in the copied AUDIT.md. A CI step
|
|
76
|
+
asserts both disclaimers are still present, because the one sentence that must
|
|
77
|
+
never be edited out of an evidence bundle is the one saying it is not an audit.
|
|
78
|
+
|
|
79
|
+
A step that fails is recorded as a failure in the manifest rather than aborting
|
|
80
|
+
the build: a bundle missing a section with no explanation is worse than one
|
|
81
|
+
that says what did not run and why. Optional steps (peers needing a JVM, a
|
|
82
|
+
registry check needing a token) do not fail the build; required ones do.
|
|
83
|
+
|
|
84
|
+
The `evidence` workflow runs on Node 22 and 24 — the first has no OpenSSL 3.5
|
|
85
|
+
FIPS primitives and so exercises the JavaScript backend, the second exercises
|
|
86
|
+
OpenSSL. Shipping only one bundle would let a reader draw a conclusion about
|
|
87
|
+
code that never ran. Push builds subsample and record that they did; scheduled
|
|
88
|
+
and release builds do a full pass.
|
|
89
|
+
|
|
90
|
+
New `LICENCE-PRODUCT.md` states what is free (the maths, permanently, and
|
|
91
|
+
chain-agnostic) and what is paid (the hosted registry, the relay, anchoring,
|
|
92
|
+
live revocation, SLA), that the `KXCO Verified` mark needs a contract, and that
|
|
93
|
+
no third party has assessed this code and a customer may re-run the published
|
|
94
|
+
vectors without asking.
|
|
95
|
+
|
|
96
|
+
**`backend()` and `isNative(alg)`.** The package chooses its backend by probing
|
|
97
|
+
the runtime, so the only honest way to state which one a deployment used is to
|
|
98
|
+
ask it. These report; neither switches. The ACVP conformance report now records
|
|
99
|
+
`backendUnderTest` explicitly, saying that it drives the JavaScript primitives
|
|
100
|
+
directly and that the OpenSSL backend is evidenced by the interop matrix
|
|
101
|
+
instead. The same pass count previously described either implementation.
|
|
102
|
+
|
|
103
|
+
## 1.5.3
|
|
104
|
+
|
|
105
|
+
Documentation and metadata. No code change.
|
|
106
|
+
|
|
107
|
+
Corrects stale performance figures. The README and BENCHMARKS.md both quoted a
|
|
108
|
+
4.5x median-to-p99 tail for ML-DSA signing and about 6.8 seconds for
|
|
109
|
+
SLH-DSA-SHA2-192s. Measured now, across both backends: the tail is 5.1x in
|
|
110
|
+
JavaScript and 3.5x on OpenSSL, and SLH-DSA-SHA2-192s signs in 4.3 s and 1.7 s.
|
|
111
|
+
|
|
112
|
+
Says what the package does. The README still described it as a wrapper around
|
|
113
|
+
@noble/post-quantum without mentioning that on Node 24 and later the primitives
|
|
114
|
+
run in OpenSSL 3.5. The npm description, which is what appears in registry search
|
|
115
|
+
results, listed the algorithms and nothing else: not the 2,103 NIST ACVP vectors,
|
|
116
|
+
not the 225-check interoperability matrix, not the reproducible build or the
|
|
117
|
+
provenance attestation. Those are the parts that distinguish this from any other
|
|
118
|
+
post-quantum wrapper, and they were absent from the one line most readers see.
|
|
119
|
+
|
|
120
|
+
A dependabot ignore blocks @noble/post-quantum 0.7.1 specifically, so it is not
|
|
121
|
+
reproposed weekly. The ACVP job fails the build on it regardless; this stops the
|
|
122
|
+
pull request being opened. 0.7.2 and later will still be offered and should be
|
|
123
|
+
accepted once the vectors pass.
|
|
124
|
+
|
|
3
125
|
## 1.5.2
|
|
4
126
|
|
|
5
127
|
**Reverts `@noble/post-quantum` to 0.7.0. Upgrade from 1.5.1 immediately if you
|
package/CONFORMANCE.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Conformance and interoperability evidence
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Three claims, each with a harness in this repository that anyone can run:
|
|
4
4
|
|
|
5
5
|
1. **This package computes what FIPS 203, 204 and 205 say it should.** Evidenced
|
|
6
6
|
against NIST's own ACVP test vectors.
|
|
7
7
|
2. **Independent implementations can consume what it produces, and it can
|
|
8
8
|
consume theirs.** Evidenced against liboqs (C), Bouncy Castle (Java) and
|
|
9
9
|
dilithium-py / kyber-py (Python), in both directions.
|
|
10
|
+
3. **The PKI artefacts standard tooling issues validate here.** Evidenced by
|
|
11
|
+
having OpenSSL 3.5 issue ML-DSA certificates and signed messages, and
|
|
12
|
+
verifying them from this package with its own DER parsing.
|
|
10
13
|
|
|
11
14
|
The second claim is the one that matters in deployment and the one that vector
|
|
12
15
|
files cannot make. Passing NIST's vectors proves agreement with NIST. It does
|
|
@@ -19,9 +22,10 @@ Everything below is reproducible:
|
|
|
19
22
|
npm run conformance:fetch # pinned NIST vectors, digest-checked
|
|
20
23
|
npm run conformance:acvp # claim 1
|
|
21
24
|
npm run conformance:interop # claim 2
|
|
25
|
+
npm run conformance:protocol # claim 3
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
All three harnesses run in CI on every push and in full weekly. See
|
|
25
29
|
[.github/workflows/conformance.yml](.github/workflows/conformance.yml).
|
|
26
30
|
|
|
27
31
|
---
|
|
@@ -243,6 +247,72 @@ keys, both carrying these identifiers, which is what makes the two backends
|
|
|
243
247
|
interchangeable with each other and with any X.509 or CMS consumer that speaks
|
|
244
248
|
the same encodings.
|
|
245
249
|
|
|
250
|
+
## 5. Protocol artefacts: X.509 and CMS
|
|
251
|
+
|
|
252
|
+
Claims 1 and 2 are about primitives and encodings. Neither answers the question a
|
|
253
|
+
PKI team asks first, which is whether a certificate issued by the tooling they
|
|
254
|
+
already run will validate.
|
|
255
|
+
|
|
256
|
+
So OpenSSL issues and this package verifies. Nothing in the chain is ours on both
|
|
257
|
+
sides, and the artefacts are generated fresh on every run rather than committed,
|
|
258
|
+
because a fixture keeps passing after an encoding has drifted.
|
|
259
|
+
|
|
260
|
+
| Issuer | Version | Base image |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| OpenSSL | 3.5.8, asserted at image build time | `alpine:3.22` |
|
|
263
|
+
|
|
264
|
+
Pinned as `openssl` in
|
|
265
|
+
[conformance/interop/peers-lock.json](conformance/interop/peers-lock.json). The
|
|
266
|
+
exact build string is recorded in every report rather than the family, because
|
|
267
|
+
"OpenSSL 3.5" names a line and not the binary that signed the bytes.
|
|
268
|
+
|
|
269
|
+
### X.509
|
|
270
|
+
|
|
271
|
+
A self-signed certificate per parameter set. Verification walks the DER to the
|
|
272
|
+
`tbsCertificate`, takes it including its own tag and length octets, which is what
|
|
273
|
+
RFC 5280 actually signs, and checks the `signature` BIT STRING over it with the
|
|
274
|
+
public key read out of a separately supplied SubjectPublicKeyInfo.
|
|
275
|
+
|
|
276
|
+
### CMS SignedData
|
|
277
|
+
|
|
278
|
+
A signed message per parameter set, `-nodetach`, SHA-256 digest. ML-DSA carries no
|
|
279
|
+
default digest in OpenSSL 3.5, so `-md` has to be named explicitly; without it
|
|
280
|
+
`CMS_add1_signer` fails with "no default digest". That is a property of the CMS
|
|
281
|
+
layer rather than of the key.
|
|
282
|
+
|
|
283
|
+
The signature in a SignedData does not cover the message. It covers the DER
|
|
284
|
+
encoding of the signed attributes, re-tagged from the `[0] IMPLICIT` they travel
|
|
285
|
+
in to the `SET OF` they are signed as (RFC 5652 s5.4). Verifying it therefore
|
|
286
|
+
takes four checks that only mean something together:
|
|
287
|
+
|
|
288
|
+
| Check | What it establishes |
|
|
289
|
+
|---|---|
|
|
290
|
+
| `signatureVerified` | the signature is valid over the signed attributes |
|
|
291
|
+
| `digestBindsMessage` | the `messageDigest` attribute is the digest of the content |
|
|
292
|
+
| `contentTypeBound` | the signed `contentType` matches `eContentType` (RFC 5652 s5.3) |
|
|
293
|
+
| `contentMatches` | the embedded content is the message that was submitted |
|
|
294
|
+
|
|
295
|
+
The first alone would hold equally for a signature over somebody else's message.
|
|
296
|
+
|
|
297
|
+
### Controls
|
|
298
|
+
|
|
299
|
+
Every row carries OpenSSL's own verdict on the artefact, so a disagreement is
|
|
300
|
+
attributable rather than assumed, and a tamper control that flips one bit of the
|
|
301
|
+
signed body and requires the verification to fail. The controls are not
|
|
302
|
+
decoration: the first version of the certificate parser sliced the wrong byte
|
|
303
|
+
range and returned false for everything, which passed the tamper control and
|
|
304
|
+
failed the positive check. Without the pair, a verifier that always returned true
|
|
305
|
+
and one that always returned false would both have looked green.
|
|
306
|
+
|
|
307
|
+
`algorithmOid` additionally checks that the SignerInfo names the OID in section 4
|
|
308
|
+
for the set it claims. An implementation that verifies the bytes but disagrees
|
|
309
|
+
about which algorithm they belong to still fails to interoperate.
|
|
310
|
+
|
|
311
|
+
### Result
|
|
312
|
+
|
|
313
|
+
33 checks across 6 rows, all passing, none not applicable. Reports are published
|
|
314
|
+
as CI artefacts per Node version.
|
|
315
|
+
|
|
246
316
|
## What this evidence does not cover
|
|
247
317
|
|
|
248
318
|
Stated plainly, because a conformance report that only lists what passed is
|
|
@@ -264,9 +334,11 @@ marketing.
|
|
|
264
334
|
these modules change. The accurate sentences are "NIST FIPS 203/204/205
|
|
265
335
|
conformant" and "supports ML-DSA-87 and ML-KEM-1024". Anything stronger,
|
|
266
336
|
including "CNSA 2.0 ready", would be an overclaim.
|
|
267
|
-
- **Protocol
|
|
268
|
-
|
|
269
|
-
TLS group negotiation are separate surfaces and
|
|
337
|
+
- **Protocol coverage is X.509 and CMS, for the ML-DSA sets.** Section 5 covers
|
|
338
|
+
certificates and CMS SignedData against OpenSSL 3.5 for ML-DSA-44/65/87. COSE,
|
|
339
|
+
JOSE and TLS group negotiation are separate surfaces, and ML-KEM and SLH-DSA
|
|
340
|
+
are covered at the key and signature layer rather than in certificates,
|
|
341
|
+
because OpenSSL 3.5 issues neither.
|
|
270
342
|
- **Self-administered.** Every number above was produced by harnesses in this
|
|
271
343
|
repository, run by us. That is why they are reproducible and why the pins,
|
|
272
344
|
digests and negative controls are there. It is not third-party attestation and
|
package/DEPENDENCIES.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Dependency audit
|
|
2
|
+
|
|
3
|
+
Four production dependencies. No development dependencies. Every one of them
|
|
4
|
+
reviewed by a person, and every mechanical fact in that review re-checked by a
|
|
5
|
+
harness that fails when reality moves away from it.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm run audit:deps
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- Curated review: [audit/dependency-review.json](audit/dependency-review.json)
|
|
12
|
+
- Harness: [audit/run-audit.mjs](audit/run-audit.mjs)
|
|
13
|
+
- Report: `audit/results/dependencies.json`, published as a CI artefact
|
|
14
|
+
|
|
15
|
+
Current result: **26 checks passed, 0 failed, 0 skipped.**
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The tree
|
|
20
|
+
|
|
21
|
+
| Package | Version | Licence | Pinning | On the code path | Independently reviewed by |
|
|
22
|
+
|---|---|---|---|---|---|
|
|
23
|
+
| `@noble/post-quantum` | 0.7.0 | MIT | exact | yes | maintainer self-audit at 0.6.1, Apr 2026 |
|
|
24
|
+
| `@noble/hashes` | 2.3.0 | MIT | exact | yes | Cure53, Jan 2022 |
|
|
25
|
+
| `@noble/curves` | 2.3.0 | MIT | transitive | yes | Trail of Bits, Feb 2023; Kudelski Security, Sep 2023; Cure53, Sep 2024 |
|
|
26
|
+
| `@noble/ciphers` | 2.3.0 | MIT | transitive | no | Cure53, Sep 2024 |
|
|
27
|
+
|
|
28
|
+
One maintainer publishes all four, and all four carry a verified npm registry
|
|
29
|
+
signature and a verified SLSA provenance attestation, with nothing invalid and
|
|
30
|
+
nothing missing across the installed tree. Nothing in the tree runs code at
|
|
31
|
+
install time, and `npm audit` reports no advisory at any severity.
|
|
32
|
+
|
|
33
|
+
## Why each one is there
|
|
34
|
+
|
|
35
|
+
**`@noble/post-quantum`** supplies the FIPS 203, 204 and 205 primitives. This
|
|
36
|
+
package is the layer above: parameter-set selection, FIPS 204 context strings,
|
|
37
|
+
key derivation, fingerprinting, webhook signing and the OpenSSL backend. It does
|
|
38
|
+
not reimplement the arithmetic.
|
|
39
|
+
|
|
40
|
+
It is also the one package in the tree with no independent review, and that is
|
|
41
|
+
the reason [conformance/](CONFORMANCE.md) exists. Every parameter set is checked
|
|
42
|
+
against NIST's own ACVP vectors, cross-checked against liboqs, Bouncy Castle and
|
|
43
|
+
dilithium-py / kyber-py in both directions, and checked against certificates and
|
|
44
|
+
signed messages that OpenSSL 3.5 issued. The primitives are evidenced here rather
|
|
45
|
+
than accepted on the dependency's word, and that evidence is reproducible on any
|
|
46
|
+
machine.
|
|
47
|
+
|
|
48
|
+
The pin is exact and deliberately so. Version 0.7.1 regressed nine NIST SLH-DSA
|
|
49
|
+
verification vectors that 0.7.0 passes. It shipped in this package's 1.5.1, was
|
|
50
|
+
reverted in 1.5.2, is deprecated on npm and is blocked in dependabot. A caret
|
|
51
|
+
range would have taken that regression automatically, which is why the harness
|
|
52
|
+
fails the build on any range in `dependencies`.
|
|
53
|
+
|
|
54
|
+
**`@noble/hashes`** supplies SHA-256, SHA-512, SHAKE-256 and HKDF, for key
|
|
55
|
+
derivation from a master secret, public-key fingerprinting and the hashing FIPS
|
|
56
|
+
205 requires. It is declared directly rather than inherited, so the version in
|
|
57
|
+
use is visible in this package's own manifest instead of resolved out of a
|
|
58
|
+
transitive range. The 2022 Cure53 scope covered everything except `blake3`,
|
|
59
|
+
`sha3-addons`, `sha1` and `argon2`; none of those four are used here.
|
|
60
|
+
|
|
61
|
+
**`@noble/curves`** is not named anywhere in this package's source. It arrives
|
|
62
|
+
through `@noble/post-quantum`, and it is on the hot path: `ml-dsa.js` takes
|
|
63
|
+
`abool` from `@noble/curves/utils.js`, and `_crystals.js`, which both ML-DSA and
|
|
64
|
+
ML-KEM are built on, takes `FFTCore` and `reverseBits` from
|
|
65
|
+
`@noble/curves/abstract/fft.js`. So every signature and every encapsulation this
|
|
66
|
+
package performs runs through it.
|
|
67
|
+
|
|
68
|
+
That was established by the reachability walk, not by reading manifests. It is
|
|
69
|
+
also the most heavily reviewed package in the tree: three independent firms
|
|
70
|
+
across four years.
|
|
71
|
+
|
|
72
|
+
**`@noble/ciphers`** arrives the same way and is the one package in the tree that
|
|
73
|
+
nothing here reaches. It is present in `node_modules` and absent from every code
|
|
74
|
+
path from every published entry point.
|
|
75
|
+
|
|
76
|
+
## What the harness checks
|
|
77
|
+
|
|
78
|
+
The review is a human document. The harness re-derives each of its mechanical
|
|
79
|
+
claims from the lockfile, the registry and the installed tree, and exits non-zero
|
|
80
|
+
on any disagreement:
|
|
81
|
+
|
|
82
|
+
| Check | Source of truth |
|
|
83
|
+
|---|---|
|
|
84
|
+
| every dependency in the tree appears in the review | `package-lock.json` |
|
|
85
|
+
| the review names nothing that has left the tree | `package-lock.json` |
|
|
86
|
+
| the tree is within the declared ceiling of four | `package-lock.json` |
|
|
87
|
+
| every direct dependency is pinned to an exact version | `package.json` |
|
|
88
|
+
| no development dependencies | `package.json` |
|
|
89
|
+
| each package is at the reviewed version | `package-lock.json` |
|
|
90
|
+
| each licence is on the allowed list | `package-lock.json` |
|
|
91
|
+
| no package declares an install script | lockfile flag and every installed manifest |
|
|
92
|
+
| reachability matches the review, in both directions | static import walk |
|
|
93
|
+
| no known advisories | `npm audit` |
|
|
94
|
+
| no invalid registry signature or attestation anywhere | `npm audit signatures --json` |
|
|
95
|
+
| no missing registry signature or attestation anywhere | `npm audit signatures --json` |
|
|
96
|
+
|
|
97
|
+
The reachability check is bidirectional, and that matters. A package the review
|
|
98
|
+
calls unused becoming reachable fails the build, and so does a package the review
|
|
99
|
+
calls used becoming unreachable. The first version of the review recorded
|
|
100
|
+
`@noble/curves` as unused; the harness rejected it, and the entry above is what
|
|
101
|
+
replaced it.
|
|
102
|
+
|
|
103
|
+
Adding or bumping a dependency fails this harness until somebody updates the
|
|
104
|
+
review. That is the mechanism by which the document stays true, rather than being
|
|
105
|
+
accurate on the day it was written.
|
|
106
|
+
|
|
107
|
+
## How reachability is derived
|
|
108
|
+
|
|
109
|
+
A breadth-first walk from all nine entry points in the `exports` map, reading
|
|
110
|
+
every import, export-from, dynamic `import()` and `require()` specifier out of
|
|
111
|
+
the source and resolving each with `import.meta.resolve` from the importing file.
|
|
112
|
+
Using Node's own resolver means subpath exports, export maps and the `#native`
|
|
113
|
+
conditional import resolve the way Node resolves them at run time.
|
|
114
|
+
|
|
115
|
+
Both branches of `#native` are walked, because the Node backend and the browser
|
|
116
|
+
stub both live in `src/`. The reported set is therefore the union of the Node and
|
|
117
|
+
browser paths, which can only over-report what is reachable. Current figures: 33
|
|
118
|
+
files walked, three packages reached, and one Node builtin, `node:crypto`, used
|
|
119
|
+
by the OpenSSL backend.
|
|
120
|
+
|
|
121
|
+
## Supply-chain posture in one paragraph
|
|
122
|
+
|
|
123
|
+
Four packages, one publisher, all MIT, all exactly locked by integrity hash, all
|
|
124
|
+
carrying registry signatures and SLSA provenance with nothing invalid or missing,
|
|
125
|
+
none able to execute at install time, none carrying an advisory, three of four independently reviewed by named
|
|
126
|
+
firms, and the fourth evidenced directly against NIST's vectors and three
|
|
127
|
+
independent implementations in this repository. The whole tree is small enough
|
|
128
|
+
that "all dependencies audited" is a statement someone can check in an afternoon,
|
|
129
|
+
which is the only reason it is worth making.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Correcting this document
|
|
134
|
+
|
|
135
|
+
Every figure comes from `npm run audit:deps`. If one does not reproduce on your
|
|
136
|
+
machine, that is a defect worth reporting through [SECURITY.md](SECURITY.md);
|
|
137
|
+
include the generated `audit/results/dependencies.json` from your run.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Licensing: what is free, what is paid
|
|
2
|
+
|
|
3
|
+
Two different things share the KXCO name, and confusing them wastes everyone's time. This document draws the line.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Free, and permanently so: the maths
|
|
8
|
+
|
|
9
|
+
Apache-2.0. Use it commercially, fork it, ship it inside a product, never speak to us.
|
|
10
|
+
|
|
11
|
+
| Package | What it does |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [`kxco-post-quantum`](https://www.npmjs.com/package/kxco-post-quantum) | ML-DSA-65, ML-KEM-768, SLH-DSA, seed-form keys, compact JWS, deterministic derivation, kid fingerprints |
|
|
14
|
+
| [`kxco-verify`](https://www.npmjs.com/package/kxco-verify) | Verify a site's deploy attestation, in a terminal or a browser |
|
|
15
|
+
|
|
16
|
+
These are **chain-agnostic**. There is no chain id, no relay URL and no licence check anywhere in their source. They do not phone home. Signing and verification work with the network unplugged and keep working if KXCO stops existing, which is the only sense in which a signature is worth anything ten years from now.
|
|
17
|
+
|
|
18
|
+
The other Apache-2.0 packages — `kxco-pq-attest`, `kxco-pq-audit`, `kxco-pq-hsm`, `kxco-pq-tls`, `kxco-pq-vault`, `kxco-post-quantum-webhook`, `kxco-pq-sdk`, `kxco-pq-agent`, `kxco-pq-chain`, `kxco-pq-network` — are also Apache-2.0 source. **The licence covers the code, not the service.** Reading and modifying the source is free. Calling the KXCO-operated registry and relay is not.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Paid: the service
|
|
23
|
+
|
|
24
|
+
What you buy is not cryptography. Cryptography is free, and we could not charge for it honestly if we wanted to.
|
|
25
|
+
|
|
26
|
+
What you buy is **an answer about the present**.
|
|
27
|
+
|
|
28
|
+
A signature made by a key that was revoked an hour ago is still a perfectly valid signature. No amount of offline mathematics can tell you it was revoked. Only something that knows what happened after the signature was made can, and that something has to be operated, kept available, and answerable when it is wrong.
|
|
29
|
+
|
|
30
|
+
| Paid | Why it cannot be free |
|
|
31
|
+
|---|---|
|
|
32
|
+
| **Hosted key registry** (`chain.kxco.ai/kids/:kid`) | Answers whether a key is active, revoked or rotated. Someone has to run it and stand behind the answer |
|
|
33
|
+
| **Meta-transaction relay** (`relay.kxco.ai`) | KXCO validates your signed intent, **pays the gas in ARMR**, and submits the transaction. You never hold a token or run a node |
|
|
34
|
+
| **On-chain anchoring** | Writes to Armature L1, chain 1111111 |
|
|
35
|
+
| **Live revocation** (`anchored+live`) | The registry lookup at verification time |
|
|
36
|
+
| **Support and SLA** | Availability commitments, an escalation path, a name to call |
|
|
37
|
+
|
|
38
|
+
Priced **in USD, per seat**. You do not need to hold ARMR, run a node, configure an RPC endpoint or touch a wallet. If a vendor tells you post-quantum identity requires you to buy a token, that is a different product from this one.
|
|
39
|
+
|
|
40
|
+
See [`SALES-SKU.md`](https://github.com/KnightsbridgeAIQ/kxco-pq-network/blob/main/SALES-SKU.md) in `kxco-pq-network` for the seat definitions.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Which mode you are in
|
|
45
|
+
|
|
46
|
+
| Mode | Network at verify time | Licence | What it proves |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `signature` | none | no | The holder of this key signed this |
|
|
49
|
+
| `anchored` | none | no | …and it was written to Armature L1 |
|
|
50
|
+
| `anchored+live` | yes, fails closed | **yes** | …and that key is still trusted **now** |
|
|
51
|
+
|
|
52
|
+
`signature` and `anchored` will not start requiring a licence. Envelopes issued today verify in those modes forever, with no KXCO server in the path. That is a commitment about the format, not a pricing tier that may move.
|
|
53
|
+
|
|
54
|
+
Writes to the hosted relay require a licence, and always did in substance — this is now enforced in the client rather than only at the server, so a misconfigured service fails at boot instead of at a customer's first transaction.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Trademark
|
|
59
|
+
|
|
60
|
+
The name **KXCO** and the mark **"KXCO Verified"** are not covered by the Apache-2.0 licence.
|
|
61
|
+
|
|
62
|
+
You may fork the code, ship it, and say so. You may not describe your deployment as "KXCO Verified", or use the mark on a badge, a certificate, a report or a sales page, without a written agreement with us. The mark is meant to tell a reader that KXCO stands behind a specific claim, and it is worthless the moment anyone can apply it to themselves.
|
|
63
|
+
|
|
64
|
+
Describing your product as "built on kxco-post-quantum" is fine and accurate. Describing it as "KXCO Verified" is not, unless it is.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Assurance
|
|
69
|
+
|
|
70
|
+
The cryptography is NIST-standardised and the conformance is published, pinned and reproducible.
|
|
71
|
+
|
|
72
|
+
| | |
|
|
73
|
+
|---|---|
|
|
74
|
+
| Algorithms | ML-DSA-65 (FIPS 204), ML-KEM-768 (FIPS 203), SLH-DSA (FIPS 205) |
|
|
75
|
+
| Backend | OpenSSL 3.5 where the runtime provides it, `@noble/post-quantum` elsewhere. Identical on the wire, checked in both directions |
|
|
76
|
+
| Conformance | 2,103 NIST ACVP vectors across FIPS 203, 204 and 205. Vectors pinned by digest |
|
|
77
|
+
| Interoperability | 225 checks against OpenSSL 3.5, liboqs, Bouncy Castle and dilithium-py/kyber-py |
|
|
78
|
+
| Supply chain | SLSA provenance attestation on every release; CycloneDX SBOM; reproducible build |
|
|
79
|
+
| Evidence bundle | `npm run evidence` regenerates all of it from source, on your machine |
|
|
80
|
+
|
|
81
|
+
**You do not have to take any of it on our word.** Every figure above comes from a command you can run yourself against the same commit, and the vectors are NIST's, not ours. A customer may re-run them at any time without asking us and without telling us. If your result differs from ours we want to know: `john@knightsbridgelaw.com`, acknowledged within 2 business days.
|
|
82
|
+
|
|
83
|
+
Dependency audit history — which upstream libraries were reviewed, by whom, and when — is recorded in [`AUDIT.md`](AUDIT.md), kept current by a generated dependency review rather than by hand.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Support
|
|
88
|
+
|
|
89
|
+
Commercial terms, seat pricing and SLA: **hello@kxco.ai**
|
|
90
|
+
Security and vulnerability reports: **john@knightsbridgelaw.com**, or a private advisory on the relevant repository. Full policy, including safe harbour for good-faith research: <https://kxco.ai/security>
|
package/README.md
CHANGED
|
@@ -7,15 +7,18 @@ Post-quantum cryptography primitives for the KXCO stack.
|
|
|
7
7
|
[](https://github.com/KnightsbridgeAIQ/kxco-post-quantum/actions/workflows/conformance.yml)
|
|
8
8
|
[](./LICENSE)
|
|
9
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.
|
|
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. All other `kxco-pq-*` packages depend on this one.
|
|
11
|
+
|
|
12
|
+
**On Node 24 and later the primitives run in OpenSSL 3.5**, not in JavaScript. Older Node and browsers use [`@noble/post-quantum`](https://github.com/paulmillr/noble-post-quantum). The two are interchangeable on the wire, which is checked rather than assumed: the interoperability matrix runs in full against both, and every report records which one produced it.
|
|
11
13
|
|
|
12
14
|
**Evidence, not adjectives:**
|
|
13
15
|
|
|
14
16
|
- [CONFORMANCE.md](./CONFORMANCE.md): NIST ACVP vectors for FIPS 203/204/205 (2,103 tests, 0 failed), and a cross-implementation interop matrix against liboqs, Bouncy Castle and two pure-Python implementations (225 checks, 0 failed, both directions, with negative controls), run against both backends. Reproducible: `npm run conformance:acvp`, `npm run conformance:interop`.
|
|
15
|
-
- [BENCHMARKS.md](./BENCHMARKS.md): per-algorithm latency at p95/p99, plus memory. Two figures worth designing around: ML-DSA signing
|
|
17
|
+
- [BENCHMARKS.md](./BENCHMARKS.md): per-algorithm latency at p95/p99 on both backends and on x86-64 and arm64, plus memory. Two figures worth designing around: ML-DSA signing keeps a rejection-sampling tail on either backend (5.1x median-to-p99 in JavaScript, 3.5x on OpenSSL), and SLH-DSA-SHA2-192s signs in seconds rather than milliseconds (4.3 s and 1.7 s).
|
|
16
18
|
- [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.
|
|
17
19
|
- [MIGRATION.md](./MIGRATION.md): moving an RSA or ECDSA system across, and moving between versions of this package.
|
|
18
|
-
- [SECURITY.md](./SECURITY.md): reporting, and
|
|
20
|
+
- [SECURITY.md](./SECURITY.md): reporting, release integrity, and the dependency policy.
|
|
21
|
+
- **Every release is reproducible and attested.** The published tarball rebuilds bit-for-bit from its own tag, verified in CI on every run, and each release carries a SLSA provenance attestation plus a CycloneDX SBOM at a permanent unauthenticated URL. A provenance attestation says a build happened in CI; the reproducible build says the artefact is the source. They are different claims and both are checkable without asking us for anything.
|
|
19
22
|
|
|
20
23
|
---
|
|
21
24
|
|
|
@@ -132,7 +135,7 @@ separation.
|
|
|
132
135
|
|
|
133
136
|
| Export | Signature | Description |
|
|
134
137
|
|---|---|---|
|
|
135
|
-
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'ml-dsa-65-v1'`. |
|
|
138
|
+
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey, seed }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'ml-dsa-65-v1'`. `seed` is the 32 bytes the pair was expanded from — see [`seed`](#seed--seed-form-keys-rfc-9964-lamps). |
|
|
136
139
|
| `sign` | `(secretKey, message) → string` | Signs a message. Returns a hex-encoded signature (6618 chars). |
|
|
137
140
|
| `verify` | `(publicKey, message, sigHex) → boolean` | Verifies a hex-encoded signature. Returns `false` on any failure. |
|
|
138
141
|
| `ml_dsa65` | raw primitive | The underlying `@noble/post-quantum` primitive, re-exported. |
|
|
@@ -156,7 +159,7 @@ Hash-based, stateless signatures. Security Category 3 (matching ML-DSA-65), but
|
|
|
156
159
|
|
|
157
160
|
| Export | Signature | Description |
|
|
158
161
|
|---|---|---|
|
|
159
|
-
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'ml-kem-768-v1'`. |
|
|
162
|
+
| `keypairFromMaster` | `(master, info?) → { publicKey, secretKey, seed }` | Deterministic keypair via HKDF-SHA-512. `info` defaults to `'ml-kem-768-v1'`. `seed` is the 64 bytes the pair was expanded from. |
|
|
160
163
|
| `encapsulate` | `(publicKey) → { ciphertext, sharedSecret }` | Generates a shared secret and ciphertext to send to the key holder. |
|
|
161
164
|
| `decapsulate` | `(ciphertext, secretKey) → Buffer` | Recovers the shared secret from a ciphertext. Returns 32 bytes. |
|
|
162
165
|
| `ml_kem768` | raw primitive | The underlying `@noble/post-quantum` primitive, re-exported. |
|
|
@@ -175,6 +178,70 @@ Constant-time comparison of two kid strings. Use this when comparing user-suppli
|
|
|
175
178
|
|
|
176
179
|
HKDF-SHA-512 derivation. `master` must be at least 16 bytes. `info` is a required domain-separation string. Returns `length` bytes.
|
|
177
180
|
|
|
181
|
+
### `seed` — seed-form keys (RFC 9964, LAMPS)
|
|
182
|
+
|
|
183
|
+
FIPS 203 and 204 expand a keypair from a short seed. The expanded private key
|
|
184
|
+
this package returns is derived from that seed and does not contain it, so a
|
|
185
|
+
seed cannot be recovered from an expanded key. `keypairFromMaster` therefore
|
|
186
|
+
returns the seed it derived alongside the pair — additive, so callers that
|
|
187
|
+
destructure `{ publicKey, secretKey }` are unaffected.
|
|
188
|
+
|
|
189
|
+
Seed form is 32 bytes for ML-DSA and 64 for ML-KEM. It fits in a KMS secret, an
|
|
190
|
+
HSM object or an env var, and it is the only private form an RFC 9964 AKP JWK
|
|
191
|
+
accepts.
|
|
192
|
+
|
|
193
|
+
| Export | Signature | Description |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `SEED_ALGORITHMS` | `string[]` | `ML-DSA-65`, `ML-DSA-87`, `ML-KEM-768`, `ML-KEM-1024`. SLH-DSA has no seed form. |
|
|
196
|
+
| `seedFromMaster` | `(alg, master, info?) → bytes` | Same HKDF-SHA-512 derivation `keypairFromMaster` uses, so it reproduces keys already in production. |
|
|
197
|
+
| `keypairFromSeed` | `(alg, seed) → { publicKey, secretKey, seed }` | Expands a seed. Byte-identical to what OpenSSL 3.5 derives from the same seed. |
|
|
198
|
+
| `exportJwk` | `(alg, { publicKey, seed? }, opts?) → jwk` | RFC 9964 AKP JWK. `priv` carries the seed. |
|
|
199
|
+
| `importJwk` | `(jwk) → { alg, publicKey, secretKey?, seed? }` | Rejects a JWK whose seed and `pub` disagree. |
|
|
200
|
+
| `exportSeedPkcs8` | `(alg, seed) → bytes` | PKCS#8 in LAMPS seed form, the `[0] IMPLICIT` CHOICE. |
|
|
201
|
+
| `importSeedPkcs8` | `(der) → { alg, seed }` | Refuses an expanded-form key rather than truncating it into a seed. |
|
|
202
|
+
|
|
203
|
+
The encodings are not read off the specification. `test/seed.test.js` asserts
|
|
204
|
+
that the DER this package writes is byte-identical to what this machine's
|
|
205
|
+
OpenSSL writes, for every parameter set, and skips with a reason where there is
|
|
206
|
+
no native backend to compare against.
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
import { mlDsa, seed } from 'kxco-post-quantum'
|
|
210
|
+
|
|
211
|
+
const key = mlDsa.keypairFromMaster(process.env.KXCO_MASTER_KEY)
|
|
212
|
+
const jwk = seed.exportJwk('ML-DSA-65', key, { kid: fingerprint(key.publicKey) })
|
|
213
|
+
// { kty: 'AKP', alg: 'ML-DSA-65', pub: '...', priv: '<32-byte seed>', kid: '...' }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `jws` — compact JWS with the RFC 9964 algorithm names
|
|
217
|
+
|
|
218
|
+
Format only. A token signed here verifies in any process holding the public
|
|
219
|
+
key, offline, with no configuration and no licence. RFC 9964 registered
|
|
220
|
+
`ML-DSA-65` and `ML-DSA-87` as JWS algorithms so a post-quantum signature can
|
|
221
|
+
travel the path an institution's gateway, IdP and partner verifier already
|
|
222
|
+
parse.
|
|
223
|
+
|
|
224
|
+
| Export | Signature | Description |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `signJws` | `(payload, secretKey, opts?) → string` | Compact JWS. Objects are JSON-serialised. `opts.alg` defaults to `ML-DSA-65`. |
|
|
227
|
+
| `verifyJws` | `(token, publicKey, opts?) → { valid, ... }` | Fails closed. `{ alg }` and `{ kid }` pin what the header may declare. |
|
|
228
|
+
| `decodeJwsHeader` | `(token) → object \| null` | Unauthenticated read, for choosing which key to fetch. |
|
|
229
|
+
|
|
230
|
+
The algorithm is resolved from an allowlist inside the module, never from the
|
|
231
|
+
token, so a token cannot name its own verification routine. `crit` and `b64`
|
|
232
|
+
headers are refused rather than ignored, and the public key's length must match
|
|
233
|
+
the algorithm the header declares.
|
|
234
|
+
|
|
235
|
+
There is no SLH-DSA option here: FIPS 205 signing takes on the order of a
|
|
236
|
+
second and a half, which does not belong on a request path.
|
|
237
|
+
|
|
238
|
+
### `backend()` and `isNative(alg)`
|
|
239
|
+
|
|
240
|
+
Reports which implementation is doing the maths in this process — `openssl`
|
|
241
|
+
with its version and parameter sets, or `javascript` with the reason the native
|
|
242
|
+
backend is unavailable. For evidence bundles and support tickets. It reports;
|
|
243
|
+
there is deliberately no way to switch backend from here.
|
|
244
|
+
|
|
178
245
|
### `webhook` — hybrid HMAC + ML-DSA-65 delivery signing
|
|
179
246
|
|
|
180
247
|
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`.
|
|
@@ -186,7 +253,6 @@ Low-level helpers for the KXCO hybrid webhook pattern: `envelope`, `hmacHex`, `v
|
|
|
186
253
|
- No identity credentials or verifiable claims (those are in `kxco-pq-sdk`)
|
|
187
254
|
- No relay, transport, or network layer
|
|
188
255
|
- No key storage or KMS integration
|
|
189
|
-
- No FIPS 140-3 module validation (the algorithms are FIPS-standardised; the module is not validated)
|
|
190
256
|
|
|
191
257
|
---
|
|
192
258
|
|