thunder-bridge 2.1.1 → 2.2.1

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/nwc.cjs CHANGED
@@ -25,9 +25,9 @@ __export(nwc_exports, {
25
25
  nwcHoldInvoice: () => nwcHoldInvoice,
26
26
  nwcInvoice: () => nwcInvoice,
27
27
  nwcPay: () => nwcPay,
28
- nwcRail: () => nwcRail,
28
+ nwcRail: () => nwcRail2,
29
29
  nwcSettlement: () => nwcSettlement,
30
- nwcVerifyEndpoint: () => nwcVerifyEndpoint,
30
+ nwcVerifyEndpoint: () => nwcVerifyEndpoint2,
31
31
  nwcVerifyUrl: () => nwcVerifyUrl
32
32
  });
33
33
  module.exports = __toCommonJS(nwc_exports);
@@ -365,58 +365,106 @@ var WalletRefused = class extends Error {
365
365
  }
366
366
  };
367
367
 
368
+ // ../core/hmac.ts
369
+ async function hmacHex(secret, payload) {
370
+ const raw = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
371
+ const key = await crypto.subtle.importKey(
372
+ "raw",
373
+ asBuffer(raw),
374
+ { name: "HMAC", hash: "SHA-256" },
375
+ false,
376
+ ["sign"]
377
+ );
378
+ const message = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
379
+ return bytesToHex(new Uint8Array(await crypto.subtle.sign("HMAC", key, asBuffer(message))));
380
+ }
381
+ function asBuffer(bytes) {
382
+ return bytes instanceof Uint8Array && bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
383
+ }
384
+
368
385
  // ../core/sealed.ts
369
386
  var VERSION = "v2";
370
387
  var UNPADDED_VERSION = "v1";
388
+ var PURPOSE_VERSION = "v3";
371
389
  var IV_BYTES = 12;
372
390
  var MIN_SECRET_CHARS = 32;
373
391
  var MAX_PLAIN_BYTES = 3e3;
374
392
  var SIZE_CLASSES = [256, 1024, MAX_PLAIN_BYTES];
375
393
  var END_OF_TEXT = 128;
376
- var INFO = new TextEncoder().encode("thunder-bridge/sealed");
394
+ var INFO = "thunder-bridge/sealed";
377
395
  async function seal(secret, plaintext, paymentHash) {
378
396
  return await sealUnder(
379
- secret,
397
+ await keyFor(secret, INFO),
380
398
  plaintext,
381
399
  crypto.getRandomValues(new Uint8Array(IV_BYTES)),
400
+ VERSION,
382
401
  boundTo(paymentHash)
383
402
  );
384
403
  }
385
- async function sealUnder(secret, plaintext, iv, additionalData) {
404
+ async function sealFor(purpose, secret, plaintext, { stable = false } = {}) {
405
+ return await sealUnder(
406
+ await keyFor(secret, `${INFO}/${purpose}`),
407
+ plaintext,
408
+ stable ? await nonceOf(secret, plaintext) : crypto.getRandomValues(new Uint8Array(IV_BYTES)),
409
+ PURPOSE_VERSION,
410
+ new TextEncoder().encode(PURPOSE_VERSION)
411
+ );
412
+ }
413
+ async function unsealFor(purpose, secret, sealed) {
414
+ if (!sealed.startsWith(`${PURPOSE_VERSION}.`)) {
415
+ return await unseal(secret, sealed);
416
+ }
417
+ return await opened(
418
+ await keyFor(secret, `${INFO}/${purpose}`),
419
+ sealed.slice(PURPOSE_VERSION.length + 1),
420
+ new TextEncoder().encode(PURPOSE_VERSION)
421
+ );
422
+ }
423
+ async function nonceOf(secret, plaintext) {
424
+ const derived = await hmacHex(secret, `sealed-nonce|${plaintext}`);
425
+ const nonce = new Uint8Array(IV_BYTES);
426
+ for (let at = 0; at < IV_BYTES; at++) {
427
+ nonce[at] = Number.parseInt(derived.slice(at * 2, at * 2 + 2), 16);
428
+ }
429
+ return nonce;
430
+ }
431
+ async function sealUnder(key, plaintext, iv, version, additionalData) {
386
432
  const body = padToClass(plaintext);
387
- const key = await keyFor(secret);
388
433
  const cipher = new Uint8Array(
389
434
  await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, body)
390
435
  );
391
436
  const joined2 = new Uint8Array(iv.length + cipher.length);
392
437
  joined2.set(iv);
393
438
  joined2.set(cipher, iv.length);
394
- return `${VERSION}.${toBase64Url(joined2)}`;
439
+ return `${version}.${toBase64Url(joined2)}`;
395
440
  }
396
441
  async function unseal(secret, sealed, paymentHash) {
397
- const key = await keyFor(secret);
442
+ const key = await keyFor(secret, INFO);
398
443
  const padded2 = sealed.startsWith(`${VERSION}.`);
399
444
  const unbound = !padded2 && sealed.startsWith(`${UNPADDED_VERSION}.`);
400
445
  if (!padded2 && (!unbound || paymentHash !== void 0)) {
401
446
  return null;
402
447
  }
403
- const joined2 = fromBase64Url(sealed.slice(VERSION.length + 1));
448
+ return await opened(
449
+ key,
450
+ sealed.slice(VERSION.length + 1),
451
+ padded2 ? boundTo(paymentHash) : void 0
452
+ );
453
+ }
454
+ async function opened(key, encoded, additionalData) {
455
+ const joined2 = fromBase64Url(encoded);
404
456
  if (joined2 === null || joined2.length <= IV_BYTES) {
405
457
  return null;
406
458
  }
407
459
  try {
408
460
  const body = new Uint8Array(
409
461
  await crypto.subtle.decrypt(
410
- {
411
- name: "AES-GCM",
412
- iv: joined2.slice(0, IV_BYTES),
413
- additionalData: padded2 ? boundTo(paymentHash) : void 0
414
- },
462
+ { name: "AES-GCM", iv: joined2.slice(0, IV_BYTES), additionalData },
415
463
  key,
416
464
  joined2.slice(IV_BYTES)
417
465
  )
418
466
  );
419
- const text = padded2 ? unpad(body) : body;
467
+ const text = additionalData === void 0 ? body : unpad(body);
420
468
  return text === null ? null : new TextDecoder().decode(text);
421
469
  } catch {
422
470
  return null;
@@ -447,7 +495,7 @@ function unpad(body) {
447
495
  const end = body.lastIndexOf(END_OF_TEXT);
448
496
  return end === -1 ? null : body.subarray(0, end);
449
497
  }
450
- async function keyFor(secret) {
498
+ async function keyFor(secret, info) {
451
499
  refuseAWeakSecret(secret);
452
500
  const material = await crypto.subtle.importKey(
453
501
  "raw",
@@ -457,7 +505,12 @@ async function keyFor(secret) {
457
505
  ["deriveKey"]
458
506
  );
459
507
  return crypto.subtle.deriveKey(
460
- { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: INFO },
508
+ {
509
+ name: "HKDF",
510
+ hash: "SHA-256",
511
+ salt: new Uint8Array(0),
512
+ info: new TextEncoder().encode(info)
513
+ },
461
514
  material,
462
515
  { name: "AES-GCM", length: 256 },
463
516
  false,
@@ -882,7 +935,7 @@ async function answerVerifyChallenge(request) {
882
935
  if (request.method !== "POST") {
883
936
  return null;
884
937
  }
885
- const nonce = nonceOf(await request.clone().text(), VERIFY_CHALLENGE);
938
+ const nonce = nonceOf2(await request.clone().text(), VERIFY_CHALLENGE);
886
939
  return nonce === null ? null : answered(nonce);
887
940
  }
888
941
  function answered(nonce) {
@@ -890,7 +943,7 @@ function answered(nonce) {
890
943
  headers: { "content-type": "application/json" }
891
944
  });
892
945
  }
893
- function nonceOf(text, type) {
946
+ function nonceOf2(text, type) {
894
947
  try {
895
948
  const said = JSON.parse(text);
896
949
  if (said["type"] !== type || typeof said["nonce"] !== "string") {
@@ -1036,7 +1089,7 @@ function nwcVerifyEndpoint(config) {
1036
1089
  if (sealed === null) {
1037
1090
  return Response.json({ settled: false }, { status: 400 });
1038
1091
  }
1039
- const paymentHash = await unseal(config.secret, sealed);
1092
+ const paymentHash = await unsealFor("nwc-verify", config.secret, sealed);
1040
1093
  if (paymentHash === null) {
1041
1094
  return Response.json({ settled: false }, { status: 403 });
1042
1095
  }
@@ -1051,7 +1104,7 @@ function nwcVerifyEndpoint(config) {
1051
1104
  }
1052
1105
  async function nwcVerifyUrl(endpoint, paymentHash, secret) {
1053
1106
  const sealed = new URL(endpoint);
1054
- sealed.searchParams.set(HASH, await seal(secret, paymentHash));
1107
+ sealed.searchParams.set(HASH, await sealFor("nwc-verify", secret, paymentHash));
1055
1108
  return sealed.toString();
1056
1109
  }
1057
1110
  async function askWallet(connection, method, params, timeoutMs = DEFAULT_ASK_TIMEOUT_MS) {
@@ -1222,6 +1275,10 @@ function nwcRail(gateway, config) {
1222
1275
  };
1223
1276
  };
1224
1277
  }
1278
+
1279
+ // src/entry/nwc.ts
1280
+ var nwcRail2 = nwcRail;
1281
+ var nwcVerifyEndpoint2 = nwcVerifyEndpoint;
1225
1282
  // Annotate the CommonJS export names for ESM import in node:
1226
1283
  0 && (module.exports = {
1227
1284
  askWallet,
package/dist/nwc.d.cts CHANGED
@@ -1,122 +1,19 @@
1
- import { A as Amount, T as Ticker } from './types-DYZ9EkmJ.cjs';
2
- import { R as RailConfig, O as Order, T as ThunderBridge, f as Rail } from './bank-CEIuNhV_.cjs';
1
+ import { n as nwcRail$1, f as nwcVerifyEndpoint$1 } from './bank-DvcV85sa.cjs';
2
+ export { N as NwcConnection, g as NwcInvoice, h as NwcRailConfig, i as NwcVerifyConfig, j as askWallet, k as nwcConnection, l as nwcHoldInvoice, m as nwcInvoice, o as nwcPay, p as nwcSettlement, q as nwcVerifyUrl } from './bank-DvcV85sa.cjs';
3
3
  import './qr-CF-YeXU1.cjs';
4
+ import './types-DYZ9EkmJ.cjs';
4
5
 
5
- /** A wallet reachable over NIP-47, as its `nostr+walletconnect://` URI describes it */
6
- interface NwcConnection {
7
- /** The wallet service's public key, which is what its answers have to be signed by */
8
- walletPubkey: string;
9
- /** Where to reach it, tried in order until one answers */
10
- relays: string[];
11
- /** Our own private key on this connection, and the only thing that authorises it */
12
- secret: string;
13
- }
14
- /** A minted invoice and everything needed to watch it */
15
- interface NwcInvoice {
16
- bolt11: string;
17
- paymentHash: string;
18
- expiresAt: number;
19
- }
20
- /** The endpoint the gateway polls for an NWC payment, answering off your own wallet */
21
- interface NwcVerifyConfig {
22
- /** The wallet this endpoint speaks for. It never leaves this process */
23
- connection: NwcConnection;
24
- /** The secret the payment hash was sealed with, and nothing else uses it */
25
- secret: string;
26
- /** How often the gateway should ask, in seconds, sent as `Cache-Control: max-age`, `5` by default */
27
- pollEverySecs?: number;
28
- /** How long one `lookup_invoice` may take before the wallet counts as unreachable, `10_000` by default */
29
- askTimeoutMs?: number;
30
- }
31
6
  /**
32
- * Read a `nostr+walletconnect://` URI. Refuses a relay that is not `wss`, for the
33
- * reason the gateway refuses a verify URL that is not https
34
- */
35
- declare function nwcConnection(uri: string): NwcConnection;
36
- /** Mint an invoice on the connected wallet, decoded so the caller need not trust its word */
37
- declare function nwcInvoice(connection: NwcConnection, amountMsat: number, description: string, timeoutMs?: number): Promise<NwcInvoice>;
38
- /**
39
- * Mint a hold invoice on a hash the wallet does not hold the preimage for, which
40
- * is what lets an operator be paid only by paying somebody else first. The hash
41
- * has to come from the recipient's own invoice, and the invoice that comes back
42
- * is decoded rather than believed
43
- */
44
- declare function nwcHoldInvoice(connection: NwcConnection, held: {
45
- paymentHash: string;
46
- amountMsat: number;
47
- description: string;
48
- expirySecs: number;
49
- minCltvExpiryDelta?: number;
50
- }, timeoutMs?: number): Promise<NwcInvoice>;
51
- /**
52
- * The preimage the wallet released for this hash, null while it has released
53
- * none. A preimage that does not hash to what was asked for is a lie rather than
54
- * an answer, so it throws instead of being passed on
55
- */
56
- declare function nwcSettlement(connection: NwcConnection, paymentHash: string, timeoutMs?: number): Promise<string | null>;
57
- /**
58
- * Pay an invoice and keep the preimage the network handed back. Whoever pays
59
- * learns it, which is what makes delivery provable to a recipient publishing no
60
- * LUD-21 of their own. A preimage that does not hash to the invoice's own hash is
61
- * a lie rather than a receipt, so it throws instead of being passed on
62
- */
63
- declare function nwcPay(connection: NwcConnection, bolt11: string, timeoutMs?: number): Promise<string>;
64
- /**
65
- * A verify endpoint of your own that asks your wallet over NIP-47, so the gateway
66
- * polls you and never learns the connection, the relay, or which wallet it is.
67
- *
68
- * `nwcVerifyUrl` seals the payment hash into the query with your secret, which is
69
- * what stops a stranger driving your wallet through this handler. It answers the
70
- * LUD-21 shape the gateway already speaks, so nothing on that side changes.
7
+ * The NWC rail as a free function, which is how it was reached before 2.2.0
71
8
  *
72
- * A wallet it cannot reach answers `502` rather than "not settled", because those
73
- * are different claims and only one of them is true.
74
- */
75
- declare function nwcVerifyEndpoint(config: NwcVerifyConfig): (request: Request) => Promise<Response>;
76
- /**
77
- * The URL to hand the gateway, with the payment hash sealed inside it. Point it at
78
- * wherever `nwcVerifyEndpoint` is mounted
79
- */
80
- declare function nwcVerifyUrl(endpoint: string, paymentHash: string, secret: string): Promise<string>;
81
- /**
82
- * One NIP-47 call, for a method this SDK does not wrap. The wallet's own info
83
- * event lists what it will answer, and anything it refuses comes back as a
84
- * `WalletRefused` whose `reason` says which kind of refusal it was
9
+ * @deprecated Use `gateway.rails.nwc(config)`, the same rail beside every other one
85
10
  */
86
- declare function askWallet(connection: NwcConnection, method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<Record<string, unknown>>;
87
- /** A Lightning rail minting on a wallet of your own over NIP-47, bound once per shop */
88
- interface NwcRailConfig extends RailConfig {
89
- /** The wallet that mints, which never leaves this process */
90
- connection: NwcConnection;
91
- /**
92
- * What to charge for one order, the order's own price converted at `rate` by
93
- * default. Give it a function and the price is whatever you say
94
- */
95
- amount?: (order: Order) => Amount;
96
- /** Where the default conversion gets its rate, the median of four venues by default */
97
- rate?: Ticker;
98
- /** Where `nwcVerifyEndpoint` is mounted, and the secret the hash is sealed with */
99
- verifyThrough: {
100
- endpoint: string;
101
- secret: string;
102
- };
103
- /** What the payer's wallet shows, the order's reference by default */
104
- description?: (order: Order) => string;
105
- /** Sealed before the gateway sees it, the way the blind Lightning rail does */
106
- sealed?: {
107
- secret: string;
108
- data: (order: Order) => unknown;
109
- };
110
- }
11
+ declare const nwcRail: typeof nwcRail$1;
111
12
  /**
112
- * Sell for Lightning against a wallet of your own over NIP-47, for a wallet that
113
- * has no LUD-21 address to be watched at. Your node mints the invoice and releases
114
- * the preimage, so the proof comes from one hop nearer than any hosted address can
115
- * manage, and the gateway sees a hash and a URL of yours.
13
+ * The NWC verify endpoint as a free function, which is how it was reached before 2.2.0
116
14
  *
117
- * This rail lives here rather than on `gateway.rails` because NIP-47 needs the
118
- * nostr crypto in this module, and a browser showing a QR should not download it
15
+ * @deprecated Use `gateway.serve.nwcVerify(config)`, beside every other verify endpoint
119
16
  */
120
- declare function nwcRail(gateway: ThunderBridge, config: NwcRailConfig): Rail;
17
+ declare const nwcVerifyEndpoint: typeof nwcVerifyEndpoint$1;
121
18
 
122
- export { type NwcConnection, type NwcInvoice, type NwcRailConfig, type NwcVerifyConfig, askWallet, nwcConnection, nwcHoldInvoice, nwcInvoice, nwcPay, nwcRail, nwcSettlement, nwcVerifyEndpoint, nwcVerifyUrl };
19
+ export { nwcRail, nwcVerifyEndpoint };
package/dist/nwc.d.ts CHANGED
@@ -1,122 +1,19 @@
1
- import { A as Amount, T as Ticker } from './types-DYZ9EkmJ.js';
2
- import { R as RailConfig, O as Order, T as ThunderBridge, f as Rail } from './bank-M8flcMks.js';
1
+ import { n as nwcRail$1, f as nwcVerifyEndpoint$1 } from './bank-BgA45yOU.js';
2
+ export { N as NwcConnection, g as NwcInvoice, h as NwcRailConfig, i as NwcVerifyConfig, j as askWallet, k as nwcConnection, l as nwcHoldInvoice, m as nwcInvoice, o as nwcPay, p as nwcSettlement, q as nwcVerifyUrl } from './bank-BgA45yOU.js';
3
3
  import './qr-CF-YeXU1.js';
4
+ import './types-DYZ9EkmJ.js';
4
5
 
5
- /** A wallet reachable over NIP-47, as its `nostr+walletconnect://` URI describes it */
6
- interface NwcConnection {
7
- /** The wallet service's public key, which is what its answers have to be signed by */
8
- walletPubkey: string;
9
- /** Where to reach it, tried in order until one answers */
10
- relays: string[];
11
- /** Our own private key on this connection, and the only thing that authorises it */
12
- secret: string;
13
- }
14
- /** A minted invoice and everything needed to watch it */
15
- interface NwcInvoice {
16
- bolt11: string;
17
- paymentHash: string;
18
- expiresAt: number;
19
- }
20
- /** The endpoint the gateway polls for an NWC payment, answering off your own wallet */
21
- interface NwcVerifyConfig {
22
- /** The wallet this endpoint speaks for. It never leaves this process */
23
- connection: NwcConnection;
24
- /** The secret the payment hash was sealed with, and nothing else uses it */
25
- secret: string;
26
- /** How often the gateway should ask, in seconds, sent as `Cache-Control: max-age`, `5` by default */
27
- pollEverySecs?: number;
28
- /** How long one `lookup_invoice` may take before the wallet counts as unreachable, `10_000` by default */
29
- askTimeoutMs?: number;
30
- }
31
6
  /**
32
- * Read a `nostr+walletconnect://` URI. Refuses a relay that is not `wss`, for the
33
- * reason the gateway refuses a verify URL that is not https
34
- */
35
- declare function nwcConnection(uri: string): NwcConnection;
36
- /** Mint an invoice on the connected wallet, decoded so the caller need not trust its word */
37
- declare function nwcInvoice(connection: NwcConnection, amountMsat: number, description: string, timeoutMs?: number): Promise<NwcInvoice>;
38
- /**
39
- * Mint a hold invoice on a hash the wallet does not hold the preimage for, which
40
- * is what lets an operator be paid only by paying somebody else first. The hash
41
- * has to come from the recipient's own invoice, and the invoice that comes back
42
- * is decoded rather than believed
43
- */
44
- declare function nwcHoldInvoice(connection: NwcConnection, held: {
45
- paymentHash: string;
46
- amountMsat: number;
47
- description: string;
48
- expirySecs: number;
49
- minCltvExpiryDelta?: number;
50
- }, timeoutMs?: number): Promise<NwcInvoice>;
51
- /**
52
- * The preimage the wallet released for this hash, null while it has released
53
- * none. A preimage that does not hash to what was asked for is a lie rather than
54
- * an answer, so it throws instead of being passed on
55
- */
56
- declare function nwcSettlement(connection: NwcConnection, paymentHash: string, timeoutMs?: number): Promise<string | null>;
57
- /**
58
- * Pay an invoice and keep the preimage the network handed back. Whoever pays
59
- * learns it, which is what makes delivery provable to a recipient publishing no
60
- * LUD-21 of their own. A preimage that does not hash to the invoice's own hash is
61
- * a lie rather than a receipt, so it throws instead of being passed on
62
- */
63
- declare function nwcPay(connection: NwcConnection, bolt11: string, timeoutMs?: number): Promise<string>;
64
- /**
65
- * A verify endpoint of your own that asks your wallet over NIP-47, so the gateway
66
- * polls you and never learns the connection, the relay, or which wallet it is.
67
- *
68
- * `nwcVerifyUrl` seals the payment hash into the query with your secret, which is
69
- * what stops a stranger driving your wallet through this handler. It answers the
70
- * LUD-21 shape the gateway already speaks, so nothing on that side changes.
7
+ * The NWC rail as a free function, which is how it was reached before 2.2.0
71
8
  *
72
- * A wallet it cannot reach answers `502` rather than "not settled", because those
73
- * are different claims and only one of them is true.
74
- */
75
- declare function nwcVerifyEndpoint(config: NwcVerifyConfig): (request: Request) => Promise<Response>;
76
- /**
77
- * The URL to hand the gateway, with the payment hash sealed inside it. Point it at
78
- * wherever `nwcVerifyEndpoint` is mounted
79
- */
80
- declare function nwcVerifyUrl(endpoint: string, paymentHash: string, secret: string): Promise<string>;
81
- /**
82
- * One NIP-47 call, for a method this SDK does not wrap. The wallet's own info
83
- * event lists what it will answer, and anything it refuses comes back as a
84
- * `WalletRefused` whose `reason` says which kind of refusal it was
9
+ * @deprecated Use `gateway.rails.nwc(config)`, the same rail beside every other one
85
10
  */
86
- declare function askWallet(connection: NwcConnection, method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<Record<string, unknown>>;
87
- /** A Lightning rail minting on a wallet of your own over NIP-47, bound once per shop */
88
- interface NwcRailConfig extends RailConfig {
89
- /** The wallet that mints, which never leaves this process */
90
- connection: NwcConnection;
91
- /**
92
- * What to charge for one order, the order's own price converted at `rate` by
93
- * default. Give it a function and the price is whatever you say
94
- */
95
- amount?: (order: Order) => Amount;
96
- /** Where the default conversion gets its rate, the median of four venues by default */
97
- rate?: Ticker;
98
- /** Where `nwcVerifyEndpoint` is mounted, and the secret the hash is sealed with */
99
- verifyThrough: {
100
- endpoint: string;
101
- secret: string;
102
- };
103
- /** What the payer's wallet shows, the order's reference by default */
104
- description?: (order: Order) => string;
105
- /** Sealed before the gateway sees it, the way the blind Lightning rail does */
106
- sealed?: {
107
- secret: string;
108
- data: (order: Order) => unknown;
109
- };
110
- }
11
+ declare const nwcRail: typeof nwcRail$1;
111
12
  /**
112
- * Sell for Lightning against a wallet of your own over NIP-47, for a wallet that
113
- * has no LUD-21 address to be watched at. Your node mints the invoice and releases
114
- * the preimage, so the proof comes from one hop nearer than any hosted address can
115
- * manage, and the gateway sees a hash and a URL of yours.
13
+ * The NWC verify endpoint as a free function, which is how it was reached before 2.2.0
116
14
  *
117
- * This rail lives here rather than on `gateway.rails` because NIP-47 needs the
118
- * nostr crypto in this module, and a browser showing a QR should not download it
15
+ * @deprecated Use `gateway.serve.nwcVerify(config)`, beside every other verify endpoint
119
16
  */
120
- declare function nwcRail(gateway: ThunderBridge, config: NwcRailConfig): Rail;
17
+ declare const nwcVerifyEndpoint: typeof nwcVerifyEndpoint$1;
121
18
 
122
- export { type NwcConnection, type NwcInvoice, type NwcRailConfig, type NwcVerifyConfig, askWallet, nwcConnection, nwcHoldInvoice, nwcInvoice, nwcPay, nwcRail, nwcSettlement, nwcVerifyEndpoint, nwcVerifyUrl };
19
+ export { nwcRail, nwcVerifyEndpoint };
package/dist/nwc.js CHANGED
@@ -331,58 +331,106 @@ var WalletRefused = class extends Error {
331
331
  }
332
332
  };
333
333
 
334
+ // ../core/hmac.ts
335
+ async function hmacHex(secret, payload) {
336
+ const raw = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
337
+ const key = await crypto.subtle.importKey(
338
+ "raw",
339
+ asBuffer(raw),
340
+ { name: "HMAC", hash: "SHA-256" },
341
+ false,
342
+ ["sign"]
343
+ );
344
+ const message = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
345
+ return bytesToHex(new Uint8Array(await crypto.subtle.sign("HMAC", key, asBuffer(message))));
346
+ }
347
+ function asBuffer(bytes) {
348
+ return bytes instanceof Uint8Array && bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
349
+ }
350
+
334
351
  // ../core/sealed.ts
335
352
  var VERSION = "v2";
336
353
  var UNPADDED_VERSION = "v1";
354
+ var PURPOSE_VERSION = "v3";
337
355
  var IV_BYTES = 12;
338
356
  var MIN_SECRET_CHARS = 32;
339
357
  var MAX_PLAIN_BYTES = 3e3;
340
358
  var SIZE_CLASSES = [256, 1024, MAX_PLAIN_BYTES];
341
359
  var END_OF_TEXT = 128;
342
- var INFO = new TextEncoder().encode("thunder-bridge/sealed");
360
+ var INFO = "thunder-bridge/sealed";
343
361
  async function seal(secret, plaintext, paymentHash) {
344
362
  return await sealUnder(
345
- secret,
363
+ await keyFor(secret, INFO),
346
364
  plaintext,
347
365
  crypto.getRandomValues(new Uint8Array(IV_BYTES)),
366
+ VERSION,
348
367
  boundTo(paymentHash)
349
368
  );
350
369
  }
351
- async function sealUnder(secret, plaintext, iv, additionalData) {
370
+ async function sealFor(purpose, secret, plaintext, { stable = false } = {}) {
371
+ return await sealUnder(
372
+ await keyFor(secret, `${INFO}/${purpose}`),
373
+ plaintext,
374
+ stable ? await nonceOf(secret, plaintext) : crypto.getRandomValues(new Uint8Array(IV_BYTES)),
375
+ PURPOSE_VERSION,
376
+ new TextEncoder().encode(PURPOSE_VERSION)
377
+ );
378
+ }
379
+ async function unsealFor(purpose, secret, sealed) {
380
+ if (!sealed.startsWith(`${PURPOSE_VERSION}.`)) {
381
+ return await unseal(secret, sealed);
382
+ }
383
+ return await opened(
384
+ await keyFor(secret, `${INFO}/${purpose}`),
385
+ sealed.slice(PURPOSE_VERSION.length + 1),
386
+ new TextEncoder().encode(PURPOSE_VERSION)
387
+ );
388
+ }
389
+ async function nonceOf(secret, plaintext) {
390
+ const derived = await hmacHex(secret, `sealed-nonce|${plaintext}`);
391
+ const nonce = new Uint8Array(IV_BYTES);
392
+ for (let at = 0; at < IV_BYTES; at++) {
393
+ nonce[at] = Number.parseInt(derived.slice(at * 2, at * 2 + 2), 16);
394
+ }
395
+ return nonce;
396
+ }
397
+ async function sealUnder(key, plaintext, iv, version, additionalData) {
352
398
  const body = padToClass(plaintext);
353
- const key = await keyFor(secret);
354
399
  const cipher = new Uint8Array(
355
400
  await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, body)
356
401
  );
357
402
  const joined2 = new Uint8Array(iv.length + cipher.length);
358
403
  joined2.set(iv);
359
404
  joined2.set(cipher, iv.length);
360
- return `${VERSION}.${toBase64Url(joined2)}`;
405
+ return `${version}.${toBase64Url(joined2)}`;
361
406
  }
362
407
  async function unseal(secret, sealed, paymentHash) {
363
- const key = await keyFor(secret);
408
+ const key = await keyFor(secret, INFO);
364
409
  const padded2 = sealed.startsWith(`${VERSION}.`);
365
410
  const unbound = !padded2 && sealed.startsWith(`${UNPADDED_VERSION}.`);
366
411
  if (!padded2 && (!unbound || paymentHash !== void 0)) {
367
412
  return null;
368
413
  }
369
- const joined2 = fromBase64Url(sealed.slice(VERSION.length + 1));
414
+ return await opened(
415
+ key,
416
+ sealed.slice(VERSION.length + 1),
417
+ padded2 ? boundTo(paymentHash) : void 0
418
+ );
419
+ }
420
+ async function opened(key, encoded, additionalData) {
421
+ const joined2 = fromBase64Url(encoded);
370
422
  if (joined2 === null || joined2.length <= IV_BYTES) {
371
423
  return null;
372
424
  }
373
425
  try {
374
426
  const body = new Uint8Array(
375
427
  await crypto.subtle.decrypt(
376
- {
377
- name: "AES-GCM",
378
- iv: joined2.slice(0, IV_BYTES),
379
- additionalData: padded2 ? boundTo(paymentHash) : void 0
380
- },
428
+ { name: "AES-GCM", iv: joined2.slice(0, IV_BYTES), additionalData },
381
429
  key,
382
430
  joined2.slice(IV_BYTES)
383
431
  )
384
432
  );
385
- const text = padded2 ? unpad(body) : body;
433
+ const text = additionalData === void 0 ? body : unpad(body);
386
434
  return text === null ? null : new TextDecoder().decode(text);
387
435
  } catch {
388
436
  return null;
@@ -413,7 +461,7 @@ function unpad(body) {
413
461
  const end = body.lastIndexOf(END_OF_TEXT);
414
462
  return end === -1 ? null : body.subarray(0, end);
415
463
  }
416
- async function keyFor(secret) {
464
+ async function keyFor(secret, info) {
417
465
  refuseAWeakSecret(secret);
418
466
  const material = await crypto.subtle.importKey(
419
467
  "raw",
@@ -423,7 +471,12 @@ async function keyFor(secret) {
423
471
  ["deriveKey"]
424
472
  );
425
473
  return crypto.subtle.deriveKey(
426
- { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: INFO },
474
+ {
475
+ name: "HKDF",
476
+ hash: "SHA-256",
477
+ salt: new Uint8Array(0),
478
+ info: new TextEncoder().encode(info)
479
+ },
427
480
  material,
428
481
  { name: "AES-GCM", length: 256 },
429
482
  false,
@@ -848,7 +901,7 @@ async function answerVerifyChallenge(request) {
848
901
  if (request.method !== "POST") {
849
902
  return null;
850
903
  }
851
- const nonce = nonceOf(await request.clone().text(), VERIFY_CHALLENGE);
904
+ const nonce = nonceOf2(await request.clone().text(), VERIFY_CHALLENGE);
852
905
  return nonce === null ? null : answered(nonce);
853
906
  }
854
907
  function answered(nonce) {
@@ -856,7 +909,7 @@ function answered(nonce) {
856
909
  headers: { "content-type": "application/json" }
857
910
  });
858
911
  }
859
- function nonceOf(text, type) {
912
+ function nonceOf2(text, type) {
860
913
  try {
861
914
  const said = JSON.parse(text);
862
915
  if (said["type"] !== type || typeof said["nonce"] !== "string") {
@@ -1002,7 +1055,7 @@ function nwcVerifyEndpoint(config) {
1002
1055
  if (sealed === null) {
1003
1056
  return Response.json({ settled: false }, { status: 400 });
1004
1057
  }
1005
- const paymentHash = await unseal(config.secret, sealed);
1058
+ const paymentHash = await unsealFor("nwc-verify", config.secret, sealed);
1006
1059
  if (paymentHash === null) {
1007
1060
  return Response.json({ settled: false }, { status: 403 });
1008
1061
  }
@@ -1017,7 +1070,7 @@ function nwcVerifyEndpoint(config) {
1017
1070
  }
1018
1071
  async function nwcVerifyUrl(endpoint, paymentHash, secret) {
1019
1072
  const sealed = new URL(endpoint);
1020
- sealed.searchParams.set(HASH, await seal(secret, paymentHash));
1073
+ sealed.searchParams.set(HASH, await sealFor("nwc-verify", secret, paymentHash));
1021
1074
  return sealed.toString();
1022
1075
  }
1023
1076
  async function askWallet(connection, method, params, timeoutMs = DEFAULT_ASK_TIMEOUT_MS) {
@@ -1188,14 +1241,18 @@ function nwcRail(gateway, config) {
1188
1241
  };
1189
1242
  };
1190
1243
  }
1244
+
1245
+ // src/entry/nwc.ts
1246
+ var nwcRail2 = nwcRail;
1247
+ var nwcVerifyEndpoint2 = nwcVerifyEndpoint;
1191
1248
  export {
1192
1249
  askWallet,
1193
1250
  nwcConnection,
1194
1251
  nwcHoldInvoice,
1195
1252
  nwcInvoice,
1196
1253
  nwcPay,
1197
- nwcRail,
1254
+ nwcRail2 as nwcRail,
1198
1255
  nwcSettlement,
1199
- nwcVerifyEndpoint,
1256
+ nwcVerifyEndpoint2 as nwcVerifyEndpoint,
1200
1257
  nwcVerifyUrl
1201
1258
  };
package/dist/price.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AmountError, a as AmountFault } from './errors-DriqW-lp.cjs';
1
+ export { A as AmountError, a as AmountFault } from './errors-DJmsalYZ.cjs';
2
2
  export { M as MedianOptions, T as Ticker, b as bitstamp, c as coinbase, a as coinmate, k as kraken, m as medianOf, d as msatFor } from './types-DYZ9EkmJ.cjs';
3
3
 
4
4
  /**