thunder-bridge 2.1.1 → 2.2.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/README.md +38 -15
- package/dist/{bank-M8flcMks.d.ts → bank-B5MxT_6u.d.ts} +177 -15
- package/dist/{bank-CEIuNhV_.d.cts → bank-Bg6RjuO-.d.cts} +177 -15
- package/dist/bank.cjs +0 -1
- package/dist/bank.d.cts +2 -2
- package/dist/bank.d.ts +2 -2
- package/dist/bank.js +0 -1
- package/dist/{errors-DriqW-lp.d.cts → errors-DJmsalYZ.d.cts} +1 -1
- package/dist/{errors-ZEUT-MhO.d.ts → errors-VX0R18oE.d.ts} +1 -1
- package/dist/index.cjs +617 -90
- package/dist/index.d.cts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +616 -90
- package/dist/nwc.cjs +78 -21
- package/dist/nwc.d.cts +10 -113
- package/dist/nwc.d.ts +10 -113
- package/dist/nwc.js +78 -21
- package/dist/price.d.cts +1 -1
- package/dist/price.d.ts +1 -1
- package/openapi.yaml +2 -2
- package/package.json +1 -1
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: () =>
|
|
28
|
+
nwcRail: () => nwcRail2,
|
|
29
29
|
nwcSettlement: () => nwcSettlement,
|
|
30
|
-
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 =
|
|
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
|
|
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 `${
|
|
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
|
-
|
|
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 =
|
|
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
|
-
{
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
2
|
-
|
|
1
|
+
import { n as nwcRail$1, f as nwcVerifyEndpoint$1 } from './bank-Bg6RjuO-.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-Bg6RjuO-.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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
17
|
+
declare const nwcVerifyEndpoint: typeof nwcVerifyEndpoint$1;
|
|
121
18
|
|
|
122
|
-
export {
|
|
19
|
+
export { nwcRail, nwcVerifyEndpoint };
|
package/dist/nwc.d.ts
CHANGED
|
@@ -1,122 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { n as nwcRail$1, f as nwcVerifyEndpoint$1 } from './bank-B5MxT_6u.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-B5MxT_6u.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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
17
|
+
declare const nwcVerifyEndpoint: typeof nwcVerifyEndpoint$1;
|
|
121
18
|
|
|
122
|
-
export {
|
|
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 =
|
|
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
|
|
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 `${
|
|
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
|
-
|
|
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 =
|
|
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
|
-
{
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
/**
|