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

lightningnetwork / lnd / 21485572389

29 Jan 2026 04:09PM UTC coverage: 65.247% (+0.2%) from 65.074%
21485572389

Pull #10089

github

web-flow
Merge 22d34d15e into 19b2ad797
Pull Request #10089: Onion message forwarding

1152 of 1448 new or added lines in 23 files covered. (79.56%)

4109 existing lines in 29 files now uncovered.

139515 of 213825 relevant lines covered (65.25%)

20529.09 hits per line

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

89.86
/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/fn/v2"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
        "github.com/lightningnetwork/lnd/tlv"
12
)
13

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

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

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

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

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

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

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

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

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

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

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

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

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

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

91
        return info
27✔
92
}
93

94
// NewNonFinalBlindedRouteData creates the data that's provided for hops within
95
// a blinded route.
96
func NewNonFinalBlindedRouteDataOnionMessage(
97
        nextNode fn.Either[*btcec.PublicKey, lnwire.ShortChannelID],
98
        blindingOverride *btcec.PublicKey,
99
        features *lnwire.FeatureVector) *BlindedRouteData {
9✔
100

9✔
101
        info := fn.ElimEither(
9✔
102
                nextNode,
9✔
103
                func(nextNodeID *btcec.PublicKey) *BlindedRouteData {
15✔
104
                        return &BlindedRouteData{
6✔
105
                                NextNodeID: tlv.SomeRecordT(
6✔
106
                                        tlv.NewPrimitiveRecord[tlv.TlvType4](
6✔
107
                                                nextNodeID,
6✔
108
                                        ),
6✔
109
                                ),
6✔
110
                        }
6✔
111
                },
6✔
112
                func(chanID lnwire.ShortChannelID) *BlindedRouteData {
3✔
113
                        return &BlindedRouteData{
3✔
114
                                ShortChannelID: tlv.SomeRecordT(
3✔
115
                                        tlv.NewRecordT[tlv.TlvType2](chanID),
3✔
116
                                ),
3✔
117
                        }
3✔
118
                },
3✔
119
        )
120

121
        if blindingOverride != nil {
11✔
122
                info.NextBlindingOverride = tlv.SomeRecordT(
2✔
123
                        tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride))
2✔
124
        }
2✔
125

126
        if features != nil {
9✔
NEW
127
                info.Features = tlv.SomeRecordT(
×
NEW
128
                        tlv.NewRecordT[tlv.TlvType14](*features),
×
NEW
129
                )
×
NEW
130
        }
×
131

132
        return info
9✔
133
}
134

135
// NewFinalHopBlindedRouteData creates the data that's provided for the final
136
// hop in a blinded route.
137
func NewFinalHopBlindedRouteData(constraints *PaymentConstraints,
138
        pathID []byte) *BlindedRouteData {
9✔
139

9✔
140
        var data BlindedRouteData
9✔
141
        if pathID != nil {
17✔
142
                data.PathID = tlv.SomeRecordT(
8✔
143
                        tlv.NewPrimitiveRecord[tlv.TlvType6](pathID),
8✔
144
                )
8✔
145
        }
8✔
146

147
        if constraints != nil {
16✔
148
                data.Constraints = tlv.SomeRecordT(
7✔
149
                        tlv.NewRecordT[tlv.TlvType12](*constraints))
7✔
150
        }
7✔
151

152
        return &data
9✔
153
}
154

155
// NewDummyHopRouteData creates the data that's provided for any hop preceding
156
// a dummy hop. The presence of such a payload indicates to the reader that
157
// they are the intended recipient and should peel the remainder of the onion.
158
func NewDummyHopRouteData(ourPubKey *btcec.PublicKey,
159
        relayInfo PaymentRelayInfo,
160
        constraints PaymentConstraints) *BlindedRouteData {
7✔
161

7✔
162
        return &BlindedRouteData{
7✔
163
                NextNodeID: tlv.SomeRecordT(
7✔
164
                        tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey),
7✔
165
                ),
7✔
166
                RelayInfo: tlv.SomeRecordT(
7✔
167
                        tlv.NewRecordT[tlv.TlvType10](relayInfo),
7✔
168
                ),
7✔
169
                Constraints: tlv.SomeRecordT(
7✔
170
                        tlv.NewRecordT[tlv.TlvType12](constraints),
7✔
171
                ),
7✔
172
        }
7✔
173
}
7✔
174

175
// DecodeBlindedRouteData decodes the data provided within a blinded route.
176
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
43✔
177
        var (
43✔
178
                d BlindedRouteData
43✔
179

43✔
180
                padding          = d.Padding.Zero()
43✔
181
                scid             = d.ShortChannelID.Zero()
43✔
182
                nextNodeID       = d.NextNodeID.Zero()
43✔
183
                pathID           = d.PathID.Zero()
43✔
184
                blindingOverride = d.NextBlindingOverride.Zero()
43✔
185
                relayInfo        = d.RelayInfo.Zero()
43✔
186
                constraints      = d.Constraints.Zero()
43✔
187
                features         = d.Features.Zero()
43✔
188
        )
43✔
189

43✔
190
        var tlvRecords lnwire.ExtraOpaqueData
43✔
191
        if err := lnwire.ReadElements(r, &tlvRecords); err != nil {
43✔
192
                return nil, err
×
193
        }
×
194

195
        typeMap, err := tlvRecords.ExtractRecords(
43✔
196
                &padding, &scid, &nextNodeID, &pathID, &blindingOverride,
43✔
197
                &relayInfo, &constraints, &features,
43✔
198
        )
43✔
199
        if err != nil {
45✔
200
                return nil, err
2✔
201
        }
2✔
202

203
        val, ok := typeMap[d.Padding.TlvType()]
41✔
204
        if ok && val == nil {
52✔
205
                d.Padding = tlv.SomeRecordT(padding)
11✔
206
        }
11✔
207

208
        if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil {
63✔
209
                d.ShortChannelID = tlv.SomeRecordT(scid)
22✔
210
        }
22✔
211

212
        if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil {
55✔
213
                d.NextNodeID = tlv.SomeRecordT(nextNodeID)
14✔
214
        }
14✔
215

216
        if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil {
47✔
217
                d.PathID = tlv.SomeRecordT(pathID)
6✔
218
        }
6✔
219

220
        val, ok = typeMap[d.NextBlindingOverride.TlvType()]
41✔
221
        if ok && val == nil {
54✔
222
                d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
13✔
223
        }
13✔
224

225
        if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil {
62✔
226
                d.RelayInfo = tlv.SomeRecordT(relayInfo)
21✔
227
        }
21✔
228

229
        if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil {
59✔
230
                d.Constraints = tlv.SomeRecordT(constraints)
18✔
231
        }
18✔
232

233
        if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil {
45✔
234
                d.Features = tlv.SomeRecordT(features)
4✔
235
        }
4✔
236

237
        return &d, nil
41✔
238
}
239

240
// EncodeBlindedRouteData encodes the blinded route data provided.
241
func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
2,749✔
242
        var (
2,749✔
243
                e               lnwire.ExtraOpaqueData
2,749✔
244
                recordProducers = make([]tlv.RecordProducer, 0, 5)
2,749✔
245
        )
2,749✔
246

2,749✔
247
        data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) {
5,456✔
248
                recordProducers = append(recordProducers, &p)
2,707✔
249
        })
2,707✔
250

251
        data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2,
2,749✔
252
                lnwire.ShortChannelID]) {
2,780✔
253

31✔
254
                recordProducers = append(recordProducers, &scid)
31✔
255
        })
31✔
256

257
        data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4,
2,749✔
258
                *btcec.PublicKey]) {
2,766✔
259

17✔
260
                recordProducers = append(recordProducers, &f)
17✔
261
        })
17✔
262

263
        data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) {
2,789✔
264
                recordProducers = append(recordProducers, &pathID)
40✔
265
        })
40✔
266

267
        data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
2,749✔
268
                *btcec.PublicKey]) {
2,764✔
269

15✔
270
                recordProducers = append(recordProducers, &pk)
15✔
271
        })
15✔
272

273
        data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10,
2,749✔
274
                PaymentRelayInfo]) {
2,786✔
275

37✔
276
                recordProducers = append(recordProducers, &r)
37✔
277
        })
37✔
278

279
        data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12,
2,749✔
280
                PaymentConstraints]) {
2,789✔
281

40✔
282
                recordProducers = append(recordProducers, &cs)
40✔
283
        })
40✔
284

285
        data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14,
2,749✔
286
                lnwire.FeatureVector]) {
2,751✔
287

2✔
288
                recordProducers = append(recordProducers, &f)
2✔
289
        })
2✔
290

291
        if err := e.PackRecords(recordProducers...); err != nil {
2,749✔
292
                return nil, err
×
293
        }
×
294

295
        return e[:], nil
2,749✔
296
}
297

298
// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field.
299
// Callers should be aware that the total payload size will change by more than
300
// "n" since the "n" bytes will be prefixed by BigSize type and length fields.
301
// Callers may need to call PadBy iteratively until each encrypted data packet
302
// is the same size and so each call will overwrite the Padding record.
303
// Note that calling PadBy with an n value of 0 will still result in a zero
304
// length TLV entry being added.
305
func (b *BlindedRouteData) PadBy(n int) {
2,694✔
306
        b.Padding = tlv.SomeRecordT(
2,694✔
307
                tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)),
2,694✔
308
        )
2,694✔
309
}
2,694✔
310

311
// PaymentRelayInfo describes the relay policy for a blinded path.
312
type PaymentRelayInfo struct {
313
        // CltvExpiryDelta is the expiry delta for the payment.
314
        CltvExpiryDelta uint16
315

316
        // FeeRate is the fee rate that will be charged per millionth of a
317
        // satoshi.
318
        FeeRate uint32
319

320
        // BaseFee is the per-htlc fee charged in milli-satoshis.
321
        BaseFee lnwire.MilliSatoshi
322
}
323

324
// Record creates a tlv.Record that encodes the payment relay (type 10) type for
325
// an encrypted blob payload.
326
func (i *PaymentRelayInfo) Record() tlv.Record {
78✔
327
        return tlv.MakeDynamicRecord(
78✔
328
                10, &i, func() uint64 {
115✔
329
                        // uint16 + uint32 + tuint32
37✔
330
                        return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee))
37✔
331
                }, encodePaymentRelay, decodePaymentRelay,
37✔
332
        )
333
}
334

335
func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error {
37✔
336
        if t, ok := val.(**PaymentRelayInfo); ok {
74✔
337
                relayInfo := *t
37✔
338

37✔
339
                // Just write our first 6 bytes directly.
37✔
340
                binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta)
37✔
341
                binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate)
37✔
342
                if _, err := w.Write(buf[0:6]); err != nil {
37✔
343
                        return err
×
344
                }
×
345

346
                baseFee := uint32(relayInfo.BaseFee)
37✔
347

37✔
348
                // We can safely reuse buf here because we overwrite its
37✔
349
                // contents.
37✔
350
                return tlv.ETUint32(w, &baseFee, buf)
37✔
351
        }
352

353
        return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo")
×
354
}
355

356
func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte,
357
        l uint64) error {
21✔
358

21✔
359
        if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 {
42✔
360
                scratch := make([]byte, l)
21✔
361

21✔
362
                n, err := io.ReadFull(r, scratch)
21✔
363
                if err != nil {
21✔
364
                        return err
×
365
                }
×
366

367
                // We expect at least 6 bytes, because we have 2 bytes for
368
                // cltv delta and 4 bytes for fee rate.
369
                if n < 6 {
21✔
370
                        return tlv.NewTypeForDecodingErr(val,
×
371
                                "*hop.paymentRelayInfo", uint64(n), 6)
×
372
                }
×
373

374
                relayInfo := *t
21✔
375

21✔
376
                relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16(
21✔
377
                        scratch[0:2],
21✔
378
                )
21✔
379
                relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6])
21✔
380

21✔
381
                // To be able to re-use the DTUint32 function we create a
21✔
382
                // buffer with just the bytes holding the variable length u32.
21✔
383
                // If the base fee is zero, this will be an empty buffer, which
21✔
384
                // is okay.
21✔
385
                b := bytes.NewBuffer(scratch[6:])
21✔
386

21✔
387
                var baseFee uint32
21✔
388
                err = tlv.DTUint32(b, &baseFee, buf, l-6)
21✔
389
                if err != nil {
21✔
390
                        return err
×
391
                }
×
392

393
                relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee)
21✔
394

21✔
395
                return nil
21✔
396
        }
397

398
        return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10)
×
399
}
400

401
// PaymentConstraints is a set of restrictions on a payment.
402
type PaymentConstraints struct {
403
        // MaxCltvExpiry is the maximum expiry height for the payment.
404
        MaxCltvExpiry uint32
405

406
        // HtlcMinimumMsat is the minimum htlc size for the payment.
407
        HtlcMinimumMsat lnwire.MilliSatoshi
408
}
409

410
func (p *PaymentConstraints) Record() tlv.Record {
81✔
411
        return tlv.MakeDynamicRecord(
81✔
412
                12, &p, func() uint64 {
121✔
413
                        // uint32 + tuint64.
40✔
414
                        return 4 + tlv.SizeTUint64(uint64(
40✔
415
                                p.HtlcMinimumMsat,
40✔
416
                        ))
40✔
417
                },
40✔
418
                encodePaymentConstraints, decodePaymentConstraints,
419
        )
420
}
421

422
func encodePaymentConstraints(w io.Writer, val interface{},
423
        buf *[8]byte) error {
40✔
424

40✔
425
        if c, ok := val.(**PaymentConstraints); ok {
80✔
426
                constraints := *c
40✔
427

40✔
428
                binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry)
40✔
429
                if _, err := w.Write(buf[:4]); err != nil {
40✔
430
                        return err
×
431
                }
×
432

433
                // We can safely re-use buf here because we overwrite its
434
                // contents.
435
                htlcMsat := uint64(constraints.HtlcMinimumMsat)
40✔
436

40✔
437
                return tlv.ETUint64(w, &htlcMsat, buf)
40✔
438
        }
439

440
        return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints")
×
441
}
442

443
func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte,
444
        l uint64) error {
18✔
445

18✔
446
        if c, ok := val.(**PaymentConstraints); ok && l <= 12 {
36✔
447
                scratch := make([]byte, l)
18✔
448

18✔
449
                n, err := io.ReadFull(r, scratch)
18✔
450
                if err != nil {
18✔
451
                        return err
×
452
                }
×
453

454
                // We expect at least 4 bytes for our uint32.
455
                if n < 4 {
18✔
456
                        return tlv.NewTypeForDecodingErr(val,
×
457
                                "*paymentConstraints", uint64(n), 4)
×
458
                }
×
459

460
                payConstraints := *c
18✔
461

18✔
462
                payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32(
18✔
463
                        scratch[:4],
18✔
464
                )
18✔
465

18✔
466
                // This could be empty if our minimum is zero, that's okay.
18✔
467
                var (
18✔
468
                        b       = bytes.NewBuffer(scratch[4:])
18✔
469
                        minHtlc uint64
18✔
470
                )
18✔
471

18✔
472
                err = tlv.DTUint64(b, &minHtlc, buf, l-4)
18✔
473
                if err != nil {
18✔
474
                        return err
×
475
                }
×
476
                payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc)
18✔
477

18✔
478
                return nil
18✔
479
        }
480

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

© 2026 Coveralls, Inc