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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

51.94
/htlcswitch/hop/payload.go
1
package hop
2

3
import (
4
        "encoding/binary"
5
        "fmt"
6
        "io"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        sphinx "github.com/lightningnetwork/lightning-onion"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
        "github.com/lightningnetwork/lnd/record"
12
        "github.com/lightningnetwork/lnd/tlv"
13
)
14

15
// PayloadViolation is an enum encapsulating the possible invalid payload
16
// violations that can occur when processing or validating a payload.
17
type PayloadViolation byte
18

19
const (
20
        // OmittedViolation indicates that a type was expected to be found the
21
        // payload but was absent.
22
        OmittedViolation PayloadViolation = iota
23

24
        // IncludedViolation indicates that a type was expected to be omitted
25
        // from the payload but was present.
26
        IncludedViolation
27

28
        // RequiredViolation indicates that an unknown even type was found in
29
        // the payload that we could not process.
30
        RequiredViolation
31

32
        // InsufficientViolation indicates that the provided type does
33
        // not satisfy constraints.
34
        InsufficientViolation
35
)
36

37
// String returns a human-readable description of the violation as a verb.
38
func (v PayloadViolation) String() string {
×
39
        switch v {
×
40
        case OmittedViolation:
×
41
                return "omitted"
×
42

43
        case IncludedViolation:
×
44
                return "included"
×
45

46
        case RequiredViolation:
×
47
                return "required"
×
48

49
        case InsufficientViolation:
×
50
                return "insufficient"
×
51

52
        default:
×
53
                return "unknown violation"
×
54
        }
55
}
56

57
// ErrInvalidPayload is an error returned when a parsed onion payload either
58
// included or omitted incorrect records for a particular hop type.
59
type ErrInvalidPayload struct {
60
        // Type the record's type that cause the violation.
61
        Type tlv.Type
62

63
        // Violation is an enum indicating the type of violation detected in
64
        // processing Type.
65
        Violation PayloadViolation
66

67
        // FinalHop if true, indicates that the violation is for the final hop
68
        // in the route (identified by next hop id), otherwise the violation is
69
        // for an intermediate hop.
70
        FinalHop bool
71
}
72

73
// Error returns a human-readable description of the invalid payload error.
74
func (e ErrInvalidPayload) Error() string {
×
75
        hopType := "intermediate"
×
76
        if e.FinalHop {
×
77
                hopType = "final"
×
78
        }
×
79

80
        return fmt.Sprintf("onion payload for %s hop %v record with type %d",
×
81
                hopType, e.Violation, e.Type)
×
82
}
83

84
// Payload encapsulates all information delivered to a hop in an onion payload.
85
// A Hop can represent either a TLV or legacy payload. The primary forwarding
86
// instruction can be accessed via ForwardingInfo, and additional records can be
87
// accessed by other member functions.
88
type Payload struct {
89
        // FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
90
        // amount, cltv, and next hop.
91
        FwdInfo ForwardingInfo
92

93
        // MPP holds the info provided in an option_mpp record when parsed from
94
        // a TLV onion payload.
95
        MPP *record.MPP
96

97
        // AMP holds the info provided in an option_amp record when parsed from
98
        // a TLV onion payload.
99
        AMP *record.AMP
100

101
        // customRecords are user-defined records in the custom type range that
102
        // were included in the payload.
103
        customRecords record.CustomSet
104

105
        // encryptedData is a blob of data encrypted by the receiver for use
106
        // in blinded routes.
107
        encryptedData []byte
108

109
        // blindingPoint is an ephemeral pubkey for use in blinded routes.
110
        blindingPoint *btcec.PublicKey
111

112
        // metadata is additional data that is sent along with the payment to
113
        // the payee.
114
        metadata []byte
115

116
        // totalAmtMsat holds the info provided in total_amount_msat when
117
        // parsed from a TLV onion payload.
118
        totalAmtMsat lnwire.MilliSatoshi
119
}
120

121
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
122
// parameters provided by leegacy onion payloads.
123
func NewLegacyPayload(f *sphinx.HopData) *Payload {
×
124
        nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
×
125

×
126
        return &Payload{
×
127
                FwdInfo: ForwardingInfo{
×
128
                        NextHop:         lnwire.NewShortChanIDFromInt(nextHop),
×
129
                        AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
×
130
                        OutgoingCTLV:    f.OutgoingCltv,
×
131
                },
×
132
                customRecords: make(record.CustomSet),
×
133
        }
×
134
}
×
135

136
// ParseTLVPayload builds a new Hop from the passed io.Reader and returns
137
// a map of all the types that were found in the payload. This function
138
// does not perform validation of TLV types included in the payload.
139
func ParseTLVPayload(r io.Reader) (*Payload, map[tlv.Type][]byte, error) {
3✔
140
        var (
3✔
141
                cid           uint64
3✔
142
                amt           uint64
3✔
143
                totalAmtMsat  uint64
3✔
144
                cltv          uint32
3✔
145
                mpp           = &record.MPP{}
3✔
146
                amp           = &record.AMP{}
3✔
147
                encryptedData []byte
3✔
148
                blindingPoint *btcec.PublicKey
3✔
149
                metadata      []byte
3✔
150
        )
3✔
151

3✔
152
        tlvStream, err := tlv.NewStream(
3✔
153
                record.NewAmtToFwdRecord(&amt),
3✔
154
                record.NewLockTimeRecord(&cltv),
3✔
155
                record.NewNextHopIDRecord(&cid),
3✔
156
                mpp.Record(),
3✔
157
                record.NewEncryptedDataRecord(&encryptedData),
3✔
158
                record.NewBlindingPointRecord(&blindingPoint),
3✔
159
                amp.Record(),
3✔
160
                record.NewMetadataRecord(&metadata),
3✔
161
                record.NewTotalAmtMsatBlinded(&totalAmtMsat),
3✔
162
        )
3✔
163
        if err != nil {
3✔
164
                return nil, nil, err
×
165
        }
×
166

167
        // Since this data is provided by a potentially malicious peer, pass it
168
        // into the P2P decoding variant.
169
        parsedTypes, err := tlvStream.DecodeWithParsedTypesP2P(r)
3✔
170
        if err != nil {
3✔
171
                return nil, nil, err
×
172
        }
×
173

174
        // If no MPP field was parsed, set the MPP field on the resulting
175
        // payload to nil.
176
        if _, ok := parsedTypes[record.MPPOnionType]; !ok {
6✔
177
                mpp = nil
3✔
178
        }
3✔
179

180
        // If no AMP field was parsed, set the MPP field on the resulting
181
        // payload to nil.
182
        if _, ok := parsedTypes[record.AMPOnionType]; !ok {
6✔
183
                amp = nil
3✔
184
        }
3✔
185

186
        // If no encrypted data was parsed, set the field on our resulting
187
        // payload to nil.
188
        if _, ok := parsedTypes[record.EncryptedDataOnionType]; !ok {
6✔
189
                encryptedData = nil
3✔
190
        }
3✔
191

192
        // If no metadata field was parsed, set the metadata field on the
193
        // resulting payload to nil.
194
        if _, ok := parsedTypes[record.MetadataOnionType]; !ok {
6✔
195
                metadata = nil
3✔
196
        }
3✔
197

198
        // Filter out the custom records.
199
        customRecords := NewCustomRecords(parsedTypes)
3✔
200

3✔
201
        return &Payload{
3✔
202
                FwdInfo: ForwardingInfo{
3✔
203
                        NextHop:         lnwire.NewShortChanIDFromInt(cid),
3✔
204
                        AmountToForward: lnwire.MilliSatoshi(amt),
3✔
205
                        OutgoingCTLV:    cltv,
3✔
206
                },
3✔
207
                MPP:           mpp,
3✔
208
                AMP:           amp,
3✔
209
                metadata:      metadata,
3✔
210
                encryptedData: encryptedData,
3✔
211
                blindingPoint: blindingPoint,
3✔
212
                customRecords: customRecords,
3✔
213
                totalAmtMsat:  lnwire.MilliSatoshi(totalAmtMsat),
3✔
214
        }, parsedTypes, nil
3✔
215
}
216

217
// ValidateTLVPayload validates the TLV fields that were included in a TLV
218
// payload.
219
func ValidateTLVPayload(parsedTypes map[tlv.Type][]byte,
220
        finalHop bool, updateAddBlinding bool) error {
3✔
221

3✔
222
        // Validate whether the sender properly included or omitted tlv records
3✔
223
        // in accordance with BOLT 04.
3✔
224
        err := ValidateParsedPayloadTypes(
3✔
225
                parsedTypes, finalHop, updateAddBlinding,
3✔
226
        )
3✔
227
        if err != nil {
3✔
228
                return err
×
229
        }
×
230

231
        // Check for violation of the rules for mandatory fields.
232
        violatingType := getMinRequiredViolation(parsedTypes)
3✔
233
        if violatingType != nil {
3✔
234
                return ErrInvalidPayload{
×
235
                        Type:      *violatingType,
×
236
                        Violation: RequiredViolation,
×
237
                        FinalHop:  finalHop,
×
238
                }
×
239
        }
×
240

241
        return nil
3✔
242
}
243

244
// ForwardingInfo returns the basic parameters required for HTLC forwarding,
245
// e.g. amount, cltv, and next hop.
246
func (h *Payload) ForwardingInfo() ForwardingInfo {
3✔
247
        return h.FwdInfo
3✔
248
}
3✔
249

250
// NewCustomRecords filters the types parsed from the tlv stream for custom
251
// records.
252
func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet {
3✔
253
        customRecords := make(record.CustomSet)
3✔
254
        for t, parseResult := range parsedTypes {
6✔
255
                if parseResult == nil || t < record.CustomTypeStart {
6✔
256
                        continue
3✔
257
                }
258
                customRecords[uint64(t)] = parseResult
3✔
259
        }
260
        return customRecords
3✔
261
}
262

263
// ValidateParsedPayloadTypes checks the types parsed from a hop payload to
264
// ensure that the proper fields are either included or omitted. The finalHop
265
// boolean should be true if the payload was parsed for an exit hop. The
266
// requirements for this method are described in BOLT 04.
267
func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap,
268
        isFinalHop, updateAddBlinding bool) error {
3✔
269

3✔
270
        _, hasAmt := parsedTypes[record.AmtOnionType]
3✔
271
        _, hasLockTime := parsedTypes[record.LockTimeOnionType]
3✔
272
        _, hasNextHop := parsedTypes[record.NextHopOnionType]
3✔
273
        _, hasMPP := parsedTypes[record.MPPOnionType]
3✔
274
        _, hasAMP := parsedTypes[record.AMPOnionType]
3✔
275
        _, hasEncryptedData := parsedTypes[record.EncryptedDataOnionType]
3✔
276
        _, hasBlinding := parsedTypes[record.BlindingPointOnionType]
3✔
277

3✔
278
        // All cleartext hops (including final hop) and the final hop in a
3✔
279
        // blinded path require the forwading amount and expiry TLVs to be set.
3✔
280
        needFwdInfo := isFinalHop || !hasEncryptedData
3✔
281

3✔
282
        // No blinded hops should have a next hop specified, and only the final
3✔
283
        // hop in a cleartext route should exclude it.
3✔
284
        needNextHop := !(hasEncryptedData || isFinalHop)
3✔
285

3✔
286
        switch {
3✔
287
        // Both blinding point being set is invalid.
288
        case hasBlinding && updateAddBlinding:
×
289
                return ErrInvalidPayload{
×
290
                        Type:      record.BlindingPointOnionType,
×
291
                        Violation: IncludedViolation,
×
292
                        FinalHop:  isFinalHop,
×
293
                }
×
294

295
        // If encrypted data is not provided, blinding points should not be
296
        // set.
297
        case !hasEncryptedData && (hasBlinding || updateAddBlinding):
×
298
                return ErrInvalidPayload{
×
299
                        Type:      record.EncryptedDataOnionType,
×
300
                        Violation: OmittedViolation,
×
301
                        FinalHop:  isFinalHop,
×
302
                }
×
303

304
        // If encrypted data is present, we require that one blinding point
305
        // is set.
306
        case hasEncryptedData && !(hasBlinding || updateAddBlinding):
×
307
                return ErrInvalidPayload{
×
308
                        Type:      record.EncryptedDataOnionType,
×
309
                        Violation: IncludedViolation,
×
310
                        FinalHop:  isFinalHop,
×
311
                }
×
312

313
        // Hops that need forwarding info must include an amount to forward.
314
        case needFwdInfo && !hasAmt:
×
315
                return ErrInvalidPayload{
×
316
                        Type:      record.AmtOnionType,
×
317
                        Violation: OmittedViolation,
×
318
                        FinalHop:  isFinalHop,
×
319
                }
×
320

321
        // Hops that need forwarding info must include a cltv expiry.
322
        case needFwdInfo && !hasLockTime:
×
323
                return ErrInvalidPayload{
×
324
                        Type:      record.LockTimeOnionType,
×
325
                        Violation: OmittedViolation,
×
326
                        FinalHop:  isFinalHop,
×
327
                }
×
328

329
        // Hops that don't need forwarding info shouldn't have an amount TLV.
330
        case !needFwdInfo && hasAmt:
×
331
                return ErrInvalidPayload{
×
332
                        Type:      record.AmtOnionType,
×
333
                        Violation: IncludedViolation,
×
334
                        FinalHop:  isFinalHop,
×
335
                }
×
336

337
        // Hops that don't need forwarding info shouldn't have a cltv TLV.
338
        case !needFwdInfo && hasLockTime:
×
339
                return ErrInvalidPayload{
×
340
                        Type:      record.LockTimeOnionType,
×
341
                        Violation: IncludedViolation,
×
342
                        FinalHop:  isFinalHop,
×
343
                }
×
344

345
        // The exit hop and all blinded hops should omit the next hop id.
346
        case !needNextHop && hasNextHop:
×
347
                return ErrInvalidPayload{
×
348
                        Type:      record.NextHopOnionType,
×
349
                        Violation: IncludedViolation,
×
350
                        FinalHop:  isFinalHop,
×
351
                }
×
352

353
        // Require that the next hop is set for intermediate hops in regular
354
        // routes.
355
        case needNextHop && !hasNextHop:
×
356
                return ErrInvalidPayload{
×
357
                        Type:      record.NextHopOnionType,
×
358
                        Violation: OmittedViolation,
×
359
                        FinalHop:  isFinalHop,
×
360
                }
×
361

362
        // Intermediate nodes should never receive MPP fields.
363
        case !isFinalHop && hasMPP:
×
364
                return ErrInvalidPayload{
×
365
                        Type:      record.MPPOnionType,
×
366
                        Violation: IncludedViolation,
×
367
                        FinalHop:  isFinalHop,
×
368
                }
×
369

370
        // Intermediate nodes should never receive AMP fields.
371
        case !isFinalHop && hasAMP:
×
372
                return ErrInvalidPayload{
×
373
                        Type:      record.AMPOnionType,
×
374
                        Violation: IncludedViolation,
×
375
                        FinalHop:  isFinalHop,
×
376
                }
×
377
        }
378

379
        return nil
3✔
380
}
381

382
// MultiPath returns the record corresponding the option_mpp parsed from the
383
// onion payload.
384
func (h *Payload) MultiPath() *record.MPP {
3✔
385
        return h.MPP
3✔
386
}
3✔
387

388
// AMPRecord returns the record corresponding with option_amp parsed from the
389
// onion payload.
390
func (h *Payload) AMPRecord() *record.AMP {
3✔
391
        return h.AMP
3✔
392
}
3✔
393

394
// CustomRecords returns the custom tlv type records that were parsed from the
395
// payload.
396
func (h *Payload) CustomRecords() record.CustomSet {
3✔
397
        return h.customRecords
3✔
398
}
3✔
399

400
// EncryptedData returns the route blinding encrypted data parsed from the
401
// onion payload.
402
func (h *Payload) EncryptedData() []byte {
×
403
        return h.encryptedData
×
404
}
×
405

406
// BlindingPoint returns the route blinding point parsed from the onion payload.
407
func (h *Payload) BlindingPoint() *btcec.PublicKey {
×
408
        return h.blindingPoint
×
409
}
×
410

411
// Metadata returns the additional data that is sent along with the
412
// payment to the payee.
413
func (h *Payload) Metadata() []byte {
3✔
414
        return h.metadata
3✔
415
}
3✔
416

417
// TotalAmtMsat returns the total amount sent to the final hop, as set by the
418
// payee.
419
func (h *Payload) TotalAmtMsat() lnwire.MilliSatoshi {
×
420
        return h.totalAmtMsat
×
421
}
×
422

423
// getMinRequiredViolation checks for unrecognized required (even) fields in the
424
// standard range and returns the lowest required type. Always returning the
425
// lowest required type allows a failure message to be deterministic.
426
func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type {
3✔
427
        var (
3✔
428
                requiredViolation        bool
3✔
429
                minRequiredViolationType tlv.Type
3✔
430
        )
3✔
431
        for t, parseResult := range set {
6✔
432
                // If a type is even but not known to us, we cannot process the
3✔
433
                // payload. We are required to understand a field that we don't
3✔
434
                // support.
3✔
435
                //
3✔
436
                // We always accept custom fields, because a higher level
3✔
437
                // application may understand them.
3✔
438
                if parseResult == nil || t%2 != 0 ||
3✔
439
                        t >= record.CustomTypeStart {
6✔
440

3✔
441
                        continue
3✔
442
                }
443

444
                if !requiredViolation || t < minRequiredViolationType {
×
445
                        minRequiredViolationType = t
×
446
                }
×
447
                requiredViolation = true
×
448
        }
449

450
        if requiredViolation {
3✔
451
                return &minRequiredViolationType
×
452
        }
×
453

454
        return nil
3✔
455
}
456

457
// ValidateBlindedRouteData performs the additional validation that is
458
// required for payments that rely on data provided in an encrypted blob to
459
// be forwarded. We enforce the blinded route's maximum expiry height so that
460
// the route "expires" and a malicious party does not have endless opportunity
461
// to probe the blinded route and compare it to updated channel policies in
462
// the network.
463
//
464
// Note that this function only validates blinded route data for forwarding
465
// nodes, as LND does not yet support receiving via a blinded route (which has
466
// different validation rules).
467
func ValidateBlindedRouteData(blindedData *record.BlindedRouteData,
468
        incomingAmount lnwire.MilliSatoshi, incomingTimelock uint32) error {
3✔
469

3✔
470
        // Bolt 04 notes that we should enforce payment constraints _if_ they
3✔
471
        // are present, so we do not fail if not provided.
3✔
472
        var err error
3✔
473
        blindedData.Constraints.WhenSome(
3✔
474
                func(c tlv.RecordT[tlv.TlvType12, record.PaymentConstraints]) {
6✔
475
                        // MUST fail if the expiry is greater than
3✔
476
                        // max_cltv_expiry.
3✔
477
                        if incomingTimelock > c.Val.MaxCltvExpiry {
3✔
478
                                err = ErrInvalidPayload{
×
479
                                        Type:      record.LockTimeOnionType,
×
480
                                        Violation: InsufficientViolation,
×
481
                                }
×
482
                        }
×
483

484
                        // MUST fail if the amount is below htlc_minimum_msat.
485
                        if incomingAmount < c.Val.HtlcMinimumMsat {
3✔
486
                                err = ErrInvalidPayload{
×
487
                                        Type:      record.AmtOnionType,
×
488
                                        Violation: InsufficientViolation,
×
489
                                }
×
490
                        }
×
491
                },
492
        )
493
        if err != nil {
3✔
494
                return err
×
495
        }
×
496

497
        // Fail if we don't understand any features (even or odd), because we
498
        // expect the features to have been set from our announcement. If the
499
        // feature vector TLV is not included, it's interpreted as an empty
500
        // vector (no validation required).
501
        // expect the features to have been set from our announcement.
502
        //
503
        // Note that we do not yet check the features that the blinded payment
504
        // is using against our own features, because there are currently no
505
        // payment-related features that they utilize other than tlv-onion,
506
        // which is implicitly supported.
507
        blindedData.Features.WhenSome(
3✔
508
                func(f tlv.RecordT[tlv.TlvType14, lnwire.FeatureVector]) {
3✔
509
                        if f.Val.UnknownFeatures() {
×
510
                                err = ErrInvalidPayload{
×
511
                                        Type:      14,
×
512
                                        Violation: IncludedViolation,
×
513
                                }
×
514
                        }
×
515
                },
516
        )
517
        if err != nil {
3✔
518
                return err
×
519
        }
×
520

521
        return nil
3✔
522
}
523

524
// ValidatePayloadWithBlinded validates a payload against the contents of
525
// its encrypted data blob.
526
func ValidatePayloadWithBlinded(isFinalHop bool,
527
        payloadParsed map[tlv.Type][]byte) error {
3✔
528

3✔
529
        // Blinded routes restrict the presence of TLVs more strictly than
3✔
530
        // regular routes, check that intermediate and final hops only have
3✔
531
        // the TLVs the spec allows them to have.
3✔
532
        allowedTLVs := map[tlv.Type]bool{
3✔
533
                record.EncryptedDataOnionType: true,
3✔
534
                record.BlindingPointOnionType: true,
3✔
535
        }
3✔
536

3✔
537
        if isFinalHop {
6✔
538
                allowedTLVs[record.AmtOnionType] = true
3✔
539
                allowedTLVs[record.LockTimeOnionType] = true
3✔
540
                allowedTLVs[record.TotalAmtMsatBlindedType] = true
3✔
541
        }
3✔
542

543
        for tlvType := range payloadParsed {
6✔
544
                if _, ok := allowedTLVs[tlvType]; ok {
6✔
545
                        continue
3✔
546
                }
547

548
                return ErrInvalidPayload{
×
549
                        Type:      tlvType,
×
550
                        Violation: IncludedViolation,
×
551
                        FinalHop:  isFinalHop,
×
552
                }
×
553
        }
554

555
        return nil
3✔
556
}
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