@quantakrypto/sieve 0.1.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/README.md +227 -0
- package/dist/categories/correctness.d.ts +13 -0
- package/dist/categories/correctness.d.ts.map +1 -0
- package/dist/categories/correctness.js +78 -0
- package/dist/categories/correctness.js.map +1 -0
- package/dist/categories/determinism.d.ts +12 -0
- package/dist/categories/determinism.d.ts.map +1 -0
- package/dist/categories/determinism.js +78 -0
- package/dist/categories/determinism.js.map +1 -0
- package/dist/categories/dsa.d.ts +17 -0
- package/dist/categories/dsa.d.ts.map +1 -0
- package/dist/categories/dsa.js +17 -0
- package/dist/categories/dsa.js.map +1 -0
- package/dist/categories/helpers.d.ts +50 -0
- package/dist/categories/helpers.d.ts.map +1 -0
- package/dist/categories/helpers.js +127 -0
- package/dist/categories/helpers.js.map +1 -0
- package/dist/categories/implicit-rejection.d.ts +29 -0
- package/dist/categories/implicit-rejection.d.ts.map +1 -0
- package/dist/categories/implicit-rejection.js +126 -0
- package/dist/categories/implicit-rejection.js.map +1 -0
- package/dist/categories/index.d.ts +30 -0
- package/dist/categories/index.d.ts.map +1 -0
- package/dist/categories/index.js +33 -0
- package/dist/categories/index.js.map +1 -0
- package/dist/categories/kat.d.ts +21 -0
- package/dist/categories/kat.d.ts.map +1 -0
- package/dist/categories/kat.js +173 -0
- package/dist/categories/kat.js.map +1 -0
- package/dist/categories/robustness.d.ts +15 -0
- package/dist/categories/robustness.d.ts.map +1 -0
- package/dist/categories/robustness.js +98 -0
- package/dist/categories/robustness.js.map +1 -0
- package/dist/categories/signature.d.ts +29 -0
- package/dist/categories/signature.d.ts.map +1 -0
- package/dist/categories/signature.js +259 -0
- package/dist/categories/signature.js.map +1 -0
- package/dist/categories/sizes.d.ts +23 -0
- package/dist/categories/sizes.d.ts.map +1 -0
- package/dist/categories/sizes.js +150 -0
- package/dist/categories/sizes.js.map +1 -0
- package/dist/categories/slh-dsa.d.ts +23 -0
- package/dist/categories/slh-dsa.d.ts.map +1 -0
- package/dist/categories/slh-dsa.js +23 -0
- package/dist/categories/slh-dsa.js.map +1 -0
- package/dist/categories/timing.d.ts +16 -0
- package/dist/categories/timing.d.ts.map +1 -0
- package/dist/categories/timing.js +98 -0
- package/dist/categories/timing.js.map +1 -0
- package/dist/categories/types.d.ts +68 -0
- package/dist/categories/types.d.ts.map +1 -0
- package/dist/categories/types.js +31 -0
- package/dist/categories/types.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +181 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +169 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +115 -0
- package/dist/protocol.js.map +1 -0
- package/dist/report.d.ts +48 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +79 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.d.ts +129 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +305 -0
- package/dist/runner.js.map +1 -0
- package/dist/sizes.d.ts +84 -0
- package/dist/sizes.d.ts.map +1 -0
- package/dist/sizes.js +214 -0
- package/dist/sizes.js.map +1 -0
- package/dist/vectors.d.ts +69 -0
- package/dist/vectors.d.ts.map +1 -0
- package/dist/vectors.js +198 -0
- package/dist/vectors.js.map +1 -0
- package/package.json +43 -0
- package/vectors/README.md +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# @quantakrypto/sieve
|
|
2
|
+
|
|
3
|
+
**Conformance battery for ML-KEM (FIPS 203), ML-DSA (FIPS 204), and SLH-DSA
|
|
4
|
+
(FIPS 205) implementations.** Sieve drives *any* implementation through a small
|
|
5
|
+
stdin/stdout JSON protocol and reports where it deviates from the standard.
|
|
6
|
+
|
|
7
|
+
Zero runtime dependencies — Node built-ins only. Node ≥ 20, ESM.
|
|
8
|
+
|
|
9
|
+
## What Sieve is (and is not)
|
|
10
|
+
|
|
11
|
+
Sieve **tests other people's implementations**. It deliberately does **not**
|
|
12
|
+
implement ML-KEM/ML-DSA/SLH-DSA, and it **never fabricates** cryptographic
|
|
13
|
+
Known-Answer-Test values. Its power comes from two honest sources:
|
|
14
|
+
|
|
15
|
+
1. **Tests that need no external vectors** — self-consistency, structural, and
|
|
16
|
+
negative/robustness checks that every conforming implementation must pass.
|
|
17
|
+
2. **A loader for official NIST ACVP / KAT vectors you supply** — exact-value
|
|
18
|
+
checks whose expected bytes come from *your* downloaded NIST files, not from
|
|
19
|
+
Sieve. See [`vectors/README.md`](./vectors/README.md).
|
|
20
|
+
|
|
21
|
+
The only hard-coded cryptographic constants in Sieve are the **public,
|
|
22
|
+
standardized parameter sizes** (e.g. ML-KEM-768 public key = 1184 bytes;
|
|
23
|
+
SLH-DSA-SHA2-128f signature = 17088 bytes, FIPS 205 Table 2). Those are not
|
|
24
|
+
secrets and not KAT values.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Within the `quantakrypto-tools` monorepo it builds with the workspace:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install
|
|
32
|
+
npm run build # tsc -b across the workspace
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The package exposes a `sieve` bin and a programmatic API.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
Sieve spawns your implementation (the **SUT**) as a child process and exchanges
|
|
40
|
+
newline-delimited JSON: one request per line in, one response per line out, all
|
|
41
|
+
byte fields base64. Full spec: [`PROTOCOL.md`](./PROTOCOL.md).
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
sieve ──request (NDJSON)──▶ your SUT process
|
|
45
|
+
◀─response (NDJSON)── (ML-KEM / ML-DSA)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick start (against the mock)
|
|
49
|
+
|
|
50
|
+
`examples/mock-sut.ts` is a **non-cryptographic** reference SUT used only to
|
|
51
|
+
exercise the harness. It satisfies the protocol with deterministic, correctly
|
|
52
|
+
sized fake bytes — **it provides no security; never use it for anything real.**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# run the full ML-KEM-768 battery against the mock
|
|
56
|
+
node --import tsx src/cli.ts \
|
|
57
|
+
--impl "node --import tsx examples/mock-sut.ts" \
|
|
58
|
+
--param ml-kem-768 --iterations 32
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
After `npm run build` the bin runs directly:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
sieve --impl "node ./my-impl.js" --param ml-kem-768 --iterations 64 --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Pointing Sieve at a REAL implementation
|
|
68
|
+
|
|
69
|
+
Write a thin **adapter** that speaks the protocol and calls a vetted library.
|
|
70
|
+
The adapter is the SUT; Sieve never bundles a crypto implementation.
|
|
71
|
+
|
|
72
|
+
Example sketch using a Node binding (e.g. a WASM/native ML-KEM library):
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
// my-impl.js — adapter only; the crypto comes from a vetted dependency.
|
|
76
|
+
import { createInterface } from "node:readline";
|
|
77
|
+
import { mlKem768 } from "some-vetted-pqc-lib"; // <- you choose & audit this
|
|
78
|
+
|
|
79
|
+
const b64 = (u8) => Buffer.from(u8).toString("base64");
|
|
80
|
+
const un = (s) => new Uint8Array(Buffer.from(s, "base64"));
|
|
81
|
+
|
|
82
|
+
createInterface({ input: process.stdin }).on("line", (line) => {
|
|
83
|
+
if (!line.trim()) return;
|
|
84
|
+
const r = JSON.parse(line);
|
|
85
|
+
const out = (o) => process.stdout.write(JSON.stringify({ id: r.id, ok: true, ...o }) + "\n");
|
|
86
|
+
const err = (code, message) => process.stdout.write(JSON.stringify({ id: r.id, ok: false, code, message }) + "\n");
|
|
87
|
+
try {
|
|
88
|
+
if (r.op === "keygen") { const { publicKey, secretKey } = mlKem768.keygen(); return out({ pk: b64(publicKey), sk: b64(secretKey) }); }
|
|
89
|
+
if (r.op === "encaps") { const { cipherText, sharedSecret } = mlKem768.encapsulate(un(r.pk)); return out({ ct: b64(cipherText), ss: b64(sharedSecret) }); }
|
|
90
|
+
if (r.op === "decaps") { const ss = mlKem768.decapsulate(un(r.ct), un(r.sk)); return out({ ss: b64(ss) }); }
|
|
91
|
+
return err("unsupported", `op ${r.op}`);
|
|
92
|
+
} catch (e) { err("error", String(e?.message ?? e)); }
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
sieve --impl "node ./my-impl.js" --param ml-kem-768
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
For exact-value KAT, also pass `--vectors <dir>` with official NIST files.
|
|
103
|
+
|
|
104
|
+
## CLI
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
sieve --impl "<command...>" --param <set> [options]
|
|
108
|
+
|
|
109
|
+
--impl "<cmd...>" command Sieve spawns and drives (quote the whole thing)
|
|
110
|
+
--param <set> ml-kem-512 | ml-kem-768 | ml-kem-1024 |
|
|
111
|
+
ml-dsa-44 | ml-dsa-65 | ml-dsa-87 |
|
|
112
|
+
slh-dsa-{sha2,shake}-{128,192,256}{s,f} (12 sets)
|
|
113
|
+
--iterations <N> randomized iterations (default 32)
|
|
114
|
+
--vectors <dir> official NIST ACVP vector dir for KAT (else SKIPPED)
|
|
115
|
+
--timing include the advisory decaps-timing probe
|
|
116
|
+
--only <a,b,...> run only these categories
|
|
117
|
+
--timeout-ms <N> per-request timeout (default 10000)
|
|
118
|
+
--pipeline-depth <N> max concurrent in-flight requests for independent-iteration
|
|
119
|
+
categories (default 16; 1 = strictly serial)
|
|
120
|
+
--inherit-env pass the FULL parent env to the SUT (DANGEROUS; see below)
|
|
121
|
+
--json machine-readable report
|
|
122
|
+
|
|
123
|
+
exit: 0 = PASS, 1 = FAIL, 2 = usage error
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Programmatic API
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { runSieve, formatHuman } from "@quantakrypto/sieve";
|
|
130
|
+
|
|
131
|
+
const report = await runSieve({
|
|
132
|
+
command: ["node", "./my-impl.js"],
|
|
133
|
+
param: "ml-kem-768",
|
|
134
|
+
iterations: 64,
|
|
135
|
+
// vectorsDir: "./vectors/ml-kem-768",
|
|
136
|
+
});
|
|
137
|
+
console.log(formatHuman(report));
|
|
138
|
+
process.exit(report.overall === "PASS" ? 0 : 1);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Category catalog
|
|
142
|
+
|
|
143
|
+
| Category | Family | Bug class | What it checks |
|
|
144
|
+
|----------------------|---------|-----------|----------------|
|
|
145
|
+
| `correctness` | ML-KEM | — | keygen→encaps(pk)→decaps(sk,ct) ⇒ `ss_encaps === ss_decaps` over N iterations. |
|
|
146
|
+
| `determinism` | ML-KEM | — | repeated `decaps(sk, ct)` returns identical `ss`. |
|
|
147
|
+
| `implicit-rejection` | ML-KEM | **AF-02** | `decaps` of a corrupted ct returns a valid-length `ss` with **no error/crash**, is deterministic, and differs from the honest `ss`. No exact-value assertions. |
|
|
148
|
+
| `sizes` | ML-KEM | **AF-05** | emitted pk/sk/ct/ss lengths match the set; wrong-length pk/ct/sk are rejected with a defined error; **FIPS 203 §7.2 ek modulus-range check** — a correctly-sized-but-out-of-range encapsulation key (`t̂` coefficient ≥ q) is rejected. |
|
|
149
|
+
| `robustness` | ML-KEM | AF-05 | empty / non-base64 / oversized inputs ⇒ defined error, never crash/silent-accept. |
|
|
150
|
+
| `dsa` | ML-DSA | AF-05 | sign→verify ⇒ true; tampered message/signature ⇒ false; wrong message ⇒ false; pk/sk/sig sizes for ml-dsa-44/65/87; wrong-length verify input rejected; **deterministic-vs-hedged signing advisory** (surfaced, never fails). |
|
|
151
|
+
| `slh-dsa` | SLH-DSA | AF-05 | same self-consistency surface as `dsa` for the 12 FIPS 205 parameter sets: sign→verify, tamper checks, pk/sk/sig sizes, signing-mode advisory, wrong-length verify rejection. |
|
|
152
|
+
| `kat` | all | — | **SKIPPED** unless `--vectors <dir>` of official ACVP files is given; then checks decaps/seeded-keygen/seeded-encaps/sigVer against those exact expected values. Never fabricated. |
|
|
153
|
+
| `timing` | ML-KEM | (AF-02) | **ADVISORY only.** Coarse decaps timing for valid vs. invalid ct. Reports a signal but never changes the verdict — cross-process timing is noisy and not proof of (non-)constant-time behavior. |
|
|
154
|
+
|
|
155
|
+
The overall verdict is **FAIL** if any non-advisory category fails, else
|
|
156
|
+
**PASS**. A `skip` (e.g. KAT with no vectors) never causes a failure.
|
|
157
|
+
|
|
158
|
+
## Honest stance on KAT vectors
|
|
159
|
+
|
|
160
|
+
Sieve ships **no** test vectors. Hard-coding "expected" ciphertext or
|
|
161
|
+
shared-secret bytes would be both a maintenance hazard and a correctness lie if
|
|
162
|
+
ever mistyped. Instead:
|
|
163
|
+
|
|
164
|
+
- Self-consistency and structural categories give strong coverage with no
|
|
165
|
+
external data.
|
|
166
|
+
- For exact-value conformance, download the **official NIST ACVP vectors** and
|
|
167
|
+
pass `--vectors`. The loader (`src/vectors.ts`) parses the standard ACVP JSON.
|
|
168
|
+
|
|
169
|
+
See [`vectors/README.md`](./vectors/README.md) for sources and the file format.
|
|
170
|
+
|
|
171
|
+
## Bug classes
|
|
172
|
+
|
|
173
|
+
- **AF-02** — implicit-rejection / Fujisaki–Okamoto reject-path mistakes
|
|
174
|
+
(erroring on bad ct, constant/unkeyed reject secrets, non-determinism).
|
|
175
|
+
- **AF-05** — size/format confusion (accepting or emitting wrong-length
|
|
176
|
+
artifacts, crashing on malformed input, accepting an out-of-range `ek`).
|
|
177
|
+
|
|
178
|
+
## Security: the SUT runs with a scrubbed environment
|
|
179
|
+
|
|
180
|
+
The SUT is **code you point Sieve at** — frequently a third-party or candidate
|
|
181
|
+
implementation. Sieve drives it with the harness's own OS privileges, so by
|
|
182
|
+
default Sieve spawns it with a **scrubbed, minimal environment**: only a small
|
|
183
|
+
allow-list (`PATH`, `HOME`, `LANG`, locale/temp vars, plus Windows spawn
|
|
184
|
+
essentials) is copied from the parent process. Harness secrets in the parent env
|
|
185
|
+
— CI tokens, `NPM_TOKEN`, cloud credentials, signing keys — are **not**
|
|
186
|
+
forwarded, so an untrusted SUT cannot read them.
|
|
187
|
+
|
|
188
|
+
- Pass explicit variables your SUT needs with `env` (programmatic) — these are
|
|
189
|
+
layered on top of the scrubbed base.
|
|
190
|
+
- Widen the allow-list with `envAllowlist` (copy named parent vars).
|
|
191
|
+
- `--inherit-env` / `inheritEnv: true` restores the **full** parent environment.
|
|
192
|
+
Use it only for trusted, local implementations you control.
|
|
193
|
+
|
|
194
|
+
Treat any non-trusted SUT as you would untrusted code generally — sandbox it
|
|
195
|
+
(container / seccomp) if its provenance is unknown.
|
|
196
|
+
|
|
197
|
+
## Throughput: bounded-concurrency pipelining
|
|
198
|
+
|
|
199
|
+
The SUT protocol is **id-correlated** (each response echoes its request `id`), so
|
|
200
|
+
independent requests can be in flight at once. Categories whose iterations are
|
|
201
|
+
mutually independent (`correctness`, `determinism`, `dsa`, `slh-dsa`) pipeline up
|
|
202
|
+
to `--pipeline-depth` (default 16) requests against the single SUT process; the
|
|
203
|
+
results are reassembled in request order so the verdict is identical to a serial
|
|
204
|
+
run. Dependent steps within one iteration (e.g. keygen→encaps→decaps for a single
|
|
205
|
+
key) and order-sensitive categories (`timing`) stay strictly serial. Set
|
|
206
|
+
`--pipeline-depth 1` to force the legacy serial behavior.
|
|
207
|
+
|
|
208
|
+
## Algorithm scope
|
|
209
|
+
|
|
210
|
+
Sieve drives the three **stateless** NIST PQC schemes: ML-KEM (FIPS 203), ML-DSA
|
|
211
|
+
(FIPS 204), and SLH-DSA (FIPS 205). The 12 standardized SLH-DSA parameter sets
|
|
212
|
+
(`slh-dsa-{sha2,shake}-{128,192,256}{s,f}`) are supported with size and
|
|
213
|
+
self-consistency categories; their public byte sizes come from FIPS 205 Table 2.
|
|
214
|
+
|
|
215
|
+
**Out of scope: SP 800-208 stateful hash signatures (LMS / XMSS / HSS).** These
|
|
216
|
+
are deliberately *not* supported. Their security depends on **one-time use of
|
|
217
|
+
each leaf key** — the signer must persist and advance secret state across signing
|
|
218
|
+
operations, and reusing state catastrophically breaks the scheme. A stateless,
|
|
219
|
+
request/response conformance harness like Sieve cannot model or verify the
|
|
220
|
+
state-management guarantees that dominate SP 800-208 correctness; doing so safely
|
|
221
|
+
requires a different, state-aware harness with durable per-key state tracking.
|
|
222
|
+
LMS/XMSS are also intended for narrow uses (firmware/boot signing), not the
|
|
223
|
+
general PQC migration Sieve targets.
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
Apache-2.0.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correctness category (ML-KEM).
|
|
3
|
+
*
|
|
4
|
+
* The fundamental KEM round-trip: for N independent key pairs,
|
|
5
|
+
* keygen -> encaps(pk) -> decaps(sk, ct)
|
|
6
|
+
* must yield ss_encaps === ss_decaps. This needs no external vectors: it is a
|
|
7
|
+
* self-consistency property every conforming implementation must satisfy.
|
|
8
|
+
*
|
|
9
|
+
* (ML-DSA correctness — sign then verify === true — lives in dsa.ts.)
|
|
10
|
+
*/
|
|
11
|
+
import { type Category } from "./types.js";
|
|
12
|
+
export declare const correctness: Category;
|
|
13
|
+
//# sourceMappingURL=correctness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correctness.d.ts","sourceRoot":"","sources":["../../src/categories/correctness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAuD,MAAM,YAAY,CAAC;AAkBhG,eAAO,MAAM,WAAW,EAAE,QA6EzB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correctness category (ML-KEM).
|
|
3
|
+
*
|
|
4
|
+
* The fundamental KEM round-trip: for N independent key pairs,
|
|
5
|
+
* keygen -> encaps(pk) -> decaps(sk, ct)
|
|
6
|
+
* must yield ss_encaps === ss_decaps. This needs no external vectors: it is a
|
|
7
|
+
* self-consistency property every conforming implementation must satisfy.
|
|
8
|
+
*
|
|
9
|
+
* (ML-DSA correctness — sign then verify === true — lives in dsa.ts.)
|
|
10
|
+
*/
|
|
11
|
+
import { fail, pass, rollUp } from "./types.js";
|
|
12
|
+
import { bytesEqual, kemDecaps, kemEncaps, kemKeygen, mapBounded, requireKem, UnexpectedResponse, } from "./helpers.js";
|
|
13
|
+
import { toB64 } from "../protocol.js";
|
|
14
|
+
export const correctness = async (ctx) => {
|
|
15
|
+
const checks = [];
|
|
16
|
+
if (ctx.sizes.family !== "ml-kem") {
|
|
17
|
+
return {
|
|
18
|
+
category: "correctness",
|
|
19
|
+
status: "skip",
|
|
20
|
+
checks: [],
|
|
21
|
+
summary: "correctness round-trip is defined for ML-KEM; see DSA category for ML-DSA",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const km = requireKem(ctx.sizes);
|
|
25
|
+
const param = km.id;
|
|
26
|
+
let mismatches = 0;
|
|
27
|
+
let errored = 0;
|
|
28
|
+
// Iterations are mutually independent (each builds its own keypair), so run
|
|
29
|
+
// several chains concurrently bounded by pipelineDepth. The keygen→encaps→
|
|
30
|
+
// decaps steps WITHIN an iteration stay serial (they are dependent).
|
|
31
|
+
const outcomes = await mapBounded(ctx.iterations, ctx.pipelineDepth ?? 16, async () => {
|
|
32
|
+
try {
|
|
33
|
+
const { pk, sk } = await kemKeygen(ctx.runner, param);
|
|
34
|
+
const { ct, ss: ssEncaps } = await kemEncaps(ctx.runner, param, toB64(pk));
|
|
35
|
+
const ssDecaps = await kemDecaps(ctx.runner, param, toB64(sk), toB64(ct));
|
|
36
|
+
if (!bytesEqual(ssEncaps, ssDecaps)) {
|
|
37
|
+
return {
|
|
38
|
+
kind: "mismatch",
|
|
39
|
+
detail: `shared secrets differ: encaps ss != decaps ss ` +
|
|
40
|
+
`(encaps=${toB64(ssEncaps).slice(0, 12)}…, decaps=${toB64(ssDecaps).slice(0, 12)}…)`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { kind: "ok" };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const detail = err instanceof UnexpectedResponse
|
|
47
|
+
? `SUT returned an unexpected response: ${err.message}`
|
|
48
|
+
: `harness error: ${err.message}`;
|
|
49
|
+
return { kind: "error", detail };
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
for (let i = 0; i < outcomes.length; i++) {
|
|
53
|
+
const o = outcomes[i];
|
|
54
|
+
if (o.kind === "mismatch") {
|
|
55
|
+
mismatches++;
|
|
56
|
+
if (mismatches <= 3)
|
|
57
|
+
checks.push(fail(`roundtrip[${i}]`, o.detail));
|
|
58
|
+
}
|
|
59
|
+
else if (o.kind === "error") {
|
|
60
|
+
errored++;
|
|
61
|
+
if (errored <= 3)
|
|
62
|
+
checks.push(fail(`roundtrip[${i}]`, o.detail));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (mismatches === 0 && errored === 0) {
|
|
66
|
+
checks.push(pass("roundtrip", `${ctx.iterations}/${ctx.iterations} keygen→encaps→decaps round-trips produced matching shared secrets`));
|
|
67
|
+
}
|
|
68
|
+
const status = rollUp(checks);
|
|
69
|
+
return {
|
|
70
|
+
category: "correctness",
|
|
71
|
+
status,
|
|
72
|
+
checks,
|
|
73
|
+
summary: status === "pass"
|
|
74
|
+
? `${ctx.iterations} round-trips OK`
|
|
75
|
+
: `${mismatches} mismatch(es), ${errored} error(s) over ${ctx.iterations} iterations`,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=correctness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correctness.js","sourceRoot":"","sources":["../../src/categories/correctness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determinism category (ML-KEM).
|
|
3
|
+
*
|
|
4
|
+
* Decapsulation is a deterministic function of (sk, ct): calling decaps twice
|
|
5
|
+
* with identical inputs must return identical shared secrets. A SUT that mixes
|
|
6
|
+
* fresh randomness into decaps (a real-world implementation smell) fails here.
|
|
7
|
+
*
|
|
8
|
+
* This is a self-consistency property; no external vectors are required.
|
|
9
|
+
*/
|
|
10
|
+
import { type Category } from "./types.js";
|
|
11
|
+
export declare const determinism: Category;
|
|
12
|
+
//# sourceMappingURL=determinism.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"determinism.d.ts","sourceRoot":"","sources":["../../src/categories/determinism.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,QAAQ,EAAuD,MAAM,YAAY,CAAC;AAiBhG,eAAO,MAAM,WAAW,EAAE,QAkFzB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determinism category (ML-KEM).
|
|
3
|
+
*
|
|
4
|
+
* Decapsulation is a deterministic function of (sk, ct): calling decaps twice
|
|
5
|
+
* with identical inputs must return identical shared secrets. A SUT that mixes
|
|
6
|
+
* fresh randomness into decaps (a real-world implementation smell) fails here.
|
|
7
|
+
*
|
|
8
|
+
* This is a self-consistency property; no external vectors are required.
|
|
9
|
+
*/
|
|
10
|
+
import { fail, pass, rollUp } from "./types.js";
|
|
11
|
+
import { bytesEqual, kemDecaps, kemEncaps, kemKeygen, mapBounded, requireKem, UnexpectedResponse, } from "./helpers.js";
|
|
12
|
+
import { toB64 } from "../protocol.js";
|
|
13
|
+
const REPEATS = 3;
|
|
14
|
+
export const determinism = async (ctx) => {
|
|
15
|
+
const checks = [];
|
|
16
|
+
if (ctx.sizes.family !== "ml-kem") {
|
|
17
|
+
return {
|
|
18
|
+
category: "determinism",
|
|
19
|
+
status: "skip",
|
|
20
|
+
checks: [],
|
|
21
|
+
summary: "decaps-determinism is defined for ML-KEM",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const param = requireKem(ctx.sizes).id;
|
|
25
|
+
let nondeterministic = 0;
|
|
26
|
+
let errored = 0;
|
|
27
|
+
// Iterations are independent → run concurrently (bounded). The REPEATS decaps
|
|
28
|
+
// calls within an iteration MUST stay serial: determinism is a property of
|
|
29
|
+
// repeated identical calls, so we issue them one after another on purpose.
|
|
30
|
+
const outcomes = await mapBounded(ctx.iterations, ctx.pipelineDepth ?? 16, async () => {
|
|
31
|
+
try {
|
|
32
|
+
const { pk, sk } = await kemKeygen(ctx.runner, param);
|
|
33
|
+
const { ct } = await kemEncaps(ctx.runner, param, toB64(pk));
|
|
34
|
+
const skB64 = toB64(sk);
|
|
35
|
+
const ctB64 = toB64(ct);
|
|
36
|
+
const first = await kemDecaps(ctx.runner, param, skB64, ctB64);
|
|
37
|
+
for (let r = 1; r < REPEATS; r++) {
|
|
38
|
+
const again = await kemDecaps(ctx.runner, param, skB64, ctB64);
|
|
39
|
+
if (!bytesEqual(first, again))
|
|
40
|
+
return { kind: "unstable" };
|
|
41
|
+
}
|
|
42
|
+
return { kind: "ok" };
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const detail = err instanceof UnexpectedResponse
|
|
46
|
+
? `SUT returned an unexpected response: ${err.message}`
|
|
47
|
+
: `harness error: ${err.message}`;
|
|
48
|
+
return { kind: "error", detail };
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
for (let i = 0; i < outcomes.length; i++) {
|
|
52
|
+
const o = outcomes[i];
|
|
53
|
+
if (o.kind === "unstable") {
|
|
54
|
+
nondeterministic++;
|
|
55
|
+
if (nondeterministic <= 3) {
|
|
56
|
+
checks.push(fail(`decaps-stable[${i}]`, `decaps(sk, ct) returned different shared secrets across ${REPEATS} identical calls`));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (o.kind === "error") {
|
|
60
|
+
errored++;
|
|
61
|
+
if (errored <= 3)
|
|
62
|
+
checks.push(fail(`decaps-stable[${i}]`, o.detail));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (nondeterministic === 0 && errored === 0) {
|
|
66
|
+
checks.push(pass("decaps-stable", `decaps was deterministic across ${REPEATS} repeats for all ${ctx.iterations} ciphertexts`));
|
|
67
|
+
}
|
|
68
|
+
const status = rollUp(checks);
|
|
69
|
+
return {
|
|
70
|
+
category: "determinism",
|
|
71
|
+
status,
|
|
72
|
+
checks,
|
|
73
|
+
summary: status === "pass"
|
|
74
|
+
? `decaps deterministic over ${ctx.iterations} ciphertexts`
|
|
75
|
+
: `${nondeterministic} non-deterministic, ${errored} error(s)`,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=determinism.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"determinism.js","sourceRoot":"","sources":["../../src/categories/determinism.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ML-DSA self-consistency category (FIPS 204).
|
|
3
|
+
*
|
|
4
|
+
* Thin binding over the shared signature driver in signature.ts. ML-DSA and
|
|
5
|
+
* SLH-DSA present the same black-box surface (keygen / sign / verify), so the
|
|
6
|
+
* driver is shared; this category fixes the family to "ml-dsa". It covers:
|
|
7
|
+
* - pk/sk/sig size conformance for ml-dsa-44/65/87,
|
|
8
|
+
* - sign→verify correctness round-trips,
|
|
9
|
+
* - verify rejects a tampered signature and a wrong message,
|
|
10
|
+
* - a deterministic-vs-hedged signing advisory probe,
|
|
11
|
+
* - wrong-length verify inputs rejected with a defined error (AF-05).
|
|
12
|
+
*
|
|
13
|
+
* Exact-value KAT (sigVer vectors) is handled separately by the kat category.
|
|
14
|
+
*/
|
|
15
|
+
import type { Category } from "./types.js";
|
|
16
|
+
export declare const dsa: Category;
|
|
17
|
+
//# sourceMappingURL=dsa.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsa.d.ts","sourceRoot":"","sources":["../../src/categories/dsa.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,eAAO,MAAM,GAAG,EAAE,QAAiD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ML-DSA self-consistency category (FIPS 204).
|
|
3
|
+
*
|
|
4
|
+
* Thin binding over the shared signature driver in signature.ts. ML-DSA and
|
|
5
|
+
* SLH-DSA present the same black-box surface (keygen / sign / verify), so the
|
|
6
|
+
* driver is shared; this category fixes the family to "ml-dsa". It covers:
|
|
7
|
+
* - pk/sk/sig size conformance for ml-dsa-44/65/87,
|
|
8
|
+
* - sign→verify correctness round-trips,
|
|
9
|
+
* - verify rejects a tampered signature and a wrong message,
|
|
10
|
+
* - a deterministic-vs-hedged signing advisory probe,
|
|
11
|
+
* - wrong-length verify inputs rejected with a defined error (AF-05).
|
|
12
|
+
*
|
|
13
|
+
* Exact-value KAT (sigVer vectors) is handled separately by the kat category.
|
|
14
|
+
*/
|
|
15
|
+
import { makeSignatureCategory } from "./signature.js";
|
|
16
|
+
export const dsa = makeSignatureCategory("dsa", "ml-dsa");
|
|
17
|
+
//# sourceMappingURL=dsa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsa.js","sourceRoot":"","sources":["../../src/categories/dsa.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,CAAC,MAAM,GAAG,GAAa,qBAAqB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by the test categories: typed request wrappers that drive a
|
|
3
|
+
* Runner, plus small byte utilities. No cryptography — these just move bytes.
|
|
4
|
+
*/
|
|
5
|
+
import { type Response } from "../protocol.js";
|
|
6
|
+
import type { Runner } from "../runner.js";
|
|
7
|
+
import type { KemSizes, ParamSet } from "../sizes.js";
|
|
8
|
+
/** A response we expected to succeed but did not. */
|
|
9
|
+
export declare class UnexpectedResponse extends Error {
|
|
10
|
+
readonly response: Response;
|
|
11
|
+
constructor(message: string, response: Response);
|
|
12
|
+
}
|
|
13
|
+
/** ML-KEM keygen, returning decoded pk/sk bytes. Throws on error/wrong shape. */
|
|
14
|
+
export declare function kemKeygen(runner: Runner, param: ParamSet, seed?: string): Promise<{
|
|
15
|
+
pk: Uint8Array;
|
|
16
|
+
sk: Uint8Array;
|
|
17
|
+
}>;
|
|
18
|
+
/** ML-KEM encapsulate, returning decoded ct/ss. Throws on error/wrong shape. */
|
|
19
|
+
export declare function kemEncaps(runner: Runner, param: ParamSet, pkB64: string, coins?: string): Promise<{
|
|
20
|
+
ct: Uint8Array;
|
|
21
|
+
ss: Uint8Array;
|
|
22
|
+
}>;
|
|
23
|
+
/** ML-KEM decapsulate, returning decoded ss. Throws on error/wrong shape. */
|
|
24
|
+
export declare function kemDecaps(runner: Runner, param: ParamSet, skB64: string, ctB64: string): Promise<Uint8Array>;
|
|
25
|
+
/** Raw decaps that returns the full response (used by categories that expect errors). */
|
|
26
|
+
export declare function kemDecapsRaw(runner: Runner, param: ParamSet, skB64: string, ctB64: string): Promise<Response>;
|
|
27
|
+
/** Raw encaps returning the full response (for error-path tests). */
|
|
28
|
+
export declare function kemEncapsRaw(runner: Runner, param: ParamSet, pkB64: string): Promise<Response>;
|
|
29
|
+
/** Constant-ish byte equality (length then content). Not security-critical. */
|
|
30
|
+
export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
31
|
+
/** Base64 of `n` bytes, all zero — a deterministic wrong-but-valid blob. */
|
|
32
|
+
export declare function zerosB64(n: number): string;
|
|
33
|
+
/** Flip one bit in a base64 blob, returning new base64. Used to corrupt ct. */
|
|
34
|
+
export declare function flipBitB64(b64: string, byteIndex?: number, bit?: number): string;
|
|
35
|
+
/** Assert a value is a KEM size record (narrowing). */
|
|
36
|
+
export declare function requireKem(s: {
|
|
37
|
+
family: string;
|
|
38
|
+
}): KemSizes;
|
|
39
|
+
/**
|
|
40
|
+
* Run `task(i)` for i in [0, count) with at most `limit` running concurrently,
|
|
41
|
+
* returning results in index order. Used by categories whose ITERATIONS are
|
|
42
|
+
* mutually independent (each builds its own keypair), so several iteration
|
|
43
|
+
* chains can be in flight against the id-correlated SUT at once. Dependent
|
|
44
|
+
* steps WITHIN a single iteration remain serial inside `task`.
|
|
45
|
+
*
|
|
46
|
+
* `limit <= 1` degrades to strictly serial execution. See
|
|
47
|
+
* docs/audits/performance.md §7.1.
|
|
48
|
+
*/
|
|
49
|
+
export declare function mapBounded<T>(count: number, limit: number, task: (i: number) => Promise<T>): Promise<T[]>;
|
|
50
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/categories/helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,qDAAqD;AACrD,qBAAa,kBAAmB,SAAQ,KAAK;aAGzB,QAAQ,EAAE,QAAQ;gBADlC,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,QAAQ;CAKrC;AAED,iFAAiF;AACjF,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,EACf,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,EAAE,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAAC,CAW7C;AAED,gFAAgF;AAChF,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAAC,CAY7C;AAED,6EAA6E;AAC7E,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,UAAU,CAAC,CAMrB;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,QAAQ,CAAC,CAEnB;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAE9F;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAMhE;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAI,EAAE,GAAG,SAAI,GAAG,MAAM,CAMtE;AAED,uDAAuD;AACvD,wBAAgB,UAAU,CAAC,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAK1D;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,CAAC,EAAE,CAAC,CAyBd"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by the test categories: typed request wrappers that drive a
|
|
3
|
+
* Runner, plus small byte utilities. No cryptography — these just move bytes.
|
|
4
|
+
*/
|
|
5
|
+
import { fromB64 } from "../protocol.js";
|
|
6
|
+
/** A response we expected to succeed but did not. */
|
|
7
|
+
export class UnexpectedResponse extends Error {
|
|
8
|
+
response;
|
|
9
|
+
constructor(message, response) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.response = response;
|
|
12
|
+
this.name = "UnexpectedResponse";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** ML-KEM keygen, returning decoded pk/sk bytes. Throws on error/wrong shape. */
|
|
16
|
+
export async function kemKeygen(runner, param, seed) {
|
|
17
|
+
const resp = await runner.send({
|
|
18
|
+
family: "ml-kem",
|
|
19
|
+
param,
|
|
20
|
+
op: "keygen",
|
|
21
|
+
...(seed ? { seed } : {}),
|
|
22
|
+
});
|
|
23
|
+
if (resp.ok !== true || !("pk" in resp) || !("sk" in resp)) {
|
|
24
|
+
throw new UnexpectedResponse("expected keygen pk/sk result", resp);
|
|
25
|
+
}
|
|
26
|
+
return { pk: fromB64(resp.pk), sk: fromB64(resp.sk) };
|
|
27
|
+
}
|
|
28
|
+
/** ML-KEM encapsulate, returning decoded ct/ss. Throws on error/wrong shape. */
|
|
29
|
+
export async function kemEncaps(runner, param, pkB64, coins) {
|
|
30
|
+
const resp = await runner.send({
|
|
31
|
+
family: "ml-kem",
|
|
32
|
+
param,
|
|
33
|
+
op: "encaps",
|
|
34
|
+
pk: pkB64,
|
|
35
|
+
...(coins ? { coins } : {}),
|
|
36
|
+
});
|
|
37
|
+
if (resp.ok !== true || !("ct" in resp) || !("ss" in resp)) {
|
|
38
|
+
throw new UnexpectedResponse("expected encaps ct/ss result", resp);
|
|
39
|
+
}
|
|
40
|
+
return { ct: fromB64(resp.ct), ss: fromB64(resp.ss) };
|
|
41
|
+
}
|
|
42
|
+
/** ML-KEM decapsulate, returning decoded ss. Throws on error/wrong shape. */
|
|
43
|
+
export async function kemDecaps(runner, param, skB64, ctB64) {
|
|
44
|
+
const resp = await runner.send({ family: "ml-kem", param, op: "decaps", sk: skB64, ct: ctB64 });
|
|
45
|
+
if (resp.ok !== true || !("ss" in resp)) {
|
|
46
|
+
throw new UnexpectedResponse("expected decaps ss result", resp);
|
|
47
|
+
}
|
|
48
|
+
return fromB64(resp.ss);
|
|
49
|
+
}
|
|
50
|
+
/** Raw decaps that returns the full response (used by categories that expect errors). */
|
|
51
|
+
export function kemDecapsRaw(runner, param, skB64, ctB64) {
|
|
52
|
+
return runner.send({ family: "ml-kem", param, op: "decaps", sk: skB64, ct: ctB64 });
|
|
53
|
+
}
|
|
54
|
+
/** Raw encaps returning the full response (for error-path tests). */
|
|
55
|
+
export function kemEncapsRaw(runner, param, pkB64) {
|
|
56
|
+
return runner.send({ family: "ml-kem", param, op: "encaps", pk: pkB64 });
|
|
57
|
+
}
|
|
58
|
+
/** Constant-ish byte equality (length then content). Not security-critical. */
|
|
59
|
+
export function bytesEqual(a, b) {
|
|
60
|
+
if (a.length !== b.length)
|
|
61
|
+
return false;
|
|
62
|
+
for (let i = 0; i < a.length; i++) {
|
|
63
|
+
if (a[i] !== b[i])
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/** Base64 of `n` bytes, all zero — a deterministic wrong-but-valid blob. */
|
|
69
|
+
export function zerosB64(n) {
|
|
70
|
+
return Buffer.alloc(n).toString("base64");
|
|
71
|
+
}
|
|
72
|
+
/** Flip one bit in a base64 blob, returning new base64. Used to corrupt ct. */
|
|
73
|
+
export function flipBitB64(b64, byteIndex = 0, bit = 0) {
|
|
74
|
+
const buf = Buffer.from(b64, "base64");
|
|
75
|
+
if (buf.length === 0)
|
|
76
|
+
return b64;
|
|
77
|
+
const idx = byteIndex % buf.length;
|
|
78
|
+
buf[idx] = buf[idx] ^ (1 << (bit & 7));
|
|
79
|
+
return buf.toString("base64");
|
|
80
|
+
}
|
|
81
|
+
/** Assert a value is a KEM size record (narrowing). */
|
|
82
|
+
export function requireKem(s) {
|
|
83
|
+
if (s.family !== "ml-kem") {
|
|
84
|
+
throw new Error(`expected an ML-KEM parameter set, got ${s.family}`);
|
|
85
|
+
}
|
|
86
|
+
return s;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run `task(i)` for i in [0, count) with at most `limit` running concurrently,
|
|
90
|
+
* returning results in index order. Used by categories whose ITERATIONS are
|
|
91
|
+
* mutually independent (each builds its own keypair), so several iteration
|
|
92
|
+
* chains can be in flight against the id-correlated SUT at once. Dependent
|
|
93
|
+
* steps WITHIN a single iteration remain serial inside `task`.
|
|
94
|
+
*
|
|
95
|
+
* `limit <= 1` degrades to strictly serial execution. See
|
|
96
|
+
* docs/audits/performance.md §7.1.
|
|
97
|
+
*/
|
|
98
|
+
export async function mapBounded(count, limit, task) {
|
|
99
|
+
const n = Math.max(0, count);
|
|
100
|
+
const cap = Math.max(1, Math.floor(limit));
|
|
101
|
+
const out = new Array(n);
|
|
102
|
+
let next = 0;
|
|
103
|
+
let firstError;
|
|
104
|
+
const worker = async () => {
|
|
105
|
+
while (firstError === undefined) {
|
|
106
|
+
const i = next++;
|
|
107
|
+
if (i >= n)
|
|
108
|
+
return;
|
|
109
|
+
try {
|
|
110
|
+
out[i] = await task(i);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (firstError === undefined)
|
|
114
|
+
firstError = err;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const workers = [];
|
|
120
|
+
for (let w = 0; w < Math.min(cap, n); w++)
|
|
121
|
+
workers.push(worker());
|
|
122
|
+
await Promise.all(workers);
|
|
123
|
+
if (firstError !== undefined)
|
|
124
|
+
throw firstError;
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=helpers.js.map
|