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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 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:lll
×
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:lll,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) {
1,490✔
737
        return r.HopIterator, r.FailCode
1,490✔
738
}
1,490✔
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