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