forked from libp2p/go-libp2p
-
Notifications
You must be signed in to change notification settings - Fork 0
/
node.go
157 lines (133 loc) · 4.52 KB
/
node.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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 protobuf 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 protobuf 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
}