From eb068bf6667b816d925f3b2758a5425d5b7dad33 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 19 May 2021 09:03:46 +0200 Subject: [PATCH 1/3] multi: unexport session key and add constructor for htlc attempt info --- channeldb/duplicate_payments.go | 2 +- channeldb/mp_payment.go | 23 +++++++++++++++++++++-- channeldb/payment_control_test.go | 2 +- channeldb/payments.go | 4 ++-- channeldb/payments_test.go | 4 +--- routing/control_tower_test.go | 8 +++----- routing/payment_lifecycle.go | 16 ++++++---------- 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/channeldb/duplicate_payments.go b/channeldb/duplicate_payments.go index c26f185a..1a1fdae7 100644 --- a/channeldb/duplicate_payments.go +++ b/channeldb/duplicate_payments.go @@ -181,7 +181,7 @@ func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) { HTLCAttemptInfo: HTLCAttemptInfo{ AttemptID: attempt.attemptID, Route: attempt.route, - SessionKey: attempt.sessionKey, + sessionKey: attempt.sessionKey, }, } diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go index 4d14d504..8603c291 100644 --- a/channeldb/mp_payment.go +++ b/channeldb/mp_payment.go @@ -21,8 +21,8 @@ type HTLCAttemptInfo struct { // AttemptID is the unique ID used for this attempt. AttemptID uint64 - // SessionKey is the ephemeral key used for this attempt. - SessionKey *btcec.PrivateKey + // sessionKey is the ephemeral key used for this attempt. + sessionKey *btcec.PrivateKey // Route is the route attempted to send the HTLC. Route route.Route @@ -38,6 +38,25 @@ type HTLCAttemptInfo struct { Hash *lntypes.Hash } +// NewHtlcAttemptInfo creates a htlc attempt. +func NewHtlcAttemptInfo(attemptID uint64, sessionKey *btcec.PrivateKey, + route route.Route, attemptTime time.Time, + hash *lntypes.Hash) *HTLCAttemptInfo { + + return &HTLCAttemptInfo{ + AttemptID: attemptID, + sessionKey: sessionKey, + Route: route, + AttemptTime: attemptTime, + Hash: hash, + } +} + +// SessionKey returns the ephemeral key used for a htlc attempt. +func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey { + return h.sessionKey +} + // HTLCAttempt contains information about a specific HTLC attempt for a given // payment. It contains the HTLCAttemptInfo used to send the HTLC, as well // as a timestamp and any known outcome of the attempt. diff --git a/channeldb/payment_control_test.go b/channeldb/payment_control_test.go index a524fb0e..a4e9c846 100644 --- a/channeldb/payment_control_test.go +++ b/channeldb/payment_control_test.go @@ -45,7 +45,7 @@ func genInfo() (*PaymentCreationInfo, *HTLCAttemptInfo, }, &HTLCAttemptInfo{ AttemptID: 0, - SessionKey: priv, + sessionKey: priv, Route: *testRoute.Copy(), }, preimage, nil } diff --git a/channeldb/payments.go b/channeldb/payments.go index 72b15ffb..1abe54a5 100644 --- a/channeldb/payments.go +++ b/channeldb/payments.go @@ -919,7 +919,7 @@ func deserializePaymentCreationInfo(r io.Reader) (*PaymentCreationInfo, error) { } func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error { - if err := WriteElements(w, a.SessionKey); err != nil { + if err := WriteElements(w, a.sessionKey); err != nil { return err } @@ -945,7 +945,7 @@ func serializeHTLCAttemptInfo(w io.Writer, a *HTLCAttemptInfo) error { func deserializeHTLCAttemptInfo(r io.Reader) (*HTLCAttemptInfo, error) { a := &HTLCAttemptInfo{} - err := ReadElements(r, &a.SessionKey) + err := ReadElements(r, &a.sessionKey) if err != nil { return nil, err } diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 9fab9a1f..1ef0e7dd 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -70,7 +70,7 @@ func makeFakeInfo() (*PaymentCreationInfo, *HTLCAttemptInfo) { a := &HTLCAttemptInfo{ AttemptID: 44, - SessionKey: priv, + sessionKey: priv, Route: testRoute, AttemptTime: time.Unix(100, 0), Hash: &hash, @@ -124,8 +124,6 @@ func TestSentPaymentSerialization(t *testing.T) { s.Route = route.Route{} if !reflect.DeepEqual(s, newWireInfo) { - s.SessionKey.Curve = nil - newWireInfo.SessionKey.Curve = nil t.Fatalf("Payments do not match after "+ "serialization/deserialization %v vs %v", spew.Sdump(s), spew.Sdump(newWireInfo), diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go index 15706eaf..8b7082db 100644 --- a/routing/control_tower_test.go +++ b/routing/control_tower_test.go @@ -331,11 +331,9 @@ func genInfo() (*channeldb.PaymentCreationInfo, *channeldb.HTLCAttemptInfo, CreationTime: time.Unix(time.Now().Unix(), 0), PaymentRequest: []byte("hola"), }, - &channeldb.HTLCAttemptInfo{ - AttemptID: 1, - SessionKey: priv, - Route: testRoute, - }, preimage, nil + channeldb.NewHtlcAttemptInfo( + 1, priv, testRoute, time.Time{}, nil, + ), preimage, nil } func genPreimage() ([32]byte, error) { diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 90dd684b..4703a627 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -499,7 +499,7 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( // Regenerate the circuit for this attempt. _, circuit, err := generateSphinxPacket( - &attempt.Route, hash[:], attempt.SessionKey, + &attempt.Route, hash[:], attempt.SessionKey(), ) if err != nil { return nil, err @@ -677,15 +677,11 @@ func (p *shardHandler) createNewPaymentAttempt(rt *route.Route, lastShard bool) rt.Hops[0].ChannelID, ) - // We now have all the information needed to populate - // the current attempt information. - attempt := &channeldb.HTLCAttemptInfo{ - AttemptID: attemptID, - AttemptTime: p.router.cfg.Clock.Now(), - SessionKey: sessionKey, - Route: *rt, - Hash: &hash, - } + // We now have all the information needed to populate the current + // attempt information. + attempt := channeldb.NewHtlcAttemptInfo( + attemptID, sessionKey, *rt, p.router.cfg.Clock.Now(), &hash, + ) return firstHop, htlcAdd, attempt, nil } From 8071ebb16a0836deb7c17cfd51b1d63ff00f1e43 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 19 May 2021 09:03:52 +0200 Subject: [PATCH 2/3] channeldb: read raw htlc attempt session key --- channeldb/duplicate_payments.go | 2 +- channeldb/mp_payment.go | 17 ++++++++++++----- channeldb/mp_payment_test.go | 27 +++++++++++++++++++++++++++ channeldb/payment_control_test.go | 18 ++++++++---------- channeldb/payments_test.go | 11 ++++------- 5 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 channeldb/mp_payment_test.go diff --git a/channeldb/duplicate_payments.go b/channeldb/duplicate_payments.go index 1a1fdae7..7dad7df7 100644 --- a/channeldb/duplicate_payments.go +++ b/channeldb/duplicate_payments.go @@ -53,7 +53,7 @@ type duplicateHTLCAttemptInfo struct { attemptID uint64 // sessionKey is the ephemeral key used for this attempt. - sessionKey *btcec.PrivateKey + sessionKey [btcec.PrivKeyBytesLen]byte // route is the route attempted to send the HTLC. route route.Route diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go index 8603c291..6c41647e 100644 --- a/channeldb/mp_payment.go +++ b/channeldb/mp_payment.go @@ -21,8 +21,10 @@ type HTLCAttemptInfo struct { // AttemptID is the unique ID used for this attempt. AttemptID uint64 - // sessionKey is the ephemeral key used for this attempt. - sessionKey *btcec.PrivateKey + // sessionKey is the raw bytes ephemeral key used for this attempt. + // These bytes are lazily read off disk to save ourselves the expensive + // EC operations used by btcec.PrivKeyFromBytes. + sessionKey [btcec.PrivKeyBytesLen]byte // Route is the route attempted to send the HTLC. Route route.Route @@ -43,18 +45,23 @@ func NewHtlcAttemptInfo(attemptID uint64, sessionKey *btcec.PrivateKey, route route.Route, attemptTime time.Time, hash *lntypes.Hash) *HTLCAttemptInfo { + var scratch [btcec.PrivKeyBytesLen]byte + copy(scratch[:], sessionKey.Serialize()) + return &HTLCAttemptInfo{ AttemptID: attemptID, - sessionKey: sessionKey, + sessionKey: scratch, Route: route, AttemptTime: attemptTime, Hash: hash, } } -// SessionKey returns the ephemeral key used for a htlc attempt. +// SessionKey returns the ephemeral key used for a htlc attempt. This function +// performs expensive ec-ops to obtain the session key. func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey { - return h.sessionKey + priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), h.sessionKey[:]) + return priv } // HTLCAttempt contains information about a specific HTLC attempt for a given diff --git a/channeldb/mp_payment_test.go b/channeldb/mp_payment_test.go new file mode 100644 index 00000000..eb5f0f48 --- /dev/null +++ b/channeldb/mp_payment_test.go @@ -0,0 +1,27 @@ +package channeldb + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestLazySessionKeyDeserialize tests that we can read htlc attempt session +// keys that were previously serialized as a private key as raw bytes. +func TestLazySessionKeyDeserialize(t *testing.T) { + var b bytes.Buffer + + // Serialize as a private key. + err := WriteElements(&b, priv) + require.NoError(t, err) + + // Deserialize into [btcec.PrivKeyBytesLen]byte. + attempt := HTLCAttemptInfo{} + err = ReadElements(&b, &attempt.sessionKey) + require.NoError(t, err) + require.Zero(t, b.Len()) + + sessionKey := attempt.SessionKey() + require.Equal(t, priv, sessionKey) +} diff --git a/channeldb/payment_control_test.go b/channeldb/payment_control_test.go index a4e9c846..4c9fcf00 100644 --- a/channeldb/payment_control_test.go +++ b/channeldb/payment_control_test.go @@ -37,17 +37,15 @@ func genInfo() (*PaymentCreationInfo, *HTLCAttemptInfo, } rhash := sha256.Sum256(preimage[:]) + attempt := NewHtlcAttemptInfo( + 0, priv, *testRoute.Copy(), time.Time{}, nil, + ) return &PaymentCreationInfo{ - PaymentIdentifier: rhash, - Value: testRoute.ReceiverAmt(), - CreationTime: time.Unix(time.Now().Unix(), 0), - PaymentRequest: []byte("hola"), - }, - &HTLCAttemptInfo{ - AttemptID: 0, - sessionKey: priv, - Route: *testRoute.Copy(), - }, preimage, nil + PaymentIdentifier: rhash, + Value: testRoute.ReceiverAmt(), + CreationTime: time.Unix(time.Now().Unix(), 0), + PaymentRequest: []byte("hola"), + }, attempt, preimage, nil } // TestPaymentControlSwitchFail checks that payment status returns to Failed diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 1ef0e7dd..37b20b51 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -68,13 +68,10 @@ func makeFakeInfo() (*PaymentCreationInfo, *HTLCAttemptInfo) { PaymentRequest: []byte(""), } - a := &HTLCAttemptInfo{ - AttemptID: 44, - sessionKey: priv, - Route: testRoute, - AttemptTime: time.Unix(100, 0), - Hash: &hash, - } + a := NewHtlcAttemptInfo( + 44, priv, testRoute, time.Unix(100, 0), &hash, + ) + return c, a } From 64333c0303f69ed121f164b1c25d8444e761b213 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 19 May 2021 09:03:53 +0200 Subject: [PATCH 3/3] channeldb: cache htlc attempt session key --- channeldb/mp_payment.go | 27 +++++++++++++++++++-------- channeldb/payments_test.go | 4 ++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/channeldb/mp_payment.go b/channeldb/mp_payment.go index 6c41647e..49a4b4df 100644 --- a/channeldb/mp_payment.go +++ b/channeldb/mp_payment.go @@ -26,6 +26,11 @@ type HTLCAttemptInfo struct { // EC operations used by btcec.PrivKeyFromBytes. sessionKey [btcec.PrivKeyBytesLen]byte + // cachedSessionKey is our fully deserialized sesionKey. This value + // may be nil if the attempt has just been read from disk and its + // session key has not been used yet. + cachedSessionKey *btcec.PrivateKey + // Route is the route attempted to send the HTLC. Route route.Route @@ -49,19 +54,25 @@ func NewHtlcAttemptInfo(attemptID uint64, sessionKey *btcec.PrivateKey, copy(scratch[:], sessionKey.Serialize()) return &HTLCAttemptInfo{ - AttemptID: attemptID, - sessionKey: scratch, - Route: route, - AttemptTime: attemptTime, - Hash: hash, + AttemptID: attemptID, + sessionKey: scratch, + cachedSessionKey: sessionKey, + Route: route, + AttemptTime: attemptTime, + Hash: hash, } } // SessionKey returns the ephemeral key used for a htlc attempt. This function -// performs expensive ec-ops to obtain the session key. +// performs expensive ec-ops to obtain the session key if it is not cached. func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey { - priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), h.sessionKey[:]) - return priv + if h.cachedSessionKey == nil { + h.cachedSessionKey, _ = btcec.PrivKeyFromBytes( + btcec.S256(), h.sessionKey[:], + ) + } + + return h.cachedSessionKey } // HTLCAttempt contains information about a specific HTLC attempt for a given diff --git a/channeldb/payments_test.go b/channeldb/payments_test.go index 37b20b51..370a1d9c 100644 --- a/channeldb/payments_test.go +++ b/channeldb/payments_test.go @@ -120,6 +120,10 @@ func TestSentPaymentSerialization(t *testing.T) { newWireInfo.Route = route.Route{} s.Route = route.Route{} + // Call session key method to set our cached session key so we can use + // DeepEqual, and assert that our key equals the original key. + require.Equal(t, s.cachedSessionKey, newWireInfo.SessionKey()) + if !reflect.DeepEqual(s, newWireInfo) { t.Fatalf("Payments do not match after "+ "serialization/deserialization %v vs %v",