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

lightningnetwork / lnd / 15867504649

25 Jun 2025 04:40AM UTC coverage: 58.087% (-9.9%) from 67.978%
15867504649

Pull #9993

github

web-flow
Merge 402f364cb into 4335d9cfb
Pull Request #9993: Validate UTF-8 description and empty route hints when parsing BOLT-11 invoices

2 of 6 new or added lines in 1 file covered. (33.33%)

28397 existing lines in 456 files now uncovered.

98322 of 169268 relevant lines covered (58.09%)

1.8 hits per line

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

48.6
/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
// DecodeOption is a type that can be used to supply functional options to the
23
// Decode function.
24
type DecodeOption func(*decodeOptions)
25

26
// WithKnownFeatureBits is a functional option that overwrites the set of
27
// known feature bits. If not set, then LND's lnwire.Features variable will be
28
// used by default.
UNCOV
29
func WithKnownFeatureBits(features map[lnwire.FeatureBit]string) DecodeOption {
×
UNCOV
30
        return func(options *decodeOptions) {
×
UNCOV
31
                options.knownFeatureBits = features
×
UNCOV
32
        }
×
33
}
34

35
// WithErrorOnUnknownFeatureBit is a functional option that will cause the
36
// Decode function to return an error if the decoded invoice contains an unknown
37
// feature bit.
UNCOV
38
func WithErrorOnUnknownFeatureBit() DecodeOption {
×
UNCOV
39
        return func(options *decodeOptions) {
×
UNCOV
40
                options.errorOnUnknownFeature = true
×
UNCOV
41
        }
×
42
}
43

44
// decodeOptions holds the set of Decode options.
45
type decodeOptions struct {
46
        knownFeatureBits      map[lnwire.FeatureBit]string
47
        errorOnUnknownFeature bool
48
}
49

50
// newDecodeOptions constructs the default decodeOptions struct.
51
func newDecodeOptions() *decodeOptions {
3✔
52
        return &decodeOptions{
3✔
53
                knownFeatureBits:      lnwire.Features,
3✔
54
                errorOnUnknownFeature: false,
3✔
55
        }
3✔
56
}
3✔
57

58
// Decode parses the provided encoded invoice and returns a decoded Invoice if
59
// it is valid by BOLT-0011 and matches the provided active network.
60
func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) (
61
        *Invoice, error) {
3✔
62

3✔
63
        options := newDecodeOptions()
3✔
64
        for _, o := range opts {
3✔
UNCOV
65
                o(options)
×
UNCOV
66
        }
×
67

68
        var decodedInvoice Invoice
3✔
69

3✔
70
        // Before bech32 decoding the invoice, make sure that it is not too large.
3✔
71
        // This is done as an anti-DoS measure since bech32 decoding is expensive.
3✔
72
        if len(invoice) > maxInvoiceLength {
3✔
UNCOV
73
                return nil, ErrInvoiceTooLarge
×
UNCOV
74
        }
×
75

76
        // Decode the invoice using the modified bech32 decoder.
77
        hrp, data, err := decodeBech32(invoice)
3✔
78
        if err != nil {
3✔
UNCOV
79
                return nil, err
×
UNCOV
80
        }
×
81

82
        // We expect the human-readable part to at least have ln + one char
83
        // encoding the network.
84
        if len(hrp) < 3 {
3✔
UNCOV
85
                return nil, fmt.Errorf("hrp too short")
×
UNCOV
86
        }
×
87

88
        // First two characters of HRP should be "ln".
89
        if hrp[:2] != "ln" {
3✔
UNCOV
90
                return nil, fmt.Errorf("prefix should be \"ln\"")
×
UNCOV
91
        }
×
92

93
        // The next characters should be a valid prefix for a segwit BIP173
94
        // address that match the active network except for signet where we add
95
        // an additional "s" to differentiate it from the older testnet3 (Core
96
        // devs decided to use the same hrp for signet as for testnet3 which is
97
        // not optimal for LN). See
98
        // https://github.com/lightningnetwork/lightning-rfc/pull/844 for more
99
        // information.
100
        expectedPrefix := net.Bech32HRPSegwit
3✔
101
        if net.Name == chaincfg.SigNetParams.Name {
3✔
UNCOV
102
                expectedPrefix = "tbs"
×
UNCOV
103
        }
×
104
        if !strings.HasPrefix(hrp[2:], expectedPrefix) {
3✔
UNCOV
105
                return nil, fmt.Errorf(
×
UNCOV
106
                        "invoice not for current active network '%s'", net.Name)
×
UNCOV
107
        }
×
108
        decodedInvoice.Net = net
3✔
109

3✔
110
        // Optionally, if there's anything left of the HRP after ln + the segwit
3✔
111
        // prefix, we try to decode this as the payment amount.
3✔
112
        var netPrefixLength = len(expectedPrefix) + 2
3✔
113
        if len(hrp) > netPrefixLength {
6✔
114
                amount, err := decodeAmount(hrp[netPrefixLength:])
3✔
115
                if err != nil {
3✔
UNCOV
116
                        return nil, err
×
UNCOV
117
                }
×
118
                decodedInvoice.MilliSat = &amount
3✔
119
        }
120

121
        // Everything except the last 520 bits of the data encodes the invoice's
122
        // timestamp and tagged fields.
123
        if len(data) < signatureBase32Len {
3✔
UNCOV
124
                return nil, errors.New("short invoice")
×
UNCOV
125
        }
×
126
        invoiceData := data[:len(data)-signatureBase32Len]
3✔
127

3✔
128
        // Parse the timestamp and tagged fields, and fill the Invoice struct.
3✔
129
        if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
3✔
UNCOV
130
                return nil, err
×
UNCOV
131
        }
×
132

133
        // The last 520 bits (104 groups) make up the signature.
134
        sigBase32 := data[len(data)-signatureBase32Len:]
3✔
135
        sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
3✔
136
        if err != nil {
3✔
137
                return nil, err
×
138
        }
×
139
        sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64])
3✔
140
        if err != nil {
3✔
141
                return nil, err
×
142
        }
×
143
        recoveryID := sigBase256[64]
3✔
144

3✔
145
        // The signature is over the hrp + the data the invoice, encoded in
3✔
146
        // base 256.
3✔
147
        taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
3✔
148
        if err != nil {
3✔
149
                return nil, err
×
150
        }
×
151

152
        toSign := append([]byte(hrp), taggedDataBytes...)
3✔
153

3✔
154
        // We expect the signature to be over the single SHA-256 hash of that
3✔
155
        // data.
3✔
156
        hash := chainhash.HashB(toSign)
3✔
157

3✔
158
        // If the destination pubkey was provided as a tagged field, use that
3✔
159
        // to verify the signature, if not do public key recovery.
3✔
160
        if decodedInvoice.Destination != nil {
3✔
UNCOV
161
                signature, err := sig.ToSignature()
×
UNCOV
162
                if err != nil {
×
UNCOV
163
                        return nil, fmt.Errorf("unable to deserialize "+
×
UNCOV
164
                                "signature: %v", err)
×
UNCOV
165
                }
×
UNCOV
166
                if !signature.Verify(hash, decodedInvoice.Destination) {
×
UNCOV
167
                        return nil, fmt.Errorf("invalid invoice signature")
×
UNCOV
168
                }
×
169
        } else {
3✔
170
                headerByte := recoveryID + 27 + 4
3✔
171
                compactSign := append([]byte{headerByte}, sig.RawBytes()...)
3✔
172
                pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
3✔
173
                if err != nil {
3✔
UNCOV
174
                        return nil, err
×
UNCOV
175
                }
×
176
                decodedInvoice.Destination = pubkey
3✔
177
        }
178

179
        // If no feature vector was decoded, populate an empty one.
180
        if decodedInvoice.Features == nil {
3✔
UNCOV
181
                decodedInvoice.Features = lnwire.NewFeatureVector(
×
UNCOV
182
                        nil, options.knownFeatureBits,
×
UNCOV
183
                )
×
UNCOV
184
        }
×
185

186
        // Now that we have created the invoice, make sure it has the required
187
        // fields set.
188
        if err := validateInvoice(&decodedInvoice); err != nil {
3✔
UNCOV
189
                return nil, err
×
UNCOV
190
        }
×
191

192
        if options.errorOnUnknownFeature {
3✔
UNCOV
193
                // Make sure that we understand all the required feature bits
×
UNCOV
194
                // in the invoice.
×
UNCOV
195
                unknownFeatureBits := decodedInvoice.Features.
×
UNCOV
196
                        UnknownRequiredFeatures()
×
UNCOV
197

×
UNCOV
198
                if len(unknownFeatureBits) > 0 {
×
UNCOV
199
                        errStr := fmt.Sprintf("invoice contains " +
×
UNCOV
200
                                "unknown feature bits:")
×
UNCOV
201

×
UNCOV
202
                        for _, bit := range unknownFeatureBits {
×
UNCOV
203
                                errStr += fmt.Sprintf(" %d,", bit)
×
UNCOV
204
                        }
×
205

UNCOV
206
                        return nil, errors.New(strings.TrimRight(errStr, ","))
×
207
                }
208
        }
209

210
        return &decodedInvoice, nil
3✔
211
}
212

213
// parseData parses the data part of the invoice. It expects base32 data
214
// returned from the bech32.Decode method, except signature.
215
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
3✔
216
        // It must contain the timestamp, encoded using 35 bits (7 groups).
3✔
217
        if len(data) < timestampBase32Len {
3✔
UNCOV
218
                return fmt.Errorf("data too short: %d", len(data))
×
UNCOV
219
        }
×
220

221
        t, err := parseTimestamp(data[:timestampBase32Len])
3✔
222
        if err != nil {
3✔
223
                return err
×
224
        }
×
225
        invoice.Timestamp = time.Unix(int64(t), 0)
3✔
226

3✔
227
        // The rest are tagged parts.
3✔
228
        tagData := data[7:]
3✔
229
        return parseTaggedFields(invoice, tagData, net)
3✔
230
}
231

232
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
233
func parseTimestamp(data []byte) (uint64, error) {
3✔
234
        if len(data) != timestampBase32Len {
3✔
UNCOV
235
                return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
×
UNCOV
236
                        len(data)*5)
×
UNCOV
237
        }
×
238

239
        return base32ToUint64(data)
3✔
240
}
241

242
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
243
// fills the Invoice struct accordingly.
244
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
3✔
245
        index := 0
3✔
246
        for len(fields)-index > 0 {
6✔
247
                // If there are less than 3 groups to read, there cannot be more
3✔
248
                // interesting information, as we need the type (1 group) and
3✔
249
                // length (2 groups).
3✔
250
                //
3✔
251
                // This means the last tagged field is broken.
3✔
252
                if len(fields)-index < 3 {
3✔
UNCOV
253
                        return ErrBrokenTaggedField
×
UNCOV
254
                }
×
255

256
                typ := fields[index]
3✔
257
                dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
3✔
258
                if err != nil {
3✔
259
                        return err
×
260
                }
×
261

262
                // If we don't have enough field data left to read this length,
263
                // return error.
264
                if len(fields) < index+3+int(dataLength) {
3✔
UNCOV
265
                        return ErrInvalidFieldLength
×
UNCOV
266
                }
×
267
                base32Data := fields[index+3 : index+3+int(dataLength)]
3✔
268

3✔
269
                // Advance the index in preparation for the next iteration.
3✔
270
                index += 3 + int(dataLength)
3✔
271

3✔
272
                switch typ {
3✔
273
                case fieldTypeP:
3✔
274
                        if invoice.PaymentHash != nil {
3✔
UNCOV
275
                                // We skip the field if we have already seen a
×
UNCOV
276
                                // supported one.
×
UNCOV
277
                                continue
×
278
                        }
279

280
                        invoice.PaymentHash, err = parse32Bytes(base32Data)
3✔
281

282
                case fieldTypeS:
3✔
283
                        if invoice.PaymentAddr.IsSome() {
3✔
UNCOV
284
                                // We skip the field if we have already seen a
×
UNCOV
285
                                // supported one.
×
UNCOV
286
                                continue
×
287
                        }
288

289
                        addr, err := parse32Bytes(base32Data)
3✔
290
                        if err != nil {
3✔
UNCOV
291
                                return err
×
UNCOV
292
                        }
×
293
                        if addr != nil {
6✔
294
                                invoice.PaymentAddr = fn.Some(*addr)
3✔
295
                        }
3✔
296

297
                case fieldTypeD:
3✔
298
                        if invoice.Description != nil {
3✔
UNCOV
299
                                // We skip the field if we have already seen a
×
UNCOV
300
                                // supported one.
×
UNCOV
301
                                continue
×
302
                        }
303

304
                        invoice.Description, err = parseDescription(base32Data)
3✔
305

UNCOV
306
                case fieldTypeM:
×
UNCOV
307
                        if invoice.Metadata != nil {
×
UNCOV
308
                                // We skip the field if we have already seen a
×
UNCOV
309
                                // supported one.
×
UNCOV
310
                                continue
×
311
                        }
312

UNCOV
313
                        invoice.Metadata, err = parseMetadata(base32Data)
×
314

UNCOV
315
                case fieldTypeN:
×
UNCOV
316
                        if invoice.Destination != nil {
×
UNCOV
317
                                // We skip the field if we have already seen a
×
UNCOV
318
                                // supported one.
×
UNCOV
319
                                continue
×
320
                        }
321

UNCOV
322
                        invoice.Destination, err = parseDestination(base32Data)
×
323

UNCOV
324
                case fieldTypeH:
×
UNCOV
325
                        if invoice.DescriptionHash != nil {
×
UNCOV
326
                                // We skip the field if we have already seen a
×
UNCOV
327
                                // supported one.
×
UNCOV
328
                                continue
×
329
                        }
330

UNCOV
331
                        invoice.DescriptionHash, err = parse32Bytes(base32Data)
×
332

333
                case fieldTypeX:
3✔
334
                        if invoice.expiry != nil {
3✔
UNCOV
335
                                // We skip the field if we have already seen a
×
UNCOV
336
                                // supported one.
×
UNCOV
337
                                continue
×
338
                        }
339

340
                        invoice.expiry, err = parseExpiry(base32Data)
3✔
341

342
                case fieldTypeC:
3✔
343
                        if invoice.minFinalCLTVExpiry != nil {
3✔
UNCOV
344
                                // We skip the field if we have already seen a
×
UNCOV
345
                                // supported one.
×
UNCOV
346
                                continue
×
347
                        }
348

349
                        invoice.minFinalCLTVExpiry, err =
3✔
350
                                parseMinFinalCLTVExpiry(base32Data)
3✔
351

UNCOV
352
                case fieldTypeF:
×
UNCOV
353
                        if invoice.FallbackAddr != nil {
×
UNCOV
354
                                // We skip the field if we have already seen a
×
UNCOV
355
                                // supported one.
×
UNCOV
356
                                continue
×
357
                        }
358

UNCOV
359
                        invoice.FallbackAddr, err = parseFallbackAddr(
×
UNCOV
360
                                base32Data, net,
×
UNCOV
361
                        )
×
362

363
                case fieldTypeR:
3✔
364
                        // An `r` field can be included in an invoice multiple
3✔
365
                        // times, so we won't skip it if we have already seen
3✔
366
                        // one.
3✔
367
                        routeHint, err := parseRouteHint(base32Data)
3✔
368
                        if err != nil {
3✔
UNCOV
369
                                return err
×
UNCOV
370
                        }
×
371

372
                        invoice.RouteHints = append(
3✔
373
                                invoice.RouteHints, routeHint,
3✔
374
                        )
3✔
375

376
                case fieldType9:
3✔
377
                        if invoice.Features != nil {
3✔
UNCOV
378
                                // We skip the field if we have already seen a
×
UNCOV
379
                                // supported one.
×
UNCOV
380
                                continue
×
381
                        }
382

383
                        invoice.Features, err = parseFeatures(base32Data)
3✔
384

385
                case fieldTypeB:
3✔
386
                        blindedPaymentPath, err := parseBlindedPaymentPath(
3✔
387
                                base32Data,
3✔
388
                        )
3✔
389
                        if err != nil {
3✔
UNCOV
390
                                return err
×
UNCOV
391
                        }
×
392

393
                        invoice.BlindedPaymentPaths = append(
3✔
394
                                invoice.BlindedPaymentPaths, blindedPaymentPath,
3✔
395
                        )
3✔
396

UNCOV
397
                default:
×
398
                        // Ignore unknown type.
399
                }
400

401
                // Check if there was an error from parsing any of the tagged
402
                // fields and return it.
403
                if err != nil {
3✔
UNCOV
404
                        return err
×
UNCOV
405
                }
×
406
        }
407

408
        return nil
3✔
409
}
410

411
// parseFieldDataLength converts the two byte slice into a uint16.
412
func parseFieldDataLength(data []byte) (uint16, error) {
3✔
413
        if len(data) != 2 {
3✔
UNCOV
414
                return 0, fmt.Errorf("data length must be 2 bytes, was %d",
×
UNCOV
415
                        len(data))
×
UNCOV
416
        }
×
417

418
        return uint16(data[0])<<5 | uint16(data[1]), nil
3✔
419
}
420

421
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
422
// can be used for payment hashes, description hashes, payment addresses, etc.
423
func parse32Bytes(data []byte) (*[32]byte, error) {
3✔
424
        var paymentHash [32]byte
3✔
425

3✔
426
        // As BOLT-11 states, a reader must skip over the 32-byte fields if
3✔
427
        // it does not have a length of 52, so avoid returning an error.
3✔
428
        if len(data) != hashBase32Len {
3✔
UNCOV
429
                return nil, nil
×
UNCOV
430
        }
×
431

432
        hash, err := bech32.ConvertBits(data, 5, 8, false)
3✔
433
        if err != nil {
3✔
UNCOV
434
                return nil, err
×
UNCOV
435
        }
×
436

437
        copy(paymentHash[:], hash)
3✔
438

3✔
439
        return &paymentHash, nil
3✔
440
}
441

442
// parseDescription converts the data (encoded in base32) into a string to use
443
// as the description.
444
func parseDescription(data []byte) (*string, error) {
3✔
445
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
446
        if err != nil {
3✔
UNCOV
447
                return nil, err
×
UNCOV
448
        }
×
449

450
        if !utf8.Valid(base256Data) {
3✔
NEW
451
                return nil, fmt.Errorf("description is not valid UTF-8")
×
NEW
452
        }
×
453

454
        description := string(base256Data)
3✔
455

3✔
456
        return &description, nil
3✔
457
}
458

459
// parseMetadata converts the data (encoded in base32) into a byte slice to use
460
// as the metadata.
UNCOV
461
func parseMetadata(data []byte) ([]byte, error) {
×
UNCOV
462
        return bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
463
}
×
464

465
// parseDestination converts the data (encoded in base32) into a 33-byte public
466
// key of the payee node.
UNCOV
467
func parseDestination(data []byte) (*btcec.PublicKey, error) {
×
UNCOV
468
        // As BOLT-11 states, a reader must skip over the destination field
×
UNCOV
469
        // if it does not have a length of 53, so avoid returning an error.
×
UNCOV
470
        if len(data) != pubKeyBase32Len {
×
UNCOV
471
                return nil, nil
×
UNCOV
472
        }
×
473

UNCOV
474
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
475
        if err != nil {
×
UNCOV
476
                return nil, err
×
UNCOV
477
        }
×
478

UNCOV
479
        return btcec.ParsePubKey(base256Data)
×
480
}
481

482
// parseExpiry converts the data (encoded in base32) into the expiry time.
483
func parseExpiry(data []byte) (*time.Duration, error) {
3✔
484
        expiry, err := base32ToUint64(data)
3✔
485
        if err != nil {
3✔
UNCOV
486
                return nil, err
×
UNCOV
487
        }
×
488

489
        duration := time.Duration(expiry) * time.Second
3✔
490

3✔
491
        return &duration, nil
3✔
492
}
493

494
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
495
// to use as the minFinalCLTVExpiry.
496
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
3✔
497
        expiry, err := base32ToUint64(data)
3✔
498
        if err != nil {
3✔
UNCOV
499
                return nil, err
×
UNCOV
500
        }
×
501

502
        return &expiry, nil
3✔
503
}
504

505
// parseFallbackAddr converts the data (encoded in base32) into a fallback
506
// on-chain address.
UNCOV
507
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { // nolint:dupl
×
UNCOV
508
        // Checks if the data is empty or contains a version without an address.
×
UNCOV
509
        if len(data) < 2 {
×
UNCOV
510
                return nil, fmt.Errorf("empty fallback address field")
×
UNCOV
511
        }
×
512

UNCOV
513
        var addr btcutil.Address
×
UNCOV
514

×
UNCOV
515
        version := data[0]
×
UNCOV
516
        switch version {
×
UNCOV
517
        case 0:
×
UNCOV
518
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
519
                if err != nil {
×
UNCOV
520
                        return nil, err
×
UNCOV
521
                }
×
522

UNCOV
523
                switch len(witness) {
×
UNCOV
524
                case 20:
×
UNCOV
525
                        addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
×
UNCOV
526
                case 32:
×
UNCOV
527
                        addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
×
UNCOV
528
                default:
×
UNCOV
529
                        return nil, fmt.Errorf("unknown witness program length %d",
×
UNCOV
530
                                len(witness))
×
531
                }
532

UNCOV
533
                if err != nil {
×
534
                        return nil, err
×
535
                }
×
UNCOV
536
        case 17:
×
UNCOV
537
                pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
538
                if err != nil {
×
UNCOV
539
                        return nil, err
×
UNCOV
540
                }
×
541

UNCOV
542
                addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
×
UNCOV
543
                if err != nil {
×
UNCOV
544
                        return nil, err
×
UNCOV
545
                }
×
UNCOV
546
        case 18:
×
UNCOV
547
                scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
548
                if err != nil {
×
UNCOV
549
                        return nil, err
×
UNCOV
550
                }
×
551

UNCOV
552
                addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
×
UNCOV
553
                if err != nil {
×
UNCOV
554
                        return nil, err
×
UNCOV
555
                }
×
UNCOV
556
        default:
×
557
                // Ignore unknown version.
558
        }
559

UNCOV
560
        return addr, nil
×
561
}
562

563
// parseRouteHint converts the data (encoded in base32) into an array containing
564
// one or more routing hop hints that represent a single route hint.
565
func parseRouteHint(data []byte) ([]HopHint, error) {
3✔
566
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
567
        if err != nil {
3✔
UNCOV
568
                return nil, err
×
UNCOV
569
        }
×
570

571
        // Check that base256Data is a multiple of hopHintLen.
572
        if len(base256Data)%hopHintLen != 0 {
3✔
UNCOV
573
                return nil, fmt.Errorf("expected length multiple of %d bytes, "+
×
UNCOV
574
                        "got %d", hopHintLen, len(base256Data))
×
UNCOV
575
        }
×
576

577
        // Check for empty route hint
578
        if len(base256Data) == 0 {
3✔
NEW
579
                return nil, fmt.Errorf("route hint field contains no hop data")
×
NEW
580
        }
×
581

582
        var routeHint []HopHint
3✔
583

3✔
584
        for len(base256Data) > 0 {
6✔
585
                hopHint := HopHint{}
3✔
586
                hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
3✔
587
                if err != nil {
3✔
UNCOV
588
                        return nil, err
×
UNCOV
589
                }
×
590
                hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
3✔
591
                hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
3✔
592
                hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
3✔
593
                hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
3✔
594

3✔
595
                routeHint = append(routeHint, hopHint)
3✔
596

3✔
597
                base256Data = base256Data[51:]
3✔
598
        }
599

600
        return routeHint, nil
3✔
601
}
602

603
// parseBlindedPaymentPath attempts to parse a BlindedPaymentPath from the given
604
// byte slice.
605
func parseBlindedPaymentPath(data []byte) (*BlindedPaymentPath, error) {
3✔
606
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
607
        if err != nil {
3✔
UNCOV
608
                return nil, err
×
UNCOV
609
        }
×
610

611
        return DecodeBlindedPayment(bytes.NewReader(base256Data))
3✔
612
}
613

614
// parseFeatures decodes any feature bits directly from the base32
615
// representation.
616
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
3✔
617
        rawFeatures := lnwire.NewRawFeatureVector()
3✔
618
        err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
3✔
619
        if err != nil {
3✔
620
                return nil, err
×
621
        }
×
622

623
        return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
3✔
624
}
625

626
// base32ToUint64 converts a base32 encoded number to uint64.
627
func base32ToUint64(data []byte) (uint64, error) {
3✔
628
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
3✔
629
        if len(data) > 13 {
3✔
UNCOV
630
                return 0, fmt.Errorf("cannot parse data of length %d as uint64",
×
UNCOV
631
                        len(data))
×
UNCOV
632
        }
×
633

634
        val := uint64(0)
3✔
635
        for i := 0; i < len(data); i++ {
6✔
636
                val = val<<5 | uint64(data[i])
3✔
637
        }
3✔
638
        return val, nil
3✔
639
}
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