github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/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 "math/big" 32 "os" 33 "os/signal" 34 "os/user" 35 "path/filepath" 36 "runtime" 37 "strings" 38 39 "github.com/ethereum/go-ethereum/accounts" 40 "github.com/ethereum/go-ethereum/accounts/keystore" 41 "github.com/ethereum/go-ethereum/cmd/utils" 42 "github.com/ethereum/go-ethereum/common" 43 "github.com/ethereum/go-ethereum/common/hexutil" 44 "github.com/ethereum/go-ethereum/console" 45 "github.com/ethereum/go-ethereum/core/types" 46 "github.com/ethereum/go-ethereum/crypto" 47 "github.com/ethereum/go-ethereum/internal/ethapi" 48 "github.com/ethereum/go-ethereum/log" 49 "github.com/ethereum/go-ethereum/node" 50 "github.com/ethereum/go-ethereum/params" 51 "github.com/ethereum/go-ethereum/rlp" 52 "github.com/ethereum/go-ethereum/rpc" 53 "github.com/ethereum/go-ethereum/signer/core" 54 "github.com/ethereum/go-ethereum/signer/rules" 55 "github.com/ethereum/go-ethereum/signer/storage" 56 "gopkg.in/urfave/cli.v1" 57 ) 58 59 const legalWarning = ` 60 WARNING! 61 62 Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there 63 are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software 64 unless you agree to take full responsibility for doing so, and know what you are doing. 65 66 TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! 67 68 ` 69 70 var ( 71 logLevelFlag = cli.IntFlag{ 72 Name: "loglevel", 73 Value: 4, 74 Usage: "log level to emit to the screen", 75 } 76 advancedMode = cli.BoolFlag{ 77 Name: "advanced", 78 Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off", 79 } 80 keystoreFlag = cli.StringFlag{ 81 Name: "keystore", 82 Value: filepath.Join(node.DefaultDataDir(), "keystore"), 83 Usage: "Directory for the keystore", 84 } 85 configdirFlag = cli.StringFlag{ 86 Name: "configdir", 87 Value: DefaultConfigDir(), 88 Usage: "Directory for Clef configuration", 89 } 90 chainIdFlag = cli.Int64Flag{ 91 Name: "chainid", 92 Value: params.MainnetChainConfig.ChainID.Int64(), 93 Usage: "Chain id to use for signing (1=mainnet, 3=ropsten, 4=rinkeby, 5=Goerli)", 94 } 95 rpcPortFlag = cli.IntFlag{ 96 Name: "rpcport", 97 Usage: "HTTP-RPC server listening port", 98 Value: node.DefaultHTTPPort + 5, 99 } 100 signerSecretFlag = cli.StringFlag{ 101 Name: "signersecret", 102 Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash", 103 } 104 dBFlag = cli.StringFlag{ 105 Name: "4bytedb", 106 Usage: "File containing 4byte-identifiers", 107 Value: "./4byte.json", 108 } 109 customDBFlag = cli.StringFlag{ 110 Name: "4bytedb-custom", 111 Usage: "File used for writing new 4byte-identifiers submitted via API", 112 Value: "./4byte-custom.json", 113 } 114 auditLogFlag = cli.StringFlag{ 115 Name: "auditlog", 116 Usage: "File used to emit audit logs. Set to \"\" to disable", 117 Value: "audit.log", 118 } 119 ruleFlag = cli.StringFlag{ 120 Name: "rules", 121 Usage: "Enable rule-engine", 122 Value: "rules.json", 123 } 124 stdiouiFlag = cli.BoolFlag{ 125 Name: "stdio-ui", 126 Usage: "Use STDIN/STDOUT as a channel for an external UI. " + 127 "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + 128 "interface, and can be used when Clef is started by an external process.", 129 } 130 testFlag = cli.BoolFlag{ 131 Name: "stdio-ui-test", 132 Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.", 133 } 134 app = cli.NewApp() 135 initCommand = cli.Command{ 136 Action: utils.MigrateFlags(initializeSecrets), 137 Name: "init", 138 Usage: "Initialize the signer, generate secret storage", 139 ArgsUsage: "", 140 Flags: []cli.Flag{ 141 logLevelFlag, 142 configdirFlag, 143 }, 144 Description: ` 145 The init command generates a master seed which Clef can use to store credentials and data needed for 146 the rule-engine to work.`, 147 } 148 attestCommand = cli.Command{ 149 Action: utils.MigrateFlags(attestFile), 150 Name: "attest", 151 Usage: "Attest that a js-file is to be used", 152 ArgsUsage: "<sha256sum>", 153 Flags: []cli.Flag{ 154 logLevelFlag, 155 configdirFlag, 156 signerSecretFlag, 157 }, 158 Description: ` 159 The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 160 incoming requests. 161 162 Whenever you make an edit to the rule file, you need to use attestation to tell 163 Clef that the file is 'safe' to execute.`, 164 } 165 166 setCredentialCommand = cli.Command{ 167 Action: utils.MigrateFlags(setCredential), 168 Name: "setpw", 169 Usage: "Store a credential for a keystore file", 170 ArgsUsage: "<address>", 171 Flags: []cli.Flag{ 172 logLevelFlag, 173 configdirFlag, 174 signerSecretFlag, 175 }, 176 Description: ` 177 The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will 178 remove any stored credential for that address (keyfile) 179 `} 180 gendocCommand = cli.Command{ 181 Action: GenDoc, 182 Name: "gendoc", 183 Usage: "Generate documentation about json-rpc format", 184 Description: ` 185 The gendoc generates example structures of the json-rpc communication types. 186 `} 187 ) 188 189 func init() { 190 app.Name = "Clef" 191 app.Usage = "Manage Ethereum account operations" 192 app.Flags = []cli.Flag{ 193 logLevelFlag, 194 keystoreFlag, 195 configdirFlag, 196 chainIdFlag, 197 utils.LightKDFFlag, 198 utils.NoUSBFlag, 199 utils.RPCListenAddrFlag, 200 utils.RPCVirtualHostsFlag, 201 utils.IPCDisabledFlag, 202 utils.IPCPathFlag, 203 utils.RPCEnabledFlag, 204 rpcPortFlag, 205 signerSecretFlag, 206 dBFlag, 207 customDBFlag, 208 auditLogFlag, 209 ruleFlag, 210 stdiouiFlag, 211 testFlag, 212 advancedMode, 213 } 214 app.Action = signer 215 app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand, gendocCommand} 216 217 } 218 func main() { 219 if err := app.Run(os.Args); err != nil { 220 fmt.Fprintln(os.Stderr, err) 221 os.Exit(1) 222 } 223 } 224 225 func initializeSecrets(c *cli.Context) error { 226 if err := initialize(c); err != nil { 227 return err 228 } 229 configDir := c.GlobalString(configdirFlag.Name) 230 231 masterSeed := make([]byte, 256) 232 num, err := io.ReadFull(rand.Reader, masterSeed) 233 if err != nil { 234 return err 235 } 236 if num != len(masterSeed) { 237 return fmt.Errorf("failed to read enough random") 238 } 239 240 n, p := keystore.StandardScryptN, keystore.StandardScryptP 241 if c.GlobalBool(utils.LightKDFFlag.Name) { 242 n, p = keystore.LightScryptN, keystore.LightScryptP 243 } 244 text := "The master seed of clef is locked with a password. Please give a password. Do not forget this password." 245 var password string 246 for { 247 password = getPassPhrase(text, true) 248 if err := core.ValidatePasswordFormat(password); err != nil { 249 fmt.Printf("invalid password: %v\n", err) 250 } else { 251 break 252 } 253 } 254 cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p) 255 if err != nil { 256 return fmt.Errorf("failed to encrypt master seed: %v", err) 257 } 258 259 err = os.Mkdir(configDir, 0700) 260 if err != nil && !os.IsExist(err) { 261 return err 262 } 263 location := filepath.Join(configDir, "masterseed.json") 264 if _, err := os.Stat(location); err == nil { 265 return fmt.Errorf("file %v already exists, will not overwrite", location) 266 } 267 err = ioutil.WriteFile(location, cipherSeed, 0400) 268 if err != nil { 269 return err 270 } 271 fmt.Printf("A master seed has been generated into %s\n", location) 272 fmt.Printf(` 273 This is required to be able to store credentials, such as : 274 * Passwords for keystores (used by rule engine) 275 * Storage for javascript rules 276 * Hash of rule-file 277 278 You should treat that file with utmost secrecy, and make a backup of it. 279 NOTE: This file does not contain your accounts. Those need to be backed up separately! 280 281 `) 282 return nil 283 } 284 func attestFile(ctx *cli.Context) error { 285 if len(ctx.Args()) < 1 { 286 utils.Fatalf("This command requires an argument.") 287 } 288 if err := initialize(ctx); err != nil { 289 return err 290 } 291 292 stretchedKey, err := readMasterKey(ctx, nil) 293 if err != nil { 294 utils.Fatalf(err.Error()) 295 } 296 configDir := ctx.GlobalString(configdirFlag.Name) 297 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 298 confKey := crypto.Keccak256([]byte("config"), stretchedKey) 299 300 // Initialize the encrypted storages 301 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey) 302 val := ctx.Args().First() 303 configStorage.Put("ruleset_sha256", val) 304 log.Info("Ruleset attestation updated", "sha256", val) 305 return nil 306 } 307 308 func setCredential(ctx *cli.Context) error { 309 if len(ctx.Args()) < 1 { 310 utils.Fatalf("This command requires an address to be passed as an argument.") 311 } 312 if err := initialize(ctx); err != nil { 313 return err 314 } 315 316 address := ctx.Args().First() 317 password := getPassPhrase("Enter a passphrase to store with this address.", true) 318 319 stretchedKey, err := readMasterKey(ctx, nil) 320 if err != nil { 321 utils.Fatalf(err.Error()) 322 } 323 configDir := ctx.GlobalString(configdirFlag.Name) 324 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 325 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 326 327 // Initialize the encrypted storages 328 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 329 pwStorage.Put(address, password) 330 log.Info("Credential store updated", "key", address) 331 return nil 332 } 333 334 func initialize(c *cli.Context) error { 335 // Set up the logger to print everything 336 logOutput := os.Stdout 337 if c.GlobalBool(stdiouiFlag.Name) { 338 logOutput = os.Stderr 339 // If using the stdioui, we can't do the 'confirm'-flow 340 fmt.Fprintf(logOutput, legalWarning) 341 } else { 342 if !confirm(legalWarning) { 343 return fmt.Errorf("aborted by user") 344 } 345 } 346 347 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) 348 return nil 349 } 350 351 func signer(c *cli.Context) error { 352 if err := initialize(c); err != nil { 353 return err 354 } 355 var ( 356 ui core.UIClientAPI 357 ) 358 if c.GlobalBool(stdiouiFlag.Name) { 359 log.Info("Using stdin/stdout as UI-channel") 360 ui = core.NewStdIOUI() 361 } else { 362 log.Info("Using CLI as UI-channel") 363 ui = core.NewCommandlineUI() 364 } 365 fourByteDb := c.GlobalString(dBFlag.Name) 366 fourByteLocal := c.GlobalString(customDBFlag.Name) 367 db, err := core.NewAbiDBFromFiles(fourByteDb, fourByteLocal) 368 if err != nil { 369 utils.Fatalf(err.Error()) 370 } 371 log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal) 372 373 var ( 374 api core.ExternalAPI 375 ) 376 377 configDir := c.GlobalString(configdirFlag.Name) 378 if stretchedKey, err := readMasterKey(c, ui); err != nil { 379 log.Info("No master seed provided, rules disabled", "error", err) 380 } else { 381 382 if err != nil { 383 utils.Fatalf(err.Error()) 384 } 385 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 386 387 // Generate domain specific keys 388 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 389 jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) 390 confkey := crypto.Keccak256([]byte("config"), stretchedKey) 391 392 // Initialize the encrypted storages 393 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 394 jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) 395 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) 396 397 //Do we have a rule-file? 398 ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name)) 399 if err != nil { 400 log.Info("Could not load rulefile, rules not enabled", "file", "rulefile") 401 } else { 402 hasher := sha256.New() 403 hasher.Write(ruleJS) 404 shasum := hasher.Sum(nil) 405 storedShasum := configStorage.Get("ruleset_sha256") 406 if storedShasum != hex.EncodeToString(shasum) { 407 log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum) 408 } else { 409 // Initialize rules 410 ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage) 411 if err != nil { 412 utils.Fatalf(err.Error()) 413 } 414 ruleEngine.Init(string(ruleJS)) 415 ui = ruleEngine 416 log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) 417 } 418 } 419 } 420 var ( 421 chainId = c.GlobalInt64(chainIdFlag.Name) 422 ksLoc = c.GlobalString(keystoreFlag.Name) 423 lightKdf = c.GlobalBool(utils.LightKDFFlag.Name) 424 advanced = c.GlobalBool(advancedMode.Name) 425 nousb = c.GlobalBool(utils.NoUSBFlag.Name) 426 ) 427 log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc, 428 "light-kdf", lightKdf, "advanced", advanced) 429 am := core.StartClefAccountManager(ksLoc, nousb, lightKdf) 430 apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced) 431 432 // Establish the bidirectional communication, by creating a new UI backend and registering 433 // it with the UI. 434 ui.RegisterUIServer(core.NewUIServerAPI(apiImpl)) 435 api = apiImpl 436 // Audit logging 437 if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" { 438 api, err = core.NewAuditLogger(logfile, api) 439 if err != nil { 440 utils.Fatalf(err.Error()) 441 } 442 log.Info("Audit logs configured", "file", logfile) 443 } 444 // register signer API with server 445 var ( 446 extapiURL = "n/a" 447 ipcapiURL = "n/a" 448 ) 449 rpcAPI := []rpc.API{ 450 { 451 Namespace: "account", 452 Public: true, 453 Service: api, 454 Version: "1.0"}, 455 } 456 if c.GlobalBool(utils.RPCEnabledFlag.Name) { 457 458 vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) 459 cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) 460 461 // start http server 462 httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) 463 listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts) 464 if err != nil { 465 utils.Fatalf("Could not start RPC api: %v", err) 466 } 467 extapiURL = fmt.Sprintf("http://%s", httpEndpoint) 468 log.Info("HTTP endpoint opened", "url", extapiURL) 469 470 defer func() { 471 listener.Close() 472 log.Info("HTTP endpoint closed", "url", httpEndpoint) 473 }() 474 475 } 476 if !c.GlobalBool(utils.IPCDisabledFlag.Name) { 477 if c.IsSet(utils.IPCPathFlag.Name) { 478 ipcapiURL = c.GlobalString(utils.IPCPathFlag.Name) 479 } else { 480 ipcapiURL = filepath.Join(configDir, "clef.ipc") 481 } 482 483 listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) 484 if err != nil { 485 utils.Fatalf("Could not start IPC api: %v", err) 486 } 487 log.Info("IPC endpoint opened", "url", ipcapiURL) 488 defer func() { 489 listener.Close() 490 log.Info("IPC endpoint closed", "url", ipcapiURL) 491 }() 492 493 } 494 495 if c.GlobalBool(testFlag.Name) { 496 log.Info("Performing UI test") 497 go testExternalUI(apiImpl) 498 } 499 ui.OnSignerStartup(core.StartupInfo{ 500 Info: map[string]interface{}{ 501 "extapi_version": core.ExternalAPIVersion, 502 "intapi_version": core.InternalAPIVersion, 503 "extapi_http": extapiURL, 504 "extapi_ipc": ipcapiURL, 505 }, 506 }) 507 508 abortChan := make(chan os.Signal) 509 signal.Notify(abortChan, os.Interrupt) 510 511 sig := <-abortChan 512 log.Info("Exiting...", "signal", sig) 513 514 return nil 515 } 516 517 // splitAndTrim splits input separated by a comma 518 // and trims excessive white space from the substrings. 519 func splitAndTrim(input string) []string { 520 result := strings.Split(input, ",") 521 for i, r := range result { 522 result[i] = strings.TrimSpace(r) 523 } 524 return result 525 } 526 527 // DefaultConfigDir is the default config directory to use for the vaults and other 528 // persistence requirements. 529 func DefaultConfigDir() string { 530 // Try to place the data folder in the user's home dir 531 home := homeDir() 532 if home != "" { 533 if runtime.GOOS == "darwin" { 534 return filepath.Join(home, "Library", "Signer") 535 } else if runtime.GOOS == "windows" { 536 appdata := os.Getenv("APPDATA") 537 if appdata != "" { 538 return filepath.Join(appdata, "Signer") 539 } else { 540 return filepath.Join(home, "AppData", "Roaming", "Signer") 541 } 542 } else { 543 return filepath.Join(home, ".clef") 544 } 545 } 546 // As we cannot guess a stable location, return empty and handle later 547 return "" 548 } 549 550 func homeDir() string { 551 if home := os.Getenv("HOME"); home != "" { 552 return home 553 } 554 if usr, err := user.Current(); err == nil { 555 return usr.HomeDir 556 } 557 return "" 558 } 559 func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) { 560 var ( 561 file string 562 configDir = ctx.GlobalString(configdirFlag.Name) 563 ) 564 if ctx.GlobalIsSet(signerSecretFlag.Name) { 565 file = ctx.GlobalString(signerSecretFlag.Name) 566 } else { 567 file = filepath.Join(configDir, "masterseed.json") 568 } 569 if err := checkFile(file); err != nil { 570 return nil, err 571 } 572 cipherKey, err := ioutil.ReadFile(file) 573 if err != nil { 574 return nil, err 575 } 576 var password string 577 // If ui is not nil, get the password from ui. 578 if ui != nil { 579 resp, err := ui.OnInputRequired(core.UserInputRequest{ 580 Title: "Master Password", 581 Prompt: "Please enter the password to decrypt the master seed", 582 IsPassword: true}) 583 if err != nil { 584 return nil, err 585 } 586 password = resp.Text 587 } else { 588 password = getPassPhrase("Decrypt master seed of clef", false) 589 } 590 masterSeed, err := decryptSeed(cipherKey, password) 591 if err != nil { 592 return nil, fmt.Errorf("failed to decrypt the master seed of clef") 593 } 594 if len(masterSeed) < 256 { 595 return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed)) 596 } 597 598 // Create vault location 599 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10])) 600 err = os.Mkdir(vaultLocation, 0700) 601 if err != nil && !os.IsExist(err) { 602 return nil, err 603 } 604 return masterSeed, nil 605 } 606 607 // checkFile is a convenience function to check if a file 608 // * exists 609 // * is mode 0400 610 func checkFile(filename string) error { 611 info, err := os.Stat(filename) 612 if err != nil { 613 return fmt.Errorf("failed stat on %s: %v", filename, err) 614 } 615 // Check the unix permission bits 616 if info.Mode().Perm()&0377 != 0 { 617 return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) 618 } 619 return nil 620 } 621 622 // confirm displays a text and asks for user confirmation 623 func confirm(text string) bool { 624 fmt.Printf(text) 625 fmt.Printf("\nEnter 'ok' to proceed:\n>") 626 627 text, err := bufio.NewReader(os.Stdin).ReadString('\n') 628 if err != nil { 629 log.Crit("Failed to read user input", "err", err) 630 } 631 632 if text := strings.TrimSpace(text); text == "ok" { 633 return true 634 } 635 return false 636 } 637 638 func testExternalUI(api *core.SignerAPI) { 639 640 ctx := context.WithValue(context.Background(), "remote", "clef binary") 641 ctx = context.WithValue(ctx, "scheme", "in-proc") 642 ctx = context.WithValue(ctx, "local", "main") 643 644 errs := make([]string, 0) 645 646 api.UI.ShowInfo("Testing 'ShowInfo'") 647 api.UI.ShowError("Testing 'ShowError'") 648 649 checkErr := func(method string, err error) { 650 if err != nil && err != core.ErrRequestDenied { 651 errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error())) 652 } 653 } 654 var err error 655 656 cliqueHeader := types.Header{ 657 common.HexToHash("0000H45H"), 658 common.HexToHash("0000H45H"), 659 common.HexToAddress("0000H45H"), 660 common.HexToHash("0000H00H"), 661 common.HexToHash("0000H45H"), 662 common.HexToHash("0000H45H"), 663 types.Bloom{}, 664 big.NewInt(1337), 665 big.NewInt(1337), 666 1338, 667 1338, 668 big.NewInt(1338), 669 []byte("Extra data Extra data Extra data Extra data Extra data Extra data Extra data Extra data"), 670 common.HexToHash("0x0000H45H"), 671 types.BlockNonce{}, 672 } 673 cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader) 674 if err != nil { 675 utils.Fatalf("Should not error: %v", err) 676 } 677 addr, err := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899") 678 if err != nil { 679 utils.Fatalf("Should not error: %v", err) 680 } 681 _, err = api.SignData(ctx, "application/clique", *addr, cliqueRlp) 682 checkErr("SignData", err) 683 684 _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) 685 checkErr("SignTransaction", err) 686 _, err = api.SignData(ctx, "text/plain", common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) 687 checkErr("SignData", err) 688 //_, err = api.SignTypedData(ctx, common.MixedcaseAddress{}, core.TypedData{}) 689 //checkErr("SignTypedData", err) 690 _, err = api.List(ctx) 691 checkErr("List", err) 692 _, err = api.New(ctx) 693 checkErr("New", err) 694 695 api.UI.ShowInfo("Tests completed") 696 697 if len(errs) > 0 { 698 log.Error("Got errors") 699 for _, e := range errs { 700 log.Error(e) 701 } 702 } else { 703 log.Info("No errors") 704 } 705 } 706 707 // getPassPhrase retrieves the password associated with clef, either fetched 708 // from a list of preloaded passphrases, or requested interactively from the user. 709 // TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one. 710 func getPassPhrase(prompt string, confirmation bool) string { 711 fmt.Println(prompt) 712 password, err := console.Stdin.PromptPassword("Passphrase: ") 713 if err != nil { 714 utils.Fatalf("Failed to read passphrase: %v", err) 715 } 716 if confirmation { 717 confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") 718 if err != nil { 719 utils.Fatalf("Failed to read passphrase confirmation: %v", err) 720 } 721 if password != confirm { 722 utils.Fatalf("Passphrases do not match") 723 } 724 } 725 return password 726 } 727 728 type encryptedSeedStorage struct { 729 Description string `json:"description"` 730 Version int `json:"version"` 731 Params keystore.CryptoJSON `json:"params"` 732 } 733 734 // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping, 735 // to encrypt the master seed 736 func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) { 737 cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP) 738 if err != nil { 739 return nil, err 740 } 741 return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct}) 742 } 743 744 // decryptSeed decrypts the master seed 745 func decryptSeed(keyjson []byte, auth string) ([]byte, error) { 746 var encSeed encryptedSeedStorage 747 if err := json.Unmarshal(keyjson, &encSeed); err != nil { 748 return nil, err 749 } 750 if encSeed.Version != 1 { 751 log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version)) 752 } 753 seed, err := keystore.DecryptDataV3(encSeed.Params, auth) 754 if err != nil { 755 return nil, err 756 } 757 return seed, err 758 } 759 760 // GenDoc outputs examples of all structures used in json-rpc communication 761 func GenDoc(ctx *cli.Context) { 762 763 var ( 764 a = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef") 765 b = common.HexToAddress("0x1111111122222222222233333333334444444444") 766 meta = core.Metadata{ 767 Scheme: "http", 768 Local: "localhost:8545", 769 Origin: "www.malicious.ru", 770 Remote: "localhost:9999", 771 UserAgent: "Firefox 3.2", 772 } 773 output []string 774 add = func(name, desc string, v interface{}) { 775 if data, err := json.MarshalIndent(v, "", " "); err == nil { 776 output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data)) 777 } else { 778 log.Error("Error generating output", err) 779 } 780 } 781 ) 782 783 { // Sign plain text request 784 desc := "SignDataRequest contains information about a pending request to sign some data. " + 785 "The data to be signed can be of various types, defined by content-type. Clef has done most " + 786 "of the work in canonicalizing and making sense of the data, and it's up to the UI to present" + 787 "the user with the contents of the `message`" 788 sighash, msg := accounts.TextAndHash([]byte("hello world")) 789 message := []*core.NameValueType{{"message", msg, accounts.MimetypeTextPlain}} 790 791 add("SignDataRequest", desc, &core.SignDataRequest{ 792 Address: common.NewMixedcaseAddress(a), 793 Meta: meta, 794 ContentType: accounts.MimetypeTextPlain, 795 Rawdata: []byte(msg), 796 Message: message, 797 Hash: sighash}) 798 } 799 { // Sign plain text response 800 add("SignDataResponse - approve", "Response to SignDataRequest", 801 &core.SignDataResponse{Password: "apassword", Approved: true}) 802 add("SignDataResponse - deny", "Response to SignDataRequest", 803 &core.SignDataResponse{}) 804 } 805 { // Sign transaction request 806 desc := "SignTxRequest contains information about a pending request to sign a transaction. " + 807 "Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " + 808 "messages of various types, that the user should be informed of." + 809 "\n\n" + 810 "As in any request, it's important to consider that the `meta` info also contains untrusted data." + 811 "\n\n" + 812 "The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " + 813 "they must be identical, otherwise an error is generated. " + 814 "However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)" 815 816 data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04}) 817 add("SignTxRequest", desc, &core.SignTxRequest{ 818 Meta: meta, 819 Callinfo: []core.ValidationInfo{ 820 {"Warning", "Something looks odd, show this message as a warning"}, 821 {"Info", "User should see this aswell"}, 822 }, 823 Transaction: core.SendTxArgs{ 824 Data: &data, 825 Nonce: 0x1, 826 Value: hexutil.Big(*big.NewInt(6)), 827 From: common.NewMixedcaseAddress(a), 828 To: nil, 829 GasPrice: hexutil.Big(*big.NewInt(5)), 830 Gas: 1000, 831 Input: nil, 832 }}) 833 } 834 { // Sign tx response 835 data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01}) 836 add("SignDataResponse - approve", "Response to SignDataRequest. This response needs to contain the `transaction`"+ 837 ", because the UI is free to make modifications to the transaction.", 838 &core.SignTxResponse{Password: "apassword", Approved: true, 839 Transaction: core.SendTxArgs{ 840 Data: &data, 841 Nonce: 0x4, 842 Value: hexutil.Big(*big.NewInt(6)), 843 From: common.NewMixedcaseAddress(a), 844 To: nil, 845 GasPrice: hexutil.Big(*big.NewInt(5)), 846 Gas: 1000, 847 Input: nil, 848 }}) 849 add("SignDataResponse - deny", "Response to SignDataRequest. When denying a request, there's no need to "+ 850 "provide the transaction in return", 851 &core.SignDataResponse{}) 852 } 853 { // WHen a signed tx is ready to go out 854 desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" + 855 "\n\n" + 856 "This occurs _after_ successful completion of the entire signing procedure, but right before the signed " + 857 "transaction is passed to the external caller. This method (and data) can be used by the UI to signal " + 858 "to the user that the transaction was signed, but it is primarily useful for ruleset implementations." + 859 "\n\n" + 860 "A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " + 861 "interface. By hooking into this methods, the ruleset can maintain track of that count." + 862 "\n\n" + 863 "**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" + 864 " (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " + 865 "\n\n" + 866 "The `OnApproved` method cannot be responded to, it's purely informative" 867 868 rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed") 869 var tx types.Transaction 870 rlp.DecodeBytes(rlpdata, &tx) 871 add("OnApproved - SignTransactionResult", desc, ðapi.SignTransactionResult{Raw: rlpdata, Tx: &tx}) 872 873 } 874 { // User input 875 add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)", 876 &core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"}) 877 add("UserInputResponse", "Response to SignDataRequest", 878 &core.UserInputResponse{Text: "The textual response from user"}) 879 } 880 { // List request 881 add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+ 882 "full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+ 883 "who only sees the `address`es. ", 884 &core.ListRequest{ 885 Meta: meta, 886 Accounts: []accounts.Account{ 887 {a, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}}, 888 {b, accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}}, 889 }) 890 891 add("UserInputResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+ 892 "Note: the UI is free to respond with any address the caller, regardless of whether it exists or not", 893 &core.ListResponse{ 894 Accounts: []accounts.Account{ 895 {common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"), accounts.URL{Path: ".. ignored .."}}, 896 {common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"), accounts.URL{}}, 897 }}) 898 } 899 900 fmt.Println(`## UI Client interface 901 902 These data types are defined in the channel between clef and the UI`) 903 for _, elem := range output { 904 fmt.Println(elem) 905 } 906 } 907 908 /** 909 //Create Account 910 911 curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 912 913 // List accounts 914 915 curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/ 916 917 // Make Transaction 918 // safeSend(0x12) 919 // 4401a6e40000000000000000000000000000000000000000000000000000000000000012 920 921 // supplied abi 922 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/ 923 924 // Not supplied 925 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/ 926 927 // Sign data 928 929 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/ 930 931 932 **/