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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 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 {
3✔
72
        switch {
3✔
73
        case updateAddBlinding:
3✔
74
                return RouteRoleRelaying
3✔
75

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

79
        default:
3✔
80
                return RouteRoleCleartext
3✔
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 {
3✔
145

3✔
146
        return &sphinxHopIterator{
3✔
147
                router:          router,
3✔
148
                ogPacket:        ogPacket,
3✔
149
                processedPacket: packet,
3✔
150
                blindingKit:     blindingKit,
3✔
151
                rHash:           rHash,
3✔
152
        }
3✔
153
}
3✔
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 {
3✔
163
        return r.processedPacket.NextPacket.Encode(w)
3✔
164
}
3✔
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) {
3✔
177
        switch r.processedPacket.Payload.Type {
3✔
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:
3✔
188
                return extractTLVPayload(r)
3✔
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) {
3✔
201
        isFinal := r.processedPacket.Action == sphinx.ExitNode
3✔
202

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

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

217
        return parseAndValidateRecipientData(r, payload, isFinal, routeRole)
3✔
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) {
3✔
225

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

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

241
        // Else, we are a forwarding node in this blinded path.
242
        return deriveBlindedRouteForwardingInfo(
3✔
243
                r, routeData, payload, routeRole, blindingPoint,
3✔
244
        )
3✔
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) {
3✔
252

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

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

3✔
270
        return payload, routeRole, nil
3✔
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) {
3✔
279

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

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

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

305
                        return tlv.NewPrimitiveRecord[tlv.TlvType8](next), nil
3✔
306
                })
307
        if err != nil {
3✔
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()) {
6✔
315
                return peelBlindedPathDummyHop(
3✔
316
                        r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
3✔
317
                        routeRole, nextEph,
3✔
318
                )
3✔
319
        }
3✔
320

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

3✔
341
        return payload, routeRole, nil
3✔
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 {
3✔
348

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

356
        return isDummy
3✔
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) {
3✔
367

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

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

3✔
390
        return extractTLVPayload(iterator)
3✔
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) {
3✔
398

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

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

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

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

427
        return routeData, blindingPoint, nil
3✔
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) {
3✔
439

3✔
440
        // Extract TLVs from the packet constructor (the sender).
3✔
441
        payload, parsed, err := ParseTLVPayload(bytes.NewReader(payloadBytes))
3✔
442
        if err != nil {
3✔
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]
3✔
458
        routeRole := NewRouteRole(updateAddBlindingSet, payloadBlinding)
3✔
459

3✔
460
        // Validate the presence of the various payload fields we received from
3✔
461
        // the sender.
3✔
462
        err = ValidateTLVPayload(parsed, isFinalHop, updateAddBlindingSet)
3✔
463
        if err != nil {
3✔
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 {
6✔
471
                return payload, routeRole, false, nil
3✔
472
        }
3✔
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)
3✔
478
        if err != nil {
3✔
479
                return payload, routeRole, true, err
×
480
        }
×
481

482
        return payload, routeRole, true, nil
3✔
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) {
3✔
494

3✔
495
        encrypter, errCode := extracter(r.ogPacket.EphemeralKey)
3✔
496
        if errCode != lnwire.CodeNone {
3✔
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 {
3✔
504
        case introductionNode:
3✔
505
                return &IntroductionErrorEncrypter{
3✔
506
                        ErrorEncrypter: encrypter,
3✔
507
                }, errCode
3✔
508

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

514
        default:
3✔
515
                return encrypter, errCode
3✔
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) {
3✔
558

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

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

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

574
                return pk.Val, nil
3✔
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) {
3✔
612

3✔
613
        // Sanity check to prevent overflow.
3✔
614
        if incomingAmount < baseFee {
3✔
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
3✔
619
        denominator := 1e6 + uint64(proportionalFee)
3✔
620

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

3✔
623
        return lnwire.MilliSatoshi(ceiling), nil
3✔
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 {
3✔
641
        return &OnionProcessor{router}
3✔
642
}
3✔
643

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

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

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

3✔
656
        p.router.Stop()
3✔
657
        return nil
3✔
658
}
3✔
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) {
3✔
678

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

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

3✔
688
                opts = append(opts, sphinx.WithBlindingPoint(r.Val))
3✔
689
        })
3✔
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(
3✔
697
                onionPkt, rHash, opts...,
3✔
698
        )
3✔
699
        if err != nil {
3✔
700
                return nil, err
×
701
        }
×
702

703
        return makeSphinxHopIterator(p.router, onionPkt, sphinxPacket,
3✔
704
                BlindingKit{
3✔
705
                        Processor:         p.router,
3✔
706
                        UpdateAddBlinding: blindingInfo.BlindingKey,
3✔
707
                        IncomingAmount:    blindingInfo.IncomingAmt,
3✔
708
                        IncomingCltv:      blindingInfo.IncomingExpiry,
3✔
709
                }, rHash,
3✔
710
        ), nil
3✔
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) {
3✔
737
        return r.HopIterator, r.FailCode
3✔
738
}
3✔
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) {
3✔
749

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

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

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

3✔
761
                err := onionPkt.Decode(req.OnionReader)
3✔
762
                switch err {
3✔
763
                case nil:
3✔
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
3✔
778
                req.BlindingPoint.WhenSome(func(
3✔
779
                        b tlv.RecordT[lnwire.BlindingPointTlvType,
3✔
780
                                *btcec.PublicKey]) {
6✔
781

3✔
782
                        opts = append(opts, sphinx.WithBlindingPoint(
3✔
783
                                b.Val,
3✔
784
                        ))
3✔
785
                })
3✔
786
                err = tx.ProcessOnionPacket(
3✔
787
                        seqNum, onionPkt, req.RHash, req.IncomingCltv, opts...,
3✔
788
                )
3✔
789
                switch err {
3✔
790
                case nil:
3✔
791
                        // success
3✔
792
                        return lnwire.CodeNone
3✔
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
3✔
811
        for i := range reqs {
6✔
812
                wg.Add(1)
3✔
813
                go func(seqNum uint16) {
6✔
814
                        defer wg.Done()
3✔
815

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

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

3✔
825
        // With that batch created, we will now attempt to write the shared
3✔
826
        // secrets to disk. This operation will returns the set of indices that
3✔
827
        // were detected as replays, and the computed sphinx packets for all
3✔
828
        // indices that did not fail the above loop. Only indices that are not
3✔
829
        // in the replay set should be considered valid, as they are
3✔
830
        // opportunistically computed.
3✔
831
        packets, replays, err := tx.Commit()
3✔
832
        if err != nil {
3✔
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 {
6✔
860
                resp := &resps[i]
3✔
861

3✔
862
                // Skip any indexes that already failed onion decoding.
3✔
863
                if resp.FailCode != lnwire.CodeNone {
3✔
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)) {
6✔
872
                        log.Errorf("unable to process onion packet: %v",
3✔
873
                                sphinx.ErrReplayedPacket)
3✔
874

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

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

897
        return resps, nil
3✔
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) {
3✔
907

3✔
908
        onionObfuscator, err := sphinx.NewOnionErrorEncrypter(
3✔
909
                p.router, ephemeralKey,
3✔
910
        )
3✔
911
        if err != nil {
3✔
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{
3✔
926
                OnionErrorEncrypter: onionObfuscator,
3✔
927
                EphemeralKey:        ephemeralKey,
3✔
928
        }, lnwire.CodeNone
3✔
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