• 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

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

21
// DecodeOption is a type that can be used to supply functional options to the
22
// Decode function.
23
type DecodeOption func(*decodeOptions)
24

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

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

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

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

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

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

67
        var decodedInvoice Invoice
3✔
68

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

209
        return &decodedInvoice, nil
3✔
210
}
211

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

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

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

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

238
        return base32ToUint64(data)
3✔
239
}
240

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

407
        return nil
3✔
408
}
409

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

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

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

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

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

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

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

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

449
        description := string(base256Data)
3✔
450

3✔
451
        return &description, nil
3✔
452
}
453

454
// parseMetadata converts the data (encoded in base32) into a byte slice to use
455
// as the metadata.
UNCOV
456
func parseMetadata(data []byte) ([]byte, error) {
×
UNCOV
457
        return bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
458
}
×
459

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

UNCOV
469
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
470
        if err != nil {
×
UNCOV
471
                return nil, err
×
UNCOV
472
        }
×
473

UNCOV
474
        return btcec.ParsePubKey(base256Data)
×
475
}
476

477
// parseExpiry converts the data (encoded in base32) into the expiry time.
478
func parseExpiry(data []byte) (*time.Duration, error) {
3✔
479
        expiry, err := base32ToUint64(data)
3✔
480
        if err != nil {
3✔
UNCOV
481
                return nil, err
×
UNCOV
482
        }
×
483

484
        duration := time.Duration(expiry) * time.Second
3✔
485

3✔
486
        return &duration, nil
3✔
487
}
488

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

497
        return &expiry, nil
3✔
498
}
499

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

UNCOV
508
        var addr btcutil.Address
×
UNCOV
509

×
UNCOV
510
        version := data[0]
×
UNCOV
511
        switch version {
×
UNCOV
512
        case 0:
×
UNCOV
513
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
514
                if err != nil {
×
UNCOV
515
                        return nil, err
×
UNCOV
516
                }
×
517

UNCOV
518
                switch len(witness) {
×
UNCOV
519
                case 20:
×
UNCOV
520
                        addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
×
UNCOV
521
                case 32:
×
UNCOV
522
                        addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
×
UNCOV
523
                default:
×
UNCOV
524
                        return nil, fmt.Errorf("unknown witness program length %d",
×
UNCOV
525
                                len(witness))
×
526
                }
527

UNCOV
528
                if err != nil {
×
529
                        return nil, err
×
530
                }
×
UNCOV
531
        case 17:
×
UNCOV
532
                pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
533
                if err != nil {
×
UNCOV
534
                        return nil, err
×
UNCOV
535
                }
×
536

UNCOV
537
                addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
×
UNCOV
538
                if err != nil {
×
UNCOV
539
                        return nil, err
×
UNCOV
540
                }
×
UNCOV
541
        case 18:
×
UNCOV
542
                scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
UNCOV
543
                if err != nil {
×
UNCOV
544
                        return nil, err
×
UNCOV
545
                }
×
546

UNCOV
547
                addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
×
UNCOV
548
                if err != nil {
×
UNCOV
549
                        return nil, err
×
UNCOV
550
                }
×
UNCOV
551
        default:
×
552
                // Ignore unknown version.
553
        }
554

UNCOV
555
        return addr, nil
×
556
}
557

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

566
        // Check that base256Data is a multiple of hopHintLen.
567
        if len(base256Data)%hopHintLen != 0 {
3✔
UNCOV
568
                return nil, fmt.Errorf("expected length multiple of %d bytes, "+
×
UNCOV
569
                        "got %d", hopHintLen, len(base256Data))
×
UNCOV
570
        }
×
571

572
        var routeHint []HopHint
3✔
573

3✔
574
        for len(base256Data) > 0 {
6✔
575
                hopHint := HopHint{}
3✔
576
                hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
3✔
577
                if err != nil {
3✔
UNCOV
578
                        return nil, err
×
UNCOV
579
                }
×
580
                hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
3✔
581
                hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
3✔
582
                hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
3✔
583
                hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
3✔
584

3✔
585
                routeHint = append(routeHint, hopHint)
3✔
586

3✔
587
                base256Data = base256Data[51:]
3✔
588
        }
589

590
        return routeHint, nil
3✔
591
}
592

593
// parseBlindedPaymentPath attempts to parse a BlindedPaymentPath from the given
594
// byte slice.
595
func parseBlindedPaymentPath(data []byte) (*BlindedPaymentPath, error) {
3✔
596
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
3✔
597
        if err != nil {
3✔
UNCOV
598
                return nil, err
×
UNCOV
599
        }
×
600

601
        return DecodeBlindedPayment(bytes.NewReader(base256Data))
3✔
602
}
603

604
// parseFeatures decodes any feature bits directly from the base32
605
// representation.
606
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
3✔
607
        rawFeatures := lnwire.NewRawFeatureVector()
3✔
608
        err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
3✔
609
        if err != nil {
3✔
610
                return nil, err
×
611
        }
×
612

613
        return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
3✔
614
}
615

616
// base32ToUint64 converts a base32 encoded number to uint64.
617
func base32ToUint64(data []byte) (uint64, error) {
3✔
618
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
3✔
619
        if len(data) > 13 {
3✔
UNCOV
620
                return 0, fmt.Errorf("cannot parse data of length %d as uint64",
×
UNCOV
621
                        len(data))
×
UNCOV
622
        }
×
623

624
        val := uint64(0)
3✔
625
        for i := 0; i < len(data); i++ {
6✔
626
                val = val<<5 | uint64(data[i])
3✔
627
        }
3✔
628
        return val, nil
3✔
629
}
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