@qntx/wallet-worker 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,8 +75,15 @@ const wallet = await createNodeWallet({
75
75
  update the app, do not reset.
76
76
  - The Node store's RPC failures surface as `unavailable` and precondition failures as
77
77
  `conflict` — neither hangs a kernel call.
78
- - The Web host is a **hot wallet**. CSP needs `worker-src 'self'`; Vite dev needs
79
- `optimizeDeps.exclude: ["@qntx/wallet-worker"]` — `vite build` needs nothing.
78
+ - The Web host is a **hot wallet**. CSP needs `worker-src 'self'`. The host spawns the kernel
79
+ with `new Worker(new URL("kernel.worker.js", import.meta.url))`, which bundlers emit as a
80
+ worker entry with its chunks and `spark.wasm`. Consumer setup: Vite —
81
+ `worker: { format: "es" }` (required to keep the Spark backend lazy; the default `"iife"`
82
+ still runs but ships Spark inside the initial worker) and dev-server
83
+ `optimizeDeps.exclude: ["@qntx/wallet-worker"]`; webpack 5 — nothing.
84
+ - `dist/kernel.manifest.json` pins the artifacts the package was built from. Consumer bundlers
85
+ re-bundle the worker, so the shipped bytes differ from the manifest's; `spark.wasm` is still
86
+ verified by its integrity constant when the kernel loads it.
80
87
  - `@qntx/wallet/host` is for host packages only — not application API.
81
88
 
82
89
  ## License
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  SparkError,
3
3
  SparkCryptoError
4
- } from "./worker-30nrftxx.js";
4
+ } from "./worker-gxb54jcq.js";
5
5
 
6
- // ../../node_modules/.bun/@qntx+spark@0.3.0/node_modules/@qntx/spark/dist/crypto.mjs
6
+ // ../../node_modules/.bun/@qntx+spark@0.5.0/node_modules/@qntx/spark/dist/crypto.mjs
7
7
  var SPARK_ABI_VERSION = 1;
8
8
 
9
- // ../../node_modules/.bun/@qntx+spark-wasm@0.3.0+8240c1e209d110e0/node_modules/@qntx/spark-wasm/dist/integrity-793Q8_IW.mjs
9
+ // ../../node_modules/.bun/@qntx+spark-wasm@0.5.0+8f9f9cc94002ed5a/node_modules/@qntx/spark-wasm/dist/integrity-BP6ziDEM.mjs
10
10
  var OUT_PARAMS_LEN = 8;
11
11
  var INTERNAL_ERROR = 8;
12
12
  function requireMemory(value) {
@@ -175,9 +175,9 @@ function bindSparkExports(exports) {
175
175
  splitSecretWithProofs: (secret, threshold, shares, random) => run(() => wasmSplitSecretWithProofs(exports, secret, threshold, shares, random))
176
176
  };
177
177
  }
178
- var SPARK_WASM_INTEGRITY = "sha256-Ge1DhJu87OH//DsZUQlEBaqDCo1jGxg/hkGP4WHTqB4=";
178
+ var SPARK_WASM_INTEGRITY = "sha256-PFqThpVpNZHbmIuR13MZqLAupIIhfVcd3nMtB9QMTTE=";
179
179
 
180
- // ../../node_modules/.bun/@qntx+spark-wasm@0.3.0+8240c1e209d110e0/node_modules/@qntx/spark-wasm/dist/browser.mjs
180
+ // ../../node_modules/.bun/@qntx+spark-wasm@0.5.0+8f9f9cc94002ed5a/node_modules/@qntx/spark-wasm/dist/browser.mjs
181
181
  async function fetchWasmUrl(url) {
182
182
  const res = await fetch(url);
183
183
  if (!res.ok)
package/dist/browser.mjs CHANGED
@@ -182,13 +182,12 @@ async function createWebWallet(options) {
182
182
  t: "init",
183
183
  idleMinutes: autoLock.idleMinutes
184
184
  };
185
- const kernelUrl = new URL("kernel.worker.js", import.meta.url);
186
185
  const ceremony = createWebAuthnPasskey({
187
186
  id: options.passkey.rpId,
188
187
  name: options.passkey.rpName
189
188
  });
190
189
  const spawn = () => {
191
- const worker = new Worker(kernelUrl, {
190
+ const worker = new Worker(new URL("kernel.worker.js", import.meta.url), {
192
191
  type: "module",
193
192
  name: "qntx-wallet"
194
193
  });
@@ -1 +1 @@
1
- {"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser/passkey.ts","../src/browser/index.ts"],"sourcesContent":["/**\n * The WebAuthn passkey ceremony (main thread only): `navigator.credentials` with the PRF extension.\n * A wallet passkey derives the wallet seed from `PRF(walletSalt)`; the vault factor derives the\n * vault KEK from `PRF(vaultSalt)` on the same credential. Every ceremony output is checked for\n * `prf.enabled`, PRF results and the user-verification flag — a credential that cannot prove UV\n * never reaches the kernel.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport { PASSKEY_PURPOSE_VAULT, PASSKEY_PURPOSE_WALLET } from \"@qntx/wallet/host\";\nimport type { PasskeyCeremony, PrfResult, PrfSalts } from \"@qntx/wallet/host\";\n\nexport type WebAuthnRp = {\n /** RP id — the origin's effective domain, e.g. `location.hostname`. */\n readonly id: string;\n readonly name: string;\n};\n\nconst UV_FLAG = 0x04; // authenticator-data flags byte (offset 32), bit 2 = user verified\nconst BE_FLAG = 0x08; // bit 3 = credential is backup-eligible (multi-device capable)\nconst BS_FLAG = 0x10; // bit 4 = credential is currently backed up (synced)\n\nfunction factorUnavailable(message: string): WalletError {\n return new WalletError(\"factor-unavailable\", message);\n}\n\nfunction cancelled(message: string): WalletError {\n return new WalletError(\"cancelled\", message);\n}\n\nfunction mapCeremonyError(error: unknown): never {\n if (\n error instanceof DOMException &&\n (error.name === \"NotAllowedError\" || error.name === \"AbortError\")\n ) {\n throw cancelled(\"the passkey ceremony was cancelled\");\n }\n if (error instanceof WalletError) {\n throw error;\n }\n throw factorUnavailable(\"the passkey ceremony failed\");\n}\n\n/** The UV bit of an authenticator-data blob (flags byte at offset 32, bit 0x04). */\nfunction userVerified(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & UV_FLAG) !== 0\n );\n}\n\n/**\n * Whether the credential is backed up off-device (flags byte at offset 32): synced requires both BE\n * (the credential is eligible for backup) and BS (the authenticator actually backs it up).\n */\nfunction credentialSynced(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & (BE_FLAG | BS_FLAG)) === (BE_FLAG | BS_FLAG)\n );\n}\n\nfunction prfEval(salts: PrfSalts): AuthenticationExtensionsPRFValues {\n // The constructor copy narrows Uint8Array<ArrayBufferLike> to Uint8Array<ArrayBuffer> —\n // the DOM BufferSource does not admit shared-array-buffer views.\n return salts.second === undefined\n ? { first: new Uint8Array(salts.first) }\n : { first: new Uint8Array(salts.first), second: new Uint8Array(salts.second) };\n}\n\n/** A detached copy of a BufferSource — the returned Uint8Array owns its own fresh buffer. */\nfunction copyBufferSource(source: BufferSource): Uint8Array {\n const view =\n source instanceof ArrayBuffer\n ? new Uint8Array(source)\n : new Uint8Array(source.buffer, source.byteOffset, source.byteLength);\n return new Uint8Array(view);\n}\n\nfunction readPrfResult(\n credential: PublicKeyCredential,\n results: AuthenticationExtensionsPRFValues | undefined,\n wantSecond: boolean,\n synced: boolean,\n userHandle?: ArrayBuffer,\n): PrfResult {\n const first = results?.first;\n const second = results?.second;\n if (first === undefined || (wantSecond && second === undefined)) {\n throw factorUnavailable(\"the passkey produced no PRF output\");\n }\n // Copy into fresh Uint8Arrays and drop every reference to the extension's ArrayBuffers.\n return {\n credentialId: copyBufferSource(credential.rawId),\n first: copyBufferSource(first),\n ...(second === undefined ? {} : { second: copyBufferSource(second) }),\n ...(userHandle === undefined ? {} : { userHandle: copyBufferSource(userHandle) }),\n synced,\n };\n}\n\nfunction checkCredential(credential: unknown): PublicKeyCredential {\n if (!(credential instanceof PublicKeyCredential)) {\n throw factorUnavailable(\"the authenticator returned no public-key credential\");\n }\n return credential;\n}\n\n/**\n * The WebAuthn ceremony. `create` registers a resident credential with `prf.eval` over the salts;\n * when the extension returns no results a single assertion over the new credential produces them.\n * `assert` runs a discoverable (or allowCredentials-scoped) get with the same eval shape.\n */\nexport function createWebAuthnPasskey(rp: WebAuthnRp): PasskeyCeremony {\n async function assert(input: {\n readonly credentialIds?: ReadonlyArray<Uint8Array>;\n readonly salts: PrfSalts;\n }): Promise<PrfResult> {\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.get({\n publicKey: {\n rpId: rp.id,\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n userVerification: \"required\",\n ...(input.credentialIds === undefined\n ? {}\n : {\n allowCredentials: input.credentialIds.map((id) => ({\n type: \"public-key\" as const,\n id: new Uint8Array(id),\n })),\n }),\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAssertionResponse)) {\n throw factorUnavailable(\"the authenticator returned no assertion response\");\n }\n if (!userVerified(response.authenticatorData)) {\n throw factorUnavailable(\"the passkey assertion was not user-verified\");\n }\n return readPrfResult(\n credential,\n credential.getClientExtensionResults().prf?.results,\n input.salts.second !== undefined,\n credentialSynced(response.authenticatorData),\n response.userHandle ?? undefined,\n );\n }\n\n return {\n create: async (input) => {\n const userId = new Uint8Array(16);\n userId[0] = input.purpose === \"wallet\" ? PASSKEY_PURPOSE_WALLET : PASSKEY_PURPOSE_VAULT;\n crypto.getRandomValues(userId.subarray(1));\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.create({\n publicKey: {\n rp: { id: rp.id, name: rp.name },\n user: {\n id: userId,\n name: input.name,\n displayName: input.name,\n },\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n pubKeyCredParams: [\n { type: \"public-key\", alg: -7 },\n { type: \"public-key\", alg: -257 },\n ],\n authenticatorSelection: {\n residentKey: \"required\",\n userVerification: \"required\",\n },\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAttestationResponse)) {\n throw factorUnavailable(\"the authenticator returned no attestation response\");\n }\n const extensions = credential.getClientExtensionResults();\n if (extensions.prf?.enabled !== true) {\n throw factorUnavailable(\"the authenticator does not support the PRF extension\");\n }\n if (extensions.prf.results === undefined) {\n // Some authenticators omit creation-time PRF outputs: one assertion over the new\n // credential produces them (it is that response whose UV and BE/BS flags get read).\n return assert({\n credentialIds: [copyBufferSource(credential.rawId)],\n salts: input.salts,\n });\n }\n const authenticatorData = response.getAuthenticatorData();\n if (!userVerified(authenticatorData)) {\n throw factorUnavailable(\"the passkey creation was not user-verified\");\n }\n return readPrfResult(\n credential,\n extensions.prf.results,\n input.salts.second !== undefined,\n credentialSynced(authenticatorData),\n );\n },\n assert,\n };\n}\n","/**\n * The Web host: `createWebWallet` spawns the bundled kernel worker (dist/kernel.worker.js) whose\n * vault lives in the worker's own IndexedDB. Lock destroys the worker; the vault survives in\n * IndexedDB, so the respawned worker reloads it. `pagehide` locks immediately; the idle timer is\n * refreshed by user-presence signals, not request traffic. The WebAuthn ceremony runs on the main\n * thread.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport type { Wallet, WalletOptions } from \"@qntx/wallet\";\nimport { connectKernel } from \"@qntx/wallet/host\";\nimport type { MainPort } from \"@qntx/wallet/host\";\nimport type { ProtocolValue } from \"@qntx/wallet/protocol\";\n\nimport type { BrowserWorkerInit } from \"../shared.ts\";\nimport { createWebAuthnPasskey } from \"./passkey.ts\";\n\nconst LISTENER_OPTIONS = { capture: true, passive: true };\nconst ACTIVITY_EVENTS = [\"pointerdown\", \"pointermove\", \"keydown\", \"wheel\", \"touchstart\"];\n\n/**\n * The browser's user-presence source: direct input on the page, or returning to a visible tab.\n * Capture-phase passive listeners do a single boolean check per event — high-frequency signals\n * (scroll, pointermove) cost nothing. Returns the unsubscribe the wallet owns.\n */\nfunction subscribePresence(onActivity: () => void): () => void {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.addEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n const onVisible = (): void => {\n if (document.visibilityState === \"visible\") {\n onActivity();\n }\n };\n document.addEventListener(\"visibilitychange\", onVisible);\n return () => {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.removeEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n document.removeEventListener(\"visibilitychange\", onVisible);\n };\n}\n\nasync function bestEffort(task: () => Promise<unknown>): Promise<void> {\n try {\n await task();\n } catch {\n // Lifecycle hooks and persistence hints must never surface failures.\n }\n}\n\nexport async function createWebWallet(options: WalletOptions): Promise<Wallet> {\n const { autoLock } = options;\n if (autoLock.platform !== \"web\") {\n throw new WalletError(\"invalid-input\", \"createWebWallet accepts only the web autoLock policy\");\n }\n // The page sends only the idle choice; the worker owns the maximum session duration.\n const init: BrowserWorkerInit = { t: \"init\", idleMinutes: autoLock.idleMinutes };\n const kernelUrl = new URL(\"kernel.worker.js\", import.meta.url);\n const ceremony = createWebAuthnPasskey({\n id: options.passkey.rpId,\n name: options.passkey.rpName,\n });\n\n const spawn = (): MainPort => {\n const worker = new Worker(kernelUrl, { type: \"module\", name: \"qntx-wallet\" });\n // oxlint-disable-next-line unicorn/require-post-message-target-origin -- Worker.postMessage takes a transfer list, not an origin\n worker.postMessage(init); // the kernel validates it strictly before startKernel\n return {\n post: (message: ProtocolValue, transfer: ArrayBuffer[]) => {\n worker.postMessage(message, transfer);\n },\n listen: (handler) => {\n const listener = (event: MessageEvent): void => {\n handler(event.data);\n };\n worker.addEventListener(\"message\", listener);\n return () => {\n worker.removeEventListener(\"message\", listener);\n };\n },\n onExit: (handler) => {\n // Web Workers have no exit event; error/messageerror are the only death signals.\n const listener = (): void => {\n handler();\n };\n worker.addEventListener(\"error\", listener);\n worker.addEventListener(\"messageerror\", listener);\n return () => {\n worker.removeEventListener(\"error\", listener);\n worker.removeEventListener(\"messageerror\", listener);\n };\n },\n terminate: () => {\n worker.terminate();\n },\n };\n };\n\n const wallet = await connectKernel({\n spawn,\n passkey: options.passkey,\n ceremony,\n setTimer: (delayMs, fn) => {\n const timer = globalThis.setTimeout(fn, delayMs);\n return () => {\n globalThis.clearTimeout(timer);\n };\n },\n presence: subscribePresence,\n onDestroy: () => {\n globalThis.removeEventListener(\"pagehide\", onPageHide);\n },\n });\n\n // Page lifecycle: lock on pagehide. A BFCache-frozen page still runs the queued locked-event\n // handler on restore, so the wallet respawns then — terminating the worker directly would\n // leave the wallet pointing at a dead port.\n const onPageHide = (): void => {\n void bestEffort(async () => wallet.vault.lock());\n };\n globalThis.addEventListener(\"pagehide\", onPageHide);\n\n // Ask the browser to persist the vault once it exists — a denied request is fine.\n let persisted = false;\n const unsubscribe = wallet.subscribe(() => {\n if (!persisted && wallet.getSnapshot().vault !== \"uninitialized\") {\n persisted = true;\n unsubscribe();\n void bestEffort(async () => navigator.storage.persist());\n }\n });\n\n return wallet;\n}\n\nexport { createWebAuthnPasskey } from \"./passkey.ts\";\nexport type { WebAuthnRp } from \"./passkey.ts\";\n"],"mappings":";;;;;;;;;;AAiBA,MAAM,UAAU;AAIhB,SAAS,kBAAkB,SAA8B;CACvD,OAAO,IAAI,YAAY,sBAAsB,OAAO;AACtD;AAEA,SAAS,UAAU,SAA8B;CAC/C,OAAO,IAAI,YAAY,aAAa,OAAO;AAC7C;AAEA,SAAS,iBAAiB,OAAuB;CAC/C,IACE,iBAAiB,iBAChB,MAAM,SAAS,qBAAqB,MAAM,SAAS,eAEpD,MAAM,UAAU,oCAAoC;CAEtD,IAAI,iBAAiB,aACnB,MAAM;CAER,MAAM,kBAAkB,6BAA6B;AACvD;;AAGA,SAAS,aAAa,mBAAyC;CAC7D,OACE,kBAAkB,aAAa,OAE9B,IAAI,SAAS,iBAAiB,CAAC,CAAC,SAAS,EAAE,IAAI,aAAa;AAEjE;;;;;AAMA,SAAS,iBAAiB,mBAAyC;CACjE,OACE,kBAAkB,aAAa,OAE9B,IAAI,SAAS,iBAAiB,CAAC,CAAC,SAAS,EAAE,IAAK,QAAyB;AAE9E;AAEA,SAAS,QAAQ,OAAoD;CAGnE,OAAO,MAAM,WAAW,KAAA,IACpB,EAAE,OAAO,IAAI,WAAW,MAAM,KAAK,EAAE,IACrC;EAAE,OAAO,IAAI,WAAW,MAAM,KAAK;EAAG,QAAQ,IAAI,WAAW,MAAM,MAAM;CAAE;AACjF;;AAGA,SAAS,iBAAiB,QAAkC;CAC1D,MAAM,OACJ,kBAAkB,cACd,IAAI,WAAW,MAAM,IACrB,IAAI,WAAW,OAAO,QAAQ,OAAO,YAAY,OAAO,UAAU;CACxE,OAAO,IAAI,WAAW,IAAI;AAC5B;AAEA,SAAS,cACP,YACA,SACA,YACA,QACA,YACW;CACX,MAAM,QAAQ,SAAS;CACvB,MAAM,SAAS,SAAS;CACxB,IAAI,UAAU,KAAA,KAAc,cAAc,WAAW,KAAA,GACnD,MAAM,kBAAkB,oCAAoC;CAG9D,OAAO;EACL,cAAc,iBAAiB,WAAW,KAAK;EAC/C,OAAO,iBAAiB,KAAK;EAC7B,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,iBAAiB,MAAM,EAAE;EACnE,GAAI,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,iBAAiB,UAAU,EAAE;EAC/E;CACF;AACF;AAEA,SAAS,gBAAgB,YAA0C;CACjE,IAAI,EAAE,sBAAsB,sBAC1B,MAAM,kBAAkB,qDAAqD;CAE/E,OAAO;AACT;;;;;;AAOA,SAAgB,sBAAsB,IAAiC;CACrE,eAAe,OAAO,OAGC;EACrB,IAAI;EACJ,IAAI;GACF,aAAa,gBACX,MAAM,UAAU,YAAY,IAAI,EAC9B,WAAW;IACT,MAAM,GAAG;IACT,WAAW,OAAO,gCAAgB,IAAI,WAAW,EAAE,CAAC;IACpD,kBAAkB;IAClB,GAAI,MAAM,kBAAkB,KAAA,IACxB,CAAC,IACD,EACE,kBAAkB,MAAM,cAAc,KAAK,QAAQ;KACjD,MAAM;KACN,IAAI,IAAI,WAAW,EAAE;IACvB,EAAE,EACJ;IACJ,YAAY,EAAE,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,EAAE,EAAE;GACpD,EACF,CAAC,CACH;EACF,SAAS,OAAO;GACd,iBAAiB,KAAK;EACxB;EACA,MAAM,EAAE,aAAa;EACrB,IAAI,EAAE,oBAAoB,iCACxB,MAAM,kBAAkB,kDAAkD;EAE5E,IAAI,CAAC,aAAa,SAAS,iBAAiB,GAC1C,MAAM,kBAAkB,6CAA6C;EAEvE,OAAO,cACL,YACA,WAAW,0BAA0B,CAAC,CAAC,KAAK,SAC5C,MAAM,MAAM,WAAW,KAAA,GACvB,iBAAiB,SAAS,iBAAiB,GAC3C,SAAS,cAAc,KAAA,CACzB;CACF;CAEA,OAAO;EACL,QAAQ,OAAO,UAAU;GACvB,MAAM,yBAAS,IAAI,WAAW,EAAE;GAChC,OAAO,KAAK,MAAM,YAAY,WAAW,yBAAyB;GAClE,OAAO,gBAAgB,OAAO,SAAS,CAAC,CAAC;GACzC,IAAI;GACJ,IAAI;IACF,aAAa,gBACX,MAAM,UAAU,YAAY,OAAO,EACjC,WAAW;KACT,IAAI;MAAE,IAAI,GAAG;MAAI,MAAM,GAAG;KAAK;KAC/B,MAAM;MACJ,IAAI;MACJ,MAAM,MAAM;MACZ,aAAa,MAAM;KACrB;KACA,WAAW,OAAO,gCAAgB,IAAI,WAAW,EAAE,CAAC;KACpD,kBAAkB,CAChB;MAAE,MAAM;MAAc,KAAK;KAAG,GAC9B;MAAE,MAAM;MAAc,KAAK;KAAK,CAClC;KACA,wBAAwB;MACtB,aAAa;MACb,kBAAkB;KACpB;KACA,YAAY,EAAE,KAAK,EAAE,MAAM,QAAQ,MAAM,KAAK,EAAE,EAAE;IACpD,EACF,CAAC,CACH;GACF,SAAS,OAAO;IACd,iBAAiB,KAAK;GACxB;GACA,MAAM,EAAE,aAAa;GACrB,IAAI,EAAE,oBAAoB,mCACxB,MAAM,kBAAkB,oDAAoD;GAE9E,MAAM,aAAa,WAAW,0BAA0B;GACxD,IAAI,WAAW,KAAK,YAAY,MAC9B,MAAM,kBAAkB,sDAAsD;GAEhF,IAAI,WAAW,IAAI,YAAY,KAAA,GAG7B,OAAO,OAAO;IACZ,eAAe,CAAC,iBAAiB,WAAW,KAAK,CAAC;IAClD,OAAO,MAAM;GACf,CAAC;GAEH,MAAM,oBAAoB,SAAS,qBAAqB;GACxD,IAAI,CAAC,aAAa,iBAAiB,GACjC,MAAM,kBAAkB,4CAA4C;GAEtE,OAAO,cACL,YACA,WAAW,IAAI,SACf,MAAM,MAAM,WAAW,KAAA,GACvB,iBAAiB,iBAAiB,CACpC;EACF;EACA;CACF;AACF;;;;;;;;;;AC3MA,MAAM,mBAAmB;CAAE,SAAS;CAAM,SAAS;AAAK;AACxD,MAAM,kBAAkB;CAAC;CAAe;CAAe;CAAW;CAAS;AAAY;;;;;;AAOvF,SAAS,kBAAkB,YAAoC;CAC7D,KAAK,MAAM,QAAQ,iBACjB,WAAW,iBAAiB,MAAM,YAAY,gBAAgB;CAEhE,MAAM,kBAAwB;EAC5B,IAAI,SAAS,oBAAoB,WAC/B,WAAW;CAEf;CACA,SAAS,iBAAiB,oBAAoB,SAAS;CACvD,aAAa;EACX,KAAK,MAAM,QAAQ,iBACjB,WAAW,oBAAoB,MAAM,YAAY,gBAAgB;EAEnE,SAAS,oBAAoB,oBAAoB,SAAS;CAC5D;AACF;AAEA,eAAe,WAAW,MAA6C;CACrE,IAAI;EACF,MAAM,KAAK;CACb,QAAQ,CAER;AACF;AAEA,eAAsB,gBAAgB,SAAyC;CAC7E,MAAM,EAAE,aAAa;CACrB,IAAI,SAAS,aAAa,OACxB,MAAM,IAAI,YAAY,iBAAiB,sDAAsD;CAG/F,MAAM,OAA0B;EAAE,GAAG;EAAQ,aAAa,SAAS;CAAY;CAC/E,MAAM,YAAY,IAAI,IAAI,oBAAoB,YAAY,GAAG;CAC7D,MAAM,WAAW,sBAAsB;EACrC,IAAI,QAAQ,QAAQ;EACpB,MAAM,QAAQ,QAAQ;CACxB,CAAC;CAED,MAAM,cAAwB;EAC5B,MAAM,SAAS,IAAI,OAAO,WAAW;GAAE,MAAM;GAAU,MAAM;EAAc,CAAC;EAE5E,OAAO,YAAY,IAAI;EACvB,OAAO;GACL,OAAO,SAAwB,aAA4B;IACzD,OAAO,YAAY,SAAS,QAAQ;GACtC;GACA,SAAS,YAAY;IACnB,MAAM,YAAY,UAA8B;KAC9C,QAAQ,MAAM,IAAI;IACpB;IACA,OAAO,iBAAiB,WAAW,QAAQ;IAC3C,aAAa;KACX,OAAO,oBAAoB,WAAW,QAAQ;IAChD;GACF;GACA,SAAS,YAAY;IAEnB,MAAM,iBAAuB;KAC3B,QAAQ;IACV;IACA,OAAO,iBAAiB,SAAS,QAAQ;IACzC,OAAO,iBAAiB,gBAAgB,QAAQ;IAChD,aAAa;KACX,OAAO,oBAAoB,SAAS,QAAQ;KAC5C,OAAO,oBAAoB,gBAAgB,QAAQ;IACrD;GACF;GACA,iBAAiB;IACf,OAAO,UAAU;GACnB;EACF;CACF;CAEA,MAAM,SAAS,MAAM,cAAc;EACjC;EACA,SAAS,QAAQ;EACjB;EACA,WAAW,SAAS,OAAO;GACzB,MAAM,QAAQ,WAAW,WAAW,IAAI,OAAO;GAC/C,aAAa;IACX,WAAW,aAAa,KAAK;GAC/B;EACF;EACA,UAAU;EACV,iBAAiB;GACf,WAAW,oBAAoB,YAAY,UAAU;EACvD;CACF,CAAC;CAKD,MAAM,mBAAyB;EAC7B,WAAgB,YAAY,OAAO,MAAM,KAAK,CAAC;CACjD;CACA,WAAW,iBAAiB,YAAY,UAAU;CAGlD,IAAI,YAAY;CAChB,MAAM,cAAc,OAAO,gBAAgB;EACzC,IAAI,CAAC,aAAa,OAAO,YAAY,CAAC,CAAC,UAAU,iBAAiB;GAChE,YAAY;GACZ,YAAY;GACZ,WAAgB,YAAY,UAAU,QAAQ,QAAQ,CAAC;EACzD;CACF,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser/passkey.ts","../src/browser/index.ts"],"sourcesContent":["/**\n * The WebAuthn passkey ceremony (main thread only): `navigator.credentials` with the PRF extension.\n * A wallet passkey derives the wallet seed from `PRF(walletSalt)`; the vault factor derives the\n * vault KEK from `PRF(vaultSalt)` on the same credential. Every ceremony output is checked for\n * `prf.enabled`, PRF results and the user-verification flag — a credential that cannot prove UV\n * never reaches the kernel.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport { PASSKEY_PURPOSE_VAULT, PASSKEY_PURPOSE_WALLET } from \"@qntx/wallet/host\";\nimport type { PasskeyCeremony, PrfResult, PrfSalts } from \"@qntx/wallet/host\";\n\nexport type WebAuthnRp = {\n /** RP id — the origin's effective domain, e.g. `location.hostname`. */\n readonly id: string;\n readonly name: string;\n};\n\nconst UV_FLAG = 0x04; // authenticator-data flags byte (offset 32), bit 2 = user verified\nconst BE_FLAG = 0x08; // bit 3 = credential is backup-eligible (multi-device capable)\nconst BS_FLAG = 0x10; // bit 4 = credential is currently backed up (synced)\n\nfunction factorUnavailable(message: string): WalletError {\n return new WalletError(\"factor-unavailable\", message);\n}\n\nfunction cancelled(message: string): WalletError {\n return new WalletError(\"cancelled\", message);\n}\n\nfunction mapCeremonyError(error: unknown): never {\n if (\n error instanceof DOMException &&\n (error.name === \"NotAllowedError\" || error.name === \"AbortError\")\n ) {\n throw cancelled(\"the passkey ceremony was cancelled\");\n }\n if (error instanceof WalletError) {\n throw error;\n }\n throw factorUnavailable(\"the passkey ceremony failed\");\n}\n\n/** The UV bit of an authenticator-data blob (flags byte at offset 32, bit 0x04). */\nfunction userVerified(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & UV_FLAG) !== 0\n );\n}\n\n/**\n * Whether the credential is backed up off-device (flags byte at offset 32): synced requires both BE\n * (the credential is eligible for backup) and BS (the authenticator actually backs it up).\n */\nfunction credentialSynced(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & (BE_FLAG | BS_FLAG)) === (BE_FLAG | BS_FLAG)\n );\n}\n\nfunction prfEval(salts: PrfSalts): AuthenticationExtensionsPRFValues {\n // The constructor copy narrows Uint8Array<ArrayBufferLike> to Uint8Array<ArrayBuffer> —\n // the DOM BufferSource does not admit shared-array-buffer views.\n return salts.second === undefined\n ? { first: new Uint8Array(salts.first) }\n : { first: new Uint8Array(salts.first), second: new Uint8Array(salts.second) };\n}\n\n/** A detached copy of a BufferSource — the returned Uint8Array owns its own fresh buffer. */\nfunction copyBufferSource(source: BufferSource): Uint8Array {\n const view =\n source instanceof ArrayBuffer\n ? new Uint8Array(source)\n : new Uint8Array(source.buffer, source.byteOffset, source.byteLength);\n return new Uint8Array(view);\n}\n\nfunction readPrfResult(\n credential: PublicKeyCredential,\n results: AuthenticationExtensionsPRFValues | undefined,\n wantSecond: boolean,\n synced: boolean,\n userHandle?: ArrayBuffer,\n): PrfResult {\n const first = results?.first;\n const second = results?.second;\n if (first === undefined || (wantSecond && second === undefined)) {\n throw factorUnavailable(\"the passkey produced no PRF output\");\n }\n // Copy into fresh Uint8Arrays and drop every reference to the extension's ArrayBuffers.\n return {\n credentialId: copyBufferSource(credential.rawId),\n first: copyBufferSource(first),\n ...(second === undefined ? {} : { second: copyBufferSource(second) }),\n ...(userHandle === undefined ? {} : { userHandle: copyBufferSource(userHandle) }),\n synced,\n };\n}\n\nfunction checkCredential(credential: unknown): PublicKeyCredential {\n if (!(credential instanceof PublicKeyCredential)) {\n throw factorUnavailable(\"the authenticator returned no public-key credential\");\n }\n return credential;\n}\n\n/**\n * The WebAuthn ceremony. `create` registers a resident credential with `prf.eval` over the salts;\n * when the extension returns no results a single assertion over the new credential produces them.\n * `assert` runs a discoverable (or allowCredentials-scoped) get with the same eval shape.\n */\nexport function createWebAuthnPasskey(rp: WebAuthnRp): PasskeyCeremony {\n async function assert(input: {\n readonly credentialIds?: ReadonlyArray<Uint8Array>;\n readonly salts: PrfSalts;\n }): Promise<PrfResult> {\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.get({\n publicKey: {\n rpId: rp.id,\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n userVerification: \"required\",\n ...(input.credentialIds === undefined\n ? {}\n : {\n allowCredentials: input.credentialIds.map((id) => ({\n type: \"public-key\" as const,\n id: new Uint8Array(id),\n })),\n }),\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAssertionResponse)) {\n throw factorUnavailable(\"the authenticator returned no assertion response\");\n }\n if (!userVerified(response.authenticatorData)) {\n throw factorUnavailable(\"the passkey assertion was not user-verified\");\n }\n return readPrfResult(\n credential,\n credential.getClientExtensionResults().prf?.results,\n input.salts.second !== undefined,\n credentialSynced(response.authenticatorData),\n response.userHandle ?? undefined,\n );\n }\n\n return {\n create: async (input) => {\n const userId = new Uint8Array(16);\n userId[0] = input.purpose === \"wallet\" ? PASSKEY_PURPOSE_WALLET : PASSKEY_PURPOSE_VAULT;\n crypto.getRandomValues(userId.subarray(1));\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.create({\n publicKey: {\n rp: { id: rp.id, name: rp.name },\n user: {\n id: userId,\n name: input.name,\n displayName: input.name,\n },\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n pubKeyCredParams: [\n { type: \"public-key\", alg: -7 },\n { type: \"public-key\", alg: -257 },\n ],\n authenticatorSelection: {\n residentKey: \"required\",\n userVerification: \"required\",\n },\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAttestationResponse)) {\n throw factorUnavailable(\"the authenticator returned no attestation response\");\n }\n const extensions = credential.getClientExtensionResults();\n if (extensions.prf?.enabled !== true) {\n throw factorUnavailable(\"the authenticator does not support the PRF extension\");\n }\n if (extensions.prf.results === undefined) {\n // Some authenticators omit creation-time PRF outputs: one assertion over the new\n // credential produces them (it is that response whose UV and BE/BS flags get read).\n return assert({\n credentialIds: [copyBufferSource(credential.rawId)],\n salts: input.salts,\n });\n }\n const authenticatorData = response.getAuthenticatorData();\n if (!userVerified(authenticatorData)) {\n throw factorUnavailable(\"the passkey creation was not user-verified\");\n }\n return readPrfResult(\n credential,\n extensions.prf.results,\n input.salts.second !== undefined,\n credentialSynced(authenticatorData),\n );\n },\n assert,\n };\n}\n","/**\n * The Web host: `createWebWallet` spawns the bundled kernel worker (dist/kernel.worker.js) whose\n * vault lives in the worker's own IndexedDB. Lock destroys the worker; the vault survives in\n * IndexedDB, so the respawned worker reloads it. `pagehide` locks immediately; the idle timer is\n * refreshed by user-presence signals, not request traffic. The WebAuthn ceremony runs on the main\n * thread.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport type { Wallet, WalletOptions } from \"@qntx/wallet\";\nimport { connectKernel } from \"@qntx/wallet/host\";\nimport type { MainPort } from \"@qntx/wallet/host\";\nimport type { ProtocolValue } from \"@qntx/wallet/protocol\";\n\nimport type { BrowserWorkerInit } from \"../shared.ts\";\nimport { createWebAuthnPasskey } from \"./passkey.ts\";\n\nconst LISTENER_OPTIONS = { capture: true, passive: true };\nconst ACTIVITY_EVENTS = [\"pointerdown\", \"pointermove\", \"keydown\", \"wheel\", \"touchstart\"];\n\n/**\n * The browser's user-presence source: direct input on the page, or returning to a visible tab.\n * Capture-phase passive listeners do a single boolean check per event — high-frequency signals\n * (scroll, pointermove) cost nothing. Returns the unsubscribe the wallet owns.\n */\nfunction subscribePresence(onActivity: () => void): () => void {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.addEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n const onVisible = (): void => {\n if (document.visibilityState === \"visible\") {\n onActivity();\n }\n };\n document.addEventListener(\"visibilitychange\", onVisible);\n return () => {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.removeEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n document.removeEventListener(\"visibilitychange\", onVisible);\n };\n}\n\nasync function bestEffort(task: () => Promise<unknown>): Promise<void> {\n try {\n await task();\n } catch {\n // Lifecycle hooks and persistence hints must never surface failures.\n }\n}\n\nexport async function createWebWallet(options: WalletOptions): Promise<Wallet> {\n const { autoLock } = options;\n if (autoLock.platform !== \"web\") {\n throw new WalletError(\"invalid-input\", \"createWebWallet accepts only the web autoLock policy\");\n }\n // The page sends only the idle choice; the worker owns the maximum session duration.\n const init: BrowserWorkerInit = { t: \"init\", idleMinutes: autoLock.idleMinutes };\n const ceremony = createWebAuthnPasskey({\n id: options.passkey.rpId,\n name: options.passkey.rpName,\n });\n\n const spawn = (): MainPort => {\n // Keep this exact `new Worker(new URL(\"…\", import.meta.url))` shape: bundlers (Vite,\n // webpack, Parcel, esbuild-based tools) recognize only this literal form as a worker entry and\n // bundle the kernel with its chunks and spark.wasm. A precomputed URL is copied as one opaque\n // asset, and the worker then dies loading its first chunk.\n const worker = new Worker(new URL(\"kernel.worker.js\", import.meta.url), {\n type: \"module\",\n name: \"qntx-wallet\",\n });\n // oxlint-disable-next-line unicorn/require-post-message-target-origin -- Worker.postMessage takes a transfer list, not an origin\n worker.postMessage(init); // the kernel validates it strictly before startKernel\n return {\n post: (message: ProtocolValue, transfer: ArrayBuffer[]) => {\n worker.postMessage(message, transfer);\n },\n listen: (handler) => {\n const listener = (event: MessageEvent): void => {\n handler(event.data);\n };\n worker.addEventListener(\"message\", listener);\n return () => {\n worker.removeEventListener(\"message\", listener);\n };\n },\n onExit: (handler) => {\n // Web Workers have no exit event; error/messageerror are the only death signals.\n const listener = (): void => {\n handler();\n };\n worker.addEventListener(\"error\", listener);\n worker.addEventListener(\"messageerror\", listener);\n return () => {\n worker.removeEventListener(\"error\", listener);\n worker.removeEventListener(\"messageerror\", listener);\n };\n },\n terminate: () => {\n worker.terminate();\n },\n };\n };\n\n const wallet = await connectKernel({\n spawn,\n passkey: options.passkey,\n ceremony,\n setTimer: (delayMs, fn) => {\n const timer = globalThis.setTimeout(fn, delayMs);\n return () => {\n globalThis.clearTimeout(timer);\n };\n },\n presence: subscribePresence,\n onDestroy: () => {\n globalThis.removeEventListener(\"pagehide\", onPageHide);\n },\n });\n\n // Page lifecycle: lock on pagehide. A BFCache-frozen page still runs the queued locked-event\n // handler on restore, so the wallet respawns then — terminating the worker directly would\n // leave the wallet pointing at a dead port.\n const onPageHide = (): void => {\n void bestEffort(async () => wallet.vault.lock());\n };\n globalThis.addEventListener(\"pagehide\", onPageHide);\n\n // Ask the browser to persist the vault once it exists — a denied request is fine.\n let persisted = false;\n const unsubscribe = wallet.subscribe(() => {\n if (!persisted && wallet.getSnapshot().vault !== \"uninitialized\") {\n persisted = true;\n unsubscribe();\n void bestEffort(async () => navigator.storage.persist());\n }\n });\n\n return wallet;\n}\n\nexport { createWebAuthnPasskey } from \"./passkey.ts\";\nexport type { WebAuthnRp } from \"./passkey.ts\";\n"],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  "backends": {
5
5
  "spark": {
6
6
  "abiVersion": 1,
7
- "wasmIntegrity": "sha256-Ge1DhJu87OH//DsZUQlEBaqDCo1jGxg/hkGP4WHTqB4="
7
+ "wasmIntegrity": "sha256-PFqThpVpNZHbmIuR13MZqLAupIIhfVcd3nMtB9QMTTE="
8
8
  }
9
9
  },
10
10
  "libraries": [
@@ -30,13 +30,13 @@
30
30
  },
31
31
  {
32
32
  "name": "@qntx/spark",
33
- "version": "0.3.0",
34
- "integrity": "sha512-gu1RWmYAqcR+PxE/xGzVQllUzfbyrzEwxvGaQyVcLehV+M6qbfyVcOw2W/opMBOsQuCdMt1TlCXWXtUSl4e/uA=="
33
+ "version": "0.5.0",
34
+ "integrity": "sha512-nRJzrACUqRkKPwZP676dtYHXLWDTb2AzhVKLzN8PLlrcF7IEDYjQvlAMV0USC22vcQh1EqtWPvLGG6X/dCQ4Pw=="
35
35
  },
36
36
  {
37
37
  "name": "@qntx/spark-wasm",
38
- "version": "0.3.0",
39
- "integrity": "sha512-+i7NTus+k+KljVqDTHEun9cADgVvGr5T6TTm41xx/a/M4G5TFQdD+5uhPlb5qo8UvEInSzjzEkfFd/iqhXM+Dg=="
38
+ "version": "0.5.0",
39
+ "integrity": "sha512-C+TQc3xr7TtIF/jX+gcgAKOgvxVqKR70fVZQHzSd6ulnjwXGqS/CmfjgH59kqEvTrsl6vyMu1r7p4cOweTTVWQ=="
40
40
  },
41
41
  {
42
42
  "name": "@scure/base",
@@ -52,54 +52,37 @@
52
52
  "name": "@scure/bip39",
53
53
  "version": "2.4.0",
54
54
  "integrity": "sha512-82dxFbZUYboyOf0AXiydsQrFQ5Q4h9mX+O2UkE91ROYmsc0BKMGZLwDmy96Jpa2+vrtoxomjUhy1RPIgH/r2nA=="
55
- },
56
- {
57
- "name": "@scure/btc-signer",
58
- "version": "2.4.1",
59
- "integrity": "sha512-ch6h66o9nPhEoe/KciMMUJeH2MBPB8UAJI1if35Fxl6885lKTsc9YoCRqTW0/RjI0Vv9GSg1tmKtHHODyAw81g=="
60
55
  }
61
56
  ],
62
57
  "bundles": [
63
- {
64
- "file": "worker-30nrftxx.js",
65
- "sha256": "d550424185659b7a49f324b4e5327620892a1318947202216a042f627346338c"
66
- },
67
- {
68
- "file": "signer-tgawen1v.js",
69
- "sha256": "fabee6d12dda04cbd9ccb4ec95ae69c15cd17ac5a5dc3a684098a4b7394a97af"
70
- },
71
58
  {
72
59
  "file": "kernel.worker.js",
73
- "sha256": "e84bcc14639b60d592517dca3ffb7b6d02c7076db6ec5312957a696e714bf725"
74
- },
75
- {
76
- "file": "browser-gx857d3g.js",
77
- "sha256": "c7bd79fa8c14cfbe8f30fd908a519c9d68d38c8775dac474534dd71c87142de3"
78
- },
79
- {
80
- "file": "kernel.node.js",
81
- "sha256": "1683c068c2b114b2fb868d19e274df20b2ce0475b420e066255b5205ba30d4c3"
60
+ "sha256": "f6bbcd9c410672061398fba3b1aa0b407667c2ab5828fe00a69ceba270113d1b"
82
61
  },
83
62
  {
84
- "file": "index-cgstn3zw.js",
85
- "sha256": "ba08ed6a721c04343273ada887bdb946aa65e9d543d0f7af87ede2605a2d246e"
63
+ "file": "node-gxb54jcq.js",
64
+ "sha256": "e58ca528749b9191f761652a3ad7dedc961e2ea2e7fc61987c9ed1afa588eb94"
86
65
  },
87
66
  {
88
- "file": "node-7sztn879.js",
89
- "sha256": "a8abc88fd4de74b8070e3a029418016e6645675453c02a10147da3a16620d71a"
67
+ "file": "worker-gxb54jcq.js",
68
+ "sha256": "e58ca528749b9191f761652a3ad7dedc961e2ea2e7fc61987c9ed1afa588eb94"
90
69
  },
91
70
  {
92
- "file": "worker-7sztn879.js",
93
- "sha256": "54ede3dd274c15b3704a88aab9536f0c15fa95c38a50002770a62e8709f07b41"
71
+ "file": "kernel.node.js",
72
+ "sha256": "ed259c5cc067602e8344a731403b5a05c37d46d234978a30f6b8a577936791c4"
94
73
  },
95
74
  {
96
- "file": "node-30nrftxx.js",
97
- "sha256": "d550424185659b7a49f324b4e5327620892a1318947202216a042f627346338c"
75
+ "file": "browser-dy5h3c5e.js",
76
+ "sha256": "31c1cc26ff286d492668c5b4bf60833283cf8f17b252bdc234fbba94c67db155"
98
77
  },
99
78
  {
100
79
  "file": "node-nhrchcf5.js",
101
80
  "sha256": "55fdd5775b473d0f283502976f68a3413b8a5587ca6293b1021fe008fcbf8302"
102
81
  },
82
+ {
83
+ "file": "signer-yectsdty.js",
84
+ "sha256": "6f26acebc7bb9b40b0074ef3efea88c1126d416b710e8e04e3deb297364aa82a"
85
+ },
103
86
  {
104
87
  "file": "worker-nhrchcf5.js",
105
88
  "sha256": "55fdd5775b473d0f283502976f68a3413b8a5587ca6293b1021fe008fcbf8302"
@@ -7955,36 +7955,24 @@ function createNostrModule(backend) {
7955
7955
 
7956
7956
  // ../wallet-kernel/src/modules/spark/runtime.ts
7957
7957
  async function loadSparkRuntime() {
7958
- const [signer, btc] = await Promise.all([
7959
- import("./signer-tgawen1v.js"),
7960
- import("./index-cgstn3zw.js")
7961
- ]);
7958
+ const signer = await import("./signer-yectsdty.js");
7962
7959
  return {
7960
+ bitcoinAddressScript: signer.bitcoinAddressScript,
7963
7961
  createSparkSigner: signer.createSparkSigner,
7964
7962
  decodeBolt11: signer.decodeBolt11,
7965
7963
  EPHEMERAL_ANCHOR_SCRIPT: signer.EPHEMERAL_ANCHOR_SCRIPT,
7966
7964
  lightningSendHtlcScript: signer.lightningSendHtlcScript,
7967
7965
  sparkAccountKey: signer.sparkAccountKey,
7968
- sparkNetworkConfig: signer.sparkNetworkConfig,
7969
- Address: btc.Address,
7970
- NETWORK: btc.NETWORK,
7971
- OutScript: btc.OutScript,
7972
- TEST_NETWORK: btc.TEST_NETWORK
7966
+ sparkNetworkConfig: signer.sparkNetworkConfig
7973
7967
  };
7974
7968
  }
7975
7969
 
7976
7970
  // ../wallet-kernel/src/modules/spark/index.ts
7977
7971
  var poolKey = (account) => `${account.walletId}/${account.groupIndex}/${account.network}`;
7978
7972
  var approvalRequired = () => new KernelError("approval-required", "this outflow needs a matching approval");
7979
- var btcNet = (rt, network) => network === "mainnet" ? rt.NETWORK : { ...rt.TEST_NETWORK, bech32: "bcrt" };
7980
7973
  var addressScriptFor = (rt, network, address) => {
7981
7974
  try {
7982
- const net = rt.Address(btcNet(rt, network));
7983
- const decoded = rt.OutScript.encode(net.decode(address));
7984
- if (net.encode(rt.OutScript.decode(decoded)).toLowerCase() !== address.toLowerCase()) {
7985
- throw new KernelError("invalid-input", "address: wrong network");
7986
- }
7987
- return decoded;
7975
+ return rt.bitcoinAddressScript(address, network);
7988
7976
  } catch (error) {
7989
7977
  if (error instanceof KernelError) {
7990
7978
  throw error;
@@ -9041,7 +9029,7 @@ function isNodeWorkerData(value) {
9041
9029
 
9042
9030
  // src/worker/spark.ts
9043
9031
  async function loadSparkBackend(readWasm) {
9044
- const { loadSparkWasm, SPARK_WASM_INTEGRITY } = await import("./browser-gx857d3g.js");
9032
+ const { loadSparkWasm, SPARK_WASM_INTEGRITY } = await import("./browser-dy5h3c5e.js");
9045
9033
  const bytes = await readWasm();
9046
9034
  const integrity = `sha256-${base64.encode(sha256(bytes))}`;
9047
9035
  if (integrity !== SPARK_WASM_INTEGRITY) {
@@ -72,7 +72,7 @@ var __chunks = (base, ids, nodes, entry) => {
72
72
  __chunkSeen ||= {};
73
73
  __preload(ids[entry], 1);
74
74
  };
75
- __chunks(import.meta.url,["avvj0v9s","tgawen1v","cgstn3zw","gx857d3g","nhrchcf5","7sztn879","30nrftxx"],[["./kernel.worker.js",4],["./signer-tgawen1v.js",5,4,6],["./index-cgstn3zw.js",5],["./browser-gx857d3g.js",6],["./worker-nhrchcf5.js"],["./worker-7sztn879.js",4],["./worker-30nrftxx.js"]],0);
75
+ __chunks(import.meta.url,["777kk90k","yectsdty","dy5h3c5e","nhrchcf5","gxb54jcq"],[["./kernel.worker.js",3],["./signer-yectsdty.js",3,4],["./browser-dy5h3c5e.js",4],["./worker-nhrchcf5.js"],["./worker-gxb54jcq.js"]],0);
76
76
  // ../../node_modules/.bun/@qntx+nostr@0.15.0+ea9f70ecde856c26/node_modules/@qntx/nostr/dist/util-BcRBO6o-.mjs
77
77
  var NostrError = class extends Error {
78
78
  name = "NostrError";
@@ -7982,36 +7982,24 @@ function createNostrModule(backend) {
7982
7982
 
7983
7983
  // ../wallet-kernel/src/modules/spark/runtime.ts
7984
7984
  async function loadSparkRuntime() {
7985
- const [signer, btc] = await Promise.all([
7986
- (__preload("tgawen1v"), import("./signer-tgawen1v.js")),
7987
- (__preload("cgstn3zw"), import("./index-cgstn3zw.js"))
7988
- ]);
7985
+ const signer = await (__preload("yectsdty"), import("./signer-yectsdty.js"));
7989
7986
  return {
7987
+ bitcoinAddressScript: signer.bitcoinAddressScript,
7990
7988
  createSparkSigner: signer.createSparkSigner,
7991
7989
  decodeBolt11: signer.decodeBolt11,
7992
7990
  EPHEMERAL_ANCHOR_SCRIPT: signer.EPHEMERAL_ANCHOR_SCRIPT,
7993
7991
  lightningSendHtlcScript: signer.lightningSendHtlcScript,
7994
7992
  sparkAccountKey: signer.sparkAccountKey,
7995
- sparkNetworkConfig: signer.sparkNetworkConfig,
7996
- Address: btc.Address,
7997
- NETWORK: btc.NETWORK,
7998
- OutScript: btc.OutScript,
7999
- TEST_NETWORK: btc.TEST_NETWORK
7993
+ sparkNetworkConfig: signer.sparkNetworkConfig
8000
7994
  };
8001
7995
  }
8002
7996
 
8003
7997
  // ../wallet-kernel/src/modules/spark/index.ts
8004
7998
  var poolKey = (account) => `${account.walletId}/${account.groupIndex}/${account.network}`;
8005
7999
  var approvalRequired = () => new KernelError("approval-required", "this outflow needs a matching approval");
8006
- var btcNet = (rt, network) => network === "mainnet" ? rt.NETWORK : { ...rt.TEST_NETWORK, bech32: "bcrt" };
8007
8000
  var addressScriptFor = (rt, network, address) => {
8008
8001
  try {
8009
- const net = rt.Address(btcNet(rt, network));
8010
- const decoded = rt.OutScript.encode(net.decode(address));
8011
- if (net.encode(rt.OutScript.decode(decoded)).toLowerCase() !== address.toLowerCase()) {
8012
- throw new KernelError("invalid-input", "address: wrong network");
8013
- }
8014
- return decoded;
8002
+ return rt.bitcoinAddressScript(address, network);
8015
8003
  } catch (error) {
8016
8004
  if (error instanceof KernelError) {
8017
8005
  throw error;
@@ -9325,7 +9313,7 @@ function createIndexedDbStore() {
9325
9313
 
9326
9314
  // src/worker/spark.ts
9327
9315
  async function loadSparkBackend(readWasm) {
9328
- const { loadSparkWasm, SPARK_WASM_INTEGRITY } = await (__preload("gx857d3g"), import("./browser-gx857d3g.js"));
9316
+ const { loadSparkWasm, SPARK_WASM_INTEGRITY } = await (__preload("dy5h3c5e"), import("./browser-dy5h3c5e.js"));
9329
9317
  const bytes = await readWasm();
9330
9318
  const integrity = `sha256-${base64.encode(sha256(bytes))}`;
9331
9319
  if (integrity !== SPARK_WASM_INTEGRITY) {
@@ -1,4 +1,4 @@
1
- // ../../node_modules/.bun/@qntx+spark@0.3.0/node_modules/@qntx/spark/dist/errors-5xLkExNL.mjs
1
+ // ../../node_modules/.bun/@qntx+spark@0.5.0/node_modules/@qntx/spark/dist/errors-5xLkExNL.mjs
2
2
  var SparkError = class extends Error {
3
3
  constructor(message, options) {
4
4
  super(message, options);