node.go
1 package main 2 3 import ( 4 "context" 5 "log" 6 "time" 7 8 "github.com/libp2p/go-libp2p/core/crypto" 9 "github.com/libp2p/go-libp2p/core/host" 10 "github.com/libp2p/go-libp2p/core/peer" 11 "github.com/libp2p/go-libp2p/core/protocol" 12 p2p "github.com/libp2p/go-libp2p/examples/multipro/pb" 13 14 ggio "github.com/gogo/protobuf/io" 15 "github.com/gogo/protobuf/proto" 16 ) 17 18 // node client version 19 const clientVersion = "go-p2p-node/0.0.1" 20 21 // Node type - a p2p host implementing one or more p2p protocols 22 type Node struct { 23 host.Host // lib-p2p host 24 *PingProtocol // ping protocol impl 25 *EchoProtocol // echo protocol impl 26 // add other protocols here... 27 } 28 29 // Create a new node with its implemented protocols 30 func NewNode(host host.Host, done chan bool) *Node { 31 node := &Node{Host: host} 32 node.PingProtocol = NewPingProtocol(node, done) 33 node.EchoProtocol = NewEchoProtocol(node, done) 34 return node 35 } 36 37 // Authenticate incoming p2p message 38 // message: a protobufs go data object 39 // data: common p2p message data 40 func (n *Node) authenticateMessage(message proto.Message, data *p2p.MessageData) bool { 41 // store a temp ref to signature and remove it from message data 42 // sign is a string to allow easy reset to zero-value (empty string) 43 sign := data.Sign 44 data.Sign = nil 45 46 // marshall data without the signature to protobufs3 binary format 47 bin, err := proto.Marshal(message) 48 if err != nil { 49 log.Println(err, "failed to marshal pb message") 50 return false 51 } 52 53 // restore sig in message data (for possible future use) 54 data.Sign = sign 55 56 // restore peer id binary format from base58 encoded node id data 57 peerId, err := peer.Decode(data.NodeId) 58 if err != nil { 59 log.Println(err, "Failed to decode node id from base58") 60 return false 61 } 62 63 // verify the data was authored by the signing peer identified by the public key 64 // and signature included in the message 65 return n.verifyData(bin, []byte(sign), peerId, data.NodePubKey) 66 } 67 68 // sign an outgoing p2p message payload 69 func (n *Node) signProtoMessage(message proto.Message) ([]byte, error) { 70 data, err := proto.Marshal(message) 71 if err != nil { 72 return nil, err 73 } 74 return n.signData(data) 75 } 76 77 // sign binary data using the local node's private key 78 func (n *Node) signData(data []byte) ([]byte, error) { 79 key := n.Peerstore().PrivKey(n.ID()) 80 res, err := key.Sign(data) 81 return res, err 82 } 83 84 // Verify incoming p2p message data integrity 85 // data: data to verify 86 // signature: author signature provided in the message payload 87 // peerId: author peer id from the message payload 88 // pubKeyData: author public key from the message payload 89 func (n *Node) verifyData(data []byte, signature []byte, peerId peer.ID, pubKeyData []byte) bool { 90 key, err := crypto.UnmarshalPublicKey(pubKeyData) 91 if err != nil { 92 log.Println(err, "Failed to extract key from message key data") 93 return false 94 } 95 96 // extract node id from the provided public key 97 idFromKey, err := peer.IDFromPublicKey(key) 98 99 if err != nil { 100 log.Println(err, "Failed to extract peer id from public key") 101 return false 102 } 103 104 // verify that message author node id matches the provided node public key 105 if idFromKey != peerId { 106 log.Println(err, "Node id and provided public key mismatch") 107 return false 108 } 109 110 res, err := key.Verify(data, signature) 111 if err != nil { 112 log.Println(err, "Error authenticating data") 113 return false 114 } 115 116 return res 117 } 118 119 // helper method - generate message data shared between all node's p2p protocols 120 // messageId: unique for requests, copied from request for responses 121 func (n *Node) NewMessageData(messageId string, gossip bool) *p2p.MessageData { 122 // Add protobufs bin data for message author public key 123 // this is useful for authenticating messages forwarded by a node authored by another node 124 nodePubKey, err := crypto.MarshalPublicKey(n.Peerstore().PubKey(n.ID())) 125 126 if err != nil { 127 panic("Failed to get public key for sender from local peer store.") 128 } 129 130 return &p2p.MessageData{ClientVersion: clientVersion, 131 NodeId: n.ID().String(), 132 NodePubKey: nodePubKey, 133 Timestamp: time.Now().Unix(), 134 Id: messageId, 135 Gossip: gossip} 136 } 137 138 // helper method - writes a protobuf go data object to a network stream 139 // data: reference of protobuf go data object to send (not the object itself) 140 // s: network stream to write the data to 141 func (n *Node) sendProtoMessage(id peer.ID, p protocol.ID, data proto.Message) bool { 142 s, err := n.NewStream(context.Background(), id, p) 143 if err != nil { 144 log.Println(err) 145 return false 146 } 147 defer s.Close() 148 149 writer := ggio.NewFullWriter(s) 150 err = writer.WriteMsg(data) 151 if err != nil { 152 log.Println(err) 153 s.Reset() 154 return false 155 } 156 return true 157 }