@medialane/sdk 0.195.0 → 0.198.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.
@@ -1092,6 +1092,9 @@ async function deriveAesKey(prfSecret, hkdfInfo) {
1092
1092
  ["encrypt", "decrypt"]
1093
1093
  );
1094
1094
  }
1095
+ function generateDeviceKey() {
1096
+ return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
1097
+ }
1095
1098
  function generateStarkKeyPair() {
1096
1099
  const privateKeyHex = "0x" + Array.from(starknet.ec.starkCurve.utils.randomPrivateKey()).map((b) => b.toString(16).padStart(2, "0")).join("");
1097
1100
  return { privateKeyHex, publicKeyHex: starknet.ec.starkCurve.getStarkKey(privateKeyHex) };
@@ -1214,6 +1217,38 @@ function computeAccountAddress(ownerPubkey, salt = 0) {
1214
1217
  );
1215
1218
  }
1216
1219
 
1220
+ // src/wallet/device-keys.ts
1221
+ var DATABASE = "medialane-wallet";
1222
+ var STORE = "device-keys";
1223
+ function open() {
1224
+ return new Promise((resolve, reject) => {
1225
+ const request = indexedDB.open(DATABASE, 1);
1226
+ request.onupgradeneeded = () => request.result.createObjectStore(STORE);
1227
+ request.onsuccess = () => resolve(request.result);
1228
+ request.onerror = () => reject(request.error);
1229
+ });
1230
+ }
1231
+ async function run(mode, act) {
1232
+ const db = await open();
1233
+ try {
1234
+ return await new Promise((resolve, reject) => {
1235
+ const request = act(db.transaction(STORE, mode).objectStore(STORE));
1236
+ request.onsuccess = () => resolve(request.result);
1237
+ request.onerror = () => reject(request.error);
1238
+ });
1239
+ } finally {
1240
+ db.close();
1241
+ }
1242
+ }
1243
+ var indexedDbDeviceKeys = {
1244
+ async save(credentialId, key) {
1245
+ await run("readwrite", (store) => store.put(key, credentialId));
1246
+ },
1247
+ async load(credentialId) {
1248
+ return await run("readonly", (store) => store.get(credentialId)) ?? null;
1249
+ }
1250
+ };
1251
+
1217
1252
  // src/wallet/passkey.ts
1218
1253
  var PasskeyCancelledError = class extends Error {
1219
1254
  constructor(message = "Passkey prompt was cancelled.") {
@@ -1221,6 +1256,12 @@ var PasskeyCancelledError = class extends Error {
1221
1256
  this.name = "PasskeyCancelledError";
1222
1257
  }
1223
1258
  };
1259
+ var DeviceKeyMissingError = class extends Error {
1260
+ constructor() {
1261
+ super("This browser no longer holds the wallet key. Recover the wallet with its recovery key.");
1262
+ this.name = "DeviceKeyMissingError";
1263
+ }
1264
+ };
1224
1265
  var PasskeyUnsupportedError = class extends Error {
1225
1266
  constructor(reason) {
1226
1267
  super(
@@ -1253,21 +1294,7 @@ function createPasskeyOwner(config) {
1253
1294
  if (!api) throw new PasskeyUnsupportedError("no-webauthn");
1254
1295
  return api;
1255
1296
  };
1256
- const browserRefusesPrf = async () => {
1257
- const read = config.clientCapabilities ?? (typeof PublicKeyCredential !== "undefined" && typeof PublicKeyCredential.getClientCapabilities === "function" ? () => PublicKeyCredential.getClientCapabilities() : void 0);
1258
- if (!read) return false;
1259
- try {
1260
- return (await read())["extension:prf"] === false;
1261
- } catch {
1262
- return false;
1263
- }
1264
- };
1265
- const ensurePrfPossible = async () => {
1266
- credentialsApi();
1267
- if (await browserRefusesPrf()) throw new PasskeyUnsupportedError("no-prf");
1268
- };
1269
1297
  async function registerPasskey() {
1270
- await ensurePrfPossible();
1271
1298
  let credential;
1272
1299
  try {
1273
1300
  credential = await credentialsApi().create({
@@ -1295,53 +1322,55 @@ function createPasskeyOwner(config) {
1295
1322
  const prf = credential.getClientExtensionResults().prf;
1296
1323
  return {
1297
1324
  credentialId: encodeBase64(credential.rawId),
1298
- prfFirst: prf?.results?.first ?? null,
1299
- prfEnabled: prf?.enabled
1325
+ prfFirst: prf?.results?.first ?? null
1300
1326
  };
1301
1327
  }
1302
- async function prfSecret(credentialId) {
1303
- await ensurePrfPossible();
1304
- let assertion;
1328
+ async function assertPasskey(credentialId, extensions) {
1305
1329
  try {
1306
- assertion = await credentialsApi().get({
1330
+ return await credentialsApi().get({
1307
1331
  publicKey: {
1308
1332
  challenge: randomBytes(32),
1309
1333
  rpId: config.relyingPartyId(),
1310
1334
  allowCredentials: [{ type: "public-key", id: decodeBase64(credentialId) }],
1311
1335
  userVerification: "required",
1312
- extensions: { prf: { eval: { first: config.prfSalt } } }
1336
+ ...extensions ? { extensions } : {}
1313
1337
  }
1314
1338
  });
1315
1339
  } catch (err) {
1316
1340
  if (isPasskeyCancellation(err)) throw new PasskeyCancelledError();
1317
1341
  throw err;
1318
1342
  }
1343
+ }
1344
+ async function prfSecret(credentialId) {
1345
+ const assertion = await assertPasskey(credentialId, {
1346
+ prf: { eval: { first: config.prfSalt } }
1347
+ });
1319
1348
  const result = assertion.getClientExtensionResults().prf?.results?.first;
1320
1349
  if (!result) throw new PasskeyUnsupportedError("no-prf");
1321
1350
  return new Uint8Array(result);
1322
1351
  }
1323
- const forgetCredential = async (credentialId) => {
1324
- const signal = config.signalUnknownCredential ?? (typeof PublicKeyCredential !== "undefined" && typeof PublicKeyCredential.signalUnknownCredential === "function" ? (options) => PublicKeyCredential.signalUnknownCredential(options) : void 0);
1325
- if (!signal) return;
1326
- const base64url = credentialId.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1327
- try {
1328
- await signal({ rpId: config.relyingPartyId(), credentialId: base64url });
1329
- } catch {
1330
- return;
1352
+ const deviceKeys = config.deviceKeys ?? indexedDbDeviceKeys;
1353
+ async function lockFor(registration) {
1354
+ if (registration.prfFirst) {
1355
+ return { aes: await deriveAesKey(new Uint8Array(registration.prfFirst), config.hkdfInfo) };
1331
1356
  }
1332
- };
1333
- async function secretFromRegistration(registration) {
1334
- if (registration.prfFirst) return new Uint8Array(registration.prfFirst);
1335
1357
  try {
1336
- if (registration.prfEnabled === false) throw new PasskeyUnsupportedError("no-prf");
1337
- return await prfSecret(registration.credentialId);
1358
+ return { aes: await deriveAesKey(await prfSecret(registration.credentialId), config.hkdfInfo) };
1338
1359
  } catch (err) {
1339
- if (err instanceof PasskeyUnsupportedError) await forgetCredential(registration.credentialId);
1340
- throw err;
1360
+ if (!(err instanceof PasskeyUnsupportedError) || err.reason !== "no-prf") throw err;
1341
1361
  }
1342
- }
1343
- async function seal(secret, privateKeyHex) {
1344
- const aes = await deriveAesKey(secret, config.hkdfInfo);
1362
+ const aes = await generateDeviceKey();
1363
+ await deviceKeys.save(registration.credentialId, aes);
1364
+ return { aes, lock: "device" };
1365
+ }
1366
+ async function unlockFor(sealed) {
1367
+ if (sealed.lock !== "device") return deriveAesKey(await prfSecret(sealed.credentialId), config.hkdfInfo);
1368
+ await assertPasskey(sealed.credentialId);
1369
+ const aes = await deviceKeys.load(sealed.credentialId);
1370
+ if (!aes) throw new DeviceKeyMissingError();
1371
+ return aes;
1372
+ }
1373
+ async function seal(aes, privateKeyHex) {
1345
1374
  const iv = randomBytes(12);
1346
1375
  const ciphertext = await sealPrivateKey(aes, iv, privateKeyHex);
1347
1376
  return { iv: encodeBase64(iv), ciphertext: encodeBase64(ciphertext) };
@@ -1349,36 +1378,37 @@ function createPasskeyOwner(config) {
1349
1378
  return {
1350
1379
  async createOwnerKey() {
1351
1380
  const registration = await registerPasskey();
1352
- const secret = await secretFromRegistration(registration);
1381
+ const { aes, lock } = await lockFor(registration);
1353
1382
  const { privateKeyHex, publicKeyHex } = generateStarkKeyPair();
1354
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1383
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1355
1384
  return {
1356
1385
  sealed: {
1357
1386
  credentialId: registration.credentialId,
1358
1387
  ownerPubKey: publicKeyHex,
1359
1388
  address: computeAccountAddress(publicKeyHex, 0),
1360
1389
  iv,
1361
- ciphertext
1390
+ ciphertext,
1391
+ ...lock ? { lock } : {}
1362
1392
  },
1363
1393
  privateKeyHex
1364
1394
  };
1365
1395
  },
1366
1396
  async unlockOwnerKey(sealed) {
1367
- const secret = await prfSecret(sealed.credentialId);
1368
- const aes = await deriveAesKey(secret, config.hkdfInfo);
1397
+ const aes = await unlockFor(sealed);
1369
1398
  return unsealPrivateKey(aes, decodeBase64(sealed.iv), decodeBase64(sealed.ciphertext));
1370
1399
  },
1371
1400
  async sealImportedOwnerKey(recovery) {
1372
1401
  const { privateKeyHex, publicKeyHex } = starkKeyPairFromPrivateKey(recovery.privateKey);
1373
1402
  const registration = await registerPasskey();
1374
- const secret = await secretFromRegistration(registration);
1375
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1403
+ const { aes, lock } = await lockFor(registration);
1404
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1376
1405
  return {
1377
1406
  credentialId: registration.credentialId,
1378
1407
  ownerPubKey: publicKeyHex,
1379
1408
  address: recovery.walletAddress,
1380
1409
  iv,
1381
- ciphertext
1410
+ ciphertext,
1411
+ ...lock ? { lock } : {}
1382
1412
  };
1383
1413
  }
1384
1414
  };
@@ -1822,6 +1852,7 @@ var UserFacingError = class extends Error {
1822
1852
 
1823
1853
  exports.ApiClient = ApiClient;
1824
1854
  exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
1855
+ exports.DeviceKeyMissingError = DeviceKeyMissingError;
1825
1856
  exports.FeeConfigSchema = FeeConfigSchema;
1826
1857
  exports.InvalidStarkPrivateKeyError = InvalidStarkPrivateKeyError;
1827
1858
  exports.MedialaneApiError = MedialaneApiError;
@@ -1885,6 +1916,7 @@ exports.getServicesByCapability = getServicesByCapability;
1885
1916
  exports.getTokenByAddress = getTokenByAddress;
1886
1917
  exports.getTokenBySymbol = getTokenBySymbol;
1887
1918
  exports.hasCapability = hasCapability;
1919
+ exports.indexedDbDeviceKeys = indexedDbDeviceKeys;
1888
1920
  exports.isServiceId = isServiceId;
1889
1921
  exports.listServices = listServices;
1890
1922
  exports.normalizeAddress = normalizeAddress;
@@ -1902,5 +1934,5 @@ exports.starkKeyPairFromPrivateKey = starkKeyPairFromPrivateKey;
1902
1934
  exports.stringifyBigInts = stringifyBigInts;
1903
1935
  exports.u256ToBigInt = u256ToBigInt;
1904
1936
  exports.unsealPrivateKey = unsealPrivateKey;
1905
- //# sourceMappingURL=chunk-7G7YIGXJ.cjs.map
1906
- //# sourceMappingURL=chunk-7G7YIGXJ.cjs.map
1937
+ //# sourceMappingURL=chunk-2R7XBA34.cjs.map
1938
+ //# sourceMappingURL=chunk-2R7XBA34.cjs.map