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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

83.08
/record/blinded_data.go
1
package record
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "io"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/lightningnetwork/lnd/lnwire"
10
        "github.com/lightningnetwork/lnd/tlv"
11
)
12

13
// AverageDummyHopPayloadSize is the size of a standard blinded path dummy hop
14
// payload. In most cases, this is larger than the other payload types and so
15
// to make sure that a sender cannot use this fact to know if a dummy hop is
16
// present or not, we'll make sure to always pad all payloads to at least this
17
// size.
18
const AverageDummyHopPayloadSize = 51
19

20
// BlindedRouteData contains the information that is included in a blinded
21
// route encrypted data blob that is created by the recipient to provide
22
// forwarding information.
23
type BlindedRouteData struct {
24
        // Padding is an optional set of bytes that a recipient can use to pad
25
        // the data so that the encrypted recipient data blobs are all the same
26
        // length.
27
        Padding tlv.OptionalRecordT[tlv.TlvType1, []byte]
28

29
        // ShortChannelID is the channel ID of the next hop.
30
        ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID]
31

32
        // NextNodeID is the node ID of the next node on the path. In the
33
        // context of blinded path payments, this is used to indicate the
34
        // presence of dummy hops that need to be peeled from the onion.
35
        NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey]
36

37
        // PathID is a secret set of bytes that the blinded path creator will
38
        // set so that they can check the value on decryption to ensure that the
39
        // path they created was used for the intended purpose.
40
        PathID tlv.OptionalRecordT[tlv.TlvType6, []byte]
41

42
        // NextBlindingOverride is a blinding point that should be switched
43
        // in for the next hop. This is used to combine two blinded paths into
44
        // one (which primarily is used in onion messaging, but in theory
45
        // could be used for payments as well).
46
        NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey]
47

48
        // RelayInfo provides the relay parameters for the hop.
49
        RelayInfo tlv.OptionalRecordT[tlv.TlvType10, PaymentRelayInfo]
50

51
        // Constraints provides the payment relay constraints for the hop.
52
        Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints]
53

54
        // Features is the set of features the payment requires.
55
        Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector]
56
}
57

58
// NewNonFinalBlindedRouteData creates the data that's provided for hops within
59
// a blinded route.
60
func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID,
61
        blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo,
62
        constraints *PaymentConstraints,
63
        features *lnwire.FeatureVector) *BlindedRouteData {
3✔
64

3✔
65
        info := &BlindedRouteData{
3✔
66
                ShortChannelID: tlv.SomeRecordT(
3✔
67
                        tlv.NewRecordT[tlv.TlvType2](chanID),
3✔
68
                ),
3✔
69
                RelayInfo: tlv.SomeRecordT(
3✔
70
                        tlv.NewRecordT[tlv.TlvType10](relayInfo),
3✔
71
                ),
3✔
72
        }
3✔
73

3✔
74
        if blindingOverride != nil {
3✔
UNCOV
75
                info.NextBlindingOverride = tlv.SomeRecordT(
×
UNCOV
76
                        tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride))
×
UNCOV
77
        }
×
78

79
        if constraints != nil {
6✔
80
                info.Constraints = tlv.SomeRecordT(
3✔
81
                        tlv.NewRecordT[tlv.TlvType12](*constraints))
3✔
82
        }
3✔
83

84
        if features != nil {
3✔
UNCOV
85
                info.Features = tlv.SomeRecordT(
×
UNCOV
86
                        tlv.NewRecordT[tlv.TlvType14](*features),
×
UNCOV
87
                )
×
UNCOV
88
        }
×
89

90
        return info
3✔
91
}
92

93
// NewFinalHopBlindedRouteData creates the data that's provided for the final
94
// hop in a blinded route.
95
func NewFinalHopBlindedRouteData(constraints *PaymentConstraints,
96
        pathID []byte) *BlindedRouteData {
3✔
97

3✔
98
        var data BlindedRouteData
3✔
99
        if pathID != nil {
6✔
100
                data.PathID = tlv.SomeRecordT(
3✔
101
                        tlv.NewPrimitiveRecord[tlv.TlvType6](pathID),
3✔
102
                )
3✔
103
        }
3✔
104

105
        if constraints != nil {
6✔
106
                data.Constraints = tlv.SomeRecordT(
3✔
107
                        tlv.NewRecordT[tlv.TlvType12](*constraints))
3✔
108
        }
3✔
109

110
        return &data
3✔
111
}
112

113
// NewDummyHopRouteData creates the data that's provided for any hop preceding
114
// a dummy hop. The presence of such a payload indicates to the reader that
115
// they are the intended recipient and should peel the remainder of the onion.
116
func NewDummyHopRouteData(ourPubKey *btcec.PublicKey,
117
        relayInfo PaymentRelayInfo,
118
        constraints PaymentConstraints) *BlindedRouteData {
3✔
119

3✔
120
        return &BlindedRouteData{
3✔
121
                NextNodeID: tlv.SomeRecordT(
3✔
122
                        tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey),
3✔
123
                ),
3✔
124
                RelayInfo: tlv.SomeRecordT(
3✔
125
                        tlv.NewRecordT[tlv.TlvType10](relayInfo),
3✔
126
                ),
3✔
127
                Constraints: tlv.SomeRecordT(
3✔
128
                        tlv.NewRecordT[tlv.TlvType12](constraints),
3✔
129
                ),
3✔
130
        }
3✔
131
}
3✔
132

133
// DecodeBlindedRouteData decodes the data provided within a blinded route.
134
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
3✔
135
        var (
3✔
136
                d BlindedRouteData
3✔
137

3✔
138
                padding          = d.Padding.Zero()
3✔
139
                scid             = d.ShortChannelID.Zero()
3✔
140
                nextNodeID       = d.NextNodeID.Zero()
3✔
141
                pathID           = d.PathID.Zero()
3✔
142
                blindingOverride = d.NextBlindingOverride.Zero()
3✔
143
                relayInfo        = d.RelayInfo.Zero()
3✔
144
                constraints      = d.Constraints.Zero()
3✔
145
                features         = d.Features.Zero()
3✔
146
        )
3✔
147

3✔
148
        var tlvRecords lnwire.ExtraOpaqueData
3✔
149
        if err := lnwire.ReadElements(r, &tlvRecords); err != nil {
3✔
150
                return nil, err
×
151
        }
×
152

153
        typeMap, err := tlvRecords.ExtractRecords(
3✔
154
                &padding, &scid, &nextNodeID, &pathID, &blindingOverride,
3✔
155
                &relayInfo, &constraints, &features,
3✔
156
        )
3✔
157
        if err != nil {
3✔
UNCOV
158
                return nil, err
×
UNCOV
159
        }
×
160

161
        val, ok := typeMap[d.Padding.TlvType()]
3✔
162
        if ok && val == nil {
6✔
163
                d.Padding = tlv.SomeRecordT(padding)
3✔
164
        }
3✔
165

166
        if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil {
6✔
167
                d.ShortChannelID = tlv.SomeRecordT(scid)
3✔
168
        }
3✔
169

170
        if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil {
6✔
171
                d.NextNodeID = tlv.SomeRecordT(nextNodeID)
3✔
172
        }
3✔
173

174
        if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil {
6✔
175
                d.PathID = tlv.SomeRecordT(pathID)
3✔
176
        }
3✔
177

178
        val, ok = typeMap[d.NextBlindingOverride.TlvType()]
3✔
179
        if ok && val == nil {
3✔
UNCOV
180
                d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
×
UNCOV
181
        }
×
182

183
        if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil {
6✔
184
                d.RelayInfo = tlv.SomeRecordT(relayInfo)
3✔
185
        }
3✔
186

187
        if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil {
6✔
188
                d.Constraints = tlv.SomeRecordT(constraints)
3✔
189
        }
3✔
190

191
        if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil {
3✔
UNCOV
192
                d.Features = tlv.SomeRecordT(features)
×
UNCOV
193
        }
×
194

195
        return &d, nil
3✔
196
}
197

198
// EncodeBlindedRouteData encodes the blinded route data provided.
199
func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
3✔
200
        var (
3✔
201
                e               lnwire.ExtraOpaqueData
3✔
202
                recordProducers = make([]tlv.RecordProducer, 0, 5)
3✔
203
        )
3✔
204

3✔
205
        data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) {
6✔
206
                recordProducers = append(recordProducers, &p)
3✔
207
        })
3✔
208

209
        data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2,
3✔
210
                lnwire.ShortChannelID]) {
6✔
211

3✔
212
                recordProducers = append(recordProducers, &scid)
3✔
213
        })
3✔
214

215
        data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4,
3✔
216
                *btcec.PublicKey]) {
6✔
217

3✔
218
                recordProducers = append(recordProducers, &f)
3✔
219
        })
3✔
220

221
        data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) {
6✔
222
                recordProducers = append(recordProducers, &pathID)
3✔
223
        })
3✔
224

225
        data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
3✔
226
                *btcec.PublicKey]) {
3✔
UNCOV
227

×
UNCOV
228
                recordProducers = append(recordProducers, &pk)
×
UNCOV
229
        })
×
230

231
        data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10,
3✔
232
                PaymentRelayInfo]) {
6✔
233

3✔
234
                recordProducers = append(recordProducers, &r)
3✔
235
        })
3✔
236

237
        data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12,
3✔
238
                PaymentConstraints]) {
6✔
239

3✔
240
                recordProducers = append(recordProducers, &cs)
3✔
241
        })
3✔
242

243
        data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14,
3✔
244
                lnwire.FeatureVector]) {
3✔
UNCOV
245

×
UNCOV
246
                recordProducers = append(recordProducers, &f)
×
UNCOV
247
        })
×
248

249
        if err := e.PackRecords(recordProducers...); err != nil {
3✔
250
                return nil, err
×
251
        }
×
252

253
        return e[:], nil
3✔
254
}
255

256
// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field.
257
// Callers should be aware that the total payload size will change by more than
258
// "n" since the "n" bytes will be prefixed by BigSize type and length fields.
259
// Callers may need to call PadBy iteratively until each encrypted data packet
260
// is the same size and so each call will overwrite the Padding record.
261
// Note that calling PadBy with an n value of 0 will still result in a zero
262
// length TLV entry being added.
263
func (b *BlindedRouteData) PadBy(n int) {
3✔
264
        b.Padding = tlv.SomeRecordT(
3✔
265
                tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)),
3✔
266
        )
3✔
267
}
3✔
268

269
// PaymentRelayInfo describes the relay policy for a blinded path.
270
type PaymentRelayInfo struct {
271
        // CltvExpiryDelta is the expiry delta for the payment.
272
        CltvExpiryDelta uint16
273

274
        // FeeRate is the fee rate that will be charged per millionth of a
275
        // satoshi.
276
        FeeRate uint32
277

278
        // BaseFee is the per-htlc fee charged in milli-satoshis.
279
        BaseFee lnwire.MilliSatoshi
280
}
281

282
// Record creates a tlv.Record that encodes the payment relay (type 10) type for
283
// an encrypted blob payload.
284
func (i *PaymentRelayInfo) Record() tlv.Record {
3✔
285
        return tlv.MakeDynamicRecord(
3✔
286
                10, &i, func() uint64 {
6✔
287
                        // uint16 + uint32 + tuint32
3✔
288
                        return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee))
3✔
289
                }, encodePaymentRelay, decodePaymentRelay,
3✔
290
        )
291
}
292

293
func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error {
3✔
294
        if t, ok := val.(**PaymentRelayInfo); ok {
6✔
295
                relayInfo := *t
3✔
296

3✔
297
                // Just write our first 6 bytes directly.
3✔
298
                binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta)
3✔
299
                binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate)
3✔
300
                if _, err := w.Write(buf[0:6]); err != nil {
3✔
301
                        return err
×
302
                }
×
303

304
                baseFee := uint32(relayInfo.BaseFee)
3✔
305

3✔
306
                // We can safely reuse buf here because we overwrite its
3✔
307
                // contents.
3✔
308
                return tlv.ETUint32(w, &baseFee, buf)
3✔
309
        }
310

311
        return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo")
×
312
}
313

314
func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte,
315
        l uint64) error {
3✔
316

3✔
317
        if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 {
6✔
318
                scratch := make([]byte, l)
3✔
319

3✔
320
                n, err := io.ReadFull(r, scratch)
3✔
321
                if err != nil {
3✔
322
                        return err
×
323
                }
×
324

325
                // We expect at least 6 bytes, because we have 2 bytes for
326
                // cltv delta and 4 bytes for fee rate.
327
                if n < 6 {
3✔
328
                        return tlv.NewTypeForDecodingErr(val,
×
329
                                "*hop.paymentRelayInfo", uint64(n), 6)
×
330
                }
×
331

332
                relayInfo := *t
3✔
333

3✔
334
                relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16(
3✔
335
                        scratch[0:2],
3✔
336
                )
3✔
337
                relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6])
3✔
338

3✔
339
                // To be able to re-use the DTUint32 function we create a
3✔
340
                // buffer with just the bytes holding the variable length u32.
3✔
341
                // If the base fee is zero, this will be an empty buffer, which
3✔
342
                // is okay.
3✔
343
                b := bytes.NewBuffer(scratch[6:])
3✔
344

3✔
345
                var baseFee uint32
3✔
346
                err = tlv.DTUint32(b, &baseFee, buf, l-6)
3✔
347
                if err != nil {
3✔
348
                        return err
×
349
                }
×
350

351
                relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee)
3✔
352

3✔
353
                return nil
3✔
354
        }
355

356
        return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10)
×
357
}
358

359
// PaymentConstraints is a set of restrictions on a payment.
360
type PaymentConstraints struct {
361
        // MaxCltvExpiry is the maximum expiry height for the payment.
362
        MaxCltvExpiry uint32
363

364
        // HtlcMinimumMsat is the minimum htlc size for the payment.
365
        HtlcMinimumMsat lnwire.MilliSatoshi
366
}
367

368
func (p *PaymentConstraints) Record() tlv.Record {
3✔
369
        return tlv.MakeDynamicRecord(
3✔
370
                12, &p, func() uint64 {
6✔
371
                        // uint32 + tuint64.
3✔
372
                        return 4 + tlv.SizeTUint64(uint64(
3✔
373
                                p.HtlcMinimumMsat,
3✔
374
                        ))
3✔
375
                },
3✔
376
                encodePaymentConstraints, decodePaymentConstraints,
377
        )
378
}
379

380
func encodePaymentConstraints(w io.Writer, val interface{},
381
        buf *[8]byte) error {
3✔
382

3✔
383
        if c, ok := val.(**PaymentConstraints); ok {
6✔
384
                constraints := *c
3✔
385

3✔
386
                binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry)
3✔
387
                if _, err := w.Write(buf[:4]); err != nil {
3✔
388
                        return err
×
389
                }
×
390

391
                // We can safely re-use buf here because we overwrite its
392
                // contents.
393
                htlcMsat := uint64(constraints.HtlcMinimumMsat)
3✔
394

3✔
395
                return tlv.ETUint64(w, &htlcMsat, buf)
3✔
396
        }
397

398
        return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints")
×
399
}
400

401
func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte,
402
        l uint64) error {
3✔
403

3✔
404
        if c, ok := val.(**PaymentConstraints); ok && l <= 12 {
6✔
405
                scratch := make([]byte, l)
3✔
406

3✔
407
                n, err := io.ReadFull(r, scratch)
3✔
408
                if err != nil {
3✔
409
                        return err
×
410
                }
×
411

412
                // We expect at least 4 bytes for our uint32.
413
                if n < 4 {
3✔
414
                        return tlv.NewTypeForDecodingErr(val,
×
415
                                "*paymentConstraints", uint64(n), 4)
×
416
                }
×
417

418
                payConstraints := *c
3✔
419

3✔
420
                payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32(
3✔
421
                        scratch[:4],
3✔
422
                )
3✔
423

3✔
424
                // This could be empty if our minimum is zero, that's okay.
3✔
425
                var (
3✔
426
                        b       = bytes.NewBuffer(scratch[4:])
3✔
427
                        minHtlc uint64
3✔
428
                )
3✔
429

3✔
430
                err = tlv.DTUint64(b, &minHtlc, buf, l-4)
3✔
431
                if err != nil {
3✔
432
                        return err
×
433
                }
×
434
                payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc)
3✔
435

3✔
436
                return nil
3✔
437
        }
438

439
        return tlv.NewTypeForDecodingErr(val, "**PaymentConstraints", l, l)
×
440
}
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