bolt12-utils 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +348 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# bolt12-utils
|
|
2
|
+
|
|
3
|
+
Pure TypeScript BOLT12 library for the Lightning Network. Decode, validate, and create BOLT12 offers, invoice requests, invoices, and payer proofs — with zero native dependencies.
|
|
4
|
+
|
|
5
|
+
**[Live Playground](https://vincenzopalazzo.github.io/bolt12/)**
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install bolt12-utils
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Decode an Offer
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { decodeOffer } from 'bolt12-utils';
|
|
19
|
+
|
|
20
|
+
const offer = decodeOffer('lno1pgx9getnwss8vetrw3hhyuc...');
|
|
21
|
+
|
|
22
|
+
console.log(offer.hrp); // 'lno'
|
|
23
|
+
console.log(offer.description); // 'Test vectors'
|
|
24
|
+
console.log(offer.issuer_id); // hex-encoded 33-byte compressed pubkey
|
|
25
|
+
console.log(offer.offer_id); // Uint8Array (merkle root)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Low-Level Decoding
|
|
29
|
+
|
|
30
|
+
If you need more control, use the building blocks directly:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { decodeBolt12, parseTlvStream, validateOffer } from 'bolt12-utils';
|
|
34
|
+
|
|
35
|
+
// Step 1: Bech32 decode
|
|
36
|
+
const { hrp, data } = decodeBolt12('lno1pgx9getnwss8vetrw3hhyuc...');
|
|
37
|
+
console.log(hrp); // 'lno'
|
|
38
|
+
|
|
39
|
+
// Step 2: Parse TLV stream
|
|
40
|
+
const records = parseTlvStream(data);
|
|
41
|
+
for (const rec of records) {
|
|
42
|
+
console.log(`type=${rec.type} length=${rec.length}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Step 3: Validate offer semantics (throws on invalid)
|
|
46
|
+
validateOffer(records);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Extract Typed Fields (Generated from Spec)
|
|
50
|
+
|
|
51
|
+
The library auto-generates types from the [BOLT12 spec CSV](https://github.com/lightning/bolts/blob/master/12-offer-encoding.md). These cover all four message types:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import {
|
|
55
|
+
decodeBolt12,
|
|
56
|
+
parseTlvStream,
|
|
57
|
+
extractGeneratedOfferFields,
|
|
58
|
+
extractInvoiceRequestFields,
|
|
59
|
+
extractInvoiceFields,
|
|
60
|
+
extractInvoiceErrorFields,
|
|
61
|
+
type GeneratedOfferFields,
|
|
62
|
+
type InvoiceRequestFields,
|
|
63
|
+
type InvoiceFields,
|
|
64
|
+
type InvoiceErrorFields,
|
|
65
|
+
} from 'bolt12-utils';
|
|
66
|
+
|
|
67
|
+
const { data } = decodeBolt12('lno1...');
|
|
68
|
+
const records = parseTlvStream(data);
|
|
69
|
+
|
|
70
|
+
// Spec-compliant field names (offer_description, not description)
|
|
71
|
+
const offer: GeneratedOfferFields = extractGeneratedOfferFields(records);
|
|
72
|
+
console.log(offer.offer_description); // string | undefined
|
|
73
|
+
console.log(offer.offer_amount); // bigint | undefined
|
|
74
|
+
console.log(offer.offer_issuer_id); // string (hex) | undefined
|
|
75
|
+
console.log(offer.offer_chains); // Uint8Array[] | undefined
|
|
76
|
+
console.log(offer.offer_absolute_expiry); // bigint | undefined
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### TLV Constants and Lookups
|
|
80
|
+
|
|
81
|
+
Every TLV type is exported as a `bigint` constant, and lookup maps let you resolve type numbers to names:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import {
|
|
85
|
+
OFFER_DESCRIPTION,
|
|
86
|
+
OFFER_AMOUNT,
|
|
87
|
+
INVOICE_PAYMENT_HASH,
|
|
88
|
+
SIGNATURE,
|
|
89
|
+
OFFER_TLV_NAMES,
|
|
90
|
+
INVOICE_TLV_NAMES,
|
|
91
|
+
KNOWN_OFFER_TYPES,
|
|
92
|
+
} from 'bolt12-utils';
|
|
93
|
+
|
|
94
|
+
// Constants
|
|
95
|
+
console.log(OFFER_DESCRIPTION); // 10n
|
|
96
|
+
console.log(INVOICE_PAYMENT_HASH); // 168n
|
|
97
|
+
console.log(SIGNATURE); // 240n
|
|
98
|
+
|
|
99
|
+
// Name lookups
|
|
100
|
+
console.log(OFFER_TLV_NAMES.get(10n)); // 'offer_description'
|
|
101
|
+
console.log(INVOICE_TLV_NAMES.get(168n)); // 'invoice_payment_hash'
|
|
102
|
+
|
|
103
|
+
// Known type sets (useful for validation)
|
|
104
|
+
console.log(KNOWN_OFFER_TYPES.has(10n)); // true
|
|
105
|
+
console.log(KNOWN_OFFER_TYPES.has(99n)); // false
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Merkle Root and Signature Verification
|
|
109
|
+
|
|
110
|
+
BOLT12 uses BIP-340 Schnorr signatures over a Merkle tree of TLV fields:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import {
|
|
114
|
+
decodeBolt12,
|
|
115
|
+
parseTlvStream,
|
|
116
|
+
computeMerkleRoot,
|
|
117
|
+
verifySignature,
|
|
118
|
+
} from 'bolt12-utils';
|
|
119
|
+
|
|
120
|
+
const { data } = decodeBolt12('lni1...');
|
|
121
|
+
const records = parseTlvStream(data);
|
|
122
|
+
|
|
123
|
+
// Compute the merkle root (= offer_id for offers)
|
|
124
|
+
const merkleRoot = computeMerkleRoot(records);
|
|
125
|
+
|
|
126
|
+
// Verify a signature (64-byte Schnorr sig, 32-byte x-only pubkey)
|
|
127
|
+
const valid = verifySignature('invoice', merkleRoot, pubkey32, sig64);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Payer Proofs
|
|
131
|
+
|
|
132
|
+
Payer proofs (`lnp`) let a payer prove they paid an invoice while selectively disclosing only certain fields:
|
|
133
|
+
|
|
134
|
+
#### Decode and Verify
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { decodePayerProof, verifyPayerProof } from 'bolt12-utils';
|
|
138
|
+
|
|
139
|
+
const { proof } = decodePayerProof('lnp1...');
|
|
140
|
+
|
|
141
|
+
// Inspect disclosed fields
|
|
142
|
+
console.log(proof.includedRecords); // TlvRecord[]
|
|
143
|
+
console.log(proof.preimage); // Uint8Array | undefined
|
|
144
|
+
console.log(proof.payerNote); // string (e.g. "Payment for coffee")
|
|
145
|
+
console.log(proof.omittedTlvs); // bigint[] (marker numbers)
|
|
146
|
+
|
|
147
|
+
// Verify both invoice and payer signatures
|
|
148
|
+
const result = verifyPayerProof(proof);
|
|
149
|
+
console.log(result.valid); // true/false
|
|
150
|
+
console.log(result.merkleRoot); // Uint8Array
|
|
151
|
+
console.log(result.error); // string | undefined
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Create a Payer Proof
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { createPayerProof } from 'bolt12-utils';
|
|
158
|
+
|
|
159
|
+
const result = createPayerProof({
|
|
160
|
+
invoiceHex: '...', // hex-encoded invoice TLV stream
|
|
161
|
+
preimageHex: '...', // 32-byte payment preimage (hex)
|
|
162
|
+
payerSecretKeyHex: '...', // 32-byte BIP-340 secret key (hex)
|
|
163
|
+
note: 'Payment for coffee',
|
|
164
|
+
includedTlvTypes: [174], // optional: extra types to disclose
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
console.log(result.proofBech32); // 'lnp1...'
|
|
168
|
+
console.log(result.proofHex); // hex-encoded proof TLV stream
|
|
169
|
+
console.log(result.merkleRoot); // Uint8Array
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Bech32 Encoding
|
|
173
|
+
|
|
174
|
+
Encode raw TLV bytes back to a BOLT12 string:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { encodeBolt12 } from 'bolt12-utils';
|
|
178
|
+
|
|
179
|
+
const bolt12String = encodeBolt12('lno', tlvBytes);
|
|
180
|
+
// 'lno1pgx9getnwss8...'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## API Reference
|
|
184
|
+
|
|
185
|
+
### High-Level Functions
|
|
186
|
+
|
|
187
|
+
| Function | Description |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `decodeOffer(str)` | Decode, validate, and extract fields from a BOLT12 offer string. Returns `DecodedOffer`. |
|
|
190
|
+
| `decodePayerProof(str)` | Decode and parse a payer proof string. Returns `DecodedPayerProof`. |
|
|
191
|
+
| `createPayerProof(params)` | Create a payer proof from an invoice, preimage, and payer key. |
|
|
192
|
+
| `verifyPayerProof(proof)` | Verify both the invoice signature and payer signature of a proof. |
|
|
193
|
+
|
|
194
|
+
### Building Blocks
|
|
195
|
+
|
|
196
|
+
| Function | Description |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `decodeBolt12(str)` | Bech32-decode a BOLT12 string into `{ hrp, data }`. Handles `+` continuation. |
|
|
199
|
+
| `encodeBolt12(hrp, data)` | Bech32-encode raw bytes into a BOLT12 string. |
|
|
200
|
+
| `parseTlvStream(data)` | Parse raw bytes into `TlvRecord[]`. Enforces ascending type order. |
|
|
201
|
+
| `validateOffer(records)` | Validate offer TLV records against BOLT12 semantic rules. |
|
|
202
|
+
| `computeMerkleRoot(records)` | Compute the Merkle root of TLV records (= `offer_id` for offers). |
|
|
203
|
+
| `verifySignature(name, root, pubkey, sig)` | Verify a BIP-340 Schnorr signature on a BOLT12 message. |
|
|
204
|
+
| `taggedHash(tag, msg)` | Compute `SHA256(SHA256(tag) \|\| SHA256(tag) \|\| msg)`. |
|
|
205
|
+
|
|
206
|
+
### Generated Field Extractors
|
|
207
|
+
|
|
208
|
+
| Function | Returns | Message Type |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `extractGeneratedOfferFields(records)` | `GeneratedOfferFields` | Offer (`lno`) |
|
|
211
|
+
| `extractInvoiceRequestFields(records)` | `InvoiceRequestFields` | Invoice Request (`lnr`) |
|
|
212
|
+
| `extractInvoiceFields(records)` | `InvoiceFields` | Invoice (`lni`) |
|
|
213
|
+
| `extractInvoiceErrorFields(records)` | `InvoiceErrorFields` | Invoice Error |
|
|
214
|
+
|
|
215
|
+
### Types
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
interface TlvRecord {
|
|
219
|
+
type: bigint;
|
|
220
|
+
length: bigint;
|
|
221
|
+
value: Uint8Array;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
interface DecodedOffer extends OfferFields {
|
|
225
|
+
hrp: 'lno' | 'lnr' | 'lni' | 'lnp';
|
|
226
|
+
offer_id: Uint8Array;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface GeneratedOfferFields {
|
|
230
|
+
offer_chains?: Uint8Array[];
|
|
231
|
+
offer_metadata?: Uint8Array;
|
|
232
|
+
offer_currency?: string;
|
|
233
|
+
offer_amount?: bigint;
|
|
234
|
+
offer_description?: string;
|
|
235
|
+
offer_features?: Uint8Array;
|
|
236
|
+
offer_absolute_expiry?: bigint;
|
|
237
|
+
offer_paths?: Uint8Array;
|
|
238
|
+
offer_issuer?: string;
|
|
239
|
+
offer_quantity_max?: bigint;
|
|
240
|
+
offer_issuer_id?: string;
|
|
241
|
+
records: TlvRecord[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
interface InvoiceRequestFields extends GeneratedOfferFields {
|
|
245
|
+
invreq_metadata?: Uint8Array;
|
|
246
|
+
invreq_chain?: Uint8Array;
|
|
247
|
+
invreq_amount?: bigint;
|
|
248
|
+
invreq_features?: Uint8Array;
|
|
249
|
+
invreq_quantity?: bigint;
|
|
250
|
+
invreq_payer_id?: string;
|
|
251
|
+
invreq_payer_note?: string;
|
|
252
|
+
invreq_paths?: Uint8Array;
|
|
253
|
+
invreq_bip_353_name?: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
interface InvoiceFields extends InvoiceRequestFields {
|
|
257
|
+
invoice_paths?: Uint8Array;
|
|
258
|
+
invoice_blindedpay?: BlindedPayinfo[];
|
|
259
|
+
invoice_created_at?: bigint;
|
|
260
|
+
invoice_relative_expiry?: bigint;
|
|
261
|
+
invoice_payment_hash?: Uint8Array;
|
|
262
|
+
invoice_amount?: bigint;
|
|
263
|
+
invoice_fallbacks?: FallbackAddress[];
|
|
264
|
+
invoice_features?: Uint8Array;
|
|
265
|
+
invoice_node_id?: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
interface PayerProofFields {
|
|
269
|
+
includedRecords: TlvRecord[];
|
|
270
|
+
signature: Uint8Array;
|
|
271
|
+
preimage: Uint8Array | undefined;
|
|
272
|
+
omittedTlvs: bigint[];
|
|
273
|
+
missingHashes: Uint8Array[];
|
|
274
|
+
leafHashes: Uint8Array[];
|
|
275
|
+
payerSignature: Uint8Array;
|
|
276
|
+
payerNote: string;
|
|
277
|
+
invoicePaymentHash: Uint8Array;
|
|
278
|
+
invoiceNodeId: Uint8Array;
|
|
279
|
+
payerId: Uint8Array;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Constants
|
|
284
|
+
|
|
285
|
+
All TLV type numbers are exported as `bigint` constants:
|
|
286
|
+
|
|
287
|
+
**Offer:** `OFFER_CHAINS` (2), `OFFER_METADATA` (4), `OFFER_CURRENCY` (6), `OFFER_AMOUNT` (8), `OFFER_DESCRIPTION` (10), `OFFER_FEATURES` (12), `OFFER_ABSOLUTE_EXPIRY` (14), `OFFER_PATHS` (16), `OFFER_ISSUER` (18), `OFFER_QUANTITY_MAX` (20), `OFFER_ISSUER_ID` (22)
|
|
288
|
+
|
|
289
|
+
**Invoice Request:** `INVREQ_METADATA` (0), `INVREQ_CHAIN` (80), `INVREQ_AMOUNT` (82), `INVREQ_FEATURES` (84), `INVREQ_QUANTITY` (86), `INVREQ_PAYER_ID` (88), `INVREQ_PAYER_NOTE` (89), `INVREQ_PATHS` (90), `INVREQ_BIP_353_NAME` (91)
|
|
290
|
+
|
|
291
|
+
**Invoice:** `INVOICE_PATHS` (160), `INVOICE_BLINDEDPAY` (162), `INVOICE_CREATED_AT` (164), `INVOICE_RELATIVE_EXPIRY` (166), `INVOICE_PAYMENT_HASH` (168), `INVOICE_AMOUNT` (170), `INVOICE_FALLBACKS` (172), `INVOICE_FEATURES` (174), `INVOICE_NODE_ID` (176)
|
|
292
|
+
|
|
293
|
+
**Signature:** `SIGNATURE` (240)
|
|
294
|
+
|
|
295
|
+
## Browser Usage
|
|
296
|
+
|
|
297
|
+
The library ships a browser bundle for the [live playground](https://vincenzopalazzo.github.io/bolt12/):
|
|
298
|
+
|
|
299
|
+
```html
|
|
300
|
+
<script src="https://vincenzopalazzo.github.io/bolt12/bolt12-bundle.js"></script>
|
|
301
|
+
<script>
|
|
302
|
+
const { decodeOffer } = bolt12;
|
|
303
|
+
const offer = decodeOffer('lno1...');
|
|
304
|
+
console.log(offer.description);
|
|
305
|
+
</script>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Build it yourself:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
npm run build:web
|
|
312
|
+
# Output: website/bolt12-bundle.js
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Dependencies
|
|
316
|
+
|
|
317
|
+
Only two dependencies, both from the [noble](https://github.com/paulmillr/noble-curves) family (audited, pure JS):
|
|
318
|
+
|
|
319
|
+
- `@noble/curves` — secp256k1 / BIP-340 Schnorr
|
|
320
|
+
- `@noble/hashes` — SHA-256
|
|
321
|
+
|
|
322
|
+
## Regenerating Types from Spec
|
|
323
|
+
|
|
324
|
+
The generated types in `src/generated.ts` are produced from the BOLT12 spec CSV:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm run generate
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
This runs `tools/generate-ts.ts` against `specs/bolt12.csv` (extracted from [lightning/bolts](https://github.com/lightning/bolts) `12-offer-encoding.md`).
|
|
331
|
+
|
|
332
|
+
## Support Development
|
|
333
|
+
|
|
334
|
+
If you find this library useful, consider donating. The following [BIP21/321](https://bips.dev/321/) URI supports on-chain (taproot), [ARK](https://ark-protocol.org), and [BOLT12](https://bolt12.org) payments:
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
bitcoin:bc1pyys36jag8qug09c36d9j6427kny3d0x08u3wf5l89sks5sxyq3fsp2vddt?ark=ark1qq4hfssprtcgnjzf8qlw2f78yvjau5kldfugg29k34y7j96q2w4t5fjuejh7a4eauna0vf2eegcw95w3dyjzl8gykpye50e9qneuwezqdfwupf&lno=lno1pgqppmsrse80qf0aara4slvcjxrvu6j2rp5ftmjy4yntlsmsutpkvkt6878sxn8g96fuzlhw75hendmuhjy0gp607tsgzaasdvjmstcwcgc6vgwyqgp6mv9u948ngt3j0urev4ga0vw06cpvasexgn00feez9vfgdkyykfgqxdaa8ysjuy8um26ekywlceecwalj0zvqu5h0dd486uhhzvj9m3qlnmaa9awj0cft7x95h7yn9vaep4gm055q8rsctl6lthka2htmk8pzxvgyzae72gnapuhg2v9rtgwfg4mlr56lqqerat2vv2u2aka8e592vqluf5erqqs2ve30snd2pr2d0h7fdfl9js6wyzjl4c66nu6d32nj4w2ft0um9q4q
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| Method | Details |
|
|
341
|
+
|---|---|
|
|
342
|
+
| On-chain (Taproot) | `bc1pyys36jag8qug09c36d9j6427kny3d0x08u3wf5l89sks5sxyq3fsp2vddt` |
|
|
343
|
+
| ARK | `ark1qq4hfssprtcgnjzf8qlw2f78yvjau5kldfugg29k34y7j96q2w4t5fjuejh7a4eauna0vf2eegcw95w3dyjzl8gykpye50e9qneuwezqdfwupf` |
|
|
344
|
+
| BOLT12 Offer | `lno1pgqppmsrse80qf0aara4slvcjxrvu6j2rp5ftmjy4yntlsmsutpkvkt6878sxn8g96fuzlhw75hendmuhjy0gp607tsgzaasdvjmstcwcgc6vgwyqgp6mv9u948ngt3j0urev4ga0vw06cpvasexgn00feez9vfgdkyykfgqxdaa8ysjuy8um26ekywlceecwalj0zvqu5h0dd486uhhzvj9m3qlnmaa9awj0cft7x95h7yn9vaep4gm055q8rsctl6lthka2htmk8pzxvgyzae72gnapuhg2v9rtgwfg4mlr56lqqerat2vv2u2aka8e592vqluf5erqqs2ve30snd2pr2d0h7fdfl9js6wyzjl4c66nu6d32nj4w2ft0um9q4q` |
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bolt12-utils",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Pure JS/TS BOLT12
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Pure JS/TS BOLT12 utilities for Lightning Network — decode, validate, sign, and verify offers, invoices, and payer proofs",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|