@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.
Files changed (83) hide show
  1. package/README.md +227 -0
  2. package/dist/categories/correctness.d.ts +13 -0
  3. package/dist/categories/correctness.d.ts.map +1 -0
  4. package/dist/categories/correctness.js +78 -0
  5. package/dist/categories/correctness.js.map +1 -0
  6. package/dist/categories/determinism.d.ts +12 -0
  7. package/dist/categories/determinism.d.ts.map +1 -0
  8. package/dist/categories/determinism.js +78 -0
  9. package/dist/categories/determinism.js.map +1 -0
  10. package/dist/categories/dsa.d.ts +17 -0
  11. package/dist/categories/dsa.d.ts.map +1 -0
  12. package/dist/categories/dsa.js +17 -0
  13. package/dist/categories/dsa.js.map +1 -0
  14. package/dist/categories/helpers.d.ts +50 -0
  15. package/dist/categories/helpers.d.ts.map +1 -0
  16. package/dist/categories/helpers.js +127 -0
  17. package/dist/categories/helpers.js.map +1 -0
  18. package/dist/categories/implicit-rejection.d.ts +29 -0
  19. package/dist/categories/implicit-rejection.d.ts.map +1 -0
  20. package/dist/categories/implicit-rejection.js +126 -0
  21. package/dist/categories/implicit-rejection.js.map +1 -0
  22. package/dist/categories/index.d.ts +30 -0
  23. package/dist/categories/index.d.ts.map +1 -0
  24. package/dist/categories/index.js +33 -0
  25. package/dist/categories/index.js.map +1 -0
  26. package/dist/categories/kat.d.ts +21 -0
  27. package/dist/categories/kat.d.ts.map +1 -0
  28. package/dist/categories/kat.js +173 -0
  29. package/dist/categories/kat.js.map +1 -0
  30. package/dist/categories/robustness.d.ts +15 -0
  31. package/dist/categories/robustness.d.ts.map +1 -0
  32. package/dist/categories/robustness.js +98 -0
  33. package/dist/categories/robustness.js.map +1 -0
  34. package/dist/categories/signature.d.ts +29 -0
  35. package/dist/categories/signature.d.ts.map +1 -0
  36. package/dist/categories/signature.js +259 -0
  37. package/dist/categories/signature.js.map +1 -0
  38. package/dist/categories/sizes.d.ts +23 -0
  39. package/dist/categories/sizes.d.ts.map +1 -0
  40. package/dist/categories/sizes.js +150 -0
  41. package/dist/categories/sizes.js.map +1 -0
  42. package/dist/categories/slh-dsa.d.ts +23 -0
  43. package/dist/categories/slh-dsa.d.ts.map +1 -0
  44. package/dist/categories/slh-dsa.js +23 -0
  45. package/dist/categories/slh-dsa.js.map +1 -0
  46. package/dist/categories/timing.d.ts +16 -0
  47. package/dist/categories/timing.d.ts.map +1 -0
  48. package/dist/categories/timing.js +98 -0
  49. package/dist/categories/timing.js.map +1 -0
  50. package/dist/categories/types.d.ts +68 -0
  51. package/dist/categories/types.d.ts.map +1 -0
  52. package/dist/categories/types.js +31 -0
  53. package/dist/categories/types.js.map +1 -0
  54. package/dist/cli.d.ts +14 -0
  55. package/dist/cli.d.ts.map +1 -0
  56. package/dist/cli.js +181 -0
  57. package/dist/cli.js.map +1 -0
  58. package/dist/index.d.ts +70 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +96 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/protocol.d.ts +169 -0
  63. package/dist/protocol.d.ts.map +1 -0
  64. package/dist/protocol.js +115 -0
  65. package/dist/protocol.js.map +1 -0
  66. package/dist/report.d.ts +48 -0
  67. package/dist/report.d.ts.map +1 -0
  68. package/dist/report.js +79 -0
  69. package/dist/report.js.map +1 -0
  70. package/dist/runner.d.ts +129 -0
  71. package/dist/runner.d.ts.map +1 -0
  72. package/dist/runner.js +305 -0
  73. package/dist/runner.js.map +1 -0
  74. package/dist/sizes.d.ts +84 -0
  75. package/dist/sizes.d.ts.map +1 -0
  76. package/dist/sizes.js +214 -0
  77. package/dist/sizes.js.map +1 -0
  78. package/dist/vectors.d.ts +69 -0
  79. package/dist/vectors.d.ts.map +1 -0
  80. package/dist/vectors.js +198 -0
  81. package/dist/vectors.js.map +1 -0
  82. package/package.json +43 -0
  83. 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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAkD,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAChG,OAAO,EACL,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAQvC,MAAM,CAAC,MAAM,WAAW,GAAa,KAAK,EAAE,GAAG,EAA2B,EAAE;IAC1E,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO;YACL,QAAQ,EAAE,aAAa;YACvB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,EAAE;YACV,OAAO,EAAE,2EAA2E;SACrF,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC;IACpB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,4EAA4E;IAC5E,2EAA2E;IAC3E,qEAAqE;IACrE,MAAM,QAAQ,GAAG,MAAM,UAAU,CAC/B,GAAG,CAAC,UAAU,EACd,GAAG,CAAC,aAAa,IAAI,EAAE,EACvB,KAAK,IAAsB,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACtD,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3E,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAC1E,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACpC,OAAO;oBACL,IAAI,EAAE,UAAU;oBAChB,MAAM,EACJ,gDAAgD;wBAChD,WAAW,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI;iBACvF,CAAC;YACJ,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GACV,GAAG,YAAY,kBAAkB;gBAC/B,CAAC,CAAC,wCAAwC,GAAG,CAAC,OAAO,EAAE;gBACvD,CAAC,CAAC,kBAAmB,GAAa,CAAC,OAAO,EAAE,CAAC;YACjD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QACnC,CAAC;IACH,CAAC,CACF,CAAC;IAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAY,CAAC;QACjC,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC1B,UAAU,EAAE,CAAC;YACb,IAAI,UAAU,IAAI,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACtE,CAAC;aAAM,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9B,OAAO,EAAE,CAAC;YACV,IAAI,OAAO,IAAI,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,IAAI,CACT,IAAI,CACF,WAAW,EACX,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,oEAAoE,CACxG,CACF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO;QACL,QAAQ,EAAE,aAAa;QACvB,MAAM;QACN,MAAM;QACN,OAAO,EACL,MAAM,KAAK,MAAM;YACf,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,iBAAiB;YACpC,CAAC,CAAC,GAAG,UAAU,kBAAkB,OAAO,kBAAkB,GAAG,CAAC,UAAU,aAAa;KAC1F,CAAC;AACJ,CAAC,CAAC"}
@@ -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