github.com/MaynardMiner/ethereumprogpow@v1.8.23/cmd/clef/main.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum 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 // go-ethereum 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 go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 // signer is a utility that can be used so sign transactions and 18 // arbitrary data. 19 package main 20 21 import ( 22 "bufio" 23 "context" 24 "crypto/rand" 25 "crypto/sha256" 26 "encoding/hex" 27 "encoding/json" 28 "fmt" 29 "io" 30 "io/ioutil" 31 "os" 32 "os/signal" 33 "os/user" 34 "path/filepath" 35 "runtime" 36 "strings" 37 38 "github.com/ethereumprogpow/ethereumprogpow/accounts/keystore" 39 "github.com/ethereumprogpow/ethereumprogpow/cmd/utils" 40 "github.com/ethereumprogpow/ethereumprogpow/common" 41 "github.com/ethereumprogpow/ethereumprogpow/console" 42 "github.com/ethereumprogpow/ethereumprogpow/crypto" 43 "github.com/ethereumprogpow/ethereumprogpow/log" 44 "github.com/ethereumprogpow/ethereumprogpow/node" 45 "github.com/ethereumprogpow/ethereumprogpow/rpc" 46 "github.com/ethereumprogpow/ethereumprogpow/signer/core" 47 "github.com/ethereumprogpow/ethereumprogpow/signer/rules" 48 "github.com/ethereumprogpow/ethereumprogpow/signer/storage" 49 "gopkg.in/urfave/cli.v1" 50 ) 51 52 // ExternalAPIVersion -- see extapi_changelog.md 53 const ExternalAPIVersion = "4.0.0" 54 55 // InternalAPIVersion -- see intapi_changelog.md 56 const InternalAPIVersion = "3.0.0" 57 58 const legalWarning = ` 59 WARNING! 60 61 Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there 62 are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software 63 unless you agree to take full responsibility for doing so, and know what you are doing. 64 65 TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! 66 67 ` 68 69 var ( 70 logLevelFlag = cli.IntFlag{ 71 Name: "loglevel", 72 Value: 4, 73 Usage: "log level to emit to the screen", 74 } 75 advancedMode = cli.BoolFlag{ 76 Name: "advanced", 77 Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off", 78 } 79 keystoreFlag = cli.StringFlag{ 80 Name: "keystore", 81 Value: filepath.Join(node.DefaultDataDir(), "keystore"), 82 Usage: "Directory for the keystore", 83 } 84 configdirFlag = cli.StringFlag{ 85 Name: "configdir", 86 Value: DefaultConfigDir(), 87 Usage: "Directory for Clef configuration", 88 } 89 rpcPortFlag = cli.IntFlag{ 90 Name: "rpcport", 91 Usage: "HTTP-RPC server listening port", 92 Value: node.DefaultHTTPPort + 5, 93 } 94 signerSecretFlag = cli.StringFlag{ 95 Name: "signersecret", 96 Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", 97 } 98 dBFlag = cli.StringFlag{ 99 Name: "4bytedb", 100 Usage: "File containing 4byte-identifiers", 101 Value: "./4byte.json", 102 } 103 customDBFlag = cli.StringFlag{ 104 Name: "4bytedb-custom", 105 Usage: "File used for writing new 4byte-identifiers submitted via API", 106 Value: "./4byte-custom.json", 107 } 108 auditLogFlag = cli.StringFlag{ 109 Name: "auditlog", 110 Usage: "File used to emit audit logs. Set to \"\" to disable", 111 Value: "audit.log", 112 } 113 ruleFlag = cli.StringFlag{ 114 Name: "rules", 115 Usage: "Enable rule-engine", 116 Value: "rules.json", 117 } 118 stdiouiFlag = cli.BoolFlag{ 119 Name: "stdio-ui", 120 Usage: "Use STDIN/STDOUT as a channel for an external UI. " + 121 "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + 122 "interface, and can be used when Clef is started by an external process.", 123 } 124 testFlag = cli.BoolFlag{ 125 Name: "stdio-ui-test", 126 Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.", 127 } 128 app = cli.NewApp() 129 initCommand = cli.Command{ 130 Action: utils.MigrateFlags(initializeSecrets), 131 Name: "init", 132 Usage: "Initialize the signer, generate secret storage", 133 ArgsUsage: "", 134 Flags: []cli.Flag{ 135 logLevelFlag, 136 configdirFlag, 137 }, 138 Description: ` 139 The init command generates a master seed which Clef can use to store credentials and data needed for 140 the rule-engine to work.`, 141 } 142 attestCommand = cli.Command{ 143 Action: utils.MigrateFlags(attestFile), 144 Name: "attest", 145 Usage: "Attest that a js-file is to be used", 146 ArgsUsage: "<sha256sum>", 147 Flags: []cli.Flag{ 148 logLevelFlag, 149 configdirFlag, 150 signerSecretFlag, 151 }, 152 Description: ` 153 The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 154 incoming requests. 155 156 Whenever you make an edit to the rule file, you need to use attestation to tell 157 Clef that the file is 'safe' to execute.`, 158 } 159 160 setCredentialCommand = cli.Command{ 161 Action: utils.MigrateFlags(setCredential), 162 Name: "setpw", 163 Usage: "Store a credential for a keystore file", 164 ArgsUsage: "<address>", 165 Flags: []cli.Flag{ 166 logLevelFlag, 167 configdirFlag, 168 signerSecretFlag, 169 }, 170 Description: ` 171 The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will 172 remove any stored credential for that address (keyfile) 173 `, 174 } 175 ) 176 177 func init() { 178 app.Name = "Clef" 179 app.Usage = "Manage Ethereum account operations" 180 app.Flags = []cli.Flag{ 181 logLevelFlag, 182 keystoreFlag, 183 configdirFlag, 184 utils.NetworkIdFlag, 185 utils.LightKDFFlag, 186 utils.NoUSBFlag, 187 utils.RPCListenAddrFlag, 188 utils.RPCVirtualHostsFlag, 189 utils.IPCDisabledFlag, 190 utils.IPCPathFlag, 191 utils.RPCEnabledFlag, 192 rpcPortFlag, 193 signerSecretFlag, 194 dBFlag, 195 customDBFlag, 196 auditLogFlag, 197 ruleFlag, 198 stdiouiFlag, 199 testFlag, 200 advancedMode, 201 } 202 app.Action = signer 203 app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand} 204 205 } 206 func main() { 207 if err := app.Run(os.Args); err != nil { 208 fmt.Fprintln(os.Stderr, err) 209 os.Exit(1) 210 } 211 } 212 213 func initializeSecrets(c *cli.Context) error { 214 if err := initialize(c); err != nil { 215 return err 216 } 217 configDir := c.GlobalString(configdirFlag.Name) 218 219 masterSeed := make([]byte, 256) 220 num, err := io.ReadFull(rand.Reader, masterSeed) 221 if err != nil { 222 return err 223 } 224 if num != len(masterSeed) { 225 return fmt.Errorf("failed to read enough random") 226 } 227 228 n, p := keystore.StandardScryptN, keystore.StandardScryptP 229 if c.GlobalBool(utils.LightKDFFlag.Name) { 230 n, p = keystore.LightScryptN, keystore.LightScryptP 231 } 232 text := "The master seed of clef is locked with a password. Please give a password. Do not forget this password." 233 var password string 234 for { 235 password = getPassPhrase(text, true) 236 if err := core.ValidatePasswordFormat(password); err != nil { 237 fmt.Printf("invalid password: %v\n", err) 238 } else { 239 break 240 } 241 } 242 cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p) 243 if err != nil { 244 return fmt.Errorf("failed to encrypt master seed: %v", err) 245 } 246 247 err = os.Mkdir(configDir, 0700) 248 if err != nil && !os.IsExist(err) { 249 return err 250 } 251 location := filepath.Join(configDir, "masterseed.json") 252 if _, err := os.Stat(location); err == nil { 253 return fmt.Errorf("file %v already exists, will not overwrite", location) 254 } 255 err = ioutil.WriteFile(location, cipherSeed, 0400) 256 if err != nil { 257 return err 258 } 259 fmt.Printf("A master seed has been generated into %s\n", location) 260 fmt.Printf(` 261 This is required to be able to store credentials, such as : 262 * Passwords for keystores (used by rule engine) 263 * Storage for javascript rules 264 * Hash of rule-file 265 266 You should treat that file with utmost secrecy, and make a backup of it. 267 NOTE: This file does not contain your accounts. Those need to be backed up separately! 268 269 `) 270 return nil 271 } 272 func attestFile(ctx *cli.Context) error { 273 if len(ctx.Args()) < 1 { 274 utils.Fatalf("This command requires an argument.") 275 } 276 if err := initialize(ctx); err != nil { 277 return err 278 } 279 280 stretchedKey, err := readMasterKey(ctx, nil) 281 if err != nil { 282 utils.Fatalf(err.Error()) 283 } 284 configDir := ctx.GlobalString(configdirFlag.Name) 285 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 286 confKey := crypto.Keccak256([]byte("config"), stretchedKey) 287 288 // Initialize the encrypted storages 289 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey) 290 val := ctx.Args().First() 291 configStorage.Put("ruleset_sha256", val) 292 log.Info("Ruleset attestation updated", "sha256", val) 293 return nil 294 } 295 296 func setCredential(ctx *cli.Context) error { 297 if len(ctx.Args()) < 1 { 298 utils.Fatalf("This command requires an address to be passed as an argument.") 299 } 300 if err := initialize(ctx); err != nil { 301 return err 302 } 303 304 address := ctx.Args().First() 305 password := getPassPhrase("Enter a passphrase to store with this address.", true) 306 307 stretchedKey, err := readMasterKey(ctx, nil) 308 if err != nil { 309 utils.Fatalf(err.Error()) 310 } 311 configDir := ctx.GlobalString(configdirFlag.Name) 312 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 313 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 314 315 // Initialize the encrypted storages 316 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 317 pwStorage.Put(address, password) 318 log.Info("Credential store updated", "key", address) 319 return nil 320 } 321 322 func initialize(c *cli.Context) error { 323 // Set up the logger to print everything 324 logOutput := os.Stdout 325 if c.GlobalBool(stdiouiFlag.Name) { 326 logOutput = os.Stderr 327 // If using the stdioui, we can't do the 'confirm'-flow 328 fmt.Fprintf(logOutput, legalWarning) 329 } else { 330 if !confirm(legalWarning) { 331 return fmt.Errorf("aborted by user") 332 } 333 } 334 335 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) 336 return nil 337 } 338 339 func signer(c *cli.Context) error { 340 if err := initialize(c); err != nil { 341 return err 342 } 343 var ( 344 ui core.SignerUI 345 ) 346 if c.GlobalBool(stdiouiFlag.Name) { 347 log.Info("Using stdin/stdout as UI-channel") 348 ui = core.NewStdIOUI() 349 } else { 350 log.Info("Using CLI as UI-channel") 351 ui = core.NewCommandlineUI() 352 } 353 fourByteDb := c.GlobalString(dBFlag.Name) 354 fourByteLocal := c.GlobalString(customDBFlag.Name) 355 db, err := core.NewAbiDBFromFiles(fourByteDb, fourByteLocal) 356 if err != nil { 357 utils.Fatalf(err.Error()) 358 } 359 log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal) 360 361 var ( 362 api core.ExternalAPI 363 ) 364 365 configDir := c.GlobalString(configdirFlag.Name) 366 if stretchedKey, err := readMasterKey(c, ui); err != nil { 367 log.Info("No master seed provided, rules disabled", "error", err) 368 } else { 369 370 if err != nil { 371 utils.Fatalf(err.Error()) 372 } 373 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 374 375 // Generate domain specific keys 376 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 377 jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) 378 confkey := crypto.Keccak256([]byte("config"), stretchedKey) 379 380 // Initialize the encrypted storages 381 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 382 jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) 383 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) 384 385 //Do we have a rule-file? 386 ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name)) 387 if err != nil { 388 log.Info("Could not load rulefile, rules not enabled", "file", "rulefile") 389 } else { 390 hasher := sha256.New() 391 hasher.Write(ruleJS) 392 shasum := hasher.Sum(nil) 393 storedShasum := configStorage.Get("ruleset_sha256") 394 if storedShasum != hex.EncodeToString(shasum) { 395 log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum) 396 } else { 397 // Initialize rules 398 ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage) 399 if err != nil { 400 utils.Fatalf(err.Error()) 401 } 402 ruleEngine.Init(string(ruleJS)) 403 ui = ruleEngine 404 log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) 405 } 406 } 407 } 408 409 apiImpl := core.NewSignerAPI( 410 c.GlobalInt64(utils.NetworkIdFlag.Name), 411 c.GlobalString(keystoreFlag.Name), 412 c.GlobalBool(utils.NoUSBFlag.Name), 413 ui, db, 414 c.GlobalBool(utils.LightKDFFlag.Name), 415 c.GlobalBool(advancedMode.Name)) 416 api = apiImpl 417 // Audit logging 418 if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" { 419 api, err = core.NewAuditLogger(logfile, api) 420 if err != nil { 421 utils.Fatalf(err.Error()) 422 } 423 log.Info("Audit logs configured", "file", logfile) 424 } 425 // register signer API with server 426 var ( 427 extapiURL = "n/a" 428 ipcapiURL = "n/a" 429 ) 430 rpcAPI := []rpc.API{ 431 { 432 Namespace: "account", 433 Public: true, 434 Service: api, 435 Version: "1.0"}, 436 } 437 if c.GlobalBool(utils.RPCEnabledFlag.Name) { 438 439 vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) 440 cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) 441 442 // start http server 443 httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) 444 listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts) 445 if err != nil { 446 utils.Fatalf("Could not start RPC api: %v", err) 447 } 448 extapiURL = fmt.Sprintf("http://%s", httpEndpoint) 449 log.Info("HTTP endpoint opened", "url", extapiURL) 450 451 defer func() { 452 listener.Close() 453 log.Info("HTTP endpoint closed", "url", httpEndpoint) 454 }() 455 456 } 457 if !c.GlobalBool(utils.IPCDisabledFlag.Name) { 458 if c.IsSet(utils.IPCPathFlag.Name) { 459 ipcapiURL = c.GlobalString(utils.IPCPathFlag.Name) 460 } else { 461 ipcapiURL = filepath.Join(configDir, "clef.ipc") 462 } 463 464 listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) 465 if err != nil { 466 utils.Fatalf("Could not start IPC api: %v", err) 467 } 468 log.Info("IPC endpoint opened", "url", ipcapiURL) 469 defer func() { 470 listener.Close() 471 log.Info("IPC endpoint closed", "url", ipcapiURL) 472 }() 473 474 } 475 476 if c.GlobalBool(testFlag.Name) { 477 log.Info("Performing UI test") 478 go testExternalUI(apiImpl) 479 } 480 ui.OnSignerStartup(core.StartupInfo{ 481 Info: map[string]interface{}{ 482 "extapi_version": ExternalAPIVersion, 483 "intapi_version": InternalAPIVersion, 484 "extapi_http": extapiURL, 485 "extapi_ipc": ipcapiURL, 486 }, 487 }) 488 489 abortChan := make(chan os.Signal) 490 signal.Notify(abortChan, os.Interrupt) 491 492 sig := <-abortChan 493 log.Info("Exiting...", "signal", sig) 494 495 return nil 496 } 497 498 // splitAndTrim splits input separated by a comma 499 // and trims excessive white space from the substrings. 500 func splitAndTrim(input string) []string { 501 result := strings.Split(input, ",") 502 for i, r := range result { 503 result[i] = strings.TrimSpace(r) 504 } 505 return result 506 } 507 508 // DefaultConfigDir is the default config directory to use for the vaults and other 509 // persistence requirements. 510 func DefaultConfigDir() string { 511 // Try to place the data folder in the user's home dir 512 home := homeDir() 513 if home != "" { 514 if runtime.GOOS == "darwin" { 515 return filepath.Join(home, "Library", "Signer") 516 } else if runtime.GOOS == "windows" { 517 return filepath.Join(home, "AppData", "Roaming", "Signer") 518 } else { 519 return filepath.Join(home, ".clef") 520 } 521 } 522 // As we cannot guess a stable location, return empty and handle later 523 return "" 524 } 525 526 func homeDir() string { 527 if home := os.Getenv("HOME"); home != "" { 528 return home 529 } 530 if usr, err := user.Current(); err == nil { 531 return usr.HomeDir 532 } 533 return "" 534 } 535 func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) { 536 var ( 537 file string 538 configDir = ctx.GlobalString(configdirFlag.Name) 539 ) 540 if ctx.GlobalIsSet(signerSecretFlag.Name) { 541 file = ctx.GlobalString(signerSecretFlag.Name) 542 } else { 543 file = filepath.Join(configDir, "masterseed.json") 544 } 545 if err := checkFile(file); err != nil { 546 return nil, err 547 } 548 cipherKey, err := ioutil.ReadFile(file) 549 if err != nil { 550 return nil, err 551 } 552 var password string 553 // If ui is not nil, get the password from ui. 554 if ui != nil { 555 resp, err := ui.OnInputRequired(core.UserInputRequest{ 556 Title: "Master Password", 557 Prompt: "Please enter the password to decrypt the master seed", 558 IsPassword: true}) 559 if err != nil { 560 return nil, err 561 } 562 password = resp.Text 563 } else { 564 password = getPassPhrase("Decrypt master seed of clef", false) 565 } 566 masterSeed, err := decryptSeed(cipherKey, password) 567 if err != nil { 568 return nil, fmt.Errorf("failed to decrypt the master seed of clef") 569 } 570 if len(masterSeed) < 256 { 571 return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed)) 572 } 573 574 // Create vault location 575 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10])) 576 err = os.Mkdir(vaultLocation, 0700) 577 if err != nil && !os.IsExist(err) { 578 return nil, err 579 } 580 return masterSeed, nil 581 } 582 583 // checkFile is a convenience function to check if a file 584 // * exists 585 // * is mode 0400 586 func checkFile(filename string) error { 587 info, err := os.Stat(filename) 588 if err != nil { 589 return fmt.Errorf("failed stat on %s: %v", filename, err) 590 } 591 // Check the unix permission bits 592 if info.Mode().Perm()&0377 != 0 { 593 return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) 594 } 595 return nil 596 } 597 598 // confirm displays a text and asks for user confirmation 599 func confirm(text string) bool { 600 fmt.Printf(text) 601 fmt.Printf("\nEnter 'ok' to proceed:\n>") 602 603 text, err := bufio.NewReader(os.Stdin).ReadString('\n') 604 if err != nil { 605 log.Crit("Failed to read user input", "err", err) 606 } 607 608 if text := strings.TrimSpace(text); text == "ok" { 609 return true 610 } 611 return false 612 } 613 614 func testExternalUI(api *core.SignerAPI) { 615 616 ctx := context.WithValue(context.Background(), "remote", "clef binary") 617 ctx = context.WithValue(ctx, "scheme", "in-proc") 618 ctx = context.WithValue(ctx, "local", "main") 619 620 errs := make([]string, 0) 621 622 api.UI.ShowInfo("Testing 'ShowInfo'") 623 api.UI.ShowError("Testing 'ShowError'") 624 625 checkErr := func(method string, err error) { 626 if err != nil && err != core.ErrRequestDenied { 627 errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error())) 628 } 629 } 630 var err error 631 632 _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) 633 checkErr("SignTransaction", err) 634 _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) 635 checkErr("Sign", err) 636 _, err = api.List(ctx) 637 checkErr("List", err) 638 _, err = api.New(ctx) 639 checkErr("New", err) 640 _, err = api.Export(ctx, common.Address{}) 641 checkErr("Export", err) 642 _, err = api.Import(ctx, json.RawMessage{}) 643 checkErr("Import", err) 644 645 api.UI.ShowInfo("Tests completed") 646 647 if len(errs) > 0 { 648 log.Error("Got errors") 649 for _, e := range errs { 650 log.Error(e) 651 } 652 } else { 653 log.Info("No errors") 654 } 655 656 } 657 658 // getPassPhrase retrieves the password associated with clef, either fetched 659 // from a list of preloaded passphrases, or requested interactively from the user. 660 // TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one. 661 func getPassPhrase(prompt string, confirmation bool) string { 662 fmt.Println(prompt) 663 password, err := console.Stdin.PromptPassword("Passphrase: ") 664 if err != nil { 665 utils.Fatalf("Failed to read passphrase: %v", err) 666 } 667 if confirmation { 668 confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") 669 if err != nil { 670 utils.Fatalf("Failed to read passphrase confirmation: %v", err) 671 } 672 if password != confirm { 673 utils.Fatalf("Passphrases do not match") 674 } 675 } 676 return password 677 } 678 679 type encryptedSeedStorage struct { 680 Description string `json:"description"` 681 Version int `json:"version"` 682 Params keystore.CryptoJSON `json:"params"` 683 } 684 685 // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping, 686 // to encrypt the master seed 687 func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) { 688 cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP) 689 if err != nil { 690 return nil, err 691 } 692 return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct}) 693 } 694 695 // decryptSeed decrypts the master seed 696 func decryptSeed(keyjson []byte, auth string) ([]byte, error) { 697 var encSeed encryptedSeedStorage 698 if err := json.Unmarshal(keyjson, &encSeed); err != nil { 699 return nil, err 700 } 701 if encSeed.Version != 1 { 702 log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version)) 703 } 704 seed, err := keystore.DecryptDataV3(encSeed.Params, auth) 705 if err != nil { 706 return nil, err 707 } 708 return seed, err 709 } 710 711 /** 712 //Create Account 713 714 curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 715 716 // List accounts 717 718 curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/ 719 720 // Make Transaction 721 // safeSend(0x12) 722 // 4401a6e40000000000000000000000000000000000000000000000000000000000000012 723 724 // supplied abi 725 curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/ 726 727 // Not supplied 728 curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/ 729 730 // Sign data 731 732 curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_sign","params":["0x694267f14675d7e1b9494fd8d72fefe1755710fa","bazonk gaz baz"],"id":67}' http://localhost:8550/ 733 734 735 **/