@medialane/sdk 0.194.0 → 0.197.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({
@@ -1281,6 +1308,7 @@ function createPasskeyOwner(config) {
1281
1308
  { type: "public-key", alg: -257 }
1282
1309
  ],
1283
1310
  authenticatorSelection: {
1311
+ authenticatorAttachment: "platform",
1284
1312
  residentKey: "required",
1285
1313
  userVerification: "required"
1286
1314
  },
@@ -1294,53 +1322,55 @@ function createPasskeyOwner(config) {
1294
1322
  const prf = credential.getClientExtensionResults().prf;
1295
1323
  return {
1296
1324
  credentialId: encodeBase64(credential.rawId),
1297
- prfFirst: prf?.results?.first ?? null,
1298
- prfEnabled: prf?.enabled
1325
+ prfFirst: prf?.results?.first ?? null
1299
1326
  };
1300
1327
  }
1301
- async function prfSecret(credentialId) {
1302
- await ensurePrfPossible();
1303
- let assertion;
1328
+ async function assertPasskey(credentialId, extensions) {
1304
1329
  try {
1305
- assertion = await credentialsApi().get({
1330
+ return await credentialsApi().get({
1306
1331
  publicKey: {
1307
1332
  challenge: randomBytes(32),
1308
1333
  rpId: config.relyingPartyId(),
1309
1334
  allowCredentials: [{ type: "public-key", id: decodeBase64(credentialId) }],
1310
1335
  userVerification: "required",
1311
- extensions: { prf: { eval: { first: config.prfSalt } } }
1336
+ ...extensions ? { extensions } : {}
1312
1337
  }
1313
1338
  });
1314
1339
  } catch (err) {
1315
1340
  if (isPasskeyCancellation(err)) throw new PasskeyCancelledError();
1316
1341
  throw err;
1317
1342
  }
1343
+ }
1344
+ async function prfSecret(credentialId) {
1345
+ const assertion = await assertPasskey(credentialId, {
1346
+ prf: { eval: { first: config.prfSalt } }
1347
+ });
1318
1348
  const result = assertion.getClientExtensionResults().prf?.results?.first;
1319
1349
  if (!result) throw new PasskeyUnsupportedError("no-prf");
1320
1350
  return new Uint8Array(result);
1321
1351
  }
1322
- const forgetCredential = async (credentialId) => {
1323
- const signal = config.signalUnknownCredential ?? (typeof PublicKeyCredential !== "undefined" && typeof PublicKeyCredential.signalUnknownCredential === "function" ? (options) => PublicKeyCredential.signalUnknownCredential(options) : void 0);
1324
- if (!signal) return;
1325
- const base64url = credentialId.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1326
- try {
1327
- await signal({ rpId: config.relyingPartyId(), credentialId: base64url });
1328
- } catch {
1329
- 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) };
1330
1356
  }
1331
- };
1332
- async function secretFromRegistration(registration) {
1333
- if (registration.prfFirst) return new Uint8Array(registration.prfFirst);
1334
1357
  try {
1335
- if (registration.prfEnabled === false) throw new PasskeyUnsupportedError("no-prf");
1336
- return await prfSecret(registration.credentialId);
1358
+ return { aes: await deriveAesKey(await prfSecret(registration.credentialId), config.hkdfInfo) };
1337
1359
  } catch (err) {
1338
- if (err instanceof PasskeyUnsupportedError) await forgetCredential(registration.credentialId);
1339
- throw err;
1360
+ if (!(err instanceof PasskeyUnsupportedError) || err.reason !== "no-prf") throw err;
1340
1361
  }
1341
- }
1342
- async function seal(secret, privateKeyHex) {
1343
- 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) {
1344
1374
  const iv = randomBytes(12);
1345
1375
  const ciphertext = await sealPrivateKey(aes, iv, privateKeyHex);
1346
1376
  return { iv: encodeBase64(iv), ciphertext: encodeBase64(ciphertext) };
@@ -1348,36 +1378,37 @@ function createPasskeyOwner(config) {
1348
1378
  return {
1349
1379
  async createOwnerKey() {
1350
1380
  const registration = await registerPasskey();
1351
- const secret = await secretFromRegistration(registration);
1381
+ const { aes, lock } = await lockFor(registration);
1352
1382
  const { privateKeyHex, publicKeyHex } = generateStarkKeyPair();
1353
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1383
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1354
1384
  return {
1355
1385
  sealed: {
1356
1386
  credentialId: registration.credentialId,
1357
1387
  ownerPubKey: publicKeyHex,
1358
1388
  address: computeAccountAddress(publicKeyHex, 0),
1359
1389
  iv,
1360
- ciphertext
1390
+ ciphertext,
1391
+ ...lock ? { lock } : {}
1361
1392
  },
1362
1393
  privateKeyHex
1363
1394
  };
1364
1395
  },
1365
1396
  async unlockOwnerKey(sealed) {
1366
- const secret = await prfSecret(sealed.credentialId);
1367
- const aes = await deriveAesKey(secret, config.hkdfInfo);
1397
+ const aes = await unlockFor(sealed);
1368
1398
  return unsealPrivateKey(aes, decodeBase64(sealed.iv), decodeBase64(sealed.ciphertext));
1369
1399
  },
1370
1400
  async sealImportedOwnerKey(recovery) {
1371
1401
  const { privateKeyHex, publicKeyHex } = starkKeyPairFromPrivateKey(recovery.privateKey);
1372
1402
  const registration = await registerPasskey();
1373
- const secret = await secretFromRegistration(registration);
1374
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1403
+ const { aes, lock } = await lockFor(registration);
1404
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1375
1405
  return {
1376
1406
  credentialId: registration.credentialId,
1377
1407
  ownerPubKey: publicKeyHex,
1378
1408
  address: recovery.walletAddress,
1379
1409
  iv,
1380
- ciphertext
1410
+ ciphertext,
1411
+ ...lock ? { lock } : {}
1381
1412
  };
1382
1413
  }
1383
1414
  };
@@ -1821,6 +1852,7 @@ var UserFacingError = class extends Error {
1821
1852
 
1822
1853
  exports.ApiClient = ApiClient;
1823
1854
  exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
1855
+ exports.DeviceKeyMissingError = DeviceKeyMissingError;
1824
1856
  exports.FeeConfigSchema = FeeConfigSchema;
1825
1857
  exports.InvalidStarkPrivateKeyError = InvalidStarkPrivateKeyError;
1826
1858
  exports.MedialaneApiError = MedialaneApiError;
@@ -1884,6 +1916,7 @@ exports.getServicesByCapability = getServicesByCapability;
1884
1916
  exports.getTokenByAddress = getTokenByAddress;
1885
1917
  exports.getTokenBySymbol = getTokenBySymbol;
1886
1918
  exports.hasCapability = hasCapability;
1919
+ exports.indexedDbDeviceKeys = indexedDbDeviceKeys;
1887
1920
  exports.isServiceId = isServiceId;
1888
1921
  exports.listServices = listServices;
1889
1922
  exports.normalizeAddress = normalizeAddress;
@@ -1901,5 +1934,5 @@ exports.starkKeyPairFromPrivateKey = starkKeyPairFromPrivateKey;
1901
1934
  exports.stringifyBigInts = stringifyBigInts;
1902
1935
  exports.u256ToBigInt = u256ToBigInt;
1903
1936
  exports.unsealPrivateKey = unsealPrivateKey;
1904
- //# sourceMappingURL=chunk-BLEFV7GP.cjs.map
1905
- //# sourceMappingURL=chunk-BLEFV7GP.cjs.map
1937
+ //# sourceMappingURL=chunk-2R7XBA34.cjs.map
1938
+ //# sourceMappingURL=chunk-2R7XBA34.cjs.map