discovery+funding: add validation of the announcement messages

Add validation functions and include validation checks in the
annoncement process function.
This commit is contained in:
Andrey Samokhvalov 2017-03-27 20:25:44 +03:00 committed by Olaoluwa Osuntokun
parent a23715a9c7
commit d4055d7830
7 changed files with 244 additions and 60 deletions

@ -1,14 +1,11 @@
package discovery package discovery
import ( import (
"bytes"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"bytes"
"encoding/hex"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
@ -81,20 +78,6 @@ type Config struct {
// New create new discovery service structure. // New create new discovery service structure.
func New(cfg Config) (*Discovery, error) { 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{ return &Discovery{
cfg: &cfg, cfg: &cfg,
networkMsgs: make(chan *networkMsg), networkMsgs: make(chan *networkMsg),
@ -102,7 +85,6 @@ func New(cfg Config) (*Discovery, error) {
syncRequests: make(chan *syncRequest), syncRequests: make(chan *syncRequest),
prematureAnnouncements: make(map[uint32][]*networkMsg), prematureAnnouncements: make(map[uint32][]*networkMsg),
waitingProofs: make(map[waitingProofKey]*lnwire.AnnounceSignatures), waitingProofs: make(map[waitingProofKey]*lnwire.AnnounceSignatures),
fakeSig: fakeSig,
}, nil }, nil
} }
@ -157,8 +139,6 @@ type Discovery struct {
// bestHeight is the height of the block at the tip of the main chain // bestHeight is the height of the block at the tip of the main chain
// as we know it. // as we know it.
bestHeight uint32 bestHeight uint32
fakeSig *btcec.Signature
} }
// ProcessRemoteAnnouncement sends a new remote announcement message along with // 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. // node, or a node updating previously advertised information.
case *lnwire.NodeAnnouncement: case *lnwire.NodeAnnouncement:
if nMsg.isRemote { 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{ node := &channeldb.LightningNode{
@ -500,7 +486,13 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag
var proof *channeldb.ChannelAuthProof var proof *channeldb.ChannelAuthProof
if nMsg.isRemote { 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{ proof = &channeldb.ChannelAuthProof{
NodeSig1: msg.NodeSig1, NodeSig1: msg.NodeSig1,
@ -577,7 +569,21 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag
return nil 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 // TODO(roasbeef): should be msat here
update := &channeldb.ChannelEdgePolicy{ update := &channeldb.ChannelEdgePolicy{
@ -732,7 +738,14 @@ func (d *Discovery) processNetworkAnnouncement(nMsg *networkMsg) []lnwire.Messag
chanAnn, e1Ann, e2Ann := createChanAnnouncement(&dbProof, chanInfo, e1, e2) 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 // If the channel was returned by the router it means that
// existence of funding point and inclusion of nodes bitcoin // existence of funding point and inclusion of nodes bitcoin

@ -16,6 +16,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
@ -251,6 +252,10 @@ func createAnnouncements(blockHeight uint32) (*annBatch, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
batch.localChanAnn.BitcoinSig1 = nil
batch.localChanAnn.BitcoinSig2 = nil
batch.localChanAnn.NodeSig1 = nil
batch.localChanAnn.NodeSig2 = nil
return &batch, nil return &batch, nil
@ -258,6 +263,7 @@ func createAnnouncements(blockHeight uint32) (*annBatch, error) {
func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement, func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement,
error) { error) {
var err error
alias, err := lnwire.NewAlias("kek" + string(priv.Serialize())) alias, err := lnwire.NewAlias("kek" + string(priv.Serialize()))
if err != nil { if err != nil {
@ -265,7 +271,6 @@ func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement,
} }
a := &lnwire.NodeAnnouncement{ a := &lnwire.NodeAnnouncement{
Signature: testSig,
Timestamp: uint32(prand.Int31()), Timestamp: uint32(prand.Int31()),
Addresses: testAddrs, Addresses: testAddrs,
NodeID: priv.PubKey(), NodeID: priv.PubKey(),
@ -273,14 +278,19 @@ func createNodeAnnouncement(priv *btcec.PrivateKey) (*lnwire.NodeAnnouncement,
Features: testFeatures, Features: testFeatures,
} }
signer := lnwallet.NewMessageSigner(nodeKeyPriv1)
if a.Signature, err = SignAnnouncement(signer, a); err != nil {
return nil, err
}
return a, nil return a, nil
} }
func createUpdateAnnouncement(blockHeight uint32) (*lnwire.ChannelUpdateAnnouncement, func createUpdateAnnouncement(blockHeight uint32) (*lnwire.ChannelUpdateAnnouncement,
error) { error) {
var err error
a := &lnwire.ChannelUpdateAnnouncement{ a := &lnwire.ChannelUpdateAnnouncement{
Signature: testSig,
ShortChannelID: lnwire.ShortChannelID{ ShortChannelID: lnwire.ShortChannelID{
BlockHeight: blockHeight, BlockHeight: blockHeight,
}, },
@ -291,11 +301,17 @@ func createUpdateAnnouncement(blockHeight uint32) (*lnwire.ChannelUpdateAnnounce
FeeProportionalMillionths: uint32(prand.Int31()), FeeProportionalMillionths: uint32(prand.Int31()),
} }
signer := lnwallet.NewMessageSigner(nodeKeyPriv1)
if a.Signature, err = SignAnnouncement(signer, a); err != nil {
return nil, err
}
return a, nil return a, nil
} }
func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnouncement, func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnouncement,
error) { error) {
var err error
a := &lnwire.ChannelAnnouncement{ a := &lnwire.ChannelAnnouncement{
ShortChannelID: lnwire.ShortChannelID{ ShortChannelID: lnwire.ShortChannelID{
@ -307,11 +323,28 @@ func createRemoteChannelAnnouncement(blockHeight uint32) (*lnwire.ChannelAnnounc
NodeID2: nodeKeyPub2, NodeID2: nodeKeyPub2,
BitcoinKey1: bitcoinKeyPub1, BitcoinKey1: bitcoinKeyPub1,
BitcoinKey2: bitcoinKeyPub2, BitcoinKey2: bitcoinKeyPub2,
}
NodeSig1: testSig, signer := lnwallet.NewMessageSigner(nodeKeyPriv1)
NodeSig2: testSig, if a.NodeSig1, err = SignAnnouncement(signer, a); err != nil {
BitcoinSig1: testSig, return nil, err
BitcoinSig2: testSig, }
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 return a, nil

@ -3,8 +3,11 @@ package discovery
import ( import (
"encoding/binary" "encoding/binary"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
) )
// newProofKey constructs new announcement signature message key. // newProofKey constructs new announcement signature message key.
@ -91,3 +94,39 @@ func createChanAnnouncement(chanProof *channeldb.ChannelAuthProof,
return chanAnn, edge1Ann, edge2Ann 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)
}

83
discovery/validation.go Normal file

@ -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
}

9
lnd.go

@ -140,8 +140,8 @@ func lndMain() error {
// Create, and start the lnwallet, which handles the core payment // Create, and start the lnwallet, which handles the core payment
// channel logic, and exposes control via proxy state machines. // channel logic, and exposes control via proxy state machines.
wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wallet, err := lnwallet.NewLightningWallet(chanDB, notifier, wc, signer,
wc, signer, bio, activeNetParams.Params) bio, activeNetParams.Params)
if err != nil { if err != nil {
fmt.Printf("unable to create wallet: %v\n", err) fmt.Printf("unable to create wallet: %v\n", err)
return err return err
@ -157,7 +157,10 @@ func lndMain() error {
defaultListenAddrs := []string{ defaultListenAddrs := []string{
net.JoinHostPort("", strconv.Itoa(cfg.PeerPort)), 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 { if err != nil {
srvrLog.Errorf("unable to create server: %v\n", err) srvrLog.Errorf("unable to create server: %v\n", err)
return err return err

@ -502,7 +502,8 @@ out:
p.server.discoverSrv.ProcessRemoteAnnouncement(msg, p.server.discoverSrv.ProcessRemoteAnnouncement(msg,
p.addr.IdentityKey) p.addr.IdentityKey)
default: default:
peerLog.Errorf("unknown message received from peer "+"%v", p) peerLog.Errorf("unknown message received from peer "+
"%v", p)
} }
if isChanUpdate { if isChanUpdate {

@ -17,6 +17,7 @@ import (
"github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
@ -95,7 +96,7 @@ type server struct {
// passed listener address. // passed listener address.
func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
bio lnwallet.BlockChainIO, wallet *lnwallet.LightningWallet, bio lnwallet.BlockChainIO, wallet *lnwallet.LightningWallet,
chanDB *channeldb.DB) (*server, error) { chanDB *channeldb.DB, fundingSigner *btcwallet.FundingSigner) (*server, error) {
privKey, err := wallet.GetIdentitykey() privKey, err := wallet.GetIdentitykey()
if err != nil { if err != nil {
@ -168,32 +169,41 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
selfAddrs = append(selfAddrs, addr) 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() 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{ self := &channeldb.LightningNode{
AuthSig: fakeSig,
LastUpdate: time.Now(), LastUpdate: time.Now(),
Addresses: selfAddrs, Addresses: selfAddrs,
PubKey: privKey.PubKey(), PubKey: privKey.PubKey(),
// TODO(roasbeef): make alias configurable // TODO(roasbeef): make alias configurable
Alias: hex.EncodeToString(serializedPubKey[:10]), Alias: alias.String(),
Features: globalFeatures, 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 { 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{ s.chanRouter, err = routing.New(routing.Config{
@ -213,7 +223,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
}, },
}) })
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("can't create router: %v", err)
} }
s.discoverSrv, err = discovery.New(discovery.Config{ s.discoverSrv, err = discovery.New(discovery.Config{
@ -235,17 +245,19 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
IDKey: s.identityPriv.PubKey(), IDKey: s.identityPriv.PubKey(),
Wallet: wallet, Wallet: wallet,
Notifier: s.chainNotifier, Notifier: s.chainNotifier,
SignNodeKey: func(nodeKey, fundingKey *btcec.PublicKey) (*btcec.Signature, SignNodeKey: func(nodeKey,
error) { fundingKey *btcec.PublicKey) (*btcec.Signature, error) {
return fakeSig, nil data := nodeKey.SerializeCompressed()
return fundingSigner.SignData(data, fundingKey)
}, },
SignAnnouncement: func(msg lnwire.Message) (*btcec.Signature, SignAnnouncement: func(msg lnwire.Message) (*btcec.Signature,
error) { error) {
return fakeSig, nil return discovery.SignAnnouncement(messageSigner, msg)
}, },
SendToDiscovery: func(msg lnwire.Message) chan error { SendToDiscovery: func(msg lnwire.Message) error {
return s.discoverSrv.ProcessLocalAnnouncement(msg, s.discoverSrv.ProcessLocalAnnouncement(msg,
s.identityPriv.PubKey()) s.identityPriv.PubKey())
return nil
}, },
ArbiterChan: s.breachArbiter.newContracts, ArbiterChan: s.breachArbiter.newContracts,
SendToPeer: s.sendToPeer, SendToPeer: s.sendToPeer,