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 }