@getalby/lightning-tools 8.0.0 → 8.1.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.
package/dist/cjs/402.cjs CHANGED
@@ -1176,6 +1176,19 @@ const decodeInvoice = (paymentRequest) => {
1176
1176
  return null;
1177
1177
  }
1178
1178
  };
1179
+ function validatePreimage(preimage, paymentHash) {
1180
+ try {
1181
+ if (!/^[0-9a-fA-F]{64}$/.test(preimage))
1182
+ return false;
1183
+ if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
1184
+ return false;
1185
+ const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
1186
+ return paymentHash === preimageHash;
1187
+ }
1188
+ catch {
1189
+ return false;
1190
+ }
1191
+ }
1179
1192
 
1180
1193
  class Invoice {
1181
1194
  constructor(args) {
@@ -1215,13 +1228,7 @@ class Invoice {
1215
1228
  validatePreimage(preimage) {
1216
1229
  if (!preimage || !this.paymentHash)
1217
1230
  return false;
1218
- try {
1219
- const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
1220
- return this.paymentHash === preimageHash;
1221
- }
1222
- catch {
1223
- return false;
1224
- }
1231
+ return validatePreimage(preimage, this.paymentHash);
1225
1232
  }
1226
1233
  async verifyPayment() {
1227
1234
  try {
@@ -1264,6 +1271,11 @@ function createGuardedWallet(wallet, maxAmountSats) {
1264
1271
  };
1265
1272
  }
1266
1273
 
1274
+ /**
1275
+ * Client: parse "www-authenticate" header from server response
1276
+ * @param input
1277
+ * @returns details from the header value (token or macaroon, invoice)
1278
+ */
1267
1279
  const parseL402 = (input) => {
1268
1280
  // Remove the L402 and LSAT identifiers
1269
1281
  const string = input.replace("L402", "").replace("LSAT", "").trim();
@@ -1278,6 +1290,19 @@ const parseL402 = (input) => {
1278
1290
  // Value is either match[3] (double-quoted), match[4] (single-quoted), or match[5] (unquoted)
1279
1291
  keyValuePairs[match[1]] = match[3] || match[4] || match[5];
1280
1292
  }
1293
+ if (!keyValuePairs["token"] && keyValuePairs["macaroon"]) {
1294
+ // fallback to old naming
1295
+ keyValuePairs["token"] = keyValuePairs["macaroon"];
1296
+ delete keyValuePairs["macaroon"];
1297
+ }
1298
+ if (!("token" in keyValuePairs) ||
1299
+ typeof keyValuePairs["token"] !== "string") {
1300
+ throw new Error("No macaroon or token found in www-authenticate header");
1301
+ }
1302
+ if (!("invoice" in keyValuePairs) ||
1303
+ typeof keyValuePairs["invoice"] !== "string") {
1304
+ throw new Error("No invoice found in www-authenticate header");
1305
+ }
1281
1306
  return keyValuePairs;
1282
1307
  };
1283
1308
 
@@ -1577,9 +1602,93 @@ const fetch402 = async (url, fetchArgs, options) => {
1577
1602
  return initResp;
1578
1603
  };
1579
1604
 
1605
+ async function issueL402Macaroon(secret, paymentHash, params) {
1606
+ if (params !== undefined &&
1607
+ Object.prototype.hasOwnProperty.call(params, "paymentHash")) {
1608
+ throw new Error("paymentHash is reserved");
1609
+ }
1610
+ const payload = { ...params, paymentHash };
1611
+ const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
1612
+ const mac = await sign(secret, encoded);
1613
+ return `${encoded}.${mac}`;
1614
+ }
1615
+ async function verifyL402Macaroon(secret, token) {
1616
+ const { timingSafeEqual } = await import('crypto');
1617
+ const dotIndex = token.lastIndexOf(".");
1618
+ if (dotIndex === -1)
1619
+ throw new Error("Invalid macaroon token");
1620
+ const encoded = token.slice(0, dotIndex);
1621
+ const mac = token.slice(dotIndex + 1);
1622
+ // Constant-time comparison to prevent timing attacks
1623
+ const expectedMac = await sign(secret, encoded);
1624
+ try {
1625
+ if (!timingSafeEqual(Buffer.from(mac, "hex"), Buffer.from(expectedMac, "hex"))) {
1626
+ throw new Error("Invalid macaroon token");
1627
+ }
1628
+ }
1629
+ catch (e) {
1630
+ throw new Error("Invalid macaroon token");
1631
+ }
1632
+ try {
1633
+ const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
1634
+ if (parsed === null ||
1635
+ typeof parsed !== "object" ||
1636
+ Array.isArray(parsed) ||
1637
+ typeof parsed.paymentHash !== "string") {
1638
+ throw new Error("Invalid macaroon payload");
1639
+ }
1640
+ return parsed;
1641
+ }
1642
+ catch {
1643
+ throw new Error("Invalid macaroon token");
1644
+ }
1645
+ }
1646
+ async function sign(secret, payload) {
1647
+ const { createHmac } = await import('crypto');
1648
+ return createHmac("sha256", secret).update(payload).digest("hex");
1649
+ }
1650
+
1651
+ /**
1652
+ * Server: create a WWW-Authenticate header for a given macaroon and invoice
1653
+ * @param args the macaroon/token and invoice generated for the client's request
1654
+ * @returns the header value
1655
+ */
1656
+ const makeL402AuthenticateHeader = (args) => {
1657
+ if (!args.token) {
1658
+ throw new Error("token must be provided");
1659
+ }
1660
+ return `L402 version="0" token="${args.token}", invoice="${args.invoice}"`;
1661
+ };
1662
+ /**
1663
+ * Server: parse "authorization" header sent from client
1664
+ * @param input value from authorization header
1665
+ * @returns the macaroon and preimage
1666
+ */
1667
+ function parseL402Authorization(input) {
1668
+ // Backwards compat: LSAT was the former name of L402
1669
+ const normalized = input.replace(/^LSAT /, "L402 ");
1670
+ const prefix = "L402 ";
1671
+ if (!normalized.startsWith(prefix))
1672
+ return null;
1673
+ const credentials = normalized.slice(prefix.length);
1674
+ const colonIndex = credentials.indexOf(":");
1675
+ if (colonIndex === -1) {
1676
+ throw new Error("Invalid authorization header value");
1677
+ }
1678
+ return {
1679
+ token: credentials.slice(0, colonIndex),
1680
+ preimage: credentials.slice(colonIndex + 1),
1681
+ };
1682
+ }
1683
+
1580
1684
  exports.createGuardedWallet = createGuardedWallet;
1581
1685
  exports.fetch402 = fetch402;
1582
1686
  exports.fetchWithL402 = fetchWithL402;
1583
1687
  exports.fetchWithMpp = fetchWithMpp;
1584
1688
  exports.fetchWithX402 = fetchWithX402;
1689
+ exports.issueL402Macaroon = issueL402Macaroon;
1690
+ exports.makeL402AuthenticateHeader = makeL402AuthenticateHeader;
1691
+ exports.parseL402 = parseL402;
1692
+ exports.parseL402Authorization = parseL402Authorization;
1693
+ exports.verifyL402Macaroon = verifyL402Macaroon;
1585
1694
  //# sourceMappingURL=402.cjs.map