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