package types import ( "encoding/hex" "errors" "fmt" "regexp" "strings" "github.com/tendermint/tendermint/crypto" ) // NodeIDByteLength is the length of a crypto.Address. Currently only 20. // FIXME: support other length addresses? const NodeIDByteLength = crypto.AddressSize // reNodeID is a regexp for valid node IDs. var reNodeID = regexp.MustCompile(`^[0-9a-f]{40}$`) // NodeID is a hex-encoded crypto.Address. It must be lowercased // (for uniqueness) and of length 2*NodeIDByteLength. type NodeID string // NewNodeID returns a lowercased (normalized) NodeID, or errors if the // node ID is invalid. func NewNodeID(nodeID string) (NodeID, error) { n := NodeID(strings.ToLower(nodeID)) return n, n.Validate() } // IDAddressString returns id@hostPort. It strips the leading // protocol from protocolHostPort if it exists. func (id NodeID) AddressString(protocolHostPort string) string { hostPort := removeProtocolIfDefined(protocolHostPort) return fmt.Sprintf("%s@%s", id, hostPort) } // NodeIDFromPubKey creates a node ID from a given PubKey address. func NodeIDFromPubKey(pubKey crypto.PubKey) NodeID { return NodeID(hex.EncodeToString(pubKey.Address())) } // Bytes converts the node ID to its binary byte representation. func (id NodeID) Bytes() ([]byte, error) { bz, err := hex.DecodeString(string(id)) if err != nil { return nil, fmt.Errorf("invalid node ID encoding: %w", err) } return bz, nil } // Validate validates the NodeID. func (id NodeID) Validate() error { switch { case len(id) == 0: return errors.New("empty node ID") case len(id) != 2*NodeIDByteLength: return fmt.Errorf("invalid node ID length %d, expected %d", len(id), 2*NodeIDByteLength) case !reNodeID.MatchString(string(id)): return fmt.Errorf("node ID can only contain lowercased hex digits") default: return nil } }