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:
commit
64b8aa8f11
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"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
|
||||
// public key. In the case of a handshake failure, the connection is closed and
|
||||
// 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) {
|
||||
|
||||
ipAddr := netAddr.Address.String()
|
||||
var conn net.Conn
|
||||
var err error
|
||||
@ -44,7 +46,7 @@ func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress,
|
||||
|
||||
b := &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.
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
// 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
|
||||
// connection.
|
||||
type Listener struct {
|
||||
localStatic *btcec.PrivateKey
|
||||
localStatic keychain.SingleKeyECDH
|
||||
|
||||
tcp *net.TCPListener
|
||||
|
||||
@ -34,8 +34,9 @@ var _ net.Listener = (*Listener)(nil)
|
||||
|
||||
// NewListener returns a new net.Listener which enforces the Brontide scheme
|
||||
// during both initial connection establishment and data transfer.
|
||||
func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener,
|
||||
error) {
|
||||
func NewListener(localStatic keychain.SingleKeyECDH,
|
||||
listenAddr string) (*Listener, error) {
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", listenAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -71,14 +72,9 @@ var (
|
||||
|
||||
// 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[:]
|
||||
func ecdh(pub *btcec.PublicKey, priv keychain.SingleKeyECDH) ([]byte, error) {
|
||||
hash, err := priv.ECDH(pub)
|
||||
return hash[:], err
|
||||
}
|
||||
|
||||
// cipherState encapsulates the state for the AEAD which will be used to
|
||||
@ -289,8 +285,8 @@ type handshakeState struct {
|
||||
|
||||
initiator bool
|
||||
|
||||
localStatic *btcec.PrivateKey
|
||||
localEphemeral *btcec.PrivateKey
|
||||
localStatic keychain.SingleKeyECDH
|
||||
localEphemeral keychain.SingleKeyECDH // nolint (false positive)
|
||||
|
||||
remoteStatic *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
|
||||
// state, then the remotePub can be nil.
|
||||
func newHandshakeState(initiator bool, prologue []byte,
|
||||
localPub *btcec.PrivateKey, remotePub *btcec.PublicKey) handshakeState {
|
||||
localKey keychain.SingleKeyECDH,
|
||||
remotePub *btcec.PublicKey) handshakeState {
|
||||
|
||||
h := handshakeState{
|
||||
initiator: initiator,
|
||||
localStatic: localPub,
|
||||
localStatic: localKey,
|
||||
remoteStatic: remotePub,
|
||||
}
|
||||
|
||||
@ -322,7 +319,7 @@ func newHandshakeState(initiator bool, prologue []byte,
|
||||
if initiator {
|
||||
h.mixHash(remotePub.SerializeCompressed())
|
||||
} else {
|
||||
h.mixHash(localPub.PubKey().SerializeCompressed())
|
||||
h.mixHash(localKey.PubKey().SerializeCompressed())
|
||||
}
|
||||
|
||||
return h
|
||||
@ -393,11 +390,11 @@ type Machine struct {
|
||||
// string "lightning" as the prologue. The last parameter is a set of variadic
|
||||
// arguments for adding additional options to the brontide Machine
|
||||
// initialization.
|
||||
func NewBrontideMachine(initiator bool, localPub *btcec.PrivateKey,
|
||||
func NewBrontideMachine(initiator bool, localKey keychain.SingleKeyECDH,
|
||||
remotePub *btcec.PublicKey, options ...func(*Machine)) *Machine {
|
||||
|
||||
handshake := newHandshakeState(
|
||||
initiator, lightningPrologue, localPub, remotePub,
|
||||
initiator, lightningPrologue, localKey, remotePub,
|
||||
)
|
||||
|
||||
m := &Machine{
|
||||
@ -451,22 +448,25 @@ const (
|
||||
//
|
||||
// -> e, es
|
||||
func (b *Machine) GenActOne() ([ActOneSize]byte, error) {
|
||||
var (
|
||||
err error
|
||||
actOne [ActOneSize]byte
|
||||
)
|
||||
var actOne [ActOneSize]byte
|
||||
|
||||
// e
|
||||
b.localEphemeral, err = b.ephemeralGen()
|
||||
localEphemeral, err := b.ephemeralGen()
|
||||
if err != nil {
|
||||
return actOne, err
|
||||
}
|
||||
b.localEphemeral = &keychain.PrivKeyECDH{
|
||||
PrivKey: localEphemeral,
|
||||
}
|
||||
|
||||
ephemeral := b.localEphemeral.PubKey().SerializeCompressed()
|
||||
ephemeral := localEphemeral.PubKey().SerializeCompressed()
|
||||
b.mixHash(ephemeral)
|
||||
|
||||
// es
|
||||
s := ecdh(b.remoteStatic, b.localEphemeral)
|
||||
s, err := ecdh(b.remoteStatic, b.localEphemeral)
|
||||
if err != nil {
|
||||
return actOne, err
|
||||
}
|
||||
b.mixKey(s[:])
|
||||
|
||||
authPayload := b.EncryptAndHash([]byte{})
|
||||
@ -508,7 +508,10 @@ func (b *Machine) RecvActOne(actOne [ActOneSize]byte) error {
|
||||
b.mixHash(b.remoteEphemeral.SerializeCompressed())
|
||||
|
||||
// es
|
||||
s := ecdh(b.remoteEphemeral, b.localStatic)
|
||||
s, err := ecdh(b.remoteEphemeral, b.localStatic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.mixKey(s)
|
||||
|
||||
// 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
|
||||
func (b *Machine) GenActTwo() ([ActTwoSize]byte, error) {
|
||||
var (
|
||||
err error
|
||||
actTwo [ActTwoSize]byte
|
||||
)
|
||||
var actTwo [ActTwoSize]byte
|
||||
|
||||
// e
|
||||
b.localEphemeral, err = b.ephemeralGen()
|
||||
localEphemeral, err := b.ephemeralGen()
|
||||
if err != nil {
|
||||
return actTwo, err
|
||||
}
|
||||
b.localEphemeral = &keychain.PrivKeyECDH{
|
||||
PrivKey: localEphemeral,
|
||||
}
|
||||
|
||||
ephemeral := b.localEphemeral.PubKey().SerializeCompressed()
|
||||
b.mixHash(b.localEphemeral.PubKey().SerializeCompressed())
|
||||
ephemeral := localEphemeral.PubKey().SerializeCompressed()
|
||||
b.mixHash(localEphemeral.PubKey().SerializeCompressed())
|
||||
|
||||
// ee
|
||||
s := ecdh(b.remoteEphemeral, b.localEphemeral)
|
||||
s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
|
||||
if err != nil {
|
||||
return actTwo, err
|
||||
}
|
||||
b.mixKey(s)
|
||||
|
||||
authPayload := b.EncryptAndHash([]byte{})
|
||||
@ -580,7 +586,10 @@ func (b *Machine) RecvActTwo(actTwo [ActTwoSize]byte) error {
|
||||
b.mixHash(b.remoteEphemeral.SerializeCompressed())
|
||||
|
||||
// ee
|
||||
s := ecdh(b.remoteEphemeral, b.localEphemeral)
|
||||
s, err := ecdh(b.remoteEphemeral, b.localEphemeral)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.mixKey(s)
|
||||
|
||||
_, err = b.DecryptAndHash(p[:])
|
||||
@ -600,7 +609,10 @@ func (b *Machine) GenActThree() ([ActThreeSize]byte, error) {
|
||||
ourPubkey := b.localStatic.PubKey().SerializeCompressed()
|
||||
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)
|
||||
|
||||
authPayload := b.EncryptAndHash([]byte{})
|
||||
@ -649,7 +661,10 @@ func (b *Machine) RecvActThree(actThree [ActThreeSize]byte) error {
|
||||
}
|
||||
|
||||
// se
|
||||
se := ecdh(b.remoteStatic, b.localEphemeral)
|
||||
se, err := ecdh(b.remoteStatic, b.localEphemeral)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.mixKey(se)
|
||||
|
||||
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.
|
||||
func (b *Machine) SetCurveToNil() {
|
||||
if b.localStatic != nil {
|
||||
b.localStatic.Curve = nil
|
||||
b.localStatic.PubKey().Curve = nil
|
||||
}
|
||||
|
||||
if b.localEphemeral != nil {
|
||||
b.localEphemeral.Curve = nil
|
||||
b.localEphemeral.PubKey().Curve = nil
|
||||
}
|
||||
|
||||
if b.remoteStatic != nil {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"testing/iotest"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
@ -25,13 +26,14 @@ func makeListener() (*Listener, *lnwire.NetAddress, error) {
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
localKeyECDH := &keychain.PrivKeyECDH{PrivKey: localPriv}
|
||||
|
||||
// Having a port of ":0" means a random port, and interface will be
|
||||
// chosen for our listener.
|
||||
addr := "localhost:0"
|
||||
|
||||
// Our listener will be local, and the connection remote.
|
||||
listener, err := NewListener(localPriv, addr)
|
||||
listener, err := NewListener(localKeyECDH, addr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -57,13 +59,14 @@ func establishTestConnection() (net.Conn, net.Conn, func(), error) {
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
remoteKeyECDH := &keychain.PrivKeyECDH{PrivKey: remotePriv}
|
||||
|
||||
// Initiate a connection with a separate goroutine, and listen with our
|
||||
// main one. If both errors are nil, then encryption+auth was
|
||||
// successful.
|
||||
remoteConnChan := make(chan maybeNetConn, 1)
|
||||
go func() {
|
||||
remoteConn, err := Dial(remotePriv, netAddr, net.Dial)
|
||||
remoteConn, err := Dial(remoteKeyECDH, netAddr, net.Dial)
|
||||
remoteConnChan <- maybeNetConn{remoteConn, err}
|
||||
}()
|
||||
|
||||
@ -190,9 +193,10 @@ func TestConcurrentHandshakes(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate private key: %v", err)
|
||||
}
|
||||
remoteKeyECDH := &keychain.PrivKeyECDH{PrivKey: remotePriv}
|
||||
|
||||
go func() {
|
||||
remoteConn, err := Dial(remotePriv, netAddr, net.Dial)
|
||||
remoteConn, err := Dial(remoteKeyECDH, netAddr, net.Dial)
|
||||
connChan <- maybeNetConn{remoteConn, err}
|
||||
}()
|
||||
|
||||
@ -314,8 +318,10 @@ func TestBolt0008TestVectors(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode hex: %v", err)
|
||||
}
|
||||
initiatorPriv, _ := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
initiatorKeyBytes)
|
||||
initiatorPriv, _ := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), initiatorKeyBytes,
|
||||
)
|
||||
initiatorKeyECDH := &keychain.PrivKeyECDH{PrivKey: initiatorPriv}
|
||||
|
||||
// We'll then do the same for the responder.
|
||||
responderKeyBytes, err := hex.DecodeString("212121212121212121212121" +
|
||||
@ -323,8 +329,10 @@ func TestBolt0008TestVectors(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to decode hex: %v", err)
|
||||
}
|
||||
responderPriv, responderPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
responderKeyBytes)
|
||||
responderPriv, responderPub := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), responderKeyBytes,
|
||||
)
|
||||
responderKeyECDH := &keychain.PrivKeyECDH{PrivKey: responderPriv}
|
||||
|
||||
// With the initiator's key data parsed, we'll now define a custom
|
||||
// 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
|
||||
// our test.
|
||||
initiator := NewBrontideMachine(true, initiatorPriv, responderPub,
|
||||
initiatorEphemeral)
|
||||
responder := NewBrontideMachine(false, responderPriv, nil,
|
||||
responderEphemeral)
|
||||
initiator := NewBrontideMachine(
|
||||
true, initiatorKeyECDH, responderPub, initiatorEphemeral,
|
||||
)
|
||||
responder := NewBrontideMachine(
|
||||
false, responderKeyECDH, nil, responderEphemeral,
|
||||
)
|
||||
|
||||
// We'll start with the initiator generating the initial payload for
|
||||
// act one. This should consist of exactly 50 bytes. We'll assert that
|
||||
|
2
go.mod
2
go.mod
@ -37,7 +37,7 @@ require (
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec
|
||||
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200
|
||||
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/queue v1.0.4
|
||||
github.com/lightningnetwork/lnd/ticker v1.0.0
|
||||
|
4
go.sum
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/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/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea h1:oCj48NQ8u7Vz+MmzHqt0db6mxcFZo3Ho7M5gCJauY/k=
|
||||
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/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
|
||||
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/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
@ -84,7 +85,8 @@ func initTestExtracter() {
|
||||
// db and no garbage collection.
|
||||
func newOnionProcessor(t *testing.T) *hop.OnionProcessor {
|
||||
sphinxRouter := sphinx.NewRouter(
|
||||
sphinxPrivKey, &bitcoinCfg.SimNetParams, sphinx.NewMemoryReplayLog(),
|
||||
&keychain.PrivKeyECDH{PrivKey: sphinxPrivKey},
|
||||
&bitcoinCfg.SimNetParams, sphinx.NewMemoryReplayLog(),
|
||||
)
|
||||
|
||||
if err := sphinxRouter.Start(); err != nil {
|
||||
|
@ -247,7 +247,9 @@ func (b *BtcWalletKeyRing) DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error) {
|
||||
// passed key descriptor.
|
||||
//
|
||||
// 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
|
||||
|
||||
db := b.wallet.Database()
|
||||
@ -345,21 +347,21 @@ func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateK
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ScalarMult performs a scalar multiplication (ECDH-like operation) between
|
||||
// the target key descriptor and remote public key. The output returned will be
|
||||
// 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 keychain.SecretKeyRing interface.
|
||||
func (b *BtcWalletKeyRing) ScalarMult(keyDesc KeyDescriptor,
|
||||
pub *btcec.PublicKey) ([]byte, error) {
|
||||
// NOTE: This is part of the keychain.ECDHRing interface.
|
||||
func (b *BtcWalletKeyRing) ECDH(keyDesc KeyDescriptor,
|
||||
pub *btcec.PublicKey) ([32]byte, error) {
|
||||
|
||||
privKey, err := b.DerivePrivKey(keyDesc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return [32]byte{}, err
|
||||
}
|
||||
|
||||
s := &btcec.PublicKey{}
|
||||
@ -369,5 +371,34 @@ func (b *BtcWalletKeyRing) ScalarMult(keyDesc KeyDescriptor,
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// SecretKeyRing is a similar to the regular KeyRing interface, but it is also
|
||||
// able to derive *private keys*. As this is a super-set of the regular
|
||||
// SecretKeyRing is a ring similar to the regular KeyRing interface, but it is
|
||||
// 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
|
||||
// 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
|
||||
@ -176,22 +176,74 @@ type KeyRing interface {
|
||||
type SecretKeyRing interface {
|
||||
KeyRing
|
||||
|
||||
ECDHRing
|
||||
|
||||
DigestSignerRing
|
||||
|
||||
// DerivePrivKey attempts to derive the private key that corresponds to
|
||||
// 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
|
||||
// MaxKeyRangeScan keys. In order for this to work, the caller MUST set
|
||||
// the KeyFamily within the partially populated KeyLocator.
|
||||
DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error)
|
||||
}
|
||||
|
||||
// ScalarMult performs a scalar multiplication (ECDH-like operation)
|
||||
// between the target key descriptor and remote public key. The output
|
||||
// DigestSignerRing is an interface that abstracts away basic low-level ECDSA
|
||||
// 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
|
||||
// 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())
|
||||
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?
|
||||
|
82
keychain/ecdh.go
Normal file
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
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
33
lnd.go
@ -27,7 +27,6 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/btcsuite/btcwallet/wallet"
|
||||
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)
|
||||
|
||||
// TODO(roasbeef): add rotation
|
||||
idPrivKey, err := activeChainControl.wallet.DerivePrivKey(keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
idKeyDesc, err := activeChainControl.keyRing.DeriveKey(
|
||||
keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyNodeKey,
|
||||
Index: 0,
|
||||
},
|
||||
})
|
||||
)
|
||||
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)
|
||||
return err
|
||||
}
|
||||
idPrivKey.Curve = btcec.S256()
|
||||
|
||||
if cfg.Tor.Active {
|
||||
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()
|
||||
|
||||
towerPrivKey, err := activeChainControl.wallet.DerivePrivKey(
|
||||
keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyTowerID,
|
||||
Index: 0,
|
||||
},
|
||||
towerKeyDesc, err := activeChainControl.keyRing.DeriveKey(
|
||||
keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyTowerID,
|
||||
Index: 0,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to derive watchtower "+
|
||||
"private key: %v", err)
|
||||
err := fmt.Errorf("error deriving tower key: %v", err)
|
||||
ltndLog.Error(err)
|
||||
return err
|
||||
}
|
||||
@ -570,9 +565,11 @@ func Main(cfg *Config, lisCfg ListenerCfg, shutdownChan <-chan struct{}) error {
|
||||
lnwallet.WitnessPubKey, false,
|
||||
)
|
||||
},
|
||||
NodePrivKey: towerPrivKey,
|
||||
PublishTx: activeChainControl.wallet.PublishTransaction,
|
||||
ChainHash: *activeNetParams.GenesisHash,
|
||||
NodeKeyECDH: keychain.NewPubKeyECDH(
|
||||
towerKeyDesc, activeChainControl.keyRing,
|
||||
),
|
||||
PublishTx: activeChainControl.wallet.PublishTransaction,
|
||||
ChainHash: *activeNetParams.GenesisHash,
|
||||
}
|
||||
|
||||
// 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.
|
||||
server, err := newServer(
|
||||
cfg, cfg.Listeners, chanDB, towerClientDB, activeChainControl,
|
||||
idPrivKey, walletInitParams.ChansToRestore, chainedAcceptor,
|
||||
&idKeyDesc, walletInitParams.ChansToRestore, chainedAcceptor,
|
||||
torController,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -5,7 +5,6 @@ package signrpc
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -415,24 +414,21 @@ func (s *Server) SignMessage(ctx context.Context,
|
||||
return nil, fmt.Errorf("a key locator MUST be passed in")
|
||||
}
|
||||
|
||||
// Derive the private key we'll be using for signing.
|
||||
keyLocator := keychain.KeyLocator{
|
||||
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)
|
||||
// Describe the private key we'll be using for signing.
|
||||
keyDescriptor := keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(in.KeyLoc.KeyFamily),
|
||||
Index: uint32(in.KeyLoc.KeyIndex),
|
||||
},
|
||||
}
|
||||
|
||||
// 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
|
||||
// format after.
|
||||
sig, err := privKey.Sign(digest)
|
||||
sig, err := s.cfg.KeyRing.SignDigest(keyDescriptor, digest)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// Derive our node's private key from the key ring.
|
||||
idPrivKey, err := s.cfg.KeyRing.DerivePrivKey(keychain.KeyDescriptor{
|
||||
KeyLocator: locator,
|
||||
})
|
||||
// Derive the shared key using ECDH and hashing the serialized
|
||||
// compressed shared point.
|
||||
keyDescriptor := keychain.KeyDescriptor{KeyLocator: locator}
|
||||
sharedKeyHash, err := s.cfg.KeyRing.ECDH(keyDescriptor, ephemeralPubkey)
|
||||
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)
|
||||
return nil, err
|
||||
}
|
||||
idPrivKey.Curve = btcec.S256()
|
||||
|
||||
// Derive the shared key using ECDH and hashing the serialized
|
||||
// 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[:]
|
||||
return &SharedKeyResponse{SharedKey: sharedKeyHash[:]}, nil
|
||||
}
|
||||
|
21
mock.go
21
mock.go
@ -359,7 +359,22 @@ func (m *mockSecretKeyRing) DerivePrivKey(keyDesc keychain.KeyDescriptor) (*btce
|
||||
return m.rootKey, nil
|
||||
}
|
||||
|
||||
func (m *mockSecretKeyRing) ScalarMult(keyDesc keychain.KeyDescriptor,
|
||||
pubKey *btcec.PublicKey) ([]byte, error) {
|
||||
return nil, nil
|
||||
func (m *mockSecretKeyRing) ECDH(_ keychain.KeyDescriptor,
|
||||
pubKey *btcec.PublicKey) ([32]byte, error) {
|
||||
|
||||
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/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/netann"
|
||||
)
|
||||
@ -309,6 +310,7 @@ func newManagerCfg(t *testing.T, numChannels int,
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate key pair: %v", err)
|
||||
}
|
||||
privKeySigner := &keychain.PrivKeyDigestSigner{PrivKey: privKey}
|
||||
|
||||
graph := newMockGraph(
|
||||
t, numChannels, startEnabled, startEnabled, privKey.PubKey(),
|
||||
@ -320,7 +322,7 @@ func newManagerCfg(t *testing.T, numChannels int,
|
||||
ChanEnableTimeout: 500 * time.Millisecond,
|
||||
ChanDisableTimeout: time.Second,
|
||||
OurPubKey: privKey.PubKey(),
|
||||
MessageSigner: netann.NewNodeSigner(privKey),
|
||||
MessageSigner: netann.NewNodeSigner(privKeySigner),
|
||||
IsChannelActive: htlcSwitch.HasActiveLink,
|
||||
ApplyChannelUpdate: graph.ApplyChannelUpdate,
|
||||
DB: graph,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/netann"
|
||||
@ -30,7 +31,8 @@ func (m *mockSigner) SignMessage(pk *btcec.PublicKey,
|
||||
var _ lnwallet.MessageSigner = (*mockSigner)(nil)
|
||||
|
||||
var (
|
||||
privKey, _ = btcec.NewPrivateKey(btcec.S256())
|
||||
privKey, _ = btcec.NewPrivateKey(btcec.S256())
|
||||
privKeySigner = &keychain.PrivKeyDigestSigner{PrivKey: privKey}
|
||||
|
||||
pubKey = privKey.PubKey()
|
||||
|
||||
@ -52,35 +54,35 @@ var updateDisableTests = []updateDisableTest{
|
||||
startEnabled: true,
|
||||
disable: true,
|
||||
startTime: time.Now(),
|
||||
signer: netann.NewNodeSigner(privKey),
|
||||
signer: netann.NewNodeSigner(privKeySigner),
|
||||
},
|
||||
{
|
||||
name: "working signer enabled to enabled",
|
||||
startEnabled: true,
|
||||
disable: false,
|
||||
startTime: time.Now(),
|
||||
signer: netann.NewNodeSigner(privKey),
|
||||
signer: netann.NewNodeSigner(privKeySigner),
|
||||
},
|
||||
{
|
||||
name: "working signer disabled to enabled",
|
||||
startEnabled: false,
|
||||
disable: false,
|
||||
startTime: time.Now(),
|
||||
signer: netann.NewNodeSigner(privKey),
|
||||
signer: netann.NewNodeSigner(privKeySigner),
|
||||
},
|
||||
{
|
||||
name: "working signer disabled to disabled",
|
||||
startEnabled: false,
|
||||
disable: true,
|
||||
startTime: time.Now(),
|
||||
signer: netann.NewNodeSigner(privKey),
|
||||
signer: netann.NewNodeSigner(privKeySigner),
|
||||
},
|
||||
{
|
||||
name: "working signer future monotonicity",
|
||||
startEnabled: true,
|
||||
disable: true,
|
||||
startTime: time.Now().Add(time.Hour), // must increment
|
||||
signer: netann.NewNodeSigner(privKey),
|
||||
signer: netann.NewNodeSigner(privKeySigner),
|
||||
},
|
||||
{
|
||||
name: "failing signer",
|
||||
|
@ -6,25 +6,21 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// NodeSigner is an implementation of the MessageSigner interface backed by the
|
||||
// identity private key of running lnd node.
|
||||
type NodeSigner struct {
|
||||
privKey *btcec.PrivateKey
|
||||
keySigner keychain.SingleKeyDigestSigner
|
||||
}
|
||||
|
||||
// NewNodeSigner creates a new instance of the NodeSigner backed by the target
|
||||
// private key.
|
||||
func NewNodeSigner(key *btcec.PrivateKey) *NodeSigner {
|
||||
priv := &btcec.PrivateKey{}
|
||||
priv.Curve = btcec.S256()
|
||||
priv.PublicKey.X = key.X
|
||||
priv.PublicKey.Y = key.Y
|
||||
priv.D = key.D
|
||||
func NewNodeSigner(keySigner keychain.SingleKeyDigestSigner) *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
|
||||
// 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")
|
||||
}
|
||||
|
||||
// Otherwise, we'll sign the dsha256 of the target message.
|
||||
digest := chainhash.DoubleHashB(msg)
|
||||
sig, err := n.privKey.Sign(digest)
|
||||
var digest [32]byte
|
||||
copy(digest[:], chainhash.DoubleHashB(msg))
|
||||
sig, err := n.keySigner.SignDigest(digest)
|
||||
if err != nil {
|
||||
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
|
||||
// node's private key. The returned signature is a pubkey-recoverable signature.
|
||||
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.
|
||||
isCompressedKey := true
|
||||
|
||||
// btcec.SignCompact returns a pubkey-recoverable signature
|
||||
sig, err := btcec.SignCompact(
|
||||
btcec.S256(), n.privKey, hash, isCompressedKey,
|
||||
)
|
||||
// keychain.SignDigestCompact returns a pubkey-recoverable signature.
|
||||
sig, err := n.keySigner.SignDigestCompact(digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't sign the hash: %v", err)
|
||||
}
|
||||
|
2
peer.go
2
peer.go
@ -555,7 +555,7 @@ func (p *peer) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
||||
// particular channel.
|
||||
var selfPolicy *channeldb.ChannelEdgePolicy
|
||||
if info != nil && bytes.Equal(info.NodeKey1Bytes[:],
|
||||
p.server.identityPriv.PubKey().SerializeCompressed()) {
|
||||
p.server.identityECDH.PubKey().SerializeCompressed()) {
|
||||
|
||||
selfPolicy = p1
|
||||
} else {
|
||||
|
2
pilot.go
2
pilot.go
@ -170,7 +170,7 @@ func initAutoPilot(svr *server, cfg *lncfg.AutoPilot,
|
||||
|
||||
// With the heuristic itself created, we can now populate the remainder
|
||||
// of the items that the autopilot agent needs to perform its duties.
|
||||
self := svr.identityPriv.PubKey()
|
||||
self := svr.identityECDH.PubKey()
|
||||
pilotCfg := autopilot.Config{
|
||||
Self: self,
|
||||
Heuristic: weightedAttachment,
|
||||
|
@ -1433,7 +1433,7 @@ func (r *rpcServer) ConnectPeer(ctx context.Context,
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
@ -1781,7 +1781,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||
|
||||
// Making a channel to ourselves wouldn't be of any use, so we
|
||||
// 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")
|
||||
}
|
||||
|
||||
@ -2406,7 +2406,7 @@ func (r *rpcServer) GetInfo(ctx context.Context,
|
||||
}
|
||||
nPendingChannels := uint32(len(pendingChannels))
|
||||
|
||||
idPub := r.server.identityPriv.PubKey().SerializeCompressed()
|
||||
idPub := r.server.identityECDH.PubKey().SerializeCompressed()
|
||||
encodedIDPub := hex.EncodeToString(idPub)
|
||||
|
||||
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
|
||||
// special case end height which will return transactions from the start
|
||||
// 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 req.EndHeight != 0 {
|
||||
|
65
server.go
65
server.go
@ -41,6 +41,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/invoices"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnpeer"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
@ -135,9 +136,9 @@ type server struct {
|
||||
|
||||
cfg *Config
|
||||
|
||||
// identityPriv is the private key used to authenticate any incoming
|
||||
// connections.
|
||||
identityPriv *btcec.PrivateKey
|
||||
// identityECDH is an ECDH capable wrapper for the private key used
|
||||
// to authenticate any incoming connections.
|
||||
identityECDH keychain.SingleKeyECDH
|
||||
|
||||
// nodeSigner is an implementation of the MessageSigner implementation
|
||||
// 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
|
||||
// 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) {
|
||||
|
||||
return func(a net.Addr) (net.Conn, error) {
|
||||
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.
|
||||
func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
towerClientDB *wtdb.ClientDB, cc *chainControl,
|
||||
privKey *btcec.PrivateKey,
|
||||
nodeKeyDesc *keychain.KeyDescriptor,
|
||||
chansToRestore walletunlocker.ChannelsToRecover,
|
||||
chanPredicate chanacceptor.ChannelAcceptor,
|
||||
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))
|
||||
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
|
||||
// since we are resolving a local address.
|
||||
listeners[i], err = brontide.NewListener(
|
||||
privKey, listenAddr.String(),
|
||||
nodeKeyECDH, listenAddr.String(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -366,14 +373,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
}
|
||||
|
||||
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
|
||||
// the same directory as the channel graph database.
|
||||
graphDir := chanDB.Path()
|
||||
sharedSecretPath := filepath.Join(graphDir, "sphinxreplay.db")
|
||||
replayLog := htlcswitch.NewDecayedLog(sharedSecretPath, cc.chainNotifier)
|
||||
sphinxRouter := sphinx.NewRouter(privKey, activeNetParams.Params, replayLog)
|
||||
sphinxRouter := sphinx.NewRouter(
|
||||
nodeKeyECDH, activeNetParams.Params, replayLog,
|
||||
)
|
||||
|
||||
writeBufferPool := pool.NewWriteBuffer(
|
||||
pool.DefaultWriteBufferGCInterval,
|
||||
@ -425,8 +434,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
|
||||
channelNotifier: channelnotifier.New(chanDB),
|
||||
|
||||
identityPriv: privKey,
|
||||
nodeSigner: netann.NewNodeSigner(privKey),
|
||||
identityECDH: nodeKeyECDH,
|
||||
nodeSigner: netann.NewNodeSigner(nodeKeySigner),
|
||||
|
||||
listenAddrs: listenAddrs,
|
||||
|
||||
@ -512,7 +521,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
ChanStatusSampleInterval: cfg.ChanStatusSampleInterval,
|
||||
ChanEnableTimeout: cfg.ChanEnableTimeout,
|
||||
ChanDisableTimeout: cfg.ChanDisableTimeout,
|
||||
OurPubKey: privKey.PubKey(),
|
||||
OurPubKey: nodeKeyECDH.PubKey(),
|
||||
MessageSigner: s.nodeSigner,
|
||||
IsChannelActive: s.htlcSwitch.HasActiveLink,
|
||||
ApplyChannelUpdate: s.applyChannelUpdate,
|
||||
@ -638,7 +647,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
Features: s.featureMgr.Get(feature.SetNodeAnn),
|
||||
Color: color,
|
||||
}
|
||||
copy(selfNode.PubKeyBytes[:], privKey.PubKey().SerializeCompressed())
|
||||
copy(selfNode.PubKeyBytes[:], nodeKeyECDH.PubKey().SerializeCompressed())
|
||||
|
||||
// Based on the disk representation of the node announcement generated
|
||||
// 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
|
||||
// authenticate the message on the network.
|
||||
authSig, err := netann.SignAnnouncement(
|
||||
s.nodeSigner, s.identityPriv.PubKey(), nodeAnn,
|
||||
s.nodeSigner, s.identityECDH.PubKey(), nodeAnn,
|
||||
)
|
||||
if err != nil {
|
||||
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,
|
||||
IgnoreHistoricalFilters: cfg.IgnoreHistoricalGossipFilters,
|
||||
},
|
||||
s.identityPriv.PubKey(),
|
||||
s.identityECDH.PubKey(),
|
||||
)
|
||||
|
||||
s.localChanMgr = &localchans.Manager{
|
||||
@ -980,7 +989,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
}
|
||||
|
||||
s.fundingMgr, err = newFundingManager(fundingConfig{
|
||||
IDKey: privKey.PubKey(),
|
||||
IDKey: nodeKeyECDH.PubKey(),
|
||||
Wallet: cc.wallet,
|
||||
PublishTransaction: cc.wallet.PublishTransaction,
|
||||
Notifier: cc.chainNotifier,
|
||||
@ -988,7 +997,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
SignMessage: func(pubKey *btcec.PublicKey,
|
||||
msg []byte) (input.Signature, error) {
|
||||
|
||||
if pubKey.IsEqual(privKey.PubKey()) {
|
||||
if pubKey.IsEqual(nodeKeyECDH.PubKey()) {
|
||||
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 {
|
||||
|
||||
return s.authGossiper.ProcessLocalAnnouncement(
|
||||
msg, privKey.PubKey(), optionalFields...,
|
||||
msg, nodeKeyECDH.PubKey(), optionalFields...,
|
||||
)
|
||||
},
|
||||
NotifyWhenOnline: s.NotifyWhenOnline,
|
||||
@ -1227,7 +1236,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, chanDB *channeldb.DB,
|
||||
OnAccept: s.InboundPeerConnected,
|
||||
RetryDuration: time.Second * 5,
|
||||
TargetOutbound: 100,
|
||||
Dial: noiseDial(s.identityPriv, s.cfg.net),
|
||||
Dial: noiseDial(s.identityECDH, s.cfg.net),
|
||||
OnConnection: s.OutboundPeerConnected,
|
||||
})
|
||||
if err != nil {
|
||||
@ -2020,7 +2029,7 @@ func (s *server) createNewHiddenService() error {
|
||||
Color: newNodeAnn.RGBColor,
|
||||
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 {
|
||||
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
|
||||
// modifiers.
|
||||
err := netann.SignNodeAnnouncement(
|
||||
s.nodeSigner, s.identityPriv.PubKey(), s.currentNodeAnn,
|
||||
s.nodeSigner, s.identityECDH.PubKey(), s.currentNodeAnn,
|
||||
modifiers...,
|
||||
)
|
||||
if err != nil {
|
||||
@ -2102,7 +2111,7 @@ func (s *server) establishPersistentConnections() error {
|
||||
|
||||
// TODO(roasbeef): instead iterate over link nodes and query graph for
|
||||
// each of the nodes.
|
||||
selfPub := s.identityPriv.PubKey().SerializeCompressed()
|
||||
selfPub := s.identityECDH.PubKey().SerializeCompressed()
|
||||
err = sourceNode.ForEachChannel(nil, func(
|
||||
tx kvdb.ReadTx,
|
||||
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
|
||||
// we'll close out the new connection s.t there's only a single
|
||||
// connection between us.
|
||||
localPub := s.identityPriv.PubKey()
|
||||
localPub := s.identityECDH.PubKey()
|
||||
if !connectedPeer.inbound &&
|
||||
!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
|
||||
// we'll close out the new connection s.t there's only a single
|
||||
// connection between us.
|
||||
localPub := s.identityPriv.PubKey()
|
||||
localPub := s.identityECDH.PubKey()
|
||||
if connectedPeer.inbound &&
|
||||
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
|
||||
// closed.
|
||||
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 {
|
||||
srvrLog.Errorf("Unable to connect to %v: %v", addr, err)
|
||||
select {
|
||||
@ -3467,7 +3476,7 @@ func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error)
|
||||
func (s *server) fetchLastChanUpdate() func(lnwire.ShortChannelID) (
|
||||
*lnwire.ChannelUpdate, error) {
|
||||
|
||||
ourPubKey := s.identityPriv.PubKey().SerializeCompressed()
|
||||
ourPubKey := s.identityECDH.PubKey().SerializeCompressed()
|
||||
return func(cid lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
|
||||
info, edge1, edge2, err := s.chanRouter.GetChannelByID(cid)
|
||||
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
|
||||
// the server.
|
||||
func (s *server) applyChannelUpdate(update *lnwire.ChannelUpdate) error {
|
||||
pubKey := s.identityPriv.PubKey()
|
||||
pubKey := s.identityECDH.PubKey()
|
||||
errChan := s.authGossiper.ProcessLocalAnnouncement(update, pubKey)
|
||||
select {
|
||||
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,
|
||||
*lnwallet.LightningChannel, func(), error) {
|
||||
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
alicesPrivKey)
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
|
||||
bobsPrivKey)
|
||||
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), alicesPrivKey,
|
||||
)
|
||||
aliceKeySigner := &keychain.PrivKeyDigestSigner{PrivKey: aliceKeyPriv}
|
||||
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), bobsPrivKey,
|
||||
)
|
||||
|
||||
channelCapacity := btcutil.Amount(10 * 1e8)
|
||||
channelBal := channelCapacity / 2
|
||||
@ -402,7 +405,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier, publTx chan *wire.MsgTx,
|
||||
}
|
||||
s.htlcSwitch = htlcSwitch
|
||||
|
||||
nodeSignerAlice := netann.NewNodeSigner(aliceKeyPriv)
|
||||
nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
|
||||
|
||||
const chanActiveTimeout = time.Minute
|
||||
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/tor"
|
||||
"github.com/lightningnetwork/lnd/watchtower/lookout"
|
||||
)
|
||||
@ -63,9 +63,9 @@ type Config struct {
|
||||
// successfully sent funds can be received.
|
||||
NewAddress func() (btcutil.Address, error)
|
||||
|
||||
// NodePrivKey is private key to be used in accepting new brontide
|
||||
// connections.
|
||||
NodePrivKey *btcec.PrivateKey
|
||||
// NodeKeyECDH is the ECDH capable wrapper of the key to be used in
|
||||
// accepting new brontide connections.
|
||||
NodeKeyECDH keychain.SingleKeyECDH
|
||||
|
||||
// PublishTx provides the ability to send a signed transaction to the
|
||||
// network.
|
||||
|
@ -72,7 +72,7 @@ func New(cfg *Config) (*Standalone, error) {
|
||||
listeners := make([]net.Listener, 0, len(cfg.ListenAddrs))
|
||||
for _, listenAddr := range cfg.ListenAddrs {
|
||||
listener, err := brontide.NewListener(
|
||||
cfg.NodePrivKey, listenAddr.String(),
|
||||
cfg.NodeKeyECDH, listenAddr.String(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -85,7 +85,7 @@ func New(cfg *Config) (*Standalone, error) {
|
||||
server, err := wtserver.New(&wtserver.Config{
|
||||
ChainHash: cfg.ChainHash,
|
||||
DB: cfg.DB,
|
||||
NodePrivKey: cfg.NodePrivKey,
|
||||
NodeKeyECDH: cfg.NodeKeyECDH,
|
||||
Listeners: listeners,
|
||||
ReadTimeout: cfg.ReadTimeout,
|
||||
WriteTimeout: cfg.WriteTimeout,
|
||||
@ -190,7 +190,7 @@ func (w *Standalone) createNewHiddenService() error {
|
||||
//
|
||||
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
|
||||
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
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||
@ -132,7 +133,7 @@ type Config struct {
|
||||
// SecretKeyRing is used to derive the session keys used to communicate
|
||||
// with the tower. The client only stores the KeyLocators internally so
|
||||
// that we never store private keys on disk.
|
||||
SecretKeyRing SecretKeyRing
|
||||
SecretKeyRing ECDHKeyRing
|
||||
|
||||
// Dial connects to an addr using the specified net and returns the
|
||||
// connection object.
|
||||
@ -337,7 +338,7 @@ func New(config *Config) (*TowerClient, error) {
|
||||
// NOTE: This method should only be used when deserialization of a
|
||||
// ClientSession's Tower and SessionPrivKey fields is desired, otherwise, the
|
||||
// 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) (
|
||||
map[wtdb.SessionID]*wtdb.ClientSession, error) {
|
||||
|
||||
@ -358,11 +359,14 @@ func getClientSessions(db DB, keyRing SecretKeyRing, forTower *wtdb.TowerID,
|
||||
}
|
||||
s.Tower = tower
|
||||
|
||||
sessionKey, err := DeriveSessionKey(keyRing, s.KeyIndex)
|
||||
towerKeyDesc, err := keyRing.DeriveKey(keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyTowerSession,
|
||||
Index: s.KeyIndex,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.SessionPrivKey = sessionKey
|
||||
s.SessionKeyECDH = keychain.NewPubKeyECDH(towerKeyDesc, keyRing)
|
||||
|
||||
// If an optional filter was provided, use it to filter out any
|
||||
// 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
|
||||
// connection. The connection will use the configured Net's resolver to resolve
|
||||
// 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) {
|
||||
|
||||
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
|
||||
|
@ -101,10 +101,10 @@ func (m *mockNet) ResolveTCPAddr(network string, address string) (*net.TCPAddr,
|
||||
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) {
|
||||
|
||||
localPk := localPriv.PubKey()
|
||||
localPk := local.PubKey()
|
||||
localAddr := &net.TCPAddr{
|
||||
IP: net.IP{0x32, 0x31, 0x30, 0x29},
|
||||
Port: 36723,
|
||||
@ -401,6 +401,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to generate tower private key: %v", err)
|
||||
}
|
||||
privKeyECDH := &keychain.PrivKeyECDH{PrivKey: privKey}
|
||||
|
||||
towerPubKey := privKey.PubKey()
|
||||
|
||||
@ -416,7 +417,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
|
||||
DB: serverDB,
|
||||
ReadTimeout: timeout,
|
||||
WriteTimeout: timeout,
|
||||
NodePrivKey: privKey,
|
||||
NodeKeyECDH: privKeyECDH,
|
||||
NewAddress: func() (btcutil.Address, error) {
|
||||
return addr, nil
|
||||
},
|
||||
@ -519,7 +520,7 @@ func (h *testHarness) startClient() {
|
||||
h.t.Fatalf("Unable to resolve tower TCP addr: %v", err)
|
||||
}
|
||||
towerAddr := &lnwire.NetAddress{
|
||||
IdentityKey: h.serverCfg.NodePrivKey.PubKey(),
|
||||
IdentityKey: h.serverCfg.NodeKeyECDH.PubKey(),
|
||||
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
|
||||
// brontide. The dialer argument is used to specify a resolver, which allows
|
||||
// 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)
|
||||
|
||||
// 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) {
|
||||
|
||||
return brontide.Dial(localPriv, netAddr, dialer)
|
||||
return brontide.Dial(localKey, netAddr, dialer)
|
||||
}
|
||||
|
||||
// SecretKeyRing abstracts the ability to derive HD private keys given a
|
||||
// description of the derivation path.
|
||||
type SecretKeyRing interface {
|
||||
// DerivePrivKey derives the private key from the root seed using a
|
||||
// key descriptor specifying the key's derivation path.
|
||||
DerivePrivKey(loc keychain.KeyDescriptor) (*btcec.PrivateKey, error)
|
||||
// ECDHKeyRing abstracts the ability to derive shared ECDH keys given a
|
||||
// description of the derivation path of a private key.
|
||||
type ECDHKeyRing interface {
|
||||
keychain.ECDHRing
|
||||
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||
@ -44,7 +44,7 @@ type NegotiatorConfig struct {
|
||||
|
||||
// SecretKeyRing allows the client to derive new session private keys
|
||||
// when attempting to negotiate session with a tower.
|
||||
SecretKeyRing SecretKeyRing
|
||||
SecretKeyRing ECDHKeyRing
|
||||
|
||||
// Candidates is an abstract set of tower candidates that the negotiator
|
||||
// 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
|
||||
// using a specified private key. The peer is returned in the event of a
|
||||
// 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 func(wtserver.Peer, wtwire.Message) error
|
||||
@ -315,13 +316,21 @@ func (n *sessionNegotiator) createSession(tower *wtdb.Tower,
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
sessionKey := keychain.NewPubKeyECDH(
|
||||
sessionKeyDesc, n.cfg.SecretKeyRing,
|
||||
)
|
||||
|
||||
for _, lnAddr := range tower.LNAddrs() {
|
||||
err = n.tryAddress(sessionPriv, keyIndex, tower, lnAddr)
|
||||
err := n.tryAddress(sessionKey, keyIndex, tower, lnAddr)
|
||||
switch {
|
||||
case err == ErrPermanentTowerFailure:
|
||||
// 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
|
||||
// returns true if all steps succeed and the new session has been persisted, and
|
||||
// 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 {
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
@ -417,9 +426,7 @@ func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey,
|
||||
// TODO(conner): validate reward address
|
||||
rewardPkScript := createSessionReply.Data
|
||||
|
||||
sessionID := wtdb.NewSessionIDFromPubKey(
|
||||
privKey.PubKey(),
|
||||
)
|
||||
sessionID := wtdb.NewSessionIDFromPubKey(sessionKey.PubKey())
|
||||
clientSession := &wtdb.ClientSession{
|
||||
ClientSessionBody: wtdb.ClientSessionBody{
|
||||
TowerID: tower.ID,
|
||||
@ -428,7 +435,7 @@ func (n *sessionNegotiator) tryAddress(privKey *btcec.PrivateKey,
|
||||
RewardPkScript: rewardPkScript,
|
||||
},
|
||||
Tower: tower,
|
||||
SessionPrivKey: privKey,
|
||||
SessionKeyECDH: sessionKey,
|
||||
ID: sessionID,
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||
"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
|
||||
// net address.
|
||||
Dial func(*btcec.PrivateKey,
|
||||
*lnwire.NetAddress) (wtserver.Peer, error)
|
||||
Dial func(keychain.SingleKeyECDH, *lnwire.NetAddress) (wtserver.Peer,
|
||||
error)
|
||||
|
||||
// SendMessage encodes, encrypts, and writes a message to the given peer.
|
||||
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.
|
||||
func (q *sessionQueue) drainBackups() {
|
||||
// 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 {
|
||||
log.Errorf("SessionQueue(%s) unable to dial tower at %v: %v",
|
||||
q.ID(), q.towerAddr, err)
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/blob"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
|
||||
@ -60,12 +60,12 @@ type ClientSession struct {
|
||||
// tower with TowerID.
|
||||
Tower *Tower
|
||||
|
||||
// SessionPrivKey is the ephemeral secret key used to connect to the
|
||||
// watchtower.
|
||||
// SessionKeyECDH is the ECDH capable wrapper of the ephemeral secret
|
||||
// key used to connect to the watchtower.
|
||||
//
|
||||
// NOTE: This value is not serialized. It is derived using the KeyIndex
|
||||
// on startup to avoid storing private keys on disk.
|
||||
SessionPrivKey *btcec.PrivateKey
|
||||
SessionKeyECDH keychain.SingleKeyECDH
|
||||
}
|
||||
|
||||
// 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
|
||||
// this method is called twice with the same argument, it will return the same
|
||||
// private key.
|
||||
func (m *SecretKeyRing) DerivePrivKey(
|
||||
desc 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.
|
||||
//
|
||||
// NOTE: This is part of the wtclient.ECDHKeyRing interface.
|
||||
func (m *SecretKeyRing) DeriveKey(
|
||||
keyLoc keychain.KeyLocator) (keychain.KeyDescriptor, error) {
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if key, ok := m.keys[desc.KeyLocator]; ok {
|
||||
return key, nil
|
||||
if key, ok := m.keys[keyLoc]; ok {
|
||||
return keychain.KeyDescriptor{
|
||||
KeyLocator: keyLoc,
|
||||
PubKey: key.PubKey(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
privKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||
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"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/connmgr"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtdb"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtwire"
|
||||
@ -33,9 +33,9 @@ type Config struct {
|
||||
// storing state updates.
|
||||
DB DB
|
||||
|
||||
// NodePrivKey is private key to be used in accepting new brontide
|
||||
// connections.
|
||||
NodePrivKey *btcec.PrivateKey
|
||||
// NodeKeyECDH is the the ECDH capable wrapper of the key to be used in
|
||||
// accepting new brontide connections.
|
||||
NodeKeyECDH keychain.SingleKeyECDH
|
||||
|
||||
// Listeners specifies which address to which clients may connect.
|
||||
Listeners []net.Listener
|
||||
|
Loading…
Reference in New Issue
Block a user