@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 +9 -2
- package/dist/{browser-gx857d3g.js → browser-dy5h3c5e.js} +5 -5
- package/dist/browser.mjs +1 -2
- package/dist/browser.mjs.map +1 -1
- package/dist/kernel.manifest.json +18 -35
- package/dist/kernel.node.js +5 -17
- package/dist/kernel.worker.js +6 -18
- package/dist/{worker-30nrftxx.js → node-gxb54jcq.js} +1 -1
- package/dist/{signer-tgawen1v.js → signer-yectsdty.js} +4620 -187
- package/dist/spark.wasm +0 -0
- package/dist/{node-30nrftxx.js → worker-gxb54jcq.js} +1 -1
- package/package.json +8 -7
- package/dist/index-cgstn3zw.js +0 -26
- package/dist/node-7sztn879.js +0 -4446
- package/dist/worker-7sztn879.js +0 -4446
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'
|
|
79
|
-
`
|
|
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-
|
|
4
|
+
} from "./worker-gxb54jcq.js";
|
|
5
5
|
|
|
6
|
-
// ../../node_modules/.bun/@qntx+spark@0.
|
|
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.
|
|
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-
|
|
178
|
+
var SPARK_WASM_INTEGRITY = "sha256-PFqThpVpNZHbmIuR13MZqLAupIIhfVcd3nMtB9QMTTE=";
|
|
179
179
|
|
|
180
|
-
// ../../node_modules/.bun/@qntx+spark-wasm@0.
|
|
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(
|
|
190
|
+
const worker = new Worker(new URL("kernel.worker.js", import.meta.url), {
|
|
192
191
|
type: "module",
|
|
193
192
|
name: "qntx-wallet"
|
|
194
193
|
});
|
package/dist/browser.mjs.map
CHANGED
|
@@ -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":"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"}
|
|
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-
|
|
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.
|
|
34
|
-
"integrity": "sha512-
|
|
33
|
+
"version": "0.5.0",
|
|
34
|
+
"integrity": "sha512-nRJzrACUqRkKPwZP676dtYHXLWDTb2AzhVKLzN8PLlrcF7IEDYjQvlAMV0USC22vcQh1EqtWPvLGG6X/dCQ4Pw=="
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"name": "@qntx/spark-wasm",
|
|
38
|
-
"version": "0.
|
|
39
|
-
"integrity": "sha512
|
|
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": "
|
|
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": "
|
|
85
|
-
"sha256": "
|
|
63
|
+
"file": "node-gxb54jcq.js",
|
|
64
|
+
"sha256": "e58ca528749b9191f761652a3ad7dedc961e2ea2e7fc61987c9ed1afa588eb94"
|
|
86
65
|
},
|
|
87
66
|
{
|
|
88
|
-
"file": "
|
|
89
|
-
"sha256": "
|
|
67
|
+
"file": "worker-gxb54jcq.js",
|
|
68
|
+
"sha256": "e58ca528749b9191f761652a3ad7dedc961e2ea2e7fc61987c9ed1afa588eb94"
|
|
90
69
|
},
|
|
91
70
|
{
|
|
92
|
-
"file": "
|
|
93
|
-
"sha256": "
|
|
71
|
+
"file": "kernel.node.js",
|
|
72
|
+
"sha256": "ed259c5cc067602e8344a731403b5a05c37d46d234978a30f6b8a577936791c4"
|
|
94
73
|
},
|
|
95
74
|
{
|
|
96
|
-
"file": "
|
|
97
|
-
"sha256": "
|
|
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"
|
package/dist/kernel.node.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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-
|
|
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) {
|
package/dist/kernel.worker.js
CHANGED
|
@@ -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,["
|
|
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
|
|
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
|
-
|
|
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("
|
|
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.
|
|
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);
|