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 }