github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/p2p/discover/node.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discover 18 19 import ( 20 "crypto/ecdsa" 21 "crypto/elliptic" 22 "encoding/hex" 23 "errors" 24 "fmt" 25 "math/big" 26 "net" 27 "net/url" 28 "regexp" 29 "strconv" 30 "strings" 31 32 "time" 33 34 "github.com/ethereumproject/go-ethereum/common" 35 "github.com/ethereumproject/go-ethereum/crypto" 36 "github.com/ethereumproject/go-ethereum/crypto/secp256k1" 37 ) 38 39 const nodeIDBits = 512 40 41 // Node represents a host on the network. 42 // The fields of Node may not be modified. 43 type Node struct { 44 IP net.IP `json:"ip"` // len 4 for IPv4 or 16 for IPv6 45 // port numbers 46 UDP uint16 `json:"udp"` 47 TCP uint16 `json:"tcp"` 48 ID NodeID `json:"node_id"` // the node's public key 49 50 // This is a cached copy of sha3(ID) which is used for node 51 // distance calculations. This is part of Node in order to make it 52 // possible to write tests that need a node at a certain distance. 53 // In those tests, the content of sha will not actually correspond 54 // with ID. 55 sha common.Hash `json:"sha"` 56 57 // whether this node is currently being pinged in order to replace 58 // it in a bucket 59 contested bool `json:"contested"` 60 61 // Time when the node was added to the table. 62 addedAt time.Time 63 } 64 65 // NewNode creates a new node. It is mostly meant to be used for 66 // testing purposes. 67 func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node { 68 if ipv4 := ip.To4(); ipv4 != nil { 69 ip = ipv4 70 } 71 return &Node{ 72 IP: ip, 73 UDP: udpPort, 74 TCP: tcpPort, 75 ID: id, 76 sha: crypto.Keccak256Hash(id[:]), 77 } 78 } 79 80 func (n *Node) addr() *net.UDPAddr { 81 return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)} 82 } 83 84 // Incomplete returns true for nodes with no IP address. 85 func (n *Node) Incomplete() bool { 86 return n.IP == nil 87 } 88 89 // checks whether n is a valid complete node. 90 func (n *Node) validateComplete() error { 91 if n.Incomplete() { 92 return errors.New("incomplete node") 93 } 94 if n.UDP == 0 { 95 return errors.New("missing UDP port") 96 } 97 if n.TCP == 0 { 98 return errors.New("missing TCP port") 99 } 100 if n.IP.IsMulticast() || n.IP.IsUnspecified() { 101 return errors.New("invalid IP (multicast/unspecified)") 102 } 103 _, err := n.ID.Pubkey() // validate the key (on curve, etc.) 104 return err 105 } 106 107 // The string representation of a Node is a URL. 108 // Please see ParseNode for a description of the format. 109 func (n *Node) String() string { 110 u := url.URL{Scheme: "enode"} 111 if n.Incomplete() { 112 u.Host = fmt.Sprintf("%x", n.ID[:]) 113 } else { 114 addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)} 115 u.User = url.User(fmt.Sprintf("%x", n.ID[:])) 116 u.Host = addr.String() 117 if n.UDP != n.TCP { 118 u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP)) 119 } 120 } 121 return u.String() 122 } 123 124 var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") 125 126 // ParseNode parses a node designator. 127 // 128 // There are two basic forms of node designators 129 // - incomplete nodes, which only have the public key (node ID) 130 // - complete nodes, which contain the public key and IP/Port information 131 // 132 // For incomplete nodes, the designator must look like one of these 133 // 134 // enode://<hex node id> 135 // <hex node id> 136 // 137 // For complete nodes, the node ID is encoded in the username portion 138 // of the URL, separated from the host by an @ sign. The hostname can 139 // only be given as an IP address, DNS domain names are not allowed. 140 // The port in the host name section is the TCP listening port. If the 141 // TCP and UDP (discovery) ports differ, the UDP port is specified as 142 // query parameter "discport". 143 // 144 // In the following example, the node URL describes 145 // a node with IP address 10.3.58.6, TCP listening port 30303 146 // and UDP discovery port 30301. 147 // 148 // enode://<hex node id>@10.3.58.6:30303?discport=30301 149 func ParseNode(rawurl string) (*Node, error) { 150 if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { 151 id, err := HexID(m[1]) 152 if err != nil { 153 return nil, fmt.Errorf("invalid node ID (%v)", err) 154 } 155 return NewNode(id, nil, 0, 0), nil 156 } 157 return parseComplete(rawurl) 158 } 159 160 func parseComplete(rawurl string) (*Node, error) { 161 var ( 162 id NodeID 163 ip net.IP 164 tcpPort, udpPort uint64 165 ) 166 u, err := url.Parse(rawurl) 167 if err != nil { 168 return nil, err 169 } 170 if u.Scheme != "enode" { 171 return nil, errors.New("invalid URL scheme, want \"enode\"") 172 } 173 // Parse the Node ID from the user portion. 174 if u.User == nil { 175 return nil, errors.New("does not contain node ID") 176 } 177 if id, err = HexID(u.User.String()); err != nil { 178 return nil, fmt.Errorf("invalid node ID (%v)", err) 179 } 180 // Parse the IP address. 181 host, port, err := net.SplitHostPort(u.Host) 182 if err != nil { 183 return nil, fmt.Errorf("invalid host: %v", err) 184 } 185 if ip = net.ParseIP(host); ip == nil { 186 return nil, errors.New("invalid IP address") 187 } 188 // Ensure the IP is 4 bytes long for IPv4 addresses. 189 if ipv4 := ip.To4(); ipv4 != nil { 190 ip = ipv4 191 } 192 // Parse the port numbers. 193 if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil { 194 return nil, errors.New("invalid port") 195 } 196 udpPort = tcpPort 197 qv := u.Query() 198 if qv.Get("discport") != "" { 199 udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16) 200 if err != nil { 201 return nil, errors.New("invalid discport in query") 202 } 203 } 204 return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil 205 } 206 207 // MustParseNode parses a node URL. It panics if the URL is not valid. 208 func MustParseNode(rawurl string) *Node { 209 n, err := ParseNode(rawurl) 210 if err != nil { 211 panic("invalid node URL: " + err.Error()) 212 } 213 return n 214 } 215 216 // NodeID is a unique identifier for each node. 217 // The node identifier is a marshaled elliptic curve public key. 218 type NodeID [nodeIDBits / 8]byte 219 220 // NodeID prints as a long hexadecimal number. 221 func (n NodeID) String() string { 222 return fmt.Sprintf("%x", n[:]) 223 } 224 225 // The Go syntax representation of a NodeID is a call to HexID. 226 func (n NodeID) GoString() string { 227 return fmt.Sprintf("discover.HexID(\"%x\")", n[:]) 228 } 229 230 // HexID converts a hex string to a NodeID. 231 // The string may be prefixed with 0x. 232 func HexID(in string) (NodeID, error) { 233 if strings.HasPrefix(in, "0x") { 234 in = in[2:] 235 } 236 var id NodeID 237 b, err := hex.DecodeString(in) 238 if err != nil { 239 return id, err 240 } else if len(b) != len(id) { 241 return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2) 242 } 243 copy(id[:], b) 244 return id, nil 245 } 246 247 // MustHexID converts a hex string to a NodeID. 248 // It panics if the string is not a valid NodeID. 249 func MustHexID(in string) NodeID { 250 id, err := HexID(in) 251 if err != nil { 252 panic(err) 253 } 254 return id 255 } 256 257 // PubkeyID returns a marshaled representation of the given public key. 258 func PubkeyID(pub *ecdsa.PublicKey) NodeID { 259 var id NodeID 260 pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y) 261 if len(pbytes)-1 != len(id) { 262 panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes))) 263 } 264 copy(id[:], pbytes[1:]) 265 return id 266 } 267 268 // Pubkey returns the public key represented by the node ID. 269 // It returns an error if the ID is not a point on the curve. 270 func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) { 271 p := &ecdsa.PublicKey{Curve: secp256k1.S256(), X: new(big.Int), Y: new(big.Int)} 272 half := len(id) / 2 273 p.X.SetBytes(id[:half]) 274 p.Y.SetBytes(id[half:]) 275 if !p.Curve.IsOnCurve(p.X, p.Y) { 276 return nil, errors.New("id is invalid secp256k1 curve point") 277 } 278 return p, nil 279 } 280 281 // recoverNodeID computes the public key used to sign the 282 // given hash from the signature. 283 func recoverNodeID(hash, sig []byte) (id NodeID, err error) { 284 pubkey, err := secp256k1.RecoverPubkey(hash, sig) 285 if err != nil { 286 return id, err 287 } 288 if len(pubkey)-1 != len(id) { 289 return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8) 290 } 291 for i := range id { 292 id[i] = pubkey[i+1] 293 } 294 return id, nil 295 }