github.com/ethereum/go-ethereum@v1.16.1/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 (
    35  	incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
    36  )
    37  
    38  // MustParseV4 parses a node URL. It panics if the URL is not valid.
    39  func MustParseV4(rawurl string) *Node {
    40  	n, err := ParseV4(rawurl)
    41  	if err != nil {
    42  		panic("invalid node URL: " + err.Error())
    43  	}
    44  	return n
    45  }
    46  
    47  // ParseV4 parses a node URL.
    48  //
    49  // There are two basic forms of node URLs:
    50  //
    51  //   - incomplete nodes, which only have the public key (node ID)
    52  //   - complete nodes, which contain the public key and IP/Port information
    53  //
    54  // For incomplete nodes, the designator must look like one of these
    55  //
    56  //	enode://<hex node id>
    57  //	<hex node id>
    58  //
    59  // For complete nodes, the node ID is encoded in the username portion
    60  // of the URL, separated from the host by an @ sign. The hostname can
    61  // only be given as an IP address or using DNS domain name.
    62  // The port in the host name section is the TCP listening port. If the
    63  // TCP and UDP (discovery) ports differ, the UDP port is specified as
    64  // query parameter "discport".
    65  //
    66  // In the following example, the node URL describes
    67  // a node with IP address 10.3.58.6, TCP listening port 30303
    68  // and UDP discovery port 30301.
    69  //
    70  //	enode://<hex node id>@10.3.58.6:30303?discport=30301
    71  func ParseV4(rawurl string) (*Node, error) {
    72  	if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
    73  		id, err := parsePubkey(m[1])
    74  		if err != nil {
    75  			return nil, fmt.Errorf("invalid public key (%v)", err)
    76  		}
    77  		return NewV4(id, nil, 0, 0), nil
    78  	}
    79  	return parseComplete(rawurl)
    80  }
    81  
    82  // NewV4 creates a node from discovery v4 node information. The record
    83  // contained in the node has a zero-length signature.
    84  func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
    85  	var r enr.Record
    86  	if len(ip) > 0 {
    87  		r.Set(enr.IP(ip))
    88  	}
    89  	if udp != 0 {
    90  		r.Set(enr.UDP(udp))
    91  	}
    92  	if tcp != 0 {
    93  		r.Set(enr.TCP(tcp))
    94  	}
    95  	signV4Compat(&r, pubkey)
    96  	n, err := New(v4CompatID{}, &r)
    97  	if err != nil {
    98  		panic(err)
    99  	}
   100  	return n
   101  }
   102  
   103  // isNewV4 returns true for nodes created by NewV4.
   104  func isNewV4(n *Node) bool {
   105  	var k s256raw
   106  	return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0
   107  }
   108  
   109  func parseComplete(rawurl string) (*Node, error) {
   110  	var (
   111  		id               *ecdsa.PublicKey
   112  		tcpPort, udpPort uint64
   113  	)
   114  	u, err := url.Parse(rawurl)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	if u.Scheme != "enode" {
   119  		return nil, errors.New("invalid URL scheme, want \"enode\"")
   120  	}
   121  	// Parse the Node ID from the user portion.
   122  	if u.User == nil {
   123  		return nil, errors.New("does not contain node ID")
   124  	}
   125  	if id, err = parsePubkey(u.User.String()); err != nil {
   126  		return nil, fmt.Errorf("invalid public key (%v)", err)
   127  	}
   128  
   129  	// Parse the IP and ports.
   130  	ip := net.ParseIP(u.Hostname())
   131  	if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil {
   132  		return nil, errors.New("invalid port")
   133  	}
   134  	udpPort = tcpPort
   135  	qv := u.Query()
   136  	if qv.Get("discport") != "" {
   137  		udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
   138  		if err != nil {
   139  			return nil, errors.New("invalid discport in query")
   140  		}
   141  	}
   142  
   143  	// Create the node.
   144  	node := NewV4(id, ip, int(tcpPort), int(udpPort))
   145  	if ip == nil && u.Hostname() != "" {
   146  		node = node.WithHostname(u.Hostname())
   147  	}
   148  	return node, nil
   149  }
   150  
   151  // parsePubkey parses a hex-encoded secp256k1 public key.
   152  func parsePubkey(in string) (*ecdsa.PublicKey, error) {
   153  	b, err := hex.DecodeString(in)
   154  	if err != nil {
   155  		return nil, err
   156  	} else if len(b) != 64 {
   157  		return nil, fmt.Errorf("wrong length, want %d hex chars", 128)
   158  	}
   159  	b = append([]byte{0x4}, b...)
   160  	return crypto.UnmarshalPubkey(b)
   161  }
   162  
   163  func (n *Node) URLv4() string {
   164  	var (
   165  		scheme enr.ID
   166  		nodeid string
   167  		key    ecdsa.PublicKey
   168  	)
   169  	n.Load(&scheme)
   170  	n.Load((*Secp256k1)(&key))
   171  	switch {
   172  	case scheme == "v4" || key != ecdsa.PublicKey{}:
   173  		nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:])
   174  	default:
   175  		nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:])
   176  	}
   177  	u := url.URL{Scheme: "enode"}
   178  	if n.Hostname() != "" {
   179  		// For nodes with a DNS name: include DNS name, TCP port, and optional UDP port
   180  		u.User = url.User(nodeid)
   181  		u.Host = fmt.Sprintf("%s:%d", n.Hostname(), n.TCP())
   182  		if n.UDP() != n.TCP() {
   183  			u.RawQuery = "discport=" + strconv.Itoa(n.UDP())
   184  		}
   185  	} else if n.ip.IsValid() {
   186  		// For IP-based nodes: include IP address, TCP port, and optional UDP port
   187  		addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
   188  		u.User = url.User(nodeid)
   189  		u.Host = addr.String()
   190  		if n.UDP() != n.TCP() {
   191  			u.RawQuery = "discport=" + strconv.Itoa(n.UDP())
   192  		}
   193  	} else {
   194  		u.Host = nodeid
   195  	}
   196  	return u.String()
   197  }
   198  
   199  // PubkeyToIDV4 derives the v4 node address from the given public key.
   200  func PubkeyToIDV4(key *ecdsa.PublicKey) ID {
   201  	e := make([]byte, 64)
   202  	math.ReadBits(key.X, e[:len(e)/2])
   203  	math.ReadBits(key.Y, e[len(e)/2:])
   204  	return ID(crypto.Keccak256Hash(e))
   205  }