• 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

80.1
/routing/blinding.go
1
package routing
2

3
import (
4
        "errors"
5
        "fmt"
6

7
        "github.com/btcsuite/btcd/btcec/v2"
8
        sphinx "github.com/lightningnetwork/lightning-onion"
9
        "github.com/lightningnetwork/lnd/channeldb/models"
10
        "github.com/lightningnetwork/lnd/fn"
11
        "github.com/lightningnetwork/lnd/lnwire"
12
        "github.com/lightningnetwork/lnd/routing/route"
13
)
14

15
var (
16
        // ErrNoBlindedPath is returned when the blinded path in a blinded
17
        // payment is missing.
18
        ErrNoBlindedPath = errors.New("blinded path required")
19

20
        // ErrInsufficientBlindedHops is returned when a blinded path does
21
        // not have enough blinded hops.
22
        ErrInsufficientBlindedHops = errors.New("blinded path requires " +
23
                "at least one hop")
24

25
        // ErrHTLCRestrictions is returned when a blinded path has invalid
26
        // HTLC maximum and minimum values.
27
        ErrHTLCRestrictions = errors.New("invalid htlc minimum and maximum")
28
)
29

30
// BlindedPaymentPathSet groups the data we need to handle sending to a set of
31
// blinded paths provided by the recipient of a payment.
32
//
33
// NOTE: for now this only holds a single BlindedPayment. By the end of the PR
34
// series, it will handle multiple paths.
35
type BlindedPaymentPathSet struct {
36
        // paths is the set of blinded payment paths for a single payment.
37
        // NOTE: For now this will always only have a single entry. By the end
38
        // of this PR, it can hold multiple.
39
        paths []*BlindedPayment
40

41
        // targetPubKey is the ephemeral node pub key that we will inject into
42
        // each path as the last hop. This is only for the sake of path finding.
43
        // Once the path has been found, the original destination pub key is
44
        // used again. In the edge case where there is only a single hop in the
45
        // path (the introduction node is the destination node), then this will
46
        // just be the introduction node's real public key.
47
        targetPubKey *btcec.PublicKey
48

49
        // features is the set of relay features available for the payment.
50
        // This is extracted from the set of blinded payment paths. At the
51
        // moment we require that all paths for the same payment have the
52
        // same feature set.
53
        features *lnwire.FeatureVector
54

55
        // finalCLTV is the final hop's expiry delta of _any_ path in the set.
56
        // For any multi-hop path, the final CLTV delta should be seen as zero
57
        // since the final hop's final CLTV delta is accounted for in the
58
        // accumulated path policy values. The only edge case is for when the
59
        // final hop in the path is also the introduction node in which case
60
        // that path's FinalCLTV must be the non-zero min CLTV of the final hop
61
        // so that it is accounted for in path finding. For this reason, if
62
        // we have any single path in the set with only one hop, then we throw
63
        // away all the other paths. This should be fine to do since if there is
64
        // a path where the intro node is also the destination node, then there
65
        // isn't any need to try any other longer blinded path. In other words,
66
        // if this value is non-zero, then there is only one path in this
67
        // blinded path set and that path only has a single hop: the
68
        // introduction node.
69
        finalCLTV uint16
70
}
71

72
// NewBlindedPaymentPathSet constructs a new BlindedPaymentPathSet from a set of
73
// BlindedPayments.
74
func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet,
75
        error) {
9✔
76

9✔
77
        if len(paths) == 0 {
9✔
78
                return nil, ErrNoBlindedPath
×
79
        }
×
80

81
        // For now, we assert that all the paths have the same set of features.
82
        features := paths[0].Features
9✔
83
        noFeatures := features == nil || features.IsEmpty()
9✔
84
        for i := 1; i < len(paths); i++ {
9✔
UNCOV
85
                noFeats := paths[i].Features == nil ||
×
UNCOV
86
                        paths[i].Features.IsEmpty()
×
UNCOV
87

×
UNCOV
88
                if noFeatures && !noFeats {
×
89
                        return nil, fmt.Errorf("all blinded paths must have " +
×
90
                                "the same set of features")
×
91
                }
×
92

UNCOV
93
                if noFeatures {
×
UNCOV
94
                        continue
×
95
                }
96

97
                if !features.RawFeatureVector.Equals(
×
98
                        paths[i].Features.RawFeatureVector,
×
99
                ) {
×
100

×
101
                        return nil, fmt.Errorf("all blinded paths must have " +
×
102
                                "the same set of features")
×
103
                }
×
104
        }
105

106
        // Derive an ephemeral target priv key that will be injected into each
107
        // blinded path final hop.
108
        targetPriv, err := btcec.NewPrivateKey()
9✔
109
        if err != nil {
9✔
110
                return nil, err
×
111
        }
×
112
        targetPub := targetPriv.PubKey()
9✔
113

9✔
114
        var (
9✔
115
                pathSet        = paths
9✔
116
                finalCLTVDelta uint16
9✔
117
        )
9✔
118
        // If any provided blinded path only has a single hop (ie, the
9✔
119
        // destination node is also the introduction node), then we discard all
9✔
120
        // other paths since we know the real pub key of the destination node.
9✔
121
        // We also then set the final CLTV delta to the path's delta since
9✔
122
        // there are no other edge hints that will account for it. For a single
9✔
123
        // hop path, there is also no need for the pseudo target pub key
9✔
124
        // replacement, so our target pub key in this case just remains the
9✔
125
        // real introduction node ID.
9✔
126
        for _, path := range paths {
18✔
127
                if len(path.BlindedPath.BlindedHops) != 1 {
14✔
128
                        continue
5✔
129
                }
130

131
                pathSet = []*BlindedPayment{path}
4✔
132
                finalCLTVDelta = path.CltvExpiryDelta
4✔
133
                targetPub = path.BlindedPath.IntroductionPoint
4✔
134

4✔
135
                break
4✔
136
        }
137

138
        return &BlindedPaymentPathSet{
9✔
139
                paths:        pathSet,
9✔
140
                targetPubKey: targetPub,
9✔
141
                features:     features,
9✔
142
                finalCLTV:    finalCLTVDelta,
9✔
143
        }, nil
9✔
144
}
145

146
// TargetPubKey returns the public key to be used as the destination node's
147
// public key during pathfinding.
148
func (s *BlindedPaymentPathSet) TargetPubKey() *btcec.PublicKey {
8✔
149
        return s.targetPubKey
8✔
150
}
8✔
151

152
// Features returns the set of relay features available for the payment.
UNCOV
153
func (s *BlindedPaymentPathSet) Features() *lnwire.FeatureVector {
×
UNCOV
154
        return s.features
×
UNCOV
155
}
×
156

157
// IntroNodeOnlyPath can be called if it is expected that the path set only
158
// contains a single payment path which itself only has one hop. It errors if
159
// this is not the case.
UNCOV
160
func (s *BlindedPaymentPathSet) IntroNodeOnlyPath() (*BlindedPayment, error) {
×
UNCOV
161
        if len(s.paths) != 1 {
×
162
                return nil, fmt.Errorf("expected only a single path in the "+
×
163
                        "blinded payment set, got %d", len(s.paths))
×
164
        }
×
165

UNCOV
166
        if len(s.paths[0].BlindedPath.BlindedHops) > 1 {
×
167
                return nil, fmt.Errorf("an intro node only path cannot have " +
×
168
                        "more than one hop")
×
169
        }
×
170

UNCOV
171
        return s.paths[0], nil
×
172
}
173

174
// IsIntroNode returns true if the given vertex is an introduction node for one
175
// of the paths in the blinded payment path set.
176
func (s *BlindedPaymentPathSet) IsIntroNode(source route.Vertex) bool {
6✔
177
        for _, path := range s.paths {
12✔
178
                introVertex := route.NewVertex(
6✔
179
                        path.BlindedPath.IntroductionPoint,
6✔
180
                )
6✔
181
                if source == introVertex {
7✔
182
                        return true
1✔
183
                }
1✔
184
        }
185

186
        return false
5✔
187
}
188

189
// FinalCLTVDelta is the minimum CLTV delta to use for the final hop on the
190
// route. In most cases this will return zero since the value is accounted for
191
// in the path's accumulated CLTVExpiryDelta. Only in the edge case of the path
192
// set only including a single path which only includes an introduction node
193
// will this return a non-zero value.
194
func (s *BlindedPaymentPathSet) FinalCLTVDelta() uint16 {
4✔
195
        return s.finalCLTV
4✔
196
}
4✔
197

198
// LargestLastHopPayloadPath returns the BlindedPayment in the set that has the
199
// largest last-hop payload. This is to be used for onion size estimation in
200
// path finding.
201
func (s *BlindedPaymentPathSet) LargestLastHopPayloadPath() *BlindedPayment {
4✔
202
        var (
4✔
203
                largestPath *BlindedPayment
4✔
204
                currentMax  int
4✔
205
        )
4✔
206
        for _, path := range s.paths {
8✔
207
                numHops := len(path.BlindedPath.BlindedHops)
4✔
208
                lastHop := path.BlindedPath.BlindedHops[numHops-1]
4✔
209

4✔
210
                if len(lastHop.CipherText) > currentMax {
8✔
211
                        largestPath = path
4✔
212
                }
4✔
213
        }
214

215
        return largestPath
4✔
216
}
217

218
// ToRouteHints converts the blinded path payment set into a RouteHints map so
219
// that the blinded payment paths can be treated like route hints throughout the
220
// code base.
221
func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
3✔
222
        hints := make(RouteHints)
3✔
223

3✔
224
        for _, path := range s.paths {
6✔
225
                pathHints, err := path.toRouteHints(fn.Some(s.targetPubKey))
3✔
226
                if err != nil {
3✔
227
                        return nil, err
×
228
                }
×
229

230
                for from, edges := range pathHints {
4✔
231
                        hints[from] = append(hints[from], edges...)
1✔
232
                }
1✔
233
        }
234

235
        if len(hints) == 0 {
5✔
236
                return nil, nil
2✔
237
        }
2✔
238

239
        return hints, nil
1✔
240
}
241

242
// BlindedPayment provides the path and payment parameters required to send a
243
// payment along a blinded path.
244
type BlindedPayment struct {
245
        // BlindedPath contains the unblinded introduction point and blinded
246
        // hops for the blinded section of the payment.
247
        BlindedPath *sphinx.BlindedPath
248

249
        // BaseFee is the total base fee to be paid for payments made over the
250
        // blinded path.
251
        BaseFee uint32
252

253
        // ProportionalFeeRate is the aggregated proportional fee rate for
254
        // payments made over the blinded path.
255
        ProportionalFeeRate uint32
256

257
        // CltvExpiryDelta is the total expiry delta for the blinded path. This
258
        // field includes the CLTV for the blinded hops *and* the final cltv
259
        // delta for the receiver.
260
        CltvExpiryDelta uint16
261

262
        // HtlcMinimum is the highest HLTC minimum supported along the blinded
263
        // path (while some hops may have lower values, we're effectively
264
        // bounded by the highest minimum).
265
        HtlcMinimum uint64
266

267
        // HtlcMaximum is the lowest HTLC maximum supported along the blinded
268
        // path (while some hops may have higher values, we're effectively
269
        // bounded by the lowest maximum).
270
        HtlcMaximum uint64
271

272
        // Features is the set of relay features available for the payment.
273
        Features *lnwire.FeatureVector
274
}
275

276
// Validate performs validation on a blinded payment.
277
func (b *BlindedPayment) Validate() error {
5✔
278
        if b.BlindedPath == nil {
6✔
279
                return ErrNoBlindedPath
1✔
280
        }
1✔
281

282
        // The sphinx library inserts the introduction node as the first hop,
283
        // so we expect at least one hop.
284
        if len(b.BlindedPath.BlindedHops) < 1 {
5✔
285
                return fmt.Errorf("%w got: %v", ErrInsufficientBlindedHops,
1✔
286
                        len(b.BlindedPath.BlindedHops))
1✔
287
        }
1✔
288

289
        if b.HtlcMaximum < b.HtlcMinimum {
4✔
290
                return fmt.Errorf("%w: %v < %v", ErrHTLCRestrictions,
1✔
291
                        b.HtlcMaximum, b.HtlcMinimum)
1✔
292
        }
1✔
293

294
        return nil
2✔
295
}
296

297
// toRouteHints produces a set of chained route hints that represent a blinded
298
// path. In the case of a single hop blinded route (which is paying directly
299
// to the introduction point), no hints will be returned. In this case callers
300
// *must* account for the blinded route's CLTV delta elsewhere (as this is
301
// effectively the final_cltv_delta for the receiving introduction node). In
302
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
303
// hints (both for intermediate hops and the final_cltv_delta for the receiving
304
// node). The pseudoTarget, if provided,  will be used to override the pub key
305
// of the destination node in the path.
306
func (b *BlindedPayment) toRouteHints(
307
        pseudoTarget fn.Option[*btcec.PublicKey]) (RouteHints, error) {
6✔
308

6✔
309
        // If we just have a single hop in our blinded route, it just contains
6✔
310
        // an introduction node (this is a valid path according to the spec).
6✔
311
        // Since we have the un-blinded node ID for the introduction node, we
6✔
312
        // don't need to add any route hints.
6✔
313
        if len(b.BlindedPath.BlindedHops) == 1 {
9✔
314
                return nil, nil
3✔
315
        }
3✔
316

317
        hintCount := len(b.BlindedPath.BlindedHops) - 1
3✔
318
        hints := make(
3✔
319
                RouteHints, hintCount,
3✔
320
        )
3✔
321

3✔
322
        // Start at the unblinded introduction node, because our pathfinding
3✔
323
        // will be able to locate this point in the graph.
3✔
324
        fromNode := route.NewVertex(b.BlindedPath.IntroductionPoint)
3✔
325

3✔
326
        features := lnwire.EmptyFeatureVector()
3✔
327
        if b.Features != nil {
5✔
328
                features = b.Features.Clone()
2✔
329
        }
2✔
330

331
        // Use the total aggregate relay parameters for the entire blinded
332
        // route as the policy for the hint from our introduction node. This
333
        // will ensure that pathfinding provides sufficient fees/delay for the
334
        // blinded portion to the introduction node.
335
        firstBlindedHop := b.BlindedPath.BlindedHops[1].BlindedNodePub
3✔
336
        edgePolicy := &models.CachedEdgePolicy{
3✔
337
                TimeLockDelta: b.CltvExpiryDelta,
3✔
338
                MinHTLC:       lnwire.MilliSatoshi(b.HtlcMinimum),
3✔
339
                MaxHTLC:       lnwire.MilliSatoshi(b.HtlcMaximum),
3✔
340
                FeeBaseMSat:   lnwire.MilliSatoshi(b.BaseFee),
3✔
341
                FeeProportionalMillionths: lnwire.MilliSatoshi(
3✔
342
                        b.ProportionalFeeRate,
3✔
343
                ),
3✔
344
                ToNodePubKey: func() route.Vertex {
5✔
345
                        return route.NewVertex(
2✔
346
                                // The first node in this slice is
2✔
347
                                // the introduction node, so we start
2✔
348
                                // at index 1 to get the first blinded
2✔
349
                                // relaying node.
2✔
350
                                firstBlindedHop,
2✔
351
                        )
2✔
352
                },
2✔
353
                ToNodeFeatures: features,
354
        }
355

356
        lastEdge, err := NewBlindedEdge(edgePolicy, b, 0)
3✔
357
        if err != nil {
3✔
358
                return nil, err
×
359
        }
×
360

361
        hints[fromNode] = []AdditionalEdge{lastEdge}
3✔
362

3✔
363
        // Start at an offset of 1 because the first node in our blinded hops
3✔
364
        // is the introduction node and terminate at the second-last node
3✔
365
        // because we're dealing with hops as pairs.
3✔
366
        for i := 1; i < hintCount; i++ {
5✔
367
                // Set our origin node to the current
2✔
368
                fromNode = route.NewVertex(
2✔
369
                        b.BlindedPath.BlindedHops[i].BlindedNodePub,
2✔
370
                )
2✔
371

2✔
372
                // Create a hint which has no fee or cltv delta. We
2✔
373
                // specifically want zero values here because our relay
2✔
374
                // parameters are expressed in encrypted blobs rather than the
2✔
375
                // route itself for blinded routes.
2✔
376
                nextHopIdx := i + 1
2✔
377
                nextNode := route.NewVertex(
2✔
378
                        b.BlindedPath.BlindedHops[nextHopIdx].BlindedNodePub,
2✔
379
                )
2✔
380

2✔
381
                edgePolicy := &models.CachedEdgePolicy{
2✔
382
                        ToNodePubKey: func() route.Vertex {
4✔
383
                                return nextNode
2✔
384
                        },
2✔
385
                        ToNodeFeatures: features,
386
                }
387

388
                lastEdge, err = NewBlindedEdge(edgePolicy, b, i)
2✔
389
                if err != nil {
2✔
390
                        return nil, err
×
391
                }
×
392

393
                hints[fromNode] = []AdditionalEdge{lastEdge}
2✔
394
        }
395

396
        pseudoTarget.WhenSome(func(key *btcec.PublicKey) {
4✔
397
                // For the very last hop on the path, switch out the ToNodePub
1✔
398
                // for the pseudo target pub key.
1✔
399
                lastEdge.policy.ToNodePubKey = func() route.Vertex {
1✔
UNCOV
400
                        return route.NewVertex(key)
×
UNCOV
401
                }
×
402

403
                // Then override the final hint with this updated edge.
404
                hints[fromNode] = []AdditionalEdge{lastEdge}
1✔
405
        })
406

407
        return hints, nil
3✔
408
}
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