@waaskey/sdk 0.3.1 → 0.3.2
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 +211 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -31
- package/dist/index.d.ts +90 -31
- package/dist/index.js +211 -66
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +5 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -628,7 +628,7 @@ async function buildRecoveryRegistration(params) {
|
|
|
628
628
|
const recoveryCode = params.recoveryCode ?? generateRecoveryCode();
|
|
629
629
|
const ciphertext = await sealWithPassword(recoveryCode, params.share);
|
|
630
630
|
const factors = [
|
|
631
|
-
{ type: "recovery_code",
|
|
631
|
+
{ type: "recovery_code", credential: await sha256Hex(recoveryCode) },
|
|
632
632
|
{ type: "totp", credential: params.totpSecret },
|
|
633
633
|
{ type: "email_otp", credential: params.email },
|
|
634
634
|
...params.extraFactors ?? []
|
|
@@ -656,7 +656,7 @@ async function registerRecoveryShare(http, walletId, params, signal) {
|
|
|
656
656
|
return { recoveryCode, share };
|
|
657
657
|
}
|
|
658
658
|
async function hashRecoveryFactors(verifications) {
|
|
659
|
-
return Promise.all(verifications.map(async (v) => v.type === "recovery_code" && v.token
|
|
659
|
+
return Promise.all(verifications.map(async (v) => v.type === "recovery_code" && typeof v.token === "string" ? { type: v.type, token: await sha256Hex(v.token) } : v));
|
|
660
660
|
}
|
|
661
661
|
function generateRecoveryCode() {
|
|
662
662
|
const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
@@ -912,6 +912,51 @@ function bytesToHex(bytes) {
|
|
|
912
912
|
return hex;
|
|
913
913
|
}
|
|
914
914
|
|
|
915
|
+
// src/session-sign.ts
|
|
916
|
+
function toDeviceSignParams(ceremony, share, digest) {
|
|
917
|
+
const { roles, participants, signerPosition } = ceremony;
|
|
918
|
+
if (participants.length !== 2 || roles.length !== 2 || signerPosition < 0 || signerPosition > 1) {
|
|
919
|
+
throw new WaaskeyError(
|
|
920
|
+
`Unexpected sign descriptor: the quorum must be exactly 2 parties with signerPosition in {0, 1} (got ${participants.length} participants, ${roles.length} roles, signerPosition ${signerPosition}).`,
|
|
921
|
+
"sign_failed",
|
|
922
|
+
{ details: { roles, participants, signerPosition } }
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
const peerPosition = 1 - signerPosition;
|
|
926
|
+
return {
|
|
927
|
+
curve: toMpcCurve(ceremony.curve),
|
|
928
|
+
relayUrl: ceremony.relayUrl,
|
|
929
|
+
sessionId: ceremony.sessionId,
|
|
930
|
+
role: roles[signerPosition],
|
|
931
|
+
peerRole: roles[peerPosition],
|
|
932
|
+
partyIndex: signerPosition,
|
|
933
|
+
peerPartyIndex: peerPosition,
|
|
934
|
+
relayToken: ceremony.relayToken,
|
|
935
|
+
share,
|
|
936
|
+
participants,
|
|
937
|
+
signerPosition,
|
|
938
|
+
digest
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function toSessionSignParams(session, share, digest) {
|
|
942
|
+
return toDeviceSignParams(
|
|
943
|
+
{
|
|
944
|
+
curve: session.curve,
|
|
945
|
+
relayUrl: session.relayUrl,
|
|
946
|
+
sessionId: session.sessionId,
|
|
947
|
+
roles: sessionRoles(session),
|
|
948
|
+
participants: session.participants,
|
|
949
|
+
signerPosition: session.signerPosition,
|
|
950
|
+
relayToken: session.relayToken
|
|
951
|
+
},
|
|
952
|
+
share,
|
|
953
|
+
digest
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
function sessionRoles(session) {
|
|
957
|
+
return session.signerPosition === 0 ? [session.role, session.peerRole] : [session.peerRole, session.role];
|
|
958
|
+
}
|
|
959
|
+
|
|
915
960
|
// src/share-blob.ts
|
|
916
961
|
function serializeShare(keygen, relayUrl) {
|
|
917
962
|
return JSON.stringify({
|
|
@@ -978,6 +1023,7 @@ async function getSigningAssertion(challenge, options = {}) {
|
|
|
978
1023
|
}
|
|
979
1024
|
|
|
980
1025
|
// src/wallet.ts
|
|
1026
|
+
var DEVICE_ROLE = "device";
|
|
981
1027
|
var Wallet = class {
|
|
982
1028
|
constructor(http, data, analytics, device = {}) {
|
|
983
1029
|
this.http = http;
|
|
@@ -1052,56 +1098,106 @@ var Wallet = class {
|
|
|
1052
1098
|
*/
|
|
1053
1099
|
async sign(digest, options = {}) {
|
|
1054
1100
|
const message = normalizeDigest(digest);
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1101
|
+
const quorum = this.signQuorum();
|
|
1102
|
+
const devicePosition = quorum?.indexOf(DEVICE_ROLE) ?? -1;
|
|
1103
|
+
if (devicePosition < 0) {
|
|
1104
|
+
const body = { message };
|
|
1105
|
+
await this.attachStepUp(body, "sign", options);
|
|
1106
|
+
const res = await this.http.request("POST", `/v1/wallets/${this.id}/sign`, body, options.signal);
|
|
1107
|
+
this.analytics?.track("wallet.signed", { walletId: this.id, curve: this.data.curve });
|
|
1108
|
+
return res.signature;
|
|
1109
|
+
}
|
|
1110
|
+
const { mpc, keyShare } = await this.loadDeviceParty(quorum, "signing");
|
|
1111
|
+
const startBody = { digest: message };
|
|
1112
|
+
await this.attachStepUp(startBody, "sign", options);
|
|
1113
|
+
const session = await this.http.request("POST", `/v1/wallets/${this.id}/sign-session`, startBody, options.signal);
|
|
1114
|
+
const { signature } = await this.runDeviceSign(mpc, this.toSignCeremony(session, quorum), keyShare, message);
|
|
1060
1115
|
this.analytics?.track("wallet.signed", { walletId: this.id, curve: this.data.curve });
|
|
1061
|
-
return
|
|
1116
|
+
return signature;
|
|
1062
1117
|
}
|
|
1063
1118
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
* co-signing. Returns `undefined` when the quorum is platform-only (or the wallet is not secp) —
|
|
1068
|
-
* the POST then completes alone, unchanged. A device-present quorum without the device deps or
|
|
1069
|
-
* stored share fails fast with a typed error instead of a guaranteed server-side timeout.
|
|
1119
|
+
* The roles that will actually sign — the first `threshold` of the wallet's party list (#292),
|
|
1120
|
+
* mirroring the signer's own derivation. `undefined` when the topology is unknown (a legacy
|
|
1121
|
+
* wallet record), which callers treat as "let the server decide".
|
|
1070
1122
|
*/
|
|
1071
|
-
|
|
1123
|
+
signQuorum() {
|
|
1072
1124
|
if (this.data.curve === "ed25519") return void 0;
|
|
1073
1125
|
const roles = this.data.parties;
|
|
1074
1126
|
const { threshold } = this.data;
|
|
1075
1127
|
if (!Array.isArray(roles) || typeof threshold !== "number") return void 0;
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1128
|
+
return roles.slice(0, threshold);
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Everything this device needs to join a ceremony, resolved BEFORE one is started (#89): the MPC
|
|
1132
|
+
* core, the routed-sign capability a >2-party quorum needs, and the stored key share.
|
|
1133
|
+
*
|
|
1134
|
+
* Each failure is a typed error thrown straight to the caller, and the ordering is the point: a
|
|
1135
|
+
* device that cannot co-sign must never leave the platform party waiting on the relay for a
|
|
1136
|
+
* counterpart that will never arrive. That wait ends at the party-runner timeout (~210s) and
|
|
1137
|
+
* reaches the caller as an opaque 5xx — minutes after a knowable, local cause.
|
|
1138
|
+
*/
|
|
1139
|
+
async loadDeviceParty(quorum, action) {
|
|
1079
1140
|
const { mpc, shareStore } = this.device;
|
|
1080
1141
|
if (!mpc || !shareStore) {
|
|
1081
1142
|
throw new WaaskeyError(
|
|
1082
|
-
`This wallet's
|
|
1143
|
+
`This wallet's signing quorum [${quorum.join(", ")}] includes the device, so ${action} requires the device MPC core and share store \u2014 pass \`mpc\` and \`shareStore\` to \`new Waaskey(...)\`.`,
|
|
1083
1144
|
"device_core_required"
|
|
1084
1145
|
);
|
|
1085
1146
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1147
|
+
if (quorum.length > 2 && !mpc.runMemberSign) {
|
|
1148
|
+
throw new WaaskeyError(`This wallet's sign quorum has ${quorum.length} parties, which needs an MPC core with routed (member-ceremony) sign support.`, "unsupported");
|
|
1149
|
+
}
|
|
1150
|
+
const blob = await shareStore.get(this.id);
|
|
1151
|
+
if (!blob) {
|
|
1152
|
+
throw new WaaskeyError(`No stored device share for wallet ${this.id} \u2014 this device cannot join the signing quorum.`, "share_not_found");
|
|
1153
|
+
}
|
|
1154
|
+
const { keyShare } = deserializeShare(blob);
|
|
1155
|
+
return { mpc, keyShare };
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Normalize a sign-session descriptor into the shared {@link DeviceCeremony}. The 2-party roster
|
|
1159
|
+
* comes from the descriptor's own `role`/`peerRole` (authoritative — the relay token is bound to
|
|
1160
|
+
* that role); a larger quorum carries no server-sent roster, so the wallet's own party slice — the
|
|
1161
|
+
* same slice the signer derives — supplies it.
|
|
1162
|
+
*/
|
|
1163
|
+
toSignCeremony(session, quorum) {
|
|
1164
|
+
return {
|
|
1165
|
+
curve: session.curve,
|
|
1166
|
+
relayUrl: session.relayUrl,
|
|
1167
|
+
sessionId: session.sessionId,
|
|
1168
|
+
roles: session.participants.length === 2 ? sessionRoles(session) : quorum,
|
|
1169
|
+
participants: session.participants,
|
|
1170
|
+
signerPosition: session.signerPosition,
|
|
1171
|
+
relayToken: session.relayToken
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Run this device's half of a started ceremony. A 2-party quorum uses the plain single-peer
|
|
1176
|
+
* transport; anything larger MUST be roster-routed, or the transport attributes every inbound
|
|
1177
|
+
* message to the one configured peer and the protocol aborts on the third party's first message
|
|
1178
|
+
* (waas-core#131).
|
|
1179
|
+
*/
|
|
1180
|
+
runDeviceSign(mpc, ceremony, keyShare, digest) {
|
|
1181
|
+
if (ceremony.participants.length === 2) {
|
|
1182
|
+
return mpc.runSign(toDeviceSignParams(ceremony, keyShare, digest));
|
|
1183
|
+
}
|
|
1184
|
+
if (!mpc.runMemberSign) {
|
|
1185
|
+
throw new WaaskeyError(
|
|
1186
|
+
`This wallet's sign quorum has ${ceremony.participants.length} parties, which needs an MPC core with routed (member-ceremony) sign support.`,
|
|
1187
|
+
"unsupported"
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
return mpc.runMemberSign({
|
|
1191
|
+
curve: toMpcCurve(ceremony.curve),
|
|
1192
|
+
relayUrl: ceremony.relayUrl,
|
|
1193
|
+
sessionId: ceremony.sessionId,
|
|
1194
|
+
relayToken: ceremony.relayToken,
|
|
1195
|
+
roles: ceremony.roles,
|
|
1196
|
+
share: keyShare,
|
|
1197
|
+
participants: ceremony.participants,
|
|
1198
|
+
signerPosition: ceremony.signerPosition,
|
|
1199
|
+
digest
|
|
1200
|
+
});
|
|
1105
1201
|
}
|
|
1106
1202
|
/**
|
|
1107
1203
|
* Send a transaction from this wallet. The platform builds the chain-specific transaction
|
|
@@ -1121,19 +1217,60 @@ var Wallet = class {
|
|
|
1121
1217
|
if (this.data.curve === "ed25519") {
|
|
1122
1218
|
return this.sendEd25519(params, options);
|
|
1123
1219
|
}
|
|
1220
|
+
const quorum = this.signQuorum();
|
|
1221
|
+
if ((quorum?.indexOf(DEVICE_ROLE) ?? -1) >= 0) {
|
|
1222
|
+
return this.sendWithDevice(params, options, quorum);
|
|
1223
|
+
}
|
|
1124
1224
|
const body = { ...params };
|
|
1125
1225
|
await this.attachStepUp(body, "send", options);
|
|
1126
1226
|
const res = await this.http.request("POST", `/v1/wallets/${this.id}/send`, body, options.signal);
|
|
1127
1227
|
this.analytics?.track("wallet.sent", { walletId: this.id, chain: params.chainId });
|
|
1128
1228
|
return res;
|
|
1129
1229
|
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Device-co-signed send for a secp wallet — the cggmp24 counterpart of {@link sendEd25519}:
|
|
1232
|
+
*
|
|
1233
|
+
* 1. **START** (`POST …/send-session`): the platform runs the transfer gates, builds the unsigned
|
|
1234
|
+
* tx, puts its own party on the relay in the background, and returns the 32-byte digest plus
|
|
1235
|
+
* the relay coordination. It does NOT wait for the ceremony.
|
|
1236
|
+
* 2. **CO-SIGN**: this device runs its half over the relay with its stored share; cggmp24 hands
|
|
1237
|
+
* the completed signature to both parties.
|
|
1238
|
+
* 3. **ASSEMBLE** (`POST …/send-session/:txId/assemble`): the platform verifies the signature
|
|
1239
|
+
* (recovers to the wallet key AND equals its own party's) and embeds it into the wire tx.
|
|
1240
|
+
*
|
|
1241
|
+
* Every device-side precondition is resolved BEFORE the START (see {@link loadDeviceParty}), so a
|
|
1242
|
+
* device that cannot co-sign costs nothing: no tx is built and no platform party is left waiting.
|
|
1243
|
+
*
|
|
1244
|
+
* **A co-sign that fails after a successful START rejects with its typed error.** START is not the
|
|
1245
|
+
* commit point — it yields an *unsigned* tx and a pending session, and nothing broadcastable exists
|
|
1246
|
+
* until ASSEMBLE returns a `signedTx` — so there is no result to salvage by swallowing the failure,
|
|
1247
|
+
* and no fallback to `POST /send` (the platform cannot reach the threshold on this wallet alone, so
|
|
1248
|
+
* a retry there would only hang). The backend expires the abandoned session and fails the tx row.
|
|
1249
|
+
*/
|
|
1250
|
+
async sendWithDevice(params, options, quorum) {
|
|
1251
|
+
const { signal } = options;
|
|
1252
|
+
const { mpc, keyShare } = await this.loadDeviceParty(quorum, "sending");
|
|
1253
|
+
throwIfAborted(signal);
|
|
1254
|
+
const startBody = { ...params };
|
|
1255
|
+
await this.attachStepUp(startBody, "send", options);
|
|
1256
|
+
throwIfAborted(signal);
|
|
1257
|
+
const session = await this.http.request("POST", `/v1/wallets/${this.id}/send-session`, startBody, signal);
|
|
1258
|
+
const digest = sendPayload(session, "digest");
|
|
1259
|
+
throwIfAborted(signal);
|
|
1260
|
+
const { signature } = await this.runDeviceSign(mpc, toSendCeremony(session), keyShare, digest);
|
|
1261
|
+
throwIfAborted(signal);
|
|
1262
|
+
const assemble = { signature };
|
|
1263
|
+
const res = await this.http.request("POST", `/v1/wallets/${this.id}/send-session/${session.txId}/assemble`, assemble, signal);
|
|
1264
|
+
this.analytics?.track("wallet.sent", { walletId: this.id, chain: params.chainId });
|
|
1265
|
+
return res;
|
|
1266
|
+
}
|
|
1130
1267
|
/**
|
|
1131
1268
|
* Device-co-signed send for an ed25519 (FROST) wallet (#110) — the browser holds the device FROST
|
|
1132
1269
|
* share and co-signs 2-party with the backend `server` party over the relay:
|
|
1133
1270
|
*
|
|
1134
1271
|
* 1. **START** (`POST …/send-session`): the backend builds the unsigned tx (chain adapter), starts
|
|
1135
1272
|
* its server FROST party on the relay in the background, and returns the raw `message` bytes to
|
|
1136
|
-
* sign + the relay coordination ({@link
|
|
1273
|
+
* sign + the relay coordination ({@link SendSessionResponse}).
|
|
1137
1274
|
* 2. **CO-SIGN**: the device runs `signEddsa` over the relay with its stored `{keyPackage,
|
|
1138
1275
|
* publicKeyPackage}` share; the two parties aggregate the RFC 8032 signature (returned locally).
|
|
1139
1276
|
* 3. **ASSEMBLE** (`POST …/send-session/:txId/assemble`): the backend embeds the aggregated
|
|
@@ -1168,6 +1305,7 @@ var Wallet = class {
|
|
|
1168
1305
|
await this.attachStepUp(startBody, "send", options);
|
|
1169
1306
|
throwIfAborted(signal);
|
|
1170
1307
|
const session = await this.http.request("POST", `/v1/wallets/${this.id}/send-session`, startBody, signal);
|
|
1308
|
+
const message = sendPayload(session, "message");
|
|
1171
1309
|
throwIfAborted(signal);
|
|
1172
1310
|
let signature;
|
|
1173
1311
|
try {
|
|
@@ -1179,7 +1317,7 @@ var Wallet = class {
|
|
|
1179
1317
|
keyPackage,
|
|
1180
1318
|
publicKeyPackage,
|
|
1181
1319
|
participants: session.participants,
|
|
1182
|
-
message
|
|
1320
|
+
message,
|
|
1183
1321
|
relayToken: session.relayToken
|
|
1184
1322
|
}));
|
|
1185
1323
|
} catch (cause) {
|
|
@@ -1228,6 +1366,26 @@ async function attachPasskeyStepUp(http, walletId, body, operation, options) {
|
|
|
1228
1366
|
body["passkeyAssertion"] = await getSigningAssertion(challenge, { credentialId: options.passkeyCredentialId });
|
|
1229
1367
|
body["passkeyChallengeId"] = challengeId;
|
|
1230
1368
|
}
|
|
1369
|
+
function toSendCeremony(session) {
|
|
1370
|
+
return {
|
|
1371
|
+
curve: session.curve,
|
|
1372
|
+
relayUrl: session.relayUrl,
|
|
1373
|
+
sessionId: session.sessionId,
|
|
1374
|
+
roles: session.roles,
|
|
1375
|
+
participants: session.participants,
|
|
1376
|
+
signerPosition: session.signerPosition,
|
|
1377
|
+
relayToken: session.relayToken
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function sendPayload(session, field) {
|
|
1381
|
+
const payload = session[field];
|
|
1382
|
+
if (!payload) {
|
|
1383
|
+
throw new WaaskeyError(`The send session did not return the \`${field}\` this wallet's curve (${session.curve}) signs.`, "sign_failed", {
|
|
1384
|
+
details: { txId: session.txId, curve: session.curve }
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
return payload;
|
|
1388
|
+
}
|
|
1231
1389
|
function normalizeDigest(digest) {
|
|
1232
1390
|
const hex = digest.startsWith("0x") || digest.startsWith("0X") ? digest.slice(2) : digest;
|
|
1233
1391
|
if (!/^[0-9a-fA-F]{64}$/.test(hex)) {
|
|
@@ -1690,7 +1848,7 @@ var Wallets = class {
|
|
|
1690
1848
|
throwIfAborted(signal);
|
|
1691
1849
|
let signature;
|
|
1692
1850
|
try {
|
|
1693
|
-
({ signature } = await mpc.runSign(
|
|
1851
|
+
({ signature } = await mpc.runSign(toSessionSignParams(session, share, digest)));
|
|
1694
1852
|
} catch (cause) {
|
|
1695
1853
|
if (cause instanceof WaaskeyError) throw cause;
|
|
1696
1854
|
throw new WaaskeyError("The user_backup device recover-sign ceremony failed.", "sign_failed", { cause });
|
|
@@ -1836,29 +1994,6 @@ async function restoreUserBackupShare(ciphertext, recoveryCode) {
|
|
|
1836
1994
|
}
|
|
1837
1995
|
return deserializeShare(blob).keyShare;
|
|
1838
1996
|
}
|
|
1839
|
-
function toUserBackupSignParams(session, share, digest) {
|
|
1840
|
-
if (session.participants.length !== 2 || session.signerPosition < 0 || session.signerPosition > 1) {
|
|
1841
|
-
throw new WaaskeyError(
|
|
1842
|
-
`Unexpected recover-sign descriptor: the {server, user_backup} quorum must be exactly 2 parties with signerPosition in {0, 1} (got ${session.participants.length} participants, signerPosition ${session.signerPosition}).`,
|
|
1843
|
-
"sign_failed",
|
|
1844
|
-
{ details: { participants: session.participants, signerPosition: session.signerPosition } }
|
|
1845
|
-
);
|
|
1846
|
-
}
|
|
1847
|
-
return {
|
|
1848
|
-
curve: toMpcCurve(session.curve),
|
|
1849
|
-
relayUrl: session.relayUrl,
|
|
1850
|
-
sessionId: session.sessionId,
|
|
1851
|
-
role: session.role,
|
|
1852
|
-
peerRole: session.peerRole,
|
|
1853
|
-
partyIndex: session.signerPosition,
|
|
1854
|
-
peerPartyIndex: 1 - session.signerPosition,
|
|
1855
|
-
relayToken: session.relayToken,
|
|
1856
|
-
share,
|
|
1857
|
-
participants: session.participants,
|
|
1858
|
-
signerPosition: session.signerPosition,
|
|
1859
|
-
digest
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
1997
|
function memberShareKey(walletId, membershipId) {
|
|
1863
1998
|
return `${walletId}@member-${membershipId}`;
|
|
1864
1999
|
}
|
|
@@ -1875,7 +2010,17 @@ function parseBackupPayload(stored, walletId) {
|
|
|
1875
2010
|
if (typeof parsed.ciphertext !== "string" || !Array.isArray(parsed.factors)) {
|
|
1876
2011
|
throw new WaaskeyError(`The pending user_backup backup for wallet "${walletId}" is malformed.`, "backup_failed", { details: { walletId, keys: Object.keys(parsed) } });
|
|
1877
2012
|
}
|
|
1878
|
-
return { ciphertext: parsed.ciphertext, factors: parsed.factors };
|
|
2013
|
+
return { ciphertext: parsed.ciphertext, factors: parsed.factors.map((factor) => normalizeStoredFactor(factor, walletId)) };
|
|
2014
|
+
}
|
|
2015
|
+
function normalizeStoredFactor(factor, walletId) {
|
|
2016
|
+
const { type, credential, credentialHash } = factor ?? {};
|
|
2017
|
+
const value = typeof credential === "string" && credential !== "" ? credential : credentialHash;
|
|
2018
|
+
if (typeof type !== "string" || type === "" || typeof value !== "string" || value === "") {
|
|
2019
|
+
throw new WaaskeyError(`The pending user_backup backup for wallet "${walletId}" is malformed \u2014 a factor entry is missing its type or credential.`, "backup_failed", {
|
|
2020
|
+
details: { walletId, factorKeys: Object.keys(factor ?? {}) }
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
return { type, credential: value };
|
|
1879
2024
|
}
|
|
1880
2025
|
function membershipIdFromRole(role) {
|
|
1881
2026
|
const prefix = "member:";
|
|
@@ -2332,7 +2477,7 @@ function decodePublicKey(sharedPublicKeyJson) {
|
|
|
2332
2477
|
|
|
2333
2478
|
// src/mpc/load-wasm.ts
|
|
2334
2479
|
var CLIENT_WASM_PACKAGE = "@waaskey/client-wasm";
|
|
2335
|
-
var CLIENT_WASM_VERSION = "0.2.
|
|
2480
|
+
var CLIENT_WASM_VERSION = "0.2.2";
|
|
2336
2481
|
async function verifyWasmIntegrity(bytes, expectedSha384) {
|
|
2337
2482
|
if (!expectedSha384 || !expectedSha384.startsWith("sha384-")) {
|
|
2338
2483
|
throw new Error("Waaskey: an expected SHA-384 integrity hash (sha384-<base64>) is required to load the wasm MPC core.");
|