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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 hits per line

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

38.01
/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✔
UNCOV
73
        case updateAddBlinding:
×
UNCOV
74
                return RouteRoleRelaying
×
75

UNCOV
76
        case payloadBlinding:
×
UNCOV
77
                return RouteRoleIntroduction
×
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,
UNCOV
144
        rHash []byte) *sphinxHopIterator {
×
UNCOV
145

×
UNCOV
146
        return &sphinxHopIterator{
×
UNCOV
147
                router:          router,
×
UNCOV
148
                ogPacket:        ogPacket,
×
UNCOV
149
                processedPacket: packet,
×
UNCOV
150
                blindingKit:     blindingKit,
×
UNCOV
151
                rHash:           rHash,
×
UNCOV
152
        }
×
UNCOV
153
}
×
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.
UNCOV
162
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
×
UNCOV
163
        return r.processedPacket.NextPacket.Encode(w)
×
UNCOV
164
}
×
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) {
2✔
177
        switch r.processedPacket.Payload.Type {
2✔
178

179
        // If this is the legacy payload, then we'll extract the information
180
        // directly from the pre-populated ForwardingInstructions field.
181
        case sphinx.PayloadLegacy:
1✔
182
                fwdInst := r.processedPacket.ForwardingInstructions
1✔
183
                return NewLegacyPayload(fwdInst), RouteRoleCleartext, nil
1✔
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

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

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

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

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

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

UNCOV
266
        payload.FwdInfo = ForwardingInfo{
×
UNCOV
267
                PathID: pathID,
×
UNCOV
268
        }
×
UNCOV
269

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

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

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

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

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

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

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

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

356
        return isDummy
2✔
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,
UNCOV
366
        RouteRole, error) {
×
UNCOV
367

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

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

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

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

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

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

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

427
        return routeData, blindingPoint, nil
2✔
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.
UNCOV
477
        err = ValidatePayloadWithBlinded(isFinalHop, parsed)
×
UNCOV
478
        if err != nil {
×
479
                return payload, routeRole, true, err
×
480
        }
×
481

UNCOV
482
        return payload, routeRole, true, nil
×
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) (
UNCOV
493
        ErrorEncrypter, lnwire.FailCode) {
×
UNCOV
494

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

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

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

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

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

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

574
                return pk.Val, nil
4✔
575

576
        default:
1✔
577
                return nil, ErrNoBlindingPoint
1✔
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) {
5✔
612

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

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

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

644
// Start spins up the onion processor's sphinx router.
UNCOV
645
func (p *OnionProcessor) Start() error {
×
UNCOV
646
        log.Info("Onion processor starting")
×
UNCOV
647
        return p.router.Start()
×
UNCOV
648
}
×
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,
UNCOV
677
        blindingInfo ReconstructBlindingInfo) (Iterator, error) {
×
UNCOV
678

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

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

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

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

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

×
UNCOV
756
        tx := p.router.BeginTxn(id, batchSize)
×
UNCOV
757

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

×
UNCOV
761
                err := onionPkt.Decode(req.OnionReader)
×
UNCOV
762
                switch err {
×
UNCOV
763
                case nil:
×
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

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

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

×
UNCOV
816
                        onionPkt := &onionPkts[seqNum]
×
UNCOV
817

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

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

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

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

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

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

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