Merge pull request #4227 from guggero/signing-abstraction

multi: Remove need for DerivePrivKey from watchtower, brontide, netann and sphinx package
This commit is contained in:
Conner Fromknecht 2020-05-20 16:07:44 -07:00 committed by GitHub
commit 64b8aa8f11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 549 additions and 273 deletions

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@ -32,8 +33,9 @@ var _ net.Conn = (*Conn)(nil)
// remote peer located at address which has remotePub as its long-term static // remote peer located at address which has remotePub as its long-term static
// public key. In the case of a handshake failure, the connection is closed and // public key. In the case of a handshake failure, the connection is closed and
// a non-nil error is returned. // a non-nil error is returned.
func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, func Dial(local keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
dialer func(string, string) (net.Conn, error)) (*Conn, error) { dialer func(string, string) (net.Conn, error)) (*Conn, error) {
ipAddr := netAddr.Address.String() ipAddr := netAddr.Address.String()
var conn net.Conn var conn net.Conn
var err error var err error
@ -44,7 +46,7 @@ func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress,
b := &Conn{ b := &Conn{
conn: conn, conn: conn,
noise: NewBrontideMachine(true, localPriv, netAddr.IdentityKey), noise: NewBrontideMachine(true, local, netAddr.IdentityKey),
} }
// Initiate the handshake by sending the first act to the receiver. // Initiate the handshake by sending the first act to the receiver.

@ -7,7 +7,7 @@ import (
"net" "net"
"time" "time"
"github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/keychain"
) )
// defaultHandshakes is the maximum number of handshakes that can be done in // defaultHandshakes is the maximum number of handshakes that can be done in
@ -20,7 +20,7 @@ const defaultHandshakes = 1000
// details w.r.t the handshake and encryption scheme used within the // details w.r.t the handshake and encryption scheme used within the
// connection. // connection.
type Listener struct { type Listener struct {
localStatic *btcec.PrivateKey localStatic keychain.SingleKeyECDH
tcp *net.TCPListener tcp *net.TCPListener
@ -34,8 +34,9 @@ var _ net.Listener = (*Listener)(nil)
// NewListener returns a new net.Listener which enforces the Brontide scheme // NewListener returns a new net.Listener which enforces the Brontide scheme
// during both initial connection establishment and data transfer. // during both initial connection establishment and data transfer.
func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener, func NewListener(localStatic keychain.SingleKeyECDH,
error) { listenAddr string) (*Listener, error) {
addr, err := net.ResolveTCPAddr("tcp", listenAddr) addr, err := net.ResolveTCPAddr("tcp", listenAddr)
if err != nil { if err != nil {
return nil, err return nil, err

@ -14,6 +14,7 @@ import (
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/keychain"
) )
const ( const (
@ -71,14 +72,9 @@ var (
// ecdh performs an ECDH operation between pub and priv. The returned value is // ecdh performs an ECDH operation between pub and priv. The returned value is
// the sha256 of the compressed shared point. // the sha256 of the compressed shared point.
func ecdh(pub *btcec.PublicKey, priv *btcec.PrivateKey) []byte { func ecdh(pub *btcec.PublicKey, priv keychain.SingleKeyECDH) ([]byte, error) {
s := &btcec.PublicKey{} hash, err := priv.ECDH(pub)
x, y := btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes()) return hash[:], err
s.X = x
s.Y = y
h := sha256.Sum256(s.SerializeCompressed())
return h[:]
} }
// cipherState encapsulates the state for the AEAD which will be used to // cipherState encapsulates the state for the AEAD which will be used to
@ -289,8 +285,8 @@ type handshakeState struct {
initiator bool initiator bool
localStatic *btcec.PrivateKey localStatic keychain.SingleKeyECDH
localEphemeral *btcec.PrivateKey localEphemeral keychain.SingleKeyECDH // nolint (false positive)
remoteStatic *btcec.PublicKey remoteStatic *btcec.PublicKey
remoteEphemeral *btcec.PublicKey remoteEphemeral *btcec.PublicKey
@ -300,11 +296,12 @@ type handshakeState struct {
// with the prologue and protocol name. If this is the responder's handshake // with the prologue and protocol name. If this is the responder's handshake
// state, then the remotePub can be nil. // state, then the remotePub can be nil.
func newHandshakeState(initiator bool, prologue []byte, func newHandshakeState(initiator bool, prologue []byte,
localPub *btcec.PrivateKey, remotePub *btcec.PublicKey) handshakeState { localKey keychain.SingleKeyECDH,
remotePub *btcec.PublicKey) handshakeState {
h := handshakeState{ h := handshakeState{
initiator: initiator, initiator: initiator,
localStatic: localPub, localStatic: localKey,
remoteStatic: remotePub, remoteStatic: remotePub,
} }
@ -322,7 +319,7 @@ func newHandshakeState(initiator bool, prologue []byte,
if initiator { if initiator {
h.mixHash(remotePub.SerializeCompressed()) h.mixHash(remotePub.SerializeCompressed())
} else { } else {
h.mixHash(localPub.PubKey().SerializeCompressed()) h.mixHash(localKey.PubKey().SerializeCompressed())
} }
return h return h
@ -393,11 +390,11 @@ type Machine struct {
// string "lightning" as the prologue. The last parameter is a set of variadic // string "lightning" as the prologue. The last parameter is a set of variadic
// arguments for adding additional options to the brontide Machine // arguments for adding additional options to the brontide Machine
// initialization. // initialization.
func NewBrontideMachine(initiator bool, localPub *btcec.PrivateKey, func NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH,
remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine { remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine {
handshake := newHandshakeState( handshake := newHandshakeState(
initiator, lightningPrologue, localPub, remotePub, initiator, lightningPrologue, localKey, remotePub,
) )
m := &Machine{ m := &Machine{
@ -451,22 +448,25 @@ const (
// //
// -> e, es // -> e, es
func (b *Machine) GenActOne() ([ActOneSize]byte, error) { func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
var ( var actOne [ActOneSize]byte
err error
actOne [ActOneSize]byte
)
// e // e
b.localEphemeral, err = b.ephemeralGen() localEphemeral, err := b.ephemeralGen()
if err != nil { if err != nil {
return actOne, err return actOne, err
} }
b.localEphemeral = &keychain.PrivKeyECDH{
PrivKey: localEphemeral,
}
ephemeral := b.localEphemeral.PubKey().SerializeCompressed() ephemeral := localEphemeral.PubKey().SerializeCompressed()
b.mixHash(ephemeral) b.mixHash(ephemeral)
// es // es
s := ecdh(b.remoteStatic, b.localEphemeral) s, err := ecdh(b.remoteStatic, b.localEphemeral)
if err != nil {
return actOne, err
}
b.mixKey(s[:]) b.mixKey(s[:])
authPayload := b.EncryptAndHash([]byte{}) authPayload := b.EncryptAndHash([]byte{})
@ -508,7 +508,10 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
b.mixHash(b.remoteEphemeral.SerializeCompressed()) b.mixHash(b.remoteEphemeral.SerializeCompressed())
// es // es
s := ecdh(b.remoteEphemeral, b.localStatic) s, err := ecdh(b.remoteEphemeral, b.localStatic)
if err != nil {
return err
}
b.mixKey(s) b.mixKey(s)
// If the initiator doesn't know our static key, then this operation // If the initiator doesn't know our static key, then this operation
@ -524,22 +527,25 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
// //
// <- e, ee // <- e, ee
func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) { func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
var ( var actTwo [ActTwoSize]byte
err error
actTwo [ActTwoSize]byte
)
// e // e
b.localEphemeral, err = b.ephemeralGen() localEphemeral, err := b.ephemeralGen()
if err != nil { if err != nil {
return actTwo, err return actTwo, err
} }
b.localEphemeral = &keychain.PrivKeyECDH{
PrivKey: localEphemeral,
}
ephemeral := b.localEphemeral.PubKey().SerializeCompressed() ephemeral := localEphemeral.PubKey().SerializeCompressed()
b.mixHash(b.localEphemeral.PubKey().SerializeCompressed()) b.mixHash(localEphemeral.PubKey().SerializeCompressed())
// ee // ee
s := ecdh(b.remoteEphemeral, b.localEphemeral) s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
if err != nil {
return actTwo, err
}
b.mixKey(s) b.mixKey(s)
authPayload := b.EncryptAndHash([]byte{}) authPayload := b.EncryptAndHash([]byte{})
@ -580,7 +586,10 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error {
b.mixHash(b.remoteEphemeral.SerializeCompressed()) b.mixHash(b.remoteEphemeral.SerializeCompressed())
// ee // ee
s := ecdh(b.remoteEphemeral, b.localEphemeral) s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
if err != nil {
return err
}
b.mixKey(s) b.mixKey(s)
_, err = b.DecryptAndHash(p[:]) _, err = b.DecryptAndHash(p[:])
@ -600,7 +609,10 @@ func (b *Machine) GenActThree() ([ActThreeSize]byte, error) {
ourPubkey := b.localStatic.PubKey().SerializeCompressed() ourPubkey := b.localStatic.PubKey().SerializeCompressed()
ciphertext := b.EncryptAndHash(ourPubkey) ciphertext := b.EncryptAndHash(ourPubkey)
s := ecdh(b.remoteEphemeral, b.localStatic) s, err := ecdh(b.remoteEphemeral, b.localStatic)
if err != nil {
return actThree, err
}
b.mixKey(s) b.mixKey(s)
authPayload := b.EncryptAndHash([]byte{}) authPayload := b.EncryptAndHash([]byte{})
@ -649,7 +661,10 @@ func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error {
} }
// se // se
se := ecdh(b.remoteStatic, b.localEphemeral) se, err := ecdh(b.remoteStatic, b.localEphemeral)
if err != nil {
return err
}
b.mixKey(se) b.mixKey(se)
if _, err := b.DecryptAndHash(p[:]); err != nil { if _, err := b.DecryptAndHash(p[:]); err != nil {
@ -875,11 +890,11 @@ func (b *Machine) ReadBody(r io.Reader, buf []byte) ([]byte, error) {
// This allows us to log the Machine object without spammy log messages. // This allows us to log the Machine object without spammy log messages.
func (b *Machine) SetCurveToNil() { func (b *Machine) SetCurveToNil() {
if b.localStatic != nil { if b.localStatic != nil {
b.localStatic.Curve = nil b.localStatic.PubKey().Curve = nil
} }
if b.localEphemeral != nil { if b.localEphemeral != nil {
b.localEphemeral.Curve = nil b.localEphemeral.PubKey().Curve = nil
} }
if b.remoteStatic != nil { if b.remoteStatic != nil {

@ -11,6 +11,7 @@ import (
"testing/iotest" "testing/iotest"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@ -25,13 +26,14 @@ func makeListener() (*Listener, *lnwire.NetAddress, error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
localKeyECDH := &keychain.PrivKeyECDH{PrivKey: localPriv}
// Having a port of ":0" means a random port, and interface will be // Having a port of ":0" means a random port, and interface will be
// chosen for our listener. // chosen for our listener.
addr := "localhost:0" addr := "localhost:0"
// Our listener will be local, and the connection remote. // Our listener will be local, and the connection remote.
listener, err := NewListener(localPriv, addr) listener, err := NewListener(localKeyECDH, addr)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -57,13 +59,14 @@ func establishTestConnection() (net.Conn, net.Conn, func(), error) {
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
remoteKeyECDH := &keychain.PrivKeyECDH{PrivKey: remotePriv}
// Initiate a connection with a separate goroutine, and listen with our // Initiate a connection with a separate goroutine, and listen with our
// main one. If both errors are nil, then encryption+auth was // main one. If both errors are nil, then encryption+auth was
// successful. // successful.
remoteConnChan := make(chan maybeNetConn, 1) remoteConnChan := make(chan maybeNetConn, 1)
go func() { go func() {
remoteConn, err := Dial(remotePriv, netAddr, net.Dial) remoteConn, err := Dial(remoteKeyECDH, netAddr, net.Dial)
remoteConnChan <- maybeNetConn{remoteConn, err} remoteConnChan <- maybeNetConn{remoteConn, err}
}() }()
@ -190,9 +193,10 @@ func TestConcurrentHandshakes(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to generate private key: %v", err) t.Fatalf("unable to generate private key: %v", err)
} }
remoteKeyECDH := &keychain.PrivKeyECDH{PrivKey: remotePriv}
go func() { go func() {
remoteConn, err := Dial(remotePriv, netAddr, net.Dial) remoteConn, err := Dial(remoteKeyECDH, netAddr, net.Dial)
connChan <- maybeNetConn{remoteConn, err} connChan <- maybeNetConn{remoteConn, err}
}() }()
@ -314,8 +318,10 @@ func TestBolt0008TestVectors(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to decode hex: %v", err) t.Fatalf("unable to decode hex: %v", err)
} }
initiatorPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(), initiatorPriv, _ := btcec.PrivKeyFromBytes(
initiatorKeyBytes) btcec.S256(), initiatorKeyBytes,
)
initiatorKeyECDH := &keychain.PrivKeyECDH{PrivKey: initiatorPriv}
// We'll then do the same for the responder. // We'll then do the same for the responder.
responderKeyBytes, err := hex.DecodeString("212121212121212121212121" + responderKeyBytes, err := hex.DecodeString("212121212121212121212121" +
@ -323,8 +329,10 @@ func TestBolt0008TestVectors(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to decode hex: %v", err) t.Fatalf("unable to decode hex: %v", err)
} }
responderPriv, responderPub := btcec.PrivKeyFromBytes(btcec.S256(), responderPriv, responderPub := btcec.PrivKeyFromBytes(
responderKeyBytes) btcec.S256(), responderKeyBytes,
)
responderKeyECDH := &keychain.PrivKeyECDH{PrivKey: responderPriv}
// With the initiator's key data parsed, we'll now define a custom // With the initiator's key data parsed, we'll now define a custom
// EphemeralGenerator function for the state machine to ensure that the // EphemeralGenerator function for the state machine to ensure that the
@ -355,10 +363,12 @@ func TestBolt0008TestVectors(t *testing.T) {
// Finally, we'll create both brontide state machines, so we can begin // Finally, we'll create both brontide state machines, so we can begin
// our test. // our test.
initiator := NewBrontideMachine(true, initiatorPriv, responderPub, initiator := NewBrontideMachine(
initiatorEphemeral) true, initiatorKeyECDH, responderPub, initiatorEphemeral,
responder := NewBrontideMachine(false, responderPriv, nil, )
responderEphemeral) responder := NewBrontideMachine(
false, responderKeyECDH, nil, responderEphemeral,
)
// We'll start with the initiator generating the initial payload for // We'll start with the initiator generating the initial payload for
// act one. This should consist of exactly 50 bytes. We'll assert that // act one. This should consist of exactly 50 bytes. We'll assert that

2
go.mod

@ -37,7 +37,7 @@ require (
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
github.com/lightningnetwork/lightning-onion v1.0.1 github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea
github.com/lightningnetwork/lnd/cert v1.0.2 github.com/lightningnetwork/lnd/cert v1.0.2
github.com/lightningnetwork/lnd/queue v1.0.4 github.com/lightningnetwork/lnd/queue v1.0.4
github.com/lightningnetwork/lnd/ticker v1.0.0 github.com/lightningnetwork/lnd/ticker v1.0.0

4
go.sum

@ -159,8 +159,8 @@ github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0= github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d h1:QWD/5MPnaZfUVP7P8wLa4M8Td2DI7XXHXt2vhVtUgGI= github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d h1:QWD/5MPnaZfUVP7P8wLa4M8Td2DI7XXHXt2vhVtUgGI=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604= github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea h1:oCj48NQ8u7Vz+MmzHqt0db6mxcFZo3Ho7M5gCJauY/k=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=

@ -13,6 +13,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
) )
@ -84,7 +85,8 @@ func initTestExtracter() {
// db and no garbage collection. // db and no garbage collection.
func newOnionProcessor(t *testing.T) *hop.OnionProcessor { func newOnionProcessor(t *testing.T) *hop.OnionProcessor {
sphinxRouter := sphinx.NewRouter( sphinxRouter := sphinx.NewRouter(
sphinxPrivKey, &bitcoinCfg.SimNetParams, sphinx.NewMemoryReplayLog(), &keychain.PrivKeyECDH{PrivKey: sphinxPrivKey},
&bitcoinCfg.SimNetParams, sphinx.NewMemoryReplayLog(),
) )
if err := sphinxRouter.Start(); err != nil { if err := sphinxRouter.Start(); err != nil {

@ -247,7 +247,9 @@ func (b *BtcWalletKeyRing) DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error) {
// passed key descriptor. // passed key descriptor.
// //
// NOTE: This is part of the keychain.SecretKeyRing interface. // NOTE: This is part of the keychain.SecretKeyRing interface.
func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error) { func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (
*btcec.PrivateKey, error) {
var key *btcec.PrivateKey var key *btcec.PrivateKey
db := b.wallet.Database() db := b.wallet.Database()
@ -345,21 +347,21 @@ func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateK
return key, nil return key, nil
} }
// ScalarMult performs a scalar multiplication (ECDH-like operation) between // ECDH performs a scalar multiplication (ECDH-like operation) between the
// the target key descriptor and remote public key. The output returned will be // target key descriptor and remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If // the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following // k is our private key, and P is the public key, we perform the following
// operation: // operation:
// //
// sx := k*P s := sha256(sx.SerializeCompressed()) // sx := k*P s := sha256(sx.SerializeCompressed())
// //
// NOTE: This is part of the keychain.SecretKeyRing interface. // NOTE: This is part of the keychain.ECDHRing interface.
func (b *BtcWalletKeyRing) ScalarMult(keyDesc KeyDescriptor, func (b *BtcWalletKeyRing) ECDH(keyDesc KeyDescriptor,
pub *btcec.PublicKey) ([]byte, error) { pub *btcec.PublicKey) ([32]byte, error) {
privKey, err := b.DerivePrivKey(keyDesc) privKey, err := b.DerivePrivKey(keyDesc)
if err != nil { if err != nil {
return nil, err return [32]byte{}, err
} }
s := &btcec.PublicKey{} s := &btcec.PublicKey{}
@ -369,5 +371,34 @@ func (b *BtcWalletKeyRing) ScalarMult(keyDesc KeyDescriptor,
h := sha256.Sum256(s.SerializeCompressed()) h := sha256.Sum256(s.SerializeCompressed())
return h[:], nil return h, nil
}
// SignDigest signs the given SHA256 message digest with the private key
// described in the key descriptor.
//
// NOTE: This is part of the keychain.DigestSignerRing interface.
func (b *BtcWalletKeyRing) SignDigest(keyDesc KeyDescriptor,
digest [32]byte) (*btcec.Signature, error) {
privKey, err := b.DerivePrivKey(keyDesc)
if err != nil {
return nil, err
}
return privKey.Sign(digest[:])
}
// SignDigestCompact signs the given SHA256 message digest with the private key
// described in the key descriptor and returns the signature in the compact,
// public key recoverable format.
//
// NOTE: This is part of the keychain.DigestSignerRing interface.
func (b *BtcWalletKeyRing) SignDigestCompact(keyDesc KeyDescriptor,
digest [32]byte) ([]byte, error) {
privKey, err := b.DerivePrivKey(keyDesc)
if err != nil {
return nil, err
}
return btcec.SignCompact(btcec.S256(), privKey, digest[:], true)
} }

@ -167,8 +167,8 @@ type KeyRing interface {
DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error) DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error)
} }
// SecretKeyRing is a similar to the regular KeyRing interface, but it is also // SecretKeyRing is a ring similar to the regular KeyRing interface, but it is
// able to derive *private keys*. As this is a super-set of the regular // also able to derive *private keys*. As this is a super-set of the regular
// KeyRing, we also expect the SecretKeyRing to implement the fully KeyRing // KeyRing, we also expect the SecretKeyRing to implement the fully KeyRing
// interface. The methods in this struct may be used to extract the node key in // interface. The methods in this struct may be used to extract the node key in
// order to accept inbound network connections, or to do manual signing for // order to accept inbound network connections, or to do manual signing for
@ -176,22 +176,74 @@ type KeyRing interface {
type SecretKeyRing interface { type SecretKeyRing interface {
KeyRing KeyRing
ECDHRing
DigestSignerRing
// DerivePrivKey attempts to derive the private key that corresponds to // DerivePrivKey attempts to derive the private key that corresponds to
// the passed key descriptor. If the public key is set, then this // the passed key descriptor. If the public key is set, then this
// method will perform an in-order scan over the key set, with a max of // method will perform an in-order scan over the key set, with a max of
// MaxKeyRangeScan keys. In order for this to work, the caller MUST set // MaxKeyRangeScan keys. In order for this to work, the caller MUST set
// the KeyFamily within the partially populated KeyLocator. // the KeyFamily within the partially populated KeyLocator.
DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error) DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error)
}
// ScalarMult performs a scalar multiplication (ECDH-like operation) // DigestSignerRing is an interface that abstracts away basic low-level ECDSA
// between the target key descriptor and remote public key. The output // signing on keys within a key ring.
type DigestSignerRing interface {
// SignDigest signs the given SHA256 message digest with the private key
// described in the key descriptor.
SignDigest(keyDesc KeyDescriptor, digest [32]byte) (*btcec.Signature,
error)
// SignDigestCompact signs the given SHA256 message digest with the
// private key described in the key descriptor and returns the signature
// in the compact, public key recoverable format.
SignDigestCompact(keyDesc KeyDescriptor, digest [32]byte) ([]byte, error)
}
// SingleKeyDigestSigner is an abstraction interface that hides the
// implementation of the low-level ECDSA signing operations by wrapping a
// single, specific private key.
type SingleKeyDigestSigner interface {
// PubKey returns the public key of the wrapped private key.
PubKey() *btcec.PublicKey
// SignDigest signs the given SHA256 message digest with the wrapped
// private key.
SignDigest(digest [32]byte) (*btcec.Signature, error)
// SignDigestCompact signs the given SHA256 message digest with the
// wrapped private key and returns the signature in the compact, public
// key recoverable format.
SignDigestCompact(digest [32]byte) ([]byte, error)
}
// ECDHRing is an interface that abstracts away basic low-level ECDH shared key
// generation on keys within a key ring.
type ECDHRing interface {
// ECDH performs a scalar multiplication (ECDH-like operation) between
// the target key descriptor and remote public key. The output
// returned will be the sha256 of the resulting shared point serialized // returned will be the sha256 of the resulting shared point serialized
// in compressed format. If k is our private key, and P is the public // in compressed format. If k is our private key, and P is the public
// key, we perform the following operation: // key, we perform the following operation:
// //
// sx := k*P // sx := k*P
// s := sha256(sx.SerializeCompressed()) // s := sha256(sx.SerializeCompressed())
ScalarMult(keyDesc KeyDescriptor, pubKey *btcec.PublicKey) ([]byte, error) ECDH(keyDesc KeyDescriptor, pubKey *btcec.PublicKey) ([32]byte, error)
}
// SingleKeyECDH is an abstraction interface that hides the implementation of an
// ECDH operation by wrapping a single, specific private key.
type SingleKeyECDH interface {
// PubKey returns the public key of the wrapped private key.
PubKey() *btcec.PublicKey
// ECDH performs a scalar multiplication (ECDH-like operation) between
// the wrapped private key and remote public key. The output returned
// will be the sha256 of the resulting shared point serialized in
// compressed format.
ECDH(pubKey *btcec.PublicKey) ([32]byte, error)
} }
// TODO(roasbeef): extend to actually support scalar mult of key? // TODO(roasbeef): extend to actually support scalar mult of key?

82
keychain/ecdh.go Normal file

@ -0,0 +1,82 @@
package keychain
import (
"crypto/sha256"
"github.com/btcsuite/btcd/btcec"
)
// NewPubKeyECDH wraps the given key of the key ring so it adheres to the
// SingleKeyECDH interface.
func NewPubKeyECDH(keyDesc KeyDescriptor, ecdh ECDHRing) *PubKeyECDH {
return &PubKeyECDH{
keyDesc: keyDesc,
ecdh: ecdh,
}
}
// PubKeyECDH is an implementation of the SingleKeyECDH interface. It wraps an
// ECDH key ring so it can perform ECDH shared key generation against a single
// abstracted away private key.
type PubKeyECDH struct {
keyDesc KeyDescriptor
ecdh ECDHRing
}
// PubKey returns the public key of the private key that is abstracted away by
// the interface.
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PubKeyECDH) PubKey() *btcec.PublicKey {
return p.keyDesc.PubKey
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// abstracted private key and a remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PubKeyECDH) ECDH(pubKey *btcec.PublicKey) ([32]byte, error) {
return p.ecdh.ECDH(p.keyDesc, pubKey)
}
// PrivKeyECDH is an implementation of the SingleKeyECDH in which we do have the
// full private key. This can be used to wrap a temporary key to conform to the
// SingleKeyECDH interface.
type PrivKeyECDH struct {
// PrivKey is the private key that is used for the ECDH operation.
PrivKey *btcec.PrivateKey
}
// PubKey returns the public key of the private key that is abstracted away by
// the interface.
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PrivKeyECDH) PubKey() *btcec.PublicKey {
return p.PrivKey.PubKey()
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// abstracted private key and a remote public key. The output returned will be
// the sha256 of the resulting shared point serialized in compressed format. If
// k is our private key, and P is the public key, we perform the following
// operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the SingleKeyECDH interface.
func (p *PrivKeyECDH) ECDH(pub *btcec.PublicKey) ([32]byte, error) {
s := &btcec.PublicKey{}
s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, p.PrivKey.D.Bytes())
return sha256.Sum256(s.SerializeCompressed()), nil
}
var _ SingleKeyECDH = (*PubKeyECDH)(nil)
var _ SingleKeyECDH = (*PrivKeyECDH)(nil)

56
keychain/signer.go Normal file

@ -0,0 +1,56 @@
package keychain
import "github.com/btcsuite/btcd/btcec"
func NewPubKeyDigestSigner(keyDesc KeyDescriptor,
signer DigestSignerRing) *PubKeyDigestSigner {
return &PubKeyDigestSigner{
keyDesc: keyDesc,
digestSigner: signer,
}
}
type PubKeyDigestSigner struct {
keyDesc KeyDescriptor
digestSigner DigestSignerRing
}
func (p *PubKeyDigestSigner) PubKey() *btcec.PublicKey {
return p.keyDesc.PubKey
}
func (p *PubKeyDigestSigner) SignDigest(digest [32]byte) (*btcec.Signature,
error) {
return p.digestSigner.SignDigest(p.keyDesc, digest)
}
func (p *PubKeyDigestSigner) SignDigestCompact(digest [32]byte) ([]byte,
error) {
return p.digestSigner.SignDigestCompact(p.keyDesc, digest)
}
type PrivKeyDigestSigner struct {
PrivKey *btcec.PrivateKey
}
func (p *PrivKeyDigestSigner) PubKey() *btcec.PublicKey {
return p.PrivKey.PubKey()
}
func (p *PrivKeyDigestSigner) SignDigest(digest [32]byte) (*btcec.Signature,
error) {
return p.PrivKey.Sign(digest[:])
}
func (p *PrivKeyDigestSigner) SignDigestCompact(digest [32]byte) ([]byte,
error) {
return btcec.SignCompact(btcec.S256(), p.PrivKey, digest[:], true)
}
var _ SingleKeyDigestSigner = (*PubKeyDigestSigner)(nil)
var _ SingleKeyDigestSigner = (*PrivKeyDigestSigner)(nil)

33
lnd.go

@ -27,7 +27,6 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet"
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
@ -471,18 +470,17 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
cfg.registeredChains.RegisterChain(primaryChain, activeChainControl) cfg.registeredChains.RegisterChain(primaryChain, activeChainControl)
// TODO(roasbeef): add rotation // TODO(roasbeef): add rotation
idPrivKey, err := activeChainControl.wallet.DerivePrivKey(keychain.KeyDescriptor{ idKeyDesc, err := activeChainControl.keyRing.DeriveKey(
KeyLocator: keychain.KeyLocator{ keychain.KeyLocator{
Family: keychain.KeyFamilyNodeKey, Family: keychain.KeyFamilyNodeKey,
Index: 0, Index: 0,
}, },
}) )
if err != nil { if err != nil {
err := fmt.Errorf("unable to derive node private key: %v", err) err := fmt.Errorf("error deriving node key: %v", err)
ltndLog.Error(err) ltndLog.Error(err)
return err return err
} }
idPrivKey.Curve = btcec.S256()
if cfg.Tor.Active { if cfg.Tor.Active {
srvrLog.Infof("Proxying all network traffic via Tor "+ srvrLog.Infof("Proxying all network traffic via Tor "+
@ -545,17 +543,14 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
} }
defer towerDB.Close() defer towerDB.Close()
towerPrivKey, err := activeChainControl.wallet.DerivePrivKey( towerKeyDesc, err := activeChainControl.keyRing.DeriveKey(
keychain.KeyDescriptor{ keychain.KeyLocator{
KeyLocator: keychain.KeyLocator{ Family: keychain.KeyFamilyTowerID,
Family: keychain.KeyFamilyTowerID, Index: 0,
Index: 0,
},
}, },
) )
if err != nil { if err != nil {
err := fmt.Errorf("unable to derive watchtower "+ err := fmt.Errorf("error deriving tower key: %v", err)
"private key: %v", err)
ltndLog.Error(err) ltndLog.Error(err)
return err return err
} }
@ -570,9 +565,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
lnwallet.WitnessPubKey, false, lnwallet.WitnessPubKey, false,
) )
}, },
NodePrivKey: towerPrivKey, NodeKeyECDH: keychain.NewPubKeyECDH(
PublishTx: activeChainControl.wallet.PublishTransaction, towerKeyDesc, activeChainControl.keyRing,
ChainHash: *activeNetParams.GenesisHash, ),
PublishTx: activeChainControl.wallet.PublishTransaction,
ChainHash: *activeNetParams.GenesisHash,
} }
// If there is a tor controller (user wants auto hidden services), then // If there is a tor controller (user wants auto hidden services), then
@ -612,7 +609,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
// connections. // connections.
server, err := newServer( server, err := newServer(
cfg, cfg.Listeners, chanDB, towerClientDB, activeChainControl, cfg, cfg.Listeners, chanDB, towerClientDB, activeChainControl,
idPrivKey, walletInitParams.ChansToRestore, chainedAcceptor, &idKeyDesc, walletInitParams.ChansToRestore, chainedAcceptor,
torController, torController,
) )
if err != nil { if err != nil {

@ -5,7 +5,6 @@ package signrpc
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/sha256"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -415,24 +414,21 @@ func (s *Server) SignMessage(ctx context.Context,
return nil, fmt.Errorf("a key locator MUST be passed in") return nil, fmt.Errorf("a key locator MUST be passed in")
} }
// Derive the private key we'll be using for signing. // Describe the private key we'll be using for signing.
keyLocator := keychain.KeyLocator{ keyDescriptor := keychain.KeyDescriptor{
Family: keychain.KeyFamily(in.KeyLoc.KeyFamily), KeyLocator: keychain.KeyLocator{
Index: uint32(in.KeyLoc.KeyIndex), Family: keychain.KeyFamily(in.KeyLoc.KeyFamily),
} Index: uint32(in.KeyLoc.KeyIndex),
privKey, err := s.cfg.KeyRing.DerivePrivKey(keychain.KeyDescriptor{ },
KeyLocator: keyLocator,
})
if err != nil {
return nil, fmt.Errorf("can't derive private key: %v", err)
} }
// The signature is over the sha256 hash of the message. // The signature is over the sha256 hash of the message.
digest := chainhash.HashB(in.Msg) var digest [32]byte
copy(digest[:], chainhash.HashB(in.Msg))
// Create the raw ECDSA signature first and convert it to the final wire // Create the raw ECDSA signature first and convert it to the final wire
// format after. // format after.
sig, err := privKey.Sign(digest) sig, err := s.cfg.KeyRing.SignDigest(keyDescriptor, digest)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't sign the hash: %v", err) return nil, fmt.Errorf("can't sign the hash: %v", err)
} }
@ -515,31 +511,15 @@ func (s *Server) DeriveSharedKey(_ context.Context, in *SharedKeyRequest) (
locator.Index = uint32(in.KeyLoc.KeyIndex) locator.Index = uint32(in.KeyLoc.KeyIndex)
} }
// Derive our node's private key from the key ring. // Derive the shared key using ECDH and hashing the serialized
idPrivKey, err := s.cfg.KeyRing.DerivePrivKey(keychain.KeyDescriptor{ // compressed shared point.
KeyLocator: locator, keyDescriptor := keychain.KeyDescriptor{KeyLocator: locator}
}) sharedKeyHash, err := s.cfg.KeyRing.ECDH(keyDescriptor, ephemeralPubkey)
if err != nil { if err != nil {
err := fmt.Errorf("unable to derive node private key: %v", err) err := fmt.Errorf("unable to derive shared key: %v", err)
log.Error(err) log.Error(err)
return nil, err return nil, err
} }
idPrivKey.Curve = btcec.S256()
// Derive the shared key using ECDH and hashing the serialized return &SharedKeyResponse{SharedKey: sharedKeyHash[:]}, nil
// compressed shared point.
sharedKeyHash := ecdh(ephemeralPubkey, idPrivKey)
return &SharedKeyResponse{SharedKey: sharedKeyHash}, nil
}
// ecdh performs an ECDH operation between pub and priv. The returned value is
// the sha256 of the compressed shared point.
func ecdh(pub *btcec.PublicKey, priv *btcec.PrivateKey) []byte {
s := &btcec.PublicKey{}
x, y := btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes())
s.X = x
s.Y = y
h := sha256.Sum256(s.SerializeCompressed())
return h[:]
} }

21
mock.go

@ -359,7 +359,22 @@ func (m *mockSecretKeyRing) DerivePrivKey(keyDesc keychain.KeyDescriptor) (*btce
return m.rootKey, nil return m.rootKey, nil
} }
func (m *mockSecretKeyRing) ScalarMult(keyDesc keychain.KeyDescriptor, func (m *mockSecretKeyRing) ECDH(_ keychain.KeyDescriptor,
pubKey *btcec.PublicKey) ([]byte, error) { pubKey *btcec.PublicKey) ([32]byte, error) {
return nil, nil
return [32]byte{}, nil
} }
func (m *mockSecretKeyRing) SignDigest(_ keychain.KeyDescriptor,
digest [32]byte) (*btcec.Signature, error) {
return m.rootKey.Sign(digest[:])
}
func (m *mockSecretKeyRing) SignDigestCompact(_ keychain.KeyDescriptor,
digest [32]byte) ([]byte, error) {
return btcec.SignCompact(btcec.S256(), m.rootKey, digest[:], true)
}
var _ keychain.SecretKeyRing = (*mockSecretKeyRing)(nil)

@ -14,6 +14,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/netann"
) )
@ -309,6 +310,7 @@ func newManagerCfg(t *testing.T, numChannels int,
if err != nil { if err != nil {
t.Fatalf("unable to generate key pair: %v", err) t.Fatalf("unable to generate key pair: %v", err)
} }
privKeySigner := &keychain.PrivKeyDigestSigner{PrivKey: privKey}
graph := newMockGraph( graph := newMockGraph(
t, numChannels, startEnabled, startEnabled, privKey.PubKey(), t, numChannels, startEnabled, startEnabled, privKey.PubKey(),
@ -320,7 +322,7 @@ func newManagerCfg(t *testing.T, numChannels int,
ChanEnableTimeout: 500 * time.Millisecond, ChanEnableTimeout: 500 * time.Millisecond,
ChanDisableTimeout: time.Second, ChanDisableTimeout: time.Second,
OurPubKey: privKey.PubKey(), OurPubKey: privKey.PubKey(),
MessageSigner: netann.NewNodeSigner(privKey), MessageSigner: netann.NewNodeSigner(privKeySigner),
IsChannelActive: htlcSwitch.HasActiveLink, IsChannelActive: htlcSwitch.HasActiveLink,
ApplyChannelUpdate: graph.ApplyChannelUpdate, ApplyChannelUpdate: graph.ApplyChannelUpdate,
DB: graph, DB: graph,

@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/netann"
@ -30,7 +31,8 @@ func (m *mockSigner) SignMessage(pk *btcec.PublicKey,
var _ lnwallet.MessageSigner = (*mockSigner)(nil) var _ lnwallet.MessageSigner = (*mockSigner)(nil)
var ( var (
privKey, _ = btcec.NewPrivateKey(btcec.S256()) privKey, _ = btcec.NewPrivateKey(btcec.S256())
privKeySigner = &keychain.PrivKeyDigestSigner{PrivKey: privKey}
pubKey = privKey.PubKey() pubKey = privKey.PubKey()
@ -52,35 +54,35 @@ var updateDisableTests = []updateDisableTest{
startEnabled: true, startEnabled: true,
disable: true, disable: true,
startTime: time.Now(), startTime: time.Now(),
signer: netann.NewNodeSigner(privKey), signer: netann.NewNodeSigner(privKeySigner),
}, },
{ {
name: "working signer enabled to enabled", name: "working signer enabled to enabled",
startEnabled: true, startEnabled: true,
disable: false, disable: false,
startTime: time.Now(), startTime: time.Now(),
signer: netann.NewNodeSigner(privKey), signer: netann.NewNodeSigner(privKeySigner),
}, },
{ {
name: "working signer disabled to enabled", name: "working signer disabled to enabled",
startEnabled: false, startEnabled: false,
disable: false, disable: false,
startTime: time.Now(), startTime: time.Now(),
signer: netann.NewNodeSigner(privKey), signer: netann.NewNodeSigner(privKeySigner),
}, },
{ {
name: "working signer disabled to disabled", name: "working signer disabled to disabled",
startEnabled: false, startEnabled: false,
disable: true, disable: true,
startTime: time.Now(), startTime: time.Now(),
signer: netann.NewNodeSigner(privKey), signer: netann.NewNodeSigner(privKeySigner),
}, },
{ {
name: "working signer future monotonicity", name: "working signer future monotonicity",
startEnabled: true, startEnabled: true,
disable: true, disable: true,
startTime: time.Now().Add(time.Hour), // must increment startTime: time.Now().Add(time.Hour), // must increment
signer: netann.NewNodeSigner(privKey), signer: netann.NewNodeSigner(privKeySigner),
}, },
{ {
name: "failing signer", name: "failing signer",

@ -6,25 +6,21 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
) )
// NodeSigner is an implementation of the MessageSigner interface backed by the // NodeSigner is an implementation of the MessageSigner interface backed by the
// identity private key of running lnd node. // identity private key of running lnd node.
type NodeSigner struct { type NodeSigner struct {
privKey *btcec.PrivateKey keySigner keychain.SingleKeyDigestSigner
} }
// NewNodeSigner creates a new instance of the NodeSigner backed by the target // NewNodeSigner creates a new instance of the NodeSigner backed by the target
// private key. // private key.
func NewNodeSigner(key *btcec.PrivateKey) *NodeSigner { func NewNodeSigner(keySigner keychain.SingleKeyDigestSigner) *NodeSigner {
priv := &btcec.PrivateKey{}
priv.Curve = btcec.S256()
priv.PublicKey.X = key.X
priv.PublicKey.Y = key.Y
priv.D = key.D
return &NodeSigner{ return &NodeSigner{
privKey: priv, keySigner: keySigner,
} }
} }
@ -36,13 +32,14 @@ func (n *NodeSigner) SignMessage(pubKey *btcec.PublicKey,
// If this isn't our identity public key, then we'll exit early with an // If this isn't our identity public key, then we'll exit early with an
// error as we can't sign with this key. // error as we can't sign with this key.
if !pubKey.IsEqual(n.privKey.PubKey()) { if !pubKey.IsEqual(n.keySigner.PubKey()) {
return nil, fmt.Errorf("unknown public key") return nil, fmt.Errorf("unknown public key")
} }
// Otherwise, we'll sign the dsha256 of the target message. // Otherwise, we'll sign the dsha256 of the target message.
digest := chainhash.DoubleHashB(msg) var digest [32]byte
sig, err := n.privKey.Sign(digest) copy(digest[:], chainhash.DoubleHashB(msg))
sig, err := n.keySigner.SignDigest(digest)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't sign the message: %v", err) return nil, fmt.Errorf("can't sign the message: %v", err)
} }
@ -63,14 +60,11 @@ func (n *NodeSigner) SignCompact(msg []byte) ([]byte, error) {
// SignDigestCompact signs the provided message digest under the resident // SignDigestCompact signs the provided message digest under the resident
// node's private key. The returned signature is a pubkey-recoverable signature. // node's private key. The returned signature is a pubkey-recoverable signature.
func (n *NodeSigner) SignDigestCompact(hash []byte) ([]byte, error) { func (n *NodeSigner) SignDigestCompact(hash []byte) ([]byte, error) {
var digest [32]byte
copy(digest[:], hash)
// Should the signature reference a compressed public key or not. // keychain.SignDigestCompact returns a pubkey-recoverable signature.
isCompressedKey := true sig, err := n.keySigner.SignDigestCompact(digest)
// btcec.SignCompact returns a pubkey-recoverable signature
sig, err := btcec.SignCompact(
btcec.S256(), n.privKey, hash, isCompressedKey,
)
if err != nil { if err != nil {
return nil, fmt.Errorf("can't sign the hash: %v", err) return nil, fmt.Errorf("can't sign the hash: %v", err)
} }

@ -555,7 +555,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) (
// particular channel. // particular channel.
var selfPolicy *channeldb.ChannelEdgePolicy var selfPolicy *channeldb.ChannelEdgePolicy
if info != nil && bytes.Equal(info.NodeKey1Bytes[:], if info != nil && bytes.Equal(info.NodeKey1Bytes[:],
p.server.identityPriv.PubKey().SerializeCompressed()) { p.server.identityECDH.PubKey().SerializeCompressed()) {
selfPolicy = p1 selfPolicy = p1
} else { } else {

@ -170,7 +170,7 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
// With the heuristic itself created, we can now populate the remainder // With the heuristic itself created, we can now populate the remainder
// of the items that the autopilot agent needs to perform its duties. // of the items that the autopilot agent needs to perform its duties.
self := svr.identityPriv.PubKey() self := svr.identityECDH.PubKey()
pilotCfg := autopilot.Config{ pilotCfg := autopilot.Config{
Self: self, Self: self,
Heuristic: weightedAttachment, Heuristic: weightedAttachment,

@ -1433,7 +1433,7 @@ func (r *rpcServer) ConnectPeer(ctx context.Context,
} }
// Connections to ourselves are disallowed for obvious reasons. // Connections to ourselves are disallowed for obvious reasons.
if pubKey.IsEqual(r.server.identityPriv.PubKey()) { if pubKey.IsEqual(r.server.identityECDH.PubKey()) {
return nil, fmt.Errorf("cannot make connection to self") return nil, fmt.Errorf("cannot make connection to self")
} }
@ -1781,7 +1781,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
// Making a channel to ourselves wouldn't be of any use, so we // Making a channel to ourselves wouldn't be of any use, so we
// explicitly disallow them. // explicitly disallow them.
if nodePubKey.IsEqual(r.server.identityPriv.PubKey()) { if nodePubKey.IsEqual(r.server.identityECDH.PubKey()) {
return nil, fmt.Errorf("cannot open channel to self") return nil, fmt.Errorf("cannot open channel to self")
} }
@ -2406,7 +2406,7 @@ func (r *rpcServer) GetInfo(ctx context.Context,
} }
nPendingChannels := uint32(len(pendingChannels)) nPendingChannels := uint32(len(pendingChannels))
idPub := r.server.identityPriv.PubKey().SerializeCompressed() idPub := r.server.identityECDH.PubKey().SerializeCompressed()
encodedIDPub := hex.EncodeToString(idPub) encodedIDPub := hex.EncodeToString(idPub)
bestHash, bestHeight, err := r.server.cc.chainIO.GetBestBlock() bestHash, bestHeight, err := r.server.cc.chainIO.GetBestBlock()
@ -4637,7 +4637,7 @@ func (r *rpcServer) GetTransactions(ctx context.Context,
// To remain backwards compatible with the old api, default to the // To remain backwards compatible with the old api, default to the
// special case end height which will return transactions from the start // special case end height which will return transactions from the start
// height until the chain tip, including unconfirmed transactions. // height until the chain tip, including unconfirmed transactions.
var endHeight int32 = btcwallet.UnconfirmedHeight var endHeight = btcwallet.UnconfirmedHeight
// If the user has provided an end height, we overwrite our default. // If the user has provided an end height, we overwrite our default.
if req.EndHeight != 0 { if req.EndHeight != 0 {

@ -41,6 +41,7 @@ import (
"github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
@ -135,9 +136,9 @@ type server struct {
cfg *Config cfg *Config
// identityPriv is the private key used to authenticate any incoming // identityECDH is an ECDH capable wrapper for the private key used
// connections. // to authenticate any incoming connections.
identityPriv *btcec.PrivateKey identityECDH keychain.SingleKeyECDH
// nodeSigner is an implementation of the MessageSigner implementation // nodeSigner is an implementation of the MessageSigner implementation
// that's backed by the identity private key of the running lnd node. // that's backed by the identity private key of the running lnd node.
@ -311,12 +312,12 @@ func parseAddr(address string, netCfg tor.Net) (net.Addr, error) {
// noiseDial is a factory function which creates a connmgr compliant dialing // noiseDial is a factory function which creates a connmgr compliant dialing
// function by returning a closure which includes the server's identity key. // function by returning a closure which includes the server's identity key.
func noiseDial(idPriv *btcec.PrivateKey, func noiseDial(idKey keychain.SingleKeyECDH,
netCfg tor.Net) func(net.Addr) (net.Conn, error) { netCfg tor.Net) func(net.Addr) (net.Conn, error) {
return func(a net.Addr) (net.Conn, error) { return func(a net.Addr) (net.Conn, error) {
lnAddr := a.(*lnwire.NetAddress) lnAddr := a.(*lnwire.NetAddress)
return brontide.Dial(idPriv, lnAddr, netCfg.Dial) return brontide.Dial(idKey, lnAddr, netCfg.Dial)
} }
} }
@ -324,12 +325,18 @@ func noiseDial(idPriv *btcec.PrivateKey,
// passed listener address. // passed listener address.
func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB, func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
towerClientDB *wtdb.ClientDB, cc *chainControl, towerClientDB *wtdb.ClientDB, cc *chainControl,
privKey *btcec.PrivateKey, nodeKeyDesc *keychain.KeyDescriptor,
chansToRestore walletunlocker.ChannelsToRecover, chansToRestore walletunlocker.ChannelsToRecover,
chanPredicate chanacceptor.ChannelAcceptor, chanPredicate chanacceptor.ChannelAcceptor,
torController *tor.Controller) (*server, error) { torController *tor.Controller) (*server, error) {
var err error var (
err error
nodeKeyECDH = keychain.NewPubKeyECDH(*nodeKeyDesc, cc.keyRing)
nodeKeySigner = keychain.NewPubKeyDigestSigner(
*nodeKeyDesc, cc.keyRing,
)
)
listeners := make([]net.Listener, len(listenAddrs)) listeners := make([]net.Listener, len(listenAddrs))
for i, listenAddr := range listenAddrs { for i, listenAddr := range listenAddrs {
@ -337,7 +344,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
// doesn't need to call the general lndResolveTCP function // doesn't need to call the general lndResolveTCP function
// since we are resolving a local address. // since we are resolving a local address.
listeners[i], err = brontide.NewListener( listeners[i], err = brontide.NewListener(
privKey, listenAddr.String(), nodeKeyECDH, listenAddr.String(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -366,14 +373,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
} }
var serializedPubKey [33]byte var serializedPubKey [33]byte
copy(serializedPubKey[:], privKey.PubKey().SerializeCompressed()) copy(serializedPubKey[:], nodeKeyECDH.PubKey().SerializeCompressed())
// Initialize the sphinx router, placing it's persistent replay log in // Initialize the sphinx router, placing it's persistent replay log in
// the same directory as the channel graph database. // the same directory as the channel graph database.
graphDir := chanDB.Path() graphDir := chanDB.Path()
sharedSecretPath := filepath.Join(graphDir, "sphinxreplay.db") sharedSecretPath := filepath.Join(graphDir, "sphinxreplay.db")
replayLog := htlcswitch.NewDecayedLog(sharedSecretPath, cc.chainNotifier) replayLog := htlcswitch.NewDecayedLog(sharedSecretPath, cc.chainNotifier)
sphinxRouter := sphinx.NewRouter(privKey, activeNetParams.Params, replayLog) sphinxRouter := sphinx.NewRouter(
nodeKeyECDH, activeNetParams.Params, replayLog,
)
writeBufferPool := pool.NewWriteBuffer( writeBufferPool := pool.NewWriteBuffer(
pool.DefaultWriteBufferGCInterval, pool.DefaultWriteBufferGCInterval,
@ -425,8 +434,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
channelNotifier: channelnotifier.New(chanDB), channelNotifier: channelnotifier.New(chanDB),
identityPriv: privKey, identityECDH: nodeKeyECDH,
nodeSigner: netann.NewNodeSigner(privKey), nodeSigner: netann.NewNodeSigner(nodeKeySigner),
listenAddrs: listenAddrs, listenAddrs: listenAddrs,
@ -512,7 +521,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
ChanStatusSampleInterval: cfg.ChanStatusSampleInterval, ChanStatusSampleInterval: cfg.ChanStatusSampleInterval,
ChanEnableTimeout: cfg.ChanEnableTimeout, ChanEnableTimeout: cfg.ChanEnableTimeout,
ChanDisableTimeout: cfg.ChanDisableTimeout, ChanDisableTimeout: cfg.ChanDisableTimeout,
OurPubKey: privKey.PubKey(), OurPubKey: nodeKeyECDH.PubKey(),
MessageSigner: s.nodeSigner, MessageSigner: s.nodeSigner,
IsChannelActive: s.htlcSwitch.HasActiveLink, IsChannelActive: s.htlcSwitch.HasActiveLink,
ApplyChannelUpdate: s.applyChannelUpdate, ApplyChannelUpdate: s.applyChannelUpdate,
@ -638,7 +647,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
Features: s.featureMgr.Get(feature.SetNodeAnn), Features: s.featureMgr.Get(feature.SetNodeAnn),
Color: color, Color: color,
} }
copy(selfNode.PubKeyBytes[:], privKey.PubKey().SerializeCompressed()) copy(selfNode.PubKeyBytes[:], nodeKeyECDH.PubKey().SerializeCompressed())
// Based on the disk representation of the node announcement generated // Based on the disk representation of the node announcement generated
// above, we'll generate a node announcement that can go out on the // above, we'll generate a node announcement that can go out on the
@ -651,7 +660,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
// With the announcement generated, we'll sign it to properly // With the announcement generated, we'll sign it to properly
// authenticate the message on the network. // authenticate the message on the network.
authSig, err := netann.SignAnnouncement( authSig, err := netann.SignAnnouncement(
s.nodeSigner, s.identityPriv.PubKey(), nodeAnn, s.nodeSigner, s.identityECDH.PubKey(), nodeAnn,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to generate signature for "+ return nil, fmt.Errorf("unable to generate signature for "+
@ -799,7 +808,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
SubBatchDelay: time.Second * 5, SubBatchDelay: time.Second * 5,
IgnoreHistoricalFilters: cfg.IgnoreHistoricalGossipFilters, IgnoreHistoricalFilters: cfg.IgnoreHistoricalGossipFilters,
}, },
s.identityPriv.PubKey(), s.identityECDH.PubKey(),
) )
s.localChanMgr = &localchans.Manager{ s.localChanMgr = &localchans.Manager{
@ -980,7 +989,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
} }
s.fundingMgr, err = newFundingManager(fundingConfig{ s.fundingMgr, err = newFundingManager(fundingConfig{
IDKey: privKey.PubKey(), IDKey: nodeKeyECDH.PubKey(),
Wallet: cc.wallet, Wallet: cc.wallet,
PublishTransaction: cc.wallet.PublishTransaction, PublishTransaction: cc.wallet.PublishTransaction,
Notifier: cc.chainNotifier, Notifier: cc.chainNotifier,
@ -988,7 +997,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
SignMessage: func(pubKey *btcec.PublicKey, SignMessage: func(pubKey *btcec.PublicKey,
msg []byte) (input.Signature, error) { msg []byte) (input.Signature, error) {
if pubKey.IsEqual(privKey.PubKey()) { if pubKey.IsEqual(nodeKeyECDH.PubKey()) {
return s.nodeSigner.SignMessage(pubKey, msg) return s.nodeSigner.SignMessage(pubKey, msg)
} }
@ -1001,7 +1010,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
optionalFields ...discovery.OptionalMsgField) chan error { optionalFields ...discovery.OptionalMsgField) chan error {
return s.authGossiper.ProcessLocalAnnouncement( return s.authGossiper.ProcessLocalAnnouncement(
msg, privKey.PubKey(), optionalFields..., msg, nodeKeyECDH.PubKey(), optionalFields...,
) )
}, },
NotifyWhenOnline: s.NotifyWhenOnline, NotifyWhenOnline: s.NotifyWhenOnline,
@ -1227,7 +1236,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
OnAccept: s.InboundPeerConnected, OnAccept: s.InboundPeerConnected,
RetryDuration: time.Second * 5, RetryDuration: time.Second * 5,
TargetOutbound: 100, TargetOutbound: 100,
Dial: noiseDial(s.identityPriv, s.cfg.net), Dial: noiseDial(s.identityECDH, s.cfg.net),
OnConnection: s.OutboundPeerConnected, OnConnection: s.OutboundPeerConnected,
}) })
if err != nil { if err != nil {
@ -2020,7 +2029,7 @@ func (s *server) createNewHiddenService() error {
Color: newNodeAnn.RGBColor, Color: newNodeAnn.RGBColor,
AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(), AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(),
} }
copy(selfNode.PubKeyBytes[:], s.identityPriv.PubKey().SerializeCompressed()) copy(selfNode.PubKeyBytes[:], s.identityECDH.PubKey().SerializeCompressed())
if err := s.chanDB.ChannelGraph().SetSourceNode(selfNode); err != nil { if err := s.chanDB.ChannelGraph().SetSourceNode(selfNode); err != nil {
return fmt.Errorf("can't set self node: %v", err) return fmt.Errorf("can't set self node: %v", err)
} }
@ -2050,7 +2059,7 @@ func (s *server) genNodeAnnouncement(refresh bool,
// Otherwise, we'll sign a new update after applying all of the passed // Otherwise, we'll sign a new update after applying all of the passed
// modifiers. // modifiers.
err := netann.SignNodeAnnouncement( err := netann.SignNodeAnnouncement(
s.nodeSigner, s.identityPriv.PubKey(), s.currentNodeAnn, s.nodeSigner, s.identityECDH.PubKey(), s.currentNodeAnn,
modifiers..., modifiers...,
) )
if err != nil { if err != nil {
@ -2102,7 +2111,7 @@ func (s *server) establishPersistentConnections() error {
// TODO(roasbeef): instead iterate over link nodes and query graph for // TODO(roasbeef): instead iterate over link nodes and query graph for
// each of the nodes. // each of the nodes.
selfPub := s.identityPriv.PubKey().SerializeCompressed() selfPub := s.identityECDH.PubKey().SerializeCompressed()
err = sourceNode.ForEachChannel(nil, func( err = sourceNode.ForEachChannel(nil, func(
tx kvdb.ReadTx, tx kvdb.ReadTx,
chanInfo *channeldb.ChannelEdgeInfo, chanInfo *channeldb.ChannelEdgeInfo,
@ -2552,7 +2561,7 @@ func (s *server) InboundPeerConnected(conn net.Conn) {
// not of the same type of the new connection (inbound), then // not of the same type of the new connection (inbound), then
// we'll close out the new connection s.t there's only a single // we'll close out the new connection s.t there's only a single
// connection between us. // connection between us.
localPub := s.identityPriv.PubKey() localPub := s.identityECDH.PubKey()
if !connectedPeer.inbound && if !connectedPeer.inbound &&
!shouldDropLocalConnection(localPub, nodePub) { !shouldDropLocalConnection(localPub, nodePub) {
@ -2663,7 +2672,7 @@ func (s *server) OutboundPeerConnected(connReq *connmgr.ConnReq, conn net.Conn)
// not of the same type of the new connection (outbound), then // not of the same type of the new connection (outbound), then
// we'll close out the new connection s.t there's only a single // we'll close out the new connection s.t there's only a single
// connection between us. // connection between us.
localPub := s.identityPriv.PubKey() localPub := s.identityECDH.PubKey()
if connectedPeer.inbound && if connectedPeer.inbound &&
shouldDropLocalConnection(localPub, nodePub) { shouldDropLocalConnection(localPub, nodePub) {
@ -3264,7 +3273,7 @@ func (s *server) ConnectToPeer(addr *lnwire.NetAddress, perm bool) error {
// notify the caller if the connection attempt has failed. Otherwise, it will be // notify the caller if the connection attempt has failed. Otherwise, it will be
// closed. // closed.
func (s *server) connectToPeer(addr *lnwire.NetAddress, errChan chan<- error) { func (s *server) connectToPeer(addr *lnwire.NetAddress, errChan chan<- error) {
conn, err := brontide.Dial(s.identityPriv, addr, s.cfg.net.Dial) conn, err := brontide.Dial(s.identityECDH, addr, s.cfg.net.Dial)
if err != nil { if err != nil {
srvrLog.Errorf("Unable to connect to %v: %v", addr, err) srvrLog.Errorf("Unable to connect to %v: %v", addr, err)
select { select {
@ -3467,7 +3476,7 @@ func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error)
func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) ( func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) (
*lnwire.ChannelUpdate, error) { *lnwire.ChannelUpdate, error) {
ourPubKey := s.identityPriv.PubKey().SerializeCompressed() ourPubKey := s.identityECDH.PubKey().SerializeCompressed()
return func(cid lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) { return func(cid lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
info, edge1, edge2, err := s.chanRouter.GetChannelByID(cid) info, edge1, edge2, err := s.chanRouter.GetChannelByID(cid)
if err != nil { if err != nil {
@ -3483,7 +3492,7 @@ func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) (
// applyChannelUpdate applies the channel update to the different sub-systems of // applyChannelUpdate applies the channel update to the different sub-systems of
// the server. // the server.
func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate) error { func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate) error {
pubKey := s.identityPriv.PubKey() pubKey := s.identityECDH.PubKey()
errChan := s.authGossiper.ProcessLocalAnnouncement(update, pubKey) errChan := s.authGossiper.ProcessLocalAnnouncement(update, pubKey)
select { select {
case err := <-errChan: case err := <-errChan:

@ -103,10 +103,13 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx,
updateChan func(a, b *channeldb.OpenChannel)) (*peer, *lnwallet.LightningChannel, updateChan func(a, b *channeldb.OpenChannel)) (*peer, *lnwallet.LightningChannel,
*lnwallet.LightningChannel, func(), error) { *lnwallet.LightningChannel, func(), error) {
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
alicesPrivKey) btcec.S256(), alicesPrivKey,
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), )
bobsPrivKey) aliceKeySigner := &keychain.PrivKeyDigestSigner{PrivKey: aliceKeyPriv}
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
btcec.S256(), bobsPrivKey,
)
channelCapacity := btcutil.Amount(10 * 1e8) channelCapacity := btcutil.Amount(10 * 1e8)
channelBal := channelCapacity / 2 channelBal := channelCapacity / 2
@ -402,7 +405,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx,
} }
s.htlcSwitch = htlcSwitch s.htlcSwitch = htlcSwitch
nodeSignerAlice := netann.NewNodeSigner(aliceKeyPriv) nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
const chanActiveTimeout = time.Minute const chanActiveTimeout = time.Minute

@ -5,10 +5,10 @@ import (
"net" "net"
"time" "time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/tor" "github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/lookout" "github.com/lightningnetwork/lnd/watchtower/lookout"
) )
@ -63,9 +63,9 @@ type Config struct {
// successfully sent funds can be received. // successfully sent funds can be received.
NewAddress func() (btcutil.Address, error) NewAddress func() (btcutil.Address, error)
// NodePrivKey is private key to be used in accepting new brontide // NodeKeyECDH is the ECDH capable wrapper of the key to be used in
// connections. // accepting new brontide connections.
NodePrivKey *btcec.PrivateKey NodeKeyECDH keychain.SingleKeyECDH
// PublishTx provides the ability to send a signed transaction to the // PublishTx provides the ability to send a signed transaction to the
// network. // network.

@ -72,7 +72,7 @@ func New(cfg *Config) (*Standalone, error) {
listeners := make([]net.Listener, 0, len(cfg.ListenAddrs)) listeners := make([]net.Listener, 0, len(cfg.ListenAddrs))
for _, listenAddr := range cfg.ListenAddrs { for _, listenAddr := range cfg.ListenAddrs {
listener, err := brontide.NewListener( listener, err := brontide.NewListener(
cfg.NodePrivKey, listenAddr.String(), cfg.NodeKeyECDH, listenAddr.String(),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -85,7 +85,7 @@ func New(cfg *Config) (*Standalone, error) {
server, err := wtserver.New(&wtserver.Config{ server, err := wtserver.New(&wtserver.Config{
ChainHash: cfg.ChainHash, ChainHash: cfg.ChainHash,
DB: cfg.DB, DB: cfg.DB,
NodePrivKey: cfg.NodePrivKey, NodeKeyECDH: cfg.NodeKeyECDH,
Listeners: listeners, Listeners: listeners,
ReadTimeout: cfg.ReadTimeout, ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout, WriteTimeout: cfg.WriteTimeout,
@ -190,7 +190,7 @@ func (w *Standalone) createNewHiddenService() error {
// //
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface. // NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
func (w *Standalone) PubKey() *btcec.PublicKey { func (w *Standalone) PubKey() *btcec.PublicKey {
return w.cfg.NodePrivKey.PubKey() return w.cfg.NodeKeyECDH.PubKey()
} }
// ListeningAddrs returns the listening addresses where the watchtower server // ListeningAddrs returns the listening addresses where the watchtower server

@ -11,6 +11,7 @@ import (
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
@ -132,7 +133,7 @@ type Config struct {
// SecretKeyRing is used to derive the session keys used to communicate // SecretKeyRing is used to derive the session keys used to communicate
// with the tower. The client only stores the KeyLocators internally so // with the tower. The client only stores the KeyLocators internally so
// that we never store private keys on disk. // that we never store private keys on disk.
SecretKeyRing SecretKeyRing SecretKeyRing ECDHKeyRing
// Dial connects to an addr using the specified net and returns the // Dial connects to an addr using the specified net and returns the
// connection object. // connection object.
@ -337,7 +338,7 @@ func New(config *Config) (*TowerClient, error) {
// NOTE: This method should only be used when deserialization of a // NOTE: This method should only be used when deserialization of a
// ClientSession's Tower and SessionPrivKey fields is desired, otherwise, the // ClientSession's Tower and SessionPrivKey fields is desired, otherwise, the
// existing ListClientSessions method should be used. // existing ListClientSessions method should be used.
func getClientSessions(db DB, keyRing SecretKeyRing, forTower *wtdb.TowerID, func getClientSessions(db DB, keyRing ECDHKeyRing, forTower *wtdb.TowerID,
passesFilter func(*wtdb.ClientSession) bool) ( passesFilter func(*wtdb.ClientSession) bool) (
map[wtdb.SessionID]*wtdb.ClientSession, error) { map[wtdb.SessionID]*wtdb.ClientSession, error) {
@ -358,11 +359,14 @@ func getClientSessions(db DB, keyRing SecretKeyRing, forTower *wtdb.TowerID,
} }
s.Tower = tower s.Tower = tower
sessionKey, err := DeriveSessionKey(keyRing, s.KeyIndex) towerKeyDesc, err := keyRing.DeriveKey(keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: s.KeyIndex,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.SessionPrivKey = sessionKey s.SessionKeyECDH = keychain.NewPubKeyECDH(towerKeyDesc, keyRing)
// If an optional filter was provided, use it to filter out any // If an optional filter was provided, use it to filter out any
// undesired sessions. // undesired sessions.
@ -897,10 +901,10 @@ func (c *TowerClient) taskRejected(task *backupTask, curStatus reserveStatus) {
// dial connects the peer at addr using privKey as our secret key for the // dial connects the peer at addr using privKey as our secret key for the
// connection. The connection will use the configured Net's resolver to resolve // connection. The connection will use the configured Net's resolver to resolve
// the address for either Tor or clear net connections. // the address for either Tor or clear net connections.
func (c *TowerClient) dial(privKey *btcec.PrivateKey, func (c *TowerClient) dial(localKey keychain.SingleKeyECDH,
addr *lnwire.NetAddress) (wtserver.Peer, error) { addr *lnwire.NetAddress) (wtserver.Peer, error) {
return c.cfg.AuthDial(privKey, addr, c.cfg.Dial) return c.cfg.AuthDial(localKey, addr, c.cfg.Dial)
} }
// readMessage receives and parses the next message from the given Peer. An // readMessage receives and parses the next message from the given Peer. An

@ -101,10 +101,10 @@ func (m *mockNet) ResolveTCPAddr(network string, address string) (*net.TCPAddr,
panic("not implemented") panic("not implemented")
} }
func (m *mockNet) AuthDial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, func (m *mockNet) AuthDial(local keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error) { dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error) {
localPk := localPriv.PubKey() localPk := local.PubKey()
localAddr := &net.TCPAddr{ localAddr := &net.TCPAddr{
IP: net.IP{0x32, 0x31, 0x30, 0x29}, IP: net.IP{0x32, 0x31, 0x30, 0x29},
Port: 36723, Port: 36723,
@ -401,6 +401,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
if err != nil { if err != nil {
t.Fatalf("Unable to generate tower private key: %v", err) t.Fatalf("Unable to generate tower private key: %v", err)
} }
privKeyECDH := &keychain.PrivKeyECDH{PrivKey: privKey}
towerPubKey := privKey.PubKey() towerPubKey := privKey.PubKey()
@ -416,7 +417,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
DB: serverDB, DB: serverDB,
ReadTimeout: timeout, ReadTimeout: timeout,
WriteTimeout: timeout, WriteTimeout: timeout,
NodePrivKey: privKey, NodeKeyECDH: privKeyECDH,
NewAddress: func() (btcutil.Address, error) { NewAddress: func() (btcutil.Address, error) {
return addr, nil return addr, nil
}, },
@ -519,7 +520,7 @@ func (h *testHarness) startClient() {
h.t.Fatalf("Unable to resolve tower TCP addr: %v", err) h.t.Fatalf("Unable to resolve tower TCP addr: %v", err)
} }
towerAddr := &lnwire.NetAddress{ towerAddr := &lnwire.NetAddress{
IdentityKey: h.serverCfg.NodePrivKey.PubKey(), IdentityKey: h.serverCfg.NodeKeyECDH.PubKey(),
Address: towerTCPAddr, Address: towerTCPAddr,
} }

@ -1,24 +0,0 @@
package wtclient
import (
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/keychain"
)
// DeriveSessionKey accepts an session key index for an existing session and
// derives the HD private key to be used to authenticate the brontide transport
// and authenticate requests sent to the tower. The key will use the
// keychain.KeyFamilyTowerSession and the provided index, giving a BIP43
// derivation path of:
//
// * m/1017'/coinType'/8/0/index
func DeriveSessionKey(keyRing SecretKeyRing,
index uint32) (*btcec.PrivateKey, error) {
return keyRing.DerivePrivKey(keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: index,
},
})
}

@ -102,20 +102,24 @@ type Dial func(net, addr string) (net.Conn, error)
// AuthDialer connects to a remote node using an authenticated transport, such as // AuthDialer connects to a remote node using an authenticated transport, such as
// brontide. The dialer argument is used to specify a resolver, which allows // brontide. The dialer argument is used to specify a resolver, which allows
// this method to be used over Tor or clear net connections. // this method to be used over Tor or clear net connections.
type AuthDialer func(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, type AuthDialer func(localKey keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error) dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error)
// AuthDial is the watchtower client's default method of dialing. // AuthDial is the watchtower client's default method of dialing.
func AuthDial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, func AuthDial(localKey keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error) { dialer func(string, string) (net.Conn, error)) (wtserver.Peer, error) {
return brontide.Dial(localPriv, netAddr, dialer) return brontide.Dial(localKey, netAddr, dialer)
} }
// SecretKeyRing abstracts the ability to derive HD private keys given a // ECDHKeyRing abstracts the ability to derive shared ECDH keys given a
// description of the derivation path. // description of the derivation path of a private key.
type SecretKeyRing interface { type ECDHKeyRing interface {
// DerivePrivKey derives the private key from the root seed using a keychain.ECDHRing
// key descriptor specifying the key's derivation path.
DerivePrivKey(loc keychain.KeyDescriptor) (*btcec.PrivateKey, error) // DeriveKey attempts to derive an arbitrary key specified by the
// passed KeyLocator. This may be used in several recovery scenarios,
// or when manually rotating something like our current default node
// key.
DeriveKey(keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error)
} }

@ -5,8 +5,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
@ -44,7 +44,7 @@ type NegotiatorConfig struct {
// SecretKeyRing allows the client to derive new session private keys // SecretKeyRing allows the client to derive new session private keys
// when attempting to negotiate session with a tower. // when attempting to negotiate session with a tower.
SecretKeyRing SecretKeyRing SecretKeyRing ECDHKeyRing
// Candidates is an abstract set of tower candidates that the negotiator // Candidates is an abstract set of tower candidates that the negotiator
// will traverse serially when attempting to negotiate a new session. // will traverse serially when attempting to negotiate a new session.
@ -58,7 +58,8 @@ type NegotiatorConfig struct {
// Dial initiates an outbound brontide connection to the given address // Dial initiates an outbound brontide connection to the given address
// using a specified private key. The peer is returned in the event of a // using a specified private key. The peer is returned in the event of a
// successful connection. // successful connection.
Dial func(*btcec.PrivateKey, *lnwire.NetAddress) (wtserver.Peer, error) Dial func(keychain.SingleKeyECDH, *lnwire.NetAddress) (wtserver.Peer,
error)
// SendMessage writes a wtwire message to remote peer. // SendMessage writes a wtwire message to remote peer.
SendMessage func(wtserver.Peer, wtwire.Message) error SendMessage func(wtserver.Peer, wtwire.Message) error
@ -315,13 +316,21 @@ func (n *sessionNegotiator) createSession(tower *wtdb.Tower,
return ErrNoTowerAddrs return ErrNoTowerAddrs
} }
sessionPriv, err := DeriveSessionKey(n.cfg.SecretKeyRing, keyIndex) sessionKeyDesc, err := n.cfg.SecretKeyRing.DeriveKey(
keychain.KeyLocator{
Family: keychain.KeyFamilyTowerSession,
Index: keyIndex,
},
)
if err != nil { if err != nil {
return err return err
} }
sessionKey := keychain.NewPubKeyECDH(
sessionKeyDesc, n.cfg.SecretKeyRing,
)
for _, lnAddr := range tower.LNAddrs() { for _, lnAddr := range tower.LNAddrs() {
err = n.tryAddress(sessionPriv, keyIndex, tower, lnAddr) err := n.tryAddress(sessionKey, keyIndex, tower, lnAddr)
switch { switch {
case err == ErrPermanentTowerFailure: case err == ErrPermanentTowerFailure:
// TODO(conner): report to iterator? can then be reset // TODO(conner): report to iterator? can then be reset
@ -346,11 +355,11 @@ func (n *sessionNegotiator) createSession(tower *wtdb.Tower,
// The address should belong to the tower's set of addresses. This method only // The address should belong to the tower's set of addresses. This method only
// returns true if all steps succeed and the new session has been persisted, and // returns true if all steps succeed and the new session has been persisted, and
// fails otherwise. // fails otherwise.
func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey, func (n *sessionNegotiator) tryAddress(sessionKey keychain.SingleKeyECDH,
keyIndex uint32, tower *wtdb.Tower, lnAddr *lnwire.NetAddress) error { keyIndex uint32, tower *wtdb.Tower, lnAddr *lnwire.NetAddress) error {
// Connect to the tower address using our generated session key. // Connect to the tower address using our generated session key.
conn, err := n.cfg.Dial(privKey, lnAddr) conn, err := n.cfg.Dial(sessionKey, lnAddr)
if err != nil { if err != nil {
return err return err
} }
@ -417,9 +426,7 @@ func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey,
// TODO(conner): validate reward address // TODO(conner): validate reward address
rewardPkScript := createSessionReply.Data rewardPkScript := createSessionReply.Data
sessionID := wtdb.NewSessionIDFromPubKey( sessionID := wtdb.NewSessionIDFromPubKey(sessionKey.PubKey())
privKey.PubKey(),
)
clientSession := &wtdb.ClientSession{ clientSession := &wtdb.ClientSession{
ClientSessionBody: wtdb.ClientSessionBody{ ClientSessionBody: wtdb.ClientSessionBody{
TowerID: tower.ID, TowerID: tower.ID,
@ -428,7 +435,7 @@ func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey,
RewardPkScript: rewardPkScript, RewardPkScript: rewardPkScript,
}, },
Tower: tower, Tower: tower,
SessionPrivKey: privKey, SessionKeyECDH: sessionKey,
ID: sessionID, ID: sessionID,
} }

@ -6,9 +6,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtserver" "github.com/lightningnetwork/lnd/watchtower/wtserver"
@ -41,8 +41,8 @@ type sessionQueueConfig struct {
// Dial allows the client to dial the tower using it's public key and // Dial allows the client to dial the tower using it's public key and
// net address. // net address.
Dial func(*btcec.PrivateKey, Dial func(keychain.SingleKeyECDH, *lnwire.NetAddress) (wtserver.Peer,
*lnwire.NetAddress) (wtserver.Peer, error) error)
// SendMessage encodes, encrypts, and writes a message to the given peer. // SendMessage encodes, encrypts, and writes a message to the given peer.
SendMessage func(wtserver.Peer, wtwire.Message) error SendMessage func(wtserver.Peer, wtwire.Message) error
@ -285,7 +285,7 @@ func (q *sessionQueue) sessionManager() {
// drainBackups attempts to send all pending updates in the queue to the tower. // drainBackups attempts to send all pending updates in the queue to the tower.
func (q *sessionQueue) drainBackups() { func (q *sessionQueue) drainBackups() {
// First, check that we are able to dial this session's tower. // First, check that we are able to dial this session's tower.
conn, err := q.cfg.Dial(q.cfg.ClientSession.SessionPrivKey, q.towerAddr) conn, err := q.cfg.Dial(q.cfg.ClientSession.SessionKeyECDH, q.towerAddr)
if err != nil { if err != nil {
log.Errorf("SessionQueue(%s) unable to dial tower at %v: %v", log.Errorf("SessionQueue(%s) unable to dial tower at %v: %v",
q.ID(), q.towerAddr, err) q.ID(), q.towerAddr, err)

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy" "github.com/lightningnetwork/lnd/watchtower/wtpolicy"
@ -60,12 +60,12 @@ type ClientSession struct {
// tower with TowerID. // tower with TowerID.
Tower *Tower Tower *Tower
// SessionPrivKey is the ephemeral secret key used to connect to the // SessionKeyECDH is the ECDH capable wrapper of the ephemeral secret
// watchtower. // key used to connect to the watchtower.
// //
// NOTE: This value is not serialized. It is derived using the KeyIndex // NOTE: This value is not serialized. It is derived using the KeyIndex
// on startup to avoid storing private keys on disk. // on startup to avoid storing private keys on disk.
SessionPrivKey *btcec.PrivateKey SessionKeyECDH keychain.SingleKeyECDH
} }
// ClientSessionBody represents the primary components of a ClientSession that // ClientSessionBody represents the primary components of a ClientSession that

@ -20,25 +20,56 @@ func NewSecretKeyRing() *SecretKeyRing {
} }
} }
// DerivePrivKey derives the private key for a given key descriptor. If // DeriveKey attempts to derive an arbitrary key specified by the
// this method is called twice with the same argument, it will return the same // passed KeyLocator. This may be used in several recovery scenarios,
// private key. // or when manually rotating something like our current default node
func (m *SecretKeyRing) DerivePrivKey( // key.
desc keychain.KeyDescriptor) (*btcec.PrivateKey, error) { //
// NOTE: This is part of the wtclient.ECDHKeyRing interface.
func (m *SecretKeyRing) DeriveKey(
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
if key, ok := m.keys[desc.KeyLocator]; ok { if key, ok := m.keys[keyLoc]; ok {
return key, nil return keychain.KeyDescriptor{
KeyLocator: keyLoc,
PubKey: key.PubKey(),
}, nil
} }
privKey, err := btcec.NewPrivateKey(btcec.S256()) privKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil { if err != nil {
return nil, err return keychain.KeyDescriptor{}, err
} }
m.keys[desc.KeyLocator] = privKey m.keys[keyLoc] = privKey
return privKey, nil return keychain.KeyDescriptor{
KeyLocator: keyLoc,
PubKey: privKey.PubKey(),
}, nil
}
// ECDH performs a scalar multiplication (ECDH-like operation) between the
// target key descriptor and remote public key. The output returned will be the
// sha256 of the resulting shared point serialized in compressed format. If k is
// our private key, and P is the public key, we perform the following operation:
//
// sx := k*P
// s := sha256(sx.SerializeCompressed())
//
// NOTE: This is part of the wtclient.ECDHKeyRing interface.
func (m *SecretKeyRing) ECDH(keyDesc keychain.KeyDescriptor,
pub *btcec.PublicKey) ([32]byte, error) {
_, err := m.DeriveKey(keyDesc.KeyLocator)
if err != nil {
return [32]byte{}, err
}
privKey := m.keys[keyDesc.KeyLocator]
ecdh := &keychain.PrivKeyECDH{PrivKey: privKey}
return ecdh.ECDH(pub)
} }

@ -8,10 +8,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/wtdb" "github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtwire" "github.com/lightningnetwork/lnd/watchtower/wtwire"
@ -33,9 +33,9 @@ type Config struct {
// storing state updates. // storing state updates.
DB DB DB DB
// NodePrivKey is private key to be used in accepting new brontide // NodeKeyECDH is the the ECDH capable wrapper of the key to be used in
// connections. // accepting new brontide connections.
NodePrivKey *btcec.PrivateKey NodeKeyECDH keychain.SingleKeyECDH
// Listeners specifies which address to which clients may connect. // Listeners specifies which address to which clients may connect.
Listeners []net.Listener Listeners []net.Listener