subtlepq 0.1.2 → 0.3.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 CHANGED
@@ -1,8 +1,13 @@
1
- # pqfill
1
+ # subtlepq
2
2
 
3
3
  Post-quantum polyfill for the Web Cryptography API: ML-KEM (FIPS 203) and ML-DSA (FIPS 204) per the [WICG Modern Algorithms draft](https://wicg.github.io/webcrypto-modern-algos/), with native delegation where the platform already supports it.
4
4
 
5
- **Status: P1 — engine + core JS layer.** Raw key formats only (`raw-public`, `raw-seed`); spki/pkcs8/jwk, wrapKey, and DHKEM are on the way.
5
+ **Status: P4 — engine, core JS layer, all key formats, key wrapping, native-parity suites, DHKEM extension.**
6
+
7
+ Key formats: `raw-public`, `raw-seed`, `spki`, `pkcs8` (seed-only encoding per the LAMPS
8
+ drafts; the `both` CHOICE is accepted on import and cross-checked), and `jwk` (kty `"AKP"`,
9
+ `pub`/`priv` with `priv` = seed). `wrapKey`/`unwrapKey` compose export/import with the native
10
+ wrapping algorithm, so AES-GCM, AES-KW, and RSA-OAEP wrapping of ML-* keys just work.
6
11
 
7
12
  ## Use
8
13
 
@@ -29,9 +34,29 @@ const sig = await crypto.subtle.sign({ name: "ML-DSA-65", context: ctx }, kp.pri
29
34
 
30
35
  Known limits (inherent to a script polyfill, made loud rather than silent): ML-* keys are not structured-cloneable — `postMessage`/IndexedDB throws `DataCloneError`; export the 32–64-byte seed and re-import instead. Key material lives in wasm linear memory: `extractable: false` is API-level enforcement, not platform isolation.
31
36
 
37
+ ## DHKEM: classical ECDH in the same KEM shape
38
+
39
+ `subtlepq/dhkem` wraps ephemeral-static ECDH as a KEM per [RFC 9180 §4.1](https://www.rfc-editor.org/rfc/rfc9180#section-4.1) — `DHKEM-X25519-HKDF-SHA256` and `DHKEM-P256-HKDF-SHA256` — so classical and post-quantum key agreement share one calling convention, and migrating to ML-KEM is a name change:
40
+
41
+ ```js
42
+ import * as dhkem from "subtlepq/dhkem";
43
+ dhkem.register(); // before install(), if you use the true polyfill
44
+ import { subtle } from "subtlepq";
45
+
46
+ const kp = await subtle.generateKey("DHKEM-X25519-HKDF-SHA256", false,
47
+ ["encapsulateKey", "decapsulateKey"]);
48
+ const { sharedKey, ciphertext } = await subtle.encapsulateKey(
49
+ "DHKEM-X25519-HKDF-SHA256", kp.publicKey, // -> "ML-KEM-768" later
50
+ { name: "AES-GCM", length: 256 }, false, ["encrypt"]);
51
+ ```
52
+
53
+ These names are **not** in the WICG draft — this is a subtlepq extension (if the WG adopts DHKEM identifiers, those will be adopted and these aliased). Underneath it is 100% native WebCrypto (ECDH/X25519 + HKDF): no wasm is involved, and the keys are genuine platform CryptoKeys — structured-cloneable, native usage enforcement (surfaced as `deriveBits`). The ciphertext is the serialized ephemeral public key (32 bytes X25519 / 65 bytes P-256), interoperable with any RFC 9180 DHKEM implementation, and validated against the RFC 9180 Appendix A test vectors.
54
+
55
+ One asymmetry vs a true KEM: RFC 9180 decapsulation mixes the recipient *public* key into the KDF. subtlepq remembers the public half for keys created through `subtlepq/dhkem`; for private keys from elsewhere (or structured-cloned into another realm) pass it explicitly — `decapsulateBits({ name: "DHKEM-X25519-HKDF-SHA256", publicKey }, key, ct)` — or use an extractable key.
56
+
32
57
  ## Engine
33
58
 
34
- `wasm/` builds a minimal [liboqs](https://github.com/open-quantum-safe/liboqs) (pinned submodule, ML-KEM + ML-DSA only) plus a thin shim (`pqfill_engine.c`) to WebAssembly:
59
+ `wasm/` builds a minimal [liboqs](https://github.com/open-quantum-safe/liboqs) (pinned submodule, ML-KEM + ML-DSA only) plus a thin shim (`subtlepq_engine.c`) to WebAssembly:
35
60
 
36
61
  ```
37
62
  git submodule update --init
@@ -39,11 +64,19 @@ wasm/build.sh # needs emsdk; override location with EMSDK=...
39
64
  npm test
40
65
  ```
41
66
 
42
- Outputs `wasm/dist/pqfill-engine.mjs` (+ `.wasm` sidecar, CSP-clean) and `pqfill-engine.single.mjs` (self-contained, requires `'wasm-unsafe-eval'` under strict CSP). 75 KB wasm covers all six parameter sets.
67
+ Outputs `wasm/dist/subtlepq-engine.mjs` (+ `.wasm` sidecar, CSP-clean) and `subtlepq-engine.single.mjs` (self-contained, requires `'wasm-unsafe-eval'` under strict CSP). 75 KB wasm covers all six parameter sets.
43
68
 
44
69
  Seeded operations liboqs does not expose publicly (ML-DSA keygen from seed, KAT-deterministic encapsulation/signing) use a bracketed RNG-playback override — the same technique liboqs' own ACVP tests use.
45
70
 
46
- Tests run NIST ACVP known-answer vectors (subset, see `test/vectors/acvp-subset.json`) for all six parameter sets, plus roundtrip, implicit-rejection, context, and polyfill-routing checks.
71
+ Tests run NIST ACVP known-answer vectors (subset, see `test/vectors/acvp-subset.json`) for all six parameter sets, plus roundtrip, implicit-rejection, context, and polyfill-routing checks. `test/dhkem-test.mjs` validates the DHKEM extension against the RFC 9180 Appendix A vectors (`test/vectors/rfc9180-subset.json`).
72
+
73
+ `npm run test:parity` (Node >= 24.7) runs the polyfill side-by-side against the
74
+ platform-native ML-KEM/ML-DSA WebCrypto implementation as an oracle: identical
75
+ spki/pkcs8/jwk bytes, cross-stack encapsulate/decapsulate and sign/verify, identical
76
+ AES-KW/AES-GCM wrapKey output, and matching DOMException names on malformed input.
77
+ `npm run test:libves` cross-checks ML-KEM against [libVES](https://github.com/vesvault/libVES)
78
+ (an independent liboqs build and ASN.1 layer): same-seed keypair equality, SPKI byte
79
+ parity, and mutual key import and encapsulation.
47
80
 
48
81
  ## License
49
82
 
package/package.json CHANGED
@@ -1,47 +1,52 @@
1
1
  {
2
2
  "name": "subtlepq",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Post-quantum polyfill for the Web Cryptography API: ML-KEM (FIPS 203) and ML-DSA (FIPS 204) per the WICG Modern Algorithms draft, with native delegation",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "author": "VESvault Corp",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/vesvault/pqfill.git"
10
+ "url": "git+https://github.com/vesvault/subtlepq.git"
11
11
  },
12
- "homepage": "https://github.com/vesvault/pqfill#readme",
12
+ "homepage": "https://github.com/vesvault/subtlepq#readme",
13
13
  "bugs": {
14
- "url": "https://github.com/vesvault/pqfill/issues"
14
+ "url": "https://github.com/vesvault/subtlepq/issues"
15
15
  },
16
16
  "engines": {
17
17
  "node": ">=18"
18
18
  },
19
19
  "sideEffects": false,
20
20
  "files": [
21
- "src",
22
- "wasm/dist"
21
+ "src/algorithms.js",
22
+ "src/dhkem.js",
23
+ "src/engine.js",
24
+ "src/errors.js",
25
+ "src/formats.js",
26
+ "src/index.js",
27
+ "src/install.js",
28
+ "src/keystore.js",
29
+ "src/router.js",
30
+ "src/subtle.js",
31
+ "wasm/dist/subtlepq-engine.mjs",
32
+ "wasm/dist/subtlepq-engine.single.mjs",
33
+ "wasm/dist/subtlepq-engine.wasm"
23
34
  ],
24
35
  "exports": {
25
36
  ".": "./src/index.js",
26
- "./install": "./src/install.js"
37
+ "./install": "./src/install.js",
38
+ "./dhkem": "./src/dhkem.js"
27
39
  },
28
40
  "scripts": {
29
41
  "build": "wasm/build.sh",
30
- "test": "node test/engine-test.mjs && node test/polyfill-test.mjs",
31
- "prepack": "node -e \"const fs=require('fs');for(const f of ['pqfill-engine.mjs','pqfill-engine.single.mjs','pqfill-engine.wasm'])if(!fs.existsSync('wasm/dist/'+f))throw new Error('wasm/dist/'+f+' missing - run wasm/build.sh before packing')\""
42
+ "prepack": "node -e \"const p=require('./package.json'),fs=require('fs');for(const f of p.files)if(!fs.existsSync(f))throw new Error(f+' missing - run wasm/build.sh before packing')\"",
43
+ "test": "node test/engine-test.mjs && node test/polyfill-test.mjs && node test/dhkem-test.mjs",
44
+ "test:parity": "node test/parity-native.mjs",
45
+ "test:libves": "node test/parity-libves.mjs"
32
46
  },
33
47
  "keywords": [
34
- "ml-kem",
35
- "ml-dsa",
36
- "webcrypto",
37
- "polyfill",
38
- "post-quantum",
39
- "fips203",
40
- "fips204",
41
- "kyber",
42
- "dilithium",
43
- "subtlecrypto",
44
- "encapsulation",
45
- "kem"
48
+ "ml-kem", "ml-dsa", "webcrypto", "polyfill", "post-quantum",
49
+ "fips203", "fips204", "kyber", "dilithium", "subtlecrypto",
50
+ "encapsulation", "kem", "dhkem", "hpke", "rfc9180"
46
51
  ]
47
- }
52
+ }
package/src/algorithms.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: algorithm registry and AlgorithmIdentifier normalization
2
+ * subtlepq: algorithm registry and AlgorithmIdentifier normalization
3
3
  *
4
4
  * (c) 2026 VESvault Corp
5
5
  * SPDX-License-Identifier: Apache-2.0
@@ -13,8 +13,25 @@ const NAMES = [
13
13
  export const KEM_PUB_USAGES = ["encapsulateBits", "encapsulateKey"];
14
14
  export const KEM_PRIV_USAGES = ["decapsulateBits", "decapsulateKey"];
15
15
 
16
+ /* Extension algorithms (subtlepq/dhkem): registered at runtime, carrying
17
+ * their own ops table; the core ML-* set above stays engine-backed. */
18
+ const EXTENSIONS = new Map();
19
+
20
+ export function registerExtension(name, kind, ops) {
21
+ EXTENSIONS.set(name.toLowerCase(), { name, kind, ops });
22
+ }
23
+
24
+ export function unregisterExtension(name) {
25
+ EXTENSIONS.delete(name.toLowerCase());
26
+ }
27
+
28
+ export function hasExtensions() {
29
+ return EXTENSIONS.size > 0;
30
+ }
31
+
16
32
  /* WebCrypto algorithm names are matched case-insensitively.
17
- * Returns { name, kind, context? } with the canonical name, or null. */
33
+ * Returns { name, kind, context?, ops? } with the canonical name, or null;
34
+ * `ops` marks an extension algorithm handled outside subtle.js. */
18
35
  export function normalizeAlg(alg) {
19
36
  let name, context;
20
37
  if (typeof alg === "string") {
@@ -26,10 +43,14 @@ export function normalizeAlg(alg) {
26
43
  return null;
27
44
  }
28
45
  const canonical = NAMES.find((n) => n.toLowerCase() === name.toLowerCase());
29
- if (!canonical) return null;
30
- return {
31
- name: canonical,
32
- kind: canonical.startsWith("ML-KEM") ? "kem" : "sig",
33
- context,
34
- };
46
+ if (canonical) {
47
+ return {
48
+ name: canonical,
49
+ kind: canonical.startsWith("ML-KEM") ? "kem" : "sig",
50
+ context,
51
+ };
52
+ }
53
+ const ext = EXTENSIONS.get(name.toLowerCase());
54
+ if (!ext) return null;
55
+ return { name: ext.name, kind: ext.kind, ops: ext.ops, context };
35
56
  }
package/src/dhkem.js ADDED
@@ -0,0 +1,321 @@
1
+ /***************************************************************************
2
+ * subtlepq/dhkem: DHKEM over the platform's native ECDH -- RFC 9180 sec 4.1
3
+ *
4
+ * (c) 2026 VESvault Corp
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ *
7
+ * KEM-shaped classical key agreement: ephemeral-static ECDH wrapped as
8
+ * encapsulate/decapsulate, so classical and post-quantum share one calling
9
+ * convention and migrating to ML-KEM is a name change. NOT part of the
10
+ * WICG draft -- a subtlepq extension; if the WG ever adopts DHKEM
11
+ * identifiers, those will be adopted here and these names aliased.
12
+ *
13
+ * Everything underneath is native WebCrypto (ECDH / X25519 + HKDF-SHA256);
14
+ * no WASM is involved and all keys are genuine platform CryptoKeys --
15
+ * structured-cloneable, native usage enforcement (surfaced as
16
+ * "deriveBits", the underlying native usage).
17
+ *
18
+ * Decapsulation needs the recipient PUBLIC key bytes for the RFC 9180
19
+ * kem_context. They are remembered automatically for private keys made by
20
+ * generateKey()/importKey() of this module; for foreign or structured-
21
+ * cloned private keys pass { name, publicKey } (CryptoKey or serialized
22
+ * bytes) or use an extractable key.
23
+ ***************************************************************************/
24
+
25
+ import { registerExtension, unregisterExtension } from "./algorithms.js";
26
+ import { importSharedKey, toBytes } from "./subtle.js";
27
+ import { unb64u } from "./formats.js";
28
+ import * as E from "./errors.js";
29
+
30
+ const te = new TextEncoder();
31
+
32
+ function cat(...parts) {
33
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
34
+ let o = 0;
35
+ for (const p of parts) { out.set(p, o); o += p.length; }
36
+ return out;
37
+ }
38
+
39
+ /* suite_id = "KEM" || I2OSP(kem_id, 2); Nsecret = 32 for both suites.
40
+ * The RFC serialized public key form is exactly WebCrypto "raw": 32 bytes
41
+ * for X25519, the 65-byte uncompressed point for P-256. */
42
+ const SUITES = {
43
+ "DHKEM-X25519-HKDF-SHA256": {
44
+ nEnc: 32,
45
+ native: { name: "X25519" },
46
+ suiteId: cat(te.encode("KEM"), [0x00, 0x20]),
47
+ jwkPub: (jwk) => (jwk.x === undefined ? null : unb64u(jwk.x, "x")),
48
+ },
49
+ "DHKEM-P256-HKDF-SHA256": {
50
+ nEnc: 65,
51
+ native: { name: "ECDH", namedCurve: "P-256" },
52
+ suiteId: cat(te.encode("KEM"), [0x00, 0x10]),
53
+ jwkPub: (jwk) => (jwk.x === undefined || jwk.y === undefined ? null
54
+ : cat([0x04], unb64u(jwk.x, "x"), unb64u(jwk.y, "y"))),
55
+ },
56
+ };
57
+
58
+ const PUB_USAGES = ["encapsulateBits", "encapsulateKey"];
59
+ const PRIV_USAGES = ["decapsulateBits", "decapsulateKey", "deriveBits", "deriveKey"];
60
+
61
+ /* key -> its serialized PUBLIC key bytes (for a private key, the public
62
+ * half): encap serializes pkR and decap mixes it into the kem_context,
63
+ * and neither can be exported off a non-extractable CryptoKey */
64
+ const pkmap = new WeakMap();
65
+
66
+ function nativeSubtle() {
67
+ const ns = globalThis.crypto && globalThis.crypto.subtle;
68
+ if (!ns) throw E.notSupported("no native SubtleCrypto for DHKEM");
69
+ return ns;
70
+ }
71
+
72
+ function requireSuite(alg) {
73
+ let name, obj;
74
+ if (typeof alg === "string") {
75
+ name = alg;
76
+ } else if (alg && typeof alg.name === "string") {
77
+ name = alg.name;
78
+ obj = alg;
79
+ }
80
+ const canonical = name === undefined ? undefined :
81
+ Object.keys(SUITES).find((n) => n.toLowerCase() === name.toLowerCase());
82
+ if (!canonical) throw E.notSupported("unrecognized DHKEM algorithm");
83
+ return { name: canonical, s: SUITES[canonical], alg: obj };
84
+ }
85
+
86
+ function requireKey(key, s, name, type) {
87
+ const ka = key && key.algorithm;
88
+ const match = ka && ka.name === s.native.name &&
89
+ (s.native.namedCurve === undefined || ka.namedCurve === s.native.namedCurve);
90
+ if (!match) {
91
+ throw E.invalidAccess("key is not a native " + s.native.name +
92
+ (s.native.namedCurve ? "/" + s.native.namedCurve : "") +
93
+ " key usable with " + name);
94
+ }
95
+ if (key.type !== type) throw E.invalidAccess("expected a " + type + " key");
96
+ }
97
+
98
+ function checkUsages(usages, allowed) {
99
+ for (const u of usages) {
100
+ if (!allowed.includes(u)) throw E.syntaxError("unsupported key usage " + u);
101
+ }
102
+ }
103
+
104
+ /* ExtractAndExpand (RFC 9180 sec 4.1) in one native HKDF deriveBits call:
105
+ * LabeledExtract("", "eae_prk", dh) + LabeledExpand(prk, "shared_secret",
106
+ * kem_context, 32) map exactly onto HKDF's extract-then-expand. */
107
+ async function extractAndExpand(ns, s, dh, kemContext) {
108
+ const ikm = cat(te.encode("HPKE-v1"), s.suiteId, te.encode("eae_prk"), dh);
109
+ const info = cat([0, 32], te.encode("HPKE-v1"), s.suiteId,
110
+ te.encode("shared_secret"), kemContext);
111
+ const k = await ns.importKey("raw", ikm, "HKDF", false, ["deriveBits"]);
112
+ ikm.fill(0);
113
+ return new Uint8Array(await ns.deriveBits(
114
+ { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info }, k, 256));
115
+ }
116
+
117
+ const dhBits = async (ns, s, priv, pub) => new Uint8Array(
118
+ await ns.deriveBits({ name: s.native.name, public: pub }, priv, 256));
119
+
120
+ async function publicBytesFor(ns, key, s, name, alg) {
121
+ const given = alg && alg.publicKey;
122
+ if (given !== undefined && given !== null) {
123
+ if (typeof CryptoKey !== "undefined" && given instanceof CryptoKey) {
124
+ requireKey(given, s, name, "public");
125
+ return new Uint8Array(await ns.exportKey("raw", given));
126
+ }
127
+ const b = toBytes(given, "publicKey");
128
+ if (b.length !== s.nEnc) throw E.dataError("bad " + name + " publicKey length");
129
+ return b;
130
+ }
131
+ const known = pkmap.get(key);
132
+ if (known) return known.slice();
133
+ if (key.extractable) {
134
+ const pub = s.jwkPub(await ns.exportKey("jwk", key));
135
+ if (pub) return pub;
136
+ }
137
+ throw E.invalidAccess("cannot determine the recipient public key for " + name +
138
+ " decapsulation; pass { name, publicKey } or use a key from this module");
139
+ }
140
+
141
+ /* ---- key management (thin translation onto native ECDH / X25519) ---- */
142
+
143
+ export async function generateKey(algorithm, extractable, keyUsages = []) {
144
+ const { s } = requireSuite(algorithm);
145
+ checkUsages(keyUsages, [...PUB_USAGES, ...PRIV_USAGES]);
146
+ if (!keyUsages.some((u) => PRIV_USAGES.includes(u))) {
147
+ throw E.syntaxError("private key usages must not be empty");
148
+ }
149
+ const ns = nativeSubtle();
150
+ const pair = await ns.generateKey(s.native, extractable, ["deriveBits"]);
151
+ const pub = new Uint8Array(await ns.exportKey("raw", pair.publicKey));
152
+ pkmap.set(pair.publicKey, pub);
153
+ pkmap.set(pair.privateKey, pub);
154
+ return pair;
155
+ }
156
+
157
+ export async function importKey(format, keyData, algorithm, extractable, keyUsages = []) {
158
+ const { s, name } = requireSuite(algorithm);
159
+ const ns = nativeSubtle();
160
+ const importPriv = async (fmt, data) => {
161
+ checkUsages(keyUsages, PRIV_USAGES);
162
+ if (!keyUsages.length) throw E.syntaxError("private key usages must not be empty");
163
+ return ns.importKey(fmt, data, s.native, extractable, ["deriveBits"]);
164
+ };
165
+ if (format === "raw" || format === "raw-public" || format === "spki") {
166
+ checkUsages(keyUsages, PUB_USAGES);
167
+ const fmt = format === "raw-public" ? "raw" : format;
168
+ const bytes = toBytes(keyData, "keyData");
169
+ const key = await ns.importKey(fmt, bytes, s.native, extractable, []);
170
+ if (fmt === "raw") {
171
+ pkmap.set(key, bytes);
172
+ } else {
173
+ /* spki: recover the raw form via an extractable twin */
174
+ try {
175
+ const twin = extractable ? key
176
+ : await ns.importKey("spki", bytes, s.native, true, []);
177
+ pkmap.set(key, new Uint8Array(await ns.exportKey("raw", twin)));
178
+ } catch {}
179
+ }
180
+ return key;
181
+ }
182
+ if (format === "pkcs8") {
183
+ const bytes = toBytes(keyData, "keyData");
184
+ const key = await importPriv("pkcs8", bytes);
185
+ /* recover the public half for decap: from the key if extractable,
186
+ * else from a throwaway extractable import of the same bytes */
187
+ try {
188
+ const twin = extractable ? key
189
+ : await ns.importKey("pkcs8", bytes, s.native, true, ["deriveBits"]);
190
+ const pub = s.jwkPub(await ns.exportKey("jwk", twin));
191
+ if (pub) pkmap.set(key, pub);
192
+ } catch {}
193
+ bytes.fill(0);
194
+ return key;
195
+ }
196
+ if (format === "jwk") {
197
+ if (!keyData || typeof keyData !== "object" || ArrayBuffer.isView(keyData) ||
198
+ keyData instanceof ArrayBuffer) {
199
+ throw E.dataError("jwk import expects a JsonWebKey object");
200
+ }
201
+ /* alg/key_ops carry DHKEM-level values the native import would
202
+ * reject; the native usage set is supplied explicitly instead */
203
+ const clean = { ...keyData };
204
+ delete clean.alg;
205
+ delete clean.key_ops;
206
+ delete clean.use;
207
+ if (keyData.d === undefined) {
208
+ checkUsages(keyUsages, PUB_USAGES);
209
+ const key = await ns.importKey("jwk", clean, s.native, extractable, []);
210
+ const pub = s.jwkPub(keyData);
211
+ if (pub) pkmap.set(key, pub);
212
+ return key;
213
+ }
214
+ const key = await importPriv("jwk", clean);
215
+ const pub = s.jwkPub(keyData);
216
+ if (pub) pkmap.set(key, pub);
217
+ return key;
218
+ }
219
+ throw E.notSupported("key format " + format + " is not supported");
220
+ }
221
+
222
+ /* ---- KEM operations (RFC 9180 sec 4.1 Encap / Decap, base mode) ---- */
223
+
224
+ async function encap(algorithm, encapsulationKey) {
225
+ const { s, name } = requireSuite(algorithm);
226
+ requireKey(encapsulationKey, s, name, "public");
227
+ const ns = nativeSubtle();
228
+ const eph = await ns.generateKey(s.native, false, ["deriveBits"]);
229
+ const enc = new Uint8Array(await ns.exportKey("raw", eph.publicKey));
230
+ const dh = await dhBits(ns, s, eph.privateKey, encapsulationKey);
231
+ const known = pkmap.get(encapsulationKey);
232
+ let pkRm;
233
+ if (known) {
234
+ pkRm = known;
235
+ } else {
236
+ try {
237
+ pkRm = new Uint8Array(await ns.exportKey("raw", encapsulationKey));
238
+ } catch {
239
+ throw E.invalidAccess("cannot serialize the " + name +
240
+ " encapsulation key: not extractable and not from this module");
241
+ }
242
+ }
243
+ const ss = await extractAndExpand(ns, s, dh, cat(enc, pkRm));
244
+ dh.fill(0);
245
+ return { ss, enc };
246
+ }
247
+
248
+ async function decap(algorithm, decapsulationKey, ciphertext) {
249
+ const { s, name, alg } = requireSuite(algorithm);
250
+ requireKey(decapsulationKey, s, name, "private");
251
+ const enc = toBytes(ciphertext, "ciphertext");
252
+ if (enc.length !== s.nEnc) throw E.opError("bad " + name + " ciphertext length");
253
+ const ns = nativeSubtle();
254
+ let pkE;
255
+ try {
256
+ pkE = await ns.importKey("raw", enc, s.native, true, []);
257
+ } catch {
258
+ /* unlike ML-KEM implicit rejection, DHKEM decap MAY fail: an
259
+ * invalid public key raises ValidationError per RFC 9180 */
260
+ throw E.opError("invalid " + name + " ciphertext");
261
+ }
262
+ const dh = await dhBits(ns, s, decapsulationKey, pkE);
263
+ const pkRm = await publicBytesFor(ns, decapsulationKey, s, name, alg);
264
+ const ss = await extractAndExpand(ns, s, dh, cat(enc, pkRm));
265
+ dh.fill(0);
266
+ return ss;
267
+ }
268
+
269
+ export async function encapsulateBits(algorithm, encapsulationKey) {
270
+ const { ss, enc } = await encap(algorithm, encapsulationKey);
271
+ return { sharedKey: ss.buffer, ciphertext: enc.buffer };
272
+ }
273
+
274
+ export async function encapsulateKey(algorithm, encapsulationKey,
275
+ sharedKeyAlgorithm, extractable, keyUsages) {
276
+ const { ss, enc } = await encap(algorithm, encapsulationKey);
277
+ const sharedKey = await importSharedKey(ss, sharedKeyAlgorithm, extractable, keyUsages);
278
+ return { sharedKey, ciphertext: enc.buffer };
279
+ }
280
+
281
+ export async function decapsulateBits(algorithm, decapsulationKey, ciphertext) {
282
+ return (await decap(algorithm, decapsulationKey, ciphertext)).buffer;
283
+ }
284
+
285
+ export async function decapsulateKey(algorithm, decapsulationKey, ciphertext,
286
+ sharedKeyAlgorithm, extractable, keyUsages) {
287
+ const ss = await decap(algorithm, decapsulationKey, ciphertext);
288
+ return importSharedKey(ss, sharedKeyAlgorithm, extractable, keyUsages);
289
+ }
290
+
291
+ /* ---- feature detection / registration ---- */
292
+
293
+ const OPS = ["generateKey", "importKey", "exportKey",
294
+ "encapsulateBits", "encapsulateKey", "decapsulateBits", "decapsulateKey"];
295
+
296
+ export function supports(operation, algorithm) {
297
+ if (algorithm !== undefined) {
298
+ try {
299
+ requireSuite(algorithm);
300
+ } catch {
301
+ return false;
302
+ }
303
+ }
304
+ return OPS.includes(operation);
305
+ }
306
+
307
+ const ops = {
308
+ generateKey, importKey,
309
+ encapsulateBits, encapsulateKey, decapsulateBits, decapsulateKey,
310
+ supports: (operation) => OPS.includes(operation),
311
+ };
312
+
313
+ /* Makes the DHKEM-* names routable through the subtlepq ponyfill `subtle`
314
+ * and, after install(), through crypto.subtle; call before install(). */
315
+ export function register() {
316
+ for (const name of Object.keys(SUITES)) registerExtension(name, "kem", ops);
317
+ }
318
+
319
+ export function unregister() {
320
+ for (const name of Object.keys(SUITES)) unregisterExtension(name);
321
+ }
package/src/engine.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: lazy wasm engine loader + typed wrappers over pqfill_engine.c
2
+ * subtlepq: lazy wasm engine loader + typed wrappers over subtlepq_engine.c
3
3
  *
4
4
  * (c) 2026 VESvault Corp
5
5
  * SPDX-License-Identifier: Apache-2.0
@@ -18,7 +18,7 @@ export function getEngine() {
18
18
  }
19
19
 
20
20
  async function load() {
21
- const { default: init } = await import("../wasm/dist/pqfill-engine.mjs");
21
+ const { default: init } = await import("../wasm/dist/subtlepq-engine.mjs");
22
22
  return new Engine(await init());
23
23
  }
24
24
 
package/src/errors.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: DOMException helpers with WebCrypto-mandated error names
2
+ * subtlepq: DOMException helpers with WebCrypto-mandated error names
3
3
  *
4
4
  * (c) 2026 VESvault Corp
5
5
  * SPDX-License-Identifier: Apache-2.0
package/src/formats.js ADDED
@@ -0,0 +1,174 @@
1
+ /***************************************************************************
2
+ * subtlepq: spki / pkcs8 / jwk codecs (fixed ASN.1 templates)
3
+ *
4
+ * (c) 2026 VESvault Corp
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ *
7
+ * DER shapes per draft-ietf-lamps-kyber/dilithium-certificates as adopted
8
+ * by the WICG draft: SPKI AlgorithmIdentifier has NO parameters; PKCS#8
9
+ * privateKey is the seed-only CHOICE, an implicit [0] primitive holding
10
+ * the seed. Import also accepts the "both" CHOICE (seed + expandedKey,
11
+ * verified against each other); expandedKey-only is NotSupportedError.
12
+ ***************************************************************************/
13
+
14
+ import * as E from "./errors.js";
15
+
16
+ /* NIST algorithm arc: 2.16.840.1.101.3.4.{4=kem,3=sig}.n */
17
+ const OIDS = {
18
+ "ML-KEM-512": [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x01],
19
+ "ML-KEM-768": [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02],
20
+ "ML-KEM-1024": [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x03],
21
+ "ML-DSA-44": [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11],
22
+ "ML-DSA-65": [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12],
23
+ "ML-DSA-87": [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13],
24
+ };
25
+
26
+ function oidToName(body) {
27
+ for (const [name, oid] of Object.entries(OIDS)) {
28
+ if (oid.length === body.length && oid.every((b, i) => b === body[i])) return name;
29
+ }
30
+ return null;
31
+ }
32
+
33
+ /* ---- DER write ---- */
34
+
35
+ function tlv(tag, ...parts) {
36
+ const len = parts.reduce((n, p) => n + p.length, 0);
37
+ const hdr = [tag];
38
+ if (len < 0x80) {
39
+ hdr.push(len);
40
+ } else {
41
+ const b = [];
42
+ for (let n = len; n; n >>>= 8) b.unshift(n & 0xff);
43
+ hdr.push(0x80 | b.length, ...b);
44
+ }
45
+ const out = new Uint8Array(hdr.length + len);
46
+ out.set(hdr);
47
+ let p = hdr.length;
48
+ for (const part of parts) { out.set(part, p); p += part.length; }
49
+ return out;
50
+ }
51
+
52
+ function algorithmIdentifier(name) {
53
+ return tlv(0x30, tlv(0x06, OIDS[name]));
54
+ }
55
+
56
+ /* ---- DER read ---- */
57
+
58
+ class Reader {
59
+ constructor(u8, what) { this.u8 = u8; this.p = 0; this.what = what; }
60
+ bad(m) { return E.dataError("malformed " + this.what + ": " + m); }
61
+ tlv() {
62
+ if (this.p + 2 > this.u8.length) throw this.bad("truncated");
63
+ const tag = this.u8[this.p++];
64
+ let len = this.u8[this.p++];
65
+ if (len & 0x80) {
66
+ const n = len & 0x7f;
67
+ if (n < 1 || n > 4) throw this.bad("bad length encoding");
68
+ len = 0;
69
+ for (let i = 0; i < n; i++) {
70
+ if (this.p >= this.u8.length) throw this.bad("truncated");
71
+ len = len * 256 + this.u8[this.p++];
72
+ }
73
+ }
74
+ if (this.p + len > this.u8.length) throw this.bad("truncated");
75
+ const val = this.u8.subarray(this.p, this.p + len);
76
+ this.p += len;
77
+ return { tag, val };
78
+ }
79
+ expect(tag, m) {
80
+ const t = this.tlv();
81
+ if (t.tag !== tag) throw this.bad("expected " + m);
82
+ return t.val;
83
+ }
84
+ get done() { return this.p >= this.u8.length; }
85
+ }
86
+
87
+ function readAlgorithm(r, what) {
88
+ const seq = new Reader(r.expect(0x30, "AlgorithmIdentifier"), what);
89
+ const name = oidToName(seq.expect(0x06, "algorithm OID"));
90
+ if (!name) throw E.dataError(what + " algorithm OID is not an ML-KEM/ML-DSA algorithm");
91
+ if (!seq.done) throw E.dataError(what + " AlgorithmIdentifier parameters must be absent");
92
+ return name;
93
+ }
94
+
95
+ /* ---- SubjectPublicKeyInfo ---- */
96
+
97
+ export function encodeSpki(name, pub) {
98
+ const bits = new Uint8Array(pub.length + 1);
99
+ bits.set(pub, 1);
100
+ return tlv(0x30, algorithmIdentifier(name), tlv(0x03, bits));
101
+ }
102
+
103
+ export function decodeSpki(bytes) {
104
+ const outer = new Reader(bytes, "SPKI");
105
+ const r = new Reader(outer.expect(0x30, "SubjectPublicKeyInfo"), "SPKI");
106
+ if (!outer.done) throw outer.bad("trailing bytes");
107
+ const name = readAlgorithm(r, "SPKI");
108
+ const bits = r.expect(0x03, "subjectPublicKey BIT STRING");
109
+ if (!r.done) throw r.bad("trailing bytes");
110
+ if (bits.length < 1 || bits[0] !== 0) throw r.bad("subjectPublicKey unused bits");
111
+ return { name, pub: bits.slice(1) };
112
+ }
113
+
114
+ /* ---- PrivateKeyInfo (seed-only CHOICE) ---- */
115
+
116
+ export function encodePkcs8(name, seed) {
117
+ return tlv(0x30,
118
+ tlv(0x02, [0]),
119
+ algorithmIdentifier(name),
120
+ tlv(0x04, tlv(0x80, seed)));
121
+ }
122
+
123
+ /* Returns { name, seed, expanded? }; expanded (from the "both" CHOICE) is
124
+ * the caller's to verify against the seed-derived key. */
125
+ export function decodePkcs8(bytes) {
126
+ const outer = new Reader(bytes, "PKCS#8");
127
+ const r = new Reader(outer.expect(0x30, "PrivateKeyInfo"), "PKCS#8");
128
+ if (!outer.done) throw outer.bad("trailing bytes");
129
+ const ver = r.expect(0x02, "version INTEGER");
130
+ if (ver.length !== 1 || ver[0] > 1) throw r.bad("unsupported version");
131
+ const name = readAlgorithm(r, "PKCS#8");
132
+ const inner = new Reader(r.expect(0x04, "privateKey OCTET STRING"), "PKCS#8");
133
+ /* anything after privateKey (attributes, [1] publicKey) is ignored */
134
+ const t = inner.tlv();
135
+ if (t.tag === 0x80) { /* seed [0] */
136
+ if (!inner.done) throw inner.bad("trailing bytes after seed");
137
+ return { name, seed: t.val.slice() };
138
+ }
139
+ if (t.tag === 0x04) { /* expandedKey */
140
+ throw E.notSupported(
141
+ "expandedKey-only ML private keys are not supported; provide the seed");
142
+ }
143
+ if (t.tag === 0x30) { /* both */
144
+ const b = new Reader(t.val, "PKCS#8");
145
+ const seed = b.expect(0x04, "seed OCTET STRING");
146
+ const expanded = b.expect(0x04, "expandedKey OCTET STRING");
147
+ if (!b.done || !inner.done) throw b.bad("trailing bytes");
148
+ return { name, seed: seed.slice(), expanded: expanded.slice() };
149
+ }
150
+ throw inner.bad("unrecognized private key CHOICE");
151
+ }
152
+
153
+ /* ---- JWK base64url ---- */
154
+
155
+ export function b64u(bytes) {
156
+ let s = "";
157
+ for (const b of bytes) s += String.fromCharCode(b);
158
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
159
+ }
160
+
161
+ export function unb64u(s, what) {
162
+ if (typeof s !== "string" || /[^A-Za-z0-9_-]/.test(s)) {
163
+ throw E.dataError("JWK " + what + " is not base64url");
164
+ }
165
+ let bin;
166
+ try {
167
+ bin = atob(s.replace(/-/g, "+").replace(/_/g, "/"));
168
+ } catch {
169
+ throw E.dataError("JWK " + what + " is not base64url");
170
+ }
171
+ const out = new Uint8Array(bin.length);
172
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
173
+ return out;
174
+ }
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: post-quantum polyfill for the Web Cryptography API
2
+ * subtlepq: post-quantum polyfill for the Web Cryptography API
3
3
  * ML-KEM (FIPS 203) + ML-DSA (FIPS 204) per the WICG Modern Algorithms
4
4
  * draft, with native delegation.
5
5
  *
@@ -12,7 +12,7 @@
12
12
  ***************************************************************************/
13
13
 
14
14
  import * as ours from "./subtle.js";
15
- import { ROUTES } from "./router.js";
15
+ import { ROUTES, shape } from "./router.js";
16
16
  import * as E from "./errors.js";
17
17
 
18
18
  export { install, uninstall } from "./install.js";
@@ -21,13 +21,12 @@ export { supports } from "./subtle.js";
21
21
  const routedFns = {};
22
22
  function routed(op) {
23
23
  if (!routedFns[op]) {
24
- routedFns[op] = function (...args) {
24
+ routedFns[op] = shape(op, (args) => {
25
25
  if (ROUTES[op](args)) return ours[op](...args);
26
26
  const ns = globalThis.crypto && globalThis.crypto.subtle;
27
27
  if (ns && typeof ns[op] === "function") return ns[op](...args);
28
- return Promise.reject(E.notSupported(op + " is not supported"));
29
- };
30
- Object.defineProperty(routedFns[op], "name", { value: op });
28
+ throw E.notSupported(op + " is not supported");
29
+ });
31
30
  }
32
31
  return routedFns[op];
33
32
  }
package/src/install.js CHANGED
@@ -1,18 +1,19 @@
1
1
  /***************************************************************************
2
- * pqfill: true-polyfill installer -- patches globalThis.crypto.subtle
2
+ * subtlepq: true-polyfill installer -- patches globalThis.crypto.subtle
3
3
  *
4
4
  * (c) 2026 VESvault Corp
5
5
  * SPDX-License-Identifier: Apache-2.0
6
6
  *
7
7
  * install() wraps SubtleCrypto.prototype methods so ML-* algorithms and
8
- * pqfill keys are handled here while every other call reaches the saved
8
+ * subtlepq keys are handled here while every other call reaches the saved
9
9
  * native original with untouched arguments. Methods and algorithms the
10
10
  * platform already supports natively are left alone (self-retiring).
11
11
  * uninstall() restores the originals.
12
12
  ***************************************************************************/
13
13
 
14
14
  import * as ours from "./subtle.js";
15
- import { ROUTES } from "./router.js";
15
+ import { ROUTES, shape } from "./router.js";
16
+ import { hasExtensions } from "./algorithms.js";
16
17
  import * as E from "./errors.js";
17
18
 
18
19
  let saved = null;
@@ -30,14 +31,17 @@ export function install() {
30
31
  if (saved) return;
31
32
  const subtle = globalThis.crypto && globalThis.crypto.subtle;
32
33
  if (!subtle) {
33
- throw new Error("pqfill: crypto.subtle is unavailable (insecure context?)");
34
+ throw new Error("subtlepq: crypto.subtle is unavailable (insecure context?)");
34
35
  }
35
36
  const proto = Object.getPrototypeOf(subtle);
36
37
  const ctor = subtle.constructor;
37
38
  saved = { proto, ctor, methods: {}, supports: Object.getOwnPropertyDescriptor(ctor, "supports") };
38
39
 
39
- /* nothing to do on platforms that already do ML-KEM + ML-DSA natively */
40
- if (nativeSupports(ctor, "encapsulateBits", "ML-KEM-768") &&
40
+ /* nothing to do on platforms that already do ML-KEM + ML-DSA natively --
41
+ * unless extension algorithms (subtlepq/dhkem) are registered, which no
42
+ * native implementation covers; register() before install() */
43
+ if (!hasExtensions() &&
44
+ nativeSupports(ctor, "encapsulateBits", "ML-KEM-768") &&
41
45
  nativeSupports(ctor, "sign", "ML-DSA-65")) {
42
46
  return;
43
47
  }
@@ -45,12 +49,11 @@ export function install() {
45
49
  for (const [op, isOurs] of Object.entries(ROUTES)) {
46
50
  const orig = proto[op];
47
51
  saved.methods[op] = Object.getOwnPropertyDescriptor(proto, op);
48
- const wrapped = async function (...args) {
52
+ const wrapped = shape(op, (args, self) => {
49
53
  if (isOurs(args)) return ours[op](...args);
50
- if (typeof orig === "function") return orig.apply(this, args);
54
+ if (typeof orig === "function") return orig.apply(self, args);
51
55
  throw E.notSupported(op + " is not supported");
52
- };
53
- Object.defineProperty(wrapped, "name", { value: op });
56
+ });
54
57
  Object.defineProperty(proto, op, {
55
58
  value: wrapped, writable: true, configurable: true,
56
59
  });
package/src/keystore.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: forged CryptoKey factory + private key-material store
2
+ * subtlepq: forged CryptoKey factory + private key-material store
3
3
  *
4
4
  * (c) 2026 VESvault Corp
5
5
  * SPDX-License-Identifier: Apache-2.0
@@ -29,8 +29,8 @@ export function forgeKey(type, algName, extractable, usages, material) {
29
29
  extractable: { value: !!extractable, enumerable: true },
30
30
  algorithm: { value: Object.freeze({ name: algName }), enumerable: true },
31
31
  usages: { value: Object.freeze([...usages]), enumerable: true },
32
- __pqfill: {
33
- value: Symbol("pqfill key: not structured-cloneable; export and re-import instead"),
32
+ __subtlepq: {
33
+ value: Symbol("subtlepq key: not structured-cloneable; export and re-import instead"),
34
34
  enumerable: true,
35
35
  },
36
36
  });
package/src/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: dispatch rules shared by the ponyfill (index.js) and the
2
+ * subtlepq: dispatch rules shared by the ponyfill (index.js) and the
3
3
  * polyfill installer (install.js)
4
4
  *
5
5
  * (c) 2026 VESvault Corp
@@ -15,7 +15,7 @@ import { isOurKey } from "./keystore.js";
15
15
 
16
16
  const ours = (alg) => !!normalizeAlg(alg);
17
17
 
18
- /* args => should pqfill handle this call? -- keyed by SubtleCrypto method */
18
+ /* args => should subtlepq handle this call? -- keyed by SubtleCrypto method */
19
19
  export const ROUTES = {
20
20
  /* wrapped existing methods */
21
21
  generateKey: (args) => ours(args[0]),
@@ -23,6 +23,10 @@ export const ROUTES = {
23
23
  exportKey: (args) => isOurKey(args[1]),
24
24
  sign: (args) => isOurKey(args[1]) || ours(args[0]),
25
25
  verify: (args) => isOurKey(args[1]) || ours(args[0]),
26
+ /* wrap/unwrap: ours when the wrapped key or either key handle is ours;
27
+ * (format, key, wrappingKey, ...) / (format, data, unwrappingKey, wrapAlg, keyAlg, ...) */
28
+ wrapKey: (args) => isOurKey(args[1]) || isOurKey(args[2]),
29
+ unwrapKey: (args) => ours(args[4]) || isOurKey(args[2]),
26
30
  /* methods added by the WICG draft */
27
31
  encapsulateBits: (args) => ours(args[0]) || isOurKey(args[1]),
28
32
  encapsulateKey: (args) => ours(args[0]) || isOurKey(args[1]),
@@ -30,3 +34,40 @@ export const ROUTES = {
30
34
  decapsulateKey: (args) => ours(args[0]) || isOurKey(args[1]),
31
35
  getPublicKey: (args) => isOurKey(args[0]),
32
36
  };
37
+
38
+ /* Wrapper factories carrying the spec parameter lists, so consoles show real
39
+ * argument hints (and fn.length matches native) instead of (...args). Written
40
+ * out statically -- Function() codegen would break under strict CSP. Each
41
+ * forwards (arguments, this) to the routing handler; the async wrapper turns
42
+ * sync throws into rejections, as native SubtleCrypto does. */
43
+ export const shape = (op, h) => SHAPES[op](h);
44
+
45
+ const SHAPES = {
46
+ generateKey: (h) => async function generateKey(
47
+ algorithm, extractable, keyUsages) { return h(arguments, this); },
48
+ importKey: (h) => async function importKey(
49
+ format, keyData, algorithm, extractable, keyUsages) { return h(arguments, this); },
50
+ exportKey: (h) => async function exportKey(
51
+ format, key) { return h(arguments, this); },
52
+ sign: (h) => async function sign(
53
+ algorithm, key, data) { return h(arguments, this); },
54
+ verify: (h) => async function verify(
55
+ algorithm, key, signature, data) { return h(arguments, this); },
56
+ encapsulateBits: (h) => async function encapsulateBits(
57
+ algorithm, encapsulationKey) { return h(arguments, this); },
58
+ encapsulateKey: (h) => async function encapsulateKey(
59
+ algorithm, encapsulationKey, sharedKeyAlgorithm, extractable, keyUsages) {
60
+ return h(arguments, this); },
61
+ decapsulateBits: (h) => async function decapsulateBits(
62
+ algorithm, decapsulationKey, ciphertext) { return h(arguments, this); },
63
+ decapsulateKey: (h) => async function decapsulateKey(
64
+ algorithm, decapsulationKey, ciphertext, sharedKeyAlgorithm, extractable, keyUsages) {
65
+ return h(arguments, this); },
66
+ getPublicKey: (h) => async function getPublicKey(
67
+ key, keyUsages) { return h(arguments, this); },
68
+ wrapKey: (h) => async function wrapKey(
69
+ format, key, wrappingKey, wrapAlgorithm) { return h(arguments, this); },
70
+ unwrapKey: (h) => async function unwrapKey(
71
+ format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm,
72
+ extractable, keyUsages) { return h(arguments, this); },
73
+ };
package/src/subtle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /***************************************************************************
2
- * pqfill: spec-shaped ML-KEM / ML-DSA operations
2
+ * subtlepq: spec-shaped ML-KEM / ML-DSA operations
3
3
  * per https://wicg.github.io/webcrypto-modern-algos/
4
4
  *
5
5
  * (c) 2026 VESvault Corp
@@ -17,11 +17,14 @@ import {
17
17
  normalizeAlg, KEM_PUB_USAGES, KEM_PRIV_USAGES,
18
18
  } from "./algorithms.js";
19
19
  import { forgeKey, materialOf, isOurKey } from "./keystore.js";
20
+ import {
21
+ encodeSpki, decodeSpki, encodePkcs8, decodePkcs8, b64u, unb64u,
22
+ } from "./formats.js";
20
23
  import * as E from "./errors.js";
21
24
 
22
25
  const rng = (n) => globalThis.crypto.getRandomValues(new Uint8Array(n));
23
26
 
24
- function toBytes(src, what) {
27
+ export function toBytes(src, what) {
25
28
  if (ArrayBuffer.isView(src)) {
26
29
  return new Uint8Array(
27
30
  src.buffer.slice(src.byteOffset, src.byteOffset + src.byteLength));
@@ -66,7 +69,7 @@ function signContext(a) {
66
69
  return ctx;
67
70
  }
68
71
 
69
- async function importSharedKey(ssBytes, sharedKeyAlgorithm, extractable, keyUsages) {
72
+ export async function importSharedKey(ssBytes, sharedKeyAlgorithm, extractable, keyUsages) {
70
73
  const ns = globalThis.crypto && globalThis.crypto.subtle;
71
74
  if (!ns) throw E.notSupported("no native SubtleCrypto to hold the shared key");
72
75
  try {
@@ -80,6 +83,7 @@ async function importSharedKey(ssBytes, sharedKeyAlgorithm, extractable, keyUsag
80
83
 
81
84
  export async function generateKey(algorithm, extractable, keyUsages = []) {
82
85
  const a = requireAlg(algorithm);
86
+ if (a.ops) return a.ops.generateKey(algorithm, extractable, keyUsages);
83
87
  const eng = await getEngine();
84
88
  if (a.kind === "kem") {
85
89
  checkUsages(keyUsages, [...KEM_PUB_USAGES, ...KEM_PRIV_USAGES]);
@@ -108,44 +112,116 @@ export async function generateKey(algorithm, extractable, keyUsages = []) {
108
112
 
109
113
  export async function importKey(format, keyData, algorithm, extractable, keyUsages = []) {
110
114
  const a = requireAlg(algorithm);
115
+ if (a.ops) return a.ops.importKey(format, keyData, algorithm, extractable, keyUsages);
111
116
  const eng = await getEngine();
112
117
  const L = a.kind === "kem" ? eng.kemLengths(a.name) : eng.sigLengths(a.name);
113
118
  const pubUsages = a.kind === "kem" ? KEM_PUB_USAGES : ["verify"];
114
119
  const privUsages = a.kind === "kem" ? KEM_PRIV_USAGES : ["sign"];
115
120
 
116
- if (format === "raw-public") {
121
+ const importPub = (pub) => {
117
122
  checkUsages(keyUsages, pubUsages);
118
- const pub = toBytes(keyData, "keyData");
119
123
  if (pub.length !== L.pk) throw E.dataError("bad " + a.name + " public key length");
120
124
  return forgeKey("public", a.name, extractable, keyUsages, { pub });
121
- }
122
- if (format === "raw-seed") {
125
+ };
126
+ /* expanded (pkcs8 "both" / jwk pub): must agree with the seed-derived value */
127
+ const importSeed = (seed, expanded, expect) => {
123
128
  checkUsages(keyUsages, privUsages);
124
129
  if (!keyUsages.length) throw E.syntaxError("private key usages must not be empty");
125
- const seed = toBytes(keyData, "keyData");
126
130
  if (seed.length !== L.seed) throw E.dataError("bad " + a.name + " seed length");
127
131
  const { pub, sk } = a.kind === "kem"
128
132
  ? eng.kemKeypairDerand(a.name, seed)
129
133
  : eng.sigKeypairDerand(a.name, seed);
134
+ if (expanded && !bytesEqual(expanded, expect === "pub" ? pub : sk)) {
135
+ throw E.dataError(a.name + " seed and " +
136
+ (expect === "pub" ? "public key" : "expandedKey") + " do not match");
137
+ }
130
138
  return forgeKey("private", a.name, extractable, keyUsages, { pub, seed, sk });
139
+ };
140
+
141
+ if (format === "raw-public") return importPub(toBytes(keyData, "keyData"));
142
+ if (format === "raw-seed") return importSeed(toBytes(keyData, "keyData"));
143
+ if (format === "spki") {
144
+ const { name, pub } = decodeSpki(toBytes(keyData, "keyData"));
145
+ if (name !== a.name) throw E.dataError("SPKI algorithm is " + name + ", not " + a.name);
146
+ return importPub(pub);
147
+ }
148
+ if (format === "pkcs8") {
149
+ const { name, seed, expanded } = decodePkcs8(toBytes(keyData, "keyData"));
150
+ if (name !== a.name) throw E.dataError("PKCS#8 algorithm is " + name + ", not " + a.name);
151
+ return importSeed(seed, expanded, "sk");
152
+ }
153
+ if (format === "jwk") return importJwk(keyData, a, extractable, importPub, importSeed);
154
+ throw E.notSupported("key format " + format + " is not supported");
155
+ }
156
+
157
+ function bytesEqual(x, y) {
158
+ if (x.length !== y.length) return false;
159
+ let d = 0;
160
+ for (let i = 0; i < x.length; i++) d |= x[i] ^ y[i];
161
+ return d === 0;
162
+ }
163
+
164
+ function importJwk(jwk, a, extractable, importPub, importSeed) {
165
+ if (!jwk || typeof jwk !== "object" || ArrayBuffer.isView(jwk) ||
166
+ jwk instanceof ArrayBuffer) {
167
+ throw E.dataError("jwk import expects a JsonWebKey object");
131
168
  }
132
- /* spki / pkcs8 / jwk land in P2 */
133
- throw E.notSupported("key format " + format + " is not supported yet");
169
+ if (jwk.kty !== "AKP") throw E.dataError('JWK kty must be "AKP"');
170
+ if (jwk.alg !== undefined && jwk.alg !== a.name) {
171
+ throw E.dataError("JWK alg does not match " + a.name);
172
+ }
173
+ if (jwk.ext === false && extractable) {
174
+ throw E.dataError("JWK ext forbids an extractable key");
175
+ }
176
+ const checkOps = (usages) => {
177
+ if (jwk.key_ops === undefined) return;
178
+ if (!Array.isArray(jwk.key_ops)) throw E.dataError("JWK key_ops must be an array");
179
+ for (const u of usages) {
180
+ if (!jwk.key_ops.includes(u)) {
181
+ throw E.dataError("JWK key_ops does not permit " + u);
182
+ }
183
+ }
184
+ };
185
+ if (jwk.priv !== undefined) {
186
+ const key = importSeed(unb64u(jwk.priv, "priv"),
187
+ jwk.pub === undefined ? null : unb64u(jwk.pub, "pub"), "pub");
188
+ checkOps(key.usages);
189
+ return key;
190
+ }
191
+ if (jwk.pub === undefined) throw E.dataError("JWK has neither pub nor priv");
192
+ const key = importPub(unb64u(jwk.pub, "pub"));
193
+ checkOps(key.usages);
194
+ return key;
134
195
  }
135
196
 
136
197
  export async function exportKey(format, key) {
137
198
  const a = requireAlg(key && key.algorithm && key.algorithm.name);
138
199
  const m = requireKey(key, a, key.type, null);
139
200
  if (!key.extractable) throw E.invalidAccess("key is not extractable");
140
- if (format === "raw-public") {
141
- if (key.type !== "public") throw E.invalidAccess("raw-public exports public keys");
142
- return m.pub.slice().buffer;
143
- }
144
- if (format === "raw-seed") {
145
- if (key.type !== "private") throw E.invalidAccess("raw-seed exports private keys");
146
- return m.seed.slice().buffer;
201
+ const pub = () => {
202
+ if (key.type !== "public") throw E.invalidAccess(format + " exports public keys");
203
+ return m.pub;
204
+ };
205
+ const seed = () => {
206
+ if (key.type !== "private") throw E.invalidAccess(format + " exports private keys");
207
+ return m.seed;
208
+ };
209
+ if (format === "raw-public") return pub().slice().buffer;
210
+ if (format === "raw-seed") return seed().slice().buffer;
211
+ if (format === "spki") return encodeSpki(a.name, pub()).buffer;
212
+ if (format === "pkcs8") return encodePkcs8(a.name, seed()).buffer;
213
+ if (format === "jwk") {
214
+ const jwk = {
215
+ kty: "AKP",
216
+ alg: a.name,
217
+ pub: b64u(m.pub),
218
+ key_ops: [...key.usages],
219
+ ext: key.extractable,
220
+ };
221
+ if (key.type === "private") jwk.priv = b64u(seed());
222
+ return jwk;
147
223
  }
148
- throw E.notSupported("key format " + format + " is not supported yet");
224
+ throw E.notSupported("key format " + format + " is not supported");
149
225
  }
150
226
 
151
227
  export async function getPublicKey(key, keyUsages = []) {
@@ -160,6 +236,7 @@ export async function getPublicKey(key, keyUsages = []) {
160
236
 
161
237
  export async function encapsulateBits(algorithm, encapsulationKey) {
162
238
  const a = requireAlg(algorithm, "kem");
239
+ if (a.ops) return a.ops.encapsulateBits(algorithm, encapsulationKey);
163
240
  const m = requireKey(encapsulationKey, a, "public", "encapsulateBits");
164
241
  const { ct, ss } = (await getEngine()).kemEncaps(a.name, m.pub);
165
242
  return { sharedKey: ss.buffer, ciphertext: ct.buffer };
@@ -168,6 +245,10 @@ export async function encapsulateBits(algorithm, encapsulationKey) {
168
245
  export async function encapsulateKey(algorithm, encapsulationKey,
169
246
  sharedKeyAlgorithm, extractable, keyUsages) {
170
247
  const a = requireAlg(algorithm, "kem");
248
+ if (a.ops) {
249
+ return a.ops.encapsulateKey(algorithm, encapsulationKey,
250
+ sharedKeyAlgorithm, extractable, keyUsages);
251
+ }
171
252
  const m = requireKey(encapsulationKey, a, "public", "encapsulateKey");
172
253
  const { ct, ss } = (await getEngine()).kemEncaps(a.name, m.pub);
173
254
  const sharedKey = await importSharedKey(ss, sharedKeyAlgorithm, extractable, keyUsages);
@@ -176,6 +257,7 @@ export async function encapsulateKey(algorithm, encapsulationKey,
176
257
 
177
258
  export async function decapsulateBits(algorithm, decapsulationKey, ciphertext) {
178
259
  const a = requireAlg(algorithm, "kem");
260
+ if (a.ops) return a.ops.decapsulateBits(algorithm, decapsulationKey, ciphertext);
179
261
  const m = requireKey(decapsulationKey, a, "private", "decapsulateBits");
180
262
  const eng = await getEngine();
181
263
  const ct = toBytes(ciphertext, "ciphertext");
@@ -188,6 +270,10 @@ export async function decapsulateBits(algorithm, decapsulationKey, ciphertext) {
188
270
  export async function decapsulateKey(algorithm, decapsulationKey, ciphertext,
189
271
  sharedKeyAlgorithm, extractable, keyUsages) {
190
272
  const a = requireAlg(algorithm, "kem");
273
+ if (a.ops) {
274
+ return a.ops.decapsulateKey(algorithm, decapsulationKey, ciphertext,
275
+ sharedKeyAlgorithm, extractable, keyUsages);
276
+ }
191
277
  const m = requireKey(decapsulationKey, a, "private", "decapsulateKey");
192
278
  const eng = await getEngine();
193
279
  const ct = toBytes(ciphertext, "ciphertext");
@@ -216,6 +302,77 @@ export async function verify(algorithm, key, signature, data) {
216
302
  a.name, m.pub, toBytes(data, "data"), ctx, toBytes(signature, "signature"));
217
303
  }
218
304
 
305
+ /* ---- wrapKey / unwrapKey: exportKey + native wrap composition ----
306
+ *
307
+ * The exported bytes ride through the native wrapKey/unwrapKey inside a
308
+ * throwaway raw-imported HMAC key, so the wrap algorithm gets the native
309
+ * spec-defined treatment (encrypt for AES-GCM/RSA-OAEP, key wrap for
310
+ * AES-KW) and the wrapping key's own usages are enforced natively. */
311
+
312
+ function nativeSubtle() {
313
+ const ns = globalThis.crypto && globalThis.crypto.subtle;
314
+ if (!ns) throw E.notSupported("no native SubtleCrypto for key wrapping");
315
+ return ns;
316
+ }
317
+
318
+ const CARRIER = { name: "HMAC", hash: "SHA-256" };
319
+
320
+ export async function wrapKey(format, key, wrappingKey, wrapAlgorithm) {
321
+ if (isOurKey(wrappingKey)) {
322
+ throw E.invalidAccess("ML-KEM/ML-DSA keys cannot wrap other keys");
323
+ }
324
+ if (!isOurKey(key)) {
325
+ throw E.invalidAccess("key is not from this polyfill");
326
+ }
327
+ const exported = await exportKey(format, key);
328
+ let bytes = format === "jwk"
329
+ ? new TextEncoder().encode(JSON.stringify(exported))
330
+ : new Uint8Array(exported);
331
+ /* WebCrypto pads JWK JSON with spaces to the AES-KW 8-byte multiple */
332
+ const wrapName = wrapAlgorithm && wrapAlgorithm.name || wrapAlgorithm;
333
+ if (format === "jwk" && bytes.length % 8 &&
334
+ String(wrapName).toUpperCase() === "AES-KW") {
335
+ const padded = new Uint8Array(Math.ceil(bytes.length / 8) * 8).fill(0x20);
336
+ padded.set(bytes);
337
+ bytes.fill(0);
338
+ bytes = padded;
339
+ }
340
+ const ns = nativeSubtle();
341
+ try {
342
+ const carrier = await ns.importKey("raw", bytes, CARRIER, true, ["sign"]);
343
+ return await ns.wrapKey("raw", carrier, wrappingKey, wrapAlgorithm);
344
+ } finally {
345
+ bytes.fill(0);
346
+ }
347
+ }
348
+
349
+ export async function unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm,
350
+ unwrappedKeyAlgorithm, extractable, keyUsages) {
351
+ if (isOurKey(unwrappingKey)) {
352
+ throw E.invalidAccess("ML-KEM/ML-DSA keys cannot unwrap other keys");
353
+ }
354
+ requireAlg(unwrappedKeyAlgorithm);
355
+ const ns = nativeSubtle();
356
+ const carrier = await ns.unwrapKey("raw", wrappedKey, unwrappingKey,
357
+ unwrapAlgorithm, CARRIER, true, ["sign"]);
358
+ const bytes = new Uint8Array(await ns.exportKey("raw", carrier));
359
+ let keyData = bytes;
360
+ if (format === "jwk") {
361
+ try {
362
+ keyData = JSON.parse(new TextDecoder().decode(bytes));
363
+ } catch {
364
+ throw E.dataError("unwrapped data is not JWK JSON");
365
+ } finally {
366
+ bytes.fill(0);
367
+ }
368
+ }
369
+ try {
370
+ return await importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
371
+ } finally {
372
+ if (keyData instanceof Uint8Array) keyData.fill(0);
373
+ }
374
+ }
375
+
219
376
  /* ---- feature detection ---- */
220
377
 
221
378
  const OPS = {
@@ -226,5 +383,7 @@ const OPS = {
226
383
 
227
384
  export function supports(operation, algorithm) {
228
385
  const a = normalizeAlg(algorithm);
229
- return a ? OPS[a.kind].includes(operation) : false;
386
+ if (!a) return false;
387
+ if (a.ops) return a.ops.supports(operation);
388
+ return OPS[a.kind].includes(operation);
230
389
  }
@@ -0,0 +1,2 @@
1
+ async function SubtlePQEngine(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await import("node:module");var require=createRequire(import.meta.url)}var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");if(_scriptName.startsWith("file:")){scriptDirectory=require("node:path").dirname(require("node:url").fileURLToPath(_scriptName))+"/"}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["f"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("subtlepq-engine.wasm")}return new URL("subtlepq-engine.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var printCharBuffers=[null,[],[]];var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>2]=num;return 0};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>(crypto.getRandomValues(view),0)};var randomFill=view=>(randomFill=initRandomFill())(view);var _random_get=(buffer,size)=>randomFill(HEAPU8.subarray(buffer,buffer+size));{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var _pqf_kem_lengths,_free,_malloc,_pqf_kem_keypair,_pqf_kem_keypair_derand,_pqf_kem_encaps,_pqf_kem_encaps_derand,_pqf_kem_decaps,_pqf_sig_lengths,_pqf_sig_keypair,_pqf_sig_keypair_derand,_pqf_sig_sign,_pqf_sig_sign_derand,_pqf_sig_verify,_pqf_liboqs_version,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_pqf_kem_lengths=Module["_pqf_kem_lengths"]=wasmExports["g"];_free=Module["_free"]=wasmExports["h"];_malloc=Module["_malloc"]=wasmExports["i"];_pqf_kem_keypair=Module["_pqf_kem_keypair"]=wasmExports["j"];_pqf_kem_keypair_derand=Module["_pqf_kem_keypair_derand"]=wasmExports["k"];_pqf_kem_encaps=Module["_pqf_kem_encaps"]=wasmExports["l"];_pqf_kem_encaps_derand=Module["_pqf_kem_encaps_derand"]=wasmExports["m"];_pqf_kem_decaps=Module["_pqf_kem_decaps"]=wasmExports["n"];_pqf_sig_lengths=Module["_pqf_sig_lengths"]=wasmExports["o"];_pqf_sig_keypair=Module["_pqf_sig_keypair"]=wasmExports["p"];_pqf_sig_keypair_derand=Module["_pqf_sig_keypair_derand"]=wasmExports["q"];_pqf_sig_sign=Module["_pqf_sig_sign"]=wasmExports["r"];_pqf_sig_sign_derand=Module["_pqf_sig_sign_derand"]=wasmExports["s"];_pqf_sig_verify=Module["_pqf_sig_verify"]=wasmExports["t"];_pqf_liboqs_version=Module["_pqf_liboqs_version"]=wasmExports["u"];memory=wasmMemory=wasmExports["e"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={c:_emscripten_resize_heap,a:_exit,b:_fd_write,d:_random_get};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})}
2
+ ;return moduleRtn}export default SubtlePQEngine;
@@ -1,2 +0,0 @@
1
- async function PQFillEngine(moduleArg={}){var moduleRtn;var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await import("node:module");var require=createRequire(import.meta.url)}var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");if(_scriptName.startsWith("file:")){scriptDirectory=require("node:path").dirname(require("node:url").fileURLToPath(_scriptName))+"/"}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;var isFileURI=filename=>filename.startsWith("file://");class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var readyPromiseResolve,readyPromiseReject;var runtimeInitialized=false;function updateMemoryViews(){var b=wasmMemory.buffer;HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["f"]()}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject?.(e);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("pqfill-engine.wasm")}return new URL("pqfill-engine.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){return new Promise((resolve,reject)=>{Module["instantiateWasm"](info,(inst,mod)=>{resolve(receiveInstance(inst,mod))})})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP16;var HEAP32;var HEAP64;var HEAP8;var HEAPF32;var HEAPF64;var HEAPU16;var HEAPU32;var HEAPU64;var HEAPU8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var addOnPostRun=cb=>onPostRuns.push(cb);var onPreRuns=[];var addOnPreRun=cb=>onPreRuns.push(cb);var noExitRuntime=true;var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var runtimeKeepaliveCounter=0;var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var printCharBuffers=[null,[],[]];var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>2]=num;return 0};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>nodeCrypto.randomFillSync(view)}return view=>(crypto.getRandomValues(view),0)};var randomFill=view=>(randomFill=initRandomFill())(view);var _random_get=(buffer,size)=>randomFill(HEAPU8.subarray(buffer,buffer+size));{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].shift()()}}}var _pqf_kem_lengths,_free,_malloc,_pqf_kem_keypair,_pqf_kem_keypair_derand,_pqf_kem_encaps,_pqf_kem_encaps_derand,_pqf_kem_decaps,_pqf_sig_lengths,_pqf_sig_keypair,_pqf_sig_keypair_derand,_pqf_sig_sign,_pqf_sig_sign_derand,_pqf_sig_verify,_pqf_liboqs_version,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_pqf_kem_lengths=Module["_pqf_kem_lengths"]=wasmExports["g"];_free=Module["_free"]=wasmExports["h"];_malloc=Module["_malloc"]=wasmExports["i"];_pqf_kem_keypair=Module["_pqf_kem_keypair"]=wasmExports["j"];_pqf_kem_keypair_derand=Module["_pqf_kem_keypair_derand"]=wasmExports["k"];_pqf_kem_encaps=Module["_pqf_kem_encaps"]=wasmExports["l"];_pqf_kem_encaps_derand=Module["_pqf_kem_encaps_derand"]=wasmExports["m"];_pqf_kem_decaps=Module["_pqf_kem_decaps"]=wasmExports["n"];_pqf_sig_lengths=Module["_pqf_sig_lengths"]=wasmExports["o"];_pqf_sig_keypair=Module["_pqf_sig_keypair"]=wasmExports["p"];_pqf_sig_keypair_derand=Module["_pqf_sig_keypair_derand"]=wasmExports["q"];_pqf_sig_sign=Module["_pqf_sig_sign"]=wasmExports["r"];_pqf_sig_sign_derand=Module["_pqf_sig_sign_derand"]=wasmExports["s"];_pqf_sig_verify=Module["_pqf_sig_verify"]=wasmExports["t"];_pqf_liboqs_version=Module["_pqf_liboqs_version"]=wasmExports["u"];memory=wasmMemory=wasmExports["e"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={c:_emscripten_resize_heap,a:_exit,b:_fd_write,d:_random_get};function run(){preRun();function doRun(){Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve?.(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}var wasmExports;wasmExports=await (createWasm());run();if(runtimeInitialized){moduleRtn=Module}else{moduleRtn=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject})}
2
- ;return moduleRtn}export default PQFillEngine;
Binary file