You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
4.5 KiB

package main
import (
"context"
"log"
"time"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
p2p "github.com/libp2p/go-libp2p/examples/multipro/pb"
ggio "github.com/gogo/protobuf/io"
"github.com/gogo/protobuf/proto"
)
// node client version
const clientVersion = "go-p2p-node/0.0.1"
// Node type - a p2p host implementing one or more p2p protocols
type Node struct {
host.Host // lib-p2p host
*PingProtocol // ping protocol impl
*EchoProtocol // echo protocol impl
// add other protocols here...
}
// Create a new node with its implemented protocols
func NewNode(host host.Host, done chan bool) *Node {
node := &Node{Host: host}
node.PingProtocol = NewPingProtocol(node, done)
node.EchoProtocol = NewEchoProtocol(node, done)
return node
}
// Authenticate incoming p2p message
// message: a protobufs go data object
// data: common p2p message data
func (n *Node) authenticateMessage(message proto.Message, data *p2p.MessageData) bool {
// store a temp ref to signature and remove it from message data
// sign is a string to allow easy reset to zero-value (empty string)
sign := data.Sign
data.Sign = nil
// marshall data without the signature to protobufs3 binary format
bin, err := proto.Marshal(message)
if err != nil {
log.Println(err, "failed to marshal pb message")
return false
}
// restore sig in message data (for possible future use)
data.Sign = sign
// restore peer id binary format from base58 encoded node id data
peerId, err := peer.Decode(data.NodeId)
if err != nil {
log.Println(err, "Failed to decode node id from base58")
return false
}
// verify the data was authored by the signing peer identified by the public key
// and signature included in the message
return n.verifyData(bin, []byte(sign), peerId, data.NodePubKey)
}
// sign an outgoing p2p message payload
func (n *Node) signProtoMessage(message proto.Message) ([]byte, error) {
data, err := proto.Marshal(message)
if err != nil {
return nil, err
}
return n.signData(data)
}
// sign binary data using the local node's private key
func (n *Node) signData(data []byte) ([]byte, error) {
key := n.Peerstore().PrivKey(n.ID())
res, err := key.Sign(data)
return res, err
}
// Verify incoming p2p message data integrity
// data: data to verify
// signature: author signature provided in the message payload
// peerId: author peer id from the message payload
// pubKeyData: author public key from the message payload
func (n *Node) verifyData(data []byte, signature []byte, peerId peer.ID, pubKeyData []byte) bool {
key, err := crypto.UnmarshalPublicKey(pubKeyData)
if err != nil {
log.Println(err, "Failed to extract key from message key data")
return false
}
// extract node id from the provided public key
idFromKey, err := peer.IDFromPublicKey(key)
if err != nil {
log.Println(err, "Failed to extract peer id from public key")
return false
}
// verify that message author node id matches the provided node public key
if idFromKey != peerId {
log.Println(err, "Node id and provided public key mismatch")
return false
}
res, err := key.Verify(data, signature)
if err != nil {
log.Println(err, "Error authenticating data")
return false
}
return res
}
// helper method - generate message data shared between all node's p2p protocols
// messageId: unique for requests, copied from request for responses
func (n *Node) NewMessageData(messageId string, gossip bool) *p2p.MessageData {
// Add protobufs bin data for message author public key
// this is useful for authenticating messages forwarded by a node authored by another node
nodePubKey, err := crypto.MarshalPublicKey(n.Peerstore().PubKey(n.ID()))
if err != nil {
panic("Failed to get public key for sender from local peer store.")
}
return &p2p.MessageData{ClientVersion: clientVersion,
NodeId: n.ID().String(),
NodePubKey: nodePubKey,
Timestamp: time.Now().Unix(),
Id: messageId,
Gossip: gossip}
}
// helper method - writes a protobuf go data object to a network stream
// data: reference of protobuf go data object to send (not the object itself)
// s: network stream to write the data to
func (n *Node) sendProtoMessage(id peer.ID, p protocol.ID, data proto.Message) bool {
s, err := n.NewStream(context.Background(), id, p)
if err != nil {
log.Println(err)
return false
}
defer s.Close()
writer := ggio.NewFullWriter(s)
err = writer.WriteMsg(data)
if err != nil {
log.Println(err)
s.Reset()
return false
}
return true
}