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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

0.0
/channeldb/migration_01_to_11/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
        lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
18
)
19

20
// Decode parses the provided encoded invoice and returns a decoded Invoice if
21
// it is valid by BOLT-0011 and matches the provided active network.
UNCOV
22
func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
×
UNCOV
23
        decodedInvoice := Invoice{}
×
UNCOV
24

×
UNCOV
25
        // Before bech32 decoding the invoice, make sure that it is not too large.
×
UNCOV
26
        // This is done as an anti-DoS measure since bech32 decoding is expensive.
×
UNCOV
27
        if len(invoice) > maxInvoiceLength {
×
28
                return nil, ErrInvoiceTooLarge
×
29
        }
×
30

31
        // Decode the invoice using the modified bech32 decoder.
UNCOV
32
        hrp, data, err := decodeBech32(invoice)
×
UNCOV
33
        if err != nil {
×
34
                return nil, err
×
35
        }
×
36

37
        // We expect the human-readable part to at least have ln + one char
38
        // encoding the network.
UNCOV
39
        if len(hrp) < 3 {
×
40
                return nil, fmt.Errorf("hrp too short")
×
41
        }
×
42

43
        // First two characters of HRP should be "ln".
UNCOV
44
        if hrp[:2] != "ln" {
×
45
                return nil, fmt.Errorf("prefix should be \"ln\"")
×
46
        }
×
47

48
        // The next characters should be a valid prefix for a segwit BIP173
49
        // address that match the active network.
UNCOV
50
        if !strings.HasPrefix(hrp[2:], net.Bech32HRPSegwit) {
×
UNCOV
51
                return nil, fmt.Errorf(
×
UNCOV
52
                        "invoice not for current active network '%s'", net.Name)
×
UNCOV
53
        }
×
UNCOV
54
        decodedInvoice.Net = net
×
UNCOV
55

×
UNCOV
56
        // Optionally, if there's anything left of the HRP after ln + the segwit
×
UNCOV
57
        // prefix, we try to decode this as the payment amount.
×
UNCOV
58
        var netPrefixLength = len(net.Bech32HRPSegwit) + 2
×
UNCOV
59
        if len(hrp) > netPrefixLength {
×
60
                amount, err := decodeAmount(hrp[netPrefixLength:])
×
61
                if err != nil {
×
62
                        return nil, err
×
63
                }
×
64
                decodedInvoice.MilliSat = &amount
×
65
        }
66

67
        // Everything except the last 520 bits of the data encodes the invoice's
68
        // timestamp and tagged fields.
UNCOV
69
        if len(data) < signatureBase32Len {
×
70
                return nil, errors.New("short invoice")
×
71
        }
×
UNCOV
72
        invoiceData := data[:len(data)-signatureBase32Len]
×
UNCOV
73

×
UNCOV
74
        // Parse the timestamp and tagged fields, and fill the Invoice struct.
×
UNCOV
75
        if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
×
76
                return nil, err
×
77
        }
×
78

79
        // The last 520 bits (104 groups) make up the signature.
UNCOV
80
        sigBase32 := data[len(data)-signatureBase32Len:]
×
UNCOV
81
        sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
×
UNCOV
82
        if err != nil {
×
83
                return nil, err
×
84
        }
×
UNCOV
85
        var sig lnwire.Sig
×
UNCOV
86
        copy(sig[:], sigBase256[:64])
×
UNCOV
87
        recoveryID := sigBase256[64]
×
UNCOV
88

×
UNCOV
89
        // The signature is over the hrp + the data the invoice, encoded in
×
UNCOV
90
        // base 256.
×
UNCOV
91
        taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
×
UNCOV
92
        if err != nil {
×
93
                return nil, err
×
94
        }
×
95

UNCOV
96
        toSign := append([]byte(hrp), taggedDataBytes...)
×
UNCOV
97

×
UNCOV
98
        // We expect the signature to be over the single SHA-256 hash of that
×
UNCOV
99
        // data.
×
UNCOV
100
        hash := chainhash.HashB(toSign)
×
UNCOV
101

×
UNCOV
102
        // If the destination pubkey was provided as a tagged field, use that
×
UNCOV
103
        // to verify the signature, if not do public key recovery.
×
UNCOV
104
        if decodedInvoice.Destination != nil {
×
105
                signature, err := sig.ToSignature()
×
106
                if err != nil {
×
107
                        return nil, fmt.Errorf("unable to deserialize "+
×
108
                                "signature: %v", err)
×
109
                }
×
110
                if !signature.Verify(hash, decodedInvoice.Destination) {
×
111
                        return nil, fmt.Errorf("invalid invoice signature")
×
112
                }
×
UNCOV
113
        } else {
×
UNCOV
114
                headerByte := recoveryID + 27 + 4
×
UNCOV
115
                compactSign := append([]byte{headerByte}, sig[:]...)
×
UNCOV
116
                pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash)
×
UNCOV
117
                if err != nil {
×
118
                        return nil, err
×
119
                }
×
UNCOV
120
                decodedInvoice.Destination = pubkey
×
121
        }
122

123
        // If no feature vector was decoded, populate an empty one.
UNCOV
124
        if decodedInvoice.Features == nil {
×
UNCOV
125
                decodedInvoice.Features = lnwire.NewFeatureVector(
×
UNCOV
126
                        nil, lnwire.Features,
×
UNCOV
127
                )
×
UNCOV
128
        }
×
129

130
        // Now that we have created the invoice, make sure it has the required
131
        // fields set.
UNCOV
132
        if err := validateInvoice(&decodedInvoice); err != nil {
×
133
                return nil, err
×
134
        }
×
135

UNCOV
136
        return &decodedInvoice, nil
×
137
}
138

139
// parseData parses the data part of the invoice. It expects base32 data
140
// returned from the bech32.Decode method, except signature.
UNCOV
141
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
×
UNCOV
142
        // It must contain the timestamp, encoded using 35 bits (7 groups).
×
UNCOV
143
        if len(data) < timestampBase32Len {
×
144
                return fmt.Errorf("data too short: %d", len(data))
×
145
        }
×
146

UNCOV
147
        t, err := parseTimestamp(data[:timestampBase32Len])
×
UNCOV
148
        if err != nil {
×
149
                return err
×
150
        }
×
UNCOV
151
        invoice.Timestamp = time.Unix(int64(t), 0)
×
UNCOV
152

×
UNCOV
153
        // The rest are tagged parts.
×
UNCOV
154
        tagData := data[7:]
×
UNCOV
155
        return parseTaggedFields(invoice, tagData, net)
×
156
}
157

158
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
UNCOV
159
func parseTimestamp(data []byte) (uint64, error) {
×
UNCOV
160
        if len(data) != timestampBase32Len {
×
161
                return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
×
162
                        len(data)*5)
×
163
        }
×
164

UNCOV
165
        return base32ToUint64(data)
×
166
}
167

168
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
169
// fills the Invoice struct accordingly.
UNCOV
170
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
×
UNCOV
171
        index := 0
×
UNCOV
172
        for len(fields)-index > 0 {
×
UNCOV
173
                // If there are less than 3 groups to read, there cannot be more
×
UNCOV
174
                // interesting information, as we need the type (1 group) and
×
UNCOV
175
                // length (2 groups).
×
UNCOV
176
                //
×
UNCOV
177
                // This means the last tagged field is broken.
×
UNCOV
178
                if len(fields)-index < 3 {
×
179
                        return ErrBrokenTaggedField
×
180
                }
×
181

UNCOV
182
                typ := fields[index]
×
UNCOV
183
                dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
×
UNCOV
184
                if err != nil {
×
185
                        return err
×
186
                }
×
187

188
                // If we don't have enough field data left to read this length,
189
                // return error.
UNCOV
190
                if len(fields) < index+3+int(dataLength) {
×
191
                        return ErrInvalidFieldLength
×
192
                }
×
UNCOV
193
                base32Data := fields[index+3 : index+3+int(dataLength)]
×
UNCOV
194

×
UNCOV
195
                // Advance the index in preparation for the next iteration.
×
UNCOV
196
                index += 3 + int(dataLength)
×
UNCOV
197

×
UNCOV
198
                switch typ {
×
UNCOV
199
                case fieldTypeP:
×
UNCOV
200
                        if invoice.PaymentHash != nil {
×
201
                                // We skip the field if we have already seen a
×
202
                                // supported one.
×
203
                                continue
×
204
                        }
205

UNCOV
206
                        invoice.PaymentHash, err = parse32Bytes(base32Data)
×
207
                case fieldTypeS:
×
208
                        if invoice.PaymentAddr != nil {
×
209
                                // We skip the field if we have already seen a
×
210
                                // supported one.
×
211
                                continue
×
212
                        }
213

214
                        invoice.PaymentAddr, err = parse32Bytes(base32Data)
×
UNCOV
215
                case fieldTypeD:
×
UNCOV
216
                        if invoice.Description != nil {
×
217
                                // We skip the field if we have already seen a
×
218
                                // supported one.
×
219
                                continue
×
220
                        }
221

UNCOV
222
                        invoice.Description, err = parseDescription(base32Data)
×
223
                case fieldTypeN:
×
224
                        if invoice.Destination != nil {
×
225
                                // We skip the field if we have already seen a
×
226
                                // supported one.
×
227
                                continue
×
228
                        }
229

230
                        invoice.Destination, err = parseDestination(base32Data)
×
231
                case fieldTypeH:
×
232
                        if invoice.DescriptionHash != nil {
×
233
                                // We skip the field if we have already seen a
×
234
                                // supported one.
×
235
                                continue
×
236
                        }
237

238
                        invoice.DescriptionHash, err = parse32Bytes(base32Data)
×
239
                case fieldTypeX:
×
240
                        if invoice.expiry != nil {
×
241
                                // We skip the field if we have already seen a
×
242
                                // supported one.
×
243
                                continue
×
244
                        }
245

246
                        invoice.expiry, err = parseExpiry(base32Data)
×
UNCOV
247
                case fieldTypeC:
×
UNCOV
248
                        if invoice.minFinalCLTVExpiry != nil {
×
249
                                // We skip the field if we have already seen a
×
250
                                // supported one.
×
251
                                continue
×
252
                        }
253

UNCOV
254
                        invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data)
×
255
                case fieldTypeF:
×
256
                        if invoice.FallbackAddr != nil {
×
257
                                // We skip the field if we have already seen a
×
258
                                // supported one.
×
259
                                continue
×
260
                        }
261

262
                        invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net)
×
263
                case fieldTypeR:
×
264
                        // An `r` field can be included in an invoice multiple
×
265
                        // times, so we won't skip it if we have already seen
×
266
                        // one.
×
267
                        routeHint, err := parseRouteHint(base32Data)
×
268
                        if err != nil {
×
269
                                return err
×
270
                        }
×
271

272
                        invoice.RouteHints = append(invoice.RouteHints, routeHint)
×
273
                case fieldType9:
×
274
                        if invoice.Features != nil {
×
275
                                // We skip the field if we have already seen a
×
276
                                // supported one.
×
277
                                continue
×
278
                        }
279

280
                        invoice.Features, err = parseFeatures(base32Data)
×
281
                default:
×
282
                        // Ignore unknown type.
283
                }
284

285
                // Check if there was an error from parsing any of the tagged
286
                // fields and return it.
UNCOV
287
                if err != nil {
×
288
                        return err
×
289
                }
×
290
        }
291

UNCOV
292
        return nil
×
293
}
294

295
// parseFieldDataLength converts the two byte slice into a uint16.
UNCOV
296
func parseFieldDataLength(data []byte) (uint16, error) {
×
UNCOV
297
        if len(data) != 2 {
×
298
                return 0, fmt.Errorf("data length must be 2 bytes, was %d",
×
299
                        len(data))
×
300
        }
×
301

UNCOV
302
        return uint16(data[0])<<5 | uint16(data[1]), nil
×
303
}
304

305
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
306
// can be used for payment hashes, description hashes, payment addresses, etc.
UNCOV
307
func parse32Bytes(data []byte) (*[32]byte, error) {
×
UNCOV
308
        var paymentHash [32]byte
×
UNCOV
309

×
UNCOV
310
        // As BOLT-11 states, a reader must skip over the 32-byte fields if
×
UNCOV
311
        // it does not have a length of 52, so avoid returning an error.
×
UNCOV
312
        if len(data) != hashBase32Len {
×
313
                return nil, nil
×
314
        }
×
315

UNCOV
316
        hash, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
317
        if err != nil {
×
318
                return nil, err
×
319
        }
×
320

UNCOV
321
        copy(paymentHash[:], hash)
×
UNCOV
322

×
UNCOV
323
        return &paymentHash, nil
×
324
}
325

326
// parseDescription converts the data (encoded in base32) into a string to use
327
// as the description.
UNCOV
328
func parseDescription(data []byte) (*string, error) {
×
UNCOV
329
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
UNCOV
330
        if err != nil {
×
331
                return nil, err
×
332
        }
×
333

UNCOV
334
        description := string(base256Data)
×
UNCOV
335

×
UNCOV
336
        return &description, nil
×
337
}
338

339
// parseDestination converts the data (encoded in base32) into a 33-byte public
340
// key of the payee node.
341
func parseDestination(data []byte) (*btcec.PublicKey, error) {
×
342
        // As BOLT-11 states, a reader must skip over the destination field
×
343
        // if it does not have a length of 53, so avoid returning an error.
×
344
        if len(data) != pubKeyBase32Len {
×
345
                return nil, nil
×
346
        }
×
347

348
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
349
        if err != nil {
×
350
                return nil, err
×
351
        }
×
352

353
        return btcec.ParsePubKey(base256Data)
×
354
}
355

356
// parseExpiry converts the data (encoded in base32) into the expiry time.
357
func parseExpiry(data []byte) (*time.Duration, error) {
×
358
        expiry, err := base32ToUint64(data)
×
359
        if err != nil {
×
360
                return nil, err
×
361
        }
×
362

363
        duration := time.Duration(expiry) * time.Second
×
364

×
365
        return &duration, nil
×
366
}
367

368
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
369
// to use as the minFinalCLTVExpiry.
UNCOV
370
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
×
UNCOV
371
        expiry, err := base32ToUint64(data)
×
UNCOV
372
        if err != nil {
×
373
                return nil, err
×
374
        }
×
375

UNCOV
376
        return &expiry, nil
×
377
}
378

379
// parseFallbackAddr converts the data (encoded in base32) into a fallback
380
// on-chain address.
381
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) {
×
382
        // Checks if the data is empty or contains a version without an address.
×
383
        if len(data) < 2 {
×
384
                return nil, fmt.Errorf("empty fallback address field")
×
385
        }
×
386

387
        var addr btcutil.Address
×
388

×
389
        version := data[0]
×
390
        switch version {
×
391
        case 0:
×
392
                witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
393
                if err != nil {
×
394
                        return nil, err
×
395
                }
×
396

397
                switch len(witness) {
×
398
                case 20:
×
399
                        addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
×
400
                case 32:
×
401
                        addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
×
402
                default:
×
403
                        return nil, fmt.Errorf("unknown witness program length %d",
×
404
                                len(witness))
×
405
                }
406

407
                if err != nil {
×
408
                        return nil, err
×
409
                }
×
410
        case 17:
×
411
                pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
412
                if err != nil {
×
413
                        return nil, err
×
414
                }
×
415

416
                addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
×
417
                if err != nil {
×
418
                        return nil, err
×
419
                }
×
420
        case 18:
×
421
                scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
×
422
                if err != nil {
×
423
                        return nil, err
×
424
                }
×
425

426
                addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
×
427
                if err != nil {
×
428
                        return nil, err
×
429
                }
×
430
        default:
×
431
                // Ignore unknown version.
432
        }
433

434
        return addr, nil
×
435
}
436

437
// parseRouteHint converts the data (encoded in base32) into an array containing
438
// one or more routing hop hints that represent a single route hint.
439
func parseRouteHint(data []byte) ([]HopHint, error) {
×
440
        base256Data, err := bech32.ConvertBits(data, 5, 8, false)
×
441
        if err != nil {
×
442
                return nil, err
×
443
        }
×
444

445
        // Check that base256Data is a multiple of hopHintLen.
446
        if len(base256Data)%hopHintLen != 0 {
×
447
                return nil, fmt.Errorf("expected length multiple of %d bytes, "+
×
448
                        "got %d", hopHintLen, len(base256Data))
×
449
        }
×
450

451
        var routeHint []HopHint
×
452

×
453
        for len(base256Data) > 0 {
×
454
                hopHint := HopHint{}
×
455
                hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33])
×
456
                if err != nil {
×
457
                        return nil, err
×
458
                }
×
459
                hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
×
460
                hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
×
461
                hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
×
462
                hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
×
463

×
464
                routeHint = append(routeHint, hopHint)
×
465

×
466
                base256Data = base256Data[51:]
×
467
        }
468

469
        return routeHint, nil
×
470
}
471

472
// parseFeatures decodes any feature bits directly from the base32
473
// representation.
474
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
×
475
        rawFeatures := lnwire.NewRawFeatureVector()
×
476
        err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
×
477
        if err != nil {
×
478
                return nil, err
×
479
        }
×
480

481
        return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil
×
482
}
483

484
// base32ToUint64 converts a base32 encoded number to uint64.
UNCOV
485
func base32ToUint64(data []byte) (uint64, error) {
×
UNCOV
486
        // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
×
UNCOV
487
        if len(data) > 13 {
×
488
                return 0, fmt.Errorf("cannot parse data of length %d as uint64",
×
489
                        len(data))
×
490
        }
×
491

UNCOV
492
        val := uint64(0)
×
UNCOV
493
        for i := 0; i < len(data); i++ {
×
UNCOV
494
                val = val<<5 | uint64(data[i])
×
UNCOV
495
        }
×
UNCOV
496
        return val, nil
×
497
}
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