@waaskey/sdk 0.2.0 → 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/index.cjs +140 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -2
- package/dist/index.d.ts +41 -2
- package/dist/index.js +140 -22
- package/dist/index.js.map +1 -1
- package/dist/node-mpc-worker.d.ts +2 -0
- package/dist/node-mpc-worker.js +142 -0
- package/dist/node-mpc-worker.js.map +1 -0
- package/dist/node.d.ts +426 -0
- package/dist/node.js +466 -0
- package/dist/node.js.map +1 -0
- package/package.json +8 -2
package/dist/index.d.cts
CHANGED
|
@@ -212,6 +212,12 @@ interface DeviceKeygenParams extends CeremonyParams {
|
|
|
212
212
|
* this device's private aux material and are NEVER sent to the server.
|
|
213
213
|
*/
|
|
214
214
|
pregeneratedPrimes?: string;
|
|
215
|
+
/**
|
|
216
|
+
* The FULL party roster (`roles[i]` = relay role of protocol index `i`), when the backend
|
|
217
|
+
* provides it (waas-core#131). Required to route a `parties > 2` ceremony — the plain 2-party
|
|
218
|
+
* transport cannot attribute messages from more than one peer.
|
|
219
|
+
*/
|
|
220
|
+
roles?: string[];
|
|
215
221
|
}
|
|
216
222
|
/** Result of the device half of keygen — the device's share never leaves the device. */
|
|
217
223
|
interface DeviceKeygenResult {
|
|
@@ -658,11 +664,21 @@ interface PrimePoolOptions {
|
|
|
658
664
|
targetSize?: number;
|
|
659
665
|
/** Persistence for the pool. Defaults to an in-memory store. */
|
|
660
666
|
store?: PrimePoolStore;
|
|
667
|
+
/**
|
|
668
|
+
* Kick off a refill automatically after every {@link PrimePool.take}. Default `false`:
|
|
669
|
+
* unless the core generates primes in a Web Worker, wasm prime generation is a
|
|
670
|
+
* synchronous CPU burn on the calling thread — a "background" refill starves the event
|
|
671
|
+
* loop and stalls the very keygen ceremony the take() was for (it also freezes a
|
|
672
|
+
* browser UI). Refill explicitly via {@link PrimePool.ensure} (`wallets.prewarm`) at
|
|
673
|
+
* boot/idle, or set `true` only when the core is worker-backed.
|
|
674
|
+
*/
|
|
675
|
+
autoRefill?: boolean;
|
|
661
676
|
}
|
|
662
677
|
declare class PrimePool {
|
|
663
678
|
private readonly core;
|
|
664
679
|
private readonly store;
|
|
665
680
|
private readonly targetSize;
|
|
681
|
+
private readonly autoRefill;
|
|
666
682
|
/** Per-curve in-flight refill, so concurrent calls don't over-generate. */
|
|
667
683
|
private readonly refilling;
|
|
668
684
|
constructor(core: PrimeGenerator, options?: PrimePoolOptions);
|
|
@@ -674,8 +690,9 @@ declare class PrimePool {
|
|
|
674
690
|
private refill;
|
|
675
691
|
/**
|
|
676
692
|
* Claim a prime for a keygen. Returns a cached one instantly when the pool is warm; otherwise
|
|
677
|
-
* generates one inline (the slow fallback) so keygen never fails on an empty pool.
|
|
678
|
-
*
|
|
693
|
+
* generates one inline (the slow fallback) so keygen never fails on an empty pool. Refill the
|
|
694
|
+
* pool for the next wallet via {@link ensure} at idle (or opt into `autoRefill` when the core
|
|
695
|
+
* is worker-backed).
|
|
679
696
|
*/
|
|
680
697
|
take(curve: MpcCurve): Promise<string>;
|
|
681
698
|
}
|
|
@@ -1089,6 +1106,12 @@ interface WalletCeremony {
|
|
|
1089
1106
|
threshold: number;
|
|
1090
1107
|
/** Short-lived relay token (JWT) the device presents to join this keygen session; present only when relay auth is enabled. */
|
|
1091
1108
|
relayToken?: string;
|
|
1109
|
+
/**
|
|
1110
|
+
* The FULL party roster: `roles[i]` is the relay role of protocol index `i` (waas-core#131).
|
|
1111
|
+
* Needed by the routed transport whenever `parties > 2` (e.g. `['device', 'server', 'recovery']`);
|
|
1112
|
+
* older backends omit it — the SDK then reconstructs the standard 3-party topology.
|
|
1113
|
+
*/
|
|
1114
|
+
roles?: string[];
|
|
1092
1115
|
/**
|
|
1093
1116
|
* The FROST DKG round-2 encryption roster — **ed25519 keygen only** (#114, backend `IWalletCeremony`).
|
|
1094
1117
|
* One X25519 encryption PUBLIC key (32-byte hex) per party, in `parties`/protocol-index order, so
|
|
@@ -2082,6 +2105,15 @@ declare class Wallet {
|
|
|
2082
2105
|
* pre-built assertion via `options.passkeyAssertion` (with its `options.passkeyChallengeId`).
|
|
2083
2106
|
*/
|
|
2084
2107
|
sign(digest: string, options?: SignOptions): Promise<string>;
|
|
2108
|
+
/**
|
|
2109
|
+
* Start this device's half of a secp sign ceremony when the wallet's sign quorum requires it.
|
|
2110
|
+
* The quorum is the first `threshold` roles of the wallet's party list (#292) — for the default
|
|
2111
|
+
* `[device, server, recovery]`/2 that is `[device, server]`, so the device MUST be online and
|
|
2112
|
+
* co-signing. Returns `undefined` when the quorum is platform-only (or the wallet is not secp) —
|
|
2113
|
+
* the POST then completes alone, unchanged. A device-present quorum without the device deps or
|
|
2114
|
+
* stored share fails fast with a typed error instead of a guaranteed server-side timeout.
|
|
2115
|
+
*/
|
|
2116
|
+
private startDeviceCoSign;
|
|
2085
2117
|
/**
|
|
2086
2118
|
* Send a transaction from this wallet. The platform builds the chain-specific transaction
|
|
2087
2119
|
* and co-signs it with the 2-of-3 MPC quorum, returning the **signed raw transaction**.
|
|
@@ -2370,6 +2402,13 @@ declare class Wallets {
|
|
|
2370
2402
|
* `[device, user_backup]` ceremony — one place owns the error contract so both paths stay identical.
|
|
2371
2403
|
*/
|
|
2372
2404
|
private runKeygenParties;
|
|
2405
|
+
/**
|
|
2406
|
+
* Run ONE client keygen party over the right transport. A 2-party ceremony uses the plain
|
|
2407
|
+
* single-peer path; anything larger MUST be roster-routed — the 2-party transport attributes
|
|
2408
|
+
* every inbound message to the one configured peer, so a third party's very first message
|
|
2409
|
+
* aborts the protocol with "route received message" (waas-core#131).
|
|
2410
|
+
*/
|
|
2411
|
+
private runKeygenParty;
|
|
2373
2412
|
/**
|
|
2374
2413
|
* Run the device half of an ed25519 (FROST) keygen (#110) and seal the resulting `{keyPackage,
|
|
2375
2414
|
* publicKeyPackage}` share — the EdDSA counterpart of the cggmp24 `runKeygen` branch in {@link create}.
|
package/dist/index.d.ts
CHANGED
|
@@ -212,6 +212,12 @@ interface DeviceKeygenParams extends CeremonyParams {
|
|
|
212
212
|
* this device's private aux material and are NEVER sent to the server.
|
|
213
213
|
*/
|
|
214
214
|
pregeneratedPrimes?: string;
|
|
215
|
+
/**
|
|
216
|
+
* The FULL party roster (`roles[i]` = relay role of protocol index `i`), when the backend
|
|
217
|
+
* provides it (waas-core#131). Required to route a `parties > 2` ceremony — the plain 2-party
|
|
218
|
+
* transport cannot attribute messages from more than one peer.
|
|
219
|
+
*/
|
|
220
|
+
roles?: string[];
|
|
215
221
|
}
|
|
216
222
|
/** Result of the device half of keygen — the device's share never leaves the device. */
|
|
217
223
|
interface DeviceKeygenResult {
|
|
@@ -658,11 +664,21 @@ interface PrimePoolOptions {
|
|
|
658
664
|
targetSize?: number;
|
|
659
665
|
/** Persistence for the pool. Defaults to an in-memory store. */
|
|
660
666
|
store?: PrimePoolStore;
|
|
667
|
+
/**
|
|
668
|
+
* Kick off a refill automatically after every {@link PrimePool.take}. Default `false`:
|
|
669
|
+
* unless the core generates primes in a Web Worker, wasm prime generation is a
|
|
670
|
+
* synchronous CPU burn on the calling thread — a "background" refill starves the event
|
|
671
|
+
* loop and stalls the very keygen ceremony the take() was for (it also freezes a
|
|
672
|
+
* browser UI). Refill explicitly via {@link PrimePool.ensure} (`wallets.prewarm`) at
|
|
673
|
+
* boot/idle, or set `true` only when the core is worker-backed.
|
|
674
|
+
*/
|
|
675
|
+
autoRefill?: boolean;
|
|
661
676
|
}
|
|
662
677
|
declare class PrimePool {
|
|
663
678
|
private readonly core;
|
|
664
679
|
private readonly store;
|
|
665
680
|
private readonly targetSize;
|
|
681
|
+
private readonly autoRefill;
|
|
666
682
|
/** Per-curve in-flight refill, so concurrent calls don't over-generate. */
|
|
667
683
|
private readonly refilling;
|
|
668
684
|
constructor(core: PrimeGenerator, options?: PrimePoolOptions);
|
|
@@ -674,8 +690,9 @@ declare class PrimePool {
|
|
|
674
690
|
private refill;
|
|
675
691
|
/**
|
|
676
692
|
* Claim a prime for a keygen. Returns a cached one instantly when the pool is warm; otherwise
|
|
677
|
-
* generates one inline (the slow fallback) so keygen never fails on an empty pool.
|
|
678
|
-
*
|
|
693
|
+
* generates one inline (the slow fallback) so keygen never fails on an empty pool. Refill the
|
|
694
|
+
* pool for the next wallet via {@link ensure} at idle (or opt into `autoRefill` when the core
|
|
695
|
+
* is worker-backed).
|
|
679
696
|
*/
|
|
680
697
|
take(curve: MpcCurve): Promise<string>;
|
|
681
698
|
}
|
|
@@ -1089,6 +1106,12 @@ interface WalletCeremony {
|
|
|
1089
1106
|
threshold: number;
|
|
1090
1107
|
/** Short-lived relay token (JWT) the device presents to join this keygen session; present only when relay auth is enabled. */
|
|
1091
1108
|
relayToken?: string;
|
|
1109
|
+
/**
|
|
1110
|
+
* The FULL party roster: `roles[i]` is the relay role of protocol index `i` (waas-core#131).
|
|
1111
|
+
* Needed by the routed transport whenever `parties > 2` (e.g. `['device', 'server', 'recovery']`);
|
|
1112
|
+
* older backends omit it — the SDK then reconstructs the standard 3-party topology.
|
|
1113
|
+
*/
|
|
1114
|
+
roles?: string[];
|
|
1092
1115
|
/**
|
|
1093
1116
|
* The FROST DKG round-2 encryption roster — **ed25519 keygen only** (#114, backend `IWalletCeremony`).
|
|
1094
1117
|
* One X25519 encryption PUBLIC key (32-byte hex) per party, in `parties`/protocol-index order, so
|
|
@@ -2082,6 +2105,15 @@ declare class Wallet {
|
|
|
2082
2105
|
* pre-built assertion via `options.passkeyAssertion` (with its `options.passkeyChallengeId`).
|
|
2083
2106
|
*/
|
|
2084
2107
|
sign(digest: string, options?: SignOptions): Promise<string>;
|
|
2108
|
+
/**
|
|
2109
|
+
* Start this device's half of a secp sign ceremony when the wallet's sign quorum requires it.
|
|
2110
|
+
* The quorum is the first `threshold` roles of the wallet's party list (#292) — for the default
|
|
2111
|
+
* `[device, server, recovery]`/2 that is `[device, server]`, so the device MUST be online and
|
|
2112
|
+
* co-signing. Returns `undefined` when the quorum is platform-only (or the wallet is not secp) —
|
|
2113
|
+
* the POST then completes alone, unchanged. A device-present quorum without the device deps or
|
|
2114
|
+
* stored share fails fast with a typed error instead of a guaranteed server-side timeout.
|
|
2115
|
+
*/
|
|
2116
|
+
private startDeviceCoSign;
|
|
2085
2117
|
/**
|
|
2086
2118
|
* Send a transaction from this wallet. The platform builds the chain-specific transaction
|
|
2087
2119
|
* and co-signs it with the 2-of-3 MPC quorum, returning the **signed raw transaction**.
|
|
@@ -2370,6 +2402,13 @@ declare class Wallets {
|
|
|
2370
2402
|
* `[device, user_backup]` ceremony — one place owns the error contract so both paths stay identical.
|
|
2371
2403
|
*/
|
|
2372
2404
|
private runKeygenParties;
|
|
2405
|
+
/**
|
|
2406
|
+
* Run ONE client keygen party over the right transport. A 2-party ceremony uses the plain
|
|
2407
|
+
* single-peer path; anything larger MUST be roster-routed — the 2-party transport attributes
|
|
2408
|
+
* every inbound message to the one configured peer, so a third party's very first message
|
|
2409
|
+
* aborts the protocol with "route received message" (waas-core#131).
|
|
2410
|
+
*/
|
|
2411
|
+
private runKeygenParty;
|
|
2373
2412
|
/**
|
|
2374
2413
|
* Run the device half of an ed25519 (FROST) keygen (#110) and seal the resulting `{keyPackage,
|
|
2375
2414
|
* publicKeyPackage}` share — the EdDSA counterpart of the cggmp24 `runKeygen` branch in {@link create}.
|
package/dist/index.js
CHANGED
|
@@ -910,6 +910,32 @@ function bytesToHex(bytes) {
|
|
|
910
910
|
return hex;
|
|
911
911
|
}
|
|
912
912
|
|
|
913
|
+
// src/share-blob.ts
|
|
914
|
+
function serializeShare(keygen, relayUrl) {
|
|
915
|
+
return JSON.stringify({
|
|
916
|
+
keyShare: keygen.keyShare,
|
|
917
|
+
auxInfo: keygen.auxInfo,
|
|
918
|
+
sharedPublicKey: keygen.sharedPublicKey,
|
|
919
|
+
...relayUrl === void 0 ? {} : { relayUrl }
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function deserializeShare(blob) {
|
|
923
|
+
let parsed;
|
|
924
|
+
try {
|
|
925
|
+
parsed = JSON.parse(blob);
|
|
926
|
+
} catch (cause) {
|
|
927
|
+
throw new WaaskeyError("Stored device share is corrupt \u2014 it is not valid JSON.", "share_not_found", { cause });
|
|
928
|
+
}
|
|
929
|
+
if (typeof parsed.keyShare !== "string") {
|
|
930
|
+
throw new WaaskeyError("Stored device share is malformed \u2014 it is missing its KeyShare.", "share_not_found", { details: { keys: Object.keys(parsed) } });
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
keyShare: parsed.keyShare,
|
|
934
|
+
sharedPublicKey: typeof parsed.sharedPublicKey === "string" ? parsed.sharedPublicKey : void 0,
|
|
935
|
+
relayUrl: typeof parsed.relayUrl === "string" ? parsed.relayUrl : void 0
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
|
|
913
939
|
// src/passkey/assertion.ts
|
|
914
940
|
function isPasskeyAssertionSupported() {
|
|
915
941
|
return typeof globalThis.navigator !== "undefined" && typeof globalThis.navigator.credentials !== "undefined";
|
|
@@ -1026,10 +1052,55 @@ var Wallet = class {
|
|
|
1026
1052
|
const message = normalizeDigest(digest);
|
|
1027
1053
|
const body = { message };
|
|
1028
1054
|
await this.attachStepUp(body, "sign", options);
|
|
1029
|
-
const
|
|
1055
|
+
const coSign = this.startDeviceCoSign(message);
|
|
1056
|
+
const post = this.http.request("POST", `/v1/wallets/${this.id}/sign`, body, options.signal);
|
|
1057
|
+
const [res] = coSign === void 0 ? [await post] : await Promise.all([post, coSign.catch(() => void 0)]);
|
|
1030
1058
|
this.analytics?.track("wallet.signed", { walletId: this.id, curve: this.data.curve });
|
|
1031
1059
|
return res.signature;
|
|
1032
1060
|
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Start this device's half of a secp sign ceremony when the wallet's sign quorum requires it.
|
|
1063
|
+
* The quorum is the first `threshold` roles of the wallet's party list (#292) — for the default
|
|
1064
|
+
* `[device, server, recovery]`/2 that is `[device, server]`, so the device MUST be online and
|
|
1065
|
+
* co-signing. Returns `undefined` when the quorum is platform-only (or the wallet is not secp) —
|
|
1066
|
+
* the POST then completes alone, unchanged. A device-present quorum without the device deps or
|
|
1067
|
+
* stored share fails fast with a typed error instead of a guaranteed server-side timeout.
|
|
1068
|
+
*/
|
|
1069
|
+
startDeviceCoSign(digest) {
|
|
1070
|
+
if (this.data.curve === "ed25519") return void 0;
|
|
1071
|
+
const roles = this.data.parties;
|
|
1072
|
+
const { threshold } = this.data;
|
|
1073
|
+
if (!Array.isArray(roles) || typeof threshold !== "number") return void 0;
|
|
1074
|
+
const quorum = roles.slice(0, threshold);
|
|
1075
|
+
const pos = quorum.indexOf("device");
|
|
1076
|
+
if (pos < 0) return void 0;
|
|
1077
|
+
const { mpc, shareStore } = this.device;
|
|
1078
|
+
if (!mpc || !shareStore) {
|
|
1079
|
+
throw new WaaskeyError(
|
|
1080
|
+
`This wallet's sign quorum [${quorum.join(", ")}] includes the device, so signing requires the device MPC core and share store \u2014 pass \`mpc\` and \`shareStore\` to \`new Waaskey(...)\`.`,
|
|
1081
|
+
"device_core_required"
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
return (async () => {
|
|
1085
|
+
const blob = await shareStore.get(this.id);
|
|
1086
|
+
if (!blob) {
|
|
1087
|
+
throw new WaaskeyError(`No stored device share for wallet ${this.id} \u2014 this device cannot join the sign quorum.`, "share_not_found");
|
|
1088
|
+
}
|
|
1089
|
+
const { keyShare, relayUrl } = deserializeShare(blob);
|
|
1090
|
+
if (!relayUrl) {
|
|
1091
|
+
throw new WaaskeyError("The stored device share predates relay-URL persistence \u2014 re-create the wallet (or recover) to enable device-present signing.", "share_not_found");
|
|
1092
|
+
}
|
|
1093
|
+
const participants = quorum.map((_role, index) => index);
|
|
1094
|
+
const base = { curve: toMpcCurve(this.data.curve), relayUrl, sessionId: this.id, share: keyShare, participants, signerPosition: pos, digest };
|
|
1095
|
+
if (quorum.length === 2) {
|
|
1096
|
+
return mpc.runSign({ ...base, role: quorum[pos], peerRole: quorum[1 - pos], partyIndex: pos, peerPartyIndex: 1 - pos });
|
|
1097
|
+
}
|
|
1098
|
+
if (!mpc.runMemberSign) {
|
|
1099
|
+
throw new WaaskeyError(`This wallet's sign quorum has ${quorum.length} parties, which needs an MPC core with routed (member-ceremony) sign support.`, "unsupported");
|
|
1100
|
+
}
|
|
1101
|
+
return mpc.runMemberSign({ ...base, roles: quorum });
|
|
1102
|
+
})();
|
|
1103
|
+
}
|
|
1033
1104
|
/**
|
|
1034
1105
|
* Send a transaction from this wallet. The platform builds the chain-specific transaction
|
|
1035
1106
|
* and co-signs it with the 2-of-3 MPC quorum, returning the **signed raw transaction**.
|
|
@@ -1491,7 +1562,7 @@ var Wallets = class {
|
|
|
1491
1562
|
const pregeneratedPrimes = primePool ? await primePool.take(curve) : void 0;
|
|
1492
1563
|
throwIfAborted(signal);
|
|
1493
1564
|
const [keygen] = await this.runKeygenParties(mpc, [{ ...ceremony, curve, pregeneratedPrimes }]);
|
|
1494
|
-
await shareStore.put(walletId, serializeShare(keygen));
|
|
1565
|
+
await shareStore.put(walletId, serializeShare(keygen, ceremony.relayUrl));
|
|
1495
1566
|
return;
|
|
1496
1567
|
}
|
|
1497
1568
|
if (!backup) {
|
|
@@ -1506,7 +1577,7 @@ var Wallets = class {
|
|
|
1506
1577
|
{ ...ceremony, curve, pregeneratedPrimes: devicePrimes },
|
|
1507
1578
|
{ ...userBackupParty, curve, pregeneratedPrimes: backupPrimes }
|
|
1508
1579
|
]);
|
|
1509
|
-
await shareStore.put(walletId, serializeShare(deviceKeygen));
|
|
1580
|
+
await shareStore.put(walletId, serializeShare(deviceKeygen, ceremony.relayUrl));
|
|
1510
1581
|
throwIfAborted(signal);
|
|
1511
1582
|
const { payload } = await buildRecoveryRegistration({ share: serializeShare(backupKeygen), ...backup });
|
|
1512
1583
|
await shareStore.put(userBackupPendingKey(walletId), JSON.stringify(payload));
|
|
@@ -1651,12 +1722,39 @@ var Wallets = class {
|
|
|
1651
1722
|
*/
|
|
1652
1723
|
async runKeygenParties(mpc, params) {
|
|
1653
1724
|
try {
|
|
1654
|
-
return await Promise.all(params.map((party) =>
|
|
1725
|
+
return await Promise.all(params.map((party) => this.runKeygenParty(mpc, party, params)));
|
|
1655
1726
|
} catch (cause) {
|
|
1656
1727
|
if (cause instanceof WaaskeyError) throw cause;
|
|
1657
1728
|
throw new WaaskeyError("The device keygen ceremony failed.", "keygen_failed", { cause });
|
|
1658
1729
|
}
|
|
1659
1730
|
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Run ONE client keygen party over the right transport. A 2-party ceremony uses the plain
|
|
1733
|
+
* single-peer path; anything larger MUST be roster-routed — the 2-party transport attributes
|
|
1734
|
+
* every inbound message to the one configured peer, so a third party's very first message
|
|
1735
|
+
* aborts the protocol with "route received message" (waas-core#131).
|
|
1736
|
+
*/
|
|
1737
|
+
runKeygenParty(mpc, party, batch) {
|
|
1738
|
+
if (party.parties <= 2) {
|
|
1739
|
+
return mpc.runKeygen(party);
|
|
1740
|
+
}
|
|
1741
|
+
if (!mpc.runMemberKeygen) {
|
|
1742
|
+
throw new WaaskeyError(
|
|
1743
|
+
`This keygen ceremony has ${party.parties} parties, which needs an MPC core with routed (member-ceremony) keygen support \u2014 use a client-wasm build exposing keygenMember.`,
|
|
1744
|
+
"unsupported"
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
return mpc.runMemberKeygen({
|
|
1748
|
+
curve: party.curve,
|
|
1749
|
+
relayUrl: party.relayUrl,
|
|
1750
|
+
sessionId: party.sessionId,
|
|
1751
|
+
roles: ceremonyRoster(party, batch),
|
|
1752
|
+
partyIndex: party.partyIndex,
|
|
1753
|
+
threshold: party.threshold,
|
|
1754
|
+
relayToken: party.relayToken,
|
|
1755
|
+
pregeneratedPrimes: party.pregeneratedPrimes
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1660
1758
|
/**
|
|
1661
1759
|
* Run the device half of an ed25519 (FROST) keygen (#110) and seal the resulting `{keyPackage,
|
|
1662
1760
|
* publicKeyPackage}` share — the EdDSA counterpart of the cggmp24 `runKeygen` branch in {@link create}.
|
|
@@ -1727,21 +1825,6 @@ var Wallets = class {
|
|
|
1727
1825
|
}
|
|
1728
1826
|
}
|
|
1729
1827
|
};
|
|
1730
|
-
function serializeShare(keygen) {
|
|
1731
|
-
return JSON.stringify({ keyShare: keygen.keyShare, auxInfo: keygen.auxInfo, sharedPublicKey: keygen.sharedPublicKey });
|
|
1732
|
-
}
|
|
1733
|
-
function deserializeShare(blob) {
|
|
1734
|
-
let parsed;
|
|
1735
|
-
try {
|
|
1736
|
-
parsed = JSON.parse(blob);
|
|
1737
|
-
} catch (cause) {
|
|
1738
|
-
throw new WaaskeyError("Stored device share is corrupt \u2014 it is not valid JSON.", "share_not_found", { cause });
|
|
1739
|
-
}
|
|
1740
|
-
if (typeof parsed.keyShare !== "string") {
|
|
1741
|
-
throw new WaaskeyError("Stored device share is malformed \u2014 it is missing its KeyShare.", "share_not_found", { details: { keys: Object.keys(parsed) } });
|
|
1742
|
-
}
|
|
1743
|
-
return { keyShare: parsed.keyShare, sharedPublicKey: typeof parsed.sharedPublicKey === "string" ? parsed.sharedPublicKey : void 0 };
|
|
1744
|
-
}
|
|
1745
1828
|
async function restoreUserBackupShare(ciphertext, recoveryCode) {
|
|
1746
1829
|
let blob;
|
|
1747
1830
|
try {
|
|
@@ -1799,6 +1882,38 @@ function membershipIdFromRole(role) {
|
|
|
1799
1882
|
}
|
|
1800
1883
|
return role.slice(prefix.length);
|
|
1801
1884
|
}
|
|
1885
|
+
function ceremonyRoster(party, batch) {
|
|
1886
|
+
if (party.roles) {
|
|
1887
|
+
if (party.roles.length !== party.parties) {
|
|
1888
|
+
throw new WaaskeyError(`The ceremony roster names ${party.roles.length} parties but the ceremony has ${party.parties}.`, "validation");
|
|
1889
|
+
}
|
|
1890
|
+
return party.roles;
|
|
1891
|
+
}
|
|
1892
|
+
const roles = new Array(party.parties).fill(void 0);
|
|
1893
|
+
const put = (index, role) => {
|
|
1894
|
+
if (role === void 0) return;
|
|
1895
|
+
if (index < 0 || index >= party.parties) {
|
|
1896
|
+
throw new WaaskeyError(`Ceremony party index ${index} is out of range for a ${party.parties}-party ceremony.`, "validation");
|
|
1897
|
+
}
|
|
1898
|
+
if (roles[index] !== void 0 && roles[index] !== role) {
|
|
1899
|
+
throw new WaaskeyError(`Conflicting ceremony roles for party index ${index}: "${roles[index]}" vs "${role}".`, "validation");
|
|
1900
|
+
}
|
|
1901
|
+
roles[index] = role;
|
|
1902
|
+
};
|
|
1903
|
+
for (const entry of batch) {
|
|
1904
|
+
put(entry.partyIndex, entry.role ?? "device");
|
|
1905
|
+
put(entry.peerPartyIndex, entry.peerRole ?? "server");
|
|
1906
|
+
}
|
|
1907
|
+
const gaps = roles.reduce((acc, role, index) => role === void 0 ? [...acc, index] : acc, []);
|
|
1908
|
+
if (gaps.length > 1) {
|
|
1909
|
+
throw new WaaskeyError(
|
|
1910
|
+
`The ${party.parties}-party keygen ceremony leaves ${gaps.length} party slots unnamed \u2014 this backend must supply the full ceremony roster (roles).`,
|
|
1911
|
+
"validation"
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
if (gaps.length === 1) roles[gaps[0]] = "recovery";
|
|
1915
|
+
return roles;
|
|
1916
|
+
}
|
|
1802
1917
|
function buildMemberRoster(shareholders, parties) {
|
|
1803
1918
|
const roles = new Array(parties).fill(void 0);
|
|
1804
1919
|
for (const shareholder of shareholders) {
|
|
@@ -2298,10 +2413,12 @@ var PrimePool = class {
|
|
|
2298
2413
|
this.core = core;
|
|
2299
2414
|
this.store = options.store ?? new MemoryPrimeStore();
|
|
2300
2415
|
this.targetSize = Math.max(1, options.targetSize ?? 2);
|
|
2416
|
+
this.autoRefill = options.autoRefill ?? false;
|
|
2301
2417
|
}
|
|
2302
2418
|
core;
|
|
2303
2419
|
store;
|
|
2304
2420
|
targetSize;
|
|
2421
|
+
autoRefill;
|
|
2305
2422
|
/** Per-curve in-flight refill, so concurrent calls don't over-generate. */
|
|
2306
2423
|
refilling = /* @__PURE__ */ new Map();
|
|
2307
2424
|
/**
|
|
@@ -2322,13 +2439,14 @@ var PrimePool = class {
|
|
|
2322
2439
|
}
|
|
2323
2440
|
/**
|
|
2324
2441
|
* Claim a prime for a keygen. Returns a cached one instantly when the pool is warm; otherwise
|
|
2325
|
-
* generates one inline (the slow fallback) so keygen never fails on an empty pool.
|
|
2326
|
-
*
|
|
2442
|
+
* generates one inline (the slow fallback) so keygen never fails on an empty pool. Refill the
|
|
2443
|
+
* pool for the next wallet via {@link ensure} at idle (or opt into `autoRefill` when the core
|
|
2444
|
+
* is worker-backed).
|
|
2327
2445
|
*/
|
|
2328
2446
|
async take(curve) {
|
|
2329
2447
|
const cached = await this.store.take(curve);
|
|
2330
2448
|
const primes = cached ?? await this.core.pregeneratePrimes(curve);
|
|
2331
|
-
void this.ensure(curve).catch(() => void 0);
|
|
2449
|
+
if (this.autoRefill) void this.ensure(curve).catch(() => void 0);
|
|
2332
2450
|
return primes;
|
|
2333
2451
|
}
|
|
2334
2452
|
};
|