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

lightningnetwork / lnd / 17684116017

12 Sep 2025 07:26PM UTC coverage: 66.644% (-0.007%) from 66.651%
17684116017

Pull #9975

github

web-flow
Merge 97f2fbb2b into 5082566ed
Pull Request #9975: Add Support for P2TR Fallback Addresses in BOLT-11

14 of 16 new or added lines in 2 files covered. (87.5%)

76 existing lines in 22 files now uncovered.

136282 of 204494 relevant lines covered (66.64%)

21419.02 hits per line

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

96.33
/zpay32/decode.go
1
package zpay32
2

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

12
        "github.com/btcsuite/btcd/btcec/v2"
13
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
14
        "github.com/btcsuite/btcd/btcutil"
15
        "github.com/btcsuite/btcd/btcutil/bech32"
16
        "github.com/btcsuite/btcd/chaincfg"
17
        "github.com/btcsuite/btcd/chaincfg/chainhash"
18
        "github.com/btcsuite/btcd/txscript"
19
        "github.com/lightningnetwork/lnd/fn/v2"
20
        "github.com/lightningnetwork/lnd/lnwire"
21
)
22

23
const (
24
        fallbackVersionWitness    = txscript.BaseSegwitWitnessVersion
25
        fallbackVersionTaproot    = txscript.TaprootWitnessVersion
26
        fallbackVersionPubkeyHash = 17
27
        fallbackVersionScriptHash = 18
28
)
29

30
var (
31
        // ErrInvalidUTF8Description is returned if the invoice description is
32
        // not valid UTF-8.
33
        ErrInvalidUTF8Description = errors.New("description is not valid UTF-8")
34

35
        // ErrLengthNotMultipleOfHopHintLength is returned if the length of the
36
        // route hint data is not a multiple of the hop hint length.
37
        ErrLengthNotMultipleOfHopHint = errors.New("length is not a multiple " +
38
                "of hop hint length")
39

40
        // ErrEmptyRouteHint is returned if the route hint field contains no hop
41
        // data.
42
        ErrEmptyRouteHint = errors.New("route hint field contains no hop data")
43
)
44

45
// DecodeOption is a type that can be used to supply functional options to the
46
// Decode function.
47
type DecodeOption func(*decodeOptions)
48

49
// WithKnownFeatureBits is a functional option that overwrites the set of
50
// known feature bits. If not set, then LND's lnwire.Features variable will be
51
// used by default.
52
func WithKnownFeatureBits(features map[lnwire.FeatureBit]string) DecodeOption {
2✔
53
        return func(options *decodeOptions) {
4✔
54
                options.knownFeatureBits = features
2✔
55
        }
2✔
56
}
57

58
// WithErrorOnUnknownFeatureBit is a functional option that will cause the
59
// Decode function to return an error if the decoded invoice contains an unknown
60
// feature bit.
61
func WithErrorOnUnknownFeatureBit() DecodeOption {
1✔
62
        return func(options *decodeOptions) {
2✔
63
                options.errorOnUnknownFeature = true
1✔
64
        }
1✔
65
}
66

67
// decodeOptions holds the set of Decode options.
68
type decodeOptions struct {
69
        knownFeatureBits      map[lnwire.FeatureBit]string
70
        errorOnUnknownFeature bool
71
}
72

73
// newDecodeOptions constructs the default decodeOptions struct.
74
func newDecodeOptions() *decodeOptions {
656✔
75
        return &decodeOptions{
656✔
76
                knownFeatureBits:      lnwire.Features,
656✔
77
                errorOnUnknownFeature: false,
656✔
78
        }
656✔
79
}
656✔
80

81
// Decode parses the provided encoded invoice and returns a decoded Invoice if
82
// it is valid by BOLT-0011 and matches the provided active network.
83
func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) (
84
        *Invoice, error) {
656✔
85

656✔
86
        options := newDecodeOptions()
656✔
87
        for _, o := range opts {
659✔
88
                o(options)
3✔
89
        }
3✔
90

91
        var decodedInvoice Invoice
656✔
92

656✔
93
        // Before bech32 decoding the invoice, make sure that it is not too large.
656✔
94
        // This is done as an anti-DoS measure since bech32 decoding is expensive.
656✔
95
        if len(invoice) > maxInvoiceLength {
659✔
96
                return nil, ErrInvoiceTooLarge
3✔
97
        }
3✔
98

99
        // Decode the invoice using the modified bech32 decoder.
100
        hrp, data, err := decodeBech32(invoice)
653✔
101
        if err != nil {
877✔
102
                return nil, err
224✔
103
        }
224✔
104

105
        // We expect the human-readable part to at least have ln + one char
106
        // encoding the network.
107
        if len(hrp) < 3 {
434✔
108
                return nil, fmt.Errorf("hrp too short")
5✔
109
        }
5✔
110

111
        // First two characters of HRP should be "ln".
112
        if hrp[:2] != "ln" {
433✔
113
                return nil, fmt.Errorf("prefix should be \"ln\"")
9✔
114
        }
9✔
115

116
        // The next characters should be a valid prefix for a segwit BIP173
117
        // address that match the active network except for signet where we add
118
        // an additional "s" to differentiate it from the older testnet3 (Core
119
        // devs decided to use the same hrp for signet as for testnet3 which is
120
        // not optimal for LN). See
121
        // https://github.com/lightningnetwork/lightning-rfc/pull/844 for more
122
        // information.
123
        expectedPrefix := net.Bech32HRPSegwit
415✔
124
        if net.Name == chaincfg.SigNetParams.Name {
467✔
125
                expectedPrefix = "tbs"
52✔
126
        }
52✔
127
        if !strings.HasPrefix(hrp[2:], expectedPrefix) {
418✔
128
                return nil, fmt.Errorf(
3✔
129
                        "invoice not for current active network '%s'", net.Name)
3✔
130
        }
3✔
131
        decodedInvoice.Net = net
412✔
132

412✔
133
        // Optionally, if there's anything left of the HRP after ln + the segwit
412✔
134
        // prefix, we try to decode this as the payment amount.
412✔
135
        var netPrefixLength = len(expectedPrefix) + 2
412✔
136
        if len(hrp) > netPrefixLength {
519✔
137
                amount, err := decodeAmount(hrp[netPrefixLength:])
107✔
138
                if err != nil {
122✔
139
                        return nil, err
15✔
140
                }
15✔
141
                decodedInvoice.MilliSat = &amount
92✔
142
        }
143

144
        // Everything except the last 520 bits of the data encodes the invoice's
145
        // timestamp and tagged fields.
146
        if len(data) < signatureBase32Len {
412✔
147
                return nil, errors.New("short invoice")
15✔
148
        }
15✔
149
        invoiceData := data[:len(data)-signatureBase32Len]
382✔
150

382✔
151
        // Parse the timestamp and tagged fields, and fill the Invoice struct.
382✔
152
        if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
518✔
153
                return nil, err
136✔
154
        }
136✔
155

156
        // The last 520 bits (104 groups) make up the signature.
157
        sigBase32 := data[len(data)-signatureBase32Len:]
246✔
158
        sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
246✔
159
        if err != nil {
246✔
160
                return nil, err
×
161
        }
×
162
        sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64])
246✔
163
        if err != nil {
246✔
164
                return nil, err
×
165
        }
×
166
        recoveryID := sigBase256[64]
246✔
167

246✔
168
        // The signature is over the hrp + the data the invoice, encoded in
246✔
169
        // base 256.
246✔
170
        taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
246✔
171
        if err != nil {
246✔
172
                return nil, err
×
173
        }
×
174

175
        toSign := append([]byte(hrp), taggedDataBytes...)
246✔
176

246✔
177
        // We expect the signature to be over the single SHA-256 hash of that
246✔
178
        // data.
246✔
179
        hash := chainhash.HashB(toSign)
246✔
180

246✔
181
        // If the destination pubkey was provided as a tagged field, use that
246✔
182
        // to verify the signature, if not do public key recovery.
246✔
183
        if decodedInvoice.Destination != nil {
273✔
184
                signature, err := sig.ToSignature()
27✔
185
                if err != nil {
31✔
186
                        return nil, fmt.Errorf("unable to deserialize "+
4✔
187
                                "signature: %v", err)
4✔
188
                }
4✔
189
                if !signature.Verify(hash, decodedInvoice.Destination) {
40✔
190
                        return nil, fmt.Errorf("invalid invoice signature")
17✔
191
                }
17✔
192
        } else {
219✔
193
                headerByte := recoveryID + 27 + 4
219✔
194
                compactSign := append([]byte{headerByte}, sig.RawBytes()...)
219✔
195
                pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
219✔
196
                if err != nil {
285✔
197
                        return nil, err
66✔
198
                }
66✔
199
                decodedInvoice.Destination = pubkey
153✔
200
        }
201

202
        // If no feature vector was decoded, populate an empty one.
203
        if decodedInvoice.Features == nil {
278✔
204
                decodedInvoice.Features = lnwire.NewFeatureVector(
119✔
205
                        nil, options.knownFeatureBits,
119✔
206
                )
119✔
207
        }
119✔
208

209
        // Now that we have created the invoice, make sure it has the required
210
        // fields set.
211
        if err := validateInvoice(&decodedInvoice); err != nil {
225✔
212
                return nil, err
66✔
213
        }
66✔
214

215
        if options.errorOnUnknownFeature {
94✔
216
                // Make sure that we understand all the required feature bits
1✔
217
                // in the invoice.
1✔
218
                unknownFeatureBits := decodedInvoice.Features.
1✔
219
                        UnknownRequiredFeatures()
1✔
220

1✔
221
                if len(unknownFeatureBits) > 0 {
2✔
222
                        errStr := fmt.Sprintf("invoice contains " +
1✔
223
                                "unknown feature bits:")
1✔
224

1✔
225
                        for _, bit := range unknownFeatureBits {
2✔
226
                                errStr += fmt.Sprintf(" %d,", bit)
1✔
227
                        }
1✔
228

229
                        return nil, errors.New(strings.TrimRight(errStr, ","))
1✔
230
                }
231
        }
232

233
        return &decodedInvoice, nil
92✔
234
}
235

236
// parseData parses the data part of the invoice. It expects base32 data
237
// returned from the bech32.Decode method, except signature.
238
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
382✔
239
        // It must contain the timestamp, encoded using 35 bits (7 groups).
382✔
240
        if len(data) < timestampBase32Len {
384✔
241
                return fmt.Errorf("data too short: %d", len(data))
2✔
242
        }
2✔
243

244
        t, err := parseTimestamp(data[:timestampBase32Len])
380✔
245
        if err != nil {
380✔
246
                return err
×
247
        }
×
248
        invoice.Timestamp = time.Unix(int64(t), 0)
380✔
249

380✔
250
        // The rest are tagged parts.
380✔
251
        tagData := data[7:]
380✔
252
        return parseTaggedFields(invoice, tagData, net)
380✔
253
}
254

255
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
256
func parseTimestamp(data []byte) (uint64, error) {
383✔
257
        if len(data) != timestampBase32Len {
385✔
258
                return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
2✔
259
                        len(data)*5)
2✔
260
        }
2✔
261

262
        return base32ToUint64(data)
381✔
263
}
264

265
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
266
// fills the Invoice struct accordingly.
267
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
389✔
268
        index := 0
389✔
269
        for len(fields)-index > 0 {
3,051✔
270
                // If there are less than 3 groups to read, there cannot be more
2,662✔
271
                // interesting information, as we need the type (1 group) and
2,662✔
272
                // length (2 groups).
2,662✔
273
                //
2,662✔
274
                // This means the last tagged field is broken.
2,662✔
275
                if len(fields)-index < 3 {
2,666✔
276
                        return ErrBrokenTaggedField
4✔
277
                }
4✔
278

279
                typ := fields[index]
2,658✔
280
                dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
2,658✔
281
                if err != nil {
2,658✔
282
                        return err
×
283
                }
×
284

285
                // If we don't have enough field data left to read this length,
286
                // return error.
287
                if len(fields) < index+3+int(dataLength) {
2,707✔
288
                        return ErrInvalidFieldLength
49✔
289
                }
49✔
290
                base32Data := fields[index+3 : index+3+int(dataLength)]
2,609✔
291

2,609✔
292
                // Advance the index in preparation for the next iteration.
2,609✔
293
                index += 3 + int(dataLength)
2,609✔
294

2,609✔
295
                switch typ {
2,609✔
296
                case fieldTypeP:
744✔
297
                        if invoice.PaymentHash != nil {
1,013✔
298
                                // We skip the field if we have already seen a
269✔
299
                                // supported one.
269✔
300
                                continue
269✔
301
                        }
302

303
                        invoice.PaymentHash, err = parse32Bytes(base32Data)
475✔
304

305
                case fieldTypeS:
53✔
306
                        if invoice.PaymentAddr.IsSome() {
66✔
307
                                // We skip the field if we have already seen a
13✔
308
                                // supported one.
13✔
309
                                continue
13✔
310
                        }
311

312
                        addr, err := parse32Bytes(base32Data)
40✔
313
                        if err != nil {
41✔
314
                                return err
1✔
315
                        }
1✔
316
                        if addr != nil {
60✔
317
                                invoice.PaymentAddr = fn.Some(*addr)
21✔
318
                        }
21✔
319

320
                case fieldTypeD:
504✔
321
                        if invoice.Description != nil {
889✔
322
                                // We skip the field if we have already seen a
385✔
323
                                // supported one.
385✔
324
                                continue
385✔
325
                        }
326

327
                        invoice.Description, err = parseDescription(base32Data)
119✔
328

329
                case fieldTypeM:
19✔
330
                        if invoice.Metadata != nil {
27✔
331
                                // We skip the field if we have already seen a
8✔
332
                                // supported one.
8✔
333
                                continue
8✔
334
                        }
335

336
                        invoice.Metadata, err = parseMetadata(base32Data)
11✔
337

338
                case fieldTypeN:
65✔
339
                        if invoice.Destination != nil {
81✔
340
                                // We skip the field if we have already seen a
16✔
341
                                // supported one.
16✔
342
                                continue
16✔
343
                        }
344

345
                        invoice.Destination, err = parseDestination(base32Data)
49✔
346

347
                case fieldTypeH:
63✔
348
                        if invoice.DescriptionHash != nil {
71✔
349
                                // We skip the field if we have already seen a
8✔
350
                                // supported one.
8✔
351
                                continue
8✔
352
                        }
353

354
                        invoice.DescriptionHash, err = parse32Bytes(base32Data)
55✔
355

356
                case fieldTypeX:
94✔
357
                        if invoice.expiry != nil {
163✔
358
                                // We skip the field if we have already seen a
69✔
359
                                // supported one.
69✔
360
                                continue
69✔
361
                        }
362

363
                        invoice.expiry, err = parseExpiry(base32Data)
25✔
364

365
                case fieldTypeC:
286✔
366
                        if invoice.minFinalCLTVExpiry != nil {
548✔
367
                                // We skip the field if we have already seen a
262✔
368
                                // supported one.
262✔
369
                                continue
262✔
370
                        }
371

372
                        invoice.minFinalCLTVExpiry, err =
24✔
373
                                parseMinFinalCLTVExpiry(base32Data)
24✔
374

375
                case fieldTypeF:
102✔
376
                        if invoice.FallbackAddr != nil {
138✔
377
                                // We skip the field if we have already seen a
36✔
378
                                // supported one.
36✔
379
                                continue
36✔
380
                        }
381

382
                        invoice.FallbackAddr, err = parseFallbackAddr(
66✔
383
                                base32Data, net,
66✔
384
                        )
66✔
385

386
                case fieldTypeR:
57✔
387
                        // An `r` field can be included in an invoice multiple
57✔
388
                        // times, so we won't skip it if we have already seen
57✔
389
                        // one.
57✔
390
                        routeHint, err := parseRouteHint(base32Data)
57✔
391
                        if err != nil {
77✔
392
                                return err
20✔
393
                        }
20✔
394

395
                        invoice.RouteHints = append(
37✔
396
                                invoice.RouteHints, routeHint,
37✔
397
                        )
37✔
398

399
                case fieldType9:
133✔
400
                        if invoice.Features != nil {
211✔
401
                                // We skip the field if we have already seen a
78✔
402
                                // supported one.
78✔
403
                                continue
78✔
404
                        }
405

406
                        invoice.Features, err = parseFeatures(base32Data)
55✔
407

408
                case fieldTypeB:
19✔
409
                        blindedPaymentPath, err := parseBlindedPaymentPath(
19✔
410
                                base32Data,
19✔
411
                        )
19✔
412
                        if err != nil {
33✔
413
                                return err
14✔
414
                        }
14✔
415

416
                        invoice.BlindedPaymentPaths = append(
5✔
417
                                invoice.BlindedPaymentPaths, blindedPaymentPath,
5✔
418
                        )
5✔
419

420
                default:
491✔
421
                        // Ignore unknown type.
422
                }
423

424
                // Check if there was an error from parsing any of the tagged
425
                // fields and return it.
426
                if err != nil {
1,481✔
427
                        return err
51✔
428
                }
51✔
429
        }
430

431
        return nil
250✔
432
}
433

434
// parseFieldDataLength converts the two byte slice into a uint16.
435
func parseFieldDataLength(data []byte) (uint16, error) {
2,665✔
436
        if len(data) != 2 {
2,668✔
437
                return 0, fmt.Errorf("data length must be 2 bytes, was %d",
3✔
438
                        len(data))
3✔
439
        }
3✔
440

441
        return uint16(data[0])<<5 | uint16(data[1]), nil
2,662✔
442
}
443

444
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
445
// can be used for payment hashes, description hashes, payment addresses, etc.
446
func parse32Bytes(data []byte) (*[32]byte, error) {
571✔
447
        var paymentHash [32]byte
571✔
448

571✔
449
        // As BOLT-11 states, a reader must skip over the 32-byte fields if
571✔
450
        // it does not have a length of 52, so avoid returning an error.
571✔
451
        if len(data) != hashBase32Len {
942✔
452
                return nil, nil
371✔
453
        }
371✔
454

455
        hash, err := bech32.ConvertBits(data, 5, 8, false)
200✔
456
        if err != nil {
202✔
457
                return nil, err
2✔
458
        }
2✔
459

460
        copy(paymentHash[:], hash)
198✔
461

198✔
462
        return &paymentHash, nil
198✔
463
}
464

465
// parseDescription converts the data (encoded in base32) into a string to use
466
// as the description.
467
func parseDescription(data []byte) (*string, error) {
123✔
468
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
123✔
469
        if err != nil {
125✔
470
                return nil, err
2✔
471
        }
2✔
472

473
        if !utf8.Valid(base256Data) {
158✔
474
                return nil, ErrInvalidUTF8Description
37✔
475
        }
37✔
476

477
        description := string(base256Data)
84✔
478

84✔
479
        return &description, nil
84✔
480
}
481

482
// parseMetadata converts the data (encoded in base32) into a byte slice to use
483
// as the metadata.
484
func parseMetadata(data []byte) ([]byte, error) {
11✔
485
        return bech32.ConvertBits(data, 5, 8, false)
11✔
486
}
11✔
487

488
// parseDestination converts the data (encoded in base32) into a 33-byte public
489
// key of the payee node.
490
func parseDestination(data []byte) (*btcec.PublicKey, error) {
53✔
491
        // As BOLT-11 states, a reader must skip over the destination field
53✔
492
        // if it does not have a length of 53, so avoid returning an error.
53✔
493
        if len(data) != pubKeyBase32Len {
72✔
494
                return nil, nil
19✔
495
        }
19✔
496

497
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
34✔
498
        if err != nil {
35✔
499
                return nil, err
1✔
500
        }
1✔
501

502
        return btcec.ParsePubKey(base256Data)
33✔
503
}
504

505
// parseExpiry converts the data (encoded in base32) into the expiry time.
506
func parseExpiry(data []byte) (*time.Duration, error) {
28✔
507
        expiry, err := base32ToUint64(data)
28✔
508
        if err != nil {
30✔
509
                return nil, err
2✔
510
        }
2✔
511

512
        duration := time.Duration(expiry) * time.Second
26✔
513

26✔
514
        return &duration, nil
26✔
515
}
516

517
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
518
// to use as the minFinalCLTVExpiry.
519
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
28✔
520
        expiry, err := base32ToUint64(data)
28✔
521
        if err != nil {
30✔
522
                return nil, err
2✔
523
        }
2✔
524

525
        return &expiry, nil
26✔
526
}
527

528
// parseFallbackAddr converts the data (encoded in base32) into a fallback
529
// on-chain address.
530
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { // nolint:dupl
75✔
531
        // Checks if the data is empty or contains a version without an address.
75✔
532
        if len(data) < 2 {
79✔
533
                return nil, fmt.Errorf("empty fallback address field")
4✔
534
        }
4✔
535

536
        var addr btcutil.Address
71✔
537

71✔
538
        version := data[0]
71✔
539
        switch version {
71✔
540
        case fallbackVersionWitness:
9✔
541
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
9✔
542
                if err != nil {
10✔
543
                        return nil, err
1✔
544
                }
1✔
545

546
                switch len(witness) {
8✔
547
                case 20:
4✔
548
                        addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
4✔
549
                case 32:
3✔
550
                        addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
3✔
551
                default:
1✔
552
                        return nil, fmt.Errorf("unknown witness program length %d",
1✔
553
                                len(witness))
1✔
554
                }
555

556
                if err != nil {
7✔
557
                        return nil, err
×
558
                }
×
559
        case fallbackVersionTaproot:
4✔
560
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
4✔
561
                if err != nil {
5✔
562
                        return nil, err
1✔
563
                }
1✔
564
                addr, err = btcutil.NewAddressTaproot(witness, net)
3✔
565
                if err != nil {
3✔
NEW
566
                        return nil, err
×
NEW
567
                }
×
568
        case fallbackVersionPubkeyHash:
16✔
569
                pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
16✔
570
                if err != nil {
17✔
571
                        return nil, err
1✔
572
                }
1✔
573

574
                addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
15✔
575
                if err != nil {
16✔
576
                        return nil, err
1✔
577
                }
1✔
578
        case fallbackVersionScriptHash:
6✔
579
                scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
6✔
580
                if err != nil {
7✔
581
                        return nil, err
1✔
582
                }
1✔
583

584
                addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
5✔
585
                if err != nil {
6✔
586
                        return nil, err
1✔
587
                }
1✔
588
        default:
36✔
589
                // Ignore unknown version.
590
        }
591

592
        return addr, nil
64✔
593
}
594

595
// parseRouteHint converts the data (encoded in base32) into an array containing
596
// one or more routing hop hints that represent a single route hint.
597
func parseRouteHint(data []byte) ([]HopHint, error) {
62✔
598
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
62✔
599
        if err != nil {
66✔
600
                return nil, err
4✔
601
        }
4✔
602

603
        // Check that base256Data is a multiple of hopHintLen.
604
        if len(base256Data)%hopHintLen != 0 {
62✔
605
                return nil, ErrLengthNotMultipleOfHopHint
4✔
606
        }
4✔
607

608
        // Check for empty route hint
609
        if len(base256Data) == 0 {
68✔
610
                return nil, ErrEmptyRouteHint
14✔
611
        }
14✔
612

613
        var routeHint []HopHint
40✔
614

40✔
615
        for len(base256Data) > 0 {
89✔
616
                hopHint := HopHint{}
49✔
617
                hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
49✔
618
                if err != nil {
50✔
619
                        return nil, err
1✔
620
                }
1✔
621
                hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
48✔
622
                hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
48✔
623
                hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
48✔
624
                hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
48✔
625

48✔
626
                routeHint = append(routeHint, hopHint)
48✔
627

48✔
628
                base256Data = base256Data[51:]
48✔
629
        }
630

631
        return routeHint, nil
39✔
632
}
633

634
// parseBlindedPaymentPath attempts to parse a BlindedPaymentPath from the given
635
// byte slice.
636
func parseBlindedPaymentPath(data []byte) (*BlindedPaymentPath, error) {
19✔
637
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
19✔
638
        if err != nil {
20✔
639
                return nil, err
1✔
640
        }
1✔
641

642
        return DecodeBlindedPayment(bytes.NewReader(base256Data))
18✔
643
}
644

645
// parseFeatures decodes any feature bits directly from the base32
646
// representation.
647
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
55✔
648
        rawFeatures := lnwire.NewRawFeatureVector()
55✔
649
        err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
55✔
650
        if err != nil {
55✔
651
                return nil, err
×
652
        }
×
653

654
        return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
55✔
655
}
656

657
// base32ToUint64 converts a base32 encoded number to uint64.
658
func base32ToUint64(data []byte) (uint64, error) {
432✔
659
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
432✔
660
        if len(data) > 13 {
436✔
661
                return 0, fmt.Errorf("cannot parse data of length %d as uint64",
4✔
662
                        len(data))
4✔
663
        }
4✔
664

665
        val := uint64(0)
428✔
666
        for i := 0; i < len(data); i++ {
3,196✔
667
                val = val<<5 | uint64(data[i])
2,768✔
668
        }
2,768✔
669
        return val, nil
428✔
670
}
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