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.
Files changed (2) hide show
  1. package/README.md +348 -0
  2. 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.0",
4
- "description": "Pure JS/TS BOLT12 offer encoder/decoder for Lightning Network",
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": [