github.com/hyperion-hyn/go-ethereum@v2.4.0+incompatible/p2p/enode/urlv4.go (about) 1 // Copyright 2018 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 enode 18 19 import ( 20 "crypto/ecdsa" 21 "encoding/hex" 22 "errors" 23 "fmt" 24 "net" 25 "net/url" 26 "regexp" 27 "strconv" 28 29 "github.com/ethereum/go-ethereum/common/math" 30 "github.com/ethereum/go-ethereum/crypto" 31 "github.com/ethereum/go-ethereum/p2p/enr" 32 ) 33 34 var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$") 35 36 // MustParseV4 parses a node URL. It panics if the URL is not valid. 37 func MustParseV4(rawurl string) *Node { 38 n, err := ParseV4(rawurl) 39 if err != nil { 40 panic("invalid node URL: " + err.Error()) 41 } 42 return n 43 } 44 45 // ParseV4 parses a node URL. 46 // 47 // There are two basic forms of node URLs: 48 // 49 // - incomplete nodes, which only have the public key (node ID) 50 // - complete nodes, which contain the public key and IP/Port information 51 // 52 // For incomplete nodes, the designator must look like one of these 53 // 54 // enode://<hex node id> 55 // <hex node id> 56 // 57 // For complete nodes, the node ID is encoded in the username portion 58 // of the URL, separated from the host by an @ sign. The hostname can 59 // only be given as an IP address, DNS domain names are not allowed. 60 // The port in the host name section is the TCP listening port. If the 61 // TCP and UDP (discovery) ports differ, the UDP port is specified as 62 // query parameter "discport". 63 // 64 // In the following example, the node URL describes 65 // a node with IP address 10.3.58.6, TCP listening port 30303 66 // and UDP discovery port 30301. 67 // 68 // enode://<hex node id>@10.3.58.6:30303?discport=30301 69 func ParseV4(rawurl string) (*Node, error) { 70 if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil { 71 id, err := parsePubkey(m[1]) 72 if err != nil { 73 return nil, fmt.Errorf("invalid node ID (%v)", err) 74 } 75 return NewV4(id, nil, 0, 0, 0), nil 76 } 77 return parseComplete(rawurl) 78 } 79 80 // NewV4 creates a node from discovery v4 node information. The record 81 // contained in the node has a zero-length signature. 82 func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp, raftPort int) *Node { 83 var r enr.Record 84 if ip != nil { 85 r.Set(enr.IP(ip)) 86 } 87 return newV4(pubkey, r, tcp, udp, raftPort) 88 } 89 90 // broken out from `func NewV4` (above) same in upstream go-ethereum, but taken out 91 // to avoid code duplication b/t NewV4 and NewV4Hostname 92 func newV4(pubkey *ecdsa.PublicKey, r enr.Record, tcp, udp, raftPort int) *Node { 93 if udp != 0 { 94 r.Set(enr.UDP(udp)) 95 } 96 if tcp != 0 { 97 r.Set(enr.TCP(tcp)) 98 } 99 100 if raftPort != 0 { // Quorum 101 r.Set(enr.RaftPort(raftPort)) 102 } 103 104 signV4Compat(&r, pubkey) 105 n, err := New(v4CompatID{}, &r) 106 if err != nil { 107 panic(err) 108 } 109 return n 110 } 111 112 // Quorum 113 114 // NewV4Hostname creates a node from discovery v4 node information. The record 115 // contained in the node has a zero-length signature. It sets the hostname of 116 // the node instead of the IP address. 117 func NewV4Hostname(pubkey *ecdsa.PublicKey, hostname string, tcp, udp, raftPort int) *Node { 118 var r enr.Record 119 if hostname != "" { 120 r.Set(enr.Hostname(hostname)) 121 } 122 return newV4(pubkey, r, tcp, udp, raftPort) 123 } 124 125 // End-Quorum 126 127 func parseComplete(rawurl string) (*Node, error) { 128 var ( 129 id *ecdsa.PublicKey 130 tcpPort, udpPort uint64 131 ) 132 u, err := url.Parse(rawurl) 133 if err != nil { 134 return nil, err 135 } 136 if u.Scheme != "enode" { 137 return nil, errors.New("invalid URL scheme, want \"enode\"") 138 } 139 // Parse the Node ID from the user portion. 140 if u.User == nil { 141 return nil, errors.New("does not contain node ID") 142 } 143 if id, err = parsePubkey(u.User.String()); err != nil { 144 return nil, fmt.Errorf("invalid node ID (%v)", err) 145 } 146 // Parse the port numbers. 147 if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil { 148 return nil, errors.New("invalid port") 149 } 150 udpPort = tcpPort 151 qv := u.Query() 152 if qv.Get("discport") != "" { 153 udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16) 154 if err != nil { 155 return nil, errors.New("invalid discport in query") 156 } 157 } 158 159 var node *Node 160 161 // Quorum 162 if qv.Get("raftport") != "" { 163 raftPort, err := strconv.ParseUint(qv.Get("raftport"), 10, 16) 164 if err != nil { 165 return nil, errors.New("invalid raftport in query") 166 } 167 node = NewV4Hostname(id, u.Hostname(), int(tcpPort), int(udpPort), int(raftPort)) 168 } else { 169 node = NewV4Hostname(id, u.Hostname(), int(tcpPort), int(udpPort), 0) 170 } 171 // End-Quorum 172 173 return node, nil 174 } 175 176 func HexPubkey(h string) (*ecdsa.PublicKey, error) { 177 k, err := parsePubkey(h) 178 if err != nil { 179 return nil, err 180 } 181 return k, err 182 } 183 184 // parsePubkey parses a hex-encoded secp256k1 public key. 185 func parsePubkey(in string) (*ecdsa.PublicKey, error) { 186 b, err := hex.DecodeString(in) 187 if err != nil { 188 return nil, err 189 } else if len(b) != 64 { 190 return nil, fmt.Errorf("wrong length, want %d hex chars", 128) 191 } 192 b = append([]byte{0x4}, b...) 193 return crypto.UnmarshalPubkey(b) 194 } 195 196 // used by Quorum RAFT - to derive enodeID 197 func (n *Node) EnodeID() string { 198 var ( 199 scheme enr.ID 200 nodeid string 201 key ecdsa.PublicKey 202 ) 203 n.Load(&scheme) 204 n.Load((*Secp256k1)(&key)) 205 switch { 206 case scheme == "v4" || key != ecdsa.PublicKey{}: 207 nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:]) 208 default: 209 nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) 210 } 211 return nodeid 212 } 213 214 func (n *Node) v4URL() string { 215 var ( 216 scheme enr.ID 217 nodeid string 218 key ecdsa.PublicKey 219 ) 220 n.Load(&scheme) 221 n.Load((*Secp256k1)(&key)) 222 switch { 223 case scheme == "v4" || key != ecdsa.PublicKey{}: 224 nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:]) 225 default: 226 nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:]) 227 } 228 u := url.URL{Scheme: "enode"} 229 if n.Incomplete() { 230 u.Host = nodeid 231 } else { 232 addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()} 233 u.User = url.User(nodeid) 234 u.Host = addr.String() 235 if n.UDP() != n.TCP() { 236 u.RawQuery = "discport=" + strconv.Itoa(n.UDP()) 237 } 238 raftPort := n.RaftPort() 239 if raftPort != 0 { 240 raftQuery := "raftport=" + strconv.Itoa(raftPort) 241 if len(u.RawQuery) > 0 { 242 u.RawQuery = u.RawQuery + "&" + raftQuery 243 } else { 244 u.RawQuery = raftQuery 245 } 246 } 247 } 248 return u.String() 249 } 250 251 // PubkeyToIDV4 derives the v4 node address from the given public key. 252 func PubkeyToIDV4(key *ecdsa.PublicKey) ID { 253 e := make([]byte, 64) 254 math.ReadBits(key.X, e[:len(e)/2]) 255 math.ReadBits(key.Y, e[len(e)/2:]) 256 return ID(crypto.Keccak256Hash(e)) 257 }