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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

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

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

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

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✔
236
                return deriveBlindedRouteFinalHopForwardingInfo(
×
237
                        routeData, payload, routeRole,
×
238
                )
×
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,
251
        routeRole RouteRole) (*Payload, RouteRole, error) {
×
252

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

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

×
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✔
315
                return peelBlindedPathDummyHop(
×
316
                        r, uint32(relayInfo.Val.CltvExpiryDelta), fwdAmt,
×
317
                        routeRole, nextEph,
×
318
                )
×
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✔
352
                        isDummy = r.Val.IsEqual(routerPubKey)
×
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,
366
        RouteRole, error) {
×
367

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

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

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

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) (
493
        ErrorEncrypter, lnwire.FailCode) {
×
494

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

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

514
        default:
×
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.
645
func (p *OnionProcessor) Start() error {
×
646
        log.Info("Onion processor starting")
×
647
        return p.router.Start()
×
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,
677
        blindingInfo ReconstructBlindingInfo) (Iterator, error) {
×
678

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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