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

lightningnetwork / lnd / 17991712134

24 Sep 2025 10:49PM UTC coverage: 57.143% (-9.5%) from 66.653%
17991712134

push

github

web-flow
Merge pull request #10183 from Roasbeef/brontide-static-alloac

brontide: eliminate all allocations from WriteMessage+Flush

59 of 67 new or added lines in 3 files covered. (88.06%)

28705 existing lines in 459 files now uncovered.

99466 of 174065 relevant lines covered (57.14%)

1.77 hits per line

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

47.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/btcsuite/btcd/txscript"
19
        "github.com/lightningnetwork/lnd/fn/v2"
20
        "github.com/lightningnetwork/lnd/lnwire"
21
)
22

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

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

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

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

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

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

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

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

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

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

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

91
        var decodedInvoice Invoice
3✔
92

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

233
        return &decodedInvoice, nil
3✔
234
}
235

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

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

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

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

262
        return base32ToUint64(data)
3✔
263
}
264

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

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

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

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

3✔
295
                switch typ {
3✔
296
                case fieldTypeP:
3✔
297
                        if invoice.PaymentHash != 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.PaymentHash, err = parse32Bytes(base32Data)
3✔
304

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

312
                        addr, err := parse32Bytes(base32Data)
3✔
313
                        if err != nil {
3✔
UNCOV
314
                                return err
×
UNCOV
315
                        }
×
316
                        if addr != nil {
6✔
317
                                invoice.PaymentAddr = fn.Some(*addr)
3✔
318
                        }
3✔
319

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

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

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

UNCOV
336
                        invoice.Metadata, err = parseMetadata(base32Data)
×
337

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

UNCOV
345
                        invoice.Destination, err = parseDestination(base32Data)
×
346

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

UNCOV
354
                        invoice.DescriptionHash, err = parse32Bytes(base32Data)
×
355

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

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

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

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

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

UNCOV
382
                        invoice.FallbackAddr, err = parseFallbackAddr(
×
UNCOV
383
                                base32Data, net,
×
UNCOV
384
                        )
×
385

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

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

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

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

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

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

UNCOV
420
                default:
×
421
                        // Ignore unknown type.
422
                }
423

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

431
        return nil
3✔
432
}
433

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

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

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

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

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

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

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

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

473
        if !utf8.Valid(base256Data) {
3✔
UNCOV
474
                return nil, ErrInvalidUTF8Description
×
UNCOV
475
        }
×
476

477
        description := string(base256Data)
3✔
478

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

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

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

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

UNCOV
502
        return btcec.ParsePubKey(base256Data)
×
503
}
504

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

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

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

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

525
        return &expiry, nil
3✔
526
}
527

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

UNCOV
536
        var addr btcutil.Address
×
UNCOV
537

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

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

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

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

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

UNCOV
592
        return addr, nil
×
593
}
594

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

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

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

613
        var routeHint []HopHint
3✔
614

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

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

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

631
        return routeHint, nil
3✔
632
}
633

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

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

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

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

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

665
        val := uint64(0)
3✔
666
        for i := 0; i < len(data); i++ {
6✔
667
                val = val<<5 | uint64(data[i])
3✔
668
        }
3✔
669
        return val, nil
3✔
670
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc