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

lightningnetwork / lnd / 18914042383

29 Oct 2025 03:53PM UTC coverage: 54.594%. First build
18914042383

Pull #10089

github

web-flow
Merge 5a7e61d97 into e8a486fa6
Pull Request #10089: Onion message forwarding

93 of 590 new or added lines in 10 files covered. (15.76%)

110416 of 202249 relevant lines covered (54.59%)

21682.71 hits per line

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

83.33
/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 {
25✔
64

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

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

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

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

90
        return info
25✔
91
}
92

93
// NewNonFinalBlindedRouteData creates the data that's provided for hops within
94
// a blinded route.
95
func NewNonFinalBlindedRouteDataOnionMessage(nextNodeID *btcec.PublicKey,
96
        blindingOverride *btcec.PublicKey, constraints *PaymentConstraints,
NEW
97
        features *lnwire.FeatureVector) *BlindedRouteData {
×
NEW
98

×
NEW
99
        info := &BlindedRouteData{
×
NEW
100
                NextNodeID: tlv.SomeRecordT(
×
NEW
101
                        tlv.NewPrimitiveRecord[tlv.TlvType4](nextNodeID),
×
NEW
102
                ),
×
NEW
103
        }
×
NEW
104

×
NEW
105
        if blindingOverride != nil {
×
NEW
106
                info.NextBlindingOverride = tlv.SomeRecordT(
×
NEW
107
                        tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride))
×
NEW
108
        }
×
109

NEW
110
        if constraints != nil {
×
NEW
111
                info.Constraints = tlv.SomeRecordT(
×
NEW
112
                        tlv.NewRecordT[tlv.TlvType12](*constraints))
×
NEW
113
        }
×
114

NEW
115
        if features != nil {
×
NEW
116
                info.Features = tlv.SomeRecordT(
×
NEW
117
                        tlv.NewRecordT[tlv.TlvType14](*features),
×
NEW
118
                )
×
NEW
119
        }
×
120

NEW
121
        return info
×
122
}
123

124
// NewFinalHopBlindedRouteData creates the data that's provided for the final
125
// hop in a blinded route.
126
func NewFinalHopBlindedRouteData(constraints *PaymentConstraints,
127
        pathID []byte) *BlindedRouteData {
7✔
128

7✔
129
        var data BlindedRouteData
7✔
130
        if pathID != nil {
13✔
131
                data.PathID = tlv.SomeRecordT(
6✔
132
                        tlv.NewPrimitiveRecord[tlv.TlvType6](pathID),
6✔
133
                )
6✔
134
        }
6✔
135

136
        if constraints != nil {
12✔
137
                data.Constraints = tlv.SomeRecordT(
5✔
138
                        tlv.NewRecordT[tlv.TlvType12](*constraints))
5✔
139
        }
5✔
140

141
        return &data
7✔
142
}
143

144
// NewDummyHopRouteData creates the data that's provided for any hop preceding
145
// a dummy hop. The presence of such a payload indicates to the reader that
146
// they are the intended recipient and should peel the remainder of the onion.
147
func NewDummyHopRouteData(ourPubKey *btcec.PublicKey,
148
        relayInfo PaymentRelayInfo,
149
        constraints PaymentConstraints) *BlindedRouteData {
5✔
150

5✔
151
        return &BlindedRouteData{
5✔
152
                NextNodeID: tlv.SomeRecordT(
5✔
153
                        tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey),
5✔
154
                ),
5✔
155
                RelayInfo: tlv.SomeRecordT(
5✔
156
                        tlv.NewRecordT[tlv.TlvType10](relayInfo),
5✔
157
                ),
5✔
158
                Constraints: tlv.SomeRecordT(
5✔
159
                        tlv.NewRecordT[tlv.TlvType12](constraints),
5✔
160
                ),
5✔
161
        }
5✔
162
}
5✔
163

164
// DecodeBlindedRouteData decodes the data provided within a blinded route.
165
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
25✔
166
        var (
25✔
167
                d BlindedRouteData
25✔
168

25✔
169
                padding          = d.Padding.Zero()
25✔
170
                scid             = d.ShortChannelID.Zero()
25✔
171
                nextNodeID       = d.NextNodeID.Zero()
25✔
172
                pathID           = d.PathID.Zero()
25✔
173
                blindingOverride = d.NextBlindingOverride.Zero()
25✔
174
                relayInfo        = d.RelayInfo.Zero()
25✔
175
                constraints      = d.Constraints.Zero()
25✔
176
                features         = d.Features.Zero()
25✔
177
        )
25✔
178

25✔
179
        var tlvRecords lnwire.ExtraOpaqueData
25✔
180
        if err := lnwire.ReadElements(r, &tlvRecords); err != nil {
25✔
181
                return nil, err
×
182
        }
×
183

184
        typeMap, err := tlvRecords.ExtractRecords(
25✔
185
                &padding, &scid, &nextNodeID, &pathID, &blindingOverride,
25✔
186
                &relayInfo, &constraints, &features,
25✔
187
        )
25✔
188
        if err != nil {
26✔
189
                return nil, err
1✔
190
        }
1✔
191

192
        val, ok := typeMap[d.Padding.TlvType()]
24✔
193
        if ok && val == nil {
33✔
194
                d.Padding = tlv.SomeRecordT(padding)
9✔
195
        }
9✔
196

197
        if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil {
40✔
198
                d.ShortChannelID = tlv.SomeRecordT(scid)
16✔
199
        }
16✔
200

201
        if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil {
27✔
202
                d.NextNodeID = tlv.SomeRecordT(nextNodeID)
3✔
203
        }
3✔
204

205
        if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil {
28✔
206
                d.PathID = tlv.SomeRecordT(pathID)
4✔
207
        }
4✔
208

209
        val, ok = typeMap[d.NextBlindingOverride.TlvType()]
24✔
210
        if ok && val == nil {
32✔
211
                d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
8✔
212
        }
8✔
213

214
        if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil {
43✔
215
                d.RelayInfo = tlv.SomeRecordT(relayInfo)
19✔
216
        }
19✔
217

218
        if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil {
40✔
219
                d.Constraints = tlv.SomeRecordT(constraints)
16✔
220
        }
16✔
221

222
        if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil {
28✔
223
                d.Features = tlv.SomeRecordT(features)
4✔
224
        }
4✔
225

226
        return &d, nil
24✔
227
}
228

229
// EncodeBlindedRouteData encodes the blinded route data provided.
230
func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
2,379✔
231
        var (
2,379✔
232
                e               lnwire.ExtraOpaqueData
2,379✔
233
                recordProducers = make([]tlv.RecordProducer, 0, 5)
2,379✔
234
        )
2,379✔
235

2,379✔
236
        data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) {
4,735✔
237
                recordProducers = append(recordProducers, &p)
2,356✔
238
        })
2,356✔
239

240
        data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2,
2,379✔
241
                lnwire.ShortChannelID]) {
2,405✔
242

26✔
243
                recordProducers = append(recordProducers, &scid)
26✔
244
        })
26✔
245

246
        data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4,
2,379✔
247
                *btcec.PublicKey]) {
2,388✔
248

9✔
249
                recordProducers = append(recordProducers, &f)
9✔
250
        })
9✔
251

252
        data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) {
2,417✔
253
                recordProducers = append(recordProducers, &pathID)
38✔
254
        })
38✔
255

256
        data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
2,379✔
257
                *btcec.PublicKey]) {
2,392✔
258

13✔
259
                recordProducers = append(recordProducers, &pk)
13✔
260
        })
13✔
261

262
        data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10,
2,379✔
263
                PaymentRelayInfo]) {
2,414✔
264

35✔
265
                recordProducers = append(recordProducers, &r)
35✔
266
        })
35✔
267

268
        data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12,
2,379✔
269
                PaymentConstraints]) {
2,417✔
270

38✔
271
                recordProducers = append(recordProducers, &cs)
38✔
272
        })
38✔
273

274
        data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14,
2,379✔
275
                lnwire.FeatureVector]) {
2,381✔
276

2✔
277
                recordProducers = append(recordProducers, &f)
2✔
278
        })
2✔
279

280
        if err := e.PackRecords(recordProducers...); err != nil {
2,379✔
281
                return nil, err
×
282
        }
×
283

284
        return e[:], nil
2,379✔
285
}
286

287
// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field.
288
// Callers should be aware that the total payload size will change by more than
289
// "n" since the "n" bytes will be prefixed by BigSize type and length fields.
290
// Callers may need to call PadBy iteratively until each encrypted data packet
291
// is the same size and so each call will overwrite the Padding record.
292
// Note that calling PadBy with an n value of 0 will still result in a zero
293
// length TLV entry being added.
294
func (b *BlindedRouteData) PadBy(n int) {
2,343✔
295
        b.Padding = tlv.SomeRecordT(
2,343✔
296
                tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)),
2,343✔
297
        )
2,343✔
298
}
2,343✔
299

300
// PaymentRelayInfo describes the relay policy for a blinded path.
301
type PaymentRelayInfo struct {
302
        // CltvExpiryDelta is the expiry delta for the payment.
303
        CltvExpiryDelta uint16
304

305
        // FeeRate is the fee rate that will be charged per millionth of a
306
        // satoshi.
307
        FeeRate uint32
308

309
        // BaseFee is the per-htlc fee charged in milli-satoshis.
310
        BaseFee lnwire.MilliSatoshi
311
}
312

313
// Record creates a tlv.Record that encodes the payment relay (type 10) type for
314
// an encrypted blob payload.
315
func (i *PaymentRelayInfo) Record() tlv.Record {
60✔
316
        return tlv.MakeDynamicRecord(
60✔
317
                10, &i, func() uint64 {
95✔
318
                        // uint16 + uint32 + tuint32
35✔
319
                        return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee))
35✔
320
                }, encodePaymentRelay, decodePaymentRelay,
35✔
321
        )
322
}
323

324
func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error {
35✔
325
        if t, ok := val.(**PaymentRelayInfo); ok {
70✔
326
                relayInfo := *t
35✔
327

35✔
328
                // Just write our first 6 bytes directly.
35✔
329
                binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta)
35✔
330
                binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate)
35✔
331
                if _, err := w.Write(buf[0:6]); err != nil {
35✔
332
                        return err
×
333
                }
×
334

335
                baseFee := uint32(relayInfo.BaseFee)
35✔
336

35✔
337
                // We can safely reuse buf here because we overwrite its
35✔
338
                // contents.
35✔
339
                return tlv.ETUint32(w, &baseFee, buf)
35✔
340
        }
341

342
        return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo")
×
343
}
344

345
func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte,
346
        l uint64) error {
19✔
347

19✔
348
        if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 {
38✔
349
                scratch := make([]byte, l)
19✔
350

19✔
351
                n, err := io.ReadFull(r, scratch)
19✔
352
                if err != nil {
19✔
353
                        return err
×
354
                }
×
355

356
                // We expect at least 6 bytes, because we have 2 bytes for
357
                // cltv delta and 4 bytes for fee rate.
358
                if n < 6 {
19✔
359
                        return tlv.NewTypeForDecodingErr(val,
×
360
                                "*hop.paymentRelayInfo", uint64(n), 6)
×
361
                }
×
362

363
                relayInfo := *t
19✔
364

19✔
365
                relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16(
19✔
366
                        scratch[0:2],
19✔
367
                )
19✔
368
                relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6])
19✔
369

19✔
370
                // To be able to re-use the DTUint32 function we create a
19✔
371
                // buffer with just the bytes holding the variable length u32.
19✔
372
                // If the base fee is zero, this will be an empty buffer, which
19✔
373
                // is okay.
19✔
374
                b := bytes.NewBuffer(scratch[6:])
19✔
375

19✔
376
                var baseFee uint32
19✔
377
                err = tlv.DTUint32(b, &baseFee, buf, l-6)
19✔
378
                if err != nil {
19✔
379
                        return err
×
380
                }
×
381

382
                relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee)
19✔
383

19✔
384
                return nil
19✔
385
        }
386

387
        return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10)
×
388
}
389

390
// PaymentConstraints is a set of restrictions on a payment.
391
type PaymentConstraints struct {
392
        // MaxCltvExpiry is the maximum expiry height for the payment.
393
        MaxCltvExpiry uint32
394

395
        // HtlcMinimumMsat is the minimum htlc size for the payment.
396
        HtlcMinimumMsat lnwire.MilliSatoshi
397
}
398

399
func (p *PaymentConstraints) Record() tlv.Record {
63✔
400
        return tlv.MakeDynamicRecord(
63✔
401
                12, &p, func() uint64 {
101✔
402
                        // uint32 + tuint64.
38✔
403
                        return 4 + tlv.SizeTUint64(uint64(
38✔
404
                                p.HtlcMinimumMsat,
38✔
405
                        ))
38✔
406
                },
38✔
407
                encodePaymentConstraints, decodePaymentConstraints,
408
        )
409
}
410

411
func encodePaymentConstraints(w io.Writer, val interface{},
412
        buf *[8]byte) error {
38✔
413

38✔
414
        if c, ok := val.(**PaymentConstraints); ok {
76✔
415
                constraints := *c
38✔
416

38✔
417
                binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry)
38✔
418
                if _, err := w.Write(buf[:4]); err != nil {
38✔
419
                        return err
×
420
                }
×
421

422
                // We can safely re-use buf here because we overwrite its
423
                // contents.
424
                htlcMsat := uint64(constraints.HtlcMinimumMsat)
38✔
425

38✔
426
                return tlv.ETUint64(w, &htlcMsat, buf)
38✔
427
        }
428

429
        return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints")
×
430
}
431

432
func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte,
433
        l uint64) error {
16✔
434

16✔
435
        if c, ok := val.(**PaymentConstraints); ok && l <= 12 {
32✔
436
                scratch := make([]byte, l)
16✔
437

16✔
438
                n, err := io.ReadFull(r, scratch)
16✔
439
                if err != nil {
16✔
440
                        return err
×
441
                }
×
442

443
                // We expect at least 4 bytes for our uint32.
444
                if n < 4 {
16✔
445
                        return tlv.NewTypeForDecodingErr(val,
×
446
                                "*paymentConstraints", uint64(n), 4)
×
447
                }
×
448

449
                payConstraints := *c
16✔
450

16✔
451
                payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32(
16✔
452
                        scratch[:4],
16✔
453
                )
16✔
454

16✔
455
                // This could be empty if our minimum is zero, that's okay.
16✔
456
                var (
16✔
457
                        b       = bytes.NewBuffer(scratch[4:])
16✔
458
                        minHtlc uint64
16✔
459
                )
16✔
460

16✔
461
                err = tlv.DTUint64(b, &minHtlc, buf, l-4)
16✔
462
                if err != nil {
16✔
463
                        return err
×
464
                }
×
465
                payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc)
16✔
466

16✔
467
                return nil
16✔
468
        }
469

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