github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/cmd/wnode/main.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of Spectrum. 3 // 4 // Spectrum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // Spectrum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Spectrum. If not, see <http://www.gnu.org/licenses/>. 16 17 // This is a simple Whisper node. It could be used as a stand-alone bootstrap node. 18 // Also, could be used for different test and diagnostics purposes. 19 20 package main 21 22 import ( 23 "bufio" 24 "crypto/ecdsa" 25 "crypto/sha512" 26 "encoding/binary" 27 "encoding/hex" 28 "flag" 29 "fmt" 30 "io/ioutil" 31 "os" 32 "path/filepath" 33 "strconv" 34 "strings" 35 "time" 36 37 "github.com/SmartMeshFoundation/Spectrum/cmd/utils" 38 "github.com/SmartMeshFoundation/Spectrum/common" 39 "github.com/SmartMeshFoundation/Spectrum/console" 40 "github.com/SmartMeshFoundation/Spectrum/crypto" 41 "github.com/SmartMeshFoundation/Spectrum/log" 42 "github.com/SmartMeshFoundation/Spectrum/p2p" 43 "github.com/SmartMeshFoundation/Spectrum/p2p/discover" 44 "github.com/SmartMeshFoundation/Spectrum/p2p/nat" 45 "github.com/SmartMeshFoundation/Spectrum/whisper/mailserver" 46 whisper "github.com/SmartMeshFoundation/Spectrum/whisper/whisperv5" 47 "golang.org/x/crypto/pbkdf2" 48 ) 49 50 const quitCommand = "~Q" 51 52 // singletons 53 var ( 54 server *p2p.Server 55 shh *whisper.Whisper 56 done chan struct{} 57 mailServer mailserver.WMailServer 58 59 input = bufio.NewReader(os.Stdin) 60 ) 61 62 // encryption 63 var ( 64 symKey []byte 65 pub *ecdsa.PublicKey 66 asymKey *ecdsa.PrivateKey 67 nodeid *ecdsa.PrivateKey 68 topic whisper.TopicType 69 asymKeyID string 70 filterID string 71 symPass string 72 msPassword string 73 ) 74 75 // cmd arguments 76 var ( 77 bootstrapMode = flag.Bool("standalone", false, "boostrap node: don't actively connect to peers, wait for incoming connections") 78 forwarderMode = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither send nor decrypt messages") 79 mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand") 80 requestMail = flag.Bool("mailclient", false, "request expired messages from the bootstrap server") 81 asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption") 82 generateKey = flag.Bool("generatekey", false, "generate and show the private key") 83 fileExMode = flag.Bool("fileexchange", false, "file exchange mode") 84 testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics") 85 echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics") 86 87 argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level") 88 argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") 89 argWorkTime = flag.Uint("work", 5, "work time in seconds") 90 argMaxSize = flag.Uint("maxsize", uint(whisper.DefaultMaxMessageSize), "max size of message") 91 argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)") 92 argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request") 93 94 argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)") 95 argPub = flag.String("pub", "", "public key for asymmetric encryption") 96 argDBPath = flag.String("dbpath", "", "path to the server's DB directory") 97 argIDFile = flag.String("idfile", "", "file name with node id (private key)") 98 argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)") 99 argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)") 100 argSaveDir = flag.String("savedir", "", "directory where incoming messages will be saved as files") 101 ) 102 103 func main() { 104 processArgs() 105 initialize() 106 run() 107 } 108 109 func processArgs() { 110 flag.Parse() 111 112 if len(*argIDFile) > 0 { 113 var err error 114 nodeid, err = crypto.LoadECDSA(*argIDFile) 115 if err != nil { 116 utils.Fatalf("Failed to load file [%s]: %s.", *argIDFile, err) 117 } 118 } 119 120 const enodePrefix = "enode://" 121 if len(*argEnode) > 0 { 122 if (*argEnode)[:len(enodePrefix)] != enodePrefix { 123 *argEnode = enodePrefix + *argEnode 124 } 125 } 126 127 if len(*argTopic) > 0 { 128 x, err := hex.DecodeString(*argTopic) 129 if err != nil { 130 utils.Fatalf("Failed to parse the topic: %s", err) 131 } 132 topic = whisper.BytesToTopic(x) 133 } 134 135 if *asymmetricMode && len(*argPub) > 0 { 136 pub = crypto.ToECDSAPub(common.FromHex(*argPub)) 137 if !isKeyValid(pub) { 138 utils.Fatalf("invalid public key") 139 } 140 } 141 142 if len(*argSaveDir) > 0 { 143 if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) { 144 utils.Fatalf("Download directory '%s' does not exist", *argSaveDir) 145 } 146 } else if *fileExMode { 147 utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode") 148 } 149 150 if *echoMode { 151 echo() 152 } 153 } 154 155 func echo() { 156 fmt.Printf("ttl = %d \n", *argTTL) 157 fmt.Printf("workTime = %d \n", *argWorkTime) 158 fmt.Printf("pow = %f \n", *argPoW) 159 fmt.Printf("mspow = %f \n", *argServerPoW) 160 fmt.Printf("ip = %s \n", *argIP) 161 fmt.Printf("pub = %s \n", common.ToHex(crypto.FromECDSAPub(pub))) 162 fmt.Printf("idfile = %s \n", *argIDFile) 163 fmt.Printf("dbpath = %s \n", *argDBPath) 164 fmt.Printf("boot = %s \n", *argEnode) 165 } 166 167 func initialize() { 168 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) 169 170 done = make(chan struct{}) 171 var peers []*discover.Node 172 var err error 173 174 if *generateKey { 175 key, err := crypto.GenerateKey() 176 if err != nil { 177 utils.Fatalf("Failed to generate private key: %s", err) 178 } 179 k := hex.EncodeToString(crypto.FromECDSA(key)) 180 fmt.Printf("Random private key: %s \n", k) 181 os.Exit(0) 182 } 183 184 if *testMode { 185 symPass = "wwww" // ascii code: 0x77777777 186 msPassword = "wwww" 187 } 188 189 if *bootstrapMode { 190 if len(*argIP) == 0 { 191 argIP = scanLineA("Please enter your IP and port (e.g. 127.0.0.1:30348): ") 192 } 193 } else { 194 if len(*argEnode) == 0 { 195 argEnode = scanLineA("Please enter the peer's enode: ") 196 } 197 peer := discover.MustParseNode(*argEnode) 198 peers = append(peers, peer) 199 } 200 201 cfg := &whisper.Config{ 202 MaxMessageSize: uint32(*argMaxSize), 203 MinimumAcceptedPOW: *argPoW, 204 } 205 206 if *mailServerMode { 207 if len(msPassword) == 0 { 208 msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") 209 if err != nil { 210 utils.Fatalf("Failed to read Mail Server password: %s", err) 211 } 212 } 213 214 shh = whisper.New(cfg) 215 shh.RegisterServer(&mailServer) 216 mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW) 217 } else { 218 shh = whisper.New(cfg) 219 } 220 221 if *argPoW != whisper.DefaultMinimumPoW { 222 err := shh.SetMinimumPoW(*argPoW) 223 if err != nil { 224 utils.Fatalf("Failed to set PoW: %s", err) 225 } 226 } 227 228 if uint32(*argMaxSize) != whisper.DefaultMaxMessageSize { 229 err := shh.SetMaxMessageSize(uint32(*argMaxSize)) 230 if err != nil { 231 utils.Fatalf("Failed to set max message size: %s", err) 232 } 233 } 234 235 asymKeyID, err = shh.NewKeyPair() 236 if err != nil { 237 utils.Fatalf("Failed to generate a new key pair: %s", err) 238 } 239 240 asymKey, err = shh.GetPrivateKey(asymKeyID) 241 if err != nil { 242 utils.Fatalf("Failed to retrieve a new key pair: %s", err) 243 } 244 245 if nodeid == nil { 246 tmpID, err := shh.NewKeyPair() 247 if err != nil { 248 utils.Fatalf("Failed to generate a new key pair: %s", err) 249 } 250 251 nodeid, err = shh.GetPrivateKey(tmpID) 252 if err != nil { 253 utils.Fatalf("Failed to retrieve a new key pair: %s", err) 254 } 255 } 256 257 maxPeers := 80 258 if *bootstrapMode { 259 maxPeers = 800 260 } 261 262 server = &p2p.Server{ 263 Config: p2p.Config{ 264 PrivateKey: nodeid, 265 MaxPeers: maxPeers, 266 Name: common.MakeName("wnode", "5.0"), 267 Protocols: shh.Protocols(), 268 ListenAddr: *argIP, 269 NAT: nat.Any(), 270 BootstrapNodes: peers, 271 StaticNodes: peers, 272 TrustedNodes: peers, 273 }, 274 } 275 } 276 277 func startServer() { 278 err := server.Start() 279 if err != nil { 280 utils.Fatalf("Failed to start Whisper peer: %s.", err) 281 } 282 283 fmt.Printf("my public key: %s \n", common.ToHex(crypto.FromECDSAPub(&asymKey.PublicKey))) 284 fmt.Println(server.NodeInfo().Enode) 285 286 if *bootstrapMode { 287 configureNode() 288 fmt.Println("Bootstrap Whisper node started") 289 } else { 290 fmt.Println("Whisper node started") 291 // first see if we can establish connection, then ask for user input 292 waitForConnection(true) 293 configureNode() 294 } 295 296 if !*forwarderMode { 297 fmt.Printf("Please type the message. To quit type: '%s'\n", quitCommand) 298 } 299 } 300 301 func isKeyValid(k *ecdsa.PublicKey) bool { 302 return k.X != nil && k.Y != nil 303 } 304 305 func configureNode() { 306 var err error 307 var p2pAccept bool 308 309 if *forwarderMode { 310 return 311 } 312 313 if *asymmetricMode { 314 if len(*argPub) == 0 { 315 s := scanLine("Please enter the peer's public key: ") 316 b := common.FromHex(s) 317 if b == nil { 318 utils.Fatalf("Error: can not convert hexadecimal string") 319 } 320 pub = crypto.ToECDSAPub(b) 321 if !isKeyValid(pub) { 322 utils.Fatalf("Error: invalid public key") 323 } 324 } 325 } 326 327 if *requestMail { 328 p2pAccept = true 329 if len(msPassword) == 0 { 330 msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ") 331 if err != nil { 332 utils.Fatalf("Failed to read Mail Server password: %s", err) 333 } 334 } 335 } 336 337 if !*asymmetricMode && !*forwarderMode { 338 if len(symPass) == 0 { 339 symPass, err = console.Stdin.PromptPassword("Please enter the password for symmetric encryption: ") 340 if err != nil { 341 utils.Fatalf("Failed to read passphrase: %v", err) 342 } 343 } 344 345 symKeyID, err := shh.AddSymKeyFromPassword(symPass) 346 if err != nil { 347 utils.Fatalf("Failed to create symmetric key: %s", err) 348 } 349 symKey, err = shh.GetSymKey(symKeyID) 350 if err != nil { 351 utils.Fatalf("Failed to save symmetric key: %s", err) 352 } 353 if len(*argTopic) == 0 { 354 generateTopic([]byte(symPass)) 355 } 356 357 fmt.Printf("Filter is configured for the topic: %x \n", topic) 358 } 359 360 if *mailServerMode { 361 if len(*argDBPath) == 0 { 362 argDBPath = scanLineA("Please enter the path to DB file: ") 363 } 364 } 365 366 filter := whisper.Filter{ 367 KeySym: symKey, 368 KeyAsym: asymKey, 369 Topics: [][]byte{topic[:]}, 370 AllowP2P: p2pAccept, 371 } 372 filterID, err = shh.Subscribe(&filter) 373 if err != nil { 374 utils.Fatalf("Failed to install filter: %s", err) 375 } 376 } 377 378 func generateTopic(password []byte) { 379 x := pbkdf2.Key(password, password, 4096, 128, sha512.New) 380 for i := 0; i < len(x); i++ { 381 topic[i%whisper.TopicLength] ^= x[i] 382 } 383 } 384 385 func waitForConnection(timeout bool) { 386 var cnt int 387 var connected bool 388 for !connected { 389 time.Sleep(time.Millisecond * 50) 390 connected = server.PeerCount() > 0 391 if timeout { 392 cnt++ 393 if cnt > 1000 { 394 utils.Fatalf("Timeout expired, failed to connect") 395 } 396 } 397 } 398 399 fmt.Println("Connected to peer.") 400 } 401 402 func run() { 403 defer mailServer.Close() 404 startServer() 405 defer server.Stop() 406 shh.Start(nil) 407 defer shh.Stop() 408 409 if !*forwarderMode { 410 go messageLoop() 411 } 412 413 if *requestMail { 414 requestExpiredMessagesLoop() 415 } else if *fileExMode { 416 sendFilesLoop() 417 } else { 418 sendLoop() 419 } 420 } 421 422 func sendLoop() { 423 for { 424 s := scanLine("") 425 if s == quitCommand { 426 fmt.Println("Quit command received") 427 close(done) 428 break 429 } 430 sendMsg([]byte(s)) 431 432 if *asymmetricMode { 433 // print your own message for convenience, 434 // because in asymmetric mode it is impossible to decrypt it 435 timestamp := time.Now().Unix() 436 from := crypto.PubkeyToAddress(asymKey.PublicKey) 437 fmt.Printf("\n%d <%x>: %s\n", timestamp, from, s) 438 } 439 } 440 } 441 442 func sendFilesLoop() { 443 for { 444 s := scanLine("") 445 if s == quitCommand { 446 fmt.Println("Quit command received") 447 close(done) 448 break 449 } 450 b, err := ioutil.ReadFile(s) 451 if err != nil { 452 fmt.Printf(">>> Error: %s \n", err) 453 continue 454 } else { 455 h := sendMsg(b) 456 if (h == common.Hash{}) { 457 fmt.Printf(">>> Error: message was not sent \n") 458 } else { 459 timestamp := time.Now().Unix() 460 from := crypto.PubkeyToAddress(asymKey.PublicKey) 461 fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h) 462 } 463 } 464 } 465 } 466 467 func scanLine(prompt string) string { 468 if len(prompt) > 0 { 469 fmt.Print(prompt) 470 } 471 txt, err := input.ReadString('\n') 472 if err != nil { 473 utils.Fatalf("input error: %s", err) 474 } 475 txt = strings.TrimRight(txt, "\n\r") 476 return txt 477 } 478 479 func scanLineA(prompt string) *string { 480 s := scanLine(prompt) 481 return &s 482 } 483 484 func scanUint(prompt string) uint32 { 485 s := scanLine(prompt) 486 i, err := strconv.Atoi(s) 487 if err != nil { 488 utils.Fatalf("Fail to parse the lower time limit: %s", err) 489 } 490 return uint32(i) 491 } 492 493 func sendMsg(payload []byte) common.Hash { 494 params := whisper.MessageParams{ 495 Src: asymKey, 496 Dst: pub, 497 KeySym: symKey, 498 Payload: payload, 499 Topic: topic, 500 TTL: uint32(*argTTL), 501 PoW: *argPoW, 502 WorkTime: uint32(*argWorkTime), 503 } 504 505 msg, err := whisper.NewSentMessage(¶ms) 506 if err != nil { 507 utils.Fatalf("failed to create new message: %s", err) 508 } 509 envelope, err := msg.Wrap(¶ms) 510 if err != nil { 511 fmt.Printf("failed to seal message: %v \n", err) 512 return common.Hash{} 513 } 514 515 err = shh.Send(envelope) 516 if err != nil { 517 fmt.Printf("failed to send message: %v \n", err) 518 return common.Hash{} 519 } 520 521 return envelope.Hash() 522 } 523 524 func messageLoop() { 525 f := shh.GetFilter(filterID) 526 if f == nil { 527 utils.Fatalf("filter is not installed") 528 } 529 530 ticker := time.NewTicker(time.Millisecond * 50) 531 532 for { 533 select { 534 case <-ticker.C: 535 messages := f.Retrieve() 536 for _, msg := range messages { 537 if *fileExMode || len(msg.Payload) > 2048 { 538 writeMessageToFile(*argSaveDir, msg) 539 } else { 540 printMessageInfo(msg) 541 } 542 } 543 case <-done: 544 return 545 } 546 } 547 } 548 549 func printMessageInfo(msg *whisper.ReceivedMessage) { 550 timestamp := fmt.Sprintf("%d", msg.Sent) // unix timestamp for diagnostics 551 text := string(msg.Payload) 552 553 var address common.Address 554 if msg.Src != nil { 555 address = crypto.PubkeyToAddress(*msg.Src) 556 } 557 558 if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { 559 fmt.Printf("\n%s <%x>: %s\n", timestamp, address, text) // message from myself 560 } else { 561 fmt.Printf("\n%s [%x]: %s\n", timestamp, address, text) // message from a peer 562 } 563 } 564 565 func writeMessageToFile(dir string, msg *whisper.ReceivedMessage) { 566 timestamp := fmt.Sprintf("%d", msg.Sent) 567 name := fmt.Sprintf("%x", msg.EnvelopeHash) 568 569 var address common.Address 570 if msg.Src != nil { 571 address = crypto.PubkeyToAddress(*msg.Src) 572 } 573 574 if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) { 575 // message from myself: don't save, only report 576 fmt.Printf("\n%s <%x>: message received: '%s'\n", timestamp, address, name) 577 } else if len(dir) > 0 { 578 fullpath := filepath.Join(dir, name) 579 err := ioutil.WriteFile(fullpath, msg.Payload, 0644) 580 if err != nil { 581 fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err) 582 } else { 583 fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(msg.Payload)) 584 } 585 } else { 586 fmt.Printf("\n%s {%x}: big message received (%d bytes), but not saved: %s\n", timestamp, address, len(msg.Payload), name) 587 } 588 } 589 590 func requestExpiredMessagesLoop() { 591 var key, peerID []byte 592 var timeLow, timeUpp uint32 593 var t string 594 var xt, empty whisper.TopicType 595 596 keyID, err := shh.AddSymKeyFromPassword(msPassword) 597 if err != nil { 598 utils.Fatalf("Failed to create symmetric key for mail request: %s", err) 599 } 600 key, err = shh.GetSymKey(keyID) 601 if err != nil { 602 utils.Fatalf("Failed to save symmetric key for mail request: %s", err) 603 } 604 peerID = extractIdFromEnode(*argEnode) 605 shh.AllowP2PMessagesFromPeer(peerID) 606 607 for { 608 timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ") 609 timeUpp = scanUint("Please enter the upper limit of the time range (unix timestamp): ") 610 t = scanLine("Please enter the topic (hexadecimal): ") 611 if len(t) >= whisper.TopicLength*2 { 612 x, err := hex.DecodeString(t) 613 if err != nil { 614 utils.Fatalf("Failed to parse the topic: %s", err) 615 } 616 xt = whisper.BytesToTopic(x) 617 } 618 if timeUpp == 0 { 619 timeUpp = 0xFFFFFFFF 620 } 621 622 data := make([]byte, 8+whisper.TopicLength) 623 binary.BigEndian.PutUint32(data, timeLow) 624 binary.BigEndian.PutUint32(data[4:], timeUpp) 625 copy(data[8:], xt[:]) 626 if xt == empty { 627 data = data[:8] 628 } 629 630 var params whisper.MessageParams 631 params.PoW = *argServerPoW 632 params.Payload = data 633 params.KeySym = key 634 params.Src = nodeid 635 params.WorkTime = 5 636 637 msg, err := whisper.NewSentMessage(¶ms) 638 if err != nil { 639 utils.Fatalf("failed to create new message: %s", err) 640 } 641 env, err := msg.Wrap(¶ms) 642 if err != nil { 643 utils.Fatalf("Wrap failed: %s", err) 644 } 645 646 err = shh.RequestHistoricMessages(peerID, env) 647 if err != nil { 648 utils.Fatalf("Failed to send P2P message: %s", err) 649 } 650 651 time.Sleep(time.Second * 5) 652 } 653 } 654 655 func extractIdFromEnode(s string) []byte { 656 n, err := discover.ParseNode(s) 657 if err != nil { 658 utils.Fatalf("Failed to parse enode: %s", err) 659 } 660 return n.ID[:] 661 }