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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

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

3
import (
4
        "bytes"
5
        "fmt"
6
        "io"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        sphinx "github.com/lightningnetwork/lightning-onion"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
)
12

13
// EncrypterType establishes an enum used in serialization to indicate how to
14
// decode a concrete instance of the ErrorEncrypter interface.
15
type EncrypterType byte
16

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

22
        // EncrypterTypeSphinx is used to identify a sphinx onion error
23
        // encrypter instance.
24
        EncrypterTypeSphinx = 1
25

26
        // EncrypterTypeMock is used to identify a mock obfuscator instance.
27
        EncrypterTypeMock = 2
28

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

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

42
// IsBlinded returns a boolean indicating whether the error encrypter belongs
43
// to a blinded route.
44
func (e EncrypterType) IsBlinded() bool {
121✔
45
        return e == EncrypterTypeIntroduction || e == EncrypterTypeRelaying
121✔
46
}
121✔
47

48
// ErrorEncrypterExtracter defines a function signature that extracts an
49
// ErrorEncrypter from an sphinx OnionPacket.
50
type ErrorEncrypterExtracter func(*btcec.PublicKey) (ErrorEncrypter,
51
        lnwire.FailCode)
52

53
// ErrorEncrypter is an interface that is used to encrypt HTLC related errors
54
// at the source of the error, and also at each intermediate hop all the way
55
// back to the source of the payment.
56
type ErrorEncrypter interface {
57
        // EncryptFirstHop transforms a concrete failure message into an
58
        // encrypted opaque failure reason. This method will be used at the
59
        // source that the error occurs. It differs from IntermediateEncrypt
60
        // slightly, in that it computes a proper MAC over the error.
61
        EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, error)
62

63
        // EncryptMalformedError is similar to EncryptFirstHop (it adds the
64
        // MAC), but it accepts an opaque failure reason rather than a failure
65
        // message. This method is used when we receive an
66
        // UpdateFailMalformedHTLC from the remote peer and then need to
67
        // convert that into a proper error from only the raw bytes.
68
        EncryptMalformedError(lnwire.OpaqueReason) lnwire.OpaqueReason
69

70
        // IntermediateEncrypt wraps an already encrypted opaque reason error
71
        // in an additional layer of onion encryption. This process repeats
72
        // until the error arrives at the source of the payment.
73
        IntermediateEncrypt(lnwire.OpaqueReason) lnwire.OpaqueReason
74

75
        // Type returns an enum indicating the underlying concrete instance
76
        // backing this interface.
77
        Type() EncrypterType
78

79
        // Encode serializes the encrypter's ephemeral public key to the given
80
        // io.Writer.
81
        Encode(io.Writer) error
82

83
        // Decode deserializes the encrypter' ephemeral public key from the
84
        // given io.Reader.
85
        Decode(io.Reader) error
86

87
        // Reextract rederives the encrypter using the extracter, performing an
88
        // ECDH with the sphinx router's key and the ephemeral public key.
89
        //
90
        // NOTE: This should be called shortly after Decode to properly
91
        // reinitialize the error encrypter.
92
        Reextract(ErrorEncrypterExtracter) error
93
}
94

95
// SphinxErrorEncrypter is a concrete implementation of both the ErrorEncrypter
96
// interface backed by an implementation of the Sphinx packet format. As a
97
// result, all errors handled are themselves wrapped in layers of onion
98
// encryption and must be treated as such accordingly.
99
type SphinxErrorEncrypter struct {
100
        *sphinx.OnionErrorEncrypter
101

102
        EphemeralKey *btcec.PublicKey
103
}
104

105
// NewSphinxErrorEncrypter initializes a blank sphinx error encrypter, that
106
// should be used to deserialize an encoded SphinxErrorEncrypter. Since the
107
// actual encrypter is not stored in plaintext while at rest, reconstructing the
108
// error encrypter requires:
109
//  1. Decode: to deserialize the ephemeral public key.
110
//  2. Reextract: to "unlock" the actual error encrypter using an active
111
//     OnionProcessor.
112
func NewSphinxErrorEncrypter() *SphinxErrorEncrypter {
32✔
113
        return &SphinxErrorEncrypter{
32✔
114
                OnionErrorEncrypter: nil,
32✔
115
                EphemeralKey:        &btcec.PublicKey{},
32✔
116
        }
32✔
117
}
32✔
118

119
// EncryptFirstHop transforms a concrete failure message into an encrypted
120
// opaque failure reason. This method will be used at the source that the error
121
// occurs. It differs from BackwardObfuscate slightly, in that it computes a
122
// proper MAC over the error.
123
//
124
// NOTE: Part of the ErrorEncrypter interface.
125
func (s *SphinxErrorEncrypter) EncryptFirstHop(
126
        failure lnwire.FailureMessage) (lnwire.OpaqueReason, error) {
×
127

×
128
        var b bytes.Buffer
×
129
        if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
×
130
                return nil, err
×
131
        }
×
132

133
        // We pass a true as the first parameter to indicate that a MAC should
134
        // be added.
135
        return s.EncryptError(true, b.Bytes()), nil
×
136
}
137

138
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
139
// it accepts an opaque failure reason rather than a failure message. This
140
// method is used when we receive an UpdateFailMalformedHTLC from the remote
141
// peer and then need to convert that into an proper error from only the raw
142
// bytes.
143
//
144
// NOTE: Part of the ErrorEncrypter interface.
145
func (s *SphinxErrorEncrypter) EncryptMalformedError(
146
        reason lnwire.OpaqueReason) lnwire.OpaqueReason {
×
147

×
148
        return s.EncryptError(true, reason)
×
149
}
×
150

151
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
152
// additional layer of onion encryption. This process repeats until the error
153
// arrives at the source of the payment. We re-encrypt the message on the
154
// backwards path to ensure that the error is indistinguishable from any other
155
// error seen.
156
//
157
// NOTE: Part of the ErrorEncrypter interface.
158
func (s *SphinxErrorEncrypter) IntermediateEncrypt(
159
        reason lnwire.OpaqueReason) lnwire.OpaqueReason {
×
160

×
161
        return s.EncryptError(false, reason)
×
162
}
×
163

164
// Type returns the identifier for a sphinx error encrypter.
165
func (s *SphinxErrorEncrypter) Type() EncrypterType {
28✔
166
        return EncrypterTypeSphinx
28✔
167
}
28✔
168

169
// Encode serializes the error encrypter' ephemeral public key to the provided
170
// io.Writer.
171
func (s *SphinxErrorEncrypter) Encode(w io.Writer) error {
28✔
172
        ephemeral := s.EphemeralKey.SerializeCompressed()
28✔
173
        _, err := w.Write(ephemeral)
28✔
174
        return err
28✔
175
}
28✔
176

177
// Decode reconstructs the error encrypter's ephemeral public key from the
178
// provided io.Reader.
179
func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
32✔
180
        var ephemeral [33]byte
32✔
181
        if _, err := io.ReadFull(r, ephemeral[:]); err != nil {
32✔
182
                return err
×
183
        }
×
184

185
        var err error
32✔
186
        s.EphemeralKey, err = btcec.ParsePubKey(ephemeral[:])
32✔
187
        if err != nil {
32✔
188
                return err
×
189
        }
×
190

191
        return nil
32✔
192
}
193

194
// Reextract rederives the error encrypter from the currently held EphemeralKey.
195
// This intended to be used shortly after Decode, to fully initialize a
196
// SphinxErrorEncrypter.
197
func (s *SphinxErrorEncrypter) Reextract(
198
        extract ErrorEncrypterExtracter) error {
32✔
199

32✔
200
        obfuscator, failcode := extract(s.EphemeralKey)
32✔
201
        if failcode != lnwire.CodeNone {
32✔
202
                // This should never happen, since we already validated that
×
203
                // this obfuscator can be extracted when it was received in the
×
204
                // link.
×
205
                return fmt.Errorf("unable to reconstruct onion "+
×
206
                        "obfuscator, got failcode: %d", failcode)
×
207
        }
×
208

209
        sphinxEncrypter, ok := obfuscator.(*SphinxErrorEncrypter)
32✔
210
        if !ok {
32✔
211
                return fmt.Errorf("incorrect onion error extracter")
×
212
        }
×
213

214
        // Copy the freshly extracted encrypter.
215
        s.OnionErrorEncrypter = sphinxEncrypter.OnionErrorEncrypter
32✔
216

32✔
217
        return nil
32✔
218
}
219

220
// A compile time check to ensure SphinxErrorEncrypter implements the
221
// ErrorEncrypter interface.
222
var _ ErrorEncrypter = (*SphinxErrorEncrypter)(nil)
223

224
// A compile time check to ensure that IntroductionErrorEncrypter implements
225
// the ErrorEncrypter interface.
226
var _ ErrorEncrypter = (*IntroductionErrorEncrypter)(nil)
227

228
// IntroductionErrorEncrypter is a wrapper type on SphinxErrorEncrypter which
229
// is used to signal that we have special HTLC error handling for this hop.
230
type IntroductionErrorEncrypter struct {
231
        // ErrorEncrypter is the underlying error encrypter, embedded
232
        // directly in the struct so that we don't have to re-implement the
233
        // ErrorEncrypter interface.
234
        ErrorEncrypter
235
}
236

237
// NewIntroductionErrorEncrypter returns a blank IntroductionErrorEncrypter.
238
func NewIntroductionErrorEncrypter() *IntroductionErrorEncrypter {
×
239
        return &IntroductionErrorEncrypter{
×
240
                ErrorEncrypter: NewSphinxErrorEncrypter(),
×
241
        }
×
242
}
×
243

244
// Type returns the identifier for an introduction error encrypter.
245
func (i *IntroductionErrorEncrypter) Type() EncrypterType {
×
246
        return EncrypterTypeIntroduction
×
247
}
×
248

249
// Reextract rederives the error encrypter from the currently held EphemeralKey,
250
// relying on the logic in the underlying SphinxErrorEncrypter.
251
func (i *IntroductionErrorEncrypter) Reextract(
252
        extract ErrorEncrypterExtracter) error {
×
253

×
254
        return i.ErrorEncrypter.Reextract(extract)
×
255
}
×
256

257
// A compile time check to ensure that RelayingErrorEncrypte implements
258
// the ErrorEncrypter interface.
259
var _ ErrorEncrypter = (*RelayingErrorEncrypter)(nil)
260

261
// RelayingErrorEncrypter is a wrapper type on SphinxErrorEncrypter which
262
// is used to signal that we have special HTLC error handling for this hop.
263
type RelayingErrorEncrypter struct {
264
        ErrorEncrypter
265
}
266

267
// NewRelayingErrorEncrypter returns a blank RelayingErrorEncrypter with
268
// an underlying SphinxErrorEncrypter.
269
func NewRelayingErrorEncrypter() *RelayingErrorEncrypter {
×
270
        return &RelayingErrorEncrypter{
×
271
                ErrorEncrypter: NewSphinxErrorEncrypter(),
×
272
        }
×
273
}
×
274

275
// Type returns the identifier for a relaying error encrypter.
276
func (r *RelayingErrorEncrypter) Type() EncrypterType {
×
277
        return EncrypterTypeRelaying
×
278
}
×
279

280
// Reextract rederives the error encrypter from the currently held EphemeralKey,
281
// relying on the logic in the underlying SphinxErrorEncrypter.
282
func (r *RelayingErrorEncrypter) Reextract(
283
        extract ErrorEncrypterExtracter) error {
×
284

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