thunder-bridge 1.5.0 → 2.1.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/README.md +11 -9
- package/dist/{client-CzGZcByI.d.cts → bank-CEIuNhV_.d.cts} +191 -28
- package/dist/{client-Cc5gtjGV.d.ts → bank-M8flcMks.d.ts} +191 -28
- package/dist/bank.cjs +197 -0
- package/dist/bank.d.cts +4 -2
- package/dist/bank.d.ts +4 -2
- package/dist/bank.js +196 -0
- package/dist/{errors-Dmh-Uoh8.d.cts → errors-DriqW-lp.d.cts} +2 -2
- package/dist/{errors-0vbVoISA.d.ts → errors-ZEUT-MhO.d.ts} +2 -2
- package/dist/index.cjs +701 -310
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +701 -310
- package/dist/nwc.cjs +115 -31
- package/dist/nwc.d.cts +6 -4
- package/dist/nwc.d.ts +6 -4
- package/dist/nwc.js +112 -28
- package/dist/price.d.cts +2 -2
- package/dist/price.d.ts +2 -2
- package/dist/qr.cjs +20 -23
- package/dist/qr.js +20 -23
- package/dist/{types-BNPmVnA7.d.cts → types-DYZ9EkmJ.d.cts} +11 -2
- package/dist/{types-BNPmVnA7.d.ts → types-DYZ9EkmJ.d.ts} +11 -2
- package/openapi.yaml +29 -39
- package/package.json +1 -1
- package/dist/bank-B3mHISn1.d.cts +0 -92
- package/dist/bank-B3mHISn1.d.ts +0 -92
package/dist/nwc.cjs
CHANGED
|
@@ -206,6 +206,7 @@ function rotate(value, by) {
|
|
|
206
206
|
|
|
207
207
|
// ../core/bolt11.ts
|
|
208
208
|
var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
209
|
+
var BECH32_GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
209
210
|
var CHECKSUM_WORDS = 6;
|
|
210
211
|
var TIMESTAMP_WORDS = 7;
|
|
211
212
|
var SIGNATURE_WORDS = 104;
|
|
@@ -223,18 +224,27 @@ var HRP_MULTIPLIER_MSAT = {
|
|
|
223
224
|
n: MSAT_PER_BTC / 1e9,
|
|
224
225
|
p: MSAT_PER_BTC / 1e12
|
|
225
226
|
};
|
|
226
|
-
var
|
|
227
|
+
var HRP = /^ln(?:bcrt|bc|tbs|tb|sb)(?:(\d+)([munp]?))?$/;
|
|
227
228
|
function decodeInvoice(bolt11) {
|
|
228
229
|
const parts = splitBech32(bolt11);
|
|
229
230
|
if (parts === null) {
|
|
230
|
-
return {
|
|
231
|
+
return {
|
|
232
|
+
paymentHash: null,
|
|
233
|
+
descriptionHash: null,
|
|
234
|
+
amountMsat: null,
|
|
235
|
+
issuedAt: null,
|
|
236
|
+
expiresAt: null
|
|
237
|
+
};
|
|
231
238
|
}
|
|
232
239
|
const tagged = taggedFields(parts.words);
|
|
240
|
+
const issuedAt = issuedAtOf(parts.words);
|
|
241
|
+
const expiry = tagged.get(EXPIRY_TAG);
|
|
233
242
|
return {
|
|
234
243
|
paymentHash: hexTag(tagged, PAYMENT_HASH_TAG, PAYMENT_HASH_WORDS),
|
|
235
244
|
descriptionHash: hexTag(tagged, DESCRIPTION_HASH_TAG, DESCRIPTION_HASH_WORDS),
|
|
236
245
|
amountMsat: amountFromHrp(parts.hrp),
|
|
237
|
-
|
|
246
|
+
issuedAt,
|
|
247
|
+
expiresAt: issuedAt === null ? null : issuedAt + (expiry ? readNumber(expiry) : DEFAULT_EXPIRY_SECS)
|
|
238
248
|
};
|
|
239
249
|
}
|
|
240
250
|
function preimageMatchesHash(preimage, paymentHash) {
|
|
@@ -245,7 +255,7 @@ function preimageMatchesHash(preimage, paymentHash) {
|
|
|
245
255
|
}
|
|
246
256
|
function splitBech32(bolt11) {
|
|
247
257
|
const lower = bolt11.toLowerCase();
|
|
248
|
-
if (isBolt12Encoding(lower)) {
|
|
258
|
+
if (isBolt12Encoding(lower) || bolt11 !== lower && bolt11 !== bolt11.toUpperCase()) {
|
|
249
259
|
return null;
|
|
250
260
|
}
|
|
251
261
|
const separator = lower.lastIndexOf("1");
|
|
@@ -264,7 +274,28 @@ function splitBech32(bolt11) {
|
|
|
264
274
|
}
|
|
265
275
|
words.push(value);
|
|
266
276
|
}
|
|
267
|
-
|
|
277
|
+
const hrp = lower.slice(0, separator);
|
|
278
|
+
if (!HRP.test(hrp) || bech32Polymod([...expandedHrp(hrp), ...words]) !== 1) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
return { hrp, words: words.slice(0, -CHECKSUM_WORDS) };
|
|
282
|
+
}
|
|
283
|
+
function expandedHrp(hrp) {
|
|
284
|
+
const codes = [...hrp].map((char) => char.charCodeAt(0));
|
|
285
|
+
return [...codes.map((code) => code >> 5), 0, ...codes.map((code) => code & 31)];
|
|
286
|
+
}
|
|
287
|
+
function bech32Polymod(values) {
|
|
288
|
+
let checksum = 1;
|
|
289
|
+
for (const value of values) {
|
|
290
|
+
const top = checksum >> 25;
|
|
291
|
+
checksum = (checksum & 33554431) << 5 ^ value;
|
|
292
|
+
for (let bit = 0; bit < 5; bit++) {
|
|
293
|
+
if (top >> bit & 1) {
|
|
294
|
+
checksum ^= BECH32_GENERATOR[bit];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return checksum;
|
|
268
299
|
}
|
|
269
300
|
function isBolt12Encoding(lower) {
|
|
270
301
|
return lower.startsWith("lno") || lower.startsWith("lni") || lower.startsWith("lnr");
|
|
@@ -294,19 +325,17 @@ function hexTag(fields, tag, expectedWords) {
|
|
|
294
325
|
}
|
|
295
326
|
return bytesToHex(wordsToBytes(words));
|
|
296
327
|
}
|
|
297
|
-
function
|
|
328
|
+
function issuedAtOf(words) {
|
|
298
329
|
if (words.length < TIMESTAMP_WORDS + SIGNATURE_WORDS) {
|
|
299
330
|
return null;
|
|
300
331
|
}
|
|
301
|
-
|
|
302
|
-
return readNumber(words.slice(0, TIMESTAMP_WORDS)) + (expiry ? readNumber(expiry) : DEFAULT_EXPIRY_SECS);
|
|
332
|
+
return readNumber(words.slice(0, TIMESTAMP_WORDS));
|
|
303
333
|
}
|
|
304
334
|
function amountFromHrp(hrp) {
|
|
305
|
-
const
|
|
306
|
-
if (
|
|
335
|
+
const [, digits, multiplier] = HRP.exec(hrp) ?? [];
|
|
336
|
+
if (digits === void 0 || multiplier === "p" && !digits.endsWith("0")) {
|
|
307
337
|
return null;
|
|
308
338
|
}
|
|
309
|
-
const [, digits, multiplier] = match;
|
|
310
339
|
return multiplier ? Number(digits) * HRP_MULTIPLIER_MSAT[multiplier] : Number(digits) * MSAT_PER_BTC;
|
|
311
340
|
}
|
|
312
341
|
function readNumber(words) {
|
|
@@ -337,27 +366,38 @@ var WalletRefused = class extends Error {
|
|
|
337
366
|
};
|
|
338
367
|
|
|
339
368
|
// ../core/sealed.ts
|
|
340
|
-
var VERSION = "
|
|
369
|
+
var VERSION = "v2";
|
|
370
|
+
var UNPADDED_VERSION = "v1";
|
|
341
371
|
var IV_BYTES = 12;
|
|
342
372
|
var MIN_SECRET_CHARS = 32;
|
|
343
373
|
var MAX_PLAIN_BYTES = 3e3;
|
|
374
|
+
var SIZE_CLASSES = [256, 1024, MAX_PLAIN_BYTES];
|
|
375
|
+
var END_OF_TEXT = 128;
|
|
344
376
|
var INFO = new TextEncoder().encode("thunder-bridge/sealed");
|
|
345
|
-
async function seal(secret, plaintext) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
377
|
+
async function seal(secret, plaintext, paymentHash) {
|
|
378
|
+
return await sealUnder(
|
|
379
|
+
secret,
|
|
380
|
+
plaintext,
|
|
381
|
+
crypto.getRandomValues(new Uint8Array(IV_BYTES)),
|
|
382
|
+
boundTo(paymentHash)
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
async function sealUnder(secret, plaintext, iv, additionalData) {
|
|
386
|
+
const body = padToClass(plaintext);
|
|
350
387
|
const key = await keyFor(secret);
|
|
351
|
-
const
|
|
352
|
-
|
|
388
|
+
const cipher = new Uint8Array(
|
|
389
|
+
await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, body)
|
|
390
|
+
);
|
|
353
391
|
const joined2 = new Uint8Array(iv.length + cipher.length);
|
|
354
392
|
joined2.set(iv);
|
|
355
393
|
joined2.set(cipher, iv.length);
|
|
356
394
|
return `${VERSION}.${toBase64Url(joined2)}`;
|
|
357
395
|
}
|
|
358
|
-
async function unseal(secret, sealed) {
|
|
396
|
+
async function unseal(secret, sealed, paymentHash) {
|
|
359
397
|
const key = await keyFor(secret);
|
|
360
|
-
|
|
398
|
+
const padded2 = sealed.startsWith(`${VERSION}.`);
|
|
399
|
+
const unbound = !padded2 && sealed.startsWith(`${UNPADDED_VERSION}.`);
|
|
400
|
+
if (!padded2 && (!unbound || paymentHash !== void 0)) {
|
|
361
401
|
return null;
|
|
362
402
|
}
|
|
363
403
|
const joined2 = fromBase64Url(sealed.slice(VERSION.length + 1));
|
|
@@ -365,20 +405,50 @@ async function unseal(secret, sealed) {
|
|
|
365
405
|
return null;
|
|
366
406
|
}
|
|
367
407
|
try {
|
|
368
|
-
const body =
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
408
|
+
const body = new Uint8Array(
|
|
409
|
+
await crypto.subtle.decrypt(
|
|
410
|
+
{
|
|
411
|
+
name: "AES-GCM",
|
|
412
|
+
iv: joined2.slice(0, IV_BYTES),
|
|
413
|
+
additionalData: padded2 ? boundTo(paymentHash) : void 0
|
|
414
|
+
},
|
|
415
|
+
key,
|
|
416
|
+
joined2.slice(IV_BYTES)
|
|
417
|
+
)
|
|
372
418
|
);
|
|
373
|
-
|
|
419
|
+
const text = padded2 ? unpad(body) : body;
|
|
420
|
+
return text === null ? null : new TextDecoder().decode(text);
|
|
374
421
|
} catch {
|
|
375
422
|
return null;
|
|
376
423
|
}
|
|
377
424
|
}
|
|
378
|
-
|
|
425
|
+
function refuseAWeakSecret(secret) {
|
|
379
426
|
if (secret.length < MIN_SECRET_CHARS) {
|
|
380
427
|
throw new Error(`the sealing secret needs ${MIN_SECRET_CHARS} characters of randomness`);
|
|
381
428
|
}
|
|
429
|
+
}
|
|
430
|
+
function boundTo(paymentHash) {
|
|
431
|
+
return new TextEncoder().encode(
|
|
432
|
+
paymentHash === void 0 ? VERSION : `${VERSION}|${paymentHash.toLowerCase()}`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
function padToClass(plaintext) {
|
|
436
|
+
const text = new TextEncoder().encode(plaintext);
|
|
437
|
+
const size = SIZE_CLASSES.find((limit) => text.length <= limit);
|
|
438
|
+
if (size === void 0) {
|
|
439
|
+
throw new Error(`sealed takes at most ${MAX_PLAIN_BYTES} bytes, this was ${text.length}`);
|
|
440
|
+
}
|
|
441
|
+
const body = new Uint8Array(size + 1);
|
|
442
|
+
body.set(text);
|
|
443
|
+
body[text.length] = END_OF_TEXT;
|
|
444
|
+
return body;
|
|
445
|
+
}
|
|
446
|
+
function unpad(body) {
|
|
447
|
+
const end = body.lastIndexOf(END_OF_TEXT);
|
|
448
|
+
return end === -1 ? null : body.subarray(0, end);
|
|
449
|
+
}
|
|
450
|
+
async function keyFor(secret) {
|
|
451
|
+
refuseAWeakSecret(secret);
|
|
382
452
|
const material = await crypto.subtle.importKey(
|
|
383
453
|
"raw",
|
|
384
454
|
new TextEncoder().encode(secret),
|
|
@@ -421,7 +491,7 @@ function fromBase64Url(text) {
|
|
|
421
491
|
var import_chacha = require("@noble/ciphers/chacha.js");
|
|
422
492
|
var import_secp256k1 = require("@noble/curves/secp256k1.js");
|
|
423
493
|
var import_hkdf = require("@noble/hashes/hkdf.js");
|
|
424
|
-
var
|
|
494
|
+
var import_hmac2 = require("@noble/hashes/hmac.js");
|
|
425
495
|
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
426
496
|
var NIP44_SALT = new TextEncoder().encode("nip44-v2");
|
|
427
497
|
var NIP44_VERSION = 2;
|
|
@@ -479,7 +549,7 @@ function encryptNip44(conversationKey, plaintext) {
|
|
|
479
549
|
function sealNip44(conversationKey, plaintext, nonce) {
|
|
480
550
|
const keys = messageKeysFrom(conversationKey, nonce);
|
|
481
551
|
const ciphertext = (0, import_chacha.chacha20)(keys.chachaKey, keys.chachaNonce, padded(plaintext));
|
|
482
|
-
const mac = (0,
|
|
552
|
+
const mac = (0, import_hmac2.hmac)(import_sha2.sha256, keys.hmacKey, joined(nonce, ciphertext));
|
|
483
553
|
return toBase64(joined(Uint8Array.of(NIP44_VERSION), nonce, ciphertext, mac));
|
|
484
554
|
}
|
|
485
555
|
function decryptNip44(conversationKey, payload) {
|
|
@@ -497,7 +567,7 @@ function decryptNip44(conversationKey, payload) {
|
|
|
497
567
|
const ciphertext = raw.subarray(1 + NONCE_BYTES, raw.length - MAC_BYTES);
|
|
498
568
|
const mac = raw.subarray(raw.length - MAC_BYTES);
|
|
499
569
|
const keys = messageKeysFrom(conversationKey, nonce);
|
|
500
|
-
if (!sameBytes((0,
|
|
570
|
+
if (!sameBytes((0, import_hmac2.hmac)(import_sha2.sha256, keys.hmacKey, joined(nonce, ciphertext)), mac)) {
|
|
501
571
|
throw new Error("nip44 payload failed its mac, so it was not written with this key");
|
|
502
572
|
}
|
|
503
573
|
return unpadded((0, import_chacha.chacha20)(keys.chachaKey, keys.chachaNonce, ciphertext));
|
|
@@ -796,6 +866,15 @@ async function amountNow(amount) {
|
|
|
796
866
|
|
|
797
867
|
// ../core/ed25519.ts
|
|
798
868
|
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
869
|
+
var SMALL_ORDER = [
|
|
870
|
+
"00".repeat(32),
|
|
871
|
+
`01${"00".repeat(31)}`,
|
|
872
|
+
"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
|
|
873
|
+
"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
|
|
874
|
+
`ec${"ff".repeat(30)}7f`,
|
|
875
|
+
`ed${"ff".repeat(30)}7f`,
|
|
876
|
+
`ee${"ff".repeat(30)}7f`
|
|
877
|
+
];
|
|
799
878
|
|
|
800
879
|
// src/webhook.ts
|
|
801
880
|
var VERIFY_CHALLENGE = "verify-challenge";
|
|
@@ -1126,11 +1205,16 @@ function nwcRail(gateway, config) {
|
|
|
1126
1205
|
expiresAt: invoice.expiresAt,
|
|
1127
1206
|
trigger: config.trigger,
|
|
1128
1207
|
replay: config.replay,
|
|
1129
|
-
sealed:
|
|
1208
|
+
sealed: config.sealed ? await seal(
|
|
1209
|
+
config.sealed.secret,
|
|
1210
|
+
JSON.stringify(config.sealed.data(order)),
|
|
1211
|
+
invoice.paymentHash
|
|
1212
|
+
) : void 0,
|
|
1130
1213
|
webhookUrl: config.webhookUrl
|
|
1131
1214
|
});
|
|
1132
1215
|
return {
|
|
1133
1216
|
id: watched.id,
|
|
1217
|
+
paymentHash: invoice.paymentHash,
|
|
1134
1218
|
rail: config.name ?? "lightning",
|
|
1135
1219
|
scan: invoice.bolt11,
|
|
1136
1220
|
qr: toLightningUri(invoice.bolt11),
|
package/dist/nwc.d.cts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { A as Amount, T as Ticker } from './types-
|
|
2
|
-
import { R as RailConfig, O as Order, T as ThunderBridge,
|
|
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';
|
|
3
3
|
import './qr-CF-YeXU1.cjs';
|
|
4
|
-
import './bank-B3mHISn1.cjs';
|
|
5
4
|
|
|
6
5
|
/** A wallet reachable over NIP-47, as its `nostr+walletconnect://` URI describes it */
|
|
7
6
|
interface NwcConnection {
|
|
@@ -104,7 +103,10 @@ interface NwcRailConfig extends RailConfig {
|
|
|
104
103
|
/** What the payer's wallet shows, the order's reference by default */
|
|
105
104
|
description?: (order: Order) => string;
|
|
106
105
|
/** Sealed before the gateway sees it, the way the blind Lightning rail does */
|
|
107
|
-
sealed?:
|
|
106
|
+
sealed?: {
|
|
107
|
+
secret: string;
|
|
108
|
+
data: (order: Order) => unknown;
|
|
109
|
+
};
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
110
112
|
* Sell for Lightning against a wallet of your own over NIP-47, for a wallet that
|
package/dist/nwc.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { A as Amount, T as Ticker } from './types-
|
|
2
|
-
import { R as RailConfig, O as Order, T as ThunderBridge,
|
|
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';
|
|
3
3
|
import './qr-CF-YeXU1.js';
|
|
4
|
-
import './bank-B3mHISn1.js';
|
|
5
4
|
|
|
6
5
|
/** A wallet reachable over NIP-47, as its `nostr+walletconnect://` URI describes it */
|
|
7
6
|
interface NwcConnection {
|
|
@@ -104,7 +103,10 @@ interface NwcRailConfig extends RailConfig {
|
|
|
104
103
|
/** What the payer's wallet shows, the order's reference by default */
|
|
105
104
|
description?: (order: Order) => string;
|
|
106
105
|
/** Sealed before the gateway sees it, the way the blind Lightning rail does */
|
|
107
|
-
sealed?:
|
|
106
|
+
sealed?: {
|
|
107
|
+
secret: string;
|
|
108
|
+
data: (order: Order) => unknown;
|
|
109
|
+
};
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
110
112
|
* Sell for Lightning against a wallet of your own over NIP-47, for a wallet that
|
package/dist/nwc.js
CHANGED
|
@@ -172,6 +172,7 @@ function rotate(value, by) {
|
|
|
172
172
|
|
|
173
173
|
// ../core/bolt11.ts
|
|
174
174
|
var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
175
|
+
var BECH32_GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
175
176
|
var CHECKSUM_WORDS = 6;
|
|
176
177
|
var TIMESTAMP_WORDS = 7;
|
|
177
178
|
var SIGNATURE_WORDS = 104;
|
|
@@ -189,18 +190,27 @@ var HRP_MULTIPLIER_MSAT = {
|
|
|
189
190
|
n: MSAT_PER_BTC / 1e9,
|
|
190
191
|
p: MSAT_PER_BTC / 1e12
|
|
191
192
|
};
|
|
192
|
-
var
|
|
193
|
+
var HRP = /^ln(?:bcrt|bc|tbs|tb|sb)(?:(\d+)([munp]?))?$/;
|
|
193
194
|
function decodeInvoice(bolt11) {
|
|
194
195
|
const parts = splitBech32(bolt11);
|
|
195
196
|
if (parts === null) {
|
|
196
|
-
return {
|
|
197
|
+
return {
|
|
198
|
+
paymentHash: null,
|
|
199
|
+
descriptionHash: null,
|
|
200
|
+
amountMsat: null,
|
|
201
|
+
issuedAt: null,
|
|
202
|
+
expiresAt: null
|
|
203
|
+
};
|
|
197
204
|
}
|
|
198
205
|
const tagged = taggedFields(parts.words);
|
|
206
|
+
const issuedAt = issuedAtOf(parts.words);
|
|
207
|
+
const expiry = tagged.get(EXPIRY_TAG);
|
|
199
208
|
return {
|
|
200
209
|
paymentHash: hexTag(tagged, PAYMENT_HASH_TAG, PAYMENT_HASH_WORDS),
|
|
201
210
|
descriptionHash: hexTag(tagged, DESCRIPTION_HASH_TAG, DESCRIPTION_HASH_WORDS),
|
|
202
211
|
amountMsat: amountFromHrp(parts.hrp),
|
|
203
|
-
|
|
212
|
+
issuedAt,
|
|
213
|
+
expiresAt: issuedAt === null ? null : issuedAt + (expiry ? readNumber(expiry) : DEFAULT_EXPIRY_SECS)
|
|
204
214
|
};
|
|
205
215
|
}
|
|
206
216
|
function preimageMatchesHash(preimage, paymentHash) {
|
|
@@ -211,7 +221,7 @@ function preimageMatchesHash(preimage, paymentHash) {
|
|
|
211
221
|
}
|
|
212
222
|
function splitBech32(bolt11) {
|
|
213
223
|
const lower = bolt11.toLowerCase();
|
|
214
|
-
if (isBolt12Encoding(lower)) {
|
|
224
|
+
if (isBolt12Encoding(lower) || bolt11 !== lower && bolt11 !== bolt11.toUpperCase()) {
|
|
215
225
|
return null;
|
|
216
226
|
}
|
|
217
227
|
const separator = lower.lastIndexOf("1");
|
|
@@ -230,7 +240,28 @@ function splitBech32(bolt11) {
|
|
|
230
240
|
}
|
|
231
241
|
words.push(value);
|
|
232
242
|
}
|
|
233
|
-
|
|
243
|
+
const hrp = lower.slice(0, separator);
|
|
244
|
+
if (!HRP.test(hrp) || bech32Polymod([...expandedHrp(hrp), ...words]) !== 1) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return { hrp, words: words.slice(0, -CHECKSUM_WORDS) };
|
|
248
|
+
}
|
|
249
|
+
function expandedHrp(hrp) {
|
|
250
|
+
const codes = [...hrp].map((char) => char.charCodeAt(0));
|
|
251
|
+
return [...codes.map((code) => code >> 5), 0, ...codes.map((code) => code & 31)];
|
|
252
|
+
}
|
|
253
|
+
function bech32Polymod(values) {
|
|
254
|
+
let checksum = 1;
|
|
255
|
+
for (const value of values) {
|
|
256
|
+
const top = checksum >> 25;
|
|
257
|
+
checksum = (checksum & 33554431) << 5 ^ value;
|
|
258
|
+
for (let bit = 0; bit < 5; bit++) {
|
|
259
|
+
if (top >> bit & 1) {
|
|
260
|
+
checksum ^= BECH32_GENERATOR[bit];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return checksum;
|
|
234
265
|
}
|
|
235
266
|
function isBolt12Encoding(lower) {
|
|
236
267
|
return lower.startsWith("lno") || lower.startsWith("lni") || lower.startsWith("lnr");
|
|
@@ -260,19 +291,17 @@ function hexTag(fields, tag, expectedWords) {
|
|
|
260
291
|
}
|
|
261
292
|
return bytesToHex(wordsToBytes(words));
|
|
262
293
|
}
|
|
263
|
-
function
|
|
294
|
+
function issuedAtOf(words) {
|
|
264
295
|
if (words.length < TIMESTAMP_WORDS + SIGNATURE_WORDS) {
|
|
265
296
|
return null;
|
|
266
297
|
}
|
|
267
|
-
|
|
268
|
-
return readNumber(words.slice(0, TIMESTAMP_WORDS)) + (expiry ? readNumber(expiry) : DEFAULT_EXPIRY_SECS);
|
|
298
|
+
return readNumber(words.slice(0, TIMESTAMP_WORDS));
|
|
269
299
|
}
|
|
270
300
|
function amountFromHrp(hrp) {
|
|
271
|
-
const
|
|
272
|
-
if (
|
|
301
|
+
const [, digits, multiplier] = HRP.exec(hrp) ?? [];
|
|
302
|
+
if (digits === void 0 || multiplier === "p" && !digits.endsWith("0")) {
|
|
273
303
|
return null;
|
|
274
304
|
}
|
|
275
|
-
const [, digits, multiplier] = match;
|
|
276
305
|
return multiplier ? Number(digits) * HRP_MULTIPLIER_MSAT[multiplier] : Number(digits) * MSAT_PER_BTC;
|
|
277
306
|
}
|
|
278
307
|
function readNumber(words) {
|
|
@@ -303,27 +332,38 @@ var WalletRefused = class extends Error {
|
|
|
303
332
|
};
|
|
304
333
|
|
|
305
334
|
// ../core/sealed.ts
|
|
306
|
-
var VERSION = "
|
|
335
|
+
var VERSION = "v2";
|
|
336
|
+
var UNPADDED_VERSION = "v1";
|
|
307
337
|
var IV_BYTES = 12;
|
|
308
338
|
var MIN_SECRET_CHARS = 32;
|
|
309
339
|
var MAX_PLAIN_BYTES = 3e3;
|
|
340
|
+
var SIZE_CLASSES = [256, 1024, MAX_PLAIN_BYTES];
|
|
341
|
+
var END_OF_TEXT = 128;
|
|
310
342
|
var INFO = new TextEncoder().encode("thunder-bridge/sealed");
|
|
311
|
-
async function seal(secret, plaintext) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
343
|
+
async function seal(secret, plaintext, paymentHash) {
|
|
344
|
+
return await sealUnder(
|
|
345
|
+
secret,
|
|
346
|
+
plaintext,
|
|
347
|
+
crypto.getRandomValues(new Uint8Array(IV_BYTES)),
|
|
348
|
+
boundTo(paymentHash)
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
async function sealUnder(secret, plaintext, iv, additionalData) {
|
|
352
|
+
const body = padToClass(plaintext);
|
|
316
353
|
const key = await keyFor(secret);
|
|
317
|
-
const
|
|
318
|
-
|
|
354
|
+
const cipher = new Uint8Array(
|
|
355
|
+
await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, body)
|
|
356
|
+
);
|
|
319
357
|
const joined2 = new Uint8Array(iv.length + cipher.length);
|
|
320
358
|
joined2.set(iv);
|
|
321
359
|
joined2.set(cipher, iv.length);
|
|
322
360
|
return `${VERSION}.${toBase64Url(joined2)}`;
|
|
323
361
|
}
|
|
324
|
-
async function unseal(secret, sealed) {
|
|
362
|
+
async function unseal(secret, sealed, paymentHash) {
|
|
325
363
|
const key = await keyFor(secret);
|
|
326
|
-
|
|
364
|
+
const padded2 = sealed.startsWith(`${VERSION}.`);
|
|
365
|
+
const unbound = !padded2 && sealed.startsWith(`${UNPADDED_VERSION}.`);
|
|
366
|
+
if (!padded2 && (!unbound || paymentHash !== void 0)) {
|
|
327
367
|
return null;
|
|
328
368
|
}
|
|
329
369
|
const joined2 = fromBase64Url(sealed.slice(VERSION.length + 1));
|
|
@@ -331,20 +371,50 @@ async function unseal(secret, sealed) {
|
|
|
331
371
|
return null;
|
|
332
372
|
}
|
|
333
373
|
try {
|
|
334
|
-
const body =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
374
|
+
const body = new Uint8Array(
|
|
375
|
+
await crypto.subtle.decrypt(
|
|
376
|
+
{
|
|
377
|
+
name: "AES-GCM",
|
|
378
|
+
iv: joined2.slice(0, IV_BYTES),
|
|
379
|
+
additionalData: padded2 ? boundTo(paymentHash) : void 0
|
|
380
|
+
},
|
|
381
|
+
key,
|
|
382
|
+
joined2.slice(IV_BYTES)
|
|
383
|
+
)
|
|
338
384
|
);
|
|
339
|
-
|
|
385
|
+
const text = padded2 ? unpad(body) : body;
|
|
386
|
+
return text === null ? null : new TextDecoder().decode(text);
|
|
340
387
|
} catch {
|
|
341
388
|
return null;
|
|
342
389
|
}
|
|
343
390
|
}
|
|
344
|
-
|
|
391
|
+
function refuseAWeakSecret(secret) {
|
|
345
392
|
if (secret.length < MIN_SECRET_CHARS) {
|
|
346
393
|
throw new Error(`the sealing secret needs ${MIN_SECRET_CHARS} characters of randomness`);
|
|
347
394
|
}
|
|
395
|
+
}
|
|
396
|
+
function boundTo(paymentHash) {
|
|
397
|
+
return new TextEncoder().encode(
|
|
398
|
+
paymentHash === void 0 ? VERSION : `${VERSION}|${paymentHash.toLowerCase()}`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
function padToClass(plaintext) {
|
|
402
|
+
const text = new TextEncoder().encode(plaintext);
|
|
403
|
+
const size = SIZE_CLASSES.find((limit) => text.length <= limit);
|
|
404
|
+
if (size === void 0) {
|
|
405
|
+
throw new Error(`sealed takes at most ${MAX_PLAIN_BYTES} bytes, this was ${text.length}`);
|
|
406
|
+
}
|
|
407
|
+
const body = new Uint8Array(size + 1);
|
|
408
|
+
body.set(text);
|
|
409
|
+
body[text.length] = END_OF_TEXT;
|
|
410
|
+
return body;
|
|
411
|
+
}
|
|
412
|
+
function unpad(body) {
|
|
413
|
+
const end = body.lastIndexOf(END_OF_TEXT);
|
|
414
|
+
return end === -1 ? null : body.subarray(0, end);
|
|
415
|
+
}
|
|
416
|
+
async function keyFor(secret) {
|
|
417
|
+
refuseAWeakSecret(secret);
|
|
348
418
|
const material = await crypto.subtle.importKey(
|
|
349
419
|
"raw",
|
|
350
420
|
new TextEncoder().encode(secret),
|
|
@@ -762,6 +832,15 @@ async function amountNow(amount) {
|
|
|
762
832
|
|
|
763
833
|
// ../core/ed25519.ts
|
|
764
834
|
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
835
|
+
var SMALL_ORDER = [
|
|
836
|
+
"00".repeat(32),
|
|
837
|
+
`01${"00".repeat(31)}`,
|
|
838
|
+
"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05",
|
|
839
|
+
"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a",
|
|
840
|
+
`ec${"ff".repeat(30)}7f`,
|
|
841
|
+
`ed${"ff".repeat(30)}7f`,
|
|
842
|
+
`ee${"ff".repeat(30)}7f`
|
|
843
|
+
];
|
|
765
844
|
|
|
766
845
|
// src/webhook.ts
|
|
767
846
|
var VERIFY_CHALLENGE = "verify-challenge";
|
|
@@ -1092,11 +1171,16 @@ function nwcRail(gateway, config) {
|
|
|
1092
1171
|
expiresAt: invoice.expiresAt,
|
|
1093
1172
|
trigger: config.trigger,
|
|
1094
1173
|
replay: config.replay,
|
|
1095
|
-
sealed:
|
|
1174
|
+
sealed: config.sealed ? await seal(
|
|
1175
|
+
config.sealed.secret,
|
|
1176
|
+
JSON.stringify(config.sealed.data(order)),
|
|
1177
|
+
invoice.paymentHash
|
|
1178
|
+
) : void 0,
|
|
1096
1179
|
webhookUrl: config.webhookUrl
|
|
1097
1180
|
});
|
|
1098
1181
|
return {
|
|
1099
1182
|
id: watched.id,
|
|
1183
|
+
paymentHash: invoice.paymentHash,
|
|
1100
1184
|
rail: config.name ?? "lightning",
|
|
1101
1185
|
scan: invoice.bolt11,
|
|
1102
1186
|
qr: toLightningUri(invoice.bolt11),
|
package/dist/price.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AmountError, a as AmountFault } from './errors-
|
|
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-
|
|
1
|
+
export { A as AmountError, a as AmountFault } from './errors-DriqW-lp.cjs';
|
|
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
|
/**
|
|
5
5
|
* How many digits ISO 4217 gives the currency's minor unit, so 2 for a crown and a
|
package/dist/price.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { A as AmountError, a as AmountFault } from './errors-
|
|
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-
|
|
1
|
+
export { A as AmountError, a as AmountFault } from './errors-ZEUT-MhO.js';
|
|
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.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* How many digits ISO 4217 gives the currency's minor unit, so 2 for a crown and a
|
package/dist/qr.cjs
CHANGED
|
@@ -111,6 +111,8 @@ var INITIAL_STATE = new Uint32Array([
|
|
|
111
111
|
]);
|
|
112
112
|
|
|
113
113
|
// ../core/bolt11.ts
|
|
114
|
+
var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
115
|
+
var BECH32_GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
114
116
|
var MSAT_PER_BTC = 1e11;
|
|
115
117
|
var HRP_MULTIPLIER_MSAT = {
|
|
116
118
|
m: MSAT_PER_BTC / 1e3,
|
|
@@ -118,10 +120,25 @@ var HRP_MULTIPLIER_MSAT = {
|
|
|
118
120
|
n: MSAT_PER_BTC / 1e9,
|
|
119
121
|
p: MSAT_PER_BTC / 1e12
|
|
120
122
|
};
|
|
123
|
+
function expandedHrp(hrp) {
|
|
124
|
+
const codes = [...hrp].map((char) => char.charCodeAt(0));
|
|
125
|
+
return [...codes.map((code) => code >> 5), 0, ...codes.map((code) => code & 31)];
|
|
126
|
+
}
|
|
127
|
+
function bech32Polymod(values) {
|
|
128
|
+
let checksum = 1;
|
|
129
|
+
for (const value of values) {
|
|
130
|
+
const top = checksum >> 25;
|
|
131
|
+
checksum = (checksum & 33554431) << 5 ^ value;
|
|
132
|
+
for (let bit = 0; bit < 5; bit++) {
|
|
133
|
+
if (top >> bit & 1) {
|
|
134
|
+
checksum ^= BECH32_GENERATOR[bit];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return checksum;
|
|
139
|
+
}
|
|
121
140
|
|
|
122
141
|
// ../core/lnurl.ts
|
|
123
|
-
var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
124
|
-
var BECH32_GENERATOR = [996825010, 642813549, 513874426, 1027748829, 705979059];
|
|
125
142
|
var CHECKSUM_SLOTS = [0, 1, 2, 3, 4, 5];
|
|
126
143
|
var LNURL_HRP = "lnurl";
|
|
127
144
|
function toLnurl(endpoint) {
|
|
@@ -158,30 +175,10 @@ function toWords(bytes) {
|
|
|
158
175
|
return words;
|
|
159
176
|
}
|
|
160
177
|
function checksumWords(hrp, words) {
|
|
161
|
-
const
|
|
162
|
-
const values = [
|
|
163
|
-
...expanded.map((code) => code >> 5),
|
|
164
|
-
0,
|
|
165
|
-
...expanded.map((code) => code & 31),
|
|
166
|
-
...words,
|
|
167
|
-
...CHECKSUM_SLOTS.map(() => 0)
|
|
168
|
-
];
|
|
178
|
+
const values = [...expandedHrp(hrp), ...words, ...CHECKSUM_SLOTS.map(() => 0)];
|
|
169
179
|
const polymod = bech32Polymod(values) ^ 1;
|
|
170
180
|
return CHECKSUM_SLOTS.map((slot) => polymod >> 5 * (5 - slot) & 31);
|
|
171
181
|
}
|
|
172
|
-
function bech32Polymod(values) {
|
|
173
|
-
let checksum = 1;
|
|
174
|
-
for (const value of values) {
|
|
175
|
-
const top = checksum >> 25;
|
|
176
|
-
checksum = (checksum & 33554431) << 5 ^ value;
|
|
177
|
-
for (let bit = 0; bit < 5; bit++) {
|
|
178
|
-
if (top >> bit & 1) {
|
|
179
|
-
checksum ^= BECH32_GENERATOR[bit];
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return checksum;
|
|
184
|
-
}
|
|
185
182
|
|
|
186
183
|
// src/qr.ts
|
|
187
184
|
var import_uqr = require("uqr");
|