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

lightningnetwork / lnd / 16673052227

01 Aug 2025 10:44AM UTC coverage: 67.016% (-0.03%) from 67.047%
16673052227

Pull #9888

github

web-flow
Merge 1dd8765d7 into 37523b6cb
Pull Request #9888: Attributable failures

325 of 384 new or added lines in 16 files covered. (84.64%)

131 existing lines in 24 files now uncovered.

135611 of 202355 relevant lines covered (67.02%)

21613.83 hits per line

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

76.43
/htlcswitch/hop/error_encryptor.go
1
package hop
2

3
import (
4
        "bytes"
5
        "errors"
6
        "fmt"
7
        "io"
8
        "time"
9

10
        "github.com/btcsuite/btcd/btcec/v2"
11
        sphinx "github.com/lightningnetwork/lightning-onion"
12
        "github.com/lightningnetwork/lnd/lnwire"
13
        "github.com/lightningnetwork/lnd/tlv"
14
)
15

16
// EncrypterType establishes an enum used in serialization to indicate how to
17
// decode a concrete instance of the ErrorEncrypter interface.
18
type EncrypterType byte
19

20
const (
21
        // EncrypterTypeNone signals that no error encyrpter is present, this
22
        // can happen if the htlc is originates in the switch.
23
        EncrypterTypeNone EncrypterType = 0
24

25
        // EncrypterTypeSphinx is used to identify a sphinx onion error
26
        // encrypter instance.
27
        EncrypterTypeSphinx = 1
28

29
        // EncrypterTypeMock is used to identify a mock obfuscator instance.
30
        EncrypterTypeMock = 2
31

32
        // EncrypterTypeIntroduction is used to identify a sphinx onion error
33
        // encrypter where we are the introduction node in a blinded route. It
34
        // has the same functionality as EncrypterTypeSphinx, but is used to
35
        // mark our special-case error handling.
36
        EncrypterTypeIntroduction = 3
37

38
        // EncrypterTypeRelaying is used to identify a sphinx onion error
39
        // encryper where we are a relaying node in a blinded route. It has
40
        // the same functionality as a EncrypterTypeSphinx, but is used to mark
41
        // our special-case error handling.
42
        EncrypterTypeRelaying = 4
43

44
        // A set of tlv type definitions used to serialize the encrypter to the
45
        // database.
46
        //
47
        // NOTE: A migration should be added whenever this list changes. This
48
        // prevents against the database being rolled back to an older
49
        // format where the surrounding logic might assume a different set of
50
        // fields are known.
51
        creationTimeType tlv.Type = 0
52
)
53

54
// AttrErrorStruct defines the message structure for an attributable error. Use
55
// a maximum route length of 20, a fixed payload length of 4 bytes to
56
// accommodate the a 32-bit hold time in milliseconds and use 4 byte hmacs.
57
// Total size including a 256 byte message from the error source works out to
58
// 1200 bytes.
59
var (
60
        AttrErrorStruct = sphinx.NewAttrErrorStructure(20, 4, 4)
61
)
62

63
// IsBlinded returns a boolean indicating whether the error encrypter belongs
64
// to a blinded route.
65
func (e EncrypterType) IsBlinded() bool {
123✔
66
        return e == EncrypterTypeIntroduction || e == EncrypterTypeRelaying
123✔
67
}
123✔
68

69
// SharedSecretGenerator defines a function signature that extracts a shared
70
// secret from an sphinx OnionPacket.
71
type SharedSecretGenerator func(*btcec.PublicKey) (sphinx.Hash256,
72
        lnwire.FailCode)
73

74
// ErrorEncrypter is an interface that is used to encrypt HTLC related errors
75
// at the source of the error, and also at each intermediate hop all the way
76
// back to the source of the payment.
77
type ErrorEncrypter interface {
78
        // EncryptFirstHop transforms a concrete failure message into an
79
        // encrypted opaque failure reason. This method will be used at the
80
        // source that the error occurs. It differs from IntermediateEncrypt
81
        // slightly, in that it computes a proper MAC over the error.
82
        EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason,
83
                []byte, error)
84

85
        // EncryptMalformedError is similar to EncryptFirstHop (it adds the
86
        // MAC), but it accepts an opaque failure reason rather than a failure
87
        // message. This method is used when we receive an
88
        // UpdateFailMalformedHTLC from the remote peer and then need to
89
        // convert that into a proper error from only the raw bytes.
90
        EncryptMalformedError(lnwire.OpaqueReason) (lnwire.OpaqueReason, []byte,
91
                error)
92

93
        // IntermediateEncrypt wraps an already encrypted opaque reason error
94
        // in an additional layer of onion encryption. This process repeats
95
        // until the error arrives at the source of the payment.
96
        IntermediateEncrypt(lnwire.OpaqueReason, []byte) (lnwire.OpaqueReason,
97
                []byte, error)
98

99
        // Type returns an enum indicating the underlying concrete instance
100
        // backing this interface.
101
        Type() EncrypterType
102

103
        // Encode serializes the encrypter's ephemeral public key to the given
104
        // io.Writer.
105
        Encode(io.Writer) error
106

107
        // Decode deserializes the encrypter' ephemeral public key from the
108
        // given io.Reader.
109
        Decode(io.Reader) error
110

111
        // Reextract rederives the encrypter using the shared secret generator,
112
        // performing an ECDH with the sphinx router's key and the ephemeral
113
        // public key.
114
        //
115
        // NOTE: This should be called shortly after Decode to properly
116
        // reinitialize the error encrypter.
117
        Reextract(SharedSecretGenerator) error
118
}
119

120
// SphinxErrorEncrypter is a concrete implementation of both the ErrorEncrypter
121
// interface backed by an implementation of the Sphinx packet format. As a
122
// result, all errors handled are themselves wrapped in layers of onion
123
// encryption and must be treated as such accordingly.
124
type SphinxErrorEncrypter struct {
125
        *sphinx.OnionErrorEncrypter
126

127
        EphemeralKey *btcec.PublicKey
128
        CreatedAt    time.Time
129
}
130

131
// NewSphinxErrorEncrypterUninitialized initializes a blank sphinx error
132
// encrypter, that should be used to deserialize an encoded
133
// SphinxErrorEncrypter. Since the actual encrypter is not stored in plaintext
134
// while at rest, reconstructing the error encrypter requires:
135
//  1. Decode: to deserialize the ephemeral public key.
136
//  2. Reextract: to "unlock" the actual error encrypter using an active
137
//     OnionProcessor.
138
func NewSphinxErrorEncrypterUninitialized() *SphinxErrorEncrypter {
35✔
139
        return &SphinxErrorEncrypter{
35✔
140
                EphemeralKey: &btcec.PublicKey{},
35✔
141
        }
35✔
142
}
35✔
143

144
// NewSphinxErrorEncrypter creates a new instance of a SphinxErrorEncrypter,
145
// initialized with the provided shared secret. To deserialize an encoded
146
// SphinxErrorEncrypter, use the NewSphinxErrorEncrypterUninitialized
147
// constructor.
148
func NewSphinxErrorEncrypter(ephemeralKey *btcec.PublicKey,
149
        sharedSecret sphinx.Hash256) *SphinxErrorEncrypter {
4✔
150

4✔
151
        encrypter := &SphinxErrorEncrypter{
4✔
152
                EphemeralKey: ephemeralKey,
4✔
153
        }
4✔
154

4✔
155
        // Set creation time rounded to nanosecond to avoid differences after
4✔
156
        // serialization.
4✔
157
        encrypter.CreatedAt = time.Now().Truncate(time.Nanosecond)
4✔
158

4✔
159
        encrypter.initialize(sharedSecret)
4✔
160

4✔
161
        return encrypter
4✔
162
}
4✔
163

164
// getHoldTime returns the hold time in decaseconds since the first
165
// instantiation of this sphinx error encrypter.
166
func (s *SphinxErrorEncrypter) getHoldTime() uint32 {
3✔
167
        return uint32(time.Since(s.CreatedAt).Milliseconds() / 100)
3✔
168
}
3✔
169

170
// encrypt is a thin wrapper around the main encryption method, mainly used to
171
// automatically derive the hold time to encode in the attribution structure.
172
func (s *SphinxErrorEncrypter) encrypt(initial bool,
173
        data, attrData []byte) (lnwire.OpaqueReason, []byte, error) {
3✔
174

3✔
175
        holdTime := s.getHoldTime()
3✔
176

3✔
177
        return s.EncryptError(initial, data, attrData, holdTime)
3✔
178
}
3✔
179

180
// initialize creates the underlying instance of the sphinx error encrypter.
181
func (s *SphinxErrorEncrypter) initialize(sharedSecret sphinx.Hash256) {
36✔
182
        s.OnionErrorEncrypter = sphinx.NewOnionErrorEncrypter(
36✔
183
                sharedSecret, AttrErrorStruct,
36✔
184
        )
36✔
185
}
36✔
186

187
// EncryptFirstHop transforms a concrete failure message into an encrypted
188
// opaque failure reason. This method will be used at the source that the error
189
// occurs. It differs from BackwardObfuscate slightly, in that it computes a
190
// proper MAC over the error.
191
//
192
// NOTE: Part of the ErrorEncrypter interface.
193
func (s *SphinxErrorEncrypter) EncryptFirstHop(
194
        failure lnwire.FailureMessage) (lnwire.OpaqueReason, []byte, error) {
3✔
195

3✔
196
        var b bytes.Buffer
3✔
197
        if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
3✔
NEW
198
                return nil, nil, err
×
199
        }
×
200

201
        return s.encrypt(true, b.Bytes(), nil)
3✔
202
}
203

204
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
205
// it accepts an opaque failure reason rather than a failure message. This
206
// method is used when we receive an UpdateFailMalformedHTLC from the remote
207
// peer and then need to convert that into an proper error from only the raw
208
// bytes.
209
//
210
// NOTE: Part of the ErrorEncrypter interface.
211
func (s *SphinxErrorEncrypter) EncryptMalformedError(
212
        reason lnwire.OpaqueReason) (lnwire.OpaqueReason, []byte, error) {
3✔
213

3✔
214
        return s.encrypt(true, reason, nil)
3✔
215
}
3✔
216

217
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
218
// additional layer of onion encryption. This process repeats until the error
219
// arrives at the source of the payment. We re-encrypt the message on the
220
// backwards path to ensure that the error is indistinguishable from any other
221
// error seen.
222
//
223
// NOTE: Part of the ErrorEncrypter interface.
224
func (s *SphinxErrorEncrypter) IntermediateEncrypt(
225
        reason lnwire.OpaqueReason, attrData []byte) (lnwire.OpaqueReason,
226
        []byte, error) {
3✔
227

3✔
228
        encrypted, attrData, err := s.encrypt(false, reason, attrData)
3✔
229

3✔
230
        switch {
3✔
231
        // If the structure of the error received from downstream is invalid,
232
        // then generate a new attribution structure so that the sender is able
233
        // to penalize the offending node.
NEW
234
        case errors.Is(err, sphinx.ErrInvalidAttrStructure):
×
NEW
235
                // Preserve the error message and initialize fresh attribution
×
NEW
236
                // data.
×
NEW
237
                return s.encrypt(true, reason, nil)
×
238

NEW
239
        case err != nil:
×
NEW
240
                return lnwire.OpaqueReason{}, nil, err
×
241
        }
242

243
        return encrypted, attrData, nil
3✔
244
}
245

246
// Type returns the identifier for a sphinx error encrypter.
247
func (s *SphinxErrorEncrypter) Type() EncrypterType {
31✔
248
        return EncrypterTypeSphinx
31✔
249
}
31✔
250

251
// Encode serializes the error encrypter' ephemeral public key to the provided
252
// io.Writer.
253
func (s *SphinxErrorEncrypter) Encode(w io.Writer) error {
31✔
254
        ephemeral := s.EphemeralKey.SerializeCompressed()
31✔
255
        _, err := w.Write(ephemeral)
31✔
256
        if err != nil {
31✔
NEW
257
                return err
×
NEW
258
        }
×
259

260
        var creationTime = uint64(s.CreatedAt.UnixNano())
31✔
261

31✔
262
        tlvStream, err := tlv.NewStream(
31✔
263
                tlv.MakePrimitiveRecord(creationTimeType, &creationTime),
31✔
264
        )
31✔
265
        if err != nil {
31✔
NEW
266
                return err
×
NEW
267
        }
×
268

269
        return tlvStream.Encode(w)
31✔
270
}
271

272
// Decode reconstructs the error encrypter's ephemeral public key from the
273
// provided io.Reader.
274
func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
35✔
275
        var ephemeral [33]byte
35✔
276
        if _, err := io.ReadFull(r, ephemeral[:]); err != nil {
35✔
277
                return err
×
278
        }
×
279

280
        var err error
35✔
281
        s.EphemeralKey, err = btcec.ParsePubKey(ephemeral[:])
35✔
282
        if err != nil {
35✔
283
                return err
×
284
        }
×
285

286
        // Try decode attributable error structure.
287
        var creationTime uint64
35✔
288

35✔
289
        tlvStream, err := tlv.NewStream(
35✔
290
                tlv.MakePrimitiveRecord(creationTimeType, &creationTime),
35✔
291
        )
35✔
292
        if err != nil {
35✔
NEW
293
                return err
×
NEW
294
        }
×
295

296
        typeMap, err := tlvStream.DecodeWithParsedTypes(r)
35✔
297
        if err != nil {
35✔
NEW
298
                return err
×
NEW
299
        }
×
300

301
        // Return early if this encrypter is not for attributable errors.
302
        if len(typeMap) == 0 {
35✔
NEW
303
                return nil
×
NEW
304
        }
×
305

306
        // Set attributable error creation time.
307
        s.CreatedAt = time.Unix(0, int64(creationTime))
35✔
308

35✔
309
        return nil
35✔
310
}
311

312
// Reextract rederives the error encrypter from the currently held EphemeralKey.
313
// This intended to be used shortly after Decode, to fully initialize a
314
// SphinxErrorEncrypter.
315
func (s *SphinxErrorEncrypter) Reextract(extract SharedSecretGenerator) error {
35✔
316
        sharedSecret, failcode := extract(s.EphemeralKey)
35✔
317
        if failcode != lnwire.CodeNone {
35✔
318
                // This should never happen, since we already validated that
×
319
                // this obfuscator can be extracted when it was received in the
×
320
                // link.
×
321
                return fmt.Errorf("unable to reconstruct onion "+
×
322
                        "obfuscator, got failcode: %d", failcode)
×
323
        }
×
324

325
        s.initialize(sharedSecret)
35✔
326

35✔
327
        return nil
35✔
328
}
329

330
// A compile time check to ensure SphinxErrorEncrypter implements the
331
// ErrorEncrypter interface.
332
var _ ErrorEncrypter = (*SphinxErrorEncrypter)(nil)
333

334
// A compile time check to ensure that IntroductionErrorEncrypter implements
335
// the ErrorEncrypter interface.
336
var _ ErrorEncrypter = (*IntroductionErrorEncrypter)(nil)
337

338
// IntroductionErrorEncrypter is a wrapper type on SphinxErrorEncrypter which
339
// is used to signal that we have special HTLC error handling for this hop.
340
type IntroductionErrorEncrypter struct {
341
        // ErrorEncrypter is the underlying error encrypter, embedded
342
        // directly in the struct so that we don't have to re-implement the
343
        // ErrorEncrypter interface.
344
        ErrorEncrypter
345
}
346

347
// NewIntroductionErrorEncrypter returns a blank IntroductionErrorEncrypter.
348
func NewIntroductionErrorEncrypter(ephemeralKey *btcec.PublicKey,
349
        sharedSecret sphinx.Hash256) *IntroductionErrorEncrypter {
3✔
350

3✔
351
        return &IntroductionErrorEncrypter{
3✔
352
                ErrorEncrypter: NewSphinxErrorEncrypter(
3✔
353
                        ephemeralKey, sharedSecret,
3✔
354
                ),
3✔
355
        }
3✔
356
}
3✔
357

358
// NewIntroductionErrorEncrypter returns a blank IntroductionErrorEncrypter.
359
// Since the actual encrypter is not stored in plaintext
360
// while at rest, reconstructing the error encrypter requires:
361
//  1. Decode: to deserialize the ephemeral public key.
362
//  2. Reextract: to "unlock" the actual error encrypter using an active
363
//     OnionProcessor.
364
func NewIntroductionErrorEncrypterUninitialized() *IntroductionErrorEncrypter {
3✔
365
        return &IntroductionErrorEncrypter{
3✔
366
                ErrorEncrypter: NewSphinxErrorEncrypterUninitialized(),
3✔
367
        }
3✔
368
}
3✔
369

370
// Type returns the identifier for an introduction error encrypter.
371
func (i *IntroductionErrorEncrypter) Type() EncrypterType {
3✔
372
        return EncrypterTypeIntroduction
3✔
373
}
3✔
374

375
// Reextract rederives the error encrypter from the currently held EphemeralKey,
376
// relying on the logic in the underlying SphinxErrorEncrypter.
377
func (i *IntroductionErrorEncrypter) Reextract(
378
        extract SharedSecretGenerator) error {
3✔
379

3✔
380
        return i.ErrorEncrypter.Reextract(extract)
3✔
381
}
3✔
382

383
// A compile time check to ensure that RelayingErrorEncrypte implements
384
// the ErrorEncrypter interface.
385
var _ ErrorEncrypter = (*RelayingErrorEncrypter)(nil)
386

387
// RelayingErrorEncrypter is a wrapper type on SphinxErrorEncrypter which
388
// is used to signal that we have special HTLC error handling for this hop.
389
type RelayingErrorEncrypter struct {
390
        ErrorEncrypter
391
}
392

393
// NewRelayingErrorEncrypter returns a blank RelayingErrorEncrypter with
394
// an underlying SphinxErrorEncrypter.
395
func NewRelayingErrorEncrypter(ephemeralKey *btcec.PublicKey,
396
        sharedSecret sphinx.Hash256) *RelayingErrorEncrypter {
3✔
397

3✔
398
        return &RelayingErrorEncrypter{
3✔
399
                ErrorEncrypter: NewSphinxErrorEncrypter(
3✔
400
                        ephemeralKey, sharedSecret,
3✔
401
                ),
3✔
402
        }
3✔
403
}
3✔
404

405
// NewRelayingErrorEncrypterUninitialized returns a blank RelayingErrorEncrypter
406
// with an underlying SphinxErrorEncrypter.
407
// Since the actual encrypter is not stored in plaintext
408
// while at rest, reconstructing the error encrypter requires:
409
//  1. Decode: to deserialize the ephemeral public key.
410
//  2. Reextract: to "unlock" the actual error encrypter using an active
411
//     OnionProcessor.
NEW
412
func NewRelayingErrorEncrypterUninitialized() *RelayingErrorEncrypter {
×
413
        return &RelayingErrorEncrypter{
×
NEW
414
                ErrorEncrypter: NewSphinxErrorEncrypterUninitialized(),
×
415
        }
×
416
}
×
417

418
// Type returns the identifier for a relaying error encrypter.
419
func (r *RelayingErrorEncrypter) Type() EncrypterType {
3✔
420
        return EncrypterTypeRelaying
3✔
421
}
3✔
422

423
// Reextract rederives the error encrypter from the currently held EphemeralKey,
424
// relying on the logic in the underlying SphinxErrorEncrypter.
425
func (r *RelayingErrorEncrypter) Reextract(
NEW
426
        extract SharedSecretGenerator) error {
×
427

×
428
        return r.ErrorEncrypter.Reextract(extract)
×
429
}
×
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