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

lightningnetwork / lnd / 16918135633

12 Aug 2025 06:56PM UTC coverage: 56.955% (-9.9%) from 66.9%
16918135633

push

github

web-flow
Merge pull request #9871 from GeorgeTsagk/htlc-noop-add

Add `NoopAdd` HTLCs

48 of 147 new or added lines in 3 files covered. (32.65%)

29154 existing lines in 462 files now uncovered.

98265 of 172532 relevant lines covered (56.95%)

1.19 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 {
2✔
67
        return &decodeOptions{
2✔
68
                knownFeatureBits:      lnwire.Features,
2✔
69
                errorOnUnknownFeature: false,
2✔
70
        }
2✔
71
}
2✔
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) {
2✔
77

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

83
        var decodedInvoice Invoice
2✔
84

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

91
        // Decode the invoice using the modified bech32 decoder.
92
        hrp, data, err := decodeBech32(invoice)
2✔
93
        if err != nil {
2✔
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 {
2✔
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" {
2✔
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
2✔
116
        if net.Name == chaincfg.SigNetParams.Name {
2✔
UNCOV
117
                expectedPrefix = "tbs"
×
UNCOV
118
        }
×
119
        if !strings.HasPrefix(hrp[2:], expectedPrefix) {
2✔
UNCOV
120
                return nil, fmt.Errorf(
×
UNCOV
121
                        "invoice not for current active network '%s'", net.Name)
×
UNCOV
122
        }
×
123
        decodedInvoice.Net = net
2✔
124

2✔
125
        // Optionally, if there's anything left of the HRP after ln + the segwit
2✔
126
        // prefix, we try to decode this as the payment amount.
2✔
127
        var netPrefixLength = len(expectedPrefix) + 2
2✔
128
        if len(hrp) > netPrefixLength {
4✔
129
                amount, err := decodeAmount(hrp[netPrefixLength:])
2✔
130
                if err != nil {
2✔
UNCOV
131
                        return nil, err
×
UNCOV
132
                }
×
133
                decodedInvoice.MilliSat = &amount
2✔
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 {
2✔
UNCOV
139
                return nil, errors.New("short invoice")
×
UNCOV
140
        }
×
141
        invoiceData := data[:len(data)-signatureBase32Len]
2✔
142

2✔
143
        // Parse the timestamp and tagged fields, and fill the Invoice struct.
2✔
144
        if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
2✔
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:]
2✔
150
        sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
2✔
151
        if err != nil {
2✔
152
                return nil, err
×
153
        }
×
154
        sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64])
2✔
155
        if err != nil {
2✔
156
                return nil, err
×
157
        }
×
158
        recoveryID := sigBase256[64]
2✔
159

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

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

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

2✔
173
        // If the destination pubkey was provided as a tagged field, use that
2✔
174
        // to verify the signature, if not do public key recovery.
2✔
175
        if decodedInvoice.Destination != nil {
2✔
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 {
2✔
185
                headerByte := recoveryID + 27 + 4
2✔
186
                compactSign := append([]byte{headerByte}, sig.RawBytes()...)
2✔
187
                pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
2✔
188
                if err != nil {
2✔
UNCOV
189
                        return nil, err
×
UNCOV
190
                }
×
191
                decodedInvoice.Destination = pubkey
2✔
192
        }
193

194
        // If no feature vector was decoded, populate an empty one.
195
        if decodedInvoice.Features == nil {
2✔
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 {
2✔
UNCOV
204
                return nil, err
×
UNCOV
205
        }
×
206

207
        if options.errorOnUnknownFeature {
2✔
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
2✔
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 {
2✔
231
        // It must contain the timestamp, encoded using 35 bits (7 groups).
2✔
232
        if len(data) < timestampBase32Len {
2✔
UNCOV
233
                return fmt.Errorf("data too short: %d", len(data))
×
UNCOV
234
        }
×
235

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

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

247
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
248
func parseTimestamp(data []byte) (uint64, error) {
2✔
249
        if len(data) != timestampBase32Len {
2✔
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)
2✔
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 {
2✔
260
        index := 0
2✔
261
        for len(fields)-index > 0 {
4✔
262
                // If there are less than 3 groups to read, there cannot be more
2✔
263
                // interesting information, as we need the type (1 group) and
2✔
264
                // length (2 groups).
2✔
265
                //
2✔
266
                // This means the last tagged field is broken.
2✔
267
                if len(fields)-index < 3 {
2✔
UNCOV
268
                        return ErrBrokenTaggedField
×
UNCOV
269
                }
×
270

271
                typ := fields[index]
2✔
272
                dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
2✔
273
                if err != nil {
2✔
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) {
2✔
UNCOV
280
                        return ErrInvalidFieldLength
×
UNCOV
281
                }
×
282
                base32Data := fields[index+3 : index+3+int(dataLength)]
2✔
283

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

2✔
287
                switch typ {
2✔
288
                case fieldTypeP:
2✔
289
                        if invoice.PaymentHash != nil {
2✔
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)
2✔
296

297
                case fieldTypeS:
2✔
298
                        if invoice.PaymentAddr.IsSome() {
2✔
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)
2✔
305
                        if err != nil {
2✔
UNCOV
306
                                return err
×
UNCOV
307
                        }
×
308
                        if addr != nil {
4✔
309
                                invoice.PaymentAddr = fn.Some(*addr)
2✔
310
                        }
2✔
311

312
                case fieldTypeD:
2✔
313
                        if invoice.Description != nil {
2✔
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)
2✔
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:
2✔
349
                        if invoice.expiry != nil {
2✔
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)
2✔
356

357
                case fieldTypeC:
2✔
358
                        if invoice.minFinalCLTVExpiry != nil {
2✔
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 =
2✔
365
                                parseMinFinalCLTVExpiry(base32Data)
2✔
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:
2✔
379
                        // An `r` field can be included in an invoice multiple
2✔
380
                        // times, so we won't skip it if we have already seen
2✔
381
                        // one.
2✔
382
                        routeHint, err := parseRouteHint(base32Data)
2✔
383
                        if err != nil {
2✔
UNCOV
384
                                return err
×
UNCOV
385
                        }
×
386

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

391
                case fieldType9:
2✔
392
                        if invoice.Features != nil {
2✔
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)
2✔
399

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

408
                        invoice.BlindedPaymentPaths = append(
2✔
409
                                invoice.BlindedPaymentPaths, blindedPaymentPath,
2✔
410
                        )
2✔
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 {
2✔
UNCOV
419
                        return err
×
UNCOV
420
                }
×
421
        }
422

423
        return nil
2✔
424
}
425

426
// parseFieldDataLength converts the two byte slice into a uint16.
427
func parseFieldDataLength(data []byte) (uint16, error) {
2✔
428
        if len(data) != 2 {
2✔
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
2✔
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) {
2✔
439
        var paymentHash [32]byte
2✔
440

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

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

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

2✔
454
        return &paymentHash, nil
2✔
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) {
2✔
460
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
2✔
461
        if err != nil {
2✔
UNCOV
462
                return nil, err
×
UNCOV
463
        }
×
464

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

469
        description := string(base256Data)
2✔
470

2✔
471
        return &description, nil
2✔
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) {
2✔
499
        expiry, err := base32ToUint64(data)
2✔
500
        if err != nil {
2✔
UNCOV
501
                return nil, err
×
UNCOV
502
        }
×
503

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

2✔
506
        return &duration, nil
2✔
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) {
2✔
512
        expiry, err := base32ToUint64(data)
2✔
513
        if err != nil {
2✔
UNCOV
514
                return nil, err
×
UNCOV
515
        }
×
516

517
        return &expiry, nil
2✔
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) {
2✔
581
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
2✔
582
        if err != nil {
2✔
UNCOV
583
                return nil, err
×
UNCOV
584
        }
×
585

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

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

596
        var routeHint []HopHint
2✔
597

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

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

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

614
        return routeHint, nil
2✔
615
}
616

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

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

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

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

640
// base32ToUint64 converts a base32 encoded number to uint64.
641
func base32ToUint64(data []byte) (uint64, error) {
2✔
642
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
2✔
643
        if len(data) > 13 {
2✔
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)
2✔
649
        for i := 0; i < len(data); i++ {
4✔
650
                val = val<<5 | uint64(data[i])
2✔
651
        }
2✔
652
        return val, nil
2✔
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