Skip to content

Commit b989225

Browse files
htlcswitch: split shared secret extraction and encrypter instantiation
Preparation for the instantiation of an attributable error encrypter. This gets rid of the sphinx encrypter instantiation in OnionProcessor. This would otherwise be problematic when an attributable encrypter would need to be created there without having access to the error structure. Co-authored-by: Joost Jager <[email protected]>
1 parent 3ff0717 commit b989225

File tree

12 files changed

+262
-168
lines changed

12 files changed

+262
-168
lines changed

htlcswitch/circuit.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,17 +199,18 @@ func (c *PaymentCircuit) Decode(r io.Reader) error {
199199

200200
case hop.EncrypterTypeSphinx:
201201
// Sphinx encrypter was used as this is a forwarded HTLC.
202-
c.ErrorEncrypter = hop.NewSphinxErrorEncrypter()
202+
c.ErrorEncrypter = hop.NewSphinxErrorEncrypterUninitialized()
203203

204204
case hop.EncrypterTypeMock:
205205
// Test encrypter.
206206
c.ErrorEncrypter = NewMockObfuscator()
207207

208208
case hop.EncrypterTypeIntroduction:
209-
c.ErrorEncrypter = hop.NewIntroductionErrorEncrypter()
209+
c.ErrorEncrypter =
210+
hop.NewIntroductionErrorEncrypterUninitialized()
210211

211212
case hop.EncrypterTypeRelaying:
212-
c.ErrorEncrypter = hop.NewRelayingErrorEncrypter()
213+
c.ErrorEncrypter = hop.NewRelayingErrorEncrypterUninitialized()
213214

214215
default:
215216
return UnknownEncrypterType(encrypterType)

htlcswitch/circuit_map.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,9 @@ type CircuitMapConfig struct {
210210
FetchClosedChannels func(
211211
pendingOnly bool) ([]*channeldb.ChannelCloseSummary, error)
212212

213-
// ExtractErrorEncrypter derives the shared secret used to encrypt
214-
// errors from the obfuscator's ephemeral public key.
215-
ExtractErrorEncrypter hop.ErrorEncrypterExtracter
213+
// ExtractSharedSecret derives the shared secret used to encrypt errors
214+
// from the obfuscator's ephemeral public key.
215+
ExtractSharedSecret hop.SharedSecretGenerator
216216

217217
// CheckResolutionMsg checks whether a given resolution message exists
218218
// for the passed CircuitKey.
@@ -632,9 +632,7 @@ func (cm *circuitMap) decodeCircuit(v []byte) (*PaymentCircuit, error) {
632632

633633
// Otherwise, we need to reextract the encrypter, so that the shared
634634
// secret is rederived from what was decoded.
635-
err := circuit.ErrorEncrypter.Reextract(
636-
cm.cfg.ExtractErrorEncrypter,
637-
)
635+
err := circuit.ErrorEncrypter.Reextract(cm.cfg.ExtractSharedSecret)
638636
if err != nil {
639637
return nil, err
640638
}

htlcswitch/circuit_test.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ func initTestExtracter() {
6565
onionProcessor := newOnionProcessor(nil)
6666
defer onionProcessor.Stop()
6767

68-
obfuscator, _ := onionProcessor.ExtractErrorEncrypter(
68+
sharedSecret, failCode := onionProcessor.ExtractSharedSecret(
6969
testEphemeralKey,
7070
)
7171

72-
sphinxExtracter, ok := obfuscator.(*hop.SphinxErrorEncrypter)
73-
if !ok {
74-
panic("did not extract sphinx error encrypter")
72+
if failCode != lnwire.CodeNone {
73+
panic("did not extract shared secret")
7574
}
7675

77-
testExtracter = sphinxExtracter
76+
testExtracter = hop.NewSphinxErrorEncrypter(
77+
testEphemeralKey, sharedSecret,
78+
)
7879

7980
// We also set this error extracter on startup, otherwise it will be nil
8081
// at compile-time.
@@ -106,10 +107,10 @@ func newCircuitMap(t *testing.T, resMsg bool) (*htlcswitch.CircuitMapConfig,
106107

107108
db := makeCircuitDB(t, "")
108109
circuitMapCfg := &htlcswitch.CircuitMapConfig{
109-
DB: db,
110-
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
111-
FetchClosedChannels: db.ChannelStateDB().FetchClosedChannels,
112-
ExtractErrorEncrypter: onionProcessor.ExtractErrorEncrypter,
110+
DB: db,
111+
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
112+
FetchClosedChannels: db.ChannelStateDB().FetchClosedChannels,
113+
ExtractSharedSecret: onionProcessor.ExtractSharedSecret,
113114
}
114115

115116
if resMsg {
@@ -216,7 +217,7 @@ func TestHalfCircuitSerialization(t *testing.T) {
216217
// encrypters, this will be a NOP.
217218
if circuit2.ErrorEncrypter != nil {
218219
err := circuit2.ErrorEncrypter.Reextract(
219-
onionProcessor.ExtractErrorEncrypter,
220+
onionProcessor.ExtractSharedSecret,
220221
)
221222
if err != nil {
222223
t.Fatalf("unable to reextract sphinx error "+
@@ -643,11 +644,11 @@ func restartCircuitMap(t *testing.T, cfg *htlcswitch.CircuitMapConfig) (
643644
// Reinitialize circuit map with same db path.
644645
db := makeCircuitDB(t, dbPath)
645646
cfg2 := &htlcswitch.CircuitMapConfig{
646-
DB: db,
647-
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
648-
FetchClosedChannels: db.ChannelStateDB().FetchClosedChannels,
649-
ExtractErrorEncrypter: cfg.ExtractErrorEncrypter,
650-
CheckResolutionMsg: cfg.CheckResolutionMsg,
647+
DB: db,
648+
FetchAllOpenChannels: db.ChannelStateDB().FetchAllOpenChannels,
649+
FetchClosedChannels: db.ChannelStateDB().FetchClosedChannels,
650+
ExtractSharedSecret: cfg.ExtractSharedSecret,
651+
CheckResolutionMsg: cfg.CheckResolutionMsg,
651652
}
652653
cm2, err := htlcswitch.NewCircuitMap(cfg2)
653654
require.NoError(t, err, "unable to recreate persistent circuit map")

htlcswitch/hop/error_encryptor.go

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func (e EncrypterType) IsBlinded() bool {
4545
return e == EncrypterTypeIntroduction || e == EncrypterTypeRelaying
4646
}
4747

48-
// ErrorEncrypterExtracter defines a function signature that extracts an
49-
// ErrorEncrypter from an sphinx OnionPacket.
50-
type ErrorEncrypterExtracter func(*btcec.PublicKey) (ErrorEncrypter,
48+
// SharedSecretGenerator defines a function signature that extracts a shared
49+
// secret from an sphinx OnionPacket.
50+
type SharedSecretGenerator func(*btcec.PublicKey) (sphinx.Hash256,
5151
lnwire.FailCode)
5252

5353
// ErrorEncrypter is an interface that is used to encrypt HTLC related errors
@@ -87,12 +87,13 @@ type ErrorEncrypter interface {
8787
// given io.Reader.
8888
Decode(io.Reader) error
8989

90-
// Reextract rederives the encrypter using the extracter, performing an
91-
// ECDH with the sphinx router's key and the ephemeral public key.
90+
// Reextract rederives the encrypter using the shared secret generator,
91+
// performing an ECDH with the sphinx router's key and the ephemeral
92+
// public key.
9293
//
9394
// NOTE: This should be called shortly after Decode to properly
9495
// reinitialize the error encrypter.
95-
Reextract(ErrorEncrypterExtracter) error
96+
Reextract(SharedSecretGenerator) error
9697
}
9798

9899
// SphinxErrorEncrypter is a concrete implementation of both the ErrorEncrypter
@@ -105,20 +106,43 @@ type SphinxErrorEncrypter struct {
105106
EphemeralKey *btcec.PublicKey
106107
}
107108

108-
// NewSphinxErrorEncrypter initializes a blank sphinx error encrypter, that
109-
// should be used to deserialize an encoded SphinxErrorEncrypter. Since the
110-
// actual encrypter is not stored in plaintext while at rest, reconstructing the
111-
// error encrypter requires:
109+
// NewSphinxErrorEncrypterUninitialized initializes a blank sphinx error
110+
// encrypter, that should be used to deserialize an encoded
111+
// SphinxErrorEncrypter. Since the actual encrypter is not stored in plaintext
112+
// while at rest, reconstructing the error encrypter requires:
112113
// 1. Decode: to deserialize the ephemeral public key.
113114
// 2. Reextract: to "unlock" the actual error encrypter using an active
114115
// OnionProcessor.
115-
func NewSphinxErrorEncrypter() *SphinxErrorEncrypter {
116+
func NewSphinxErrorEncrypterUninitialized() *SphinxErrorEncrypter {
116117
return &SphinxErrorEncrypter{
117118
OnionErrorEncrypter: nil,
118119
EphemeralKey: &btcec.PublicKey{},
119120
}
120121
}
121122

123+
// NewSphinxErrorEncrypter creates a new instance of a SphinxErrorEncrypter,
124+
// initialized with the provided shared secret. To deserialize an encoded
125+
// SphinxErrorEncrypter, use the NewSphinxErrorEncrypterUninitialized
126+
// constructor.
127+
func NewSphinxErrorEncrypter(ephemeralKey *btcec.PublicKey,
128+
sharedSecret sphinx.Hash256) *SphinxErrorEncrypter {
129+
130+
encrypter := &SphinxErrorEncrypter{
131+
EphemeralKey: ephemeralKey,
132+
}
133+
134+
encrypter.initialize(sharedSecret)
135+
136+
return encrypter
137+
}
138+
139+
// initialize creates the underlying instance of the sphinx error encrypter.
140+
func (s *SphinxErrorEncrypter) initialize(sharedSecret sphinx.Hash256) {
141+
s.OnionErrorEncrypter = sphinx.NewOnionErrorEncrypter(
142+
sharedSecret, nil,
143+
)
144+
}
145+
122146
// EncryptFirstHop transforms a concrete failure message into an encrypted
123147
// opaque failure reason. This method will be used at the source that the error
124148
// occurs. It differs from BackwardObfuscate slightly, in that it computes a
@@ -198,10 +222,8 @@ func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
198222
// Reextract rederives the error encrypter from the currently held EphemeralKey.
199223
// This intended to be used shortly after Decode, to fully initialize a
200224
// SphinxErrorEncrypter.
201-
func (s *SphinxErrorEncrypter) Reextract(
202-
extract ErrorEncrypterExtracter) error {
203-
204-
obfuscator, failcode := extract(s.EphemeralKey)
225+
func (s *SphinxErrorEncrypter) Reextract(extract SharedSecretGenerator) error {
226+
sharedSecret, failcode := extract(s.EphemeralKey)
205227
if failcode != lnwire.CodeNone {
206228
// This should never happen, since we already validated that
207229
// this obfuscator can be extracted when it was received in the
@@ -210,13 +232,7 @@ func (s *SphinxErrorEncrypter) Reextract(
210232
"obfuscator, got failcode: %d", failcode)
211233
}
212234

213-
sphinxEncrypter, ok := obfuscator.(*SphinxErrorEncrypter)
214-
if !ok {
215-
return fmt.Errorf("incorrect onion error extracter")
216-
}
217-
218-
// Copy the freshly extracted encrypter.
219-
s.OnionErrorEncrypter = sphinxEncrypter.OnionErrorEncrypter
235+
s.initialize(sharedSecret)
220236

221237
return nil
222238
}
@@ -239,9 +255,25 @@ type IntroductionErrorEncrypter struct {
239255
}
240256

241257
// NewIntroductionErrorEncrypter returns a blank IntroductionErrorEncrypter.
242-
func NewIntroductionErrorEncrypter() *IntroductionErrorEncrypter {
258+
func NewIntroductionErrorEncrypter(ephemeralKey *btcec.PublicKey,
259+
sharedSecret sphinx.Hash256) *IntroductionErrorEncrypter {
260+
261+
return &IntroductionErrorEncrypter{
262+
ErrorEncrypter: NewSphinxErrorEncrypter(
263+
ephemeralKey, sharedSecret,
264+
),
265+
}
266+
}
267+
268+
// NewIntroductionErrorEncrypter returns a blank IntroductionErrorEncrypter.
269+
// Since the actual encrypter is not stored in plaintext
270+
// while at rest, reconstructing the error encrypter requires:
271+
// 1. Decode: to deserialize the ephemeral public key.
272+
// 2. Reextract: to "unlock" the actual error encrypter using an active
273+
// OnionProcessor.
274+
func NewIntroductionErrorEncrypterUninitialized() *IntroductionErrorEncrypter {
243275
return &IntroductionErrorEncrypter{
244-
ErrorEncrypter: NewSphinxErrorEncrypter(),
276+
ErrorEncrypter: NewSphinxErrorEncrypterUninitialized(),
245277
}
246278
}
247279

@@ -253,7 +285,7 @@ func (i *IntroductionErrorEncrypter) Type() EncrypterType {
253285
// Reextract rederives the error encrypter from the currently held EphemeralKey,
254286
// relying on the logic in the underlying SphinxErrorEncrypter.
255287
func (i *IntroductionErrorEncrypter) Reextract(
256-
extract ErrorEncrypterExtracter) error {
288+
extract SharedSecretGenerator) error {
257289

258290
return i.ErrorEncrypter.Reextract(extract)
259291
}
@@ -270,9 +302,26 @@ type RelayingErrorEncrypter struct {
270302

271303
// NewRelayingErrorEncrypter returns a blank RelayingErrorEncrypter with
272304
// an underlying SphinxErrorEncrypter.
273-
func NewRelayingErrorEncrypter() *RelayingErrorEncrypter {
305+
func NewRelayingErrorEncrypter(ephemeralKey *btcec.PublicKey,
306+
sharedSecret sphinx.Hash256) *RelayingErrorEncrypter {
307+
308+
return &RelayingErrorEncrypter{
309+
ErrorEncrypter: NewSphinxErrorEncrypter(
310+
ephemeralKey, sharedSecret,
311+
),
312+
}
313+
}
314+
315+
// NewRelayingErrorEncrypterUninitialized returns a blank RelayingErrorEncrypter
316+
// with an underlying SphinxErrorEncrypter.
317+
// Since the actual encrypter is not stored in plaintext
318+
// while at rest, reconstructing the error encrypter requires:
319+
// 1. Decode: to deserialize the ephemeral public key.
320+
// 2. Reextract: to "unlock" the actual error encrypter using an active
321+
// OnionProcessor.
322+
func NewRelayingErrorEncrypterUninitialized() *RelayingErrorEncrypter {
274323
return &RelayingErrorEncrypter{
275-
ErrorEncrypter: NewSphinxErrorEncrypter(),
324+
ErrorEncrypter: NewSphinxErrorEncrypterUninitialized(),
276325
}
277326
}
278327

@@ -284,7 +333,7 @@ func (r *RelayingErrorEncrypter) Type() EncrypterType {
284333
// Reextract rederives the error encrypter from the currently held EphemeralKey,
285334
// relying on the logic in the underlying SphinxErrorEncrypter.
286335
func (r *RelayingErrorEncrypter) Reextract(
287-
extract ErrorEncrypterExtracter) error {
336+
extract SharedSecretGenerator) error {
288337

289338
return r.ErrorEncrypter.Reextract(extract)
290339
}

0 commit comments

Comments
 (0)