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

lightningnetwork / lnd / 17027244024

17 Aug 2025 11:32PM UTC coverage: 57.287% (-9.5%) from 66.765%
17027244024

Pull #10167

github

web-flow
Merge fcb4f4303 into fb1adfc21
Pull Request #10167: multi: bump Go to 1.24.6

3 of 18 new or added lines in 6 files covered. (16.67%)

28537 existing lines in 457 files now uncovered.

99094 of 172978 relevant lines covered (57.29%)

1.78 hits per line

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

48.71
/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/lightningnetwork/lnd/fn/v2"
19
        "github.com/lightningnetwork/lnd/lnwire"
20
)
21

22
var (
23
        // ErrInvalidUTF8Description is returned if the invoice description is
24
        // not valid UTF-8.
25
        ErrInvalidUTF8Description = errors.New("description is not valid UTF-8")
26

27
        // ErrLengthNotMultipleOfHopHintLength is returned if the length of the
28
        // route hint data is not a multiple of the hop hint length.
29
        ErrLengthNotMultipleOfHopHint = errors.New("length is not a multiple " +
30
                "of hop hint length")
31

32
        // ErrEmptyRouteHint is returned if the route hint field contains no hop
33
        // data.
34
        ErrEmptyRouteHint = errors.New("route hint field contains no hop data")
35
)
36

37
// DecodeOption is a type that can be used to supply functional options to the
38
// Decode function.
39
type DecodeOption func(*decodeOptions)
40

41
// WithKnownFeatureBits is a functional option that overwrites the set of
42
// known feature bits. If not set, then LND's lnwire.Features variable will be
43
// used by default.
UNCOV
44
func WithKnownFeatureBits(features map[lnwire.FeatureBit]string) DecodeOption {
×
UNCOV
45
        return func(options *decodeOptions) {
×
UNCOV
46
                options.knownFeatureBits = features
×
UNCOV
47
        }
×
48
}
49

50
// WithErrorOnUnknownFeatureBit is a functional option that will cause the
51
// Decode function to return an error if the decoded invoice contains an unknown
52
// feature bit.
UNCOV
53
func WithErrorOnUnknownFeatureBit() DecodeOption {
×
UNCOV
54
        return func(options *decodeOptions) {
×
UNCOV
55
                options.errorOnUnknownFeature = true
×
UNCOV
56
        }
×
57
}
58

59
// decodeOptions holds the set of Decode options.
60
type decodeOptions struct {
61
        knownFeatureBits      map[lnwire.FeatureBit]string
62
        errorOnUnknownFeature bool
63
}
64

65
// newDecodeOptions constructs the default decodeOptions struct.
66
func newDecodeOptions() *decodeOptions {
3✔
67
        return &decodeOptions{
3✔
68
                knownFeatureBits:      lnwire.Features,
3✔
69
                errorOnUnknownFeature: false,
3✔
70
        }
3✔
71
}
3✔
72

73
// Decode parses the provided encoded invoice and returns a decoded Invoice if
74
// it is valid by BOLT-0011 and matches the provided active network.
75
func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) (
76
        *Invoice, error) {
3✔
77

3✔
78
        options := newDecodeOptions()
3✔
79
        for _, o := range opts {
3✔
UNCOV
80
                o(options)
×
UNCOV
81
        }
×
82

83
        var decodedInvoice Invoice
3✔
84

3✔
85
        // Before bech32 decoding the invoice, make sure that it is not too large.
3✔
86
        // This is done as an anti-DoS measure since bech32 decoding is expensive.
3✔
87
        if len(invoice) > maxInvoiceLength {
3✔
UNCOV
88
                return nil, ErrInvoiceTooLarge
×
UNCOV
89
        }
×
90

91
        // Decode the invoice using the modified bech32 decoder.
92
        hrp, data, err := decodeBech32(invoice)
3✔
93
        if err != nil {
3✔
UNCOV
94
                return nil, err
×
UNCOV
95
        }
×
96

97
        // We expect the human-readable part to at least have ln + one char
98
        // encoding the network.
99
        if len(hrp) < 3 {
3✔
UNCOV
100
                return nil, fmt.Errorf("hrp too short")
×
UNCOV
101
        }
×
102

103
        // First two characters of HRP should be "ln".
104
        if hrp[:2] != "ln" {
3✔
UNCOV
105
                return nil, fmt.Errorf("prefix should be \"ln\"")
×
UNCOV
106
        }
×
107

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

3✔
125
        // Optionally, if there's anything left of the HRP after ln + the segwit
3✔
126
        // prefix, we try to decode this as the payment amount.
3✔
127
        var netPrefixLength = len(expectedPrefix) + 2
3✔
128
        if len(hrp) > netPrefixLength {
6✔
129
                amount, err := decodeAmount(hrp[netPrefixLength:])
3✔
130
                if err != nil {
3✔
UNCOV
131
                        return nil, err
×
UNCOV
132
                }
×
133
                decodedInvoice.MilliSat = &amount
3✔
134
        }
135

136
        // Everything except the last 520 bits of the data encodes the invoice's
137
        // timestamp and tagged fields.
138
        if len(data) < signatureBase32Len {
3✔
UNCOV
139
                return nil, errors.New("short invoice")
×
UNCOV
140
        }
×
141
        invoiceData := data[:len(data)-signatureBase32Len]
3✔
142

3✔
143
        // Parse the timestamp and tagged fields, and fill the Invoice struct.
3✔
144
        if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
3✔
UNCOV
145
                return nil, err
×
UNCOV
146
        }
×
147

148
        // The last 520 bits (104 groups) make up the signature.
149
        sigBase32 := data[len(data)-signatureBase32Len:]
3✔
150
        sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
3✔
151
        if err != nil {
3✔
152
                return nil, err
×
153
        }
×
154
        sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64])
3✔
155
        if err != nil {
3✔
156
                return nil, err
×
157
        }
×
158
        recoveryID := sigBase256[64]
3✔
159

3✔
160
        // The signature is over the hrp + the data the invoice, encoded in
3✔
161
        // base 256.
3✔
162
        taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
3✔
163
        if err != nil {
3✔
164
                return nil, err
×
165
        }
×
166

167
        toSign := append([]byte(hrp), taggedDataBytes...)
3✔
168

3✔
169
        // We expect the signature to be over the single SHA-256 hash of that
3✔
170
        // data.
3✔
171
        hash := chainhash.HashB(toSign)
3✔
172

3✔
173
        // If the destination pubkey was provided as a tagged field, use that
3✔
174
        // to verify the signature, if not do public key recovery.
3✔
175
        if decodedInvoice.Destination != nil {
3✔
UNCOV
176
                signature, err := sig.ToSignature()
×
UNCOV
177
                if err != nil {
×
UNCOV
178
                        return nil, fmt.Errorf("unable to deserialize "+
×
UNCOV
179
                                "signature: %v", err)
×
UNCOV
180
                }
×
UNCOV
181
                if !signature.Verify(hash, decodedInvoice.Destination) {
×
UNCOV
182
                        return nil, fmt.Errorf("invalid invoice signature")
×
UNCOV
183
                }
×
184
        } else {
3✔
185
                headerByte := recoveryID + 27 + 4
3✔
186
                compactSign := append([]byte{headerByte}, sig.RawBytes()...)
3✔
187
                pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
3✔
188
                if err != nil {
3✔
UNCOV
189
                        return nil, err
×
UNCOV
190
                }
×
191
                decodedInvoice.Destination = pubkey
3✔
192
        }
193

194
        // If no feature vector was decoded, populate an empty one.
195
        if decodedInvoice.Features == nil {
3✔
UNCOV
196
                decodedInvoice.Features = lnwire.NewFeatureVector(
×
UNCOV
197
                        nil, options.knownFeatureBits,
×
UNCOV
198
                )
×
UNCOV
199
        }
×
200

201
        // Now that we have created the invoice, make sure it has the required
202
        // fields set.
203
        if err := validateInvoice(&decodedInvoice); err != nil {
3✔
UNCOV
204
                return nil, err
×
UNCOV
205
        }
×
206

207
        if options.errorOnUnknownFeature {
3✔
UNCOV
208
                // Make sure that we understand all the required feature bits
×
UNCOV
209
                // in the invoice.
×
UNCOV
210
                unknownFeatureBits := decodedInvoice.Features.
×
UNCOV
211
                        UnknownRequiredFeatures()
×
UNCOV
212

×
UNCOV
213
                if len(unknownFeatureBits) > 0 {
×
UNCOV
214
                        errStr := fmt.Sprintf("invoice contains " +
×
UNCOV
215
                                "unknown feature bits:")
×
UNCOV
216

×
UNCOV
217
                        for _, bit := range unknownFeatureBits {
×
UNCOV
218
                                errStr += fmt.Sprintf(" %d,", bit)
×
UNCOV
219
                        }
×
220

UNCOV
221
                        return nil, errors.New(strings.TrimRight(errStr, ","))
×
222
                }
223
        }
224

225
        return &decodedInvoice, nil
3✔
226
}
227

228
// parseData parses the data part of the invoice. It expects base32 data
229
// returned from the bech32.Decode method, except signature.
230
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
3✔
231
        // It must contain the timestamp, encoded using 35 bits (7 groups).
3✔
232
        if len(data) < timestampBase32Len {
3✔
UNCOV
233
                return fmt.Errorf("data too short: %d", len(data))
×
UNCOV
234
        }
×
235

236
        t, err := parseTimestamp(data[:timestampBase32Len])
3✔
237
        if err != nil {
3✔
238
                return err
×
239
        }
×
240
        invoice.Timestamp = time.Unix(int64(t), 0)
3✔
241

3✔
242
        // The rest are tagged parts.
3✔
243
        tagData := data[7:]
3✔
244
        return parseTaggedFields(invoice, tagData, net)
3✔
245
}
246

247
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
248
func parseTimestamp(data []byte) (uint64, error) {
3✔
249
        if len(data) != timestampBase32Len {
3✔
UNCOV
250
                return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
×
UNCOV
251
                        len(data)*5)
×
UNCOV
252
        }
×
253

254
        return base32ToUint64(data)
3✔
255
}
256

257
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
258
// fills the Invoice struct accordingly.
259
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
3✔
260
        index := 0
3✔
261
        for len(fields)-index > 0 {
6✔
262
                // If there are less than 3 groups to read, there cannot be more
3✔
263
                // interesting information, as we need the type (1 group) and
3✔
264
                // length (2 groups).
3✔
265
                //
3✔
266
                // This means the last tagged field is broken.
3✔
267
                if len(fields)-index < 3 {
3✔
UNCOV
268
                        return ErrBrokenTaggedField
×
UNCOV
269
                }
×
270

271
                typ := fields[index]
3✔
272
                dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
3✔
273
                if err != nil {
3✔
274
                        return err
×
275
                }
×
276

277
                // If we don't have enough field data left to read this length,
278
                // return error.
279
                if len(fields) < index+3+int(dataLength) {
3✔
UNCOV
280
                        return ErrInvalidFieldLength
×
UNCOV
281
                }
×
282
                base32Data := fields[index+3 : index+3+int(dataLength)]
3✔
283

3✔
284
                // Advance the index in preparation for the next iteration.
3✔
285
                index += 3 + int(dataLength)
3✔
286

3✔
287
                switch typ {
3✔
288
                case fieldTypeP:
3✔
289
                        if invoice.PaymentHash != nil {
3✔
UNCOV
290
                                // We skip the field if we have already seen a
×
UNCOV
291
                                // supported one.
×
UNCOV
292
                                continue
×
293
                        }
294

295
                        invoice.PaymentHash, err = parse32Bytes(base32Data)
3✔
296

297
                case fieldTypeS:
3✔
298
                        if invoice.PaymentAddr.IsSome() {
3✔
UNCOV
299
                                // We skip the field if we have already seen a
×
UNCOV
300
                                // supported one.
×
UNCOV
301
                                continue
×
302
                        }
303

304
                        addr, err := parse32Bytes(base32Data)
3✔
305
                        if err != nil {
3✔
UNCOV
306
                                return err
×
UNCOV
307
                        }
×
308
                        if addr != nil {
6✔
309
                                invoice.PaymentAddr = fn.Some(*addr)
3✔
310
                        }
3✔
311

312
                case fieldTypeD:
3✔
313
                        if invoice.Description != nil {
3✔
UNCOV
314
                                // We skip the field if we have already seen a
×
UNCOV
315
                                // supported one.
×
UNCOV
316
                                continue
×
317
                        }
318

319
                        invoice.Description, err = parseDescription(base32Data)
3✔
320

UNCOV
321
                case fieldTypeM:
×
UNCOV
322
                        if invoice.Metadata != nil {
×
UNCOV
323
                                // We skip the field if we have already seen a
×
UNCOV
324
                                // supported one.
×
UNCOV
325
                                continue
×
326
                        }
327

UNCOV
328
                        invoice.Metadata, err = parseMetadata(base32Data)
×
329

UNCOV
330
                case fieldTypeN:
×
UNCOV
331
                        if invoice.Destination != nil {
×
UNCOV
332
                                // We skip the field if we have already seen a
×
UNCOV
333
                                // supported one.
×
UNCOV
334
                                continue
×
335
                        }
336

UNCOV
337
                        invoice.Destination, err = parseDestination(base32Data)
×
338

UNCOV
339
                case fieldTypeH:
×
UNCOV
340
                        if invoice.DescriptionHash != nil {
×
UNCOV
341
                                // We skip the field if we have already seen a
×
UNCOV
342
                                // supported one.
×
UNCOV
343
                                continue
×
344
                        }
345

UNCOV
346
                        invoice.DescriptionHash, err = parse32Bytes(base32Data)
×
347

348
                case fieldTypeX:
3✔
349
                        if invoice.expiry != nil {
3✔
UNCOV
350
                                // We skip the field if we have already seen a
×
UNCOV
351
                                // supported one.
×
UNCOV
352
                                continue
×
353
                        }
354

355
                        invoice.expiry, err = parseExpiry(base32Data)
3✔
356

357
                case fieldTypeC:
3✔
358
                        if invoice.minFinalCLTVExpiry != nil {
3✔
UNCOV
359
                                // We skip the field if we have already seen a
×
UNCOV
360
                                // supported one.
×
UNCOV
361
                                continue
×
362
                        }
363

364
                        invoice.minFinalCLTVExpiry, err =
3✔
365
                                parseMinFinalCLTVExpiry(base32Data)
3✔
366

UNCOV
367
                case fieldTypeF:
×
UNCOV
368
                        if invoice.FallbackAddr != nil {
×
UNCOV
369
                                // We skip the field if we have already seen a
×
UNCOV
370
                                // supported one.
×
UNCOV
371
                                continue
×
372
                        }
373

UNCOV
374
                        invoice.FallbackAddr, err = parseFallbackAddr(
×
UNCOV
375
                                base32Data, net,
×
UNCOV
376
                        )
×
377

378
                case fieldTypeR:
3✔
379
                        // An `r` field can be included in an invoice multiple
3✔
380
                        // times, so we won't skip it if we have already seen
3✔
381
                        // one.
3✔
382
                        routeHint, err := parseRouteHint(base32Data)
3✔
383
                        if err != nil {
3✔
UNCOV
384
                                return err
×
UNCOV
385
                        }
×
386

387
                        invoice.RouteHints = append(
3✔
388
                                invoice.RouteHints, routeHint,
3✔
389
                        )
3✔
390

391
                case fieldType9:
3✔
392
                        if invoice.Features != nil {
3✔
UNCOV
393
                                // We skip the field if we have already seen a
×
UNCOV
394
                                // supported one.
×
UNCOV
395
                                continue
×
396
                        }
397

398
                        invoice.Features, err = parseFeatures(base32Data)
3✔
399

400
                case fieldTypeB:
3✔
401
                        blindedPaymentPath, err := parseBlindedPaymentPath(
3✔
402
                                base32Data,
3✔
403
                        )
3✔
404
                        if err != nil {
3✔
UNCOV
405
                                return err
×
UNCOV
406
                        }
×
407

408
                        invoice.BlindedPaymentPaths = append(
3✔
409
                                invoice.BlindedPaymentPaths, blindedPaymentPath,
3✔
410
                        )
3✔
411

UNCOV
412
                default:
×
413
                        // Ignore unknown type.
414
                }
415

416
                // Check if there was an error from parsing any of the tagged
417
                // fields and return it.
418
                if err != nil {
3✔
UNCOV
419
                        return err
×
UNCOV
420
                }
×
421
        }
422

423
        return nil
3✔
424
}
425

426
// parseFieldDataLength converts the two byte slice into a uint16.
427
func parseFieldDataLength(data []byte) (uint16, error) {
3✔
428
        if len(data) != 2 {
3✔
UNCOV
429
                return 0, fmt.Errorf("data length must be 2 bytes, was %d",
×
UNCOV
430
                        len(data))
×
UNCOV
431
        }
×
432

433
        return uint16(data[0])<<5 | uint16(data[1]), nil
3✔
434
}
435

436
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
437
// can be used for payment hashes, description hashes, payment addresses, etc.
438
func parse32Bytes(data []byte) (*[32]byte, error) {
3✔
439
        var paymentHash [32]byte
3✔
440

3✔
441
        // As BOLT-11 states, a reader must skip over the 32-byte fields if
3✔
442
        // it does not have a length of 52, so avoid returning an error.
3✔
443
        if len(data) != hashBase32Len {
3✔
UNCOV
444
                return nil, nil
×
UNCOV
445
        }
×
446

447
        hash, err := bech32.ConvertBits(data, 5, 8, false)
3✔
448
        if err != nil {
3✔
UNCOV
449
                return nil, err
×
UNCOV
450
        }
×
451

452
        copy(paymentHash[:], hash)
3✔
453

3✔
454
        return &paymentHash, nil
3✔
455
}
456

457
// parseDescription converts the data (encoded in base32) into a string to use
458
// as the description.
459
func parseDescription(data []byte) (*string, error) {
3✔
460
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
461
        if err != nil {
3✔
UNCOV
462
                return nil, err
×
UNCOV
463
        }
×
464

465
        if !utf8.Valid(base256Data) {
3✔
UNCOV
466
                return nil, ErrInvalidUTF8Description
×
UNCOV
467
        }
×
468

469
        description := string(base256Data)
3✔
470

3✔
471
        return &description, nil
3✔
472
}
473

474
// parseMetadata converts the data (encoded in base32) into a byte slice to use
475
// as the metadata.
UNCOV
476
func parseMetadata(data []byte) ([]byte, error) {
×
UNCOV
477
        return bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
478
}
×
479

480
// parseDestination converts the data (encoded in base32) into a 33-byte public
481
// key of the payee node.
UNCOV
482
func parseDestination(data []byte) (*btcec.PublicKey, error) {
×
UNCOV
483
        // As BOLT-11 states, a reader must skip over the destination field
×
UNCOV
484
        // if it does not have a length of 53, so avoid returning an error.
×
UNCOV
485
        if len(data) != pubKeyBase32Len {
×
UNCOV
486
                return nil, nil
×
UNCOV
487
        }
×
488

UNCOV
489
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
490
        if err != nil {
×
UNCOV
491
                return nil, err
×
UNCOV
492
        }
×
493

UNCOV
494
        return btcec.ParsePubKey(base256Data)
×
495
}
496

497
// parseExpiry converts the data (encoded in base32) into the expiry time.
498
func parseExpiry(data []byte) (*time.Duration, error) {
3✔
499
        expiry, err := base32ToUint64(data)
3✔
500
        if err != nil {
3✔
UNCOV
501
                return nil, err
×
UNCOV
502
        }
×
503

504
        duration := time.Duration(expiry) * time.Second
3✔
505

3✔
506
        return &duration, nil
3✔
507
}
508

509
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
510
// to use as the minFinalCLTVExpiry.
511
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
3✔
512
        expiry, err := base32ToUint64(data)
3✔
513
        if err != nil {
3✔
UNCOV
514
                return nil, err
×
UNCOV
515
        }
×
516

517
        return &expiry, nil
3✔
518
}
519

520
// parseFallbackAddr converts the data (encoded in base32) into a fallback
521
// on-chain address.
UNCOV
522
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { // nolint:dupl
×
UNCOV
523
        // Checks if the data is empty or contains a version without an address.
×
UNCOV
524
        if len(data) < 2 {
×
UNCOV
525
                return nil, fmt.Errorf("empty fallback address field")
×
UNCOV
526
        }
×
527

UNCOV
528
        var addr btcutil.Address
×
UNCOV
529

×
UNCOV
530
        version := data[0]
×
UNCOV
531
        switch version {
×
UNCOV
532
        case 0:
×
UNCOV
533
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
534
                if err != nil {
×
UNCOV
535
                        return nil, err
×
UNCOV
536
                }
×
537

UNCOV
538
                switch len(witness) {
×
UNCOV
539
                case 20:
×
UNCOV
540
                        addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
×
UNCOV
541
                case 32:
×
UNCOV
542
                        addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
×
UNCOV
543
                default:
×
UNCOV
544
                        return nil, fmt.Errorf("unknown witness program length %d",
×
UNCOV
545
                                len(witness))
×
546
                }
547

UNCOV
548
                if err != nil {
×
549
                        return nil, err
×
550
                }
×
UNCOV
551
        case 17:
×
UNCOV
552
                pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
553
                if err != nil {
×
UNCOV
554
                        return nil, err
×
UNCOV
555
                }
×
556

UNCOV
557
                addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
×
UNCOV
558
                if err != nil {
×
UNCOV
559
                        return nil, err
×
UNCOV
560
                }
×
UNCOV
561
        case 18:
×
UNCOV
562
                scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
563
                if err != nil {
×
UNCOV
564
                        return nil, err
×
UNCOV
565
                }
×
566

UNCOV
567
                addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
×
UNCOV
568
                if err != nil {
×
UNCOV
569
                        return nil, err
×
UNCOV
570
                }
×
UNCOV
571
        default:
×
572
                // Ignore unknown version.
573
        }
574

UNCOV
575
        return addr, nil
×
576
}
577

578
// parseRouteHint converts the data (encoded in base32) into an array containing
579
// one or more routing hop hints that represent a single route hint.
580
func parseRouteHint(data []byte) ([]HopHint, error) {
3✔
581
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
582
        if err != nil {
3✔
UNCOV
583
                return nil, err
×
UNCOV
584
        }
×
585

586
        // Check that base256Data is a multiple of hopHintLen.
587
        if len(base256Data)%hopHintLen != 0 {
3✔
UNCOV
588
                return nil, ErrLengthNotMultipleOfHopHint
×
UNCOV
589
        }
×
590

591
        // Check for empty route hint
592
        if len(base256Data) == 0 {
3✔
UNCOV
593
                return nil, ErrEmptyRouteHint
×
UNCOV
594
        }
×
595

596
        var routeHint []HopHint
3✔
597

3✔
598
        for len(base256Data) > 0 {
6✔
599
                hopHint := HopHint{}
3✔
600
                hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
3✔
601
                if err != nil {
3✔
UNCOV
602
                        return nil, err
×
UNCOV
603
                }
×
604
                hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
3✔
605
                hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
3✔
606
                hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
3✔
607
                hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
3✔
608

3✔
609
                routeHint = append(routeHint, hopHint)
3✔
610

3✔
611
                base256Data = base256Data[51:]
3✔
612
        }
613

614
        return routeHint, nil
3✔
615
}
616

617
// parseBlindedPaymentPath attempts to parse a BlindedPaymentPath from the given
618
// byte slice.
619
func parseBlindedPaymentPath(data []byte) (*BlindedPaymentPath, error) {
3✔
620
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
621
        if err != nil {
3✔
UNCOV
622
                return nil, err
×
UNCOV
623
        }
×
624

625
        return DecodeBlindedPayment(bytes.NewReader(base256Data))
3✔
626
}
627

628
// parseFeatures decodes any feature bits directly from the base32
629
// representation.
630
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
3✔
631
        rawFeatures := lnwire.NewRawFeatureVector()
3✔
632
        err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
3✔
633
        if err != nil {
3✔
634
                return nil, err
×
635
        }
×
636

637
        return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
3✔
638
}
639

640
// base32ToUint64 converts a base32 encoded number to uint64.
641
func base32ToUint64(data []byte) (uint64, error) {
3✔
642
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
3✔
643
        if len(data) > 13 {
3✔
UNCOV
644
                return 0, fmt.Errorf("cannot parse data of length %d as uint64",
×
UNCOV
645
                        len(data))
×
UNCOV
646
        }
×
647

648
        val := uint64(0)
3✔
649
        for i := 0; i < len(data); i++ {
6✔
650
                val = val<<5 | uint64(data[i])
3✔
651
        }
3✔
652
        return val, nil
3✔
653
}
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