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 +38 -5
- package/package.json +27 -22
- package/src/algorithms.js +29 -8
- package/src/dhkem.js +321 -0
- package/src/engine.js +2 -2
- package/src/errors.js +1 -1
- package/src/formats.js +174 -0
- package/src/index.js +5 -6
- package/src/install.js +13 -10
- package/src/keystore.js +3 -3
- package/src/router.js +43 -2
- package/src/subtle.js +178 -19
- package/wasm/dist/subtlepq-engine.mjs +2 -0
- package/wasm/dist/subtlepq-engine.single.mjs +0 -0
- package/wasm/dist/pqfill-engine.mjs +0 -2
- package/wasm/dist/pqfill-engine.single.mjs +0 -0
- /package/wasm/dist/{pqfill-engine.wasm → subtlepq-engine.wasm} +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
#
|
|
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:
|
|
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 (`
|
|
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/
|
|
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.
|
|
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/
|
|
10
|
+
"url": "git+https://github.com/vesvault/subtlepq.git"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://github.com/vesvault/
|
|
12
|
+
"homepage": "https://github.com/vesvault/subtlepq#readme",
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/vesvault/
|
|
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
|
-
"
|
|
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
|
-
"
|
|
31
|
-
"
|
|
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
|
-
"
|
|
36
|
-
"
|
|
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
|
-
*
|
|
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 (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
*
|
|
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/
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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] =
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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("
|
|
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
|
-
|
|
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 =
|
|
52
|
+
const wrapped = shape(op, (args, self) => {
|
|
49
53
|
if (isOurs(args)) return ours[op](...args);
|
|
50
|
-
if (typeof orig === "function") return orig.apply(
|
|
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
|
-
*
|
|
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
|
-
|
|
33
|
-
value: Symbol("
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
141
|
-
if (key.type !== "public") throw E.invalidAccess("
|
|
142
|
-
return m.pub
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (key.type !== "private") throw E.invalidAccess("
|
|
146
|
-
return m.seed
|
|
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
|
|
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
|
-
|
|
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;
|
|
Binary file
|
|
@@ -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
|
|
File without changes
|