les: optional signed announce messages

This commit is contained in:
Zsolt Felfoldi 2017-08-23 18:17:35 +02:00
parent 4a96afcf95
commit bc23b730b0
4 changed files with 101 additions and 8 deletions

View file

@ -365,11 +365,23 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// Block header query, collect the requested headers and reply
case AnnounceMsg:
p.Log().Trace("Received announce message")
if p.requestAnnounceType == announceTypeNone {
return errResp(ErrUnexpectedResponse, "")
}
var req announceData
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "%v: %v", msg, err)
}
if p.requestAnnounceType == announceTypeSigned {
if err := req.checkSignature(p.pubKey); err != nil {
p.Log().Trace("Invalid announcement signature", "err", err)
return err
}
p.Log().Trace("Valid announcement signature")
}
p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth)
if pm.fetcher != nil {
pm.fetcher.announce(p, &req)

View file

@ -18,6 +18,7 @@
package les
import (
"crypto/ecdsa"
"encoding/binary"
"errors"
"fmt"
@ -42,14 +43,23 @@ var (
const maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam)
const (
announceTypeNone = iota
announceTypeSimple
announceTypeSigned
)
type peer struct {
*p2p.Peer
pubKey *ecdsa.PublicKey
rw p2p.MsgReadWriter
version int // Protocol version negotiated
network uint64 // Network ID being on
announceType, requestAnnounceType uint64
id string
headInfo *announceData
@ -70,9 +80,11 @@ type peer struct {
func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
id := p.ID()
pubKey, _ := id.Pubkey()
return &peer{
Peer: p,
pubKey: pubKey,
rw: rw,
version: version,
network: network,
@ -325,7 +337,7 @@ func (l keyValueList) decode() keyValueMap {
func (m keyValueMap) get(key string, val interface{}) error {
enc, ok := m[key]
if !ok {
return errResp(ErrHandshakeMissingKey, "%s", key)
return errResp(ErrMissingKey, "%s", key)
}
if val == nil {
return nil
@ -384,6 +396,9 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
list := server.fcCostStats.getCurrentList()
send = send.add("flowControl/MRC", list)
p.fcCosts = list.decode()
} else {
p.requestAnnounceType = announceTypeSimple // set to default until "very light" client mode is implemented
send = send.add("announceType", p.requestAnnounceType)
}
recvList, err := p.sendReceiveHandshake(send)
if err != nil {
@ -428,6 +443,9 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis
/*if recv.get("serveStateSince", nil) == nil {
return errResp(ErrUselessPeer, "wanted client, got server")
}*/
if recv.get("announceType", &p.announceType) != nil {
p.announceType = announceTypeSimple
}
p.fcClient = flowcontrol.NewClientNode(server.fcManager, server.defParams)
} else {
if recv.get("serveChainSince", nil) != nil {

View file

@ -18,11 +18,17 @@
package les
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"io"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/rlp"
)
@ -88,7 +94,7 @@ const (
ErrUnexpectedResponse
ErrInvalidResponse
ErrTooManyTimeouts
ErrHandshakeMissingKey
ErrMissingKey
)
func (e errCode) String() string {
@ -110,7 +116,13 @@ var errorToString = map[int]string{
ErrUnexpectedResponse: "Unexpected response",
ErrInvalidResponse: "Invalid response",
ErrTooManyTimeouts: "Too many request timeouts",
ErrHandshakeMissingKey: "Key missing from handshake message",
ErrMissingKey: "Key missing from list",
}
type announceBlock struct {
Hash common.Hash // Hash of one particular block being announced
Number uint64 // Number of one particular block being announced
Td *big.Int // Total difficulty of one particular block being announced
}
// announceData is the network packet for the block announcements.
@ -122,6 +134,32 @@ type announceData struct {
Update keyValueList
}
// sign adds a signature to the block announcement by the given privKey
func (a *announceData) sign(privKey *ecdsa.PrivateKey) {
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey)
a.Update = a.Update.add("sign", sig)
}
// checkSignature verifies if the block announcement has a valid signature by the given pubKey
func (a *announceData) checkSignature(pubKey *ecdsa.PublicKey) error {
var sig []byte
if err := a.Update.decode().get("sign", &sig); err != nil {
return err
}
rlp, _ := rlp.EncodeToBytes(announceBlock{a.Hash, a.Number, a.Td})
recPubkey, err := secp256k1.RecoverPubkey(crypto.Keccak256(rlp), sig)
if err != nil {
return err
}
pbytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
if bytes.Equal(pbytes, recPubkey) {
return nil
} else {
return errors.New("Wrong signature")
}
}
type blockInfo struct {
Hash common.Hash // Hash of one particular block being announced
Number uint64 // Number of one particular block being announced

View file

@ -18,6 +18,7 @@
package les
import (
"crypto/ecdsa"
"encoding/binary"
"math"
"sync"
@ -41,6 +42,7 @@ type LesServer struct {
fcCostStats *requestCostStats
defParams *flowcontrol.ServerParams
lesTopics []discv5.Topic
privateKey *ecdsa.PrivateKey
quitSync chan struct{}
chtIndexer, bltIndexer *core.ChainIndexer
@ -52,7 +54,6 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
if err != nil {
return nil, err
}
pm.blockLoop()
lesTopics := make([]discv5.Topic, len(ServerProtocolVersions))
for i, pv := range ServerProtocolVersions {
@ -95,6 +96,8 @@ func (s *LesServer) Start(srvr *p2p.Server) {
srvr.DiscV5.RegisterTopic(topic, s.quitSync)
}()
}
s.privateKey = srvr.PrivateKey
s.protocolManager.blockLoop()
}
func (s *LesServer) SetBloomBitsIndexer(bbIndexer *core.ChainIndexer) {
@ -313,11 +316,33 @@ func (pm *ProtocolManager) blockLoop() {
log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg)
announce := announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}
var (
signed bool
signedAnnounce announceData
)
for _, p := range peers {
select {
case p.announceChn <- announce:
default:
pm.removePeer(p.id)
switch p.announceType {
case announceTypeSimple:
select {
case p.announceChn <- announce:
default:
pm.removePeer(p.id)
}
case announceTypeSigned:
if !signed {
signedAnnounce = announce
signedAnnounce.sign(pm.server.privateKey)
signed = true
}
select {
case p.announceChn <- signedAnnounce:
default:
pm.removePeer(p.id)
}
}
}
}