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