github.com/embreum/go-ethereum@v1.9.6/p2p/dnsdisc/tree.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 dnsdisc
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/ecdsa"
    22  	"encoding/base32"
    23  	"encoding/base64"
    24  	"fmt"
    25  	"io"
    26  	"sort"
    27  	"strings"
    28  
    29  	"github.com/ethereum/go-ethereum/crypto"
    30  	"github.com/ethereum/go-ethereum/p2p/enode"
    31  	"github.com/ethereum/go-ethereum/p2p/enr"
    32  	"github.com/ethereum/go-ethereum/rlp"
    33  	"golang.org/x/crypto/sha3"
    34  )
    35  
    36  // Tree is a merkle tree of node records.
    37  type Tree struct {
    38  	root    *rootEntry
    39  	entries map[string]entry
    40  }
    41  
    42  // Sign signs the tree with the given private key and sets the sequence number.
    43  func (t *Tree) Sign(key *ecdsa.PrivateKey, domain string) (url string, err error) {
    44  	root := *t.root
    45  	sig, err := crypto.Sign(root.sigHash(), key)
    46  	if err != nil {
    47  		return "", err
    48  	}
    49  	root.sig = sig
    50  	t.root = &root
    51  	link := &linkEntry{domain, &key.PublicKey}
    52  	return link.url(), nil
    53  }
    54  
    55  // SetSignature verifies the given signature and assigns it as the tree's current
    56  // signature if valid.
    57  func (t *Tree) SetSignature(pubkey *ecdsa.PublicKey, signature string) error {
    58  	sig, err := b64format.DecodeString(signature)
    59  	if err != nil || len(sig) != crypto.SignatureLength {
    60  		return errInvalidSig
    61  	}
    62  	root := *t.root
    63  	root.sig = sig
    64  	if !root.verifySignature(pubkey) {
    65  		return errInvalidSig
    66  	}
    67  	t.root = &root
    68  	return nil
    69  }
    70  
    71  // Seq returns the sequence number of the tree.
    72  func (t *Tree) Seq() uint {
    73  	return t.root.seq
    74  }
    75  
    76  // Signature returns the signature of the tree.
    77  func (t *Tree) Signature() string {
    78  	return b64format.EncodeToString(t.root.sig)
    79  }
    80  
    81  // ToTXT returns all DNS TXT records required for the tree.
    82  func (t *Tree) ToTXT(domain string) map[string]string {
    83  	records := map[string]string{domain: t.root.String()}
    84  	for _, e := range t.entries {
    85  		sd := subdomain(e)
    86  		if domain != "" {
    87  			sd = sd + "." + domain
    88  		}
    89  		records[sd] = e.String()
    90  	}
    91  	return records
    92  }
    93  
    94  // Links returns all links contained in the tree.
    95  func (t *Tree) Links() []string {
    96  	var links []string
    97  	for _, e := range t.entries {
    98  		if le, ok := e.(*linkEntry); ok {
    99  			links = append(links, le.url())
   100  		}
   101  	}
   102  	return links
   103  }
   104  
   105  // Nodes returns all nodes contained in the tree.
   106  func (t *Tree) Nodes() []*enode.Node {
   107  	var nodes []*enode.Node
   108  	for _, e := range t.entries {
   109  		if ee, ok := e.(*enrEntry); ok {
   110  			nodes = append(nodes, ee.node)
   111  		}
   112  	}
   113  	return nodes
   114  }
   115  
   116  const (
   117  	hashAbbrev    = 16
   118  	maxChildren   = 300 / (hashAbbrev * (13 / 8))
   119  	minHashLength = 12
   120  	rootPrefix    = "enrtree-root=v1"
   121  )
   122  
   123  // MakeTree creates a tree containing the given nodes and links.
   124  func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
   125  	// Sort records by ID and ensure all nodes have a valid record.
   126  	records := make([]*enode.Node, len(nodes))
   127  	copy(records, nodes)
   128  	sortByID(records)
   129  	for _, n := range records {
   130  		if len(n.Record().Signature()) == 0 {
   131  			return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID())
   132  		}
   133  	}
   134  
   135  	// Create the leaf list.
   136  	enrEntries := make([]entry, len(records))
   137  	for i, r := range records {
   138  		enrEntries[i] = &enrEntry{r}
   139  	}
   140  	linkEntries := make([]entry, len(links))
   141  	for i, l := range links {
   142  		le, err := parseURL(l)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		linkEntries[i] = le
   147  	}
   148  
   149  	// Create intermediate nodes.
   150  	t := &Tree{entries: make(map[string]entry)}
   151  	eroot := t.build(enrEntries)
   152  	t.entries[subdomain(eroot)] = eroot
   153  	lroot := t.build(linkEntries)
   154  	t.entries[subdomain(lroot)] = lroot
   155  	t.root = &rootEntry{seq: seq, eroot: subdomain(eroot), lroot: subdomain(lroot)}
   156  	return t, nil
   157  }
   158  
   159  func (t *Tree) build(entries []entry) entry {
   160  	if len(entries) == 1 {
   161  		return entries[0]
   162  	}
   163  	if len(entries) <= maxChildren {
   164  		hashes := make([]string, len(entries))
   165  		for i, e := range entries {
   166  			hashes[i] = subdomain(e)
   167  			t.entries[hashes[i]] = e
   168  		}
   169  		return &subtreeEntry{hashes}
   170  	}
   171  	var subtrees []entry
   172  	for len(entries) > 0 {
   173  		n := maxChildren
   174  		if len(entries) < n {
   175  			n = len(entries)
   176  		}
   177  		sub := t.build(entries[:n])
   178  		entries = entries[n:]
   179  		subtrees = append(subtrees, sub)
   180  		t.entries[subdomain(sub)] = sub
   181  	}
   182  	return t.build(subtrees)
   183  }
   184  
   185  func sortByID(nodes []*enode.Node) []*enode.Node {
   186  	sort.Slice(nodes, func(i, j int) bool {
   187  		return bytes.Compare(nodes[i].ID().Bytes(), nodes[j].ID().Bytes()) < 0
   188  	})
   189  	return nodes
   190  }
   191  
   192  // Entry Types
   193  
   194  type entry interface {
   195  	fmt.Stringer
   196  }
   197  
   198  type (
   199  	rootEntry struct {
   200  		eroot string
   201  		lroot string
   202  		seq   uint
   203  		sig   []byte
   204  	}
   205  	subtreeEntry struct {
   206  		children []string
   207  	}
   208  	enrEntry struct {
   209  		node *enode.Node
   210  	}
   211  	linkEntry struct {
   212  		domain string
   213  		pubkey *ecdsa.PublicKey
   214  	}
   215  )
   216  
   217  // Entry Encoding
   218  
   219  var (
   220  	b32format = base32.StdEncoding.WithPadding(base32.NoPadding)
   221  	b64format = base64.URLEncoding
   222  )
   223  
   224  func subdomain(e entry) string {
   225  	h := sha3.NewLegacyKeccak256()
   226  	io.WriteString(h, e.String())
   227  	return b32format.EncodeToString(h.Sum(nil)[:16])
   228  }
   229  
   230  func (e *rootEntry) String() string {
   231  	return fmt.Sprintf(rootPrefix+" e=%s l=%s seq=%d sig=%s", e.eroot, e.lroot, e.seq, b64format.EncodeToString(e.sig))
   232  }
   233  
   234  func (e *rootEntry) sigHash() []byte {
   235  	h := sha3.NewLegacyKeccak256()
   236  	fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq)
   237  	return h.Sum(nil)
   238  }
   239  
   240  func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool {
   241  	sig := e.sig[:crypto.RecoveryIDOffset] // remove recovery id
   242  	return crypto.VerifySignature(crypto.FromECDSAPub(pubkey), e.sigHash(), sig)
   243  }
   244  
   245  func (e *subtreeEntry) String() string {
   246  	return "enrtree=" + strings.Join(e.children, ",")
   247  }
   248  
   249  func (e *enrEntry) String() string {
   250  	enc, _ := rlp.EncodeToBytes(e.node.Record())
   251  	return "enr=" + b64format.EncodeToString(enc)
   252  }
   253  
   254  func (e *linkEntry) String() string {
   255  	return "enrtree-link=" + e.link()
   256  }
   257  
   258  func (e *linkEntry) url() string {
   259  	return "enrtree://" + e.link()
   260  }
   261  
   262  func (e *linkEntry) link() string {
   263  	return fmt.Sprintf("%s@%s", b32format.EncodeToString(crypto.CompressPubkey(e.pubkey)), e.domain)
   264  }
   265  
   266  // Entry Parsing
   267  
   268  func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) {
   269  	switch {
   270  	case strings.HasPrefix(e, "enrtree-link="):
   271  		return parseLink(e[13:])
   272  	case strings.HasPrefix(e, "enrtree="):
   273  		return parseSubtree(e[8:])
   274  	case strings.HasPrefix(e, "enr="):
   275  		return parseENR(e[4:], validSchemes)
   276  	default:
   277  		return nil, errUnknownEntry
   278  	}
   279  }
   280  
   281  func parseRoot(e string) (rootEntry, error) {
   282  	var eroot, lroot, sig string
   283  	var seq uint
   284  	if _, err := fmt.Sscanf(e, rootPrefix+" e=%s l=%s seq=%d sig=%s", &eroot, &lroot, &seq, &sig); err != nil {
   285  		return rootEntry{}, entryError{"root", errSyntax}
   286  	}
   287  	if !isValidHash(eroot) || !isValidHash(lroot) {
   288  		return rootEntry{}, entryError{"root", errInvalidChild}
   289  	}
   290  	sigb, err := b64format.DecodeString(sig)
   291  	if err != nil || len(sigb) != crypto.SignatureLength {
   292  		return rootEntry{}, entryError{"root", errInvalidSig}
   293  	}
   294  	return rootEntry{eroot, lroot, seq, sigb}, nil
   295  }
   296  
   297  func parseLink(e string) (entry, error) {
   298  	pos := strings.IndexByte(e, '@')
   299  	if pos == -1 {
   300  		return nil, entryError{"link", errNoPubkey}
   301  	}
   302  	keystring, domain := e[:pos], e[pos+1:]
   303  	keybytes, err := b32format.DecodeString(keystring)
   304  	if err != nil {
   305  		return nil, entryError{"link", errBadPubkey}
   306  	}
   307  	key, err := crypto.DecompressPubkey(keybytes)
   308  	if err != nil {
   309  		return nil, entryError{"link", errBadPubkey}
   310  	}
   311  	return &linkEntry{domain, key}, nil
   312  }
   313  
   314  func parseSubtree(e string) (entry, error) {
   315  	if e == "" {
   316  		return &subtreeEntry{}, nil // empty entry is OK
   317  	}
   318  	hashes := make([]string, 0, strings.Count(e, ","))
   319  	for _, c := range strings.Split(e, ",") {
   320  		if !isValidHash(c) {
   321  			return nil, entryError{"subtree", errInvalidChild}
   322  		}
   323  		hashes = append(hashes, c)
   324  	}
   325  	return &subtreeEntry{hashes}, nil
   326  }
   327  
   328  func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) {
   329  	enc, err := b64format.DecodeString(e)
   330  	if err != nil {
   331  		return nil, entryError{"enr", errInvalidENR}
   332  	}
   333  	var rec enr.Record
   334  	if err := rlp.DecodeBytes(enc, &rec); err != nil {
   335  		return nil, entryError{"enr", err}
   336  	}
   337  	n, err := enode.New(validSchemes, &rec)
   338  	if err != nil {
   339  		return nil, entryError{"enr", err}
   340  	}
   341  	return &enrEntry{n}, nil
   342  }
   343  
   344  func isValidHash(s string) bool {
   345  	dlen := b32format.DecodedLen(len(s))
   346  	if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") {
   347  		return false
   348  	}
   349  	buf := make([]byte, 32)
   350  	_, err := b32format.Decode(buf, []byte(s))
   351  	return err == nil
   352  }
   353  
   354  // truncateHash truncates the given base32 hash string to the minimum acceptable length.
   355  func truncateHash(hash string) string {
   356  	maxLen := b32format.EncodedLen(minHashLength)
   357  	if len(hash) < maxLen {
   358  		panic(fmt.Errorf("dnsdisc: hash %q is too short", hash))
   359  	}
   360  	return hash[:maxLen]
   361  }
   362  
   363  // URL encoding
   364  
   365  // ParseURL parses an enrtree:// URL and returns its components.
   366  func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) {
   367  	le, err := parseURL(url)
   368  	if err != nil {
   369  		return "", nil, err
   370  	}
   371  	return le.domain, le.pubkey, nil
   372  }
   373  
   374  func parseURL(url string) (*linkEntry, error) {
   375  	const scheme = "enrtree://"
   376  	if !strings.HasPrefix(url, scheme) {
   377  		return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
   378  	}
   379  	le, err := parseLink(url[len(scheme):])
   380  	if err != nil {
   381  		return nil, err.(entryError).err
   382  	}
   383  	return le.(*linkEntry), nil
   384  }