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

lightningnetwork / lnd / 15155511119

21 May 2025 06:52AM UTC coverage: 57.389% (-11.6%) from 68.996%
15155511119

Pull #9844

github

web-flow
Merge 8658c8597 into c52a6ddeb
Pull Request #9844: Refactor Payment PR 3

346 of 493 new or added lines in 4 files covered. (70.18%)

30172 existing lines in 456 files now uncovered.

95441 of 166305 relevant lines covered (57.39%)

0.61 hits per line

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

75.81
/htlcswitch/hop/iterator.go
1
package hop
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "io"
8
        "sync"
9

10
        "github.com/btcsuite/btcd/btcec/v2"
11
        "github.com/btcsuite/btcd/chaincfg/chainhash"
12
        sphinx "github.com/lightningnetwork/lightning-onion"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
        "github.com/lightningnetwork/lnd/record"
15
        "github.com/lightningnetwork/lnd/tlv"
16
)
17

18
var (
19
        // ErrDecodeFailed is returned when we can't decode blinded data.
20
        ErrDecodeFailed = errors.New("could not decode blinded data")
21

22
        // ErrNoBlindingPoint is returned when we have not provided a blinding
23
        // point for a validated payload with encrypted data set.
24
        ErrNoBlindingPoint = errors.New("no blinding point set for validated " +
25
                "blinded hop")
26
)
27

28
// RouteRole represents the different types of roles a node can have as a
29
// recipient of a HTLC.
30
type RouteRole uint8
31

32
const (
33
        // RouteRoleCleartext represents a regular route hop.
34
        RouteRoleCleartext RouteRole = iota
35

36
        // RouteRoleIntroduction represents an introduction node in a blinded
37
        // path, characterized by a blinding point in the onion payload.
38
        RouteRoleIntroduction
39

40
        // RouteRoleRelaying represents a relaying node in a blinded path,
41
        // characterized by a blinding point in update_add_htlc.
42
        RouteRoleRelaying
43
)
44

45
// String representation of a role in a route.
46
func (h RouteRole) String() string {
×
47
        switch h {
×
48
        case RouteRoleCleartext:
×
49
                return "cleartext"
×
50

51
        case RouteRoleRelaying:
×
52
                return "blinded relay"
×
53

54
        case RouteRoleIntroduction:
×
55
                return "introduction node"
×
56

57
        default:
×
58
                return fmt.Sprintf("unknown route role: %d", h)
×
59
        }
60
}
61

62
// NewRouteRole returns the role we're playing in a route depending on the
63
// blinding points set (or not). If we are in the situation where we received
64
// blinding points in both the update add message and the payload:
65
//   - We must have had a valid update add blinding point, because we were able
66
//     to decrypt our onion to get the payload blinding point.
67
//   - We return a relaying node role, because an introduction node (by
68
//     definition) does not receive a blinding point in update add.
69
//   - We assume the sending node to be buggy (including a payload blinding
70
//     where it shouldn't), and rely on validation elsewhere to handle this.
71
func NewRouteRole(updateAddBlinding, payloadBlinding bool) RouteRole {
1✔
72
        switch {
1✔
73
        case updateAddBlinding:
1✔
74
                return RouteRoleRelaying
1✔
75

76
        case payloadBlinding:
1✔
77
                return RouteRoleIntroduction
1✔
78

79
        default:
1✔
80
                return RouteRoleCleartext
1✔
81
        }
82
}
83

84
// Iterator is an interface that abstracts away the routing information
85
// included in HTLC's which includes the entirety of the payment path of an
86
// HTLC. This interface provides two basic method which carry out: how to
87
// interpret the forwarding information encoded within the HTLC packet, and hop
88
// to encode the forwarding information for the _next_ hop.
89
type Iterator interface {
90
        // HopPayload returns the set of fields that detail exactly _how_ this
91
        // hop should forward the HTLC to the next hop.  Additionally, the
92
        // information encoded within the returned ForwardingInfo is to be used
93
        // by each hop to authenticate the information given to it by the prior
94
        // hop. The payload will also contain any additional TLV fields provided
95
        // by the sender. The role that this hop plays in the context of
96
        // route blinding (regular, introduction or relaying) is returned
97
        // whenever the payload is successfully parsed, even if we subsequently
98
        // face a validation error.
99
        HopPayload() (*Payload, RouteRole, error)
100

101
        // EncodeNextHop encodes the onion packet destined for the next hop
102
        // into the passed io.Writer.
103
        EncodeNextHop(w io.Writer) error
104

105
        // ExtractErrorEncrypter returns the ErrorEncrypter needed for this hop,
106
        // along with a failure code to signal if the decoding was successful.
107
        ExtractErrorEncrypter(extractor ErrorEncrypterExtracter,
108
                introductionNode bool) (ErrorEncrypter, lnwire.FailCode)
109
}
110

111
// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
112
// onion routing to encode the payment route  in such a way so that node might
113
// see only the next hop in the route.
114
type sphinxHopIterator struct {
115
        // ogPacket is the original packet from which the processed packet is
116
        // derived.
117
        ogPacket *sphinx.OnionPacket
118

119
        // processedPacket is the outcome of processing an onion packet. It
120
        // includes the information required to properly forward the packet to
121
        // the next hop.
122
        processedPacket *sphinx.ProcessedPacket
123

124
        // blindingKit contains the elements required to process hops that are
125
        // part of a blinded route.
126
        blindingKit BlindingKit
127

128
        // rHash holds the payment hash for this payment. This is needed for
129
        // when a new hop iterator is constructed.
130
        rHash []byte
131

132
        // router holds the router which can be used to decrypt onion payloads.
133
        // This is required for peeling of dummy hops in a blinded path where
134
        // the same node will iteratively need to unwrap the onion.
135
        router *sphinx.Router
136
}
137

138
// makeSphinxHopIterator converts a processed packet returned from a sphinx
139
// router and converts it into an hop iterator for usage in the link. A
140
// blinding kit is passed through for the link to obtain forwarding information
141
// for blinded routes.
142
func makeSphinxHopIterator(router *sphinx.Router, ogPacket *sphinx.OnionPacket,
143
        packet *sphinx.ProcessedPacket, blindingKit BlindingKit,
144
        rHash []byte) *sphinxHopIterator {
1✔
145

1✔
146
        return &sphinxHopIterator{
1✔
147
                router:          router,
1✔
148
                ogPacket:        ogPacket,
1✔
149
                processedPacket: packet,
1✔
150
                blindingKit:     blindingKit,
1✔
151
                rHash:           rHash,
1✔
152
        }
1✔
153
}
1✔
154

155
// A compile time check to ensure sphinxHopIterator implements the HopIterator
156
// interface.
157
var _ Iterator = (*sphinxHopIterator)(nil)
158

159
// Encode encodes iterator and writes it to the writer.
160
//
161
// NOTE: Part of the HopIterator interface.
162
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
1✔
163
        return r.processedPacket.NextPacket.Encode(w)
1✔
164
}
1✔
165

166
// HopPayload returns the set of fields that detail exactly _how_ this hop
167
// should forward the HTLC to the next hop.  Additionally, the information
168
// encoded within the returned ForwardingInfo is to be used by each hop to
169
// authenticate the information given to it by the prior hop. The role that
170
// this hop plays in the context of route blinding (regular, introduction or
171
// relaying) is returned whenever the payload is successfully parsed, even if
172
// we subsequently face a validation error. The payload will also contain any
173
// additional TLV fields provided by the sender.
174
//
175
// NOTE: Part of the HopIterator interface.
176
func (r *sphinxHopIterator) HopPayload() (*Payload, RouteRole, error) {
1✔
177
        switch r.processedPacket.Payload.Type {
1✔
178

179
        // If this is the legacy payload, then we'll extract the information
180
        // directly from the pre-populated ForwardingInstructions field.
UNCOV
181
        case sphinx.PayloadLegacy:
×
UNCOV
182
                fwdInst := r.processedPacket.ForwardingInstructions
×
UNCOV
183
                return NewLegacyPayload(fwdInst), RouteRoleCleartext, nil
×
184

185
        // Otherwise, if this is the TLV payload, then we'll make a new stream
186
        // to decode only what we need to make routing decisions.
187
        case sphinx.PayloadTLV:
1✔
188
                return extractTLVPayload(r)
1✔
189

190
        default:
×
191
                return nil, RouteRoleCleartext,
×
192
                        fmt.Errorf("unknown sphinx payload type: %v",
×
193
                                r.processedPacket.Payload.Type)
×
194
        }
195
}
196

197
// extractTLVPayload parses the hop payload and assumes that it uses the TLV
198
// format. It returns the parsed payload along with the RouteRole that this hop
199
// plays given the contents of the payload.
200
func extractTLVPayload(r *sphinxHopIterator) (*Payload, RouteRole, error) {
1✔
201
        isFinal := r.processedPacket.Action == sphinx.ExitNode
1✔
202

1✔
203
        // Initial payload parsing and validation
1✔
204
        payload, routeRole, recipientData, err := parseAndValidateSenderPayload(
1✔
205
                r.processedPacket.Payload.Payload, isFinal,
1✔
206
                r.blindingKit.UpdateAddBlinding.IsSome(),
1✔
207
        )
1✔
208
        if err != nil {
1✔
209
                return nil, routeRole, err
×
210
        }
×
211

212
        // If the payload contained no recipient data, then we can exit now.
213
        if !recipientData {
2✔
214
                return payload, routeRole, nil
1✔
215
        }
1✔
216

217
        return parseAndValidateRecipientData(r, payload, isFinal, routeRole)
1✔
218
}
219

220
// parseAndValidateRecipientData decrypts the payload from the recipient and
221
// then continues handling and validation based on if we are a forwarding node
222
// in this blinded path or the final destination node.
223
func parseAndValidateRecipientData(r *sphinxHopIterator, payload *Payload,
224
        isFinal bool, routeRole RouteRole) (*Payload, RouteRole, error) {
1✔
225

1✔
226
        // Decrypt and validate the blinded route data
1✔
227
        routeData, blindingPoint, err := decryptAndValidateBlindedRouteData(
1✔
228
                r, payload,
1✔
229
        )
1✔
230
        if err != nil {
2✔
231
                return nil, routeRole, err
1✔
232
        }
1✔
233

234
        // This is the final node in the blinded route.
235
        if isFinal {
2✔
236
                return deriveBlindedRouteFinalHopForwardingInfo(
1✔
237
                        routeData, payload, routeRole,
1✔
238
                )
1✔
239
        }
1✔
240

241
        // Else, we are a forwarding node in this blinded path.
242
        return deriveBlindedRouteForwardingInfo(
1✔
243
                r, routeData, payload, routeRole, blindingPoint,
1✔
244
        )
1✔
245
}
246

247
// deriveBlindedRouteFinalHopForwardingInfo extracts the PathID from the
248
// routeData and constructs the ForwardingInfo accordingly.
249
func deriveBlindedRouteFinalHopForwardingInfo(
250
        routeData *record.BlindedRouteData, payload *Payload,
251
        routeRole RouteRole) (*Payload, RouteRole, error) {
1✔
252

1✔
253
        var pathID *chainhash.Hash
1✔
254
        routeData.PathID.WhenSome(func(r tlv.RecordT[tlv.TlvType6, []byte]) {
2✔
255
                var id chainhash.Hash
1✔
256
                copy(id[:], r.Val)
1✔
257
                pathID = &id
1✔
258
        })
1✔
259
        if pathID == nil {
1✔
260
                return nil, routeRole, ErrInvalidPayload{
×
261
                        Type:      tlv.Type(6),
×
262
                        Violation: InsufficientViolation,
×
263
                }
×
264
        }
×
265

266
        payload.FwdInfo = ForwardingInfo{
1✔
267
                PathID: pathID,
1✔
268
        }
1✔
269

1✔
270
        return payload, routeRole, nil
1✔
271
}
272

273
// deriveBlindedRouteForwardingInfo uses the parsed BlindedRouteData from the
274
// recipient to derive the ForwardingInfo for the payment.
275
func deriveBlindedRouteForwardingInfo(r *sphinxHopIterator,
276
        routeData *record.BlindedRouteData, payload *Payload,
277
        routeRole RouteRole, blindingPoint *btcec.PublicKey) (*Payload,
278
        RouteRole, error) {
1✔
279

1✔
280
        relayInfo, err := routeData.RelayInfo.UnwrapOrErr(
1✔
281
                fmt.Errorf("relay info not set for non-final blinded hop"),
1✔
282
        )
1✔
283
        if err != nil {
1✔
284
                return nil, routeRole, err
×
285
        }
×
286

287
        fwdAmt, err := calculateForwardingAmount(
1✔
288
                r.blindingKit.IncomingAmount, relayInfo.Val.BaseFee,
1✔
289
                relayInfo.Val.FeeRate,
1✔
290
        )
1✔
291
        if err != nil {
1✔
292
                return nil, routeRole, err
×
293
        }
×
294

295
        nextEph, err := routeData.NextBlindingOverride.UnwrapOrFuncErr(
1✔
296
                func() (tlv.RecordT[tlv.TlvType8, *btcec.PublicKey], error) {
2✔
297
                        next, err := r.blindingKit.Processor.NextEphemeral(
1✔
298
                                blindingPoint,
1✔
299
                        )
1✔
300
                        if err != nil {
1✔
301
                                return routeData.NextBlindingOverride.Zero(),
×
302
                                        err
×
303
                        }
×
304

305
                        return tlv.NewPrimitiveRecord[tlv.TlvType8](next), nil
1✔
306
                })
307
        if err != nil {
1✔
308
                return nil, routeRole, err
×
309
        }
×
310

311
        // If the payload signals that the following hop is a dummy hop, then
312
        // we will iteratively peel the dummy hop until we reach the final
313
        // payload.
314
        if checkForDummyHop(routeData, r.router.OnionPublicKey()) {
2✔
315
                return peelBlindedPathDummyHop(
1✔
316
                        r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
1✔
317
                        routeRole, nextEph,
1✔
318
                )
1✔
319
        }
1✔
320

321
        nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
1✔
322
                fmt.Errorf("next SCID not set for non-final blinded hop"),
1✔
323
        )
1✔
324
        if err != nil {
1✔
325
                return nil, routeRole, err
×
326
        }
×
327
        payload.FwdInfo = ForwardingInfo{
1✔
328
                NextHop:         nextSCID.Val,
1✔
329
                AmountToForward: fwdAmt,
1✔
330
                OutgoingCTLV: r.blindingKit.IncomingCltv - uint32(
1✔
331
                        relayInfo.Val.CltvExpiryDelta,
1✔
332
                ),
1✔
333
                // Remap from blinding override type to blinding point type.
1✔
334
                NextBlinding: tlv.SomeRecordT(
1✔
335
                        tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType](
1✔
336
                                nextEph.Val,
1✔
337
                        ),
1✔
338
                ),
1✔
339
        }
1✔
340

1✔
341
        return payload, routeRole, nil
1✔
342
}
343

344
// checkForDummyHop returns whether the given BlindedRouteData packet indicates
345
// the presence of a dummy hop.
346
func checkForDummyHop(routeData *record.BlindedRouteData,
347
        routerPubKey *btcec.PublicKey) bool {
1✔
348

1✔
349
        var isDummy bool
1✔
350
        routeData.NextNodeID.WhenSome(
1✔
351
                func(r tlv.RecordT[tlv.TlvType4, *btcec.PublicKey]) {
2✔
352
                        isDummy = r.Val.IsEqual(routerPubKey)
1✔
353
                },
1✔
354
        )
355

356
        return isDummy
1✔
357
}
358

359
// peelBlindedPathDummyHop packages the next onion packet and then constructs
360
// a new hop iterator using our router and then proceeds to process the next
361
// packet. This can only be done for blinded route dummy hops since we expect
362
// to be the final hop on the path.
363
func peelBlindedPathDummyHop(r *sphinxHopIterator, cltvExpiryDelta uint32,
364
        fwdAmt lnwire.MilliSatoshi, routeRole RouteRole,
365
        nextEph tlv.RecordT[tlv.TlvType8, *btcec.PublicKey]) (*Payload,
366
        RouteRole, error) {
1✔
367

1✔
368
        onionPkt := r.processedPacket.NextPacket
1✔
369
        sphinxPacket, err := r.router.ReconstructOnionPacket(
1✔
370
                onionPkt, r.rHash, sphinx.WithBlindingPoint(nextEph.Val),
1✔
371
        )
1✔
372
        if err != nil {
1✔
373
                return nil, routeRole, err
×
374
        }
×
375

376
        iterator := makeSphinxHopIterator(
1✔
377
                r.router, onionPkt, sphinxPacket, BlindingKit{
1✔
378
                        Processor: r.router,
1✔
379
                        UpdateAddBlinding: tlv.SomeRecordT(
1✔
380
                                tlv.NewPrimitiveRecord[lnwire.BlindingPointTlvType]( //nolint:ll
1✔
381
                                        nextEph.Val,
1✔
382
                                ),
1✔
383
                        ),
1✔
384
                        IncomingAmount: fwdAmt,
1✔
385
                        IncomingCltv: r.blindingKit.IncomingCltv -
1✔
386
                                cltvExpiryDelta,
1✔
387
                }, r.rHash,
1✔
388
        )
1✔
389

1✔
390
        return extractTLVPayload(iterator)
1✔
391
}
392

393
// decryptAndValidateBlindedRouteData decrypts the encrypted payload from the
394
// payment recipient using a blinding key. The incoming HTLC amount and CLTV
395
// values are then verified against the policy values from the recipient.
396
func decryptAndValidateBlindedRouteData(r *sphinxHopIterator,
397
        payload *Payload) (*record.BlindedRouteData, *btcec.PublicKey, error) {
1✔
398

1✔
399
        blindingPoint, err := r.blindingKit.getBlindingPoint(
1✔
400
                payload.blindingPoint,
1✔
401
        )
1✔
402
        if err != nil {
1✔
UNCOV
403
                return nil, nil, err
×
UNCOV
404
        }
×
405

406
        decrypted, err := r.blindingKit.Processor.DecryptBlindedHopData(
1✔
407
                blindingPoint, payload.encryptedData,
1✔
408
        )
1✔
409
        if err != nil {
2✔
410
                return nil, nil, fmt.Errorf("decrypt blinded data: %w", err)
1✔
411
        }
1✔
412

413
        buf := bytes.NewBuffer(decrypted)
1✔
414
        routeData, err := record.DecodeBlindedRouteData(buf)
1✔
415
        if err != nil {
1✔
UNCOV
416
                return nil, nil, fmt.Errorf("%w: %w", ErrDecodeFailed, err)
×
UNCOV
417
        }
×
418

419
        err = ValidateBlindedRouteData(
1✔
420
                routeData, r.blindingKit.IncomingAmount,
1✔
421
                r.blindingKit.IncomingCltv,
1✔
422
        )
1✔
423
        if err != nil {
1✔
UNCOV
424
                return nil, nil, err
×
UNCOV
425
        }
×
426

427
        return routeData, blindingPoint, nil
1✔
428
}
429

430
// parseAndValidateSenderPayload parses the payload bytes received from the
431
// onion constructor (the sender) and validates that various fields have been
432
// set. It also uses the presence of a blinding key in either the
433
// update_add_htlc message or in the payload to determine the RouteRole.
434
// The RouteRole is returned even if an error is returned. The boolean return
435
// value indicates that the sender payload includes encrypted data from the
436
// recipient that should be parsed.
437
func parseAndValidateSenderPayload(payloadBytes []byte, isFinalHop,
438
        updateAddBlindingSet bool) (*Payload, RouteRole, bool, error) {
1✔
439

1✔
440
        // Extract TLVs from the packet constructor (the sender).
1✔
441
        payload, parsed, err := ParseTLVPayload(bytes.NewReader(payloadBytes))
1✔
442
        if err != nil {
1✔
443
                // If we couldn't even parse our payload then we do a
×
444
                // best-effort of determining our role in a blinded route,
×
445
                // accepting that we can't know whether we were the introduction
×
446
                // node (as the payload is not parseable).
×
447
                routeRole := RouteRoleCleartext
×
448
                if updateAddBlindingSet {
×
449
                        routeRole = RouteRoleRelaying
×
450
                }
×
451

452
                return nil, routeRole, false, err
×
453
        }
454

455
        // Now that we've parsed our payload we can determine which role we're
456
        // playing in the route.
457
        _, payloadBlinding := parsed[record.BlindingPointOnionType]
1✔
458
        routeRole := NewRouteRole(updateAddBlindingSet, payloadBlinding)
1✔
459

1✔
460
        // Validate the presence of the various payload fields we received from
1✔
461
        // the sender.
1✔
462
        err = ValidateTLVPayload(parsed, isFinalHop, updateAddBlindingSet)
1✔
463
        if err != nil {
1✔
464
                return nil, routeRole, false, err
×
465
        }
×
466

467
        // If there is no encrypted data from the receiver then return the
468
        // payload as is since the forwarding info would have been received
469
        // from the sender.
470
        if payload.encryptedData == nil {
2✔
471
                return payload, routeRole, false, nil
1✔
472
        }
1✔
473

474
        // Validate the presence of various fields in the sender payload given
475
        // that we now know that this is a hop with instructions from the
476
        // recipient.
477
        err = ValidatePayloadWithBlinded(isFinalHop, parsed)
1✔
478
        if err != nil {
1✔
479
                return payload, routeRole, true, err
×
480
        }
×
481

482
        return payload, routeRole, true, nil
1✔
483
}
484

485
// ExtractErrorEncrypter decodes and returns the ErrorEncrypter for this hop,
486
// along with a failure code to signal if the decoding was successful. The
487
// ErrorEncrypter is used to encrypt errors back to the sender in the event that
488
// a payment fails.
489
//
490
// NOTE: Part of the HopIterator interface.
491
func (r *sphinxHopIterator) ExtractErrorEncrypter(
492
        extracter ErrorEncrypterExtracter, introductionNode bool) (
493
        ErrorEncrypter, lnwire.FailCode) {
1✔
494

1✔
495
        encrypter, errCode := extracter(r.ogPacket.EphemeralKey)
1✔
496
        if errCode != lnwire.CodeNone {
1✔
497
                return nil, errCode
×
498
        }
×
499

500
        // If we're in a blinded path, wrap the error encrypter that we just
501
        // derived in a "marker" type which we'll use to know what type of
502
        // error we're handling.
503
        switch {
1✔
504
        case introductionNode:
1✔
505
                return &IntroductionErrorEncrypter{
1✔
506
                        ErrorEncrypter: encrypter,
1✔
507
                }, errCode
1✔
508

509
        case r.blindingKit.UpdateAddBlinding.IsSome():
1✔
510
                return &RelayingErrorEncrypter{
1✔
511
                        ErrorEncrypter: encrypter,
1✔
512
                }, errCode
1✔
513

514
        default:
1✔
515
                return encrypter, errCode
1✔
516
        }
517
}
518

519
// BlindingProcessor is an interface that provides the cryptographic operations
520
// required for processing blinded hops.
521
//
522
// This interface is extracted to allow more granular testing of blinded
523
// forwarding calculations.
524
type BlindingProcessor interface {
525
        // DecryptBlindedHopData decrypts a blinded blob of data using the
526
        // ephemeral key provided.
527
        DecryptBlindedHopData(ephemPub *btcec.PublicKey,
528
                encryptedData []byte) ([]byte, error)
529

530
        // NextEphemeral returns the next hop's ephemeral key, calculated
531
        // from the current ephemeral key provided.
532
        NextEphemeral(*btcec.PublicKey) (*btcec.PublicKey, error)
533
}
534

535
// BlindingKit contains the components required to extract forwarding
536
// information for hops in a blinded route.
537
type BlindingKit struct {
538
        // Processor provides the low-level cryptographic operations to
539
        // handle an encrypted blob of data in a blinded forward.
540
        Processor BlindingProcessor
541

542
        // UpdateAddBlinding holds a blinding point that was passed to the
543
        // node via update_add_htlc's TLVs.
544
        UpdateAddBlinding lnwire.BlindingPointRecord
545

546
        // IncomingCltv is the expiry of the incoming HTLC.
547
        IncomingCltv uint32
548

549
        // IncomingAmount is the amount of the incoming HTLC.
550
        IncomingAmount lnwire.MilliSatoshi
551
}
552

553
// getBlindingPoint returns either the payload or updateAddHtlc blinding point,
554
// assuming that validation that these values are appropriately set has already
555
// been handled elsewhere.
556
func (b *BlindingKit) getBlindingPoint(payloadBlinding *btcec.PublicKey) (
557
        *btcec.PublicKey, error) {
1✔
558

1✔
559
        payloadBlindingSet := payloadBlinding != nil
1✔
560
        updateBlindingSet := b.UpdateAddBlinding.IsSome()
1✔
561

1✔
562
        switch {
1✔
563
        case payloadBlindingSet:
1✔
564
                return payloadBlinding, nil
1✔
565

566
        case updateBlindingSet:
1✔
567
                pk, err := b.UpdateAddBlinding.UnwrapOrErr(
1✔
568
                        fmt.Errorf("expected update add blinding"),
1✔
569
                )
1✔
570
                if err != nil {
1✔
571
                        return nil, err
×
572
                }
×
573

574
                return pk.Val, nil
1✔
575

UNCOV
576
        default:
×
UNCOV
577
                return nil, ErrNoBlindingPoint
×
578
        }
579
}
580

581
// calculateForwardingAmount calculates the amount to forward for a blinded
582
// hop based on the incoming amount and forwarding parameters.
583
//
584
// When forwarding a payment, the fee we take is calculated, not on the
585
// incoming amount, but rather on the amount we forward. We charge fees based
586
// on our own liquidity we are forwarding downstream.
587
//
588
// With route blinding, we are NOT given the amount to forward.  This
589
// unintuitive looking formula comes from the fact that without the amount to
590
// forward, we cannot compute the fees taken directly.
591
//
592
// The amount to be forwarded can be computed as follows:
593
//
594
// amt_to_forward = incoming_amount - total_fees
595
// total_fees = base_fee + amt_to_forward*(fee_rate/1000000)
596
//
597
// Solving for amount_to_forward:
598
// amt_to_forward = incoming_amount - base_fee - (amount_to_forward * fee_rate)/1e6
599
// amt_to_forward + (amount_to_forward * fee_rate) / 1e6 = incoming_amount - base_fee
600
// amt_to_forward * 1e6 + (amount_to_forward * fee_rate) = (incoming_amount - base_fee) * 1e6
601
// amt_to_forward * (1e6 + fee_rate) = (incoming_amount - base_fee) * 1e6
602
// amt_to_forward = ((incoming_amount - base_fee) * 1e6) / (1e6 + fee_rate)
603
//
604
// From there we use a ceiling formula for integer division so that we always
605
// round up, otherwise the sender may receive slightly less than intended:
606
//
607
// ceil(a/b) = (a + b - 1)/(b).
608
//
609
//nolint:ll,dupword
610
func calculateForwardingAmount(incomingAmount, baseFee lnwire.MilliSatoshi,
611
        proportionalFee uint32) (lnwire.MilliSatoshi, error) {
1✔
612

1✔
613
        // Sanity check to prevent overflow.
1✔
614
        if incomingAmount < baseFee {
1✔
UNCOV
615
                return 0, fmt.Errorf("incoming amount: %v < base fee: %v",
×
UNCOV
616
                        incomingAmount, baseFee)
×
UNCOV
617
        }
×
618
        numerator := (uint64(incomingAmount) - uint64(baseFee)) * 1e6
1✔
619
        denominator := 1e6 + uint64(proportionalFee)
1✔
620

1✔
621
        ceiling := (numerator + denominator - 1) / denominator
1✔
622

1✔
623
        return lnwire.MilliSatoshi(ceiling), nil
1✔
624
}
625

626
// OnionProcessor is responsible for keeping all sphinx dependent parts inside
627
// and expose only decoding function. With such approach we give freedom for
628
// subsystems which wants to decode sphinx path to not be dependable from
629
// sphinx at all.
630
//
631
// NOTE: The reason for keeping decoder separated from hop iterator is too
632
// maintain the hop iterator abstraction. Without it the structures which using
633
// the hop iterator should contain sphinx router which makes their creations in
634
// tests dependent from the sphinx internal parts.
635
type OnionProcessor struct {
636
        router *sphinx.Router
637
}
638

639
// NewOnionProcessor creates new instance of decoder.
640
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
1✔
641
        return &OnionProcessor{router}
1✔
642
}
1✔
643

644
// Start spins up the onion processor's sphinx router.
645
func (p *OnionProcessor) Start() error {
1✔
646
        log.Info("Onion processor starting")
1✔
647
        return p.router.Start()
1✔
648
}
1✔
649

650
// Stop shutsdown the onion processor's sphinx router.
651
func (p *OnionProcessor) Stop() error {
1✔
652

1✔
653
        log.Info("Onion processor shutting down...")
1✔
654
        defer log.Debug("Onion processor shutdown complete")
1✔
655

1✔
656
        p.router.Stop()
1✔
657
        return nil
1✔
658
}
1✔
659

660
// ReconstructBlindingInfo contains the information required to reconstruct a
661
// blinded onion.
662
type ReconstructBlindingInfo struct {
663
        // BlindingKey is the blinding point set in UpdateAddHTLC.
664
        BlindingKey lnwire.BlindingPointRecord
665

666
        // IncomingAmt is the amount for the incoming HTLC.
667
        IncomingAmt lnwire.MilliSatoshi
668

669
        // IncomingExpiry is the expiry height of the incoming HTLC.
670
        IncomingExpiry uint32
671
}
672

673
// ReconstructHopIterator attempts to decode a valid sphinx packet from the
674
// passed io.Reader instance using the rHash as the associated data when
675
// checking the relevant MACs during the decoding process.
676
func (p *OnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte,
677
        blindingInfo ReconstructBlindingInfo) (Iterator, error) {
1✔
678

1✔
679
        onionPkt := &sphinx.OnionPacket{}
1✔
680
        if err := onionPkt.Decode(r); err != nil {
1✔
681
                return nil, err
×
682
        }
×
683

684
        var opts []sphinx.ProcessOnionOpt
1✔
685
        blindingInfo.BlindingKey.WhenSome(func(
1✔
686
                r tlv.RecordT[lnwire.BlindingPointTlvType, *btcec.PublicKey]) {
2✔
687

1✔
688
                opts = append(opts, sphinx.WithBlindingPoint(r.Val))
1✔
689
        })
1✔
690

691
        // Attempt to process the Sphinx packet. We include the payment hash of
692
        // the HTLC as it's authenticated within the Sphinx packet itself as
693
        // associated data in order to thwart attempts a replay attacks. In the
694
        // case of a replay, an attacker is *forced* to use the same payment
695
        // hash twice, thereby losing their money entirely.
696
        sphinxPacket, err := p.router.ReconstructOnionPacket(
1✔
697
                onionPkt, rHash, opts...,
1✔
698
        )
1✔
699
        if err != nil {
1✔
700
                return nil, err
×
701
        }
×
702

703
        return makeSphinxHopIterator(p.router, onionPkt, sphinxPacket,
1✔
704
                BlindingKit{
1✔
705
                        Processor:         p.router,
1✔
706
                        UpdateAddBlinding: blindingInfo.BlindingKey,
1✔
707
                        IncomingAmount:    blindingInfo.IncomingAmt,
1✔
708
                        IncomingCltv:      blindingInfo.IncomingExpiry,
1✔
709
                }, rHash,
1✔
710
        ), nil
1✔
711
}
712

713
// DecodeHopIteratorRequest encapsulates all date necessary to process an onion
714
// packet, perform sphinx replay detection, and schedule the entry for garbage
715
// collection.
716
type DecodeHopIteratorRequest struct {
717
        OnionReader    io.Reader
718
        RHash          []byte
719
        IncomingCltv   uint32
720
        IncomingAmount lnwire.MilliSatoshi
721
        BlindingPoint  lnwire.BlindingPointRecord
722
}
723

724
// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
725
// processing.
726
type DecodeHopIteratorResponse struct {
727
        HopIterator Iterator
728
        FailCode    lnwire.FailCode
729
}
730

731
// Result returns the (HopIterator, lnwire.FailCode) tuple, which should
732
// correspond to the index of a particular DecodeHopIteratorRequest.
733
//
734
// NOTE: The HopIterator should be considered invalid if the fail code is
735
// anything but lnwire.CodeNone.
736
func (r *DecodeHopIteratorResponse) Result() (Iterator, lnwire.FailCode) {
1✔
737
        return r.HopIterator, r.FailCode
1✔
738
}
1✔
739

740
// DecodeHopIterators performs batched decoding and validation of incoming
741
// sphinx packets. For the same `id`, this method will return the same iterators
742
// and failcodes upon subsequent invocations.
743
//
744
// NOTE: In order for the responses to be valid, the caller must guarantee that
745
// the presented readers and rhashes *NEVER* deviate across invocations for the
746
// same id.
747
func (p *OnionProcessor) DecodeHopIterators(id []byte,
748
        reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) {
1✔
749

1✔
750
        var (
1✔
751
                batchSize = len(reqs)
1✔
752
                onionPkts = make([]sphinx.OnionPacket, batchSize)
1✔
753
                resps     = make([]DecodeHopIteratorResponse, batchSize)
1✔
754
        )
1✔
755

1✔
756
        tx := p.router.BeginTxn(id, batchSize)
1✔
757

1✔
758
        decode := func(seqNum uint16, onionPkt *sphinx.OnionPacket,
1✔
759
                req DecodeHopIteratorRequest) lnwire.FailCode {
2✔
760

1✔
761
                err := onionPkt.Decode(req.OnionReader)
1✔
762
                switch err {
1✔
763
                case nil:
1✔
764
                        // success
765

766
                case sphinx.ErrInvalidOnionVersion:
×
767
                        return lnwire.CodeInvalidOnionVersion
×
768

769
                case sphinx.ErrInvalidOnionKey:
×
770
                        return lnwire.CodeInvalidOnionKey
×
771

772
                default:
×
773
                        log.Errorf("unable to decode onion packet: %v", err)
×
774
                        return lnwire.CodeInvalidOnionKey
×
775
                }
776

777
                var opts []sphinx.ProcessOnionOpt
1✔
778
                req.BlindingPoint.WhenSome(func(
1✔
779
                        b tlv.RecordT[lnwire.BlindingPointTlvType,
1✔
780
                                *btcec.PublicKey]) {
2✔
781

1✔
782
                        opts = append(opts, sphinx.WithBlindingPoint(
1✔
783
                                b.Val,
1✔
784
                        ))
1✔
785
                })
1✔
786
                err = tx.ProcessOnionPacket(
1✔
787
                        seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
1✔
788
                )
1✔
789
                switch err {
1✔
790
                case nil:
1✔
791
                        // success
1✔
792
                        return lnwire.CodeNone
1✔
793

794
                case sphinx.ErrInvalidOnionVersion:
×
795
                        return lnwire.CodeInvalidOnionVersion
×
796

797
                case sphinx.ErrInvalidOnionHMAC:
×
798
                        return lnwire.CodeInvalidOnionHmac
×
799

800
                case sphinx.ErrInvalidOnionKey:
×
801
                        return lnwire.CodeInvalidOnionKey
×
802

803
                default:
×
804
                        log.Errorf("unable to process onion packet: %v", err)
×
805
                        return lnwire.CodeInvalidOnionKey
×
806
                }
807
        }
808

809
        // Execute cpu-heavy onion decoding in parallel.
810
        var wg sync.WaitGroup
1✔
811
        for i := range reqs {
2✔
812
                wg.Add(1)
1✔
813
                go func(seqNum uint16) {
2✔
814
                        defer wg.Done()
1✔
815

1✔
816
                        onionPkt := &onionPkts[seqNum]
1✔
817

1✔
818
                        resps[seqNum].FailCode = decode(
1✔
819
                                seqNum, onionPkt, reqs[seqNum],
1✔
820
                        )
1✔
821
                }(uint16(i))
1✔
822
        }
823
        wg.Wait()
1✔
824

1✔
825
        // With that batch created, we will now attempt to write the shared
1✔
826
        // secrets to disk. This operation will returns the set of indices that
1✔
827
        // were detected as replays, and the computed sphinx packets for all
1✔
828
        // indices that did not fail the above loop. Only indices that are not
1✔
829
        // in the replay set should be considered valid, as they are
1✔
830
        // opportunistically computed.
1✔
831
        packets, replays, err := tx.Commit()
1✔
832
        if err != nil {
1✔
833
                log.Errorf("unable to process onion packet batch %x: %v",
×
834
                        id, err)
×
835

×
836
                // If we failed to commit the batch to the secret share log, we
×
837
                // will mark all not-yet-failed channels with a temporary
×
838
                // channel failure and exit since we cannot proceed.
×
839
                for i := range resps {
×
840
                        resp := &resps[i]
×
841

×
842
                        // Skip any indexes that already failed onion decoding.
×
843
                        if resp.FailCode != lnwire.CodeNone {
×
844
                                continue
×
845
                        }
846

847
                        log.Errorf("unable to process onion packet %x-%v",
×
848
                                id, i)
×
849
                        resp.FailCode = lnwire.CodeTemporaryChannelFailure
×
850
                }
851

852
                // TODO(conner): return real errors to caller so link can fail?
853
                return resps, err
×
854
        }
855

856
        // Otherwise, the commit was successful. Now we will post process any
857
        // remaining packets, additionally failing any that were included in the
858
        // replay set.
859
        for i := range resps {
2✔
860
                resp := &resps[i]
1✔
861

1✔
862
                // Skip any indexes that already failed onion decoding.
1✔
863
                if resp.FailCode != lnwire.CodeNone {
1✔
864
                        continue
×
865
                }
866

867
                // If this index is contained in the replay set, mark it with a
868
                // temporary channel failure error code. We infer that the
869
                // offending error was due to a replayed packet because this
870
                // index was found in the replay set.
871
                if replays.Contains(uint16(i)) {
2✔
872
                        log.Errorf("unable to process onion packet: %v",
1✔
873
                                sphinx.ErrReplayedPacket)
1✔
874

1✔
875
                        // We set FailCode to CodeInvalidOnionVersion even
1✔
876
                        // though the ephemeral key isn't the problem. We need
1✔
877
                        // to set the BADONION bit since we're sending back a
1✔
878
                        // malformed packet, but as there isn't a specific
1✔
879
                        // failure code for replays, we reuse one of the
1✔
880
                        // failure codes that has BADONION.
1✔
881
                        resp.FailCode = lnwire.CodeInvalidOnionVersion
1✔
882
                        continue
1✔
883
                }
884

885
                // Finally, construct a hop iterator from our processed sphinx
886
                // packet, simultaneously caching the original onion packet.
887
                resp.HopIterator = makeSphinxHopIterator(
1✔
888
                        p.router, &onionPkts[i], &packets[i], BlindingKit{
1✔
889
                                Processor:         p.router,
1✔
890
                                UpdateAddBlinding: reqs[i].BlindingPoint,
1✔
891
                                IncomingAmount:    reqs[i].IncomingAmount,
1✔
892
                                IncomingCltv:      reqs[i].IncomingCltv,
1✔
893
                        }, reqs[i].RHash,
1✔
894
                )
1✔
895
        }
896

897
        return resps, nil
1✔
898
}
899

900
// ExtractErrorEncrypter takes an io.Reader which should contain the onion
901
// packet as original received by a forwarding node and creates an
902
// ErrorEncrypter instance using the derived shared secret. In the case that en
903
// error occurs, a lnwire failure code detailing the parsing failure will be
904
// returned.
905
func (p *OnionProcessor) ExtractErrorEncrypter(ephemeralKey *btcec.PublicKey) (
906
        ErrorEncrypter, lnwire.FailCode) {
1✔
907

1✔
908
        onionObfuscator, err := sphinx.NewOnionErrorEncrypter(
1✔
909
                p.router, ephemeralKey,
1✔
910
        )
1✔
911
        if err != nil {
1✔
912
                switch err {
×
913
                case sphinx.ErrInvalidOnionVersion:
×
914
                        return nil, lnwire.CodeInvalidOnionVersion
×
915
                case sphinx.ErrInvalidOnionHMAC:
×
916
                        return nil, lnwire.CodeInvalidOnionHmac
×
917
                case sphinx.ErrInvalidOnionKey:
×
918
                        return nil, lnwire.CodeInvalidOnionKey
×
919
                default:
×
920
                        log.Errorf("unable to process onion packet: %v", err)
×
921
                        return nil, lnwire.CodeInvalidOnionKey
×
922
                }
923
        }
924

925
        return &SphinxErrorEncrypter{
1✔
926
                OnionErrorEncrypter: onionObfuscator,
1✔
927
                EphemeralKey:        ephemeralKey,
1✔
928
        }, lnwire.CodeNone
1✔
929
}
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