@getalby/lightning-tools 7.0.2 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -28
- package/dist/cjs/402/l402.cjs +157 -0
- package/dist/cjs/402/l402.cjs.map +1 -0
- package/dist/cjs/402/mpp.cjs +179 -0
- package/dist/cjs/402/mpp.cjs.map +1 -0
- package/dist/cjs/402/x402.cjs +1320 -0
- package/dist/cjs/402/x402.cjs.map +1 -0
- package/dist/cjs/402.cjs +1694 -0
- package/dist/cjs/402.cjs.map +1 -0
- package/dist/cjs/bolt11.cjs +534 -518
- package/dist/cjs/bolt11.cjs.map +1 -1
- package/dist/cjs/index.cjs +811 -453
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/lnurl.cjs +22 -7
- package/dist/cjs/lnurl.cjs.map +1 -1
- package/dist/esm/402/l402.js +150 -0
- package/dist/esm/402/l402.js.map +1 -0
- package/dist/esm/402/mpp.js +177 -0
- package/dist/esm/402/mpp.js.map +1 -0
- package/dist/esm/402/x402.js +1318 -0
- package/dist/esm/402/x402.js.map +1 -0
- package/dist/esm/402.js +1683 -0
- package/dist/esm/402.js.map +1 -0
- package/dist/esm/bolt11.js +534 -519
- package/dist/esm/bolt11.js.map +1 -1
- package/dist/esm/index.js +803 -451
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lnurl.js +22 -7
- package/dist/esm/lnurl.js.map +1 -1
- package/dist/lightning-tools.umd.js +3 -3
- package/dist/lightning-tools.umd.js.map +1 -1
- package/dist/types/402/l402.d.ts +51 -0
- package/dist/types/402/mpp.d.ts +26 -0
- package/dist/types/402/x402.d.ts +13 -0
- package/dist/types/402.d.ts +78 -0
- package/dist/types/bolt11.d.ts +6 -1
- package/dist/types/index.d.ts +76 -28
- package/dist/types/lnurl.d.ts +2 -0
- package/package.json +20 -5
- package/dist/cjs/l402.cjs +0 -93
- package/dist/cjs/l402.cjs.map +0 -1
- package/dist/esm/l402.js +0 -87
- package/dist/esm/l402.js.map +0 -1
- package/dist/types/l402.d.ts +0 -35
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,5 +1,312 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
function bytes(b, ...lengths) {
|
|
4
|
+
if (!(b instanceof Uint8Array))
|
|
5
|
+
throw new Error('Expected Uint8Array');
|
|
6
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
7
|
+
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
|
|
8
|
+
}
|
|
9
|
+
function exists(instance, checkFinished = true) {
|
|
10
|
+
if (instance.destroyed)
|
|
11
|
+
throw new Error('Hash instance has been destroyed');
|
|
12
|
+
if (checkFinished && instance.finished)
|
|
13
|
+
throw new Error('Hash#digest() has already been called');
|
|
14
|
+
}
|
|
15
|
+
function output(out, instance) {
|
|
16
|
+
bytes(out);
|
|
17
|
+
const min = instance.outputLen;
|
|
18
|
+
if (out.length < min) {
|
|
19
|
+
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
24
|
+
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
|
|
25
|
+
// node.js versions earlier than v19 don't declare it in global scope.
|
|
26
|
+
// For node.js, package.json#exports field mapping rewrites import
|
|
27
|
+
// from `crypto` to `cryptoNode`, which imports native module.
|
|
28
|
+
// Makes the utils un-importable in browsers without a bundler.
|
|
29
|
+
// Once node.js 18 is deprecated, we can just drop the import.
|
|
30
|
+
const u8a = (a) => a instanceof Uint8Array;
|
|
31
|
+
// Cast array to view
|
|
32
|
+
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
33
|
+
// The rotate right (circular right shift) operation for uint32
|
|
34
|
+
const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
|
|
35
|
+
// big-endian hardware is rare. Just in case someone still decides to run hashes:
|
|
36
|
+
// early-throw an error because we don't support BE yet.
|
|
37
|
+
const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
|
|
38
|
+
if (!isLE)
|
|
39
|
+
throw new Error('Non little-endian hardware is not supported');
|
|
40
|
+
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
|
41
|
+
/**
|
|
42
|
+
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
43
|
+
*/
|
|
44
|
+
function bytesToHex(bytes) {
|
|
45
|
+
if (!u8a(bytes))
|
|
46
|
+
throw new Error('Uint8Array expected');
|
|
47
|
+
// pre-caching improves the speed 6x
|
|
48
|
+
let hex = '';
|
|
49
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
50
|
+
hex += hexes[bytes[i]];
|
|
51
|
+
}
|
|
52
|
+
return hex;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
56
|
+
*/
|
|
57
|
+
function utf8ToBytes(str) {
|
|
58
|
+
if (typeof str !== 'string')
|
|
59
|
+
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
60
|
+
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
|
64
|
+
* Warning: when Uint8Array is passed, it would NOT get copied.
|
|
65
|
+
* Keep in mind for future mutable operations.
|
|
66
|
+
*/
|
|
67
|
+
function toBytes(data) {
|
|
68
|
+
if (typeof data === 'string')
|
|
69
|
+
data = utf8ToBytes(data);
|
|
70
|
+
if (!u8a(data))
|
|
71
|
+
throw new Error(`expected Uint8Array, got ${typeof data}`);
|
|
72
|
+
return data;
|
|
73
|
+
}
|
|
74
|
+
// For runtime check if class implements interface
|
|
75
|
+
class Hash {
|
|
76
|
+
// Safe version that clones internal state
|
|
77
|
+
clone() {
|
|
78
|
+
return this._cloneInto();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function wrapConstructor(hashCons) {
|
|
82
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
83
|
+
const tmp = hashCons();
|
|
84
|
+
hashC.outputLen = tmp.outputLen;
|
|
85
|
+
hashC.blockLen = tmp.blockLen;
|
|
86
|
+
hashC.create = () => hashCons();
|
|
87
|
+
return hashC;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Polyfill for Safari 14
|
|
91
|
+
function setBigUint64(view, byteOffset, value, isLE) {
|
|
92
|
+
if (typeof view.setBigUint64 === 'function')
|
|
93
|
+
return view.setBigUint64(byteOffset, value, isLE);
|
|
94
|
+
const _32n = BigInt(32);
|
|
95
|
+
const _u32_max = BigInt(0xffffffff);
|
|
96
|
+
const wh = Number((value >> _32n) & _u32_max);
|
|
97
|
+
const wl = Number(value & _u32_max);
|
|
98
|
+
const h = isLE ? 4 : 0;
|
|
99
|
+
const l = isLE ? 0 : 4;
|
|
100
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
|
101
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
|
102
|
+
}
|
|
103
|
+
// Base SHA2 class (RFC 6234)
|
|
104
|
+
class SHA2 extends Hash {
|
|
105
|
+
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
106
|
+
super();
|
|
107
|
+
this.blockLen = blockLen;
|
|
108
|
+
this.outputLen = outputLen;
|
|
109
|
+
this.padOffset = padOffset;
|
|
110
|
+
this.isLE = isLE;
|
|
111
|
+
this.finished = false;
|
|
112
|
+
this.length = 0;
|
|
113
|
+
this.pos = 0;
|
|
114
|
+
this.destroyed = false;
|
|
115
|
+
this.buffer = new Uint8Array(blockLen);
|
|
116
|
+
this.view = createView(this.buffer);
|
|
117
|
+
}
|
|
118
|
+
update(data) {
|
|
119
|
+
exists(this);
|
|
120
|
+
const { view, buffer, blockLen } = this;
|
|
121
|
+
data = toBytes(data);
|
|
122
|
+
const len = data.length;
|
|
123
|
+
for (let pos = 0; pos < len;) {
|
|
124
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
125
|
+
// Fast path: we have at least one block in input, cast it to view and process
|
|
126
|
+
if (take === blockLen) {
|
|
127
|
+
const dataView = createView(data);
|
|
128
|
+
for (; blockLen <= len - pos; pos += blockLen)
|
|
129
|
+
this.process(dataView, pos);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
133
|
+
this.pos += take;
|
|
134
|
+
pos += take;
|
|
135
|
+
if (this.pos === blockLen) {
|
|
136
|
+
this.process(view, 0);
|
|
137
|
+
this.pos = 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
this.length += data.length;
|
|
141
|
+
this.roundClean();
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
digestInto(out) {
|
|
145
|
+
exists(this);
|
|
146
|
+
output(out, this);
|
|
147
|
+
this.finished = true;
|
|
148
|
+
// Padding
|
|
149
|
+
// We can avoid allocation of buffer for padding completely if it
|
|
150
|
+
// was previously not allocated here. But it won't change performance.
|
|
151
|
+
const { buffer, view, blockLen, isLE } = this;
|
|
152
|
+
let { pos } = this;
|
|
153
|
+
// append the bit '1' to the message
|
|
154
|
+
buffer[pos++] = 0b10000000;
|
|
155
|
+
this.buffer.subarray(pos).fill(0);
|
|
156
|
+
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
|
|
157
|
+
if (this.padOffset > blockLen - pos) {
|
|
158
|
+
this.process(view, 0);
|
|
159
|
+
pos = 0;
|
|
160
|
+
}
|
|
161
|
+
// Pad until full block byte with zeros
|
|
162
|
+
for (let i = pos; i < blockLen; i++)
|
|
163
|
+
buffer[i] = 0;
|
|
164
|
+
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
|
|
165
|
+
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
|
|
166
|
+
// So we just write lowest 64 bits of that value.
|
|
167
|
+
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
168
|
+
this.process(view, 0);
|
|
169
|
+
const oview = createView(out);
|
|
170
|
+
const len = this.outputLen;
|
|
171
|
+
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
|
|
172
|
+
if (len % 4)
|
|
173
|
+
throw new Error('_sha2: outputLen should be aligned to 32bit');
|
|
174
|
+
const outLen = len / 4;
|
|
175
|
+
const state = this.get();
|
|
176
|
+
if (outLen > state.length)
|
|
177
|
+
throw new Error('_sha2: outputLen bigger than state');
|
|
178
|
+
for (let i = 0; i < outLen; i++)
|
|
179
|
+
oview.setUint32(4 * i, state[i], isLE);
|
|
180
|
+
}
|
|
181
|
+
digest() {
|
|
182
|
+
const { buffer, outputLen } = this;
|
|
183
|
+
this.digestInto(buffer);
|
|
184
|
+
const res = buffer.slice(0, outputLen);
|
|
185
|
+
this.destroy();
|
|
186
|
+
return res;
|
|
187
|
+
}
|
|
188
|
+
_cloneInto(to) {
|
|
189
|
+
to || (to = new this.constructor());
|
|
190
|
+
to.set(...this.get());
|
|
191
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
192
|
+
to.length = length;
|
|
193
|
+
to.pos = pos;
|
|
194
|
+
to.finished = finished;
|
|
195
|
+
to.destroyed = destroyed;
|
|
196
|
+
if (length % blockLen)
|
|
197
|
+
to.buffer.set(buffer);
|
|
198
|
+
return to;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// SHA2-256 need to try 2^128 hashes to execute birthday attack.
|
|
203
|
+
// BTC network is doing 2^67 hashes/sec as per early 2023.
|
|
204
|
+
// Choice: a ? b : c
|
|
205
|
+
const Chi = (a, b, c) => (a & b) ^ (~a & c);
|
|
206
|
+
// Majority function, true if any two inpust is true
|
|
207
|
+
const Maj = (a, b, c) => (a & b) ^ (a & c) ^ (b & c);
|
|
208
|
+
// Round constants:
|
|
209
|
+
// first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
|
|
210
|
+
// prettier-ignore
|
|
211
|
+
const SHA256_K = /* @__PURE__ */ new Uint32Array([
|
|
212
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
213
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
214
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
215
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
216
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
217
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
218
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
219
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
220
|
+
]);
|
|
221
|
+
// Initial state (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
|
|
222
|
+
// prettier-ignore
|
|
223
|
+
const IV = /* @__PURE__ */ new Uint32Array([
|
|
224
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
|
225
|
+
]);
|
|
226
|
+
// Temporary buffer, not used to store anything between runs
|
|
227
|
+
// Named this way because it matches specification.
|
|
228
|
+
const SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
229
|
+
class SHA256 extends SHA2 {
|
|
230
|
+
constructor() {
|
|
231
|
+
super(64, 32, 8, false);
|
|
232
|
+
// We cannot use array here since array allows indexing by variable
|
|
233
|
+
// which means optimizer/compiler cannot use registers.
|
|
234
|
+
this.A = IV[0] | 0;
|
|
235
|
+
this.B = IV[1] | 0;
|
|
236
|
+
this.C = IV[2] | 0;
|
|
237
|
+
this.D = IV[3] | 0;
|
|
238
|
+
this.E = IV[4] | 0;
|
|
239
|
+
this.F = IV[5] | 0;
|
|
240
|
+
this.G = IV[6] | 0;
|
|
241
|
+
this.H = IV[7] | 0;
|
|
242
|
+
}
|
|
243
|
+
get() {
|
|
244
|
+
const { A, B, C, D, E, F, G, H } = this;
|
|
245
|
+
return [A, B, C, D, E, F, G, H];
|
|
246
|
+
}
|
|
247
|
+
// prettier-ignore
|
|
248
|
+
set(A, B, C, D, E, F, G, H) {
|
|
249
|
+
this.A = A | 0;
|
|
250
|
+
this.B = B | 0;
|
|
251
|
+
this.C = C | 0;
|
|
252
|
+
this.D = D | 0;
|
|
253
|
+
this.E = E | 0;
|
|
254
|
+
this.F = F | 0;
|
|
255
|
+
this.G = G | 0;
|
|
256
|
+
this.H = H | 0;
|
|
257
|
+
}
|
|
258
|
+
process(view, offset) {
|
|
259
|
+
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
|
|
260
|
+
for (let i = 0; i < 16; i++, offset += 4)
|
|
261
|
+
SHA256_W[i] = view.getUint32(offset, false);
|
|
262
|
+
for (let i = 16; i < 64; i++) {
|
|
263
|
+
const W15 = SHA256_W[i - 15];
|
|
264
|
+
const W2 = SHA256_W[i - 2];
|
|
265
|
+
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
|
|
266
|
+
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
|
|
267
|
+
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
|
|
268
|
+
}
|
|
269
|
+
// Compression function main loop, 64 rounds
|
|
270
|
+
let { A, B, C, D, E, F, G, H } = this;
|
|
271
|
+
for (let i = 0; i < 64; i++) {
|
|
272
|
+
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
273
|
+
const T1 = (H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i]) | 0;
|
|
274
|
+
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
|
275
|
+
const T2 = (sigma0 + Maj(A, B, C)) | 0;
|
|
276
|
+
H = G;
|
|
277
|
+
G = F;
|
|
278
|
+
F = E;
|
|
279
|
+
E = (D + T1) | 0;
|
|
280
|
+
D = C;
|
|
281
|
+
C = B;
|
|
282
|
+
B = A;
|
|
283
|
+
A = (T1 + T2) | 0;
|
|
284
|
+
}
|
|
285
|
+
// Add the compressed chunk to the current hash value
|
|
286
|
+
A = (A + this.A) | 0;
|
|
287
|
+
B = (B + this.B) | 0;
|
|
288
|
+
C = (C + this.C) | 0;
|
|
289
|
+
D = (D + this.D) | 0;
|
|
290
|
+
E = (E + this.E) | 0;
|
|
291
|
+
F = (F + this.F) | 0;
|
|
292
|
+
G = (G + this.G) | 0;
|
|
293
|
+
H = (H + this.H) | 0;
|
|
294
|
+
this.set(A, B, C, D, E, F, G, H);
|
|
295
|
+
}
|
|
296
|
+
roundClean() {
|
|
297
|
+
SHA256_W.fill(0);
|
|
298
|
+
}
|
|
299
|
+
destroy() {
|
|
300
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
301
|
+
this.buffer.fill(0);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* SHA2-256 hash function
|
|
306
|
+
* @param message - data that would be hashed
|
|
307
|
+
*/
|
|
308
|
+
const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
|
|
309
|
+
|
|
3
310
|
var lib = {};
|
|
4
311
|
|
|
5
312
|
var hasRequiredLib;
|
|
@@ -767,409 +1074,121 @@ function requireBolt11 () {
|
|
|
767
1074
|
name: 'signature',
|
|
768
1075
|
letters: letters.slice(0, 104),
|
|
769
1076
|
value: hex.encode(bech32.fromWordsUnsafe(sigWords))
|
|
770
|
-
});
|
|
771
|
-
letters = letters.slice(104);
|
|
772
|
-
|
|
773
|
-
// checksum
|
|
774
|
-
sections.push({
|
|
775
|
-
name: 'checksum',
|
|
776
|
-
letters: letters
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
let result = {
|
|
780
|
-
paymentRequest,
|
|
781
|
-
sections,
|
|
782
|
-
|
|
783
|
-
get expiry() {
|
|
784
|
-
let exp = sections.find(s => s.name === 'expiry');
|
|
785
|
-
if (exp) return getValue('timestamp') + exp.value
|
|
786
|
-
},
|
|
787
|
-
|
|
788
|
-
get route_hints() {
|
|
789
|
-
return sections.filter(s => s.name === 'route_hint').map(s => s.value)
|
|
790
|
-
}
|
|
791
|
-
};
|
|
792
|
-
|
|
793
|
-
for (let name in TAGCODES) {
|
|
794
|
-
if (name === 'route_hint') {
|
|
795
|
-
// route hints can be multiple, so this won't work for them
|
|
796
|
-
continue
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
Object.defineProperty(result, name, {
|
|
800
|
-
get() {
|
|
801
|
-
return getValue(name)
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
return result
|
|
807
|
-
|
|
808
|
-
function getValue(name) {
|
|
809
|
-
let section = sections.find(s => s.name === name);
|
|
810
|
-
return section ? section.value : undefined
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
bolt11 = {
|
|
815
|
-
decode,
|
|
816
|
-
hrpToMillisat
|
|
817
|
-
};
|
|
818
|
-
return bolt11;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
var bolt11Exports = requireBolt11();
|
|
822
|
-
|
|
823
|
-
// from https://stackoverflow.com/a/50868276
|
|
824
|
-
const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
825
|
-
const decodeInvoice = (paymentRequest) => {
|
|
826
|
-
if (!paymentRequest)
|
|
827
|
-
return null;
|
|
828
|
-
try {
|
|
829
|
-
const decoded = bolt11Exports.decode(paymentRequest);
|
|
830
|
-
if (!decoded || !decoded.sections)
|
|
831
|
-
return null;
|
|
832
|
-
const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
|
|
833
|
-
if (hashTag?.name !== "payment_hash" || !hashTag.value)
|
|
834
|
-
return null;
|
|
835
|
-
const paymentHash = hashTag.value;
|
|
836
|
-
let satoshi = 0;
|
|
837
|
-
const amountTag = decoded.sections.find((value) => value.name === "amount");
|
|
838
|
-
if (amountTag?.name === "amount" && amountTag.value) {
|
|
839
|
-
satoshi = parseInt(amountTag.value) / 1000; // millisats
|
|
840
|
-
}
|
|
841
|
-
const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
|
|
842
|
-
if (timestampTag?.name !== "timestamp" || !timestampTag.value)
|
|
843
|
-
return null;
|
|
844
|
-
const timestamp = timestampTag.value;
|
|
845
|
-
let expiry;
|
|
846
|
-
const expiryTag = decoded.sections.find((value) => value.name === "expiry");
|
|
847
|
-
if (expiryTag?.name === "expiry") {
|
|
848
|
-
expiry = expiryTag.value;
|
|
849
|
-
}
|
|
850
|
-
const descriptionTag = decoded.sections.find((value) => value.name === "description");
|
|
851
|
-
const description = descriptionTag?.name === "description"
|
|
852
|
-
? descriptionTag?.value
|
|
853
|
-
: undefined;
|
|
854
|
-
return {
|
|
855
|
-
paymentHash,
|
|
856
|
-
satoshi,
|
|
857
|
-
timestamp,
|
|
858
|
-
expiry,
|
|
859
|
-
description,
|
|
860
|
-
};
|
|
861
|
-
}
|
|
862
|
-
catch {
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
|
|
867
|
-
function bytes(b, ...lengths) {
|
|
868
|
-
if (!(b instanceof Uint8Array))
|
|
869
|
-
throw new Error('Expected Uint8Array');
|
|
870
|
-
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
871
|
-
throw new Error(`Expected Uint8Array of length ${lengths}, not of length=${b.length}`);
|
|
872
|
-
}
|
|
873
|
-
function exists(instance, checkFinished = true) {
|
|
874
|
-
if (instance.destroyed)
|
|
875
|
-
throw new Error('Hash instance has been destroyed');
|
|
876
|
-
if (checkFinished && instance.finished)
|
|
877
|
-
throw new Error('Hash#digest() has already been called');
|
|
878
|
-
}
|
|
879
|
-
function output(out, instance) {
|
|
880
|
-
bytes(out);
|
|
881
|
-
const min = instance.outputLen;
|
|
882
|
-
if (out.length < min) {
|
|
883
|
-
throw new Error(`digestInto() expects output buffer of length at least ${min}`);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
1077
|
+
});
|
|
1078
|
+
letters = letters.slice(104);
|
|
886
1079
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
// Makes the utils un-importable in browsers without a bundler.
|
|
893
|
-
// Once node.js 18 is deprecated, we can just drop the import.
|
|
894
|
-
const u8a = (a) => a instanceof Uint8Array;
|
|
895
|
-
// Cast array to view
|
|
896
|
-
const createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
897
|
-
// The rotate right (circular right shift) operation for uint32
|
|
898
|
-
const rotr = (word, shift) => (word << (32 - shift)) | (word >>> shift);
|
|
899
|
-
// big-endian hardware is rare. Just in case someone still decides to run hashes:
|
|
900
|
-
// early-throw an error because we don't support BE yet.
|
|
901
|
-
const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
|
|
902
|
-
if (!isLE)
|
|
903
|
-
throw new Error('Non little-endian hardware is not supported');
|
|
904
|
-
const hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'));
|
|
905
|
-
/**
|
|
906
|
-
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
|
907
|
-
*/
|
|
908
|
-
function bytesToHex(bytes) {
|
|
909
|
-
if (!u8a(bytes))
|
|
910
|
-
throw new Error('Uint8Array expected');
|
|
911
|
-
// pre-caching improves the speed 6x
|
|
912
|
-
let hex = '';
|
|
913
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
914
|
-
hex += hexes[bytes[i]];
|
|
915
|
-
}
|
|
916
|
-
return hex;
|
|
917
|
-
}
|
|
918
|
-
/**
|
|
919
|
-
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
|
920
|
-
*/
|
|
921
|
-
function utf8ToBytes(str) {
|
|
922
|
-
if (typeof str !== 'string')
|
|
923
|
-
throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
|
924
|
-
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
|
928
|
-
* Warning: when Uint8Array is passed, it would NOT get copied.
|
|
929
|
-
* Keep in mind for future mutable operations.
|
|
930
|
-
*/
|
|
931
|
-
function toBytes(data) {
|
|
932
|
-
if (typeof data === 'string')
|
|
933
|
-
data = utf8ToBytes(data);
|
|
934
|
-
if (!u8a(data))
|
|
935
|
-
throw new Error(`expected Uint8Array, got ${typeof data}`);
|
|
936
|
-
return data;
|
|
937
|
-
}
|
|
938
|
-
// For runtime check if class implements interface
|
|
939
|
-
class Hash {
|
|
940
|
-
// Safe version that clones internal state
|
|
941
|
-
clone() {
|
|
942
|
-
return this._cloneInto();
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
function wrapConstructor(hashCons) {
|
|
946
|
-
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
947
|
-
const tmp = hashCons();
|
|
948
|
-
hashC.outputLen = tmp.outputLen;
|
|
949
|
-
hashC.blockLen = tmp.blockLen;
|
|
950
|
-
hashC.create = () => hashCons();
|
|
951
|
-
return hashC;
|
|
952
|
-
}
|
|
1080
|
+
// checksum
|
|
1081
|
+
sections.push({
|
|
1082
|
+
name: 'checksum',
|
|
1083
|
+
letters: letters
|
|
1084
|
+
});
|
|
953
1085
|
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
return view.setBigUint64(byteOffset, value, isLE);
|
|
958
|
-
const _32n = BigInt(32);
|
|
959
|
-
const _u32_max = BigInt(0xffffffff);
|
|
960
|
-
const wh = Number((value >> _32n) & _u32_max);
|
|
961
|
-
const wl = Number(value & _u32_max);
|
|
962
|
-
const h = isLE ? 4 : 0;
|
|
963
|
-
const l = isLE ? 0 : 4;
|
|
964
|
-
view.setUint32(byteOffset + h, wh, isLE);
|
|
965
|
-
view.setUint32(byteOffset + l, wl, isLE);
|
|
966
|
-
}
|
|
967
|
-
// Base SHA2 class (RFC 6234)
|
|
968
|
-
class SHA2 extends Hash {
|
|
969
|
-
constructor(blockLen, outputLen, padOffset, isLE) {
|
|
970
|
-
super();
|
|
971
|
-
this.blockLen = blockLen;
|
|
972
|
-
this.outputLen = outputLen;
|
|
973
|
-
this.padOffset = padOffset;
|
|
974
|
-
this.isLE = isLE;
|
|
975
|
-
this.finished = false;
|
|
976
|
-
this.length = 0;
|
|
977
|
-
this.pos = 0;
|
|
978
|
-
this.destroyed = false;
|
|
979
|
-
this.buffer = new Uint8Array(blockLen);
|
|
980
|
-
this.view = createView(this.buffer);
|
|
981
|
-
}
|
|
982
|
-
update(data) {
|
|
983
|
-
exists(this);
|
|
984
|
-
const { view, buffer, blockLen } = this;
|
|
985
|
-
data = toBytes(data);
|
|
986
|
-
const len = data.length;
|
|
987
|
-
for (let pos = 0; pos < len;) {
|
|
988
|
-
const take = Math.min(blockLen - this.pos, len - pos);
|
|
989
|
-
// Fast path: we have at least one block in input, cast it to view and process
|
|
990
|
-
if (take === blockLen) {
|
|
991
|
-
const dataView = createView(data);
|
|
992
|
-
for (; blockLen <= len - pos; pos += blockLen)
|
|
993
|
-
this.process(dataView, pos);
|
|
994
|
-
continue;
|
|
995
|
-
}
|
|
996
|
-
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
997
|
-
this.pos += take;
|
|
998
|
-
pos += take;
|
|
999
|
-
if (this.pos === blockLen) {
|
|
1000
|
-
this.process(view, 0);
|
|
1001
|
-
this.pos = 0;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
this.length += data.length;
|
|
1005
|
-
this.roundClean();
|
|
1006
|
-
return this;
|
|
1007
|
-
}
|
|
1008
|
-
digestInto(out) {
|
|
1009
|
-
exists(this);
|
|
1010
|
-
output(out, this);
|
|
1011
|
-
this.finished = true;
|
|
1012
|
-
// Padding
|
|
1013
|
-
// We can avoid allocation of buffer for padding completely if it
|
|
1014
|
-
// was previously not allocated here. But it won't change performance.
|
|
1015
|
-
const { buffer, view, blockLen, isLE } = this;
|
|
1016
|
-
let { pos } = this;
|
|
1017
|
-
// append the bit '1' to the message
|
|
1018
|
-
buffer[pos++] = 0b10000000;
|
|
1019
|
-
this.buffer.subarray(pos).fill(0);
|
|
1020
|
-
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
|
|
1021
|
-
if (this.padOffset > blockLen - pos) {
|
|
1022
|
-
this.process(view, 0);
|
|
1023
|
-
pos = 0;
|
|
1024
|
-
}
|
|
1025
|
-
// Pad until full block byte with zeros
|
|
1026
|
-
for (let i = pos; i < blockLen; i++)
|
|
1027
|
-
buffer[i] = 0;
|
|
1028
|
-
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
|
|
1029
|
-
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
|
|
1030
|
-
// So we just write lowest 64 bits of that value.
|
|
1031
|
-
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
|
1032
|
-
this.process(view, 0);
|
|
1033
|
-
const oview = createView(out);
|
|
1034
|
-
const len = this.outputLen;
|
|
1035
|
-
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
|
|
1036
|
-
if (len % 4)
|
|
1037
|
-
throw new Error('_sha2: outputLen should be aligned to 32bit');
|
|
1038
|
-
const outLen = len / 4;
|
|
1039
|
-
const state = this.get();
|
|
1040
|
-
if (outLen > state.length)
|
|
1041
|
-
throw new Error('_sha2: outputLen bigger than state');
|
|
1042
|
-
for (let i = 0; i < outLen; i++)
|
|
1043
|
-
oview.setUint32(4 * i, state[i], isLE);
|
|
1044
|
-
}
|
|
1045
|
-
digest() {
|
|
1046
|
-
const { buffer, outputLen } = this;
|
|
1047
|
-
this.digestInto(buffer);
|
|
1048
|
-
const res = buffer.slice(0, outputLen);
|
|
1049
|
-
this.destroy();
|
|
1050
|
-
return res;
|
|
1051
|
-
}
|
|
1052
|
-
_cloneInto(to) {
|
|
1053
|
-
to || (to = new this.constructor());
|
|
1054
|
-
to.set(...this.get());
|
|
1055
|
-
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
1056
|
-
to.length = length;
|
|
1057
|
-
to.pos = pos;
|
|
1058
|
-
to.finished = finished;
|
|
1059
|
-
to.destroyed = destroyed;
|
|
1060
|
-
if (length % blockLen)
|
|
1061
|
-
to.buffer.set(buffer);
|
|
1062
|
-
return to;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1086
|
+
let result = {
|
|
1087
|
+
paymentRequest,
|
|
1088
|
+
sections,
|
|
1065
1089
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
const W15 = SHA256_W[i - 15];
|
|
1128
|
-
const W2 = SHA256_W[i - 2];
|
|
1129
|
-
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ (W15 >>> 3);
|
|
1130
|
-
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ (W2 >>> 10);
|
|
1131
|
-
SHA256_W[i] = (s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16]) | 0;
|
|
1090
|
+
get expiry() {
|
|
1091
|
+
let exp = sections.find(s => s.name === 'expiry');
|
|
1092
|
+
if (exp) return getValue('timestamp') + exp.value
|
|
1093
|
+
},
|
|
1094
|
+
|
|
1095
|
+
get route_hints() {
|
|
1096
|
+
return sections.filter(s => s.name === 'route_hint').map(s => s.value)
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
for (let name in TAGCODES) {
|
|
1101
|
+
if (name === 'route_hint') {
|
|
1102
|
+
// route hints can be multiple, so this won't work for them
|
|
1103
|
+
continue
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
Object.defineProperty(result, name, {
|
|
1107
|
+
get() {
|
|
1108
|
+
return getValue(name)
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
return result
|
|
1114
|
+
|
|
1115
|
+
function getValue(name) {
|
|
1116
|
+
let section = sections.find(s => s.name === name);
|
|
1117
|
+
return section ? section.value : undefined
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
bolt11 = {
|
|
1122
|
+
decode,
|
|
1123
|
+
hrpToMillisat
|
|
1124
|
+
};
|
|
1125
|
+
return bolt11;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
var bolt11Exports = requireBolt11();
|
|
1129
|
+
|
|
1130
|
+
// from https://stackoverflow.com/a/50868276
|
|
1131
|
+
const fromHexString = (hexString) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
1132
|
+
const decodeInvoice = (paymentRequest) => {
|
|
1133
|
+
if (!paymentRequest)
|
|
1134
|
+
return null;
|
|
1135
|
+
try {
|
|
1136
|
+
const decoded = bolt11Exports.decode(paymentRequest);
|
|
1137
|
+
if (!decoded || !decoded.sections)
|
|
1138
|
+
return null;
|
|
1139
|
+
const hashTag = decoded.sections.find((value) => value.name === "payment_hash");
|
|
1140
|
+
if (hashTag?.name !== "payment_hash" || !hashTag.value)
|
|
1141
|
+
return null;
|
|
1142
|
+
const paymentHash = hashTag.value;
|
|
1143
|
+
let satoshi = 0;
|
|
1144
|
+
let millisatoshi = 0;
|
|
1145
|
+
let amountRaw = "0";
|
|
1146
|
+
const amountTag = decoded.sections.find((value) => value.name === "amount");
|
|
1147
|
+
if (amountTag?.name === "amount" && amountTag.value) {
|
|
1148
|
+
amountRaw = amountTag.value;
|
|
1149
|
+
millisatoshi = parseInt(amountTag.value);
|
|
1150
|
+
satoshi = parseInt(amountTag.value) / 1000; // millisats
|
|
1132
1151
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
G = F;
|
|
1142
|
-
F = E;
|
|
1143
|
-
E = (D + T1) | 0;
|
|
1144
|
-
D = C;
|
|
1145
|
-
C = B;
|
|
1146
|
-
B = A;
|
|
1147
|
-
A = (T1 + T2) | 0;
|
|
1152
|
+
const timestampTag = decoded.sections.find((value) => value.name === "timestamp");
|
|
1153
|
+
if (timestampTag?.name !== "timestamp" || !timestampTag.value)
|
|
1154
|
+
return null;
|
|
1155
|
+
const timestamp = timestampTag.value;
|
|
1156
|
+
let expiry;
|
|
1157
|
+
const expiryTag = decoded.sections.find((value) => value.name === "expiry");
|
|
1158
|
+
if (expiryTag?.name === "expiry") {
|
|
1159
|
+
expiry = expiryTag.value;
|
|
1148
1160
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1161
|
+
const descriptionTag = decoded.sections.find((value) => value.name === "description");
|
|
1162
|
+
const description = descriptionTag?.name === "description"
|
|
1163
|
+
? descriptionTag?.value
|
|
1164
|
+
: undefined;
|
|
1165
|
+
return {
|
|
1166
|
+
paymentHash,
|
|
1167
|
+
satoshi,
|
|
1168
|
+
millisatoshi,
|
|
1169
|
+
amountRaw,
|
|
1170
|
+
timestamp,
|
|
1171
|
+
expiry,
|
|
1172
|
+
description,
|
|
1173
|
+
};
|
|
1159
1174
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1175
|
+
catch {
|
|
1176
|
+
return null;
|
|
1162
1177
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1178
|
+
};
|
|
1179
|
+
function validatePreimage(preimage, paymentHash) {
|
|
1180
|
+
try {
|
|
1181
|
+
if (!/^[0-9a-fA-F]{64}$/.test(preimage))
|
|
1182
|
+
return false;
|
|
1183
|
+
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash))
|
|
1184
|
+
return false;
|
|
1185
|
+
const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
|
|
1186
|
+
return paymentHash === preimageHash;
|
|
1187
|
+
}
|
|
1188
|
+
catch {
|
|
1189
|
+
return false;
|
|
1166
1190
|
}
|
|
1167
1191
|
}
|
|
1168
|
-
/**
|
|
1169
|
-
* SHA2-256 hash function
|
|
1170
|
-
* @param message - data that would be hashed
|
|
1171
|
-
*/
|
|
1172
|
-
const sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256());
|
|
1173
1192
|
|
|
1174
1193
|
class Invoice {
|
|
1175
1194
|
constructor(args) {
|
|
@@ -1183,6 +1202,8 @@ class Invoice {
|
|
|
1183
1202
|
}
|
|
1184
1203
|
this.paymentHash = decodedInvoice.paymentHash;
|
|
1185
1204
|
this.satoshi = decodedInvoice.satoshi;
|
|
1205
|
+
this.millisatoshi = decodedInvoice.millisatoshi;
|
|
1206
|
+
this.amountRaw = decodedInvoice.amountRaw;
|
|
1186
1207
|
this.timestamp = decodedInvoice.timestamp;
|
|
1187
1208
|
this.expiry = decodedInvoice.expiry;
|
|
1188
1209
|
this.createdDate = new Date(this.timestamp * 1000);
|
|
@@ -1207,13 +1228,7 @@ class Invoice {
|
|
|
1207
1228
|
validatePreimage(preimage) {
|
|
1208
1229
|
if (!preimage || !this.paymentHash)
|
|
1209
1230
|
return false;
|
|
1210
|
-
|
|
1211
|
-
const preimageHash = bytesToHex(sha256(fromHexString(preimage)));
|
|
1212
|
-
return this.paymentHash === preimageHash;
|
|
1213
|
-
}
|
|
1214
|
-
catch {
|
|
1215
|
-
return false;
|
|
1216
|
-
}
|
|
1231
|
+
return validatePreimage(preimage, this.paymentHash);
|
|
1217
1232
|
}
|
|
1218
1233
|
async verifyPayment() {
|
|
1219
1234
|
try {
|
|
@@ -1659,24 +1674,23 @@ class LightningAddress {
|
|
|
1659
1674
|
}
|
|
1660
1675
|
}
|
|
1661
1676
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
class NoStorage {
|
|
1674
|
-
constructor(initial) { }
|
|
1675
|
-
getItem(key) {
|
|
1676
|
-
return null;
|
|
1677
|
-
}
|
|
1678
|
-
setItem(key, value) { }
|
|
1677
|
+
function createGuardedWallet(wallet, maxAmountSats) {
|
|
1678
|
+
return {
|
|
1679
|
+
payInvoice: async (args) => {
|
|
1680
|
+
const invoice = new Invoice({ pr: args.invoice });
|
|
1681
|
+
if (invoice.satoshi > maxAmountSats) {
|
|
1682
|
+
throw new Error(`Invoice amount (${invoice.satoshi} sats) exceeds maxAmount (${maxAmountSats} sats)`);
|
|
1683
|
+
}
|
|
1684
|
+
return wallet.payInvoice(args);
|
|
1685
|
+
},
|
|
1686
|
+
};
|
|
1679
1687
|
}
|
|
1688
|
+
|
|
1689
|
+
/**
|
|
1690
|
+
* Client: parse "www-authenticate" header from server response
|
|
1691
|
+
* @param input
|
|
1692
|
+
* @returns details from the header value (token or macaroon, invoice)
|
|
1693
|
+
*/
|
|
1680
1694
|
const parseL402 = (input) => {
|
|
1681
1695
|
// Remove the L402 and LSAT identifiers
|
|
1682
1696
|
const string = input.replace("L402", "").replace("LSAT", "").trim();
|
|
@@ -1691,58 +1705,396 @@ const parseL402 = (input) => {
|
|
|
1691
1705
|
// Value is either match[3] (double-quoted), match[4] (single-quoted), or match[5] (unquoted)
|
|
1692
1706
|
keyValuePairs[match[1]] = match[3] || match[4] || match[5];
|
|
1693
1707
|
}
|
|
1708
|
+
if (!keyValuePairs["token"] && keyValuePairs["macaroon"]) {
|
|
1709
|
+
// fallback to old naming
|
|
1710
|
+
keyValuePairs["token"] = keyValuePairs["macaroon"];
|
|
1711
|
+
delete keyValuePairs["macaroon"];
|
|
1712
|
+
}
|
|
1713
|
+
if (!("token" in keyValuePairs) ||
|
|
1714
|
+
typeof keyValuePairs["token"] !== "string") {
|
|
1715
|
+
throw new Error("No macaroon or token found in www-authenticate header");
|
|
1716
|
+
}
|
|
1717
|
+
if (!("invoice" in keyValuePairs) ||
|
|
1718
|
+
typeof keyValuePairs["invoice"] !== "string") {
|
|
1719
|
+
throw new Error("No invoice found in www-authenticate header");
|
|
1720
|
+
}
|
|
1694
1721
|
return keyValuePairs;
|
|
1695
1722
|
};
|
|
1696
|
-
const makeAuthenticateHeader = (args) => {
|
|
1697
|
-
const key = args.key || "L402";
|
|
1698
|
-
return `${key} macaroon="${args.macaroon}", invoice="${args.invoice}"`;
|
|
1699
|
-
};
|
|
1700
1723
|
|
|
1701
|
-
const
|
|
1702
|
-
const
|
|
1703
|
-
const
|
|
1704
|
-
|
|
1705
|
-
|
|
1724
|
+
const handleL402Payment = async (l402Header, url, fetchArgs, headers, wallet) => {
|
|
1725
|
+
const details = parseL402(l402Header);
|
|
1726
|
+
const token = details.token || details.macaroon;
|
|
1727
|
+
const invoice = details.invoice;
|
|
1728
|
+
if (!token) {
|
|
1729
|
+
throw new Error("L402: missing token/macaroon in WWW-Authenticate header");
|
|
1706
1730
|
}
|
|
1707
|
-
|
|
1731
|
+
if (!invoice) {
|
|
1732
|
+
throw new Error("L402: missing invoice in WWW-Authenticate header");
|
|
1733
|
+
}
|
|
1734
|
+
const invResp = await wallet.payInvoice({ invoice });
|
|
1735
|
+
headers.set("Authorization", `L402 ${token}:${invResp.preimage}`);
|
|
1736
|
+
return fetch(url, fetchArgs);
|
|
1737
|
+
};
|
|
1738
|
+
const fetchWithL402 = async (url, fetchArgs, options) => {
|
|
1708
1739
|
const wallet = options.wallet;
|
|
1709
1740
|
if (!wallet) {
|
|
1710
1741
|
throw new Error("wallet is missing");
|
|
1711
1742
|
}
|
|
1712
|
-
const store = options.store || memoryStorage;
|
|
1713
1743
|
if (!fetchArgs) {
|
|
1714
1744
|
fetchArgs = {};
|
|
1715
1745
|
}
|
|
1716
1746
|
fetchArgs.cache = "no-store";
|
|
1717
1747
|
fetchArgs.mode = "cors";
|
|
1718
|
-
|
|
1719
|
-
|
|
1748
|
+
const headers = new Headers(fetchArgs.headers ?? undefined);
|
|
1749
|
+
fetchArgs.headers = headers;
|
|
1750
|
+
const initResp = await fetch(url, fetchArgs);
|
|
1751
|
+
const header = initResp.headers.get("www-authenticate");
|
|
1752
|
+
if (!header) {
|
|
1753
|
+
return initResp;
|
|
1754
|
+
}
|
|
1755
|
+
return handleL402Payment(header, url, fetchArgs, headers, wallet);
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
const buildX402PaymentSignature = (scheme, network, invoice, requirements) => {
|
|
1759
|
+
const json = JSON.stringify({
|
|
1760
|
+
x402Version: 2,
|
|
1761
|
+
scheme,
|
|
1762
|
+
network,
|
|
1763
|
+
payload: { invoice },
|
|
1764
|
+
accepted: requirements,
|
|
1765
|
+
});
|
|
1766
|
+
// btoa only handles latin1; encode via UTF-8 to be safe
|
|
1767
|
+
return btoa(unescape(encodeURIComponent(json)));
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1770
|
+
const handleX402Payment = async (x402Header, url, fetchArgs, headers, wallet) => {
|
|
1771
|
+
let parsed;
|
|
1772
|
+
try {
|
|
1773
|
+
parsed = JSON.parse(decodeURIComponent(escape(atob(x402Header))));
|
|
1774
|
+
}
|
|
1775
|
+
catch (_) {
|
|
1776
|
+
throw new Error("x402: invalid PAYMENT-REQUIRED header (not valid base64-encoded JSON)");
|
|
1777
|
+
}
|
|
1778
|
+
if (!Array.isArray(parsed.accepts) || parsed.accepts.length === 0) {
|
|
1779
|
+
throw new Error("x402: PAYMENT-REQUIRED header contains no payment options");
|
|
1780
|
+
}
|
|
1781
|
+
const requirements = parsed.accepts.find((e) => {
|
|
1782
|
+
return e.extra?.paymentMethod === "lightning";
|
|
1783
|
+
});
|
|
1784
|
+
if (!requirements) {
|
|
1785
|
+
throw new Error("x402: unsupported x402 network, only Bitcoin lightning network is supported.");
|
|
1720
1786
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const data = JSON.parse(cachedL402Data);
|
|
1724
|
-
fetchArgs.headers["Authorization"] =
|
|
1725
|
-
`${headerKey} ${data.token}:${data.preimage}`;
|
|
1726
|
-
return await fetch(url, fetchArgs);
|
|
1787
|
+
if (!requirements.extra?.invoice) {
|
|
1788
|
+
throw new Error("x402: payment requirements missing lightning invoice");
|
|
1727
1789
|
}
|
|
1728
|
-
|
|
1790
|
+
const invoice = new Invoice({ pr: requirements.extra.invoice });
|
|
1791
|
+
if (invoice.amountRaw != requirements.amount) {
|
|
1792
|
+
throw new Error(`Invalid invoice amount: ${invoice.amountRaw}. expected ${requirements.amount}`);
|
|
1793
|
+
}
|
|
1794
|
+
await wallet.payInvoice({ invoice: invoice.paymentRequest });
|
|
1795
|
+
headers.set("payment-signature", buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements));
|
|
1796
|
+
return fetch(url, fetchArgs);
|
|
1797
|
+
};
|
|
1798
|
+
const fetchWithX402 = async (url, fetchArgs, options) => {
|
|
1799
|
+
const wallet = options.wallet;
|
|
1800
|
+
if (!fetchArgs) {
|
|
1801
|
+
fetchArgs = {};
|
|
1802
|
+
}
|
|
1803
|
+
fetchArgs.cache = "no-store";
|
|
1804
|
+
fetchArgs.mode = "cors";
|
|
1805
|
+
const headers = new Headers(fetchArgs.headers ?? undefined);
|
|
1806
|
+
fetchArgs.headers = headers;
|
|
1729
1807
|
const initResp = await fetch(url, fetchArgs);
|
|
1730
|
-
const header = initResp.headers.get("
|
|
1808
|
+
const header = initResp.headers.get("PAYMENT-REQUIRED");
|
|
1731
1809
|
if (!header) {
|
|
1732
1810
|
return initResp;
|
|
1733
1811
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1812
|
+
return handleX402Payment(header, url, fetchArgs, headers, wallet);
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
1816
|
+
* Parse a `WWW-Authenticate: Payment …` header produced by a
|
|
1817
|
+
* draft-lightning-charge-00 server. Expected format:
|
|
1818
|
+
*
|
|
1819
|
+
* Payment id="<id>", realm="<realm>", method="lightning",
|
|
1820
|
+
* intent="charge", request="<base64url>" [, expires="<rfc3339>"]
|
|
1821
|
+
*
|
|
1822
|
+
* Returns null when the header is not a Payment lightning/charge challenge.
|
|
1823
|
+
*/
|
|
1824
|
+
const parseMppChallenge = (header) => {
|
|
1825
|
+
if (!header.trimStart().toLowerCase().startsWith("payment")) {
|
|
1826
|
+
return null;
|
|
1827
|
+
}
|
|
1828
|
+
const rest = header
|
|
1829
|
+
.slice(header.toLowerCase().indexOf("payment") + "payment".length)
|
|
1830
|
+
.trim();
|
|
1831
|
+
const result = {};
|
|
1832
|
+
const regex = /(\w+)=("([^"]*)"|'([^']*)'|([^,\s]*))/g;
|
|
1833
|
+
let match;
|
|
1834
|
+
while ((match = regex.exec(rest)) !== null) {
|
|
1835
|
+
result[match[1]] = match[3] ?? match[4] ?? match[5] ?? "";
|
|
1836
|
+
}
|
|
1837
|
+
if (result.method !== "lightning" ||
|
|
1838
|
+
result.intent !== "charge" ||
|
|
1839
|
+
!result.id ||
|
|
1840
|
+
!result.realm ||
|
|
1841
|
+
!result.request) {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
return {
|
|
1845
|
+
id: result.id,
|
|
1846
|
+
realm: result.realm,
|
|
1847
|
+
method: result.method,
|
|
1848
|
+
intent: result.intent,
|
|
1849
|
+
request: result.request,
|
|
1850
|
+
...(result.expires ? { expires: result.expires } : {}),
|
|
1851
|
+
};
|
|
1852
|
+
};
|
|
1853
|
+
/** Decode a base64url string (no padding required) to a UTF-8 string. */
|
|
1854
|
+
const decodeBase64url = (input) => {
|
|
1855
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
1856
|
+
const binary = atob(base64);
|
|
1857
|
+
const bytes = new Uint8Array(binary.length);
|
|
1858
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1859
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1860
|
+
}
|
|
1861
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
1862
|
+
};
|
|
1863
|
+
/** Encode a UTF-8 string to base64url without padding. */
|
|
1864
|
+
const encodeBase64url = (input) => {
|
|
1865
|
+
const bytes = new TextEncoder().encode(input);
|
|
1866
|
+
let binary = "";
|
|
1867
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1868
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1869
|
+
}
|
|
1870
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1871
|
+
};
|
|
1872
|
+
/**
|
|
1873
|
+
* JSON Canonicalization Scheme (RFC 8785).
|
|
1874
|
+
* Produces compact JSON with object keys sorted lexicographically.
|
|
1875
|
+
*/
|
|
1876
|
+
const jcs = (value) => {
|
|
1877
|
+
if (value === null || typeof value !== "object") {
|
|
1878
|
+
return JSON.stringify(value);
|
|
1879
|
+
}
|
|
1880
|
+
if (Array.isArray(value)) {
|
|
1881
|
+
return "[" + value.map(jcs).join(",") + "]";
|
|
1882
|
+
}
|
|
1883
|
+
const keys = Object.keys(value).sort();
|
|
1884
|
+
return ("{" +
|
|
1885
|
+
keys
|
|
1886
|
+
.map((k) => JSON.stringify(k) + ":" + jcs(value[k]))
|
|
1887
|
+
.join(",") +
|
|
1888
|
+
"}");
|
|
1889
|
+
};
|
|
1890
|
+
/**
|
|
1891
|
+
* Build the base64url-encoded credential token for the `Authorization` header.
|
|
1892
|
+
*
|
|
1893
|
+
* Per the spec the credential is a JCS-serialised JSON object that echoes all
|
|
1894
|
+
* challenge auth-params (id, realm, method, intent, request, expires) and
|
|
1895
|
+
* carries the HTLC preimage that proves payment:
|
|
1896
|
+
*
|
|
1897
|
+
* {
|
|
1898
|
+
* "challenge": { "id": "…", "intent": "charge",
|
|
1899
|
+
* "method": "lightning", "realm": "…", "request": "…" },
|
|
1900
|
+
* "payload": { "preimage": "<64-char lowercase hex>" }
|
|
1901
|
+
* }
|
|
1902
|
+
*
|
|
1903
|
+
* Keys are sorted lexicographically at every level per JCS.
|
|
1904
|
+
*/
|
|
1905
|
+
const buildMppCredential = (challenge, preimage, source) => {
|
|
1906
|
+
const challengeEcho = {
|
|
1907
|
+
id: challenge.id,
|
|
1908
|
+
intent: challenge.intent,
|
|
1909
|
+
method: challenge.method,
|
|
1910
|
+
realm: challenge.realm,
|
|
1911
|
+
request: challenge.request,
|
|
1912
|
+
};
|
|
1913
|
+
if (challenge.expires) {
|
|
1914
|
+
challengeEcho.expires = challenge.expires;
|
|
1915
|
+
}
|
|
1916
|
+
const credential = {
|
|
1917
|
+
challenge: challengeEcho,
|
|
1918
|
+
payload: { preimage },
|
|
1919
|
+
};
|
|
1920
|
+
return encodeBase64url(jcs(credential));
|
|
1921
|
+
};
|
|
1922
|
+
|
|
1923
|
+
/**
|
|
1924
|
+
* Handle a `WWW-Authenticate: Payment …` challenge produced by a
|
|
1925
|
+
* draft-lightning-charge-00 server.
|
|
1926
|
+
*
|
|
1927
|
+
* Flow:
|
|
1928
|
+
* 1. Parse the challenge from the header.
|
|
1929
|
+
* 2. Decode the `request` auth-param to find the BOLT11 invoice.
|
|
1930
|
+
* 3. Pay the invoice via the wallet; receive the HTLC preimage.
|
|
1931
|
+
* 4. Build the `Authorization: Payment <credential>` header.
|
|
1932
|
+
* 5. Retry the original request with the credential.
|
|
1933
|
+
*/
|
|
1934
|
+
const handleMppChargePayment = async (wwwAuthHeader, url, fetchArgs, headers, wallet) => {
|
|
1935
|
+
const challenge = parseMppChallenge(wwwAuthHeader);
|
|
1936
|
+
if (!challenge) {
|
|
1937
|
+
throw new Error("mpp: invalid or unsupported WWW-Authenticate challenge (expected Payment method=lightning intent=charge)");
|
|
1938
|
+
}
|
|
1939
|
+
let request;
|
|
1940
|
+
try {
|
|
1941
|
+
request = JSON.parse(decodeBase64url(challenge.request));
|
|
1942
|
+
}
|
|
1943
|
+
catch (_) {
|
|
1944
|
+
throw new Error("mpp: invalid request auth-param (not valid base64url-encoded JSON)");
|
|
1945
|
+
}
|
|
1946
|
+
const invoice = request.methodDetails?.invoice;
|
|
1947
|
+
if (!invoice) {
|
|
1948
|
+
throw new Error("mpp: missing invoice in charge request");
|
|
1949
|
+
}
|
|
1950
|
+
const invResp = await wallet.payInvoice({ invoice });
|
|
1951
|
+
// Per spec: Authorization: Payment <base64url-token> (single token, no wrapper)
|
|
1952
|
+
const credential = buildMppCredential(challenge, invResp.preimage);
|
|
1953
|
+
headers.set("Authorization", `Payment ${credential}`);
|
|
1954
|
+
return fetch(url, fetchArgs);
|
|
1955
|
+
};
|
|
1956
|
+
/**
|
|
1957
|
+
* Fetch a resource protected by the draft-lightning-charge-00 payment
|
|
1958
|
+
* authentication protocol.
|
|
1959
|
+
*
|
|
1960
|
+
* On a `402 Payment Required` response that carries a
|
|
1961
|
+
* `WWW-Authenticate: Payment method="lightning" intent="charge" …` header
|
|
1962
|
+
* the function pays the embedded BOLT11 invoice and retries with the
|
|
1963
|
+
* resulting preimage as the credential.
|
|
1964
|
+
*
|
|
1965
|
+
* Note: lightning-charge uses consume-once challenge semantics – each
|
|
1966
|
+
* challenge embeds a fresh invoice, so paid credentials cannot be reused.
|
|
1967
|
+
* The `store` option is accepted for API consistency but is not used.
|
|
1968
|
+
*/
|
|
1969
|
+
const fetchWithMpp = async (url, fetchArgs, options) => {
|
|
1970
|
+
const wallet = options.wallet;
|
|
1971
|
+
if (!wallet) {
|
|
1972
|
+
throw new Error("wallet is missing");
|
|
1973
|
+
}
|
|
1974
|
+
if (!fetchArgs) {
|
|
1975
|
+
fetchArgs = {};
|
|
1976
|
+
}
|
|
1977
|
+
fetchArgs.cache = "no-store";
|
|
1978
|
+
fetchArgs.mode = "cors";
|
|
1979
|
+
const headers = new Headers(fetchArgs.headers ?? undefined);
|
|
1980
|
+
fetchArgs.headers = headers;
|
|
1981
|
+
const initResp = await fetch(url, fetchArgs);
|
|
1982
|
+
const wwwAuthHeader = initResp.headers.get("www-authenticate");
|
|
1983
|
+
if (!wwwAuthHeader ||
|
|
1984
|
+
!wwwAuthHeader.trimStart().toLowerCase().startsWith("payment")) {
|
|
1985
|
+
return initResp;
|
|
1986
|
+
}
|
|
1987
|
+
return handleMppChargePayment(wwwAuthHeader, url, fetchArgs, headers, wallet);
|
|
1988
|
+
};
|
|
1989
|
+
|
|
1990
|
+
const fetch402 = async (url, fetchArgs, options) => {
|
|
1991
|
+
const wallet = options.maxAmount
|
|
1992
|
+
? createGuardedWallet(options.wallet, options.maxAmount)
|
|
1993
|
+
: options.wallet;
|
|
1994
|
+
if (!fetchArgs) {
|
|
1995
|
+
fetchArgs = {};
|
|
1996
|
+
}
|
|
1997
|
+
fetchArgs.cache = "no-store";
|
|
1998
|
+
fetchArgs.mode = "cors";
|
|
1999
|
+
const headers = new Headers(fetchArgs.headers ?? undefined);
|
|
2000
|
+
fetchArgs.headers = headers;
|
|
2001
|
+
const initResp = await fetch(url, fetchArgs);
|
|
2002
|
+
const wwwAuthHeader = initResp.headers.get("www-authenticate");
|
|
2003
|
+
if (wwwAuthHeader) {
|
|
2004
|
+
const trimmed = wwwAuthHeader.trimStart().toLowerCase();
|
|
2005
|
+
if (trimmed.startsWith("payment")) {
|
|
2006
|
+
return handleMppChargePayment(wwwAuthHeader, url, fetchArgs, headers, wallet);
|
|
2007
|
+
}
|
|
2008
|
+
if (trimmed.startsWith("l402") || trimmed.startsWith("lsat")) {
|
|
2009
|
+
return handleL402Payment(wwwAuthHeader, url, fetchArgs, headers, wallet);
|
|
2010
|
+
}
|
|
2011
|
+
throw new Error(`fetch402: unsupported WWW-Authenticate scheme: ${wwwAuthHeader}`);
|
|
2012
|
+
}
|
|
2013
|
+
const x402Header = initResp.headers.get("PAYMENT-REQUIRED");
|
|
2014
|
+
if (x402Header) {
|
|
2015
|
+
return handleX402Payment(x402Header, url, fetchArgs, headers, wallet);
|
|
2016
|
+
}
|
|
2017
|
+
return initResp;
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
async function issueL402Macaroon(secret, paymentHash, params) {
|
|
2021
|
+
if (params !== undefined &&
|
|
2022
|
+
Object.prototype.hasOwnProperty.call(params, "paymentHash")) {
|
|
2023
|
+
throw new Error("paymentHash is reserved");
|
|
2024
|
+
}
|
|
2025
|
+
const payload = { ...params, paymentHash };
|
|
2026
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
2027
|
+
const mac = await sign(secret, encoded);
|
|
2028
|
+
return `${encoded}.${mac}`;
|
|
2029
|
+
}
|
|
2030
|
+
async function verifyL402Macaroon(secret, token) {
|
|
2031
|
+
const { timingSafeEqual } = await import('crypto');
|
|
2032
|
+
const dotIndex = token.lastIndexOf(".");
|
|
2033
|
+
if (dotIndex === -1)
|
|
2034
|
+
throw new Error("Invalid macaroon token");
|
|
2035
|
+
const encoded = token.slice(0, dotIndex);
|
|
2036
|
+
const mac = token.slice(dotIndex + 1);
|
|
2037
|
+
// Constant-time comparison to prevent timing attacks
|
|
2038
|
+
const expectedMac = await sign(secret, encoded);
|
|
2039
|
+
try {
|
|
2040
|
+
if (!timingSafeEqual(Buffer.from(mac, "hex"), Buffer.from(expectedMac, "hex"))) {
|
|
2041
|
+
throw new Error("Invalid macaroon token");
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
catch (e) {
|
|
2045
|
+
throw new Error("Invalid macaroon token");
|
|
2046
|
+
}
|
|
2047
|
+
try {
|
|
2048
|
+
const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
2049
|
+
if (parsed === null ||
|
|
2050
|
+
typeof parsed !== "object" ||
|
|
2051
|
+
Array.isArray(parsed) ||
|
|
2052
|
+
typeof parsed.paymentHash !== "string") {
|
|
2053
|
+
throw new Error("Invalid macaroon payload");
|
|
2054
|
+
}
|
|
2055
|
+
return parsed;
|
|
2056
|
+
}
|
|
2057
|
+
catch {
|
|
2058
|
+
throw new Error("Invalid macaroon token");
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
async function sign(secret, payload) {
|
|
2062
|
+
const { createHmac } = await import('crypto');
|
|
2063
|
+
return createHmac("sha256", secret).update(payload).digest("hex");
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
/**
|
|
2067
|
+
* Server: create a WWW-Authenticate header for a given macaroon and invoice
|
|
2068
|
+
* @param args the macaroon/token and invoice generated for the client's request
|
|
2069
|
+
* @returns the header value
|
|
2070
|
+
*/
|
|
2071
|
+
const makeL402AuthenticateHeader = (args) => {
|
|
2072
|
+
if (!args.token) {
|
|
2073
|
+
throw new Error("token must be provided");
|
|
2074
|
+
}
|
|
2075
|
+
return `L402 version="0" token="${args.token}", invoice="${args.invoice}"`;
|
|
1745
2076
|
};
|
|
2077
|
+
/**
|
|
2078
|
+
* Server: parse "authorization" header sent from client
|
|
2079
|
+
* @param input value from authorization header
|
|
2080
|
+
* @returns the macaroon and preimage
|
|
2081
|
+
*/
|
|
2082
|
+
function parseL402Authorization(input) {
|
|
2083
|
+
// Backwards compat: LSAT was the former name of L402
|
|
2084
|
+
const normalized = input.replace(/^LSAT /, "L402 ");
|
|
2085
|
+
const prefix = "L402 ";
|
|
2086
|
+
if (!normalized.startsWith(prefix))
|
|
2087
|
+
return null;
|
|
2088
|
+
const credentials = normalized.slice(prefix.length);
|
|
2089
|
+
const colonIndex = credentials.indexOf(":");
|
|
2090
|
+
if (colonIndex === -1) {
|
|
2091
|
+
throw new Error("Invalid authorization header value");
|
|
2092
|
+
}
|
|
2093
|
+
return {
|
|
2094
|
+
token: credentials.slice(0, colonIndex),
|
|
2095
|
+
preimage: credentials.slice(colonIndex + 1),
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
1746
2098
|
|
|
1747
2099
|
const numSatsInBtc = 100000000;
|
|
1748
2100
|
const getFiatCurrencies = async () => {
|
|
@@ -1797,10 +2149,12 @@ exports.DEFAULT_PROXY = DEFAULT_PROXY;
|
|
|
1797
2149
|
exports.Invoice = Invoice;
|
|
1798
2150
|
exports.LN_ADDRESS_REGEX = LN_ADDRESS_REGEX;
|
|
1799
2151
|
exports.LightningAddress = LightningAddress;
|
|
1800
|
-
exports.
|
|
1801
|
-
exports.NoStorage = NoStorage;
|
|
2152
|
+
exports.createGuardedWallet = createGuardedWallet;
|
|
1802
2153
|
exports.decodeInvoice = decodeInvoice;
|
|
2154
|
+
exports.fetch402 = fetch402;
|
|
1803
2155
|
exports.fetchWithL402 = fetchWithL402;
|
|
2156
|
+
exports.fetchWithMpp = fetchWithMpp;
|
|
2157
|
+
exports.fetchWithX402 = fetchWithX402;
|
|
1804
2158
|
exports.fromHexString = fromHexString;
|
|
1805
2159
|
exports.generateZapEvent = generateZapEvent;
|
|
1806
2160
|
exports.getEventHash = getEventHash;
|
|
@@ -1811,12 +2165,16 @@ exports.getFormattedFiatValue = getFormattedFiatValue;
|
|
|
1811
2165
|
exports.getSatoshiValue = getSatoshiValue;
|
|
1812
2166
|
exports.isUrl = isUrl;
|
|
1813
2167
|
exports.isValidAmount = isValidAmount;
|
|
1814
|
-
exports.
|
|
2168
|
+
exports.issueL402Macaroon = issueL402Macaroon;
|
|
2169
|
+
exports.makeL402AuthenticateHeader = makeL402AuthenticateHeader;
|
|
1815
2170
|
exports.parseKeysendResponse = parseKeysendResponse;
|
|
1816
2171
|
exports.parseL402 = parseL402;
|
|
2172
|
+
exports.parseL402Authorization = parseL402Authorization;
|
|
1817
2173
|
exports.parseLnUrlPayResponse = parseLnUrlPayResponse;
|
|
1818
2174
|
exports.parseNostrResponse = parseNostrResponse;
|
|
1819
2175
|
exports.sendBoostagram = sendBoostagram;
|
|
1820
2176
|
exports.serializeEvent = serializeEvent;
|
|
1821
2177
|
exports.validateEvent = validateEvent;
|
|
2178
|
+
exports.validatePreimage = validatePreimage;
|
|
2179
|
+
exports.verifyL402Macaroon = verifyL402Macaroon;
|
|
1822
2180
|
//# sourceMappingURL=index.cjs.map
|