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 }