@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.
@@ -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({
@@ -1279,6 +1306,7 @@ function createPasskeyOwner(config) {
1279
1306
  { type: "public-key", alg: -257 }
1280
1307
  ],
1281
1308
  authenticatorSelection: {
1309
+ authenticatorAttachment: "platform",
1282
1310
  residentKey: "required",
1283
1311
  userVerification: "required"
1284
1312
  },
@@ -1292,53 +1320,55 @@ function createPasskeyOwner(config) {
1292
1320
  const prf = credential.getClientExtensionResults().prf;
1293
1321
  return {
1294
1322
  credentialId: encodeBase64(credential.rawId),
1295
- prfFirst: prf?.results?.first ?? null,
1296
- prfEnabled: prf?.enabled
1323
+ prfFirst: prf?.results?.first ?? null
1297
1324
  };
1298
1325
  }
1299
- async function prfSecret(credentialId) {
1300
- await ensurePrfPossible();
1301
- let assertion;
1326
+ async function assertPasskey(credentialId, extensions) {
1302
1327
  try {
1303
- assertion = await credentialsApi().get({
1328
+ return await credentialsApi().get({
1304
1329
  publicKey: {
1305
1330
  challenge: randomBytes(32),
1306
1331
  rpId: config.relyingPartyId(),
1307
1332
  allowCredentials: [{ type: "public-key", id: decodeBase64(credentialId) }],
1308
1333
  userVerification: "required",
1309
- extensions: { prf: { eval: { first: config.prfSalt } } }
1334
+ ...extensions ? { extensions } : {}
1310
1335
  }
1311
1336
  });
1312
1337
  } catch (err) {
1313
1338
  if (isPasskeyCancellation(err)) throw new PasskeyCancelledError();
1314
1339
  throw err;
1315
1340
  }
1341
+ }
1342
+ async function prfSecret(credentialId) {
1343
+ const assertion = await assertPasskey(credentialId, {
1344
+ prf: { eval: { first: config.prfSalt } }
1345
+ });
1316
1346
  const result = assertion.getClientExtensionResults().prf?.results?.first;
1317
1347
  if (!result) throw new PasskeyUnsupportedError("no-prf");
1318
1348
  return new Uint8Array(result);
1319
1349
  }
1320
- const forgetCredential = async (credentialId) => {
1321
- const signal = config.signalUnknownCredential ?? (typeof PublicKeyCredential !== "undefined" && typeof PublicKeyCredential.signalUnknownCredential === "function" ? (options) => PublicKeyCredential.signalUnknownCredential(options) : void 0);
1322
- if (!signal) return;
1323
- const base64url = credentialId.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1324
- try {
1325
- await signal({ rpId: config.relyingPartyId(), credentialId: base64url });
1326
- } catch {
1327
- 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) };
1328
1354
  }
1329
- };
1330
- async function secretFromRegistration(registration) {
1331
- if (registration.prfFirst) return new Uint8Array(registration.prfFirst);
1332
1355
  try {
1333
- if (registration.prfEnabled === false) throw new PasskeyUnsupportedError("no-prf");
1334
- return await prfSecret(registration.credentialId);
1356
+ return { aes: await deriveAesKey(await prfSecret(registration.credentialId), config.hkdfInfo) };
1335
1357
  } catch (err) {
1336
- if (err instanceof PasskeyUnsupportedError) await forgetCredential(registration.credentialId);
1337
- throw err;
1358
+ if (!(err instanceof PasskeyUnsupportedError) || err.reason !== "no-prf") throw err;
1338
1359
  }
1339
- }
1340
- async function seal(secret, privateKeyHex) {
1341
- 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) {
1342
1372
  const iv = randomBytes(12);
1343
1373
  const ciphertext = await sealPrivateKey(aes, iv, privateKeyHex);
1344
1374
  return { iv: encodeBase64(iv), ciphertext: encodeBase64(ciphertext) };
@@ -1346,36 +1376,37 @@ function createPasskeyOwner(config) {
1346
1376
  return {
1347
1377
  async createOwnerKey() {
1348
1378
  const registration = await registerPasskey();
1349
- const secret = await secretFromRegistration(registration);
1379
+ const { aes, lock } = await lockFor(registration);
1350
1380
  const { privateKeyHex, publicKeyHex } = generateStarkKeyPair();
1351
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1381
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1352
1382
  return {
1353
1383
  sealed: {
1354
1384
  credentialId: registration.credentialId,
1355
1385
  ownerPubKey: publicKeyHex,
1356
1386
  address: computeAccountAddress(publicKeyHex, 0),
1357
1387
  iv,
1358
- ciphertext
1388
+ ciphertext,
1389
+ ...lock ? { lock } : {}
1359
1390
  },
1360
1391
  privateKeyHex
1361
1392
  };
1362
1393
  },
1363
1394
  async unlockOwnerKey(sealed) {
1364
- const secret = await prfSecret(sealed.credentialId);
1365
- const aes = await deriveAesKey(secret, config.hkdfInfo);
1395
+ const aes = await unlockFor(sealed);
1366
1396
  return unsealPrivateKey(aes, decodeBase64(sealed.iv), decodeBase64(sealed.ciphertext));
1367
1397
  },
1368
1398
  async sealImportedOwnerKey(recovery) {
1369
1399
  const { privateKeyHex, publicKeyHex } = starkKeyPairFromPrivateKey(recovery.privateKey);
1370
1400
  const registration = await registerPasskey();
1371
- const secret = await secretFromRegistration(registration);
1372
- const { iv, ciphertext } = await seal(secret, privateKeyHex);
1401
+ const { aes, lock } = await lockFor(registration);
1402
+ const { iv, ciphertext } = await seal(aes, privateKeyHex);
1373
1403
  return {
1374
1404
  credentialId: registration.credentialId,
1375
1405
  ownerPubKey: publicKeyHex,
1376
1406
  address: recovery.walletAddress,
1377
1407
  iv,
1378
- ciphertext
1408
+ ciphertext,
1409
+ ...lock ? { lock } : {}
1379
1410
  };
1380
1411
  }
1381
1412
  };
@@ -1817,6 +1848,6 @@ var UserFacingError = class extends Error {
1817
1848
  }
1818
1849
  };
1819
1850
 
1820
- 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 };
1821
- //# sourceMappingURL=chunk-MOIM2AAQ.js.map
1822
- //# sourceMappingURL=chunk-MOIM2AAQ.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