diff --git a/discovery/service.go b/discovery/service.go index d71a237c..27e8a676 100644 --- a/discovery/service.go +++ b/discovery/service.go @@ -1,14 +1,11 @@ package discovery import ( + "bytes" "sync" "sync/atomic" "time" - "bytes" - - "encoding/hex" - "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -81,20 +78,6 @@ type Config struct { // New create new discovery service structure. func New(cfg Config) (*Discovery, error) { - // TODO(roasbeef): remove this place holder after sigs are properly - // stored in the graph. - s := "30450221008ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa" + - "1bf0314f882d70220299105481d63e0f4bc2a88121167221b6700d72a0e" + - "ad154c03be696a292d24ae" - fakeSigHex, err := hex.DecodeString(s) - if err != nil { - return nil, err - } - fakeSig, err := btcec.ParseSignature(fakeSigHex, btcec.S256()) - if err != nil { - return nil, err - } - return &Discovery{ cfg: &cfg, networkMsgs: make(chan *networkMsg), @@ -102,7 +85,6 @@ func New(cfg Config) (*Discovery, error) { syncRequests: make(chan *syncRequest), prematureAnnouncements: make(map[uint32][]*networkMsg), waitingProofs: make(map[waitingProofKey]*lnwire.AnnounceSignatures), - fakeSig: fakeSig, }, nil } @@ -157,8 +139,6 @@ type Discovery struct { // bestHeight is the height of the block at the tip of the main chain // as we know it. bestHeight uint32 - - fakeSig *btcec.Signature } // ProcessRemoteAnnouncement sends a new remote announcement message along with @@ -444,7 +424,13 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag // node, or a node updating previously advertised information. case *lnwire.NodeAnnouncement: if nMsg.isRemote { - // TODO(andrew.shvv) add validation + if err := d.validateNodeAnn(msg); err != nil { + err := errors.Errorf("unable to validate "+ + "node announcement: %v", err) + log.Error(err) + nMsg.err <- err + return nil + } } node := &channeldb.LightningNode{ @@ -500,7 +486,13 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag var proof *channeldb.ChannelAuthProof if nMsg.isRemote { - // TODO(andrew.shvv) Add validation + if err := d.validateChannelAnn(msg); err != nil { + err := errors.Errorf("unable to validate "+ + "announcement: %v", err) + log.Error(err) + nMsg.err <- err + return nil + } proof = &channeldb.ChannelAuthProof{ NodeSig1: msg.NodeSig1, @@ -577,7 +569,21 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag return nil } - // TODO(andrew.shvv) Add validation + var pubKey *btcec.PublicKey + switch msg.Flags { + case 0: + pubKey = chanInfo.NodeKey1 + case 1: + pubKey = chanInfo.NodeKey2 + } + + if err := d.validateChannelUpdateAnn(pubKey, msg); err != nil { + err := errors.Errorf("unable to validate channel"+ + "update announcement for shortChanID=%v: %v", msg.ShortChannelID, err) + log.Error(err) + nMsg.err <- err + return nil + } // TODO(roasbeef): should be msat here update := &channeldb.ChannelEdgePolicy{ @@ -732,7 +738,14 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag chanAnn, e1Ann, e2Ann := createChanAnnouncement(&dbProof, chanInfo, e1, e2) - // TODO(andrew.shvv) Add validation + if err := d.validateChannelAnn(chanAnn); err != nil { + err := errors.Errorf("channel announcement proof "+ + "for shortChanID=%v isn't valid: %v", + shortChanID, err) + log.Error(err) + nMsg.err <- err + return nil + } // If the channel was returned by the router it means that // existence of funding point and inclusion of nodes bitcoin diff --git a/discovery/service_test.go b/discovery/service_test.go index 1de27f91..10954078 100644 --- a/discovery/service_test.go +++ b/discovery/service_test.go @@ -16,6 +16,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" "github.com/roasbeef/btcd/btcec" @@ -251,6 +252,10 @@ func createAnnouncements(blockHeight uint32) (*annBatch, error) { if err != nil { return nil, err } + batch.localChanAnn.BitcoinSig1 = nil + batch.localChanAnn.BitcoinSig2 = nil + batch.localChanAnn.NodeSig1 = nil + batch.localChanAnn.NodeSig2 = nil return &batch, nil @@ -258,6 +263,7 @@ func createAnnouncements(blockHeight uint32) (*annBatch, error) { func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement, error) { + var err error alias, err := lnwire.NewAlias("kek" + string(priv.Serialize())) if err != nil { @@ -265,7 +271,6 @@ func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement, } a := &lnwire.NodeAnnouncement{ - Signature: testSig, Timestamp: uint32(prand.Int31()), Addresses: testAddrs, NodeID: priv.PubKey(), @@ -273,14 +278,19 @@ func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement, Features: testFeatures, } + signer := lnwallet.NewMessageSigner(nodeKeyPriv1) + if a.Signature, err = SignAnnouncement(signer, a); err != nil { + return nil, err + } + return a, nil } func createUpdateAnnouncement(blockHeight uint32) (*lnwire.ChannelUpdateAnnouncement, error) { + var err error a := &lnwire.ChannelUpdateAnnouncement{ - Signature: testSig, ShortChannelID: lnwire.ShortChannelID{ BlockHeight: blockHeight, }, @@ -291,11 +301,17 @@ func createUpdateAnnouncement(blockHeight uint32) (*lnwire.ChannelUpdateAnnounce FeeProportionalMillionths: uint32(prand.Int31()), } + signer := lnwallet.NewMessageSigner(nodeKeyPriv1) + if a.Signature, err = SignAnnouncement(signer, a); err != nil { + return nil, err + } + return a, nil } func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnouncement, error) { + var err error a := &lnwire.ChannelAnnouncement{ ShortChannelID: lnwire.ShortChannelID{ @@ -307,11 +323,28 @@ func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnounc NodeID2: nodeKeyPub2, BitcoinKey1: bitcoinKeyPub1, BitcoinKey2: bitcoinKeyPub2, + } - NodeSig1: testSig, - NodeSig2: testSig, - BitcoinSig1: testSig, - BitcoinSig2: testSig, + signer := lnwallet.NewMessageSigner(nodeKeyPriv1) + if a.NodeSig1, err = SignAnnouncement(signer, a); err != nil { + return nil, err + } + + signer = lnwallet.NewMessageSigner(nodeKeyPriv2) + if a.NodeSig2, err = SignAnnouncement(signer, a); err != nil { + return nil, err + } + + hash := chainhash.DoubleHashB(nodeKeyPub1.SerializeCompressed()) + a.BitcoinSig1, err = bitcoinKeyPriv1.Sign(hash) + if err != nil { + return nil, err + } + + hash = chainhash.DoubleHashB(nodeKeyPub2.SerializeCompressed()) + a.BitcoinSig2, err = bitcoinKeyPriv2.Sign(hash) + if err != nil { + return nil, err } return a, nil diff --git a/discovery/utils.go b/discovery/utils.go index 53efc99b..2bcb7142 100644 --- a/discovery/utils.go +++ b/discovery/utils.go @@ -3,8 +3,11 @@ package discovery import ( "encoding/binary" + "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/btcec" ) // newProofKey constructs new announcement signature message key. @@ -91,3 +94,39 @@ func createChanAnnouncement(chanProof *channeldb.ChannelAuthProof, return chanAnn, edge1Ann, edge2Ann } + +// copyPubKey is copying the public key and setting curve. +// NOTE: At the moment of creation the function was need only because we are +// setting the curve to nil in the read message function and in order to +// properly validate the signatures we need to set the curve again. +func copyPubKey(pub *btcec.PublicKey) *btcec.PublicKey { + return &btcec.PublicKey{ + Curve: btcec.S256(), + X: pub.X, + Y: pub.Y, + } +} + +// SignAnnouncement helper function which is used for signing the announce +// messages. +func SignAnnouncement(signer *lnwallet.MessageSigner, + msg lnwire.Message) (*btcec.Signature, error) { + var data []byte + var err error + switch m := msg.(type) { + case *lnwire.ChannelAnnouncement: + data, err = m.DataToSign() + case *lnwire.ChannelUpdateAnnouncement: + data, err = m.DataToSign() + case *lnwire.NodeAnnouncement: + data, err = m.DataToSign() + default: + return nil, errors.New("can't sign message " + + "of this format") + } + if err != nil { + return nil, errors.Errorf("can't get data to sign: %v", err) + } + + return signer.SignData(data) +} diff --git a/discovery/validation.go b/discovery/validation.go new file mode 100644 index 00000000..1766f758 --- /dev/null +++ b/discovery/validation.go @@ -0,0 +1,83 @@ +package discovery + +import ( + "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/chaincfg/chainhash" +) + +// validateChannelAnn validates the channel announcement message and checks +// that node signatures contains the announcement message, and that the +// bitcoin signatures contains the node keys. +func (d *Discovery) validateChannelAnn(a *lnwire.ChannelAnnouncement) error { + sigHash := chainhash.DoubleHashB(a.NodeID1.SerializeCompressed()) + if !a.BitcoinSig1.Verify(sigHash, copyPubKey(a.BitcoinKey1)) { + return errors.New("can't verify first bitcoin signature") + } + + sigHash = chainhash.DoubleHashB(a.NodeID2.SerializeCompressed()) + if !a.BitcoinSig2.Verify(sigHash, copyPubKey(a.BitcoinKey2)) { + return errors.New("can't verify second bitcoin signature") + } + + // Get the data of announcement which should be encapsulated in + // signature and then check it. + data, err := a.DataToSign() + if err != nil { + return err + } + dataHash := chainhash.DoubleHashB(data) + + if !a.NodeSig1.Verify(dataHash, copyPubKey(a.NodeID1)) { + return errors.New("can't verify data in first node signature") + } + + if !a.NodeSig2.Verify(dataHash, copyPubKey(a.NodeID2)) { + return errors.New("can't verify data in second node signature") + } + return nil + +} + +// validateNodeAnn validates the node announcement by checking that the +// the signature corresponds to the node key and have been created with +// announcement data. +func (d *Discovery) validateNodeAnn(a *lnwire.NodeAnnouncement) error { + + // Get the data of announcement which should be encapsulated in + // signature and then check it. + data, err := a.DataToSign() + if err != nil { + return err + } + + dataHash := chainhash.DoubleHashB(data) + if !a.Signature.Verify(dataHash, copyPubKey(a.NodeID)) { + return errors.New("can't check the node annoucement signature") + } + + return nil +} + +// validateChannelUpdateAnn validates the channel update announcement by +// checking that the the signature corresponds to the node key and have been +// created with announcement data. +func (d *Discovery) validateChannelUpdateAnn(pubKey *btcec.PublicKey, + a *lnwire.ChannelUpdateAnnouncement) error { + + // Get the data of announcement which should be encapsulated in + // signature and then check it. + data, err := a.DataToSign() + if err != nil { + return errors.Errorf("can't retrieve data to sign: %v", err) + } + dataHash := chainhash.DoubleHashB(data) + + if !a.Signature.Verify(dataHash, copyPubKey(pubKey)) { + return errors.Errorf("verification of channel updates "+ + "failed chan_id=%v", a.ShortChannelID) + } + + return nil +} diff --git a/lnd.go b/lnd.go index 16c518a6..008da576 100644 --- a/lnd.go +++ b/lnd.go @@ -140,8 +140,8 @@ func lndMain() error { // Create, and start the lnwallet, which handles the core payment // channel logic, and exposes control via proxy state machines. - wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, - wc, signer, bio, activeNetParams.Params) + wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wc, signer, + bio, activeNetParams.Params) if err != nil { fmt.Printf("unable to create wallet: %v\n", err) return err @@ -157,7 +157,10 @@ func lndMain() error { defaultListenAddrs := []string{ net.JoinHostPort("", strconv.Itoa(cfg.PeerPort)), } - server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB) + + fundingSigner := btcwallet.NewFundingSigner(wc) + server, err := newServer(defaultListenAddrs, notifier, bio, wallet, + chanDB, fundingSigner) if err != nil { srvrLog.Errorf("unable to create server: %v\n", err) return err diff --git a/peer.go b/peer.go index 5783e0af..d16782ed 100644 --- a/peer.go +++ b/peer.go @@ -502,7 +502,8 @@ out: p.server.discoverSrv.ProcessRemoteAnnouncement(msg, p.addr.IdentityKey) default: - peerLog.Errorf("unknown message received from peer "+"%v", p) + peerLog.Errorf("unknown message received from peer "+ + "%v", p) } if isChanUpdate { diff --git a/server.go b/server.go index abafa067..58340b42 100644 --- a/server.go +++ b/server.go @@ -17,6 +17,7 @@ import ( "github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwallet/btcwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" "github.com/roasbeef/btcd/btcec" @@ -95,7 +96,7 @@ type server struct { // passed listener address. func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, bio lnwallet.BlockChainIO, wallet *lnwallet.LightningWallet, - chanDB *channeldb.DB) (*server, error) { + chanDB *channeldb.DB, fundingSigner *btcwallet.FundingSigner) (*server, error) { privKey, err := wallet.GetIdentitykey() if err != nil { @@ -168,32 +169,41 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, selfAddrs = append(selfAddrs, addr) } - // TODO(roasbeef): remove once we actually sign the funding_locked - // stuffs - fakeSigHex, err := hex.DecodeString("30450221008ce2bc69281ce27da07e66" + - "83571319d18e949ddfa2965fb6caa1bf0314f882d70220299105481d63e0f" + - "4bc2a88121167221b6700d72a0ead154c03be696a292d24ae") - if err != nil { - return nil, err - } - - fakeSig, err := btcec.ParseSignature(fakeSigHex, btcec.S256()) - if err != nil { - return nil, err - } - chanGraph := chanDB.ChannelGraph() + + // In order to have ability to announce the self node we need to + // sign the node announce message, and include the signature in the + // node channeldb object. + alias, err := lnwire.NewAlias(hex.EncodeToString(serializedPubKey[:10])) + if err != nil { + return nil, fmt.Errorf("can't create alias: %v", err) + } self := &channeldb.LightningNode{ - AuthSig: fakeSig, LastUpdate: time.Now(), Addresses: selfAddrs, PubKey: privKey.PubKey(), // TODO(roasbeef): make alias configurable - Alias: hex.EncodeToString(serializedPubKey[:10]), + Alias: alias.String(), Features: globalFeatures, } + + // Initialize graph with authenticated lightning node, signature is + // needed in order to be able to announce node to other network. + messageSigner := lnwallet.NewMessageSigner(s.identityPriv) + if self.AuthSig, err = discovery.SignAnnouncement(messageSigner, + &lnwire.NodeAnnouncement{ + Timestamp: uint32(self.LastUpdate.Unix()), + Addresses: self.Addresses, + NodeID: self.PubKey, + Alias: alias, + Features: self.Features, + }); err != nil { + return nil, fmt.Errorf("unable to generate signature for "+ + "self node announcement: %v", err) + } + if err := chanGraph.SetSourceNode(self); err != nil { - return nil, err + return nil, fmt.Errorf("can't set self node: %v", err) } s.chanRouter, err = routing.New(routing.Config{ @@ -213,7 +223,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, }, }) if err != nil { - return nil, err + return nil, fmt.Errorf("can't create router: %v", err) } s.discoverSrv, err = discovery.New(discovery.Config{ @@ -235,17 +245,19 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, IDKey: s.identityPriv.PubKey(), Wallet: wallet, Notifier: s.chainNotifier, - SignNodeKey: func(nodeKey, fundingKey *btcec.PublicKey) (*btcec.Signature, - error) { - return fakeSig, nil + SignNodeKey: func(nodeKey, + fundingKey *btcec.PublicKey) (*btcec.Signature, error) { + data := nodeKey.SerializeCompressed() + return fundingSigner.SignData(data, fundingKey) }, SignAnnouncement: func(msg lnwire.Message) (*btcec.Signature, error) { - return fakeSig, nil + return discovery.SignAnnouncement(messageSigner, msg) }, - SendToDiscovery: func(msg lnwire.Message) chan error { - return s.discoverSrv.ProcessLocalAnnouncement(msg, + SendToDiscovery: func(msg lnwire.Message) error { + s.discoverSrv.ProcessLocalAnnouncement(msg, s.identityPriv.PubKey()) + return nil }, ArbiterChan: s.breachArbiter.newContracts, SendToPeer: s.sendToPeer,