@waaskey/sdk 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js ADDED
@@ -0,0 +1,466 @@
1
+ import { Worker } from 'worker_threads';
2
+ import { WebSocket } from 'ws';
3
+
4
+ // src/mpc/node/worker-core.ts
5
+
6
+ // src/mpc/wasm-core.ts
7
+ var WasmMpcCore = class {
8
+ constructor(load) {
9
+ this.load = load;
10
+ }
11
+ load;
12
+ modulePromise;
13
+ init() {
14
+ return this.modulePromise ??= this.load();
15
+ }
16
+ async runKeygen(params) {
17
+ const wasm = await this.init();
18
+ const raw = await wasm.keygen(
19
+ this.encode(params, { parties: params.parties, threshold: params.threshold, pregenerated_primes_json: params.pregeneratedPrimes })
20
+ );
21
+ if (!raw || typeof raw.keyshare_json !== "string") {
22
+ throw new Error("client-wasm returned an unexpected keygen result");
23
+ }
24
+ return {
25
+ keyShare: raw.keyshare_json,
26
+ auxInfo: raw.aux_info_json,
27
+ sharedPublicKey: extractSharedPublicKey(raw.keyshare_json)
28
+ };
29
+ }
30
+ async runSign(params) {
31
+ const wasm = await this.init();
32
+ const raw = await wasm.sign(
33
+ this.encode(params, {
34
+ // The wasm deserializes `share` with `serde_json::from_value::<KeyShare>`, so it must ride
35
+ // the wire as the KeyShare OBJECT — not a JSON string, and not the storage blob.
36
+ share: toShareObject(params.share),
37
+ participants: params.participants,
38
+ signer_position: params.signerPosition,
39
+ digest: params.digest
40
+ })
41
+ );
42
+ if (!raw || typeof raw.signature_json !== "string") {
43
+ throw new Error("client-wasm returned an unexpected sign result");
44
+ }
45
+ return { signature: raw.signature_json };
46
+ }
47
+ async pregeneratePrimes(curve) {
48
+ const wasm = await this.init();
49
+ if (!wasm.pregeneratePrimes) {
50
+ throw new Error("client-wasm does not expose pregeneratePrimes");
51
+ }
52
+ const raw = await wasm.pregeneratePrimes(JSON.stringify({ curve }));
53
+ if (!raw || typeof raw.primes_json !== "string") {
54
+ throw new Error("client-wasm returned an unexpected pregeneratePrimes result");
55
+ }
56
+ return raw.primes_json;
57
+ }
58
+ async runReshareAssemble(params) {
59
+ const wasm = await this.init();
60
+ if (!wasm.reshareAssemble) {
61
+ throw new Error(reshareFeatureHint("reshareAssemble"));
62
+ }
63
+ const raw = await wasm.reshareAssemble(
64
+ JSON.stringify({
65
+ curve: params.curve,
66
+ new_position: params.newPosition,
67
+ new_preimages: params.newPreimages,
68
+ new_threshold: params.newThreshold,
69
+ wallet: params.wallet,
70
+ commitments: params.commitments,
71
+ sub_shares: params.subShares
72
+ })
73
+ );
74
+ if (!raw || typeof raw.core_json !== "string") {
75
+ throw new Error("client-wasm returned an unexpected reshareAssemble result");
76
+ }
77
+ return { core: raw.core_json, sharedPublicKey: decodePublicKey(raw.shared_public_key_json) };
78
+ }
79
+ async runCompleteReshare(params) {
80
+ const wasm = await this.init();
81
+ if (!wasm.completeReshare) {
82
+ throw new Error(reshareFeatureHint("completeReshare"));
83
+ }
84
+ const raw = await wasm.completeReshare(
85
+ this.encode(params, {
86
+ // The wasm expects the bare core as an inline JSON object (serde_json::Value), not a string.
87
+ core: JSON.parse(params.core),
88
+ parties: params.parties,
89
+ // The device's OWN primes for the aux ceremony — never server-provided. Absent ⇒ generated inline.
90
+ ...params.pregeneratedPrimes === void 0 ? {} : { pregenerated_primes_json: params.pregeneratedPrimes }
91
+ })
92
+ );
93
+ if (!raw || typeof raw.keyshare_json !== "string") {
94
+ throw new Error("client-wasm returned an unexpected completeReshare result");
95
+ }
96
+ return { keyShare: raw.keyshare_json, sharedPublicKey: extractSharedPublicKey(raw.keyshare_json) };
97
+ }
98
+ async runMemberKeygen(params) {
99
+ const wasm = await this.init();
100
+ if (!wasm.keygenMember) {
101
+ throw new Error(memberCeremonyFeatureHint("keygenMember"));
102
+ }
103
+ const raw = await wasm.keygenMember(
104
+ this.encodeMember(params, {
105
+ roles: params.roles,
106
+ party_index: params.partyIndex,
107
+ threshold: params.threshold,
108
+ ...params.pregeneratedPrimes === void 0 ? {} : { pregenerated_primes_json: params.pregeneratedPrimes }
109
+ })
110
+ );
111
+ if (!raw || typeof raw.keyshare_json !== "string") {
112
+ throw new Error("client-wasm returned an unexpected keygenMember result");
113
+ }
114
+ return {
115
+ keyShare: raw.keyshare_json,
116
+ auxInfo: raw.aux_info_json,
117
+ sharedPublicKey: extractSharedPublicKey(raw.keyshare_json)
118
+ };
119
+ }
120
+ async runMemberSign(params) {
121
+ const wasm = await this.init();
122
+ if (!wasm.signMember) {
123
+ throw new Error(memberCeremonyFeatureHint("signMember"));
124
+ }
125
+ const raw = await wasm.signMember(
126
+ this.encodeMember(params, {
127
+ roles: params.roles,
128
+ // Mirrors runSign: the wasm deserializes `share` with `serde_json::from_value::<KeyShare>`,
129
+ // so the KeyShare rides the wire as an OBJECT, not a JSON string (and not the storage blob).
130
+ share: toShareObject(params.share),
131
+ participants: params.participants,
132
+ // This device's protocol index into `roles` (the value the SDK calls `signerPosition`).
133
+ // The wire key is `party_index` — the same field keygen sends — NOT `signer_position`.
134
+ party_index: params.signerPosition,
135
+ digest: params.digest
136
+ })
137
+ );
138
+ if (!raw || typeof raw.signature_json !== "string") {
139
+ throw new Error("client-wasm returned an unexpected signMember result");
140
+ }
141
+ return { signature: raw.signature_json };
142
+ }
143
+ async runEddsaKeygen(params) {
144
+ const wasm = await this.init();
145
+ if (!wasm.keygenEddsa) {
146
+ throw new Error(eddsaFeatureHint("keygenEddsa"));
147
+ }
148
+ const raw = await wasm.keygenEddsa(
149
+ this.encodeEddsa(params, {
150
+ threshold: params.threshold,
151
+ // The FROST DKG round-2 encryption roster (#114): every party's X25519 enc PUBLIC key in `roles`
152
+ // order (`enc_pubkeys[i]` ↔ FROST id i+1) + THIS device's own X25519 enc SECRET. `enc_secret` is
153
+ // secret and MUST NOT be logged; the wasm seals/opens the round-2 packages with these.
154
+ enc_pubkeys: params.encPubkeys,
155
+ enc_secret: params.encSecret
156
+ })
157
+ );
158
+ if (!raw || raw.key_package === void 0 || raw.public_key_package === void 0) {
159
+ throw new Error("client-wasm returned an unexpected keygenEddsa result");
160
+ }
161
+ return { keyPackage: raw.key_package, publicKeyPackage: raw.public_key_package };
162
+ }
163
+ async runEddsaSign(params) {
164
+ const wasm = await this.init();
165
+ if (!wasm.signEddsa) {
166
+ throw new Error(eddsaFeatureHint("signEddsa"));
167
+ }
168
+ const raw = await wasm.signEddsa(
169
+ this.encodeEddsa(params, {
170
+ // The wasm re-serializes these with `serde_json::to_vec` + `import_*_package`, so each MUST
171
+ // ride the wire as the FROST JSON OBJECT — not a JSON string (and not a storage wrapper).
172
+ key_package: toFrostPackage(params.keyPackage, "key_package"),
173
+ public_key_package: toFrostPackage(params.publicKeyPackage, "public_key_package"),
174
+ participants: params.participants,
175
+ message: params.message
176
+ })
177
+ );
178
+ if (!raw || typeof raw.signature !== "string") {
179
+ throw new Error("client-wasm returned an unexpected signEddsa result");
180
+ }
181
+ return { signature: raw.signature };
182
+ }
183
+ /** Build the snake_case params JSON the wasm exports expect from the common routing + extras. */
184
+ encode(p, extra) {
185
+ assertAllowedRelayUrl(p.relayUrl);
186
+ return JSON.stringify({
187
+ curve: p.curve,
188
+ relay_url: p.relayUrl,
189
+ session_id: p.sessionId,
190
+ role: p.role ?? "device",
191
+ peer_role: p.peerRole ?? "server",
192
+ party_index: p.partyIndex,
193
+ peer_party_index: p.peerPartyIndex,
194
+ // Only present the relay token when set, so an auth-disabled ceremony's wire payload
195
+ // is byte-identical to before (the client-wasm treats absent as an unauthenticated join).
196
+ ...p.relayToken === void 0 ? {} : { relay_token: p.relayToken },
197
+ ...extra
198
+ });
199
+ }
200
+ /**
201
+ * Build the snake_case params JSON for an n-party (member-bound, #349) member ceremony — the
202
+ * roster-based routing shape (`roles` + `party_index`), not the 2-party `role`/`peer_role` shape
203
+ * {@link encode} builds. The `roles` roster and `party_index` are passed through in `extra` by
204
+ * the caller and land on the wire under those exact keys — what `keygenMember` / `signMember`
205
+ * deserialize.
206
+ */
207
+ encodeMember(p, extra) {
208
+ assertAllowedRelayUrl(p.relayUrl);
209
+ return JSON.stringify({
210
+ curve: p.curve,
211
+ relay_url: p.relayUrl,
212
+ session_id: p.sessionId,
213
+ ...p.relayToken === void 0 ? {} : { relay_token: p.relayToken },
214
+ ...extra
215
+ });
216
+ }
217
+ /**
218
+ * Build the snake_case params JSON for an ed25519 (FROST, #110) ceremony — the roster-based routing
219
+ * shape (`roles` + `party_index`) the `keygenEddsa` / `signEddsa` exports deserialize. Unlike
220
+ * {@link encodeMember} there is NO `curve` (ed25519-only) and no Paillier primes; the caller passes
221
+ * the ceremony-specific extras (keygen: `threshold`; sign: the FROST packages + participants + message).
222
+ */
223
+ encodeEddsa(p, extra) {
224
+ assertAllowedRelayUrl(p.relayUrl);
225
+ return JSON.stringify({
226
+ relay_url: p.relayUrl,
227
+ session_id: p.sessionId,
228
+ roles: p.roles,
229
+ party_index: p.partyIndex,
230
+ ...p.relayToken === void 0 ? {} : { relay_token: p.relayToken },
231
+ ...extra
232
+ });
233
+ }
234
+ };
235
+ function reshareFeatureHint(fn) {
236
+ return `client-wasm does not expose ${fn} \u2014 rebuild @waaskey/client-wasm with the \`reshare\` feature enabled (wasm-pack build \u2026 -- --features reshare).`;
237
+ }
238
+ function memberCeremonyFeatureHint(fn) {
239
+ return `client-wasm does not expose ${fn} \u2014 rebuild @waaskey/client-wasm with n-party (member-ceremony) support (the keygenMember/signMember exports).`;
240
+ }
241
+ function eddsaFeatureHint(fn) {
242
+ return `client-wasm does not expose ${fn} \u2014 rebuild @waaskey/client-wasm with the ed25519 (FROST) exports (keygenEddsa/signEddsa).`;
243
+ }
244
+ function assertAllowedRelayUrl(relayUrl) {
245
+ let url;
246
+ try {
247
+ url = new URL(relayUrl);
248
+ } catch (cause) {
249
+ throw new Error(`Invalid relay URL "${relayUrl}".`, { cause });
250
+ }
251
+ if (url.protocol === "ws:" && isLoopbackHost(url.hostname)) return;
252
+ if (url.protocol !== "wss:") {
253
+ throw new Error(`Refusing server-supplied relay URL "${relayUrl}" \u2014 only wss:// (or ws:// on localhost) is allowed for the MPC transport.`);
254
+ }
255
+ if (isInternalHost(url.hostname)) {
256
+ throw new Error(`Refusing server-supplied relay URL "${relayUrl}" \u2014 the relay host is internal/private; only a public wss:// endpoint is allowed.`);
257
+ }
258
+ }
259
+ function isLoopbackHost(hostname) {
260
+ const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
261
+ return host === "localhost" || host === "::1" || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
262
+ }
263
+ function isInternalHost(hostname) {
264
+ const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
265
+ if (isLoopbackHost(hostname)) return true;
266
+ if (host.endsWith(".internal") || host.endsWith(".local")) return true;
267
+ const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
268
+ if (v4) {
269
+ const [a, b] = [Number(v4[1]), Number(v4[2])];
270
+ if (a === 10) return true;
271
+ if (a === 172 && b >= 16 && b <= 31) return true;
272
+ if (a === 192 && b === 168) return true;
273
+ if (a === 169 && b === 254) return true;
274
+ return false;
275
+ }
276
+ if (host.includes(":")) {
277
+ if (/^f[cd][0-9a-f]{0,2}:/.test(host)) return true;
278
+ if (/^fe[89ab][0-9a-f]:/.test(host)) return true;
279
+ }
280
+ return false;
281
+ }
282
+ function toShareObject(share) {
283
+ const value = typeof share === "string" ? JSON.parse(share) : share;
284
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
285
+ throw new Error("KeyShare must be a JSON object (or its JSON string), not a scalar or array.");
286
+ }
287
+ return value;
288
+ }
289
+ function toFrostPackage(pkg, which) {
290
+ const value = typeof pkg === "string" ? JSON.parse(pkg) : pkg;
291
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
292
+ throw new Error(`FROST ${which} must be a JSON object (or its JSON string), not a scalar or array.`);
293
+ }
294
+ return value;
295
+ }
296
+ function extractSharedPublicKey(keyShareJson) {
297
+ const parsed = JSON.parse(keyShareJson);
298
+ const pk = parsed.core?.shared_public_key;
299
+ if (typeof pk !== "string") {
300
+ throw new Error("KeyShare is missing core.shared_public_key");
301
+ }
302
+ return pk;
303
+ }
304
+ function decodePublicKey(sharedPublicKeyJson) {
305
+ const pk = JSON.parse(sharedPublicKeyJson);
306
+ if (typeof pk !== "string") {
307
+ throw new Error("reshare assemble result is missing a shared_public_key");
308
+ }
309
+ return pk;
310
+ }
311
+
312
+ // src/mpc/node/worker-core.ts
313
+ var WorkerBridge = class {
314
+ wasmPackage;
315
+ pingIntervalMs;
316
+ worker;
317
+ modulePromise;
318
+ nextCallId = 1;
319
+ pending = /* @__PURE__ */ new Map();
320
+ sockets = /* @__PURE__ */ new Map();
321
+ constructor(options) {
322
+ this.wasmPackage = options.wasmPackage ?? "@waaskey/client-wasm";
323
+ this.pingIntervalMs = options.pingIntervalMs ?? 15e3;
324
+ }
325
+ module() {
326
+ return this.modulePromise ??= this.spawn();
327
+ }
328
+ async terminate() {
329
+ for (const [wsId] of this.sockets) this.dropSocket(wsId);
330
+ this.failAllPending(new Error("NodeWorkerMpcCore terminated"));
331
+ const { worker } = this;
332
+ this.worker = void 0;
333
+ this.modulePromise = void 0;
334
+ if (worker) await worker.terminate();
335
+ }
336
+ spawn() {
337
+ const fromSource = import.meta.url.endsWith(".ts");
338
+ const workerUrl = new URL(fromSource ? "./worker.ts" : "./node-mpc-worker.js", import.meta.url);
339
+ const worker = new Worker(workerUrl, { workerData: { wasmPackage: this.wasmPackage }, ...fromSource ? { execArgv: ["--experimental-strip-types"] } : {} });
340
+ this.worker = worker;
341
+ return new Promise((resolve, reject) => {
342
+ let settled = false;
343
+ worker.on("message", (message) => {
344
+ switch (message.type) {
345
+ case "ready":
346
+ settled = true;
347
+ resolve(this.buildProxy(message));
348
+ break;
349
+ case "fatal":
350
+ settled = true;
351
+ reject(new Error(`waaskey mpc worker failed to load ${this.wasmPackage}: ${message.error}`));
352
+ break;
353
+ case "call-result":
354
+ this.settleCall(message);
355
+ break;
356
+ case "ws-open":
357
+ this.openSocket(message);
358
+ break;
359
+ case "ws-send":
360
+ this.sendOnSocket(message);
361
+ break;
362
+ case "ws-close":
363
+ this.closeSocket(message);
364
+ break;
365
+ }
366
+ });
367
+ worker.on("error", (err) => {
368
+ if (!settled) {
369
+ settled = true;
370
+ reject(err);
371
+ }
372
+ this.failAllPending(err);
373
+ });
374
+ worker.on("exit", (code) => {
375
+ const err = new Error(`waaskey mpc worker exited unexpectedly (code ${code})`);
376
+ if (!settled) {
377
+ settled = true;
378
+ reject(err);
379
+ }
380
+ this.failAllPending(err);
381
+ });
382
+ });
383
+ }
384
+ buildProxy(ready) {
385
+ const proxy = {};
386
+ for (const name of ready.exports) {
387
+ proxy[name] = async (paramsJson) => JSON.parse(await this.call(name, paramsJson));
388
+ }
389
+ return proxy;
390
+ }
391
+ call(method, paramsJson) {
392
+ const { worker } = this;
393
+ if (!worker) return Promise.reject(new Error("NodeWorkerMpcCore is terminated"));
394
+ const id = this.nextCallId++;
395
+ return new Promise((resolve, reject) => {
396
+ this.pending.set(id, { resolve, reject });
397
+ worker.postMessage({ type: "call", id, method, paramsJson });
398
+ });
399
+ }
400
+ settleCall(result) {
401
+ const pending = this.pending.get(result.id);
402
+ if (!pending) return;
403
+ this.pending.delete(result.id);
404
+ if (result.ok) pending.resolve(result.resultJson ?? "null");
405
+ else pending.reject(new Error(result.error ?? "mpc worker call failed"));
406
+ }
407
+ failAllPending(err) {
408
+ for (const pending of this.pending.values()) pending.reject(err);
409
+ this.pending.clear();
410
+ }
411
+ openSocket(request) {
412
+ const socket = new WebSocket(request.url, request.protocols);
413
+ const entry = { socket };
414
+ this.sockets.set(request.wsId, entry);
415
+ socket.on("open", () => {
416
+ if (this.pingIntervalMs > 0) {
417
+ entry.pingTimer = setInterval(() => {
418
+ if (socket.readyState === WebSocket.OPEN) socket.ping();
419
+ }, this.pingIntervalMs);
420
+ }
421
+ this.worker?.postMessage({ type: "ws-event", wsId: request.wsId, event: "open", protocol: socket.protocol });
422
+ });
423
+ socket.on("message", (data, isBinary) => {
424
+ const buf = Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data);
425
+ const payload = isBinary ? new Uint8Array(buf) : buf.toString("utf8");
426
+ this.worker?.postMessage({ type: "ws-event", wsId: request.wsId, event: "message", data: payload });
427
+ });
428
+ socket.on("close", (code, reason) => {
429
+ this.dropSocket(request.wsId);
430
+ this.worker?.postMessage({ type: "ws-event", wsId: request.wsId, event: "close", code, reason: reason.toString("utf8"), wasClean: code === 1e3 });
431
+ });
432
+ socket.on("error", (err) => {
433
+ this.worker?.postMessage({ type: "ws-event", wsId: request.wsId, event: "error", message: err.message });
434
+ });
435
+ }
436
+ sendOnSocket(request) {
437
+ this.sockets.get(request.wsId)?.socket.send(request.data);
438
+ }
439
+ closeSocket(request) {
440
+ const entry = this.sockets.get(request.wsId);
441
+ entry?.socket.close(request.code, request.reason);
442
+ }
443
+ dropSocket(wsId) {
444
+ const entry = this.sockets.get(wsId);
445
+ if (!entry) return;
446
+ this.sockets.delete(wsId);
447
+ if (entry.pingTimer) clearInterval(entry.pingTimer);
448
+ if (entry.socket.readyState === WebSocket.OPEN || entry.socket.readyState === WebSocket.CONNECTING) entry.socket.close();
449
+ }
450
+ };
451
+ var NodeWorkerMpcCore = class extends WasmMpcCore {
452
+ bridge;
453
+ constructor(options = {}) {
454
+ const bridge = new WorkerBridge(options);
455
+ super(() => bridge.module());
456
+ this.bridge = bridge;
457
+ }
458
+ /** Close every bridged socket and terminate the worker; the core is unusable afterwards. */
459
+ terminate() {
460
+ return this.bridge.terminate();
461
+ }
462
+ };
463
+
464
+ export { NodeWorkerMpcCore };
465
+ //# sourceMappingURL=node.js.map
466
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mpc/wasm-core.ts","../src/mpc/node/worker-core.ts"],"names":["RealWebSocket"],"mappings":"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","file":"node.js","sourcesContent":["import type {\n DeviceCompleteReshareParams,\n DeviceCompleteReshareResult,\n DeviceKeygenParams,\n DeviceKeygenResult,\n DeviceReshareAssembleParams,\n DeviceReshareAssembleResult,\n DeviceSignParams,\n DeviceSignResult,\n CeremonyParams,\n EddsaCeremonyParams,\n EddsaKeygenParams,\n EddsaKeygenResult,\n EddsaSignParams,\n EddsaSignResult,\n MemberCeremonyParams,\n MemberKeygenParams,\n MemberSignParams,\n MpcCurve,\n MpcCore,\n} from './types';\n\n/** The subset of the client-wasm module the SDK drives. Each export takes one JSON string. */\nexport interface ClientWasmModule {\n /** Device half of keygen; resolves with `{ aux_info_json, keyshare_json, shared_public_key_json }`. */\n keygen(paramsJson: string): Promise<unknown>;\n /** Device half of sign; resolves with `{ signature_json }`. */\n sign(paramsJson: string): Promise<unknown>;\n /** Pre-generate Paillier safe-primes off the hot path; resolves with `{ primes_json }`. Optional. */\n pregeneratePrimes?(paramsJson: string): Promise<unknown>;\n /**\n * Device reshare **assemble** — compute the NEW-epoch bare core locally; returns\n * `{ core_json, shared_public_key_json }`. Present only in a `--features reshare` wasm build.\n * Synchronous in the core (no relay); the SDK awaits it uniformly.\n */\n reshareAssemble?(paramsJson: string): unknown | Promise<unknown>;\n /**\n * Device **complete-reshare** — run the aux ceremony over the new committee and return the\n * signable share as `{ keyshare_json, shared_public_key_json }`. Present only in a\n * `--features reshare` wasm build.\n */\n completeReshare?(paramsJson: string): Promise<unknown>;\n /**\n * This party's half of an n-party (member-bound wallet) keygen ceremony (#349); resolves with\n * `{ aux_info_json, keyshare_json, shared_public_key_json }`. Present only in a client-wasm\n * build with n-party (member-ceremony) support.\n */\n keygenMember?(paramsJson: string): Promise<unknown>;\n /**\n * This party's half of an n-party (member-bound wallet) sign ceremony over a fixed t-of-n\n * quorum (#349); resolves with `{ signature_json }`. Present only in a client-wasm build with\n * n-party (member-ceremony) support.\n */\n signMember?(paramsJson: string): Promise<unknown>;\n /**\n * This device's half of an ed25519 (FROST) DKG keygen ceremony (#110); resolves with\n * `{ key_package, public_key_package }` (JSON objects). Present only in a client-wasm build with\n * the ed25519 (FROST) exports.\n */\n keygenEddsa?(paramsJson: string): Promise<unknown>;\n /**\n * This device's half of an ed25519 (FROST) two-round sign ceremony (#110); resolves with\n * `{ signature }` (64-byte hex). Present only in a client-wasm build with the ed25519 (FROST) exports.\n */\n signEddsa?(paramsJson: string): Promise<unknown>;\n}\n\n/** Lazily loads + initializes the client-wasm module (e.g. dynamic `import()` of the wasm-pack pkg). */\nexport type ClientWasmLoader = () => Promise<ClientWasmModule>;\n\ninterface RawKeygenResult {\n aux_info_json: string;\n keyshare_json: string;\n shared_public_key_json?: string;\n}\n\ninterface RawSignResult {\n signature_json: string;\n}\n\ninterface RawPrimesResult {\n primes_json: string;\n}\n\ninterface RawReshareAssembleResult {\n core_json: string;\n shared_public_key_json: string;\n}\n\ninterface RawCompleteReshareResult {\n keyshare_json: string;\n shared_public_key_json: string;\n}\n\ninterface RawEddsaKeygenResult {\n key_package: unknown;\n public_key_package: unknown;\n}\n\ninterface RawEddsaSignResult {\n signature: string;\n}\n\n/**\n * {@link MpcCore} backed by the waas-core `client-wasm` module (`keygen` / `sign`).\n *\n * The wasm engine is injected via a loader so the SDK stays runtime-agnostic and\n * the core is unit-testable without a real ceremony. The device key share returned\n * by keygen must be sealed and stored on the device — it is never sent to the server.\n */\nexport class WasmMpcCore implements MpcCore {\n private modulePromise?: Promise<ClientWasmModule>;\n\n constructor(private readonly load: ClientWasmLoader) {}\n\n private init(): Promise<ClientWasmModule> {\n return (this.modulePromise ??= this.load());\n }\n\n async runKeygen(params: DeviceKeygenParams): Promise<DeviceKeygenResult> {\n const wasm = await this.init();\n const raw = (await wasm.keygen(\n this.encode(params, { parties: params.parties, threshold: params.threshold, pregenerated_primes_json: params.pregeneratedPrimes }),\n )) as RawKeygenResult;\n\n if (!raw || typeof raw.keyshare_json !== 'string') {\n throw new Error('client-wasm returned an unexpected keygen result');\n }\n return {\n keyShare: raw.keyshare_json,\n auxInfo: raw.aux_info_json,\n sharedPublicKey: extractSharedPublicKey(raw.keyshare_json),\n };\n }\n\n async runSign(params: DeviceSignParams): Promise<DeviceSignResult> {\n const wasm = await this.init();\n const raw = (await wasm.sign(\n this.encode(params, {\n // The wasm deserializes `share` with `serde_json::from_value::<KeyShare>`, so it must ride\n // the wire as the KeyShare OBJECT — not a JSON string, and not the storage blob.\n share: toShareObject(params.share),\n participants: params.participants,\n signer_position: params.signerPosition,\n digest: params.digest,\n }),\n )) as RawSignResult;\n\n if (!raw || typeof raw.signature_json !== 'string') {\n throw new Error('client-wasm returned an unexpected sign result');\n }\n return { signature: raw.signature_json };\n }\n\n async pregeneratePrimes(curve: MpcCurve): Promise<string> {\n const wasm = await this.init();\n if (!wasm.pregeneratePrimes) {\n throw new Error('client-wasm does not expose pregeneratePrimes');\n }\n const raw = (await wasm.pregeneratePrimes(JSON.stringify({ curve }))) as RawPrimesResult;\n if (!raw || typeof raw.primes_json !== 'string') {\n throw new Error('client-wasm returned an unexpected pregeneratePrimes result');\n }\n return raw.primes_json;\n }\n\n async runReshareAssemble(params: DeviceReshareAssembleParams): Promise<DeviceReshareAssembleResult> {\n const wasm = await this.init();\n if (!wasm.reshareAssemble) {\n throw new Error(reshareFeatureHint('reshareAssemble'));\n }\n // Pure + local (no relay), so no relay-URL allowlist here. `reshare_assemble` is synchronous\n // in the core; awaiting a non-promise is a no-op, so this works whether the export is sync or async.\n const raw = (await wasm.reshareAssemble(\n JSON.stringify({\n curve: params.curve,\n new_position: params.newPosition,\n new_preimages: params.newPreimages,\n new_threshold: params.newThreshold,\n wallet: params.wallet,\n commitments: params.commitments,\n sub_shares: params.subShares,\n }),\n )) as RawReshareAssembleResult;\n\n if (!raw || typeof raw.core_json !== 'string') {\n throw new Error('client-wasm returned an unexpected reshareAssemble result');\n }\n return { core: raw.core_json, sharedPublicKey: decodePublicKey(raw.shared_public_key_json) };\n }\n\n async runCompleteReshare(params: DeviceCompleteReshareParams): Promise<DeviceCompleteReshareResult> {\n const wasm = await this.init();\n if (!wasm.completeReshare) {\n throw new Error(reshareFeatureHint('completeReshare'));\n }\n const raw = (await wasm.completeReshare(\n this.encode(params, {\n // The wasm expects the bare core as an inline JSON object (serde_json::Value), not a string.\n core: JSON.parse(params.core),\n parties: params.parties,\n // The device's OWN primes for the aux ceremony — never server-provided. Absent ⇒ generated inline.\n ...(params.pregeneratedPrimes === undefined ? {} : { pregenerated_primes_json: params.pregeneratedPrimes }),\n }),\n )) as RawCompleteReshareResult;\n\n if (!raw || typeof raw.keyshare_json !== 'string') {\n throw new Error('client-wasm returned an unexpected completeReshare result');\n }\n // Derive the public key from the completed share itself (as keygen does), so the invariant\n // check downstream compares the share's own key, not a separately-serialized value.\n return { keyShare: raw.keyshare_json, sharedPublicKey: extractSharedPublicKey(raw.keyshare_json) };\n }\n\n async runMemberKeygen(params: MemberKeygenParams): Promise<DeviceKeygenResult> {\n const wasm = await this.init();\n if (!wasm.keygenMember) {\n throw new Error(memberCeremonyFeatureHint('keygenMember'));\n }\n const raw = (await wasm.keygenMember(\n this.encodeMember(params, {\n roles: params.roles,\n party_index: params.partyIndex,\n threshold: params.threshold,\n ...(params.pregeneratedPrimes === undefined ? {} : { pregenerated_primes_json: params.pregeneratedPrimes }),\n }),\n )) as RawKeygenResult;\n\n if (!raw || typeof raw.keyshare_json !== 'string') {\n throw new Error('client-wasm returned an unexpected keygenMember result');\n }\n return {\n keyShare: raw.keyshare_json,\n auxInfo: raw.aux_info_json,\n sharedPublicKey: extractSharedPublicKey(raw.keyshare_json),\n };\n }\n\n async runMemberSign(params: MemberSignParams): Promise<DeviceSignResult> {\n const wasm = await this.init();\n if (!wasm.signMember) {\n throw new Error(memberCeremonyFeatureHint('signMember'));\n }\n const raw = (await wasm.signMember(\n this.encodeMember(params, {\n roles: params.roles,\n // Mirrors runSign: the wasm deserializes `share` with `serde_json::from_value::<KeyShare>`,\n // so the KeyShare rides the wire as an OBJECT, not a JSON string (and not the storage blob).\n share: toShareObject(params.share),\n participants: params.participants,\n // This device's protocol index into `roles` (the value the SDK calls `signerPosition`).\n // The wire key is `party_index` — the same field keygen sends — NOT `signer_position`.\n party_index: params.signerPosition,\n digest: params.digest,\n }),\n )) as RawSignResult;\n\n if (!raw || typeof raw.signature_json !== 'string') {\n throw new Error('client-wasm returned an unexpected signMember result');\n }\n return { signature: raw.signature_json };\n }\n\n async runEddsaKeygen(params: EddsaKeygenParams): Promise<EddsaKeygenResult> {\n const wasm = await this.init();\n if (!wasm.keygenEddsa) {\n throw new Error(eddsaFeatureHint('keygenEddsa'));\n }\n const raw = (await wasm.keygenEddsa(\n this.encodeEddsa(params, {\n threshold: params.threshold,\n // The FROST DKG round-2 encryption roster (#114): every party's X25519 enc PUBLIC key in `roles`\n // order (`enc_pubkeys[i]` ↔ FROST id i+1) + THIS device's own X25519 enc SECRET. `enc_secret` is\n // secret and MUST NOT be logged; the wasm seals/opens the round-2 packages with these.\n enc_pubkeys: params.encPubkeys,\n enc_secret: params.encSecret,\n }),\n )) as RawEddsaKeygenResult;\n\n if (!raw || raw.key_package === undefined || raw.public_key_package === undefined) {\n throw new Error('client-wasm returned an unexpected keygenEddsa result');\n }\n return { keyPackage: raw.key_package, publicKeyPackage: raw.public_key_package };\n }\n\n async runEddsaSign(params: EddsaSignParams): Promise<EddsaSignResult> {\n const wasm = await this.init();\n if (!wasm.signEddsa) {\n throw new Error(eddsaFeatureHint('signEddsa'));\n }\n const raw = (await wasm.signEddsa(\n this.encodeEddsa(params, {\n // The wasm re-serializes these with `serde_json::to_vec` + `import_*_package`, so each MUST\n // ride the wire as the FROST JSON OBJECT — not a JSON string (and not a storage wrapper).\n key_package: toFrostPackage(params.keyPackage, 'key_package'),\n public_key_package: toFrostPackage(params.publicKeyPackage, 'public_key_package'),\n participants: params.participants,\n message: params.message,\n }),\n )) as RawEddsaSignResult;\n\n if (!raw || typeof raw.signature !== 'string') {\n throw new Error('client-wasm returned an unexpected signEddsa result');\n }\n return { signature: raw.signature };\n }\n\n /** Build the snake_case params JSON the wasm exports expect from the common routing + extras. */\n private encode(p: CeremonyParams, extra: Record<string, unknown>): string {\n // The relay URL is server-supplied and fed straight into the MPC core (issue #41);\n // allowlist its scheme/host so a compromised control-plane can't point the device party\n // at an attacker relay over an unencrypted transport.\n assertAllowedRelayUrl(p.relayUrl);\n return JSON.stringify({\n curve: p.curve,\n relay_url: p.relayUrl,\n session_id: p.sessionId,\n role: p.role ?? 'device',\n peer_role: p.peerRole ?? 'server',\n party_index: p.partyIndex,\n peer_party_index: p.peerPartyIndex,\n // Only present the relay token when set, so an auth-disabled ceremony's wire payload\n // is byte-identical to before (the client-wasm treats absent as an unauthenticated join).\n ...(p.relayToken === undefined ? {} : { relay_token: p.relayToken }),\n ...extra,\n });\n }\n\n /**\n * Build the snake_case params JSON for an n-party (member-bound, #349) member ceremony — the\n * roster-based routing shape (`roles` + `party_index`), not the 2-party `role`/`peer_role` shape\n * {@link encode} builds. The `roles` roster and `party_index` are passed through in `extra` by\n * the caller and land on the wire under those exact keys — what `keygenMember` / `signMember`\n * deserialize.\n */\n private encodeMember(p: MemberCeremonyParams, extra: Record<string, unknown>): string {\n assertAllowedRelayUrl(p.relayUrl);\n return JSON.stringify({\n curve: p.curve,\n relay_url: p.relayUrl,\n session_id: p.sessionId,\n ...(p.relayToken === undefined ? {} : { relay_token: p.relayToken }),\n ...extra,\n });\n }\n\n /**\n * Build the snake_case params JSON for an ed25519 (FROST, #110) ceremony — the roster-based routing\n * shape (`roles` + `party_index`) the `keygenEddsa` / `signEddsa` exports deserialize. Unlike\n * {@link encodeMember} there is NO `curve` (ed25519-only) and no Paillier primes; the caller passes\n * the ceremony-specific extras (keygen: `threshold`; sign: the FROST packages + participants + message).\n */\n private encodeEddsa(p: EddsaCeremonyParams, extra: Record<string, unknown>): string {\n assertAllowedRelayUrl(p.relayUrl);\n return JSON.stringify({\n relay_url: p.relayUrl,\n session_id: p.sessionId,\n roles: p.roles,\n party_index: p.partyIndex,\n ...(p.relayToken === undefined ? {} : { relay_token: p.relayToken }),\n ...extra,\n });\n }\n}\n\n/** Actionable error when a wasm build lacks the reshare exports (built without `--features reshare`). */\nfunction reshareFeatureHint(fn: string): string {\n return `client-wasm does not expose ${fn} — rebuild @waaskey/client-wasm with the \\`reshare\\` feature enabled (wasm-pack build … -- --features reshare).`;\n}\n\n/**\n * Actionable error when a wasm build lacks the n-party (member-bound wallet, #349) member-ceremony\n * exports (`keygenMember` / `signMember`). These drive the roster-based (`roles` + `party_index`)\n * transport — every member device + the platform on one relay session — that the native\n * `party-runner` already uses (mirroring `waas-mpc-transport::PartyRouting`).\n */\nfunction memberCeremonyFeatureHint(fn: string): string {\n return `client-wasm does not expose ${fn} — rebuild @waaskey/client-wasm with n-party (member-ceremony) support (the keygenMember/signMember exports).`;\n}\n\n/**\n * Actionable error when a wasm build lacks the ed25519 (FROST, #110) exports (`keygenEddsa` /\n * `signEddsa`). These drive the threshold-EdDSA choreography the server `party-runner-eddsa` runs,\n * so a device build without them cannot create or sign an ed25519 (Solana/NEAR/Aptos/Sui) wallet.\n */\nfunction eddsaFeatureHint(fn: string): string {\n return `client-wasm does not expose ${fn} — rebuild @waaskey/client-wasm with the ed25519 (FROST) exports (keygenEddsa/signEddsa).`;\n}\n\n/**\n * Allowlist a server-supplied relay URL (issue #41): only encrypted WebSocket\n * transports are accepted — `wss://` to a **public** host, and plaintext `ws://`\n * solely for loopback (local dev). Anything else is refused before it reaches the\n * wasm core.\n *\n * Beyond the scheme, the **host** is allowlisted too: a compromised control-plane\n * must not be able to point the device party at an internal/private endpoint\n * (loopback, RFC1918, link-local incl. cloud metadata `169.254.169.254`, IPv6\n * unique-/link-local, `.internal`/`.local`) — an SSRF-style redirect of the MPC\n * transport. Loopback is the one exception, and only over plaintext `ws://` for dev.\n */\nfunction assertAllowedRelayUrl(relayUrl: string): void {\n let url: URL;\n try {\n url = new URL(relayUrl);\n } catch (cause) {\n throw new Error(`Invalid relay URL \"${relayUrl}\".`, { cause });\n }\n // Dev: plaintext ws:// is tolerated, but only to loopback.\n if (url.protocol === 'ws:' && isLoopbackHost(url.hostname)) return;\n if (url.protocol !== 'wss:') {\n throw new Error(`Refusing server-supplied relay URL \"${relayUrl}\" — only wss:// (or ws:// on localhost) is allowed for the MPC transport.`);\n }\n // wss:// — reject internal/private hosts (loopback included: prod relays are public).\n if (isInternalHost(url.hostname)) {\n throw new Error(`Refusing server-supplied relay URL \"${relayUrl}\" — the relay host is internal/private; only a public wss:// endpoint is allowed.`);\n }\n}\n\n/** Loopback host literals (used to permit ws:// only for local dev). */\nfunction isLoopbackHost(hostname: string): boolean {\n const host = hostname.replace(/^\\[|\\]$/g, '').toLowerCase();\n return host === 'localhost' || host === '::1' || /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(host);\n}\n\n/**\n * Is `hostname` an internal/private address a server must never send a client to?\n * Covers loopback, RFC1918 private v4, link-local v4 (incl. cloud metadata),\n * IPv6 unique-local (`fc00::/7`) + link-local (`fe80::/10`), and internal name\n * suffixes (`.internal`, `.local`). Public DNS names pass.\n */\nfunction isInternalHost(hostname: string): boolean {\n const host = hostname.replace(/^\\[|\\]$/g, '').toLowerCase();\n if (isLoopbackHost(hostname)) return true;\n if (host.endsWith('.internal') || host.endsWith('.local')) return true;\n\n const v4 = host.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (v4) {\n const [a, b] = [Number(v4[1]), Number(v4[2])];\n if (a === 10) return true; // 10.0.0.0/8\n if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12\n if (a === 192 && b === 168) return true; // 192.168.0.0/16\n if (a === 169 && b === 254) return true; // 169.254.0.0/16 link-local (cloud metadata)\n return false;\n }\n\n if (host.includes(':')) {\n // IPv6 unique-local (fc00::/7 → fc/fd) and link-local (fe80::/10).\n if (/^f[cd][0-9a-f]{0,2}:/.test(host)) return true;\n if (/^fe[89ab][0-9a-f]:/.test(host)) return true;\n }\n return false;\n}\n\n/**\n * Coerce a device KeyShare into the inline JSON OBJECT the wasm sign exports expect. Both the\n * 2-party `sign` and the n-party `signMember` exports deserialize the wire `share` with\n * `serde_json::from_value::<KeyShare>`, so it MUST be a JSON object — a `share` sent as a JSON\n * string fails Rust deserialization (\"invalid type: string, expected struct\"). The KeyShare normally\n * arrives as its JSON string ({@link DeviceSignParams.share}); accept an already-parsed object too so\n * a caller can never double-encode it. The storage-blob wrapper is unpacked upstream — the core only\n * ever sees the bare KeyShare.\n */\nfunction toShareObject(share: unknown): Record<string, unknown> {\n const value: unknown = typeof share === 'string' ? JSON.parse(share) : share;\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('KeyShare must be a JSON object (or its JSON string), not a scalar or array.');\n }\n return value as Record<string, unknown>;\n}\n\n/**\n * Coerce a FROST package (`key_package` / `public_key_package`) into the inline JSON OBJECT the wasm\n * `signEddsa` export expects. The wasm re-serializes each with `serde_json::to_vec` + `import_*_package`,\n * so it MUST be a JSON object — the ed25519 analogue of {@link toShareObject}. The package normally\n * arrives as its JSON object (from {@link EddsaKeygenResult}); accept its JSON string too (a\n * storage round-trip) so a caller can never double-encode it.\n */\nfunction toFrostPackage(pkg: unknown, which: string): Record<string, unknown> {\n const value: unknown = typeof pkg === 'string' ? JSON.parse(pkg) : pkg;\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`FROST ${which} must be a JSON object (or its JSON string), not a scalar or array.`);\n }\n return value as Record<string, unknown>;\n}\n\n/** Pull the shared public key out of a KeyShare JSON (`core.shared_public_key`). */\nfunction extractSharedPublicKey(keyShareJson: string): string {\n const parsed = JSON.parse(keyShareJson) as { core?: { shared_public_key?: unknown } };\n const pk = parsed.core?.shared_public_key;\n if (typeof pk !== 'string') {\n throw new Error('KeyShare is missing core.shared_public_key');\n }\n return pk;\n}\n\n/**\n * Decode the wasm's separately-surfaced `shared_public_key_json` (a JSON-encoded compressed-hex\n * point, e.g. `\"03ab…\"`) into the bare hex string. Used for the reshare **assemble** result, whose\n * bare core does not expose `core.shared_public_key` in the keygen shape.\n */\nfunction decodePublicKey(sharedPublicKeyJson: string): string {\n const pk = JSON.parse(sharedPublicKeyJson) as unknown;\n if (typeof pk !== 'string') {\n throw new Error('reshare assemble result is missing a shared_public_key');\n }\n return pk;\n}\n","/**\n * {@link NodeWorkerMpcCore} — the Node fix for waas-sdk#82.\n *\n * In-process (the plain {@link WasmMpcCore} with a Node loader) the wasm ceremony and its\n * relay WebSocket share one thread: the minutes-long zk-proof computation in\n * `aux_info_gen` blocks the loop, the socket can neither read nor answer pings, and an\n * intermediary (Cloudflare / ingress idle timeouts) kills the \"dead\" connection\n * mid-ceremony. This core splits them: the wasm runs in a `worker_threads` Worker behind\n * a bridged `WebSocket` shim, while the REAL socket lives here on the main thread — which\n * stays responsive and sends protocol-level pings (via `ws`) for the whole ceremony.\n *\n * The worker also holds a ref on the event loop, so a pending create/sign can no longer\n * silently exit the process (waas-sdk#84's symptom). Call {@link NodeWorkerMpcCore.terminate}\n * when done — the worker (and the process) stays alive until you do.\n */\nimport { Worker } from 'node:worker_threads';\n\nimport { WebSocket as RealWebSocket } from 'ws';\n\nimport { WasmMpcCore } from '../wasm-core';\n\nimport type { CallResult, FatalMessage, ReadyMessage, WorkerToMain, WsCloseRequest, WsOpenRequest, WsSendRequest } from './bridge-protocol';\nimport type { ClientWasmModule } from '../wasm-core';\n\nexport interface NodeWorkerMpcOptions {\n /**\n * The wasm engine package the worker imports (a bare specifier or file URL).\n * Defaults to `@waaskey/client-wasm`.\n */\n wasmPackage?: string;\n /**\n * Protocol-level ping cadence on each open relay socket, keeping the connection alive\n * through proxies while the worker computes. Default 15s; `0` disables.\n */\n pingIntervalMs?: number;\n}\n\ninterface PendingCall {\n resolve: (resultJson: string) => void;\n reject: (err: Error) => void;\n}\n\ninterface BridgedSocket {\n socket: RealWebSocket;\n pingTimer?: NodeJS.Timeout;\n}\n\n/** Owns the worker + the real sockets; exposes the worker as a {@link ClientWasmModule}. */\nclass WorkerBridge {\n private readonly wasmPackage: string;\n private readonly pingIntervalMs: number;\n private worker?: Worker;\n private modulePromise?: Promise<ClientWasmModule>;\n private nextCallId = 1;\n private readonly pending = new Map<number, PendingCall>();\n private readonly sockets = new Map<number, BridgedSocket>();\n\n constructor(options: NodeWorkerMpcOptions) {\n this.wasmPackage = options.wasmPackage ?? '@waaskey/client-wasm';\n this.pingIntervalMs = options.pingIntervalMs ?? 15_000;\n }\n\n module(): Promise<ClientWasmModule> {\n return (this.modulePromise ??= this.spawn());\n }\n\n async terminate(): Promise<void> {\n for (const [wsId] of this.sockets) this.dropSocket(wsId);\n this.failAllPending(new Error('NodeWorkerMpcCore terminated'));\n const { worker } = this;\n this.worker = undefined;\n this.modulePromise = undefined;\n if (worker) await worker.terminate();\n }\n\n private spawn(): Promise<ClientWasmModule> {\n // In dist the worker is the sibling `node-mpc-worker.js` bundle; when running from\n // source (vitest) it is the sibling `worker.ts`, loaded via Node's type stripping —\n // passed explicitly, since it is only on by default from Node 22.18.\n const fromSource = import.meta.url.endsWith('.ts');\n const workerUrl = new URL(fromSource ? './worker.ts' : './node-mpc-worker.js', import.meta.url);\n const worker = new Worker(workerUrl, { workerData: { wasmPackage: this.wasmPackage }, ...(fromSource ? { execArgv: ['--experimental-strip-types'] } : {}) });\n this.worker = worker;\n\n return new Promise<ClientWasmModule>((resolve, reject) => {\n let settled = false;\n worker.on('message', (message: WorkerToMain) => {\n switch (message.type) {\n case 'ready':\n settled = true;\n resolve(this.buildProxy(message));\n break;\n case 'fatal':\n settled = true;\n reject(new Error(`waaskey mpc worker failed to load ${this.wasmPackage}: ${(message as FatalMessage).error}`));\n break;\n case 'call-result':\n this.settleCall(message);\n break;\n case 'ws-open':\n this.openSocket(message);\n break;\n case 'ws-send':\n this.sendOnSocket(message);\n break;\n case 'ws-close':\n this.closeSocket(message);\n break;\n }\n });\n worker.on('error', (err: Error) => {\n if (!settled) {\n settled = true;\n reject(err);\n }\n this.failAllPending(err);\n });\n worker.on('exit', (code) => {\n const err = new Error(`waaskey mpc worker exited unexpectedly (code ${code})`);\n if (!settled) {\n settled = true;\n reject(err);\n }\n this.failAllPending(err);\n });\n });\n }\n\n private buildProxy(ready: ReadyMessage): ClientWasmModule {\n const proxy: Record<string, (paramsJson: string) => Promise<unknown>> = {};\n for (const name of ready.exports) {\n proxy[name] = async (paramsJson: string) => JSON.parse(await this.call(name, paramsJson)) as unknown;\n }\n return proxy as unknown as ClientWasmModule;\n }\n\n private call(method: string, paramsJson: string): Promise<string> {\n const { worker } = this;\n if (!worker) return Promise.reject(new Error('NodeWorkerMpcCore is terminated'));\n const id = this.nextCallId++;\n return new Promise<string>((resolve, reject) => {\n this.pending.set(id, { resolve, reject });\n worker.postMessage({ type: 'call', id, method, paramsJson });\n });\n }\n\n private settleCall(result: CallResult): void {\n const pending = this.pending.get(result.id);\n if (!pending) return;\n this.pending.delete(result.id);\n if (result.ok) pending.resolve(result.resultJson ?? 'null');\n else pending.reject(new Error(result.error ?? 'mpc worker call failed'));\n }\n\n private failAllPending(err: Error): void {\n for (const pending of this.pending.values()) pending.reject(err);\n this.pending.clear();\n }\n\n private openSocket(request: WsOpenRequest): void {\n const socket = new RealWebSocket(request.url, request.protocols);\n const entry: BridgedSocket = { socket };\n this.sockets.set(request.wsId, entry);\n\n socket.on('open', () => {\n if (this.pingIntervalMs > 0) {\n entry.pingTimer = setInterval(() => {\n if (socket.readyState === RealWebSocket.OPEN) socket.ping();\n }, this.pingIntervalMs);\n }\n this.worker?.postMessage({ type: 'ws-event', wsId: request.wsId, event: 'open', protocol: socket.protocol });\n });\n socket.on('message', (data: Buffer | ArrayBuffer | Buffer[], isBinary: boolean) => {\n const buf = Array.isArray(data) ? Buffer.concat(data) : Buffer.from(data as ArrayBuffer);\n const payload = isBinary ? new Uint8Array(buf) : buf.toString('utf8');\n this.worker?.postMessage({ type: 'ws-event', wsId: request.wsId, event: 'message', data: payload });\n });\n socket.on('close', (code: number, reason: Buffer) => {\n this.dropSocket(request.wsId);\n this.worker?.postMessage({ type: 'ws-event', wsId: request.wsId, event: 'close', code, reason: reason.toString('utf8'), wasClean: code === 1000 });\n });\n socket.on('error', (err: Error) => {\n this.worker?.postMessage({ type: 'ws-event', wsId: request.wsId, event: 'error', message: err.message });\n });\n }\n\n private sendOnSocket(request: WsSendRequest): void {\n this.sockets.get(request.wsId)?.socket.send(request.data);\n }\n\n private closeSocket(request: WsCloseRequest): void {\n const entry = this.sockets.get(request.wsId);\n entry?.socket.close(request.code, request.reason);\n }\n\n private dropSocket(wsId: number): void {\n const entry = this.sockets.get(wsId);\n if (!entry) return;\n this.sockets.delete(wsId);\n if (entry.pingTimer) clearInterval(entry.pingTimer);\n if (entry.socket.readyState === RealWebSocket.OPEN || entry.socket.readyState === RealWebSocket.CONNECTING) entry.socket.close();\n }\n}\n\n/**\n * A drop-in {@link WasmMpcCore} for Node ≥ 22 that runs the wasm ceremony in a worker\n * thread and keeps the relay WebSocket responsive on the main thread (waas-sdk#82).\n *\n * ```ts\n * import { NodeWorkerMpcCore } from '@waaskey/sdk/node';\n * const mpc = new NodeWorkerMpcCore();\n * const waaskey = new Waaskey({ apiKey, mpc, ... });\n * // …create / sign…\n * await mpc.terminate(); // the worker keeps the process alive until terminated\n * ```\n */\nexport class NodeWorkerMpcCore extends WasmMpcCore {\n private readonly bridge: WorkerBridge;\n\n constructor(options: NodeWorkerMpcOptions = {}) {\n const bridge = new WorkerBridge(options);\n super(() => bridge.module());\n this.bridge = bridge;\n }\n\n /** Close every bridged socket and terminate the worker; the core is unusable afterwards. */\n terminate(): Promise<void> {\n return this.bridge.terminate();\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waaskey/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "WAASKey — embedded, non-custodial MPC wallets for your app. Official TypeScript SDK.",
5
5
  "license": "MIT",
6
6
  "author": "Waaskey",
@@ -31,6 +31,10 @@
31
31
  "types": "./dist/index.d.ts",
32
32
  "import": "./dist/index.js",
33
33
  "require": "./dist/index.cjs"
34
+ },
35
+ "./node": {
36
+ "types": "./dist/node.d.ts",
37
+ "import": "./dist/node.js"
34
38
  }
35
39
  },
36
40
  "files": [
@@ -46,10 +50,12 @@
46
50
  "access": "public"
47
51
  },
48
52
  "dependencies": {
49
- "@noble/curves": "^2.2.0"
53
+ "@noble/curves": "^2.2.0",
54
+ "ws": "^8.18.0"
50
55
  },
51
56
  "devDependencies": {
52
57
  "@simplewebauthn/browser": "^13.3.0",
58
+ "@types/ws": "^8.18.0",
53
59
  "@types/node": "^26.1.1",
54
60
  "tsup": "^8.5.1",
55
61
  "typescript": "^6.0.3",