• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/channeldb/migration_01_to_11/zpay32/decode.go
1
package zpay32
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "errors"
7
        "fmt"
8
        "strings"
9
        "time"
10

11
        "github.com/btcsuite/btcd/btcec/v2"
12
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
13
        "github.com/btcsuite/btcd/btcutil"
14
        "github.com/btcsuite/btcd/btcutil/bech32"
15
        "github.com/btcsuite/btcd/chaincfg"
16
        "github.com/btcsuite/btcd/chaincfg/chainhash"
17
        lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
18
)
19

20
// Decode parses the provided encoded invoice and returns a decoded Invoice if
21
// it is valid by BOLT-0011 and matches the provided active network.
UNCOV
22
func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
×
UNCOV
23
        decodedInvoice := Invoice{}
×
UNCOV
24

×
UNCOV
25
        // Before bech32 decoding the invoice, make sure that it is not too large.
×
UNCOV
26
        // This is done as an anti-DoS measure since bech32 decoding is expensive.
×
UNCOV
27
        if len(invoice) > maxInvoiceLength {
×
28
                return nil, ErrInvoiceTooLarge
×
29
        }
×
30

31
        // Decode the invoice using the modified bech32 decoder.
UNCOV
32
        hrp, data, err := decodeBech32(invoice)
×
UNCOV
33
        if err != nil {
×
34
                return nil, err
×
35
        }
×
36

37
        // We expect the human-readable part to at least have ln + one char
38
        // encoding the network.
UNCOV
39
        if len(hrp) < 3 {
×
40
                return nil, fmt.Errorf("hrp too short")
×
41
        }
×
42

43
        // First two characters of HRP should be "ln".
UNCOV
44
        if hrp[:2] != "ln" {
×
45
                return nil, fmt.Errorf("prefix should be \"ln\"")
×
46
        }
×
47

48
        // The next characters should be a valid prefix for a segwit BIP173
49
        // address that match the active network.
UNCOV
50
        if !strings.HasPrefix(hrp[2:], net.Bech32HRPSegwit) {
×
UNCOV
51
                return nil, fmt.Errorf(
×
UNCOV
52
                        "invoice not for current active network '%s'", net.Name)
×
UNCOV
53
        }
×
UNCOV
54
        decodedInvoice.Net = net
×
UNCOV
55

×
UNCOV
56
        // Optionally, if there's anything left of the HRP after ln + the segwit
×
UNCOV
57
        // prefix, we try to decode this as the payment amount.
×
UNCOV
58
        var netPrefixLength = len(net.Bech32HRPSegwit) + 2
×
UNCOV
59
        if len(hrp) > netPrefixLength {
×
60
                amount, err := decodeAmount(hrp[netPrefixLength:])
×
61
                if err != nil {
×
62
                        return nil, err
×
63
                }
×
64
                decodedInvoice.MilliSat = &amount
×
65
        }
66

67
        // Everything except the last 520 bits of the data encodes the invoice's
68
        // timestamp and tagged fields.
UNCOV
69
        if len(data) < signatureBase32Len {
×
70
                return nil, errors.New("short invoice")
×
71
        }
×
UNCOV
72
        invoiceData := data[:len(data)-signatureBase32Len]
×
UNCOV
73

×
UNCOV
74
        // Parse the timestamp and tagged fields, and fill the Invoice struct.
×
UNCOV
75
        if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
×
76
                return nil, err
×
77
        }
×
78

79
        // The last 520 bits (104 groups) make up the signature.
UNCOV
80
        sigBase32 := data[len(data)-signatureBase32Len:]
×
UNCOV
81
        sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
×
UNCOV
82
        if err != nil {
×
83
                return nil, err
×
84
        }
×
UNCOV
85
        var sig lnwire.Sig
×
UNCOV
86
        copy(sig[:], sigBase256[:64])
×
UNCOV
87
        recoveryID := sigBase256[64]
×
UNCOV
88

×
UNCOV
89
        // The signature is over the hrp + the data the invoice, encoded in
×
UNCOV
90
        // base 256.
×
UNCOV
91
        taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
×
UNCOV
92
        if err != nil {
×
93
                return nil, err
×
94
        }
×
95

UNCOV
96
        toSign := append([]byte(hrp), taggedDataBytes...)
×
UNCOV
97

×
UNCOV
98
        // We expect the signature to be over the single SHA-256 hash of that
×
UNCOV
99
        // data.
×
UNCOV
100
        hash := chainhash.HashB(toSign)
×
UNCOV
101

×
UNCOV
102
        // If the destination pubkey was provided as a tagged field, use that
×
UNCOV
103
        // to verify the signature, if not do public key recovery.
×
UNCOV
104
        if decodedInvoice.Destination != nil {
×
105
                signature, err := sig.ToSignature()
×
106
                if err != nil {
×
107
                        return nil, fmt.Errorf("unable to deserialize "+
×
108
                                "signature: %v", err)
×
109
                }
×
110
                if !signature.Verify(hash, decodedInvoice.Destination) {
×
111
                        return nil, fmt.Errorf("invalid invoice signature")
×
112
                }
×
UNCOV
113
        } else {
×
UNCOV
114
                headerByte := recoveryID + 27 + 4
×
UNCOV
115
                compactSign := append([]byte{headerByte}, sig[:]...)
×
UNCOV
116
                pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
×
UNCOV
117
                if err != nil {
×
118
                        return nil, err
×
119
                }
×
UNCOV
120
                decodedInvoice.Destination = pubkey
×
121
        }
122

123
        // If no feature vector was decoded, populate an empty one.
UNCOV
124
        if decodedInvoice.Features == nil {
×
UNCOV
125
                decodedInvoice.Features = lnwire.NewFeatureVector(
×
UNCOV
126
                        nil, lnwire.Features,
×
UNCOV
127
                )
×
UNCOV
128
        }
×
129

130
        // Now that we have created the invoice, make sure it has the required
131
        // fields set.
UNCOV
132
        if err := validateInvoice(&decodedInvoice); err != nil {
×
133
                return nil, err
×
134
        }
×
135

UNCOV
136
        return &decodedInvoice, nil
×
137
}
138

139
// parseData parses the data part of the invoice. It expects base32 data
140
// returned from the bech32.Decode method, except signature.
UNCOV
141
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
×
UNCOV
142
        // It must contain the timestamp, encoded using 35 bits (7 groups).
×
UNCOV
143
        if len(data) < timestampBase32Len {
×
144
                return fmt.Errorf("data too short: %d", len(data))
×
145
        }
×
146

UNCOV
147
        t, err := parseTimestamp(data[:timestampBase32Len])
×
UNCOV
148
        if err != nil {
×
149
                return err
×
150
        }
×
UNCOV
151
        invoice.Timestamp = time.Unix(int64(t), 0)
×
UNCOV
152

×
UNCOV
153
        // The rest are tagged parts.
×
UNCOV
154
        tagData := data[7:]
×
UNCOV
155
        return parseTaggedFields(invoice, tagData, net)
×
156
}
157

158
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
UNCOV
159
func parseTimestamp(data []byte) (uint64, error) {
×
UNCOV
160
        if len(data) != timestampBase32Len {
×
161
                return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
×
162
                        len(data)*5)
×
163
        }
×
164

UNCOV
165
        return base32ToUint64(data)
×
166
}
167

168
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
169
// fills the Invoice struct accordingly.
UNCOV
170
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
×
UNCOV
171
        index := 0
×
UNCOV
172
        for len(fields)-index > 0 {
×
UNCOV
173
                // If there are less than 3 groups to read, there cannot be more
×
UNCOV
174
                // interesting information, as we need the type (1 group) and
×
UNCOV
175
                // length (2 groups).
×
UNCOV
176
                //
×
UNCOV
177
                // This means the last tagged field is broken.
×
UNCOV
178
                if len(fields)-index < 3 {
×
179
                        return ErrBrokenTaggedField
×
180
                }
×
181

UNCOV
182
                typ := fields[index]
×
UNCOV
183
                dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
×
UNCOV
184
                if err != nil {
×
185
                        return err
×
186
                }
×
187

188
                // If we don't have enough field data left to read this length,
189
                // return error.
UNCOV
190
                if len(fields) < index+3+int(dataLength) {
×
191
                        return ErrInvalidFieldLength
×
192
                }
×
UNCOV
193
                base32Data := fields[index+3 : index+3+int(dataLength)]
×
UNCOV
194

×
UNCOV
195
                // Advance the index in preparation for the next iteration.
×
UNCOV
196
                index += 3 + int(dataLength)
×
UNCOV
197

×
UNCOV
198
                switch typ {
×
UNCOV
199
                case fieldTypeP:
×
UNCOV
200
                        if invoice.PaymentHash != nil {
×
201
                                // We skip the field if we have already seen a
×
202
                                // supported one.
×
203
                                continue
×
204
                        }
205

UNCOV
206
                        invoice.PaymentHash, err = parse32Bytes(base32Data)
×
207
                case fieldTypeS:
×
208
                        if invoice.PaymentAddr != nil {
×
209
                                // We skip the field if we have already seen a
×
210
                                // supported one.
×
211
                                continue
×
212
                        }
213

214
                        invoice.PaymentAddr, err = parse32Bytes(base32Data)
×
UNCOV
215
                case fieldTypeD:
×
UNCOV
216
                        if invoice.Description != nil {
×
217
                                // We skip the field if we have already seen a
×
218
                                // supported one.
×
219
                                continue
×
220
                        }
221

UNCOV
222
                        invoice.Description, err = parseDescription(base32Data)
×
223
                case fieldTypeN:
×
224
                        if invoice.Destination != nil {
×
225
                                // We skip the field if we have already seen a
×
226
                                // supported one.
×
227
                                continue
×
228
                        }
229

230
                        invoice.Destination, err = parseDestination(base32Data)
×
231
                case fieldTypeH:
×
232
                        if invoice.DescriptionHash != nil {
×
233
                                // We skip the field if we have already seen a
×
234
                                // supported one.
×
235
                                continue
×
236
                        }
237

238
                        invoice.DescriptionHash, err = parse32Bytes(base32Data)
×
239
                case fieldTypeX:
×
240
                        if invoice.expiry != nil {
×
241
                                // We skip the field if we have already seen a
×
242
                                // supported one.
×
243
                                continue
×
244
                        }
245

246
                        invoice.expiry, err = parseExpiry(base32Data)
×
UNCOV
247
                case fieldTypeC:
×
UNCOV
248
                        if invoice.minFinalCLTVExpiry != nil {
×
249
                                // We skip the field if we have already seen a
×
250
                                // supported one.
×
251
                                continue
×
252
                        }
253

UNCOV
254
                        invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data)
×
255
                case fieldTypeF:
×
256
                        if invoice.FallbackAddr != nil {
×
257
                                // We skip the field if we have already seen a
×
258
                                // supported one.
×
259
                                continue
×
260
                        }
261

262
                        invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net)
×
263
                case fieldTypeR:
×
264
                        // An `r` field can be included in an invoice multiple
×
265
                        // times, so we won't skip it if we have already seen
×
266
                        // one.
×
267
                        routeHint, err := parseRouteHint(base32Data)
×
268
                        if err != nil {
×
269
                                return err
×
270
                        }
×
271

272
                        invoice.RouteHints = append(invoice.RouteHints, routeHint)
×
273
                case fieldType9:
×
274
                        if invoice.Features != nil {
×
275
                                // We skip the field if we have already seen a
×
276
                                // supported one.
×
277
                                continue
×
278
                        }
279

280
                        invoice.Features, err = parseFeatures(base32Data)
×
281
                default:
×
282
                        // Ignore unknown type.
283
                }
284

285
                // Check if there was an error from parsing any of the tagged
286
                // fields and return it.
UNCOV
287
                if err != nil {
×
288
                        return err
×
289
                }
×
290
        }
291

UNCOV
292
        return nil
×
293
}
294

295
// parseFieldDataLength converts the two byte slice into a uint16.
UNCOV
296
func parseFieldDataLength(data []byte) (uint16, error) {
×
UNCOV
297
        if len(data) != 2 {
×
298
                return 0, fmt.Errorf("data length must be 2 bytes, was %d",
×
299
                        len(data))
×
300
        }
×
301

UNCOV
302
        return uint16(data[0])<<5 | uint16(data[1]), nil
×
303
}
304

305
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
306
// can be used for payment hashes, description hashes, payment addresses, etc.
UNCOV
307
func parse32Bytes(data []byte) (*[32]byte, error) {
×
UNCOV
308
        var paymentHash [32]byte
×
UNCOV
309

×
UNCOV
310
        // As BOLT-11 states, a reader must skip over the 32-byte fields if
×
UNCOV
311
        // it does not have a length of 52, so avoid returning an error.
×
UNCOV
312
        if len(data) != hashBase32Len {
×
313
                return nil, nil
×
314
        }
×
315

UNCOV
316
        hash, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
317
        if err != nil {
×
318
                return nil, err
×
319
        }
×
320

UNCOV
321
        copy(paymentHash[:], hash)
×
UNCOV
322

×
UNCOV
323
        return &paymentHash, nil
×
324
}
325

326
// parseDescription converts the data (encoded in base32) into a string to use
327
// as the description.
UNCOV
328
func parseDescription(data []byte) (*string, error) {
×
UNCOV
329
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
330
        if err != nil {
×
331
                return nil, err
×
332
        }
×
333

UNCOV
334
        description := string(base256Data)
×
UNCOV
335

×
UNCOV
336
        return &description, nil
×
337
}
338

339
// parseDestination converts the data (encoded in base32) into a 33-byte public
340
// key of the payee node.
341
func parseDestination(data []byte) (*btcec.PublicKey, error) {
×
342
        // As BOLT-11 states, a reader must skip over the destination field
×
343
        // if it does not have a length of 53, so avoid returning an error.
×
344
        if len(data) != pubKeyBase32Len {
×
345
                return nil, nil
×
346
        }
×
347

348
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
349
        if err != nil {
×
350
                return nil, err
×
351
        }
×
352

353
        return btcec.ParsePubKey(base256Data)
×
354
}
355

356
// parseExpiry converts the data (encoded in base32) into the expiry time.
357
func parseExpiry(data []byte) (*time.Duration, error) {
×
358
        expiry, err := base32ToUint64(data)
×
359
        if err != nil {
×
360
                return nil, err
×
361
        }
×
362

363
        duration := time.Duration(expiry) * time.Second
×
364

×
365
        return &duration, nil
×
366
}
367

368
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
369
// to use as the minFinalCLTVExpiry.
UNCOV
370
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
×
UNCOV
371
        expiry, err := base32ToUint64(data)
×
UNCOV
372
        if err != nil {
×
373
                return nil, err
×
374
        }
×
375

UNCOV
376
        return &expiry, nil
×
377
}
378

379
// parseFallbackAddr converts the data (encoded in base32) into a fallback
380
// on-chain address.
381
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) {
×
382
        // Checks if the data is empty or contains a version without an address.
×
383
        if len(data) < 2 {
×
384
                return nil, fmt.Errorf("empty fallback address field")
×
385
        }
×
386

387
        var addr btcutil.Address
×
388

×
389
        version := data[0]
×
390
        switch version {
×
391
        case 0:
×
392
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
393
                if err != nil {
×
394
                        return nil, err
×
395
                }
×
396

397
                switch len(witness) {
×
398
                case 20:
×
399
                        addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
×
400
                case 32:
×
401
                        addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
×
402
                default:
×
403
                        return nil, fmt.Errorf("unknown witness program length %d",
×
404
                                len(witness))
×
405
                }
406

407
                if err != nil {
×
408
                        return nil, err
×
409
                }
×
410
        case 17:
×
411
                pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
412
                if err != nil {
×
413
                        return nil, err
×
414
                }
×
415

416
                addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
×
417
                if err != nil {
×
418
                        return nil, err
×
419
                }
×
420
        case 18:
×
421
                scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
422
                if err != nil {
×
423
                        return nil, err
×
424
                }
×
425

426
                addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
×
427
                if err != nil {
×
428
                        return nil, err
×
429
                }
×
430
        default:
×
431
                // Ignore unknown version.
432
        }
433

434
        return addr, nil
×
435
}
436

437
// parseRouteHint converts the data (encoded in base32) into an array containing
438
// one or more routing hop hints that represent a single route hint.
439
func parseRouteHint(data []byte) ([]HopHint, error) {
×
440
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
441
        if err != nil {
×
442
                return nil, err
×
443
        }
×
444

445
        // Check that base256Data is a multiple of hopHintLen.
446
        if len(base256Data)%hopHintLen != 0 {
×
447
                return nil, fmt.Errorf("expected length multiple of %d bytes, "+
×
448
                        "got %d", hopHintLen, len(base256Data))
×
449
        }
×
450

451
        var routeHint []HopHint
×
452

×
453
        for len(base256Data) > 0 {
×
454
                hopHint := HopHint{}
×
455
                hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
×
456
                if err != nil {
×
457
                        return nil, err
×
458
                }
×
459
                hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
×
460
                hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
×
461
                hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
×
462
                hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
×
463

×
464
                routeHint = append(routeHint, hopHint)
×
465

×
466
                base256Data = base256Data[51:]
×
467
        }
468

469
        return routeHint, nil
×
470
}
471

472
// parseFeatures decodes any feature bits directly from the base32
473
// representation.
474
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
×
475
        rawFeatures := lnwire.NewRawFeatureVector()
×
476
        err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
×
477
        if err != nil {
×
478
                return nil, err
×
479
        }
×
480

481
        return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
×
482
}
483

484
// base32ToUint64 converts a base32 encoded number to uint64.
UNCOV
485
func base32ToUint64(data []byte) (uint64, error) {
×
UNCOV
486
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
×
UNCOV
487
        if len(data) > 13 {
×
488
                return 0, fmt.Errorf("cannot parse data of length %d as uint64",
×
489
                        len(data))
×
490
        }
×
491

UNCOV
492
        val := uint64(0)
×
UNCOV
493
        for i := 0; i < len(data); i++ {
×
UNCOV
494
                val = val<<5 | uint64(data[i])
×
UNCOV
495
        }
×
UNCOV
496
        return val, nil
×
497
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc