@qntx/nostr-wasm 0.14.0 → 0.15.1

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.
@@ -1,7 +1,7 @@
1
- import { n as LoadNostrWasmOptions, r as NostrWasm, t as WasmPoisonedError } from "./adapter-DMQvHVO_.mjs";
1
+ import { i as NostrWasm, n as WasmPoisonedError, r as LoadNostrWasmOptions, t as NOSTR_WASM_INTEGRITY } from "./integrity-BmoamQip.mjs";
2
2
  //#region src/load.browser.d.ts
3
3
  /** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */
4
4
  export declare function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm>;
5
5
  //#endregion
6
- export { type LoadNostrWasmOptions, type NostrWasm, WasmPoisonedError };
6
+ export { type LoadNostrWasmOptions, NOSTR_WASM_INTEGRITY, type NostrWasm, WasmPoisonedError };
7
7
  //# sourceMappingURL=browser.d.mts.map
package/dist/browser.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as WasmPoisonedError, n as fetchWasmUrl, r as isWasmBytes, t as createNostrWasmLoader } from "./instance-6y1G4BjC.mjs";
1
+ import { a as WasmPoisonedError, i as isWasmBytes, n as createNostrWasmLoader, r as fetchWasmUrl, t as NOSTR_WASM_INTEGRITY } from "./integrity-BfNJJNcA.mjs";
2
2
  import { CryptoError } from "@qntx/nostr/core";
3
3
  //#region src/load.browser.ts
4
4
  async function wasmBytes(opts) {
@@ -14,6 +14,6 @@ async function loadNostrWasm(opts) {
14
14
  return loader.loadNostrWasm(opts);
15
15
  }
16
16
  //#endregion
17
- export { WasmPoisonedError, loadNostrWasm };
17
+ export { NOSTR_WASM_INTEGRITY, WasmPoisonedError, loadNostrWasm };
18
18
 
19
19
  //# sourceMappingURL=browser.mjs.map
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { n as LoadNostrWasmOptions, r as NostrWasm, t as WasmPoisonedError } from "./adapter-DMQvHVO_.mjs";
1
+ import { i as NostrWasm, n as WasmPoisonedError, r as LoadNostrWasmOptions, t as NOSTR_WASM_INTEGRITY } from "./integrity-BmoamQip.mjs";
2
2
  //#region src/load.d.ts
3
3
  /** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */
4
4
  export declare function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm>;
5
5
  //#endregion
6
- export { type LoadNostrWasmOptions, type NostrWasm, WasmPoisonedError };
6
+ export { type LoadNostrWasmOptions, NOSTR_WASM_INTEGRITY, type NostrWasm, WasmPoisonedError };
7
7
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as WasmPoisonedError, n as fetchWasmUrl, r as isWasmBytes, t as createNostrWasmLoader } from "./instance-6y1G4BjC.mjs";
1
+ import { a as WasmPoisonedError, i as isWasmBytes, n as createNostrWasmLoader, r as fetchWasmUrl, t as NOSTR_WASM_INTEGRITY } from "./integrity-BfNJJNcA.mjs";
2
2
  //#region src/load.ts
3
3
  async function defaultWasmHref() {
4
4
  return (await import("./nostr_wasm-BkgBL7qN.mjs")).default;
@@ -22,6 +22,6 @@ async function loadNostrWasm(opts) {
22
22
  return loader.loadNostrWasm(opts);
23
23
  }
24
24
  //#endregion
25
- export { WasmPoisonedError, loadNostrWasm };
25
+ export { NOSTR_WASM_INTEGRITY, WasmPoisonedError, loadNostrWasm };
26
26
 
27
27
  //# sourceMappingURL=index.mjs.map
@@ -21,7 +21,8 @@ function asExports(raw) {
21
21
  nostr_verify: requireFn(raw["nostr_verify"], "nostr_verify"),
22
22
  nostr_verify_serialized: requireFn(raw["nostr_verify_serialized"], "nostr_verify_serialized"),
23
23
  nostr_sign: requireFn(raw["nostr_sign"], "nostr_sign"),
24
- nostr_public_key: requireFn(raw["nostr_public_key"], "nostr_public_key")
24
+ nostr_public_key: requireFn(raw["nostr_public_key"], "nostr_public_key"),
25
+ nostr_ecdh_x: requireFn(raw["nostr_ecdh_x"], "nostr_ecdh_x")
25
26
  };
26
27
  }
27
28
  /**
@@ -96,13 +97,25 @@ async function instantiateCryptoWasm(bytes) {
96
97
  if (imports.length > 0) throw new TypeError(`nostr_wasm module must not import anything, got ${imports.length} imports`);
97
98
  const instance = await WebAssembly.instantiate(module, {});
98
99
  const version = requireFn(instance.exports["nostr_abi_version"], "nostr_abi_version")();
99
- if (version !== 3) throw new CryptoError(`unsupported nostr_wasm ABI version ${String(version)}, expected 3`);
100
+ if (version !== 4) throw new CryptoError(`unsupported nostr_wasm ABI version ${String(version)}, expected 4`);
100
101
  const exports = asExports(instance.exports);
101
102
  const { ptr, view } = scratch(exports, SEED_LEN);
102
103
  view.set(randomBytes(SEED_LEN), 0);
103
104
  if (exports.nostr_init(ptr) !== 0) throw new CryptoError("nostr_init failed to randomize the wasm context");
104
105
  return exports;
105
106
  }
107
+ /**
108
+ * Returns the 32-byte x coordinate of the ECDH shared point, or an empty array on failure (invalid
109
+ * input). The wasm module zeroes the scratch copy of `seckey` before returning.
110
+ */
111
+ function wasmEcdhX(exports, seckey, publicKeyX) {
112
+ const outOffset = 64;
113
+ const { ptr, view } = scratch(exports, 96);
114
+ view.set(seckey, 0);
115
+ view.set(publicKeyX, SECKEY_LEN);
116
+ if (exports.nostr_ecdh_x(ptr, ptr + SECKEY_LEN, ptr + outOffset) !== 0) return /* @__PURE__ */ new Uint8Array(0);
117
+ return takeOut(exports, ptr + outOffset, 32);
118
+ }
106
119
  //#endregion
107
120
  //#region src/adapter.ts
108
121
  /** Wasm instance aborted; later calls on that instance fail. */
@@ -131,7 +144,7 @@ function runPoisoned(poison, fn) {
131
144
  * `WasmPoisonedError`; any other backend error verifies `false` (the event is marked failed).
132
145
  */
133
146
  function createWasmEventVerifier(verifySerialized, poison) {
134
- const backend = (serializedUtf8, id, pubkey, sig) => {
147
+ const raw = (serializedUtf8, id, pubkey, sig) => {
135
148
  try {
136
149
  return runPoisoned(poison, () => verifySerialized(serializedUtf8, id, pubkey, sig));
137
150
  } catch (error) {
@@ -139,7 +152,18 @@ function createWasmEventVerifier(verifySerialized, poison) {
139
152
  return false;
140
153
  }
141
154
  };
142
- return createEventVerifier(backend);
155
+ return createEventVerifier({
156
+ verifySerialized: raw,
157
+ publicKey: () => {
158
+ throw new NostrError("wasm verifier backend: publicKey is unsupported");
159
+ },
160
+ sign: () => {
161
+ throw new NostrError("wasm verifier backend: sign is unsupported");
162
+ },
163
+ ecdhX: () => {
164
+ throw new NostrError("wasm verifier backend: ecdhX is unsupported");
165
+ }
166
+ });
143
167
  }
144
168
  //#endregion
145
169
  //#region src/instance.ts
@@ -174,6 +198,13 @@ function bindExports(exports) {
174
198
  const pk = wasmPublicKey(exports, seckey);
175
199
  if (pk.length !== 32) throw new CryptoError("wasm publicKey failed");
176
200
  return pk;
201
+ }),
202
+ ecdhX: (seckey, publicKeyX) => runPoisoned(poison, () => {
203
+ requireByteLength(seckey, 32, "secret key");
204
+ requireByteLength(publicKeyX, 32, "public key");
205
+ const x = wasmEcdhX(exports, seckey, publicKeyX);
206
+ if (x.length !== 32) throw new CryptoError("wasm ecdhX failed");
207
+ return x;
177
208
  })
178
209
  };
179
210
  }
@@ -204,6 +235,9 @@ function createNostrWasmLoader(wasmBytes) {
204
235
  };
205
236
  }
206
237
  //#endregion
207
- export { WasmPoisonedError as i, fetchWasmUrl as n, isWasmBytes as r, createNostrWasmLoader as t };
238
+ //#region src/integrity.ts
239
+ const NOSTR_WASM_INTEGRITY = "sha256-p9Cj0Klb81RjKSm5EUX28CKmoXHhZqFRrdks6Pq5ejQ=";
240
+ //#endregion
241
+ export { WasmPoisonedError as a, isWasmBytes as i, createNostrWasmLoader as n, fetchWasmUrl as r, NOSTR_WASM_INTEGRITY as t };
208
242
 
209
- //# sourceMappingURL=instance-6y1G4BjC.mjs.map
243
+ //# sourceMappingURL=integrity-BfNJJNcA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity-BfNJJNcA.mjs","names":[],"sources":["../src/abi.ts","../src/adapter.ts","../src/instance.ts","../src/integrity.ts"],"sourcesContent":["import { CryptoError, randomBytes } from \"@qntx/nostr/core\";\n\n/**\n * Byte-ABI version 3 surface exported by `nostr_wasm.wasm` (`packages/nostr-wasm/c/nostr_wasm.c`\n * over the shared `nostr_core.c` in `packages/nostr-native/cpp`). All inputs are caller-written\n * regions of a single scratch buffer; status codes are 0 ok/verified, 1 verification failed, 2\n * invalid input. `nostr_init` must succeed once with a 32-byte seed before the crypto ops run.\n */\nexport type CryptoWasmExports = {\n memory: WebAssembly.Memory;\n nostr_abi_version: () => number;\n nostr_init: (seed: number) => number;\n nostr_buffer: (len: number) => number;\n nostr_verify: (id: number, pubkey: number, sig: number) => number;\n nostr_verify_serialized: (\n ser: number,\n serLen: number,\n id: number,\n pubkey: number,\n sig: number,\n ) => number;\n nostr_sign: (id: number, seckey: number, aux: number, outSig: number) => number;\n nostr_public_key: (seckey: number, outPubkey: number) => number;\n nostr_ecdh_x: (seckey: number, pubkeyX: number, out32: number) => number;\n};\n\nexport const NOSTR_ABI_VERSION = 4;\n\nconst SEED_LEN = 32;\n\nconst ID_LEN = 32;\nconst PUBKEY_LEN = 32;\nconst SIG_LEN = 64;\nconst SECKEY_LEN = 32;\nconst AUX_LEN = 32;\n\nfunction requireMemory(value: WebAssembly.ExportValue | undefined): WebAssembly.Memory {\n if (!(value instanceof WebAssembly.Memory)) {\n throw new TypeError(\"wasm missing memory export\");\n }\n return value;\n}\n\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- T binds the declared ABI signature at each call site\nfunction requireFn<T extends (...args: number[]) => unknown>(\n value: WebAssembly.ExportValue | undefined,\n name: string,\n): T {\n if (typeof value !== \"function\") {\n throw new TypeError(`wasm missing ${name} export`);\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- wasm exports are opaque; each name is bound to the signature the ABI contract requires\n return value as T;\n}\n\nfunction asExports(raw: WebAssembly.Exports): CryptoWasmExports {\n return {\n memory: requireMemory(raw[\"memory\"]),\n nostr_abi_version: requireFn(raw[\"nostr_abi_version\"], \"nostr_abi_version\"),\n nostr_init: requireFn(raw[\"nostr_init\"], \"nostr_init\"),\n nostr_buffer: requireFn(raw[\"nostr_buffer\"], \"nostr_buffer\"),\n nostr_verify: requireFn(raw[\"nostr_verify\"], \"nostr_verify\"),\n nostr_verify_serialized: requireFn(raw[\"nostr_verify_serialized\"], \"nostr_verify_serialized\"),\n nostr_sign: requireFn(raw[\"nostr_sign\"], \"nostr_sign\"),\n nostr_public_key: requireFn(raw[\"nostr_public_key\"], \"nostr_public_key\"),\n nostr_ecdh_x: requireFn(raw[\"nostr_ecdh_x\"], \"nostr_ecdh_x\"),\n };\n}\n\n/**\n * Reserve `total` bytes in the wasm scratch region and return a fresh view. `nostr_buffer` returns\n * NULL when `memory.grow` fails; `__heap_base` is never 0, so 0 is unambiguously an allocation\n * failure — throw instead of writing at address 0. The view is taken after `nostr_buffer` ran, so a\n * growth inside it cannot leave a detached view in the caller's hands.\n */\nfunction scratch(exports: CryptoWasmExports, total: number): { ptr: number; view: Uint8Array } {\n const ptr = exports.nostr_buffer(total) >>> 0;\n if (ptr === 0) {\n throw new CryptoError(\"wasm scratch allocation failed\");\n }\n return { ptr, view: new Uint8Array(exports.memory.buffer, ptr, total) };\n}\n\n/** Copies `len` scratch bytes out; the view is re-acquired after the call. */\nfunction takeOut(exports: CryptoWasmExports, ptr: number, len: number): Uint8Array {\n const out = new Uint8Array(len);\n out.set(new Uint8Array(exports.memory.buffer, ptr, len));\n return out;\n}\n\nexport function wasmVerify(\n exports: CryptoWasmExports,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n): boolean {\n if (id.length !== ID_LEN || pubkey.length !== PUBKEY_LEN || sig.length !== SIG_LEN) {\n return false; // fixed-size ABI: a wrong-length input simply cannot verify\n }\n const { ptr, view } = scratch(exports, ID_LEN + PUBKEY_LEN + SIG_LEN);\n view.set(id, 0);\n view.set(pubkey, ID_LEN);\n view.set(sig, ID_LEN + PUBKEY_LEN);\n return exports.nostr_verify(ptr, ptr + ID_LEN, ptr + ID_LEN + PUBKEY_LEN) === 0;\n}\n\nexport function wasmVerifySerialized(\n exports: CryptoWasmExports,\n serialized: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n): boolean {\n if (id.length !== ID_LEN || pubkey.length !== PUBKEY_LEN || sig.length !== SIG_LEN) {\n return false;\n }\n const serLen = serialized.length;\n const { ptr, view } = scratch(exports, serLen + ID_LEN + PUBKEY_LEN + SIG_LEN);\n view.set(serialized, 0);\n view.set(id, serLen);\n view.set(pubkey, serLen + ID_LEN);\n view.set(sig, serLen + ID_LEN + PUBKEY_LEN);\n return (\n exports.nostr_verify_serialized(\n ptr,\n serLen,\n ptr + serLen,\n ptr + serLen + ID_LEN,\n ptr + serLen + ID_LEN + PUBKEY_LEN,\n ) === 0\n );\n}\n\n/**\n * Returns the 64-byte signature, or an empty array on failure (invalid input). The wasm module\n * zeroes the scratch copy of `seckey` before returning.\n */\nexport function wasmSign(\n exports: CryptoWasmExports,\n id: Uint8Array,\n seckey: Uint8Array,\n aux: Uint8Array,\n): Uint8Array {\n const outOffset = ID_LEN + SECKEY_LEN + AUX_LEN;\n const { ptr, view } = scratch(exports, outOffset + SIG_LEN);\n view.set(id, 0);\n view.set(seckey, ID_LEN);\n view.set(aux, ID_LEN + SECKEY_LEN);\n const status = exports.nostr_sign(ptr, ptr + ID_LEN, ptr + ID_LEN + SECKEY_LEN, ptr + outOffset);\n if (status !== 0) {\n return new Uint8Array(0);\n }\n return takeOut(exports, ptr + outOffset, SIG_LEN);\n}\n\n/**\n * Returns the 32-byte x-only public key, or an empty array on failure. The wasm module zeroes the\n * scratch copy of `seckey` before returning.\n */\nexport function wasmPublicKey(exports: CryptoWasmExports, seckey: Uint8Array): Uint8Array {\n const { ptr, view } = scratch(exports, SECKEY_LEN + PUBKEY_LEN);\n view.set(seckey, 0);\n const status = exports.nostr_public_key(ptr, ptr + SECKEY_LEN);\n if (status !== 0) {\n return new Uint8Array(0);\n }\n return takeOut(exports, ptr + SECKEY_LEN, PUBKEY_LEN);\n}\n\n/**\n * Compile and instantiate `nostr_wasm.wasm`. The module must declare no imports and must implement\n * ABI version {@link NOSTR_ABI_VERSION} — a mismatch means the artifact predates or postdates this\n * loader and instantiation fails.\n */\nexport async function instantiateCryptoWasm(\n bytes: ArrayBuffer | ArrayBufferView,\n): Promise<CryptoWasmExports> {\n const module = await WebAssembly.compile(bytes);\n const imports = WebAssembly.Module.imports(module);\n if (imports.length > 0) {\n throw new TypeError(\n `nostr_wasm module must not import anything, got ${imports.length} imports`,\n );\n }\n const instance = await WebAssembly.instantiate(module, {});\n const version = requireFn(instance.exports[\"nostr_abi_version\"], \"nostr_abi_version\")();\n if (version !== NOSTR_ABI_VERSION) {\n throw new CryptoError(\n `unsupported nostr_wasm ABI version ${String(version)}, expected ${NOSTR_ABI_VERSION}`,\n );\n }\n const exports = asExports(instance.exports);\n // Seed the context: 32 bytes of fresh randomness into the scratch buffer; the C side\n // randomizes the context and wipes the seed region on every path.\n const { ptr, view } = scratch(exports, SEED_LEN);\n view.set(randomBytes(SEED_LEN), 0);\n if (exports.nostr_init(ptr) !== 0) {\n throw new CryptoError(\"nostr_init failed to randomize the wasm context\");\n }\n return exports;\n}\n\n/**\n * Returns the 32-byte x coordinate of the ECDH shared point, or an empty array on failure (invalid\n * input). The wasm module zeroes the scratch copy of `seckey` before returning.\n */\nexport function wasmEcdhX(\n exports: CryptoWasmExports,\n seckey: Uint8Array,\n publicKeyX: Uint8Array,\n): Uint8Array {\n const outOffset = SECKEY_LEN + PUBKEY_LEN;\n const { ptr, view } = scratch(exports, outOffset + 32);\n view.set(seckey, 0);\n view.set(publicKeyX, SECKEY_LEN);\n const status = exports.nostr_ecdh_x(ptr, ptr + SECKEY_LEN, ptr + outOffset);\n if (status !== 0) {\n return new Uint8Array(0);\n }\n return takeOut(exports, ptr + outOffset, 32);\n}\n","import { NostrError, createEventVerifier } from \"@qntx/nostr/core\";\nimport type { EventVerifier, NostrBackend } from \"@qntx/nostr/core\";\n\n/** Wasm instance aborted; later calls on that instance fail. */\nexport class WasmPoisonedError extends NostrError {\n override name = \"WasmPoisonedError\";\n}\n\n/**\n * Run `fn` unless the instance is already poisoned; a `WebAssembly.RuntimeError` aborts the\n * instance, so it is converted to {@link WasmPoisonedError} and rethrown on every later call.\n */\nexport function runPoisoned<T>(poison: { error?: Error }, fn: () => T): T {\n if (poison.error !== undefined) {\n throw poison.error;\n }\n try {\n return fn();\n } catch (error) {\n if (error instanceof WebAssembly.RuntimeError) {\n poison.error = new WasmPoisonedError(\"wasm instance aborted\", {\n cause: error,\n });\n throw poison.error;\n }\n throw error;\n }\n}\n\n/**\n * Build the WASM event verifier on {@link createEventVerifier}. A `WebAssembly.RuntimeError` from\n * the module means the instance aborted — it poisons every later call with the same\n * `WasmPoisonedError`; any other backend error verifies `false` (the event is marked failed).\n */\nexport function createWasmEventVerifier(\n verifySerialized: NostrBackend[\"verifySerialized\"],\n poison: { error?: Error },\n): EventVerifier {\n const raw: NostrBackend[\"verifySerialized\"] = (serializedUtf8, id, pubkey, sig) => {\n try {\n return runPoisoned(poison, () => verifySerialized(serializedUtf8, id, pubkey, sig));\n } catch (error) {\n if (error instanceof WasmPoisonedError) {\n throw error;\n }\n return false;\n }\n };\n const backend: NostrBackend = {\n verifySerialized: raw,\n // createEventVerifier only calls verifySerialized; the rest of the shape is\n // required by NostrBackend and never invoked.\n publicKey: () => {\n throw new NostrError(\"wasm verifier backend: publicKey is unsupported\");\n },\n sign: () => {\n throw new NostrError(\"wasm verifier backend: sign is unsupported\");\n },\n ecdhX: () => {\n throw new NostrError(\"wasm verifier backend: ecdhX is unsupported\");\n },\n };\n return createEventVerifier(backend);\n}\n","import { CryptoError } from \"@qntx/nostr/core\";\nimport type { NostrBackend, EventVerifier } from \"@qntx/nostr/core\";\n\nimport {\n instantiateCryptoWasm,\n wasmEcdhX,\n wasmPublicKey,\n wasmSign,\n wasmVerify,\n wasmVerifySerialized,\n} from \"./abi.ts\";\nimport type { CryptoWasmExports } from \"./abi.ts\";\nimport { createWasmEventVerifier, runPoisoned } from \"./adapter.ts\";\n\nexport type LoadNostrWasmOptions = {\n /** Bytes, or a URL whose bytes will be read by the platform loader. */\n module?: ArrayBuffer | ArrayBufferView | URL | undefined;\n};\n\nexport type NostrWasm = NostrBackend & {\n verify: (id: Uint8Array, pubkey: Uint8Array, sig: Uint8Array) => boolean;\n verifyEvent: EventVerifier;\n};\n\nexport function isWasmBytes(value: unknown): value is ArrayBuffer | ArrayBufferView {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport async function fetchWasmUrl(url: URL): Promise<Uint8Array> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new CryptoError(`failed to fetch wasm: ${res.status} ${url.href}`);\n }\n return new Uint8Array(await res.arrayBuffer());\n}\n\nfunction requireByteLength(bytes: Uint8Array, expected: number, label: string): void {\n if (bytes.length !== expected) {\n throw new CryptoError(`invalid ${label} length: expected ${expected}, got ${bytes.length}`);\n }\n}\n\nfunction bindExports(exports: CryptoWasmExports): NostrWasm {\n const poison: { error?: Error } = {};\n const rawSerialized = (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n ): boolean => wasmVerifySerialized(exports, serializedUtf8, id, pubkey, sig);\n return {\n verify: (id, pubkey, sig) => runPoisoned(poison, () => wasmVerify(exports, id, pubkey, sig)),\n verifySerialized: (serializedUtf8, id, pubkey, sig) =>\n runPoisoned(poison, () => rawSerialized(serializedUtf8, id, pubkey, sig)),\n verifyEvent: createWasmEventVerifier(rawSerialized, poison),\n sign: (id, seckey, aux) =>\n runPoisoned(poison, () => {\n requireByteLength(id, 32, \"id\");\n requireByteLength(seckey, 32, \"secret key\");\n requireByteLength(aux, 32, \"aux\");\n const sig = wasmSign(exports, id, seckey, aux);\n if (sig.length !== 64) {\n throw new CryptoError(\"wasm sign failed\");\n }\n return sig;\n }),\n publicKey: (seckey) =>\n runPoisoned(poison, () => {\n requireByteLength(seckey, 32, \"secret key\");\n const pk = wasmPublicKey(exports, seckey);\n if (pk.length !== 32) {\n throw new CryptoError(\"wasm publicKey failed\");\n }\n return pk;\n }),\n ecdhX: (seckey, publicKeyX) =>\n runPoisoned(poison, () => {\n requireByteLength(seckey, 32, \"secret key\");\n requireByteLength(publicKeyX, 32, \"public key\");\n const x = wasmEcdhX(exports, seckey, publicKeyX);\n if (x.length !== 32) {\n throw new CryptoError(\"wasm ecdhX failed\");\n }\n return x;\n }),\n };\n}\n\n/** Builds a loader that interns a single instance per module slot. */\nexport function createNostrWasmLoader(\n wasmBytes: (opts?: LoadNostrWasmOptions) => Promise<ArrayBuffer | ArrayBufferView>,\n): {\n loadNostrWasm: (opts?: LoadNostrWasmOptions) => Promise<NostrWasm>;\n resetNostrWasm: () => void;\n} {\n let interned: Promise<NostrWasm> | undefined;\n\n async function instantiate(opts?: LoadNostrWasmOptions): Promise<NostrWasm> {\n const bytes = await wasmBytes(opts);\n const exports = await instantiateCryptoWasm(bytes);\n return bindExports(exports);\n }\n\n /** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */\n async function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm> {\n if (interned) {\n return interned;\n }\n const pending = instantiate(opts);\n interned = pending;\n try {\n return await pending;\n } catch (error) {\n if (interned === pending) {\n interned = undefined;\n }\n throw error;\n }\n }\n\n function resetNostrWasm(): void {\n interned = undefined;\n }\n\n return { loadNostrWasm, resetNostrWasm };\n}\n","/*\n * Subresource Integrity hash (`sha256-<base64>`) of the shipped `nostr_wasm.wasm`, regenerated\n * whenever build:wasm runs. Pass it to `fetch(url, { integrity })` so the browser refuses to\n * execute a tampered module:\n *\n * const res = await fetch(wasmUrl, { integrity: NOSTR_WASM_INTEGRITY });\n * const wasm = await loadNostrWasm({ module: await res.arrayBuffer() });\n *\n * The constant only guards fetch-time delivery — callers that want SRI enforcement must fetch\n * with `integrity` and pass `module`.\n */\nexport const NOSTR_WASM_INTEGRITY = \"sha256-p9Cj0Klb81RjKSm5EUX28CKmoXHhZqFRrdks6Pq5ejQ=\";\n"],"mappings":"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"}
@@ -1,12 +1,11 @@
1
- import { EventVerifier, NostrError, SigningBackend } from "@qntx/nostr/core";
1
+ import { EventVerifier, NostrBackend, NostrError } from "@qntx/nostr/core";
2
2
  //#region src/instance.d.ts
3
3
  type LoadNostrWasmOptions = {
4
4
  /** Bytes, or a URL whose bytes will be read by the platform loader. */
5
5
  module?: ArrayBuffer | ArrayBufferView | URL | undefined;
6
6
  };
7
- type NostrWasm = SigningBackend & {
7
+ type NostrWasm = NostrBackend & {
8
8
  verify: (id: Uint8Array, pubkey: Uint8Array, sig: Uint8Array) => boolean;
9
- verifySerialized: (serializedUtf8: Uint8Array, id: Uint8Array, pubkey: Uint8Array, sig: Uint8Array) => boolean;
10
9
  verifyEvent: EventVerifier;
11
10
  };
12
11
  //#endregion
@@ -16,5 +15,8 @@ declare class WasmPoisonedError extends NostrError {
16
15
  name: string;
17
16
  }
18
17
  //#endregion
19
- export { LoadNostrWasmOptions as n, NostrWasm as r, WasmPoisonedError as t };
20
- //# sourceMappingURL=adapter-DMQvHVO_.d.mts.map
18
+ //#region src/integrity.d.ts
19
+ declare const NOSTR_WASM_INTEGRITY = "sha256-p9Cj0Klb81RjKSm5EUX28CKmoXHhZqFRrdks6Pq5ejQ=";
20
+ //#endregion
21
+ export { NostrWasm as i, WasmPoisonedError as n, LoadNostrWasmOptions as r, NOSTR_WASM_INTEGRITY as t };
22
+ //# sourceMappingURL=integrity-BmoamQip.d.mts.map
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qntx/nostr-wasm",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "WebAssembly-accelerated cryptography backend for @qntx/nostr",
5
5
  "keywords": [
6
6
  "nostr",
@@ -46,7 +46,8 @@
46
46
  "access": "public"
47
47
  },
48
48
  "scripts": {
49
- "build:wasm": "vp run @qntx/nostr#build && bash scripts/build.sh && vp pack && publint && attw --pack . --profile esm-only",
49
+ "build": "vp pack",
50
+ "build:wasm": "vp run @qntx/nostr#build && bash scripts/docker-build.sh && vp pack && publint && attw --pack . --profile esm-only",
50
51
  "test:wasm": "vp test",
51
52
  "bench:wasm": "BENCH=1 vp test tests/bench.ts",
52
53
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.src.json",
@@ -55,17 +56,17 @@
55
56
  "devDependencies": {
56
57
  "@arethetypeswrong/cli": "0.18.5",
57
58
  "@noble/curves": "2.4.0",
58
- "@qntx/nostr": "0.14.0",
59
- "@qntx/nostr-native": "0.14.0",
59
+ "@qntx/nostr": "0.15.1",
60
+ "@qntx/nostr-native": "0.15.1",
60
61
  "@qntx/tsconfig": "1.0.1",
61
- "@types/node": "26.6.4",
62
+ "@types/node": "26.6.5",
62
63
  "binaryen": "132.0.0",
63
64
  "publint": "0.3.25",
64
65
  "typescript": "7.0.2",
65
- "vite-plus": "1.0.0"
66
+ "vite-plus": "1.3.0"
66
67
  },
67
68
  "peerDependencies": {
68
- "@qntx/nostr": "^0.14.0"
69
+ "@qntx/nostr": "^0.15.1"
69
70
  },
70
71
  "devEngines": {
71
72
  "packageManager": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"instance-6y1G4BjC.mjs","names":[],"sources":["../src/abi.ts","../src/adapter.ts","../src/instance.ts"],"sourcesContent":["import { CryptoError, randomBytes } from \"@qntx/nostr/core\";\n\n/**\n * Byte-ABI version 3 surface exported by `nostr_wasm.wasm` (`packages/nostr-wasm/c/nostr_wasm.c`\n * over the shared `nostr_core.c` in `packages/nostr-native/cpp`). All inputs are caller-written\n * regions of a single scratch buffer; status codes are 0 ok/verified, 1 verification failed, 2\n * invalid input. `nostr_init` must succeed once with a 32-byte seed before the crypto ops run.\n */\nexport type CryptoWasmExports = {\n memory: WebAssembly.Memory;\n nostr_abi_version: () => number;\n nostr_init: (seed: number) => number;\n nostr_buffer: (len: number) => number;\n nostr_verify: (id: number, pubkey: number, sig: number) => number;\n nostr_verify_serialized: (\n ser: number,\n serLen: number,\n id: number,\n pubkey: number,\n sig: number,\n ) => number;\n nostr_sign: (id: number, seckey: number, aux: number, outSig: number) => number;\n nostr_public_key: (seckey: number, outPubkey: number) => number;\n};\n\nexport const NOSTR_ABI_VERSION = 3;\n\nconst SEED_LEN = 32;\n\nconst ID_LEN = 32;\nconst PUBKEY_LEN = 32;\nconst SIG_LEN = 64;\nconst SECKEY_LEN = 32;\nconst AUX_LEN = 32;\n\nfunction requireMemory(value: WebAssembly.ExportValue | undefined): WebAssembly.Memory {\n if (!(value instanceof WebAssembly.Memory)) {\n throw new TypeError(\"wasm missing memory export\");\n }\n return value;\n}\n\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- T binds the declared ABI signature at each call site\nfunction requireFn<T extends (...args: number[]) => unknown>(\n value: WebAssembly.ExportValue | undefined,\n name: string,\n): T {\n if (typeof value !== \"function\") {\n throw new TypeError(`wasm missing ${name} export`);\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- wasm exports are opaque; each name is bound to the signature the ABI contract requires\n return value as T;\n}\n\nfunction asExports(raw: WebAssembly.Exports): CryptoWasmExports {\n return {\n memory: requireMemory(raw[\"memory\"]),\n nostr_abi_version: requireFn(raw[\"nostr_abi_version\"], \"nostr_abi_version\"),\n nostr_init: requireFn(raw[\"nostr_init\"], \"nostr_init\"),\n nostr_buffer: requireFn(raw[\"nostr_buffer\"], \"nostr_buffer\"),\n nostr_verify: requireFn(raw[\"nostr_verify\"], \"nostr_verify\"),\n nostr_verify_serialized: requireFn(raw[\"nostr_verify_serialized\"], \"nostr_verify_serialized\"),\n nostr_sign: requireFn(raw[\"nostr_sign\"], \"nostr_sign\"),\n nostr_public_key: requireFn(raw[\"nostr_public_key\"], \"nostr_public_key\"),\n };\n}\n\n/**\n * Reserve `total` bytes in the wasm scratch region and return a fresh view. `nostr_buffer` returns\n * NULL when `memory.grow` fails; `__heap_base` is never 0, so 0 is unambiguously an allocation\n * failure — throw instead of writing at address 0. The view is taken after `nostr_buffer` ran, so a\n * growth inside it cannot leave a detached view in the caller's hands.\n */\nfunction scratch(exports: CryptoWasmExports, total: number): { ptr: number; view: Uint8Array } {\n const ptr = exports.nostr_buffer(total) >>> 0;\n if (ptr === 0) {\n throw new CryptoError(\"wasm scratch allocation failed\");\n }\n return { ptr, view: new Uint8Array(exports.memory.buffer, ptr, total) };\n}\n\n/** Copies `len` scratch bytes out; the view is re-acquired after the call. */\nfunction takeOut(exports: CryptoWasmExports, ptr: number, len: number): Uint8Array {\n const out = new Uint8Array(len);\n out.set(new Uint8Array(exports.memory.buffer, ptr, len));\n return out;\n}\n\nexport function wasmVerify(\n exports: CryptoWasmExports,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n): boolean {\n if (id.length !== ID_LEN || pubkey.length !== PUBKEY_LEN || sig.length !== SIG_LEN) {\n return false; // fixed-size ABI: a wrong-length input simply cannot verify\n }\n const { ptr, view } = scratch(exports, ID_LEN + PUBKEY_LEN + SIG_LEN);\n view.set(id, 0);\n view.set(pubkey, ID_LEN);\n view.set(sig, ID_LEN + PUBKEY_LEN);\n return exports.nostr_verify(ptr, ptr + ID_LEN, ptr + ID_LEN + PUBKEY_LEN) === 0;\n}\n\nexport function wasmVerifySerialized(\n exports: CryptoWasmExports,\n serialized: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n): boolean {\n if (id.length !== ID_LEN || pubkey.length !== PUBKEY_LEN || sig.length !== SIG_LEN) {\n return false;\n }\n const serLen = serialized.length;\n const { ptr, view } = scratch(exports, serLen + ID_LEN + PUBKEY_LEN + SIG_LEN);\n view.set(serialized, 0);\n view.set(id, serLen);\n view.set(pubkey, serLen + ID_LEN);\n view.set(sig, serLen + ID_LEN + PUBKEY_LEN);\n return (\n exports.nostr_verify_serialized(\n ptr,\n serLen,\n ptr + serLen,\n ptr + serLen + ID_LEN,\n ptr + serLen + ID_LEN + PUBKEY_LEN,\n ) === 0\n );\n}\n\n/**\n * Returns the 64-byte signature, or an empty array on failure (invalid input). The wasm module\n * zeroes the scratch copy of `seckey` before returning.\n */\nexport function wasmSign(\n exports: CryptoWasmExports,\n id: Uint8Array,\n seckey: Uint8Array,\n aux: Uint8Array,\n): Uint8Array {\n const outOffset = ID_LEN + SECKEY_LEN + AUX_LEN;\n const { ptr, view } = scratch(exports, outOffset + SIG_LEN);\n view.set(id, 0);\n view.set(seckey, ID_LEN);\n view.set(aux, ID_LEN + SECKEY_LEN);\n const status = exports.nostr_sign(ptr, ptr + ID_LEN, ptr + ID_LEN + SECKEY_LEN, ptr + outOffset);\n if (status !== 0) {\n return new Uint8Array(0);\n }\n return takeOut(exports, ptr + outOffset, SIG_LEN);\n}\n\n/**\n * Returns the 32-byte x-only public key, or an empty array on failure. The wasm module zeroes the\n * scratch copy of `seckey` before returning.\n */\nexport function wasmPublicKey(exports: CryptoWasmExports, seckey: Uint8Array): Uint8Array {\n const { ptr, view } = scratch(exports, SECKEY_LEN + PUBKEY_LEN);\n view.set(seckey, 0);\n const status = exports.nostr_public_key(ptr, ptr + SECKEY_LEN);\n if (status !== 0) {\n return new Uint8Array(0);\n }\n return takeOut(exports, ptr + SECKEY_LEN, PUBKEY_LEN);\n}\n\n/**\n * Compile and instantiate `nostr_wasm.wasm`. The module must declare no imports and must implement\n * ABI version {@link NOSTR_ABI_VERSION} — a mismatch means the artifact predates or postdates this\n * loader and instantiation fails.\n */\nexport async function instantiateCryptoWasm(\n bytes: ArrayBuffer | ArrayBufferView,\n): Promise<CryptoWasmExports> {\n const module = await WebAssembly.compile(bytes);\n const imports = WebAssembly.Module.imports(module);\n if (imports.length > 0) {\n throw new TypeError(\n `nostr_wasm module must not import anything, got ${imports.length} imports`,\n );\n }\n const instance = await WebAssembly.instantiate(module, {});\n const version = requireFn(instance.exports[\"nostr_abi_version\"], \"nostr_abi_version\")();\n if (version !== NOSTR_ABI_VERSION) {\n throw new CryptoError(\n `unsupported nostr_wasm ABI version ${String(version)}, expected ${NOSTR_ABI_VERSION}`,\n );\n }\n const exports = asExports(instance.exports);\n // Seed the context: 32 bytes of fresh randomness into the scratch buffer; the C side\n // randomizes the context and wipes the seed region on every path.\n const { ptr, view } = scratch(exports, SEED_LEN);\n view.set(randomBytes(SEED_LEN), 0);\n if (exports.nostr_init(ptr) !== 0) {\n throw new CryptoError(\"nostr_init failed to randomize the wasm context\");\n }\n return exports;\n}\n","import { NostrError, createEventVerifier } from \"@qntx/nostr/core\";\nimport type { EventVerifier, SerializedEventVerifier } from \"@qntx/nostr/core\";\n\n/** Wasm instance aborted; later calls on that instance fail. */\nexport class WasmPoisonedError extends NostrError {\n override name = \"WasmPoisonedError\";\n}\n\n/**\n * Run `fn` unless the instance is already poisoned; a `WebAssembly.RuntimeError` aborts the\n * instance, so it is converted to {@link WasmPoisonedError} and rethrown on every later call.\n */\nexport function runPoisoned<T>(poison: { error?: Error }, fn: () => T): T {\n if (poison.error !== undefined) {\n throw poison.error;\n }\n try {\n return fn();\n } catch (error) {\n if (error instanceof WebAssembly.RuntimeError) {\n poison.error = new WasmPoisonedError(\"wasm instance aborted\", {\n cause: error,\n });\n throw poison.error;\n }\n throw error;\n }\n}\n\n/**\n * Build the WASM event verifier on {@link createEventVerifier}. A `WebAssembly.RuntimeError` from\n * the module means the instance aborted — it poisons every later call with the same\n * `WasmPoisonedError`; any other backend error verifies `false` (the event is marked failed).\n */\nexport function createWasmEventVerifier(\n verifySerialized: SerializedEventVerifier,\n poison: { error?: Error },\n): EventVerifier {\n const backend: SerializedEventVerifier = (serializedUtf8, id, pubkey, sig) => {\n try {\n return runPoisoned(poison, () => verifySerialized(serializedUtf8, id, pubkey, sig));\n } catch (error) {\n if (error instanceof WasmPoisonedError) {\n throw error;\n }\n return false;\n }\n };\n return createEventVerifier(backend);\n}\n","import { CryptoError } from \"@qntx/nostr/core\";\nimport type { SigningBackend, EventVerifier } from \"@qntx/nostr/core\";\n\nimport {\n instantiateCryptoWasm,\n wasmPublicKey,\n wasmSign,\n wasmVerify,\n wasmVerifySerialized,\n} from \"./abi.ts\";\nimport type { CryptoWasmExports } from \"./abi.ts\";\nimport { createWasmEventVerifier, runPoisoned } from \"./adapter.ts\";\n\nexport type LoadNostrWasmOptions = {\n /** Bytes, or a URL whose bytes will be read by the platform loader. */\n module?: ArrayBuffer | ArrayBufferView | URL | undefined;\n};\n\nexport type NostrWasm = SigningBackend & {\n verify: (id: Uint8Array, pubkey: Uint8Array, sig: Uint8Array) => boolean;\n verifySerialized: (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n ) => boolean;\n verifyEvent: EventVerifier;\n};\n\nexport function isWasmBytes(value: unknown): value is ArrayBuffer | ArrayBufferView {\n return value instanceof ArrayBuffer || ArrayBuffer.isView(value);\n}\n\nexport async function fetchWasmUrl(url: URL): Promise<Uint8Array> {\n const res = await fetch(url);\n if (!res.ok) {\n throw new CryptoError(`failed to fetch wasm: ${res.status} ${url.href}`);\n }\n return new Uint8Array(await res.arrayBuffer());\n}\n\nfunction requireByteLength(bytes: Uint8Array, expected: number, label: string): void {\n if (bytes.length !== expected) {\n throw new CryptoError(`invalid ${label} length: expected ${expected}, got ${bytes.length}`);\n }\n}\n\nfunction bindExports(exports: CryptoWasmExports): NostrWasm {\n const poison: { error?: Error } = {};\n const rawSerialized = (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n ): boolean => wasmVerifySerialized(exports, serializedUtf8, id, pubkey, sig);\n return {\n verify: (id, pubkey, sig) => runPoisoned(poison, () => wasmVerify(exports, id, pubkey, sig)),\n verifySerialized: (serializedUtf8, id, pubkey, sig) =>\n runPoisoned(poison, () => rawSerialized(serializedUtf8, id, pubkey, sig)),\n verifyEvent: createWasmEventVerifier(rawSerialized, poison),\n sign: (id, seckey, aux) =>\n runPoisoned(poison, () => {\n requireByteLength(id, 32, \"id\");\n requireByteLength(seckey, 32, \"secret key\");\n requireByteLength(aux, 32, \"aux\");\n const sig = wasmSign(exports, id, seckey, aux);\n if (sig.length !== 64) {\n throw new CryptoError(\"wasm sign failed\");\n }\n return sig;\n }),\n publicKey: (seckey) =>\n runPoisoned(poison, () => {\n requireByteLength(seckey, 32, \"secret key\");\n const pk = wasmPublicKey(exports, seckey);\n if (pk.length !== 32) {\n throw new CryptoError(\"wasm publicKey failed\");\n }\n return pk;\n }),\n };\n}\n\n/** Builds a loader that interns a single instance per module slot. */\nexport function createNostrWasmLoader(\n wasmBytes: (opts?: LoadNostrWasmOptions) => Promise<ArrayBuffer | ArrayBufferView>,\n): {\n loadNostrWasm: (opts?: LoadNostrWasmOptions) => Promise<NostrWasm>;\n resetNostrWasm: () => void;\n} {\n let interned: Promise<NostrWasm> | undefined;\n\n async function instantiate(opts?: LoadNostrWasmOptions): Promise<NostrWasm> {\n const bytes = await wasmBytes(opts);\n const exports = await instantiateCryptoWasm(bytes);\n return bindExports(exports);\n }\n\n /** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */\n async function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm> {\n if (interned) {\n return interned;\n }\n const pending = instantiate(opts);\n interned = pending;\n try {\n return await pending;\n } catch (error) {\n if (interned === pending) {\n interned = undefined;\n }\n throw error;\n }\n }\n\n function resetNostrWasm(): void {\n interned = undefined;\n }\n\n return { loadNostrWasm, resetNostrWasm };\n}\n"],"mappings":";AA2BA,MAAM,WAAW;AAEjB,MAAM,SAAS;AACf,MAAM,aAAa;AACnB,MAAM,UAAU;AAChB,MAAM,aAAa;AAGnB,SAAS,cAAc,OAAgE;CACrF,IAAI,EAAE,iBAAiB,YAAY,SACjC,MAAM,IAAI,UAAU,4BAA4B;CAElD,OAAO;AACT;AAGA,SAAS,UACP,OACA,MACG;CACH,IAAI,OAAO,UAAU,YACnB,MAAM,IAAI,UAAU,gBAAgB,KAAK,QAAQ;CAGnD,OAAO;AACT;AAEA,SAAS,UAAU,KAA6C;CAC9D,OAAO;EACL,QAAQ,cAAc,IAAI,SAAS;EACnC,mBAAmB,UAAU,IAAI,sBAAsB,mBAAmB;EAC1E,YAAY,UAAU,IAAI,eAAe,YAAY;EACrD,cAAc,UAAU,IAAI,iBAAiB,cAAc;EAC3D,cAAc,UAAU,IAAI,iBAAiB,cAAc;EAC3D,yBAAyB,UAAU,IAAI,4BAA4B,yBAAyB;EAC5F,YAAY,UAAU,IAAI,eAAe,YAAY;EACrD,kBAAkB,UAAU,IAAI,qBAAqB,kBAAkB;CACzE;AACF;;;;;;;AAQA,SAAS,QAAQ,SAA4B,OAAkD;CAC7F,MAAM,MAAM,QAAQ,aAAa,KAAK,MAAM;CAC5C,IAAI,QAAQ,GACV,MAAM,IAAI,YAAY,gCAAgC;CAExD,OAAO;EAAE;EAAK,MAAM,IAAI,WAAW,QAAQ,OAAO,QAAQ,KAAK,KAAK;CAAE;AACxE;;AAGA,SAAS,QAAQ,SAA4B,KAAa,KAAyB;CACjF,MAAM,MAAM,IAAI,WAAW,GAAG;CAC9B,IAAI,IAAI,IAAI,WAAW,QAAQ,OAAO,QAAQ,KAAK,GAAG,CAAC;CACvD,OAAO;AACT;AAEA,SAAgB,WACd,SACA,IACA,QACA,KACS;CACT,IAAI,GAAG,WAAW,UAAU,OAAO,WAAW,cAAc,IAAI,WAAW,SACzE,OAAO;CAET,MAAM,EAAE,KAAK,SAAS,QAAQ,SAAS,GAA6B;CACpE,KAAK,IAAI,IAAI,CAAC;CACd,KAAK,IAAI,QAAQ,MAAM;CACvB,KAAK,IAAI,KAAK,EAAmB;CACjC,OAAO,QAAQ,aAAa,KAAK,MAAM,QAAQ,MAAM,SAAS,UAAU,MAAM;AAChF;AAEA,SAAgB,qBACd,SACA,YACA,IACA,QACA,KACS;CACT,IAAI,GAAG,WAAW,UAAU,OAAO,WAAW,cAAc,IAAI,WAAW,SACzE,OAAO;CAET,MAAM,SAAS,WAAW;CAC1B,MAAM,EAAE,KAAK,SAAS,QAAQ,SAAS,SAAS,SAAS,aAAa,OAAO;CAC7E,KAAK,IAAI,YAAY,CAAC;CACtB,KAAK,IAAI,IAAI,MAAM;CACnB,KAAK,IAAI,QAAQ,SAAS,MAAM;CAChC,KAAK,IAAI,KAAK,SAAS,SAAS,UAAU;CAC1C,OACE,QAAQ,wBACN,KACA,QACA,MAAM,QACN,MAAM,SAAS,QACf,MAAM,SAAS,SAAS,UAC1B,MAAM;AAEV;;;;;AAMA,SAAgB,SACd,SACA,IACA,QACA,KACY;CACZ,MAAM,YAAY;CAClB,MAAM,EAAE,KAAK,SAAS,QAAQ,SAAS,GAAmB;CAC1D,KAAK,IAAI,IAAI,CAAC;CACd,KAAK,IAAI,QAAQ,MAAM;CACvB,KAAK,IAAI,KAAK,EAAmB;CAEjC,IADe,QAAQ,WAAW,KAAK,MAAM,QAAQ,MAAM,SAAS,YAAY,MAAM,SAC7E,MAAM,GACb,uBAAO,IAAI,WAAW,CAAC;CAEzB,OAAO,QAAQ,SAAS,MAAM,WAAW,OAAO;AAClD;;;;;AAMA,SAAgB,cAAc,SAA4B,QAAgC;CACxF,MAAM,EAAE,KAAK,SAAS,QAAQ,SAAS,EAAuB;CAC9D,KAAK,IAAI,QAAQ,CAAC;CAElB,IADe,QAAQ,iBAAiB,KAAK,MAAM,UAC1C,MAAM,GACb,uBAAO,IAAI,WAAW,CAAC;CAEzB,OAAO,QAAQ,SAAS,MAAM,YAAY,UAAU;AACtD;;;;;;AAOA,eAAsB,sBACpB,OAC4B;CAC5B,MAAM,SAAS,MAAM,YAAY,QAAQ,KAAK;CAC9C,MAAM,UAAU,YAAY,OAAO,QAAQ,MAAM;CACjD,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,UACR,mDAAmD,QAAQ,OAAO,SACpE;CAEF,MAAM,WAAW,MAAM,YAAY,YAAY,QAAQ,CAAC,CAAC;CACzD,MAAM,UAAU,UAAU,SAAS,QAAQ,sBAAsB,mBAAmB,CAAC,CAAC;CACtF,IAAI,YAAA,GACF,MAAM,IAAI,YACR,sCAAsC,OAAO,OAAO,EAAE,aACxD;CAEF,MAAM,UAAU,UAAU,SAAS,OAAO;CAG1C,MAAM,EAAE,KAAK,SAAS,QAAQ,SAAS,QAAQ;CAC/C,KAAK,IAAI,YAAY,QAAQ,GAAG,CAAC;CACjC,IAAI,QAAQ,WAAW,GAAG,MAAM,GAC9B,MAAM,IAAI,YAAY,iDAAiD;CAEzE,OAAO;AACT;;;;AClMA,IAAa,oBAAb,cAAuC,WAAW;CAChD,OAAgB;AAClB;;;;;AAMA,SAAgB,YAAe,QAA2B,IAAgB;CACxE,IAAI,OAAO,UAAU,KAAA,GACnB,MAAM,OAAO;CAEf,IAAI;EACF,OAAO,GAAG;CACZ,SAAS,OAAO;EACd,IAAI,iBAAiB,YAAY,cAAc;GAC7C,OAAO,QAAQ,IAAI,kBAAkB,yBAAyB,EAC5D,OAAO,MACT,CAAC;GACD,MAAM,OAAO;EACf;EACA,MAAM;CACR;AACF;;;;;;AAOA,SAAgB,wBACd,kBACA,QACe;CACf,MAAM,WAAoC,gBAAgB,IAAI,QAAQ,QAAQ;EAC5E,IAAI;GACF,OAAO,YAAY,cAAc,iBAAiB,gBAAgB,IAAI,QAAQ,GAAG,CAAC;EACpF,SAAS,OAAO;GACd,IAAI,iBAAiB,mBACnB,MAAM;GAER,OAAO;EACT;CACF;CACA,OAAO,oBAAoB,OAAO;AACpC;;;ACpBA,SAAgB,YAAY,OAAwD;CAClF,OAAO,iBAAiB,eAAe,YAAY,OAAO,KAAK;AACjE;AAEA,eAAsB,aAAa,KAA+B;CAChE,MAAM,MAAM,MAAM,MAAM,GAAG;CAC3B,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,YAAY,yBAAyB,IAAI,OAAO,GAAG,IAAI,MAAM;CAEzE,OAAO,IAAI,WAAW,MAAM,IAAI,YAAY,CAAC;AAC/C;AAEA,SAAS,kBAAkB,OAAmB,UAAkB,OAAqB;CACnF,IAAI,MAAM,WAAW,UACnB,MAAM,IAAI,YAAY,WAAW,MAAM,oBAAoB,SAAS,QAAQ,MAAM,QAAQ;AAE9F;AAEA,SAAS,YAAY,SAAuC;CAC1D,MAAM,SAA4B,CAAC;CACnC,MAAM,iBACJ,gBACA,IACA,QACA,QACY,qBAAqB,SAAS,gBAAgB,IAAI,QAAQ,GAAG;CAC3E,OAAO;EACL,SAAS,IAAI,QAAQ,QAAQ,YAAY,cAAc,WAAW,SAAS,IAAI,QAAQ,GAAG,CAAC;EAC3F,mBAAmB,gBAAgB,IAAI,QAAQ,QAC7C,YAAY,cAAc,cAAc,gBAAgB,IAAI,QAAQ,GAAG,CAAC;EAC1E,aAAa,wBAAwB,eAAe,MAAM;EAC1D,OAAO,IAAI,QAAQ,QACjB,YAAY,cAAc;GACxB,kBAAkB,IAAI,IAAI,IAAI;GAC9B,kBAAkB,QAAQ,IAAI,YAAY;GAC1C,kBAAkB,KAAK,IAAI,KAAK;GAChC,MAAM,MAAM,SAAS,SAAS,IAAI,QAAQ,GAAG;GAC7C,IAAI,IAAI,WAAW,IACjB,MAAM,IAAI,YAAY,kBAAkB;GAE1C,OAAO;EACT,CAAC;EACH,YAAY,WACV,YAAY,cAAc;GACxB,kBAAkB,QAAQ,IAAI,YAAY;GAC1C,MAAM,KAAK,cAAc,SAAS,MAAM;GACxC,IAAI,GAAG,WAAW,IAChB,MAAM,IAAI,YAAY,uBAAuB;GAE/C,OAAO;EACT,CAAC;CACL;AACF;;AAGA,SAAgB,sBACd,WAIA;CACA,IAAI;CAEJ,eAAe,YAAY,MAAiD;EAG1E,OAAO,YAAY,MADG,sBAAsB,MADxB,UAAU,IAAI,CACe,CACvB;CAC5B;;CAGA,eAAe,cAAc,MAAiD;EAC5E,IAAI,UACF,OAAO;EAET,MAAM,UAAU,YAAY,IAAI;EAChC,WAAW;EACX,IAAI;GACF,OAAO,MAAM;EACf,SAAS,OAAO;GACd,IAAI,aAAa,SACf,WAAW,KAAA;GAEb,MAAM;EACR;CACF;CAEA,SAAS,iBAAuB;EAC9B,WAAW,KAAA;CACb;CAEA,OAAO;EAAE;EAAe;CAAe;AACzC"}