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