github.com/dominant-strategies/go-quai@v0.28.2/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/dominant-strategies/go-quai/crypto"
    30  	"github.com/dominant-strategies/go-quai/p2p/enode"
    31  	"github.com/dominant-strategies/go-quai/p2p/enr"
    32  	"github.com/dominant-strategies/go-quai/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 := newLinkEntry(domain, &key.PublicKey)
    52  	return link.String(), 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  	t.root = &root
    65  	return nil
    66  }
    67  
    68  // Seq returns the sequence number of the tree.
    69  func (t *Tree) Seq() uint {
    70  	return t.root.seq
    71  }
    72  
    73  // Signature returns the signature of the tree.
    74  func (t *Tree) Signature() string {
    75  	return b64format.EncodeToString(t.root.sig)
    76  }
    77  
    78  // ToTXT returns all DNS TXT records required for the tree.
    79  func (t *Tree) ToTXT(domain string) map[string]string {
    80  	records := map[string]string{domain: t.root.String()}
    81  	for _, e := range t.entries {
    82  		sd := subdomain(e)
    83  		if domain != "" {
    84  			sd = sd + "." + domain
    85  		}
    86  		records[sd] = e.String()
    87  	}
    88  	return records
    89  }
    90  
    91  // Links returns all links contained in the tree.
    92  func (t *Tree) Links() []string {
    93  	var links []string
    94  	for _, e := range t.entries {
    95  		if le, ok := e.(*linkEntry); ok {
    96  			links = append(links, le.String())
    97  		}
    98  	}
    99  	return links
   100  }
   101  
   102  // Nodes returns all nodes contained in the tree.
   103  func (t *Tree) Nodes() []*enode.Node {
   104  	var nodes []*enode.Node
   105  	for _, e := range t.entries {
   106  		if ee, ok := e.(*enrEntry); ok {
   107  			nodes = append(nodes, ee.node)
   108  		}
   109  	}
   110  	return nodes
   111  }
   112  
   113  /*
   114  We want to keep the UDP size below 512 bytes. The UDP size is roughly:
   115  UDP length = 8 + UDP payload length ( 229 )
   116  UPD Payload length:
   117    - dns.id 2
   118    - dns.flags 2
   119    - dns.count.queries 2
   120    - dns.count.answers 2
   121    - dns.count.auth_rr 2
   122    - dns.count.add_rr 2
   123    - queries (query-size + 6)
   124    - answers :
   125    - dns.resp.name 2
   126    - dns.resp.type 2
   127    - dns.resp.class 2
   128    - dns.resp.ttl 4
   129    - dns.resp.len 2
   130    - dns.txt.length 1
   131    - dns.txt resp_data_size
   132  
   133  So the total size is roughly a fixed overhead of `39`, and the size of the
   134  query (domain name) and response.
   135  The query size is, for example, FVY6INQ6LZ33WLCHO3BPR3FH6Y.snap.mainnet.ethdisco.net (52)
   136  
   137  We also have some static data in the response, such as `enrtree-branch:`, and potentially
   138  splitting the response up with `" "`, leaving us with a size of roughly `400` that we need
   139  to stay below.
   140  
   141  The number `370` is used to have some margin for extra overhead (for example, the dns query
   142  may be larger - more subdomains).
   143  */
   144  const (
   145  	hashAbbrevSize = 1 + 16*13/8          // Size of an encoded hash (plus comma)
   146  	maxChildren    = 370 / hashAbbrevSize // 13 children
   147  	minHashLength  = 12
   148  )
   149  
   150  // MakeTree creates a tree containing the given nodes and links.
   151  func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
   152  	// Sort records by ID and ensure all nodes have a valid record.
   153  	records := make([]*enode.Node, len(nodes))
   154  
   155  	copy(records, nodes)
   156  	sortByID(records)
   157  	for _, n := range records {
   158  		if len(n.Record().Signature()) == 0 {
   159  			return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID())
   160  		}
   161  	}
   162  
   163  	// Create the leaf list.
   164  	enrEntries := make([]entry, len(records))
   165  	for i, r := range records {
   166  		enrEntries[i] = &enrEntry{r}
   167  	}
   168  	linkEntries := make([]entry, len(links))
   169  	for i, l := range links {
   170  		le, err := parseLink(l)
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  		linkEntries[i] = le
   175  	}
   176  
   177  	// Create intermediate nodes.
   178  	t := &Tree{entries: make(map[string]entry)}
   179  	eroot := t.build(enrEntries)
   180  	t.entries[subdomain(eroot)] = eroot
   181  	lroot := t.build(linkEntries)
   182  	t.entries[subdomain(lroot)] = lroot
   183  	t.root = &rootEntry{seq: seq, eroot: subdomain(eroot), lroot: subdomain(lroot)}
   184  	return t, nil
   185  }
   186  
   187  func (t *Tree) build(entries []entry) entry {
   188  	if len(entries) == 1 {
   189  		return entries[0]
   190  	}
   191  	if len(entries) <= maxChildren {
   192  		hashes := make([]string, len(entries))
   193  		for i, e := range entries {
   194  			hashes[i] = subdomain(e)
   195  			t.entries[hashes[i]] = e
   196  		}
   197  		return &branchEntry{hashes}
   198  	}
   199  	var subtrees []entry
   200  	for len(entries) > 0 {
   201  		n := maxChildren
   202  		if len(entries) < n {
   203  			n = len(entries)
   204  		}
   205  		sub := t.build(entries[:n])
   206  		entries = entries[n:]
   207  		subtrees = append(subtrees, sub)
   208  		t.entries[subdomain(sub)] = sub
   209  	}
   210  	return t.build(subtrees)
   211  }
   212  
   213  func sortByID(nodes []*enode.Node) []*enode.Node {
   214  	sort.Slice(nodes, func(i, j int) bool {
   215  		return bytes.Compare(nodes[i].ID().Bytes(), nodes[j].ID().Bytes()) < 0
   216  	})
   217  	return nodes
   218  }
   219  
   220  // Entry Types
   221  
   222  type entry interface {
   223  	fmt.Stringer
   224  }
   225  
   226  type (
   227  	rootEntry struct {
   228  		eroot string
   229  		lroot string
   230  		seq   uint
   231  		sig   []byte
   232  	}
   233  	branchEntry struct {
   234  		children []string
   235  	}
   236  	enrEntry struct {
   237  		node *enode.Node
   238  	}
   239  	linkEntry struct {
   240  		str    string
   241  		domain string
   242  		pubkey *ecdsa.PublicKey
   243  	}
   244  )
   245  
   246  // Entry Encoding
   247  
   248  var (
   249  	b32format = base32.StdEncoding.WithPadding(base32.NoPadding)
   250  	b64format = base64.RawURLEncoding
   251  )
   252  
   253  const (
   254  	rootPrefix   = "enrtree-root:v1"
   255  	linkPrefix   = "enrtree://"
   256  	branchPrefix = "enrtree-branch:"
   257  	enrPrefix    = "enr:"
   258  )
   259  
   260  func subdomain(e entry) string {
   261  	h := sha3.NewLegacyKeccak256()
   262  	io.WriteString(h, e.String())
   263  	return b32format.EncodeToString(h.Sum(nil)[:16])
   264  }
   265  
   266  func (e *rootEntry) String() string {
   267  	return fmt.Sprintf(rootPrefix+" e=%s l=%s seq=%d sig=%s", e.eroot, e.lroot, e.seq, b64format.EncodeToString(e.sig))
   268  }
   269  
   270  func (e *rootEntry) sigHash() []byte {
   271  	h := sha3.NewLegacyKeccak256()
   272  	fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq)
   273  	return h.Sum(nil)
   274  }
   275  
   276  func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool {
   277  	sig := e.sig[:crypto.RecoveryIDOffset] // remove recovery id
   278  	enckey := crypto.FromECDSAPub(pubkey)
   279  	return crypto.VerifySignature(enckey, e.sigHash(), sig)
   280  }
   281  
   282  func (e *branchEntry) String() string {
   283  	return branchPrefix + strings.Join(e.children, ",")
   284  }
   285  
   286  func (e *enrEntry) String() string {
   287  	return e.node.String()
   288  }
   289  
   290  func (e *linkEntry) String() string {
   291  	return linkPrefix + e.str
   292  }
   293  
   294  func newLinkEntry(domain string, pubkey *ecdsa.PublicKey) *linkEntry {
   295  	key := b32format.EncodeToString(crypto.CompressPubkey(pubkey))
   296  	str := key + "@" + domain
   297  	return &linkEntry{str, domain, pubkey}
   298  }
   299  
   300  // Entry Parsing
   301  
   302  func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) {
   303  	switch {
   304  	case strings.HasPrefix(e, linkPrefix):
   305  		return parseLinkEntry(e)
   306  	case strings.HasPrefix(e, branchPrefix):
   307  		return parseBranch(e)
   308  	case strings.HasPrefix(e, enrPrefix):
   309  		return parseENR(e, validSchemes)
   310  	default:
   311  		return nil, errUnknownEntry
   312  	}
   313  }
   314  
   315  func parseRoot(e string) (rootEntry, error) {
   316  	var eroot, lroot, sig string
   317  	var seq uint
   318  	if _, err := fmt.Sscanf(e, rootPrefix+" e=%s l=%s seq=%d sig=%s", &eroot, &lroot, &seq, &sig); err != nil {
   319  		return rootEntry{}, entryError{"root", errSyntax}
   320  	}
   321  	if !isValidHash(eroot) || !isValidHash(lroot) {
   322  		return rootEntry{}, entryError{"root", errInvalidChild}
   323  	}
   324  	sigb, err := b64format.DecodeString(sig)
   325  	if err != nil || len(sigb) != crypto.SignatureLength {
   326  		return rootEntry{}, entryError{"root", errInvalidSig}
   327  	}
   328  	return rootEntry{eroot, lroot, seq, sigb}, nil
   329  }
   330  
   331  func parseLinkEntry(e string) (entry, error) {
   332  	le, err := parseLink(e)
   333  	if err != nil {
   334  		return nil, err
   335  	}
   336  	return le, nil
   337  }
   338  
   339  func parseLink(e string) (*linkEntry, error) {
   340  	if !strings.HasPrefix(e, linkPrefix) {
   341  		return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
   342  	}
   343  	e = e[len(linkPrefix):]
   344  	pos := strings.IndexByte(e, '@')
   345  	if pos == -1 {
   346  		return nil, entryError{"link", errNoPubkey}
   347  	}
   348  	keystring, domain := e[:pos], e[pos+1:]
   349  	keybytes, err := b32format.DecodeString(keystring)
   350  	if err != nil {
   351  		return nil, entryError{"link", errBadPubkey}
   352  	}
   353  	key, err := crypto.DecompressPubkey(keybytes)
   354  	if err != nil {
   355  		return nil, entryError{"link", errBadPubkey}
   356  	}
   357  	return &linkEntry{e, domain, key}, nil
   358  }
   359  
   360  func parseBranch(e string) (entry, error) {
   361  	e = e[len(branchPrefix):]
   362  	if e == "" {
   363  		return &branchEntry{}, nil // empty entry is OK
   364  	}
   365  	hashes := make([]string, 0, strings.Count(e, ","))
   366  	for _, c := range strings.Split(e, ",") {
   367  		if !isValidHash(c) {
   368  			return nil, entryError{"branch", errInvalidChild}
   369  		}
   370  		hashes = append(hashes, c)
   371  	}
   372  	return &branchEntry{hashes}, nil
   373  }
   374  
   375  func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) {
   376  	e = e[len(enrPrefix):]
   377  	enc, err := b64format.DecodeString(e)
   378  	if err != nil {
   379  		return nil, entryError{"enr", errInvalidENR}
   380  	}
   381  	var rec enr.Record
   382  	if err := rlp.DecodeBytes(enc, &rec); err != nil {
   383  		return nil, entryError{"enr", err}
   384  	}
   385  	n, err := enode.New(validSchemes, &rec)
   386  	if err != nil {
   387  		return nil, entryError{"enr", err}
   388  	}
   389  	return &enrEntry{n}, nil
   390  }
   391  
   392  func isValidHash(s string) bool {
   393  	dlen := b32format.DecodedLen(len(s))
   394  	if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") {
   395  		return false
   396  	}
   397  	buf := make([]byte, 32)
   398  	_, err := b32format.Decode(buf, []byte(s))
   399  	return err == nil
   400  }
   401  
   402  // truncateHash truncates the given base32 hash string to the minimum acceptable length.
   403  func truncateHash(hash string) string {
   404  	maxLen := b32format.EncodedLen(minHashLength)
   405  	if len(hash) < maxLen {
   406  		panic(fmt.Errorf("dnsdisc: hash %q is too short", hash))
   407  	}
   408  	return hash[:maxLen]
   409  }
   410  
   411  // URL encoding
   412  
   413  // ParseURL parses an enrtree:// URL and returns its components.
   414  func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) {
   415  	le, err := parseLink(url)
   416  	if err != nil {
   417  		return "", nil, err
   418  	}
   419  	return le.domain, le.pubkey, nil
   420  }