github.com/JFJun/bsc@v1.0.0/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  package main
    18  
    19  import (
    20  	"bufio"
    21  	"context"
    22  	"crypto/rand"
    23  	"crypto/sha256"
    24  	"encoding/hex"
    25  	"encoding/json"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"math/big"
    30  	"os"
    31  	"os/signal"
    32  	"os/user"
    33  	"path/filepath"
    34  	"runtime"
    35  	"strings"
    36  	"time"
    37  
    38  	"github.com/JFJun/bsc/accounts"
    39  	"github.com/JFJun/bsc/accounts/keystore"
    40  	"github.com/JFJun/bsc/cmd/utils"
    41  	"github.com/JFJun/bsc/common"
    42  	"github.com/JFJun/bsc/common/hexutil"
    43  	"github.com/JFJun/bsc/console"
    44  	"github.com/JFJun/bsc/core/types"
    45  	"github.com/JFJun/bsc/crypto"
    46  	"github.com/JFJun/bsc/internal/ethapi"
    47  	"github.com/JFJun/bsc/log"
    48  	"github.com/JFJun/bsc/node"
    49  	"github.com/JFJun/bsc/params"
    50  	"github.com/JFJun/bsc/rlp"
    51  	"github.com/JFJun/bsc/rpc"
    52  	"github.com/JFJun/bsc/signer/core"
    53  	"github.com/JFJun/bsc/signer/fourbyte"
    54  	"github.com/JFJun/bsc/signer/rules"
    55  	"github.com/JFJun/bsc/signer/storage"
    56  	colorable "github.com/mattn/go-colorable"
    57  	"github.com/mattn/go-isatty"
    58  	"gopkg.in/urfave/cli.v1"
    59  )
    60  
    61  const legalWarning = `
    62  WARNING!
    63  
    64  Clef is an account management tool. It may, like any software, contain bugs.
    65  
    66  Please take care to
    67  - backup your keystore files,
    68  - verify that the keystore(s) can be opened with your password.
    69  
    70  Clef is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
    71  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
    72  PURPOSE. See the GNU General Public License for more details.
    73  `
    74  
    75  var (
    76  	logLevelFlag = cli.IntFlag{
    77  		Name:  "loglevel",
    78  		Value: 4,
    79  		Usage: "log level to emit to the screen",
    80  	}
    81  	advancedMode = cli.BoolFlag{
    82  		Name:  "advanced",
    83  		Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off",
    84  	}
    85  	keystoreFlag = cli.StringFlag{
    86  		Name:  "keystore",
    87  		Value: filepath.Join(node.DefaultDataDir(), "keystore"),
    88  		Usage: "Directory for the keystore",
    89  	}
    90  	configdirFlag = cli.StringFlag{
    91  		Name:  "configdir",
    92  		Value: DefaultConfigDir(),
    93  		Usage: "Directory for Clef configuration",
    94  	}
    95  	chainIdFlag = cli.Int64Flag{
    96  		Name:  "chainid",
    97  		Value: params.MainnetChainConfig.ChainID.Int64(),
    98  		Usage: "Chain id to use for signing (1=mainnet, 3=Ropsten, 4=Rinkeby, 5=Goerli)",
    99  	}
   100  	rpcPortFlag = cli.IntFlag{
   101  		Name:  "rpcport",
   102  		Usage: "HTTP-RPC server listening port",
   103  		Value: node.DefaultHTTPPort + 5,
   104  	}
   105  	signerSecretFlag = cli.StringFlag{
   106  		Name:  "signersecret",
   107  		Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash",
   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: "Path to the rule file to auto-authorize requests with",
   122  	}
   123  	stdiouiFlag = cli.BoolFlag{
   124  		Name: "stdio-ui",
   125  		Usage: "Use STDIN/STDOUT as a channel for an external UI. " +
   126  			"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
   127  			"interface, and can be used when Clef is started by an external process.",
   128  	}
   129  	testFlag = cli.BoolFlag{
   130  		Name:  "stdio-ui-test",
   131  		Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.",
   132  	}
   133  	app         = cli.NewApp()
   134  	initCommand = cli.Command{
   135  		Action:    utils.MigrateFlags(initializeSecrets),
   136  		Name:      "init",
   137  		Usage:     "Initialize the signer, generate secret storage",
   138  		ArgsUsage: "",
   139  		Flags: []cli.Flag{
   140  			logLevelFlag,
   141  			configdirFlag,
   142  		},
   143  		Description: `
   144  The init command generates a master seed which Clef can use to store credentials and data needed for
   145  the rule-engine to work.`,
   146  	}
   147  	attestCommand = cli.Command{
   148  		Action:    utils.MigrateFlags(attestFile),
   149  		Name:      "attest",
   150  		Usage:     "Attest that a js-file is to be used",
   151  		ArgsUsage: "<sha256sum>",
   152  		Flags: []cli.Flag{
   153  			logLevelFlag,
   154  			configdirFlag,
   155  			signerSecretFlag,
   156  		},
   157  		Description: `
   158  The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of
   159  incoming requests.
   160  
   161  Whenever you make an edit to the rule file, you need to use attestation to tell
   162  Clef that the file is 'safe' to execute.`,
   163  	}
   164  	setCredentialCommand = cli.Command{
   165  		Action:    utils.MigrateFlags(setCredential),
   166  		Name:      "setpw",
   167  		Usage:     "Store a credential for a keystore file",
   168  		ArgsUsage: "<address>",
   169  		Flags: []cli.Flag{
   170  			logLevelFlag,
   171  			configdirFlag,
   172  			signerSecretFlag,
   173  		},
   174  		Description: `
   175  The setpw command stores a password for a given address (keyfile).
   176  `}
   177  	delCredentialCommand = cli.Command{
   178  		Action:    utils.MigrateFlags(removeCredential),
   179  		Name:      "delpw",
   180  		Usage:     "Remove a credential for a keystore file",
   181  		ArgsUsage: "<address>",
   182  		Flags: []cli.Flag{
   183  			logLevelFlag,
   184  			configdirFlag,
   185  			signerSecretFlag,
   186  		},
   187  		Description: `
   188  The delpw command removes a password for a given address (keyfile).
   189  `}
   190  	newAccountCommand = cli.Command{
   191  		Action:    utils.MigrateFlags(newAccount),
   192  		Name:      "newaccount",
   193  		Usage:     "Create a new account",
   194  		ArgsUsage: "",
   195  		Flags: []cli.Flag{
   196  			logLevelFlag,
   197  			keystoreFlag,
   198  			utils.LightKDFFlag,
   199  		},
   200  		Description: `
   201  The newaccount command creates a new keystore-backed account. It is a convenience-method
   202  which can be used in lieu of an external UI.`,
   203  	}
   204  
   205  	gendocCommand = cli.Command{
   206  		Action: GenDoc,
   207  		Name:   "gendoc",
   208  		Usage:  "Generate documentation about json-rpc format",
   209  		Description: `
   210  The gendoc generates example structures of the json-rpc communication types.
   211  `}
   212  )
   213  
   214  func init() {
   215  	app.Name = "Clef"
   216  	app.Usage = "Manage Ethereum account operations"
   217  	app.Flags = []cli.Flag{
   218  		logLevelFlag,
   219  		keystoreFlag,
   220  		configdirFlag,
   221  		chainIdFlag,
   222  		utils.LightKDFFlag,
   223  		utils.NoUSBFlag,
   224  		utils.SmartCardDaemonPathFlag,
   225  		utils.RPCListenAddrFlag,
   226  		utils.RPCVirtualHostsFlag,
   227  		utils.IPCDisabledFlag,
   228  		utils.IPCPathFlag,
   229  		utils.RPCEnabledFlag,
   230  		rpcPortFlag,
   231  		signerSecretFlag,
   232  		customDBFlag,
   233  		auditLogFlag,
   234  		ruleFlag,
   235  		stdiouiFlag,
   236  		testFlag,
   237  		advancedMode,
   238  	}
   239  	app.Action = signer
   240  	app.Commands = []cli.Command{initCommand,
   241  		attestCommand,
   242  		setCredentialCommand,
   243  		delCredentialCommand,
   244  		newAccountCommand,
   245  		gendocCommand}
   246  	cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate
   247  }
   248  
   249  func main() {
   250  	if err := app.Run(os.Args); err != nil {
   251  		fmt.Fprintln(os.Stderr, err)
   252  		os.Exit(1)
   253  	}
   254  }
   255  
   256  func initializeSecrets(c *cli.Context) error {
   257  	// Get past the legal message
   258  	if err := initialize(c); err != nil {
   259  		return err
   260  	}
   261  	// Ensure the master key does not yet exist, we're not willing to overwrite
   262  	configDir := c.GlobalString(configdirFlag.Name)
   263  	if err := os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) {
   264  		return err
   265  	}
   266  	location := filepath.Join(configDir, "masterseed.json")
   267  	if _, err := os.Stat(location); err == nil {
   268  		return fmt.Errorf("master key %v already exists, will not overwrite", location)
   269  	}
   270  	// Key file does not exist yet, generate a new one and encrypt it
   271  	masterSeed := make([]byte, 256)
   272  	num, err := io.ReadFull(rand.Reader, masterSeed)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if num != len(masterSeed) {
   277  		return fmt.Errorf("failed to read enough random")
   278  	}
   279  	n, p := keystore.StandardScryptN, keystore.StandardScryptP
   280  	if c.GlobalBool(utils.LightKDFFlag.Name) {
   281  		n, p = keystore.LightScryptN, keystore.LightScryptP
   282  	}
   283  	text := "The master seed of clef will be locked with a password.\nPlease specify a password. Do not forget this password!"
   284  	var password string
   285  	for {
   286  		password = getPassPhrase(text, true)
   287  		if err := core.ValidatePasswordFormat(password); err != nil {
   288  			fmt.Printf("invalid password: %v\n", err)
   289  		} else {
   290  			fmt.Println()
   291  			break
   292  		}
   293  	}
   294  	cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p)
   295  	if err != nil {
   296  		return fmt.Errorf("failed to encrypt master seed: %v", err)
   297  	}
   298  	// Double check the master key path to ensure nothing wrote there in between
   299  	if err = os.Mkdir(configDir, 0700); err != nil && !os.IsExist(err) {
   300  		return err
   301  	}
   302  	if _, err := os.Stat(location); err == nil {
   303  		return fmt.Errorf("master key %v already exists, will not overwrite", location)
   304  	}
   305  	// Write the file and print the usual warning message
   306  	if err = ioutil.WriteFile(location, cipherSeed, 0400); err != nil {
   307  		return err
   308  	}
   309  	fmt.Printf("A master seed has been generated into %s\n", location)
   310  	fmt.Printf(`
   311  This is required to be able to store credentials, such as:
   312  * Passwords for keystores (used by rule engine)
   313  * Storage for JavaScript auto-signing rules
   314  * Hash of JavaScript rule-file
   315  
   316  You should treat 'masterseed.json' with utmost secrecy and make a backup of it!
   317  * The password is necessary but not enough, you need to back up the master seed too!
   318  * The master seed does not contain your accounts, those need to be backed up separately!
   319  
   320  `)
   321  	return nil
   322  }
   323  func attestFile(ctx *cli.Context) error {
   324  	if len(ctx.Args()) < 1 {
   325  		utils.Fatalf("This command requires an argument.")
   326  	}
   327  	if err := initialize(ctx); err != nil {
   328  		return err
   329  	}
   330  
   331  	stretchedKey, err := readMasterKey(ctx, nil)
   332  	if err != nil {
   333  		utils.Fatalf(err.Error())
   334  	}
   335  	configDir := ctx.GlobalString(configdirFlag.Name)
   336  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   337  	confKey := crypto.Keccak256([]byte("config"), stretchedKey)
   338  
   339  	// Initialize the encrypted storages
   340  	configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
   341  	val := ctx.Args().First()
   342  	configStorage.Put("ruleset_sha256", val)
   343  	log.Info("Ruleset attestation updated", "sha256", val)
   344  	return nil
   345  }
   346  
   347  func setCredential(ctx *cli.Context) error {
   348  	if len(ctx.Args()) < 1 {
   349  		utils.Fatalf("This command requires an address to be passed as an argument")
   350  	}
   351  	if err := initialize(ctx); err != nil {
   352  		return err
   353  	}
   354  	addr := ctx.Args().First()
   355  	if !common.IsHexAddress(addr) {
   356  		utils.Fatalf("Invalid address specified: %s", addr)
   357  	}
   358  	address := common.HexToAddress(addr)
   359  	password := getPassPhrase("Please enter a password to store for this address:", true)
   360  	fmt.Println()
   361  
   362  	stretchedKey, err := readMasterKey(ctx, nil)
   363  	if err != nil {
   364  		utils.Fatalf(err.Error())
   365  	}
   366  	configDir := ctx.GlobalString(configdirFlag.Name)
   367  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   368  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   369  
   370  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   371  	pwStorage.Put(address.Hex(), password)
   372  
   373  	log.Info("Credential store updated", "set", address)
   374  	return nil
   375  }
   376  
   377  func removeCredential(ctx *cli.Context) error {
   378  	if len(ctx.Args()) < 1 {
   379  		utils.Fatalf("This command requires an address to be passed as an argument")
   380  	}
   381  	if err := initialize(ctx); err != nil {
   382  		return err
   383  	}
   384  	addr := ctx.Args().First()
   385  	if !common.IsHexAddress(addr) {
   386  		utils.Fatalf("Invalid address specified: %s", addr)
   387  	}
   388  	address := common.HexToAddress(addr)
   389  
   390  	stretchedKey, err := readMasterKey(ctx, nil)
   391  	if err != nil {
   392  		utils.Fatalf(err.Error())
   393  	}
   394  	configDir := ctx.GlobalString(configdirFlag.Name)
   395  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   396  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   397  
   398  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   399  	pwStorage.Del(address.Hex())
   400  
   401  	log.Info("Credential store updated", "unset", address)
   402  	return nil
   403  }
   404  
   405  func newAccount(c *cli.Context) error {
   406  	if err := initialize(c); err != nil {
   407  		return err
   408  	}
   409  	// The newaccount is meant for users using the CLI, since 'real' external
   410  	// UIs can use the UI-api instead. So we'll just use the native CLI UI here.
   411  	var (
   412  		ui                        = core.NewCommandlineUI()
   413  		pwStorage storage.Storage = &storage.NoStorage{}
   414  		ksLoc                     = c.GlobalString(keystoreFlag.Name)
   415  		lightKdf                  = c.GlobalBool(utils.LightKDFFlag.Name)
   416  	)
   417  	log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf)
   418  	am := core.StartClefAccountManager(ksLoc, true, lightKdf, "")
   419  	// This gives is us access to the external API
   420  	apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage)
   421  	// This gives us access to the internal API
   422  	internalApi := core.NewUIServerAPI(apiImpl)
   423  	addr, err := internalApi.New(context.Background())
   424  	if err == nil {
   425  		fmt.Printf("Generated account %v\n", addr.String())
   426  	}
   427  	return err
   428  }
   429  
   430  func initialize(c *cli.Context) error {
   431  	// Set up the logger to print everything
   432  	logOutput := os.Stdout
   433  	if c.GlobalBool(stdiouiFlag.Name) {
   434  		logOutput = os.Stderr
   435  		// If using the stdioui, we can't do the 'confirm'-flow
   436  		fmt.Fprint(logOutput, legalWarning)
   437  	} else {
   438  		if !confirm(legalWarning) {
   439  			return fmt.Errorf("aborted by user")
   440  		}
   441  		fmt.Println()
   442  	}
   443  	usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
   444  	output := io.Writer(logOutput)
   445  	if usecolor {
   446  		output = colorable.NewColorable(logOutput)
   447  	}
   448  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor))))
   449  
   450  	return nil
   451  }
   452  
   453  // ipcEndpoint resolves an IPC endpoint based on a configured value, taking into
   454  // account the set data folders as well as the designated platform we're currently
   455  // running on.
   456  func ipcEndpoint(ipcPath, datadir string) string {
   457  	// On windows we can only use plain top-level pipes
   458  	if runtime.GOOS == "windows" {
   459  		if strings.HasPrefix(ipcPath, `\\.\pipe\`) {
   460  			return ipcPath
   461  		}
   462  		return `\\.\pipe\` + ipcPath
   463  	}
   464  	// Resolve names into the data directory full paths otherwise
   465  	if filepath.Base(ipcPath) == ipcPath {
   466  		if datadir == "" {
   467  			return filepath.Join(os.TempDir(), ipcPath)
   468  		}
   469  		return filepath.Join(datadir, ipcPath)
   470  	}
   471  	return ipcPath
   472  }
   473  
   474  func signer(c *cli.Context) error {
   475  	// If we have some unrecognized command, bail out
   476  	if args := c.Args(); len(args) > 0 {
   477  		return fmt.Errorf("invalid command: %q", args[0])
   478  	}
   479  	if err := initialize(c); err != nil {
   480  		return err
   481  	}
   482  	var (
   483  		ui core.UIClientAPI
   484  	)
   485  	if c.GlobalBool(stdiouiFlag.Name) {
   486  		log.Info("Using stdin/stdout as UI-channel")
   487  		ui = core.NewStdIOUI()
   488  	} else {
   489  		log.Info("Using CLI as UI-channel")
   490  		ui = core.NewCommandlineUI()
   491  	}
   492  	// 4bytedb data
   493  	fourByteLocal := c.GlobalString(customDBFlag.Name)
   494  	db, err := fourbyte.NewWithFile(fourByteLocal)
   495  	if err != nil {
   496  		utils.Fatalf(err.Error())
   497  	}
   498  	embeds, locals := db.Size()
   499  	log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal)
   500  
   501  	var (
   502  		api       core.ExternalAPI
   503  		pwStorage storage.Storage = &storage.NoStorage{}
   504  	)
   505  	configDir := c.GlobalString(configdirFlag.Name)
   506  	if stretchedKey, err := readMasterKey(c, ui); err != nil {
   507  		log.Warn("Failed to open master, rules disabled", "err", err)
   508  	} else {
   509  		vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   510  
   511  		// Generate domain specific keys
   512  		pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   513  		jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
   514  		confkey := crypto.Keccak256([]byte("config"), stretchedKey)
   515  
   516  		// Initialize the encrypted storages
   517  		pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   518  		jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
   519  		configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
   520  
   521  		// Do we have a rule-file?
   522  		if ruleFile := c.GlobalString(ruleFlag.Name); ruleFile != "" {
   523  			ruleJS, err := ioutil.ReadFile(ruleFile)
   524  			if err != nil {
   525  				log.Warn("Could not load rules, disabling", "file", ruleFile, "err", err)
   526  			} else {
   527  				shasum := sha256.Sum256(ruleJS)
   528  				foundShaSum := hex.EncodeToString(shasum[:])
   529  				storedShasum, _ := configStorage.Get("ruleset_sha256")
   530  				if storedShasum != foundShaSum {
   531  					log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum)
   532  				} else {
   533  					// Initialize rules
   534  					ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage)
   535  					if err != nil {
   536  						utils.Fatalf(err.Error())
   537  					}
   538  					ruleEngine.Init(string(ruleJS))
   539  					ui = ruleEngine
   540  					log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
   541  				}
   542  			}
   543  		}
   544  	}
   545  	var (
   546  		chainId  = c.GlobalInt64(chainIdFlag.Name)
   547  		ksLoc    = c.GlobalString(keystoreFlag.Name)
   548  		lightKdf = c.GlobalBool(utils.LightKDFFlag.Name)
   549  		advanced = c.GlobalBool(advancedMode.Name)
   550  		nousb    = c.GlobalBool(utils.NoUSBFlag.Name)
   551  		scpath   = c.GlobalString(utils.SmartCardDaemonPathFlag.Name)
   552  	)
   553  	log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
   554  		"light-kdf", lightKdf, "advanced", advanced)
   555  	am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
   556  	apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
   557  
   558  	// Establish the bidirectional communication, by creating a new UI backend and registering
   559  	// it with the UI.
   560  	ui.RegisterUIServer(core.NewUIServerAPI(apiImpl))
   561  	api = apiImpl
   562  	// Audit logging
   563  	if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" {
   564  		api, err = core.NewAuditLogger(logfile, api)
   565  		if err != nil {
   566  			utils.Fatalf(err.Error())
   567  		}
   568  		log.Info("Audit logs configured", "file", logfile)
   569  	}
   570  	// register signer API with server
   571  	var (
   572  		extapiURL = "n/a"
   573  		ipcapiURL = "n/a"
   574  	)
   575  	rpcAPI := []rpc.API{
   576  		{
   577  			Namespace: "account",
   578  			Public:    true,
   579  			Service:   api,
   580  			Version:   "1.0"},
   581  	}
   582  	if c.GlobalBool(utils.RPCEnabledFlag.Name) {
   583  		vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
   584  		cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
   585  
   586  		srv := rpc.NewServer()
   587  		err := node.RegisterApisFromWhitelist(rpcAPI, []string{"account"}, srv, false)
   588  		if err != nil {
   589  			utils.Fatalf("Could not register API: %w", err)
   590  		}
   591  		handler := node.NewHTTPHandlerStack(srv, cors, vhosts)
   592  
   593  		// start http server
   594  		httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
   595  		listener, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler)
   596  		if err != nil {
   597  			utils.Fatalf("Could not start RPC api: %v", err)
   598  		}
   599  		extapiURL = fmt.Sprintf("http://%v/", listener.Addr())
   600  		log.Info("HTTP endpoint opened", "url", extapiURL)
   601  
   602  		defer func() {
   603  			listener.Close()
   604  			log.Info("HTTP endpoint closed", "url", extapiURL)
   605  		}()
   606  	}
   607  	if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
   608  		givenPath := c.GlobalString(utils.IPCPathFlag.Name)
   609  		ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir)
   610  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   611  		if err != nil {
   612  			utils.Fatalf("Could not start IPC api: %v", err)
   613  		}
   614  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   615  		defer func() {
   616  			listener.Close()
   617  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   618  		}()
   619  	}
   620  
   621  	if c.GlobalBool(testFlag.Name) {
   622  		log.Info("Performing UI test")
   623  		go testExternalUI(apiImpl)
   624  	}
   625  	ui.OnSignerStartup(core.StartupInfo{
   626  		Info: map[string]interface{}{
   627  			"intapi_version": core.InternalAPIVersion,
   628  			"extapi_version": core.ExternalAPIVersion,
   629  			"extapi_http":    extapiURL,
   630  			"extapi_ipc":     ipcapiURL,
   631  		},
   632  	})
   633  
   634  	abortChan := make(chan os.Signal, 1)
   635  	signal.Notify(abortChan, os.Interrupt)
   636  
   637  	sig := <-abortChan
   638  	log.Info("Exiting...", "signal", sig)
   639  
   640  	return nil
   641  }
   642  
   643  // splitAndTrim splits input separated by a comma
   644  // and trims excessive white space from the substrings.
   645  func splitAndTrim(input string) []string {
   646  	result := strings.Split(input, ",")
   647  	for i, r := range result {
   648  		result[i] = strings.TrimSpace(r)
   649  	}
   650  	return result
   651  }
   652  
   653  // DefaultConfigDir is the default config directory to use for the vaults and other
   654  // persistence requirements.
   655  func DefaultConfigDir() string {
   656  	// Try to place the data folder in the user's home dir
   657  	home := homeDir()
   658  	if home != "" {
   659  		if runtime.GOOS == "darwin" {
   660  			return filepath.Join(home, "Library", "Signer")
   661  		} else if runtime.GOOS == "windows" {
   662  			appdata := os.Getenv("APPDATA")
   663  			if appdata != "" {
   664  				return filepath.Join(appdata, "Signer")
   665  			} else {
   666  				return filepath.Join(home, "AppData", "Roaming", "Signer")
   667  			}
   668  		} else {
   669  			return filepath.Join(home, ".clef")
   670  		}
   671  	}
   672  	// As we cannot guess a stable location, return empty and handle later
   673  	return ""
   674  }
   675  
   676  func homeDir() string {
   677  	if home := os.Getenv("HOME"); home != "" {
   678  		return home
   679  	}
   680  	if usr, err := user.Current(); err == nil {
   681  		return usr.HomeDir
   682  	}
   683  	return ""
   684  }
   685  func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
   686  	var (
   687  		file      string
   688  		configDir = ctx.GlobalString(configdirFlag.Name)
   689  	)
   690  	if ctx.GlobalIsSet(signerSecretFlag.Name) {
   691  		file = ctx.GlobalString(signerSecretFlag.Name)
   692  	} else {
   693  		file = filepath.Join(configDir, "masterseed.json")
   694  	}
   695  	if err := checkFile(file); err != nil {
   696  		return nil, err
   697  	}
   698  	cipherKey, err := ioutil.ReadFile(file)
   699  	if err != nil {
   700  		return nil, err
   701  	}
   702  	var password string
   703  	// If ui is not nil, get the password from ui.
   704  	if ui != nil {
   705  		resp, err := ui.OnInputRequired(core.UserInputRequest{
   706  			Title:      "Master Password",
   707  			Prompt:     "Please enter the password to decrypt the master seed",
   708  			IsPassword: true})
   709  		if err != nil {
   710  			return nil, err
   711  		}
   712  		password = resp.Text
   713  	} else {
   714  		password = getPassPhrase("Decrypt master seed of clef", false)
   715  	}
   716  	masterSeed, err := decryptSeed(cipherKey, password)
   717  	if err != nil {
   718  		return nil, fmt.Errorf("failed to decrypt the master seed of clef")
   719  	}
   720  	if len(masterSeed) < 256 {
   721  		return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
   722  	}
   723  	// Create vault location
   724  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
   725  	err = os.Mkdir(vaultLocation, 0700)
   726  	if err != nil && !os.IsExist(err) {
   727  		return nil, err
   728  	}
   729  	return masterSeed, nil
   730  }
   731  
   732  // checkFile is a convenience function to check if a file
   733  // * exists
   734  // * is mode 0400
   735  func checkFile(filename string) error {
   736  	info, err := os.Stat(filename)
   737  	if err != nil {
   738  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   739  	}
   740  	// Check the unix permission bits
   741  	if info.Mode().Perm()&0377 != 0 {
   742  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   743  	}
   744  	return nil
   745  }
   746  
   747  // confirm displays a text and asks for user confirmation
   748  func confirm(text string) bool {
   749  	fmt.Print(text)
   750  	fmt.Printf("\nEnter 'ok' to proceed:\n> ")
   751  
   752  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   753  	if err != nil {
   754  		log.Crit("Failed to read user input", "err", err)
   755  	}
   756  	if text := strings.TrimSpace(text); text == "ok" {
   757  		return true
   758  	}
   759  	return false
   760  }
   761  
   762  func testExternalUI(api *core.SignerAPI) {
   763  
   764  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   765  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   766  	ctx = context.WithValue(ctx, "local", "main")
   767  	errs := make([]string, 0)
   768  
   769  	a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   770  	addErr := func(errStr string) {
   771  		log.Info("Test error", "err", errStr)
   772  		errs = append(errs, errStr)
   773  	}
   774  
   775  	queryUser := func(q string) string {
   776  		resp, err := api.UI.OnInputRequired(core.UserInputRequest{
   777  			Title:  "Testing",
   778  			Prompt: q,
   779  		})
   780  		if err != nil {
   781  			addErr(err.Error())
   782  		}
   783  		return resp.Text
   784  	}
   785  	expectResponse := func(testcase, question, expect string) {
   786  		if got := queryUser(question); got != expect {
   787  			addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
   788  		}
   789  	}
   790  	expectApprove := func(testcase string, err error) {
   791  		if err == nil || err == accounts.ErrUnknownAccount {
   792  			return
   793  		}
   794  		addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
   795  	}
   796  	expectDeny := func(testcase string, err error) {
   797  		if err == nil || err != core.ErrRequestDenied {
   798  			addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
   799  		}
   800  	}
   801  	var delay = 1 * time.Second
   802  	// Test display of info and error
   803  	{
   804  		api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
   805  		time.Sleep(delay)
   806  		expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
   807  		api.UI.ShowError("If you see this message, enter 'yes' to the next question")
   808  		time.Sleep(delay)
   809  		expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
   810  	}
   811  	{ // Sign data test - clique header
   812  		api.UI.ShowInfo("Please approve the next request for signing a clique header")
   813  		time.Sleep(delay)
   814  		cliqueHeader := types.Header{
   815  			ParentHash:  common.HexToHash("0000H45H"),
   816  			UncleHash:   common.HexToHash("0000H45H"),
   817  			Coinbase:    common.HexToAddress("0000H45H"),
   818  			Root:        common.HexToHash("0000H00H"),
   819  			TxHash:      common.HexToHash("0000H45H"),
   820  			ReceiptHash: common.HexToHash("0000H45H"),
   821  			Difficulty:  big.NewInt(1337),
   822  			Number:      big.NewInt(1337),
   823  			GasLimit:    1338,
   824  			GasUsed:     1338,
   825  			Time:        1338,
   826  			Extra:       []byte("Extra data Extra data Extra data  Extra data  Extra data  Extra data  Extra data Extra data"),
   827  			MixDigest:   common.HexToHash("0x0000H45H"),
   828  		}
   829  		cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
   830  		if err != nil {
   831  			utils.Fatalf("Should not error: %v", err)
   832  		}
   833  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   834  		_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
   835  		expectApprove("signdata - clique header", err)
   836  	}
   837  	{ // Sign data test - typed data
   838  		api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
   839  		time.Sleep(delay)
   840  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   841  		data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}`
   842  		//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
   843  		var typedData core.TypedData
   844  		json.Unmarshal([]byte(data), &typedData)
   845  		_, err := api.SignTypedData(ctx, *addr, typedData)
   846  		expectApprove("sign 712 typed data", err)
   847  	}
   848  	{ // Sign data test - plain text
   849  		api.UI.ShowInfo("Please approve the next request for signing text")
   850  		time.Sleep(delay)
   851  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   852  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   853  		expectApprove("signdata - text", err)
   854  	}
   855  	{ // Sign data test - plain text reject
   856  		api.UI.ShowInfo("Please deny the next request for signing text")
   857  		time.Sleep(delay)
   858  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   859  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   860  		expectDeny("signdata - text", err)
   861  	}
   862  	{ // Sign transaction
   863  
   864  		api.UI.ShowInfo("Please reject next transaction")
   865  		time.Sleep(delay)
   866  		data := hexutil.Bytes([]byte{})
   867  		to := common.NewMixedcaseAddress(a)
   868  		tx := core.SendTxArgs{
   869  			Data:     &data,
   870  			Nonce:    0x1,
   871  			Value:    hexutil.Big(*big.NewInt(6)),
   872  			From:     common.NewMixedcaseAddress(a),
   873  			To:       &to,
   874  			GasPrice: hexutil.Big(*big.NewInt(5)),
   875  			Gas:      1000,
   876  			Input:    nil,
   877  		}
   878  		_, err := api.SignTransaction(ctx, tx, nil)
   879  		expectDeny("signtransaction [1]", err)
   880  		expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no")
   881  	}
   882  	{ // Listing
   883  		api.UI.ShowInfo("Please reject listing-request")
   884  		time.Sleep(delay)
   885  		_, err := api.List(ctx)
   886  		expectDeny("list", err)
   887  	}
   888  	{ // Import
   889  		api.UI.ShowInfo("Please reject new account-request")
   890  		time.Sleep(delay)
   891  		_, err := api.New(ctx)
   892  		expectDeny("newaccount", err)
   893  	}
   894  	{ // Metadata
   895  		api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
   896  		time.Sleep(delay)
   897  		api.List(context.WithValue(ctx, "Origin", "origin.com"))
   898  		expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
   899  	}
   900  
   901  	for _, e := range errs {
   902  		log.Error(e)
   903  	}
   904  	result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n"))
   905  	api.UI.ShowInfo(result)
   906  
   907  }
   908  
   909  // getPassPhrase retrieves the password associated with clef, either fetched
   910  // from a list of preloaded passphrases, or requested interactively from the user.
   911  // TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one.
   912  func getPassPhrase(prompt string, confirmation bool) string {
   913  	fmt.Println(prompt)
   914  	password, err := console.Stdin.PromptPassword("Password: ")
   915  	if err != nil {
   916  		utils.Fatalf("Failed to read password: %v", err)
   917  	}
   918  	if confirmation {
   919  		confirm, err := console.Stdin.PromptPassword("Repeat password: ")
   920  		if err != nil {
   921  			utils.Fatalf("Failed to read password confirmation: %v", err)
   922  		}
   923  		if password != confirm {
   924  			utils.Fatalf("Passwords do not match")
   925  		}
   926  	}
   927  	return password
   928  }
   929  
   930  type encryptedSeedStorage struct {
   931  	Description string              `json:"description"`
   932  	Version     int                 `json:"version"`
   933  	Params      keystore.CryptoJSON `json:"params"`
   934  }
   935  
   936  // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
   937  // to encrypt the master seed
   938  func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
   939  	cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
   940  	if err != nil {
   941  		return nil, err
   942  	}
   943  	return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
   944  }
   945  
   946  // decryptSeed decrypts the master seed
   947  func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
   948  	var encSeed encryptedSeedStorage
   949  	if err := json.Unmarshal(keyjson, &encSeed); err != nil {
   950  		return nil, err
   951  	}
   952  	if encSeed.Version != 1 {
   953  		log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
   954  	}
   955  	seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
   956  	if err != nil {
   957  		return nil, err
   958  	}
   959  	return seed, err
   960  }
   961  
   962  // GenDoc outputs examples of all structures used in json-rpc communication
   963  func GenDoc(ctx *cli.Context) {
   964  
   965  	var (
   966  		a    = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   967  		b    = common.HexToAddress("0x1111111122222222222233333333334444444444")
   968  		meta = core.Metadata{
   969  			Scheme:    "http",
   970  			Local:     "localhost:8545",
   971  			Origin:    "www.malicious.ru",
   972  			Remote:    "localhost:9999",
   973  			UserAgent: "Firefox 3.2",
   974  		}
   975  		output []string
   976  		add    = func(name, desc string, v interface{}) {
   977  			if data, err := json.MarshalIndent(v, "", "  "); err == nil {
   978  				output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data))
   979  			} else {
   980  				log.Error("Error generating output", err)
   981  			}
   982  		}
   983  	)
   984  
   985  	{ // Sign plain text request
   986  		desc := "SignDataRequest contains information about a pending request to sign some data. " +
   987  			"The data to be signed can be of various types, defined by content-type. Clef has done most " +
   988  			"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
   989  			"the user with the contents of the `message`"
   990  		sighash, msg := accounts.TextAndHash([]byte("hello world"))
   991  		messages := []*core.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}}
   992  
   993  		add("SignDataRequest", desc, &core.SignDataRequest{
   994  			Address:     common.NewMixedcaseAddress(a),
   995  			Meta:        meta,
   996  			ContentType: accounts.MimetypeTextPlain,
   997  			Rawdata:     []byte(msg),
   998  			Messages:    messages,
   999  			Hash:        sighash})
  1000  	}
  1001  	{ // Sign plain text response
  1002  		add("SignDataResponse - approve", "Response to SignDataRequest",
  1003  			&core.SignDataResponse{Approved: true})
  1004  		add("SignDataResponse - deny", "Response to SignDataRequest",
  1005  			&core.SignDataResponse{})
  1006  	}
  1007  	{ // Sign transaction request
  1008  		desc := "SignTxRequest contains information about a pending request to sign a transaction. " +
  1009  			"Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " +
  1010  			"messages of various types, that the user should be informed of." +
  1011  			"\n\n" +
  1012  			"As in any request, it's important to consider that the `meta` info also contains untrusted data." +
  1013  			"\n\n" +
  1014  			"The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " +
  1015  			"they must be identical, otherwise an error is generated. " +
  1016  			"However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)"
  1017  
  1018  		data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04})
  1019  		add("SignTxRequest", desc, &core.SignTxRequest{
  1020  			Meta: meta,
  1021  			Callinfo: []core.ValidationInfo{
  1022  				{Typ: "Warning", Message: "Something looks odd, show this message as a warning"},
  1023  				{Typ: "Info", Message: "User should see this as well"},
  1024  			},
  1025  			Transaction: core.SendTxArgs{
  1026  				Data:     &data,
  1027  				Nonce:    0x1,
  1028  				Value:    hexutil.Big(*big.NewInt(6)),
  1029  				From:     common.NewMixedcaseAddress(a),
  1030  				To:       nil,
  1031  				GasPrice: hexutil.Big(*big.NewInt(5)),
  1032  				Gas:      1000,
  1033  				Input:    nil,
  1034  			}})
  1035  	}
  1036  	{ // Sign tx response
  1037  		data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
  1038  		add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
  1039  			", because the UI is free to make modifications to the transaction.",
  1040  			&core.SignTxResponse{Approved: true,
  1041  				Transaction: core.SendTxArgs{
  1042  					Data:     &data,
  1043  					Nonce:    0x4,
  1044  					Value:    hexutil.Big(*big.NewInt(6)),
  1045  					From:     common.NewMixedcaseAddress(a),
  1046  					To:       nil,
  1047  					GasPrice: hexutil.Big(*big.NewInt(5)),
  1048  					Gas:      1000,
  1049  					Input:    nil,
  1050  				}})
  1051  		add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
  1052  			"provide the transaction in return",
  1053  			&core.SignTxResponse{})
  1054  	}
  1055  	{ // WHen a signed tx is ready to go out
  1056  		desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
  1057  			"\n\n" +
  1058  			"This occurs _after_ successful completion of the entire signing procedure, but right before the signed " +
  1059  			"transaction is passed to the external caller. This method (and data) can be used by the UI to signal " +
  1060  			"to the user that the transaction was signed, but it is primarily useful for ruleset implementations." +
  1061  			"\n\n" +
  1062  			"A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " +
  1063  			"interface. By hooking into this methods, the ruleset can maintain track of that count." +
  1064  			"\n\n" +
  1065  			"**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" +
  1066  			" (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " +
  1067  			"\n\n" +
  1068  			"The `OnApproved` method cannot be responded to, it's purely informative"
  1069  
  1070  		rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed")
  1071  		var tx types.Transaction
  1072  		rlp.DecodeBytes(rlpdata, &tx)
  1073  		add("OnApproved - SignTransactionResult", desc, &ethapi.SignTransactionResult{Raw: rlpdata, Tx: &tx})
  1074  
  1075  	}
  1076  	{ // User input
  1077  		add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
  1078  			&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
  1079  		add("UserInputResponse", "Response to UserInputRequest",
  1080  			&core.UserInputResponse{Text: "The textual response from user"})
  1081  	}
  1082  	{ // List request
  1083  		add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+
  1084  			"full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+
  1085  			"who only sees the `address`es. ",
  1086  			&core.ListRequest{
  1087  				Meta: meta,
  1088  				Accounts: []accounts.Account{
  1089  					{Address: a, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}},
  1090  					{Address: b, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
  1091  			})
  1092  
  1093  		add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
  1094  			"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
  1095  			&core.ListResponse{
  1096  				Accounts: []accounts.Account{
  1097  					{
  1098  						Address: common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"),
  1099  						URL:     accounts.URL{Path: ".. ignored .."},
  1100  					},
  1101  					{
  1102  						Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
  1103  					},
  1104  				}})
  1105  	}
  1106  
  1107  	fmt.Println(`## UI Client interface
  1108  
  1109  These data types are defined in the channel between clef and the UI`)
  1110  	for _, elem := range output {
  1111  		fmt.Println(elem)
  1112  	}
  1113  }