github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/cmd/clef/main.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 12:09:27</date> 10 //</624342588865908736> 11 12 13 //签名者是一个实用程序,可以用来对事务和 14 //任意数据。 15 package main 16 17 import ( 18 "bufio" 19 "context" 20 "crypto/rand" 21 "crypto/sha256" 22 "encoding/hex" 23 "encoding/json" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "os" 28 "os/signal" 29 "os/user" 30 "path/filepath" 31 "runtime" 32 "strings" 33 34 "github.com/ethereum/go-ethereum/cmd/utils" 35 "github.com/ethereum/go-ethereum/common" 36 "github.com/ethereum/go-ethereum/crypto" 37 "github.com/ethereum/go-ethereum/log" 38 "github.com/ethereum/go-ethereum/node" 39 "github.com/ethereum/go-ethereum/rpc" 40 "github.com/ethereum/go-ethereum/signer/core" 41 "github.com/ethereum/go-ethereum/signer/rules" 42 "github.com/ethereum/go-ethereum/signer/storage" 43 "gopkg.in/urfave/cli.v1" 44 ) 45 46 //ExternalApiVersion--请参阅extapi_changelog.md 47 const ExternalAPIVersion = "2.0.0" 48 49 //InternalApiVersion--请参阅intapi_changelog.md 50 const InternalAPIVersion = "2.0.0" 51 52 const legalWarning = ` 53 WARNING! 54 55 Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there 56 are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software 57 unless you agree to take full responsibility for doing so, and know what you are doing. 58 59 TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! 60 61 ` 62 63 var ( 64 logLevelFlag = cli.IntFlag{ 65 Name: "loglevel", 66 Value: 4, 67 Usage: "log level to emit to the screen", 68 } 69 keystoreFlag = cli.StringFlag{ 70 Name: "keystore", 71 Value: filepath.Join(node.DefaultDataDir(), "keystore"), 72 Usage: "Directory for the keystore", 73 } 74 configdirFlag = cli.StringFlag{ 75 Name: "configdir", 76 Value: DefaultConfigDir(), 77 Usage: "Directory for Clef configuration", 78 } 79 rpcPortFlag = cli.IntFlag{ 80 Name: "rpcport", 81 Usage: "HTTP-RPC server listening port", 82 Value: node.DefaultHTTPPort + 5, 83 } 84 signerSecretFlag = cli.StringFlag{ 85 Name: "signersecret", 86 Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash", 87 } 88 dBFlag = cli.StringFlag{ 89 Name: "4bytedb", 90 Usage: "File containing 4byte-identifiers", 91 Value: "./4byte.json", 92 } 93 customDBFlag = cli.StringFlag{ 94 Name: "4bytedb-custom", 95 Usage: "File used for writing new 4byte-identifiers submitted via API", 96 Value: "./4byte-custom.json", 97 } 98 auditLogFlag = cli.StringFlag{ 99 Name: "auditlog", 100 Usage: "File used to emit audit logs. Set to \"\" to disable", 101 Value: "audit.log", 102 } 103 ruleFlag = cli.StringFlag{ 104 Name: "rules", 105 Usage: "Enable rule-engine", 106 Value: "rules.json", 107 } 108 stdiouiFlag = cli.BoolFlag{ 109 Name: "stdio-ui", 110 Usage: "Use STDIN/STDOUT as a channel for an external UI. " + 111 "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + 112 "interface, and can be used when Clef is started by an external process.", 113 } 114 testFlag = cli.BoolFlag{ 115 Name: "stdio-ui-test", 116 Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.", 117 } 118 app = cli.NewApp() 119 initCommand = cli.Command{ 120 Action: utils.MigrateFlags(initializeSecrets), 121 Name: "init", 122 Usage: "Initialize the signer, generate secret storage", 123 ArgsUsage: "", 124 Flags: []cli.Flag{ 125 logLevelFlag, 126 configdirFlag, 127 }, 128 Description: ` 129 The init command generates a master seed which Clef can use to store credentials and data needed for 130 the rule-engine to work.`, 131 } 132 attestCommand = cli.Command{ 133 Action: utils.MigrateFlags(attestFile), 134 Name: "attest", 135 Usage: "Attest that a js-file is to be used", 136 ArgsUsage: "<sha256sum>", 137 Flags: []cli.Flag{ 138 logLevelFlag, 139 configdirFlag, 140 signerSecretFlag, 141 }, 142 Description: ` 143 The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 144 incoming requests. 145 146 Whenever you make an edit to the rule file, you need to use attestation to tell 147 Clef that the file is 'safe' to execute.`, 148 } 149 150 addCredentialCommand = cli.Command{ 151 Action: utils.MigrateFlags(addCredential), 152 Name: "addpw", 153 Usage: "Store a credential for a keystore file", 154 ArgsUsage: "<address> <password>", 155 Flags: []cli.Flag{ 156 logLevelFlag, 157 configdirFlag, 158 signerSecretFlag, 159 }, 160 Description: ` 161 The addpw command stores a password for a given address (keyfile). If you invoke it with only one parameter, it will 162 remove any stored credential for that address (keyfile) 163 `, 164 } 165 ) 166 167 func init() { 168 app.Name = "Clef" 169 app.Usage = "Manage Ethereum account operations" 170 app.Flags = []cli.Flag{ 171 logLevelFlag, 172 keystoreFlag, 173 configdirFlag, 174 utils.NetworkIdFlag, 175 utils.LightKDFFlag, 176 utils.NoUSBFlag, 177 utils.RPCListenAddrFlag, 178 utils.RPCVirtualHostsFlag, 179 utils.IPCDisabledFlag, 180 utils.IPCPathFlag, 181 utils.RPCEnabledFlag, 182 rpcPortFlag, 183 signerSecretFlag, 184 dBFlag, 185 customDBFlag, 186 auditLogFlag, 187 ruleFlag, 188 stdiouiFlag, 189 testFlag, 190 } 191 app.Action = signer 192 app.Commands = []cli.Command{initCommand, attestCommand, addCredentialCommand} 193 194 } 195 func main() { 196 if err := app.Run(os.Args); err != nil { 197 fmt.Fprintln(os.Stderr, err) 198 os.Exit(1) 199 } 200 } 201 202 func initializeSecrets(c *cli.Context) error { 203 if err := initialize(c); err != nil { 204 return err 205 } 206 configDir := c.String(configdirFlag.Name) 207 208 masterSeed := make([]byte, 256) 209 n, err := io.ReadFull(rand.Reader, masterSeed) 210 if err != nil { 211 return err 212 } 213 if n != len(masterSeed) { 214 return fmt.Errorf("failed to read enough random") 215 } 216 err = os.Mkdir(configDir, 0700) 217 if err != nil && !os.IsExist(err) { 218 return err 219 } 220 location := filepath.Join(configDir, "secrets.dat") 221 if _, err := os.Stat(location); err == nil { 222 return fmt.Errorf("file %v already exists, will not overwrite", location) 223 } 224 err = ioutil.WriteFile(location, masterSeed, 0700) 225 if err != nil { 226 return err 227 } 228 fmt.Printf("A master seed has been generated into %s\n", location) 229 fmt.Printf(` 230 This is required to be able to store credentials, such as : 231 * Passwords for keystores (used by rule engine) 232 * Storage for javascript rules 233 * Hash of rule-file 234 235 You should treat that file with utmost secrecy, and make a backup of it. 236 NOTE: This file does not contain your accounts. Those need to be backed up separately! 237 238 `) 239 return nil 240 } 241 func attestFile(ctx *cli.Context) error { 242 if len(ctx.Args()) < 1 { 243 utils.Fatalf("This command requires an argument.") 244 } 245 if err := initialize(ctx); err != nil { 246 return err 247 } 248 249 stretchedKey, err := readMasterKey(ctx) 250 if err != nil { 251 utils.Fatalf(err.Error()) 252 } 253 configDir := ctx.String(configdirFlag.Name) 254 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 255 confKey := crypto.Keccak256([]byte("config"), stretchedKey) 256 257 //初始化加密存储 258 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey) 259 val := ctx.Args().First() 260 configStorage.Put("ruleset_sha256", val) 261 log.Info("Ruleset attestation updated", "sha256", val) 262 return nil 263 } 264 265 func addCredential(ctx *cli.Context) error { 266 if len(ctx.Args()) < 1 { 267 utils.Fatalf("This command requires at leaste one argument.") 268 } 269 if err := initialize(ctx); err != nil { 270 return err 271 } 272 273 stretchedKey, err := readMasterKey(ctx) 274 if err != nil { 275 utils.Fatalf(err.Error()) 276 } 277 configDir := ctx.String(configdirFlag.Name) 278 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 279 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 280 281 //初始化加密存储 282 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 283 key := ctx.Args().First() 284 value := "" 285 if len(ctx.Args()) > 1 { 286 value = ctx.Args().Get(1) 287 } 288 pwStorage.Put(key, value) 289 log.Info("Credential store updated", "key", key) 290 return nil 291 } 292 293 func initialize(c *cli.Context) error { 294 //设置记录器以打印所有内容 295 logOutput := os.Stdout 296 if c.Bool(stdiouiFlag.Name) { 297 logOutput = os.Stderr 298 //如果使用stdioui,则无法执行“确认”流 299 fmt.Fprintf(logOutput, legalWarning) 300 } else { 301 if !confirm(legalWarning) { 302 return fmt.Errorf("aborted by user") 303 } 304 } 305 306 log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) 307 return nil 308 } 309 310 func signer(c *cli.Context) error { 311 if err := initialize(c); err != nil { 312 return err 313 } 314 var ( 315 ui core.SignerUI 316 ) 317 if c.Bool(stdiouiFlag.Name) { 318 log.Info("Using stdin/stdout as UI-channel") 319 ui = core.NewStdIOUI() 320 } else { 321 log.Info("Using CLI as UI-channel") 322 ui = core.NewCommandlineUI() 323 } 324 db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name)) 325 if err != nil { 326 utils.Fatalf(err.Error()) 327 } 328 log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb")) 329 330 var ( 331 api core.ExternalAPI 332 ) 333 334 configDir := c.String(configdirFlag.Name) 335 if stretchedKey, err := readMasterKey(c); err != nil { 336 log.Info("No master seed provided, rules disabled") 337 } else { 338 339 if err != nil { 340 utils.Fatalf(err.Error()) 341 } 342 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) 343 344 //生成特定于域的密钥 345 pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) 346 jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) 347 confkey := crypto.Keccak256([]byte("config"), stretchedKey) 348 349 //初始化加密存储 350 pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) 351 jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) 352 configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) 353 354 //我们有规则文件吗? 355 ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name)) 356 if err != nil { 357 log.Info("Could not load rulefile, rules not enabled", "file", "rulefile") 358 } else { 359 hasher := sha256.New() 360 hasher.Write(ruleJS) 361 shasum := hasher.Sum(nil) 362 storedShasum := configStorage.Get("ruleset_sha256") 363 if storedShasum != hex.EncodeToString(shasum) { 364 log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum) 365 } else { 366 //初始化规则 367 ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage) 368 if err != nil { 369 utils.Fatalf(err.Error()) 370 } 371 ruleEngine.Init(string(ruleJS)) 372 ui = ruleEngine 373 log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) 374 } 375 } 376 } 377 378 apiImpl := core.NewSignerAPI( 379 c.Int64(utils.NetworkIdFlag.Name), 380 c.String(keystoreFlag.Name), 381 c.Bool(utils.NoUSBFlag.Name), 382 ui, db, 383 c.Bool(utils.LightKDFFlag.Name)) 384 385 api = apiImpl 386 387 //审计日志 388 if logfile := c.String(auditLogFlag.Name); logfile != "" { 389 api, err = core.NewAuditLogger(logfile, api) 390 if err != nil { 391 utils.Fatalf(err.Error()) 392 } 393 log.Info("Audit logs configured", "file", logfile) 394 } 395 //向服务器注册签名者API 396 var ( 397 extapiURL = "n/a" 398 ipcapiURL = "n/a" 399 ) 400 rpcAPI := []rpc.API{ 401 { 402 Namespace: "account", 403 Public: true, 404 Service: api, 405 Version: "1.0"}, 406 } 407 if c.Bool(utils.RPCEnabledFlag.Name) { 408 409 vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) 410 cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) 411 412 // 413 httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) 414 listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts) 415 if err != nil { 416 utils.Fatalf("Could not start RPC api: %v", err) 417 } 418 extapiURL = fmt.Sprintf("http://%s“,httpendpoint) 419 log.Info("HTTP endpoint opened", "url", extapiURL) 420 421 defer func() { 422 listener.Close() 423 log.Info("HTTP endpoint closed", "url", httpEndpoint) 424 }() 425 426 } 427 if !c.Bool(utils.IPCDisabledFlag.Name) { 428 if c.IsSet(utils.IPCPathFlag.Name) { 429 ipcapiURL = c.String(utils.IPCPathFlag.Name) 430 } else { 431 ipcapiURL = filepath.Join(configDir, "clef.ipc") 432 } 433 434 listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) 435 if err != nil { 436 utils.Fatalf("Could not start IPC api: %v", err) 437 } 438 log.Info("IPC endpoint opened", "url", ipcapiURL) 439 defer func() { 440 listener.Close() 441 log.Info("IPC endpoint closed", "url", ipcapiURL) 442 }() 443 444 } 445 446 if c.Bool(testFlag.Name) { 447 log.Info("Performing UI test") 448 go testExternalUI(apiImpl) 449 } 450 ui.OnSignerStartup(core.StartupInfo{ 451 Info: map[string]interface{}{ 452 "extapi_version": ExternalAPIVersion, 453 "intapi_version": InternalAPIVersion, 454 "extapi_http": extapiURL, 455 "extapi_ipc": ipcapiURL, 456 }, 457 }) 458 459 abortChan := make(chan os.Signal) 460 signal.Notify(abortChan, os.Interrupt) 461 462 sig := <-abortChan 463 log.Info("Exiting...", "signal", sig) 464 465 return nil 466 } 467 468 //splitandtrim拆分由逗号分隔的输入 469 //并修剪子字符串中多余的空白。 470 func splitAndTrim(input string) []string { 471 result := strings.Split(input, ",") 472 for i, r := range result { 473 result[i] = strings.TrimSpace(r) 474 } 475 return result 476 } 477 478 // 479 //持久性要求。 480 func DefaultConfigDir() string { 481 //尝试将数据文件夹放在用户的home目录中 482 home := homeDir() 483 if home != "" { 484 if runtime.GOOS == "darwin" { 485 return filepath.Join(home, "Library", "Signer") 486 } else if runtime.GOOS == "windows" { 487 return filepath.Join(home, "AppData", "Roaming", "Signer") 488 } else { 489 return filepath.Join(home, ".clef") 490 } 491 } 492 //因为我们无法猜测一个稳定的位置,所以返回空的,稍后再处理 493 return "" 494 } 495 496 func homeDir() string { 497 if home := os.Getenv("HOME"); home != "" { 498 return home 499 } 500 if usr, err := user.Current(); err == nil { 501 return usr.HomeDir 502 } 503 return "" 504 } 505 func readMasterKey(ctx *cli.Context) ([]byte, error) { 506 var ( 507 file string 508 configDir = ctx.String(configdirFlag.Name) 509 ) 510 if ctx.IsSet(signerSecretFlag.Name) { 511 file = ctx.String(signerSecretFlag.Name) 512 } else { 513 file = filepath.Join(configDir, "secrets.dat") 514 } 515 if err := checkFile(file); err != nil { 516 return nil, err 517 } 518 masterKey, err := ioutil.ReadFile(file) 519 if err != nil { 520 return nil, err 521 } 522 if len(masterKey) < 256 { 523 return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey)) 524 } 525 //创建保管库位置 526 vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10])) 527 err = os.Mkdir(vaultLocation, 0700) 528 if err != nil && !os.IsExist(err) { 529 return nil, err 530 } 531 //!todo,使用kdf拉伸主密钥 532 //拉伸键:=拉伸键(主\键) 533 534 return masterKey, nil 535 } 536 537 //check file是检查文件 538 //*存在 539 //*模式0600 540 func checkFile(filename string) error { 541 info, err := os.Stat(filename) 542 if err != nil { 543 return fmt.Errorf("failed stat on %s: %v", filename, err) 544 } 545 //检查Unix权限位 546 if info.Mode().Perm()&077 != 0 { 547 return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) 548 } 549 return nil 550 } 551 552 //确认显示文本并请求用户确认 553 func confirm(text string) bool { 554 fmt.Printf(text) 555 fmt.Printf("\nEnter 'ok' to proceed:\n>") 556 557 text, err := bufio.NewReader(os.Stdin).ReadString('\n') 558 if err != nil { 559 log.Crit("Failed to read user input", "err", err) 560 } 561 562 if text := strings.TrimSpace(text); text == "ok" { 563 return true 564 } 565 return false 566 } 567 568 func testExternalUI(api *core.SignerAPI) { 569 570 ctx := context.WithValue(context.Background(), "remote", "clef binary") 571 ctx = context.WithValue(ctx, "scheme", "in-proc") 572 ctx = context.WithValue(ctx, "local", "main") 573 574 errs := make([]string, 0) 575 576 api.UI.ShowInfo("Testing 'ShowInfo'") 577 api.UI.ShowError("Testing 'ShowError'") 578 579 checkErr := func(method string, err error) { 580 if err != nil && err != core.ErrRequestDenied { 581 errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error())) 582 } 583 } 584 var err error 585 586 _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) 587 checkErr("SignTransaction", err) 588 _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) 589 checkErr("Sign", err) 590 _, err = api.List(ctx) 591 checkErr("List", err) 592 _, err = api.New(ctx) 593 checkErr("New", err) 594 _, err = api.Export(ctx, common.Address{}) 595 checkErr("Export", err) 596 _, err = api.Import(ctx, json.RawMessage{}) 597 checkErr("Import", err) 598 599 api.UI.ShowInfo("Tests completed") 600 601 if len(errs) > 0 { 602 log.Error("Got errors") 603 for _, e := range errs { 604 log.Error(e) 605 } 606 } else { 607 log.Info("No errors") 608 } 609 610 } 611 612 /* 613 614 615 curl-h“content-type:application/json”-x post--data'“jsonrpc”:“2.0”,“method”:“account_new”,“params”:[“test”],“id”:67“localhost:8550” 616 617 //列出帐户 618 619 curl-i-h“内容类型:application/json”-x post--data'“jsonrpc”:“2.0”,“method”:“account_list”,“params”:[“”],“id”:67”http://localhost:8550/ 620 621 622 //安全端(0x12) 623 //4401A6E4000000000000000000000000000000000000000000000000000000000012 624 625 /供给ABI 626 627 628 629 curl-i-h“content-type:application/json”-x post--data'“jsonrpc”:“2.0”,“method”:“account-signtransaction”,“params”:[“from”:“0x82A2A876D39022B3019932D30CD9C97AD5616813”,“gas”:“0x333”,“gasprice”:“0x123”,“nonce”:“0x 0”,“to”:“0x07A565B7ED7D7A68680A4C162885BEDB695FE0”,“value”:“0x10”,“data”:“0x4401A6E400000000000000000000000000000000000000000”000000000000000000 12“”,“id”:67”http://localhost:8550/ 630 631 632 633 curl-i-h“内容类型:application/json”-x post--数据'“jsonrpc”:“2.0”,“方法”:“帐户符号”,“参数”:[“0x694267f14675d7e1b9494fd8d72fe1755710fa”,“bazonk gaz baz”],“id”:67“http://localhost:8550/ 634 635 636 */ 637 638