@medialane/sdk 0.195.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.
@@ -1090,6 +1090,9 @@ async function deriveAesKey(prfSecret, hkdfInfo) {
1090
1090
  ["encrypt", "decrypt"]
1091
1091
  );
1092
1092
  }
1093
+ function generateDeviceKey() {
1094
+ return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
1095
+ }
1093
1096
  function generateStarkKeyPair() {
1094
1097
  const privateKeyHex = "0x" + Array.from(ec.starkCurve.utils.randomPrivateKey()).map((b) => b.toString(16).padStart(2, "0")).join("");
1095
1098
  return { privateKeyHex, publicKeyHex: ec.starkCurve.getStarkKey(privateKeyHex) };
@@ -1212,6 +1215,38 @@ function computeAccountAddress(ownerPubkey, salt = 0) {
1212
1215
  );
1213
1216
  }
1214
1217
 
1218
+ // src/wallet/device-keys.ts
1219
+ var DATABASE = "medialane-wallet";
1220
+ var STORE = "device-keys";
1221
+ function open() {
1222
+ return new Promise((resolve, reject) => {
1223
+ const request = indexedDB.open(DATABASE, 1);
1224
+ request.onupgradeneeded = () => request.result.createObjectStore(STORE);
1225
+ request.onsuccess = () => resolve(request.result);
1226
+ request.onerror = () => reject(request.error);
1227
+ });
1228
+ }
1229
+ async function run(mode, act) {
1230
+ const db = await open();
1231
+ try {
1232
+ return await new Promise((resolve, reject) => {
1233
+ const request = act(db.transaction(STORE, mode).objectStore(STORE));
1234
+ request.onsuccess = () => resolve(request.result);
1235
+ request.onerror = () => reject(request.error);
1236
+ });
1237
+ } finally {
1238
+ db.close();
1239
+ }
1240
+ }
1241
+ var indexedDbDeviceKeys = {
1242
+ async save(credentialId, key) {
1243
+ await run("readwrite", (store) => store.put(key, credentialId));
1244
+ },
1245
+ async load(credentialId) {
1246
+ return await run("readonly", (store) => store.get(credentialId)) ?? null;
1247
+ }
1248
+ };
1249
+
1215
1250
  // src/wallet/passkey.ts
1216
1251
  var PasskeyCancelledError = class extends Error {
1217
1252
  constructor(message = "Passkey prompt was cancelled.") {
@@ -1219,6 +1254,12 @@ var PasskeyCancelledError = class extends Error {
1219
1254
  this.name = "PasskeyCancelledError";
1220
1255
  }
1221
1256
  };
1257
+ var DeviceKeyMissingError = class extends Error {
1258
+ constructor() {
1259
+ super("This browser no longer holds the wallet key. Recover the wallet with its recovery key.");
1260
+ this.name = "DeviceKeyMissingError";
1261
+ }
1262
+ };
1222
1263
  var PasskeyUnsupportedError = class extends Error {
1223
1264
  constructor(reason) {
1224
1265
  super(
@@ -1251,21 +1292,7 @@ function createPasskeyOwner(config) {
1251
1292
  if (!api) throw new PasskeyUnsupportedError("no-webauthn");
1252
1293
  return api;
1253
1294
  };
1254
- const browserRefusesPrf = async () => {
1255
- const read = config.clientCapabilities ?? (typeof PublicKeyCredential !== "undefined" && typeof PublicKeyCredential.getClientCapabilities === "function" ? () => PublicKeyCredential.getClientCapabilities() : void 0);
1256
- if (!read) return false;
1257
- try {
1258
- return (await read())["extension:prf"] === false;
1259
- } catch {
1260
- return false;
1261
- }
1262
- };
1263
- const ensurePrfPossible = async () => {
1264
- credentialsApi();
1265
- if (await browserRefusesPrf()) throw new PasskeyUnsupportedError("no-prf");
1266
- };
1267
1295
  async function registerPasskey() {
1268
- await ensurePrfPossible();
1269
1296
  let credential;
1270
1297
  try {
1271
1298
  credential = await credentialsApi().create({
@@ -1293,53 +1320,55 @@ function createPasskeyOwner(config) {
1293
1320
  const prf = credential.getClientExtensionResults().prf;
1294
1321
  return {
1295
1322
  credentialId: encodeBase64(credential.rawId),
1296
- prfFirst: prf?.results?.first ?? null,
1297
- prfEnabled: prf?.enabled
1323
+ prfFirst: prf?.results?.first ?? null
1298
1324
  };
1299
1325
  }
1300
- async function prfSecret(credentialId) {
1301
- await ensurePrfPossible();
1302
- let assertion;
1326
+ async function assertPasskey(credentialId, extensions) {
1303
1327
  try {
1304
- assertion = await credentialsApi().get({
1328
+ return await credentialsApi().get({
1305
1329
  publicKey: {
1306
1330
  challenge: randomBytes(32),
1307
1331
  rpId: config.relyingPartyId(),
1308
1332
  allowCredentials: [{ type: "public-key", id: decodeBase64(credentialId) }],
1309
1333
  userVerification: "required",
1310
- extensions: { prf: { eval: { first: config.prfSalt } } }
1334
+ ...extensions ? { extensions } : {}
1311
1335
  }
1312
1336
  });
1313
1337
  } catch (err) {
1314
1338
  if (isPasskeyCancellation(err)) throw new PasskeyCancelledError();
1315
1339
  throw err;
1316
1340
  }
1341
+ }
1342
+ async function prfSecret(credentialId) {
1343
+ const assertion = await assertPasskey(credentialId, {
1344
+ prf: { eval: { first: config.prfSalt } }
1345
+ });
1317
1346
  const result = assertion.getClientExtensionResults().prf?.results?.first;
1318
1347
  if (!result) throw new PasskeyUnsupportedError("no-prf");
1319
1348
  return new Uint8Array(result);
1320
1349
  }
1321
- const forgetCredential = async (credentialId) => {
1322
- const signal = config.signalUnknownCredential ?? (typeof PublicKeyCredential !== "undefined" && typeof PublicKeyCredential.signalUnknownCredential === "function" ? (options) => PublicKeyCredential.signalUnknownCredential(options) : void 0);
1323
- if (!signal) return;
1324
- const base64url = credentialId.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1325
- try {
1326
- await signal({ rpId: config.relyingPartyId(), credentialId: base64url });
1327
- } catch {
1328
- return;
1350
+ const deviceKeys = config.deviceKeys ?? indexedDbDeviceKeys;
1351
+ async function lockFor(registration) {
1352
+ if (registration.prfFirst) {
1353
+ return { aes: await deriveAesKey(new Uint8Array(registration.prfFirst), config.hkdfInfo) };
1329
1354
  }
1330
- };
1331
- async function secretFromRegistration(registration) {
1332
- if (registration.prfFirst) return new Uint8Array(registration.prfFirst);
1333
1355
  try {
1334
- if (registration.prfEnabled === false) throw new PasskeyUnsupportedError("no-prf");
1335
- return await prfSecret(registration.credentialId);
1356
+ return { aes: await deriveAesKey(await prfSecret(registration.credentialId), config.hkdfInfo) };
1336
1357
  } catch (err) {
1337
- if (err instanceof PasskeyUnsupportedError) await forgetCredential(registration.credentialId);
1338
- throw err;
1358
+ if (!(err instanceof PasskeyUnsupportedError) || err.reason !== "no-prf") throw err;
1339
1359
  }
1340
- }
1341
- async function seal(secret, privateKeyHex) {
1342
- const aes = await deriveAesKey(secret, config.hkdfInfo);
1360
+ const aes = await generateDeviceKey();
1361
+ await deviceKeys.save(registration.credentialId, aes);
1362
+ return { aes, lock: "device" };
1363
+ }
1364
+ async function unlockFor(sealed) {
1365
+ if (sealed.lock !== "device") return deriveAesKey(await prfSecret(sealed.credentialId), config.hkdfInfo);
1366
+ await assertPasskey(sealed.credentialId);
1367
+ const aes = await deviceKeys.load(sealed.credentialId);
1368
+ if (!aes) throw new DeviceKeyMissingError();
1369
+ return aes;
1370
+ }
1371
+ async function seal(aes, privateKeyHex) {
1343
1372
  const iv = randomBytes(12);
1344
1373
  const ciphertext = await sealPrivateKey(aes, iv, privateKeyHex);
1345
1374
  return { iv: encodeBase64(iv), ciphertext: encodeBase64(ciphertext) };
@@ -1347,36 +1376,37 @@ function createPasskeyOwner(config) {
1347
1376
  return {
1348
1377
  async createOwnerKey() {
1349
1378
  const registration = await registerPasskey();
1350
- const secret = await secretFromRegistration(registration);
1379
+ const { aes, lock } = await lockFor(registration);
1351
1380
  const { privateKeyHex, publicKeyHex } = generateStarkKeyPair();
1352
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1381
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1353
1382
  return {
1354
1383
  sealed: {
1355
1384
  credentialId: registration.credentialId,
1356
1385
  ownerPubKey: publicKeyHex,
1357
1386
  address: computeAccountAddress(publicKeyHex, 0),
1358
1387
  iv,
1359
- ciphertext
1388
+ ciphertext,
1389
+ ...lock ? { lock } : {}
1360
1390
  },
1361
1391
  privateKeyHex
1362
1392
  };
1363
1393
  },
1364
1394
  async unlockOwnerKey(sealed) {
1365
- const secret = await prfSecret(sealed.credentialId);
1366
- const aes = await deriveAesKey(secret, config.hkdfInfo);
1395
+ const aes = await unlockFor(sealed);
1367
1396
  return unsealPrivateKey(aes, decodeBase64(sealed.iv), decodeBase64(sealed.ciphertext));
1368
1397
  },
1369
1398
  async sealImportedOwnerKey(recovery) {
1370
1399
  const { privateKeyHex, publicKeyHex } = starkKeyPairFromPrivateKey(recovery.privateKey);
1371
1400
  const registration = await registerPasskey();
1372
- const secret = await secretFromRegistration(registration);
1373
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1401
+ const { aes, lock } = await lockFor(registration);
1402
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1374
1403
  return {
1375
1404
  credentialId: registration.credentialId,
1376
1405
  ownerPubKey: publicKeyHex,
1377
1406
  address: recovery.walletAddress,
1378
1407
  iv,
1379
- ciphertext
1408
+ ciphertext,
1409
+ ...lock ? { lock } : {}
1380
1410
  };
1381
1411
  }
1382
1412
  };
@@ -1818,6 +1848,6 @@ var UserFacingError = class extends Error {
1818
1848
  }
1819
1849
  };
1820
1850
 
1821
- export { ApiClient, DEFAULT_CURRENCY, FeeConfigSchema, InvalidStarkPrivateKeyError, MedialaneApiError, PasskeyCancelledError, PasskeyUnsupportedError, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DATA_TOKENIZATION_721_CONTRACT, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_DROP_FACTORY_START_BLOCK, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_CONTRACT, STARKNET_GENESIS_NFT_URI, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_MEDIAWALLET_CLASS_HASH, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, STARKNET_POP_FACTORY_START_BLOCK, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, UserFacingError, assetHref, chainFromSlug, chainSlug, coinHref, collectionHref, computeAccountAddress, createPasskeyOwner, deriveAesKey, dropHref, encodeU256, formatAmount, generateStarkKeyPair, getListableTokens, getService, getServicesByCapability, getTokenByAddress, getTokenBySymbol, hasCapability, isServiceId, listServices, normalizeAddress, normalizeHash, ownerConstructorCalldata, parseAmount, profileHref, resolveAppFeeConfig, resolveConfig, resolveFeeConfig, sealPrivateKey, shortenAddress, signWithPrivateKey, starkKeyPairFromPrivateKey, stringifyBigInts, u256ToBigInt, unsealPrivateKey };
1822
- //# sourceMappingURL=chunk-ZCHP5D7S.js.map
1823
- //# sourceMappingURL=chunk-ZCHP5D7S.js.map
1851
+ export { ApiClient, DEFAULT_CURRENCY, DeviceKeyMissingError, FeeConfigSchema, InvalidStarkPrivateKeyError, MedialaneApiError, PasskeyCancelledError, PasskeyUnsupportedError, STARKNET_COLLECTION_1155_CLASS_HASH, STARKNET_COLLECTION_1155_CONTRACT, STARKNET_COLLECTION_1155_FACTORY_CLASS_HASH, STARKNET_COLLECTION_1155_START_BLOCK, STARKNET_COLLECTION_721_CONTRACT, STARKNET_COLLECTION_721_START_BLOCK, STARKNET_CREATOR_COIN_CLASS_HASH, STARKNET_CREATOR_COIN_EKUBO_LAUNCHER, STARKNET_CREATOR_COIN_FACTORY_CLASS_HASH, STARKNET_CREATOR_COIN_FACTORY_CONTRACT, STARKNET_CREATOR_COIN_START_BLOCK, STARKNET_DATA_TOKENIZATION_721_CONTRACT, STARKNET_DROP_COLLECTION_CLASS_HASH, STARKNET_DROP_FACTORY_CONTRACT, STARKNET_DROP_FACTORY_START_BLOCK, STARKNET_EKUBO_CORE, STARKNET_GENESIS_MINT_CONTRACT, STARKNET_GENESIS_NFT_URI, STARKNET_IPCOLLECTION_CLASS_HASH, STARKNET_IPNFT_CLASS_HASH, STARKNET_IP_CLUB_COLLECTION_CLASS_HASH, STARKNET_IP_CLUB_FACTORY_CONTRACT, STARKNET_IP_SPONSORSHIP_CLASS_HASH, STARKNET_IP_SPONSORSHIP_CONTRACT, STARKNET_IP_TICKETS_FACTORY_CONTRACT, STARKNET_IP_TICKET_COLLECTION_CLASS_HASH, STARKNET_MARKETPLACE_1155_CLASS_HASH, STARKNET_MARKETPLACE_1155_CONTRACT, STARKNET_MARKETPLACE_1155_START_BLOCK, STARKNET_MARKETPLACE_721_CLASS_HASH, STARKNET_MARKETPLACE_721_CONTRACT, STARKNET_MARKETPLACE_721_START_BLOCK, STARKNET_MEDIAWALLET_CLASS_HASH, STARKNET_NFTCOMMENTS_CONTRACT, STARKNET_POP_COLLECTION_CLASS_HASH, STARKNET_POP_FACTORY_CONTRACT, STARKNET_POP_FACTORY_START_BLOCK, SUPPORTED_TOKENS, SUPPORTED_URL_CHAINS, UserFacingError, assetHref, chainFromSlug, chainSlug, coinHref, collectionHref, computeAccountAddress, createPasskeyOwner, deriveAesKey, dropHref, encodeU256, formatAmount, generateStarkKeyPair, getListableTokens, getService, getServicesByCapability, getTokenByAddress, getTokenBySymbol, hasCapability, indexedDbDeviceKeys, isServiceId, listServices, normalizeAddress, normalizeHash, ownerConstructorCalldata, parseAmount, profileHref, resolveAppFeeConfig, resolveConfig, resolveFeeConfig, sealPrivateKey, shortenAddress, signWithPrivateKey, starkKeyPairFromPrivateKey, stringifyBigInts, u256ToBigInt, unsealPrivateKey };
1852
+ //# sourceMappingURL=chunk-6WVUUBFI.js.map
1853
+ //# sourceMappingURL=chunk-6WVUUBFI.js.map