github.com/Debrief-BC/go-debrief@v0.0.0-20200420203408-0c26ca968123/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/Debrief-BC/go-debrief/accounts"
    39  	"github.com/Debrief-BC/go-debrief/accounts/keystore"
    40  	"github.com/Debrief-BC/go-debrief/cmd/utils"
    41  	"github.com/Debrief-BC/go-debrief/common"
    42  	"github.com/Debrief-BC/go-debrief/common/hexutil"
    43  	"github.com/Debrief-BC/go-debrief/console"
    44  	"github.com/Debrief-BC/go-debrief/core/types"
    45  	"github.com/Debrief-BC/go-debrief/crypto"
    46  	"github.com/Debrief-BC/go-debrief/internal/ethapi"
    47  	"github.com/Debrief-BC/go-debrief/log"
    48  	"github.com/Debrief-BC/go-debrief/node"
    49  	"github.com/Debrief-BC/go-debrief/params"
    50  	"github.com/Debrief-BC/go-debrief/rlp"
    51  	"github.com/Debrief-BC/go-debrief/rpc"
    52  	"github.com/Debrief-BC/go-debrief/signer/core"
    53  	"github.com/Debrief-BC/go-debrief/signer/fourbyte"
    54  	"github.com/Debrief-BC/go-debrief/signer/rules"
    55  	"github.com/Debrief-BC/go-debrief/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  		// start http server
   587  		httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
   588  		listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
   589  		if err != nil {
   590  			utils.Fatalf("Could not start RPC api: %v", err)
   591  		}
   592  		extapiURL = fmt.Sprintf("http://%v/", listener.Addr())
   593  		log.Info("HTTP endpoint opened", "url", extapiURL)
   594  
   595  		defer func() {
   596  			listener.Close()
   597  			log.Info("HTTP endpoint closed", "url", extapiURL)
   598  		}()
   599  	}
   600  	if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
   601  		givenPath := c.GlobalString(utils.IPCPathFlag.Name)
   602  		ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir)
   603  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   604  		if err != nil {
   605  			utils.Fatalf("Could not start IPC api: %v", err)
   606  		}
   607  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   608  		defer func() {
   609  			listener.Close()
   610  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   611  		}()
   612  	}
   613  
   614  	if c.GlobalBool(testFlag.Name) {
   615  		log.Info("Performing UI test")
   616  		go testExternalUI(apiImpl)
   617  	}
   618  	ui.OnSignerStartup(core.StartupInfo{
   619  		Info: map[string]interface{}{
   620  			"intapi_version": core.InternalAPIVersion,
   621  			"extapi_version": core.ExternalAPIVersion,
   622  			"extapi_http":    extapiURL,
   623  			"extapi_ipc":     ipcapiURL,
   624  		},
   625  	})
   626  
   627  	abortChan := make(chan os.Signal, 1)
   628  	signal.Notify(abortChan, os.Interrupt)
   629  
   630  	sig := <-abortChan
   631  	log.Info("Exiting...", "signal", sig)
   632  
   633  	return nil
   634  }
   635  
   636  // splitAndTrim splits input separated by a comma
   637  // and trims excessive white space from the substrings.
   638  func splitAndTrim(input string) []string {
   639  	result := strings.Split(input, ",")
   640  	for i, r := range result {
   641  		result[i] = strings.TrimSpace(r)
   642  	}
   643  	return result
   644  }
   645  
   646  // DefaultConfigDir is the default config directory to use for the vaults and other
   647  // persistence requirements.
   648  func DefaultConfigDir() string {
   649  	// Try to place the data folder in the user's home dir
   650  	home := homeDir()
   651  	if home != "" {
   652  		if runtime.GOOS == "darwin" {
   653  			return filepath.Join(home, "Library", "Signer")
   654  		} else if runtime.GOOS == "windows" {
   655  			appdata := os.Getenv("APPDATA")
   656  			if appdata != "" {
   657  				return filepath.Join(appdata, "Signer")
   658  			} else {
   659  				return filepath.Join(home, "AppData", "Roaming", "Signer")
   660  			}
   661  		} else {
   662  			return filepath.Join(home, ".clef")
   663  		}
   664  	}
   665  	// As we cannot guess a stable location, return empty and handle later
   666  	return ""
   667  }
   668  
   669  func homeDir() string {
   670  	if home := os.Getenv("HOME"); home != "" {
   671  		return home
   672  	}
   673  	if usr, err := user.Current(); err == nil {
   674  		return usr.HomeDir
   675  	}
   676  	return ""
   677  }
   678  func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
   679  	var (
   680  		file      string
   681  		configDir = ctx.GlobalString(configdirFlag.Name)
   682  	)
   683  	if ctx.GlobalIsSet(signerSecretFlag.Name) {
   684  		file = ctx.GlobalString(signerSecretFlag.Name)
   685  	} else {
   686  		file = filepath.Join(configDir, "masterseed.json")
   687  	}
   688  	if err := checkFile(file); err != nil {
   689  		return nil, err
   690  	}
   691  	cipherKey, err := ioutil.ReadFile(file)
   692  	if err != nil {
   693  		return nil, err
   694  	}
   695  	var password string
   696  	// If ui is not nil, get the password from ui.
   697  	if ui != nil {
   698  		resp, err := ui.OnInputRequired(core.UserInputRequest{
   699  			Title:      "Master Password",
   700  			Prompt:     "Please enter the password to decrypt the master seed",
   701  			IsPassword: true})
   702  		if err != nil {
   703  			return nil, err
   704  		}
   705  		password = resp.Text
   706  	} else {
   707  		password = getPassPhrase("Decrypt master seed of clef", false)
   708  	}
   709  	masterSeed, err := decryptSeed(cipherKey, password)
   710  	if err != nil {
   711  		return nil, fmt.Errorf("failed to decrypt the master seed of clef")
   712  	}
   713  	if len(masterSeed) < 256 {
   714  		return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
   715  	}
   716  	// Create vault location
   717  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
   718  	err = os.Mkdir(vaultLocation, 0700)
   719  	if err != nil && !os.IsExist(err) {
   720  		return nil, err
   721  	}
   722  	return masterSeed, nil
   723  }
   724  
   725  // checkFile is a convenience function to check if a file
   726  // * exists
   727  // * is mode 0400
   728  func checkFile(filename string) error {
   729  	info, err := os.Stat(filename)
   730  	if err != nil {
   731  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   732  	}
   733  	// Check the unix permission bits
   734  	if info.Mode().Perm()&0377 != 0 {
   735  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   736  	}
   737  	return nil
   738  }
   739  
   740  // confirm displays a text and asks for user confirmation
   741  func confirm(text string) bool {
   742  	fmt.Print(text)
   743  	fmt.Printf("\nEnter 'ok' to proceed:\n> ")
   744  
   745  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   746  	if err != nil {
   747  		log.Crit("Failed to read user input", "err", err)
   748  	}
   749  	if text := strings.TrimSpace(text); text == "ok" {
   750  		return true
   751  	}
   752  	return false
   753  }
   754  
   755  func testExternalUI(api *core.SignerAPI) {
   756  
   757  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   758  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   759  	ctx = context.WithValue(ctx, "local", "main")
   760  	errs := make([]string, 0)
   761  
   762  	a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   763  	addErr := func(errStr string) {
   764  		log.Info("Test error", "err", errStr)
   765  		errs = append(errs, errStr)
   766  	}
   767  
   768  	queryUser := func(q string) string {
   769  		resp, err := api.UI.OnInputRequired(core.UserInputRequest{
   770  			Title:  "Testing",
   771  			Prompt: q,
   772  		})
   773  		if err != nil {
   774  			addErr(err.Error())
   775  		}
   776  		return resp.Text
   777  	}
   778  	expectResponse := func(testcase, question, expect string) {
   779  		if got := queryUser(question); got != expect {
   780  			addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
   781  		}
   782  	}
   783  	expectApprove := func(testcase string, err error) {
   784  		if err == nil || err == accounts.ErrUnknownAccount {
   785  			return
   786  		}
   787  		addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
   788  	}
   789  	expectDeny := func(testcase string, err error) {
   790  		if err == nil || err != core.ErrRequestDenied {
   791  			addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
   792  		}
   793  	}
   794  	var delay = 1 * time.Second
   795  	// Test display of info and error
   796  	{
   797  		api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
   798  		time.Sleep(delay)
   799  		expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
   800  		api.UI.ShowError("If you see this message, enter 'yes' to the next question")
   801  		time.Sleep(delay)
   802  		expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
   803  	}
   804  	{ // Sign data test - clique header
   805  		api.UI.ShowInfo("Please approve the next request for signing a clique header")
   806  		time.Sleep(delay)
   807  		cliqueHeader := types.Header{
   808  			ParentHash:  common.HexToHash("0000H45H"),
   809  			UncleHash:   common.HexToHash("0000H45H"),
   810  			Coinbase:    common.HexToAddress("0000H45H"),
   811  			Root:        common.HexToHash("0000H00H"),
   812  			TxHash:      common.HexToHash("0000H45H"),
   813  			ReceiptHash: common.HexToHash("0000H45H"),
   814  			Difficulty:  big.NewInt(1337),
   815  			Number:      big.NewInt(1337),
   816  			GasLimit:    1338,
   817  			GasUsed:     1338,
   818  			Time:        1338,
   819  			Extra:       []byte("Extra data Extra data Extra data  Extra data  Extra data  Extra data  Extra data Extra data"),
   820  			MixDigest:   common.HexToHash("0x0000H45H"),
   821  		}
   822  		cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
   823  		if err != nil {
   824  			utils.Fatalf("Should not error: %v", err)
   825  		}
   826  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   827  		_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
   828  		expectApprove("signdata - clique header", err)
   829  	}
   830  	{ // Sign data test - typed data
   831  		api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
   832  		time.Sleep(delay)
   833  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   834  		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!"}}`
   835  		//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
   836  		var typedData core.TypedData
   837  		json.Unmarshal([]byte(data), &typedData)
   838  		_, err := api.SignTypedData(ctx, *addr, typedData)
   839  		expectApprove("sign 712 typed data", err)
   840  	}
   841  	{ // Sign data test - plain text
   842  		api.UI.ShowInfo("Please approve the next request for signing text")
   843  		time.Sleep(delay)
   844  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   845  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   846  		expectApprove("signdata - text", err)
   847  	}
   848  	{ // Sign data test - plain text reject
   849  		api.UI.ShowInfo("Please deny 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  		expectDeny("signdata - text", err)
   854  	}
   855  	{ // Sign transaction
   856  
   857  		api.UI.ShowInfo("Please reject next transaction")
   858  		time.Sleep(delay)
   859  		data := hexutil.Bytes([]byte{})
   860  		to := common.NewMixedcaseAddress(a)
   861  		tx := core.SendTxArgs{
   862  			Data:     &data,
   863  			Nonce:    0x1,
   864  			Value:    hexutil.Big(*big.NewInt(6)),
   865  			From:     common.NewMixedcaseAddress(a),
   866  			To:       &to,
   867  			GasPrice: hexutil.Big(*big.NewInt(5)),
   868  			Gas:      1000,
   869  			Input:    nil,
   870  		}
   871  		_, err := api.SignTransaction(ctx, tx, nil)
   872  		expectDeny("signtransaction [1]", err)
   873  		expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no")
   874  	}
   875  	{ // Listing
   876  		api.UI.ShowInfo("Please reject listing-request")
   877  		time.Sleep(delay)
   878  		_, err := api.List(ctx)
   879  		expectDeny("list", err)
   880  	}
   881  	{ // Import
   882  		api.UI.ShowInfo("Please reject new account-request")
   883  		time.Sleep(delay)
   884  		_, err := api.New(ctx)
   885  		expectDeny("newaccount", err)
   886  	}
   887  	{ // Metadata
   888  		api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
   889  		time.Sleep(delay)
   890  		api.List(context.WithValue(ctx, "Origin", "origin.com"))
   891  		expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
   892  	}
   893  
   894  	for _, e := range errs {
   895  		log.Error(e)
   896  	}
   897  	result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n"))
   898  	api.UI.ShowInfo(result)
   899  
   900  }
   901  
   902  // getPassPhrase retrieves the password associated with clef, either fetched
   903  // from a list of preloaded passphrases, or requested interactively from the user.
   904  // TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one.
   905  func getPassPhrase(prompt string, confirmation bool) string {
   906  	fmt.Println(prompt)
   907  	password, err := console.Stdin.PromptPassword("Password: ")
   908  	if err != nil {
   909  		utils.Fatalf("Failed to read password: %v", err)
   910  	}
   911  	if confirmation {
   912  		confirm, err := console.Stdin.PromptPassword("Repeat password: ")
   913  		if err != nil {
   914  			utils.Fatalf("Failed to read password confirmation: %v", err)
   915  		}
   916  		if password != confirm {
   917  			utils.Fatalf("Passwords do not match")
   918  		}
   919  	}
   920  	return password
   921  }
   922  
   923  type encryptedSeedStorage struct {
   924  	Description string              `json:"description"`
   925  	Version     int                 `json:"version"`
   926  	Params      keystore.CryptoJSON `json:"params"`
   927  }
   928  
   929  // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
   930  // to encrypt the master seed
   931  func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
   932  	cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
   933  	if err != nil {
   934  		return nil, err
   935  	}
   936  	return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
   937  }
   938  
   939  // decryptSeed decrypts the master seed
   940  func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
   941  	var encSeed encryptedSeedStorage
   942  	if err := json.Unmarshal(keyjson, &encSeed); err != nil {
   943  		return nil, err
   944  	}
   945  	if encSeed.Version != 1 {
   946  		log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
   947  	}
   948  	seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
   949  	if err != nil {
   950  		return nil, err
   951  	}
   952  	return seed, err
   953  }
   954  
   955  // GenDoc outputs examples of all structures used in json-rpc communication
   956  func GenDoc(ctx *cli.Context) {
   957  
   958  	var (
   959  		a    = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   960  		b    = common.HexToAddress("0x1111111122222222222233333333334444444444")
   961  		meta = core.Metadata{
   962  			Scheme:    "http",
   963  			Local:     "localhost:8545",
   964  			Origin:    "www.malicious.ru",
   965  			Remote:    "localhost:9999",
   966  			UserAgent: "Firefox 3.2",
   967  		}
   968  		output []string
   969  		add    = func(name, desc string, v interface{}) {
   970  			if data, err := json.MarshalIndent(v, "", "  "); err == nil {
   971  				output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data))
   972  			} else {
   973  				log.Error("Error generating output", err)
   974  			}
   975  		}
   976  	)
   977  
   978  	{ // Sign plain text request
   979  		desc := "SignDataRequest contains information about a pending request to sign some data. " +
   980  			"The data to be signed can be of various types, defined by content-type. Clef has done most " +
   981  			"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
   982  			"the user with the contents of the `message`"
   983  		sighash, msg := accounts.TextAndHash([]byte("hello world"))
   984  		messages := []*core.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}}
   985  
   986  		add("SignDataRequest", desc, &core.SignDataRequest{
   987  			Address:     common.NewMixedcaseAddress(a),
   988  			Meta:        meta,
   989  			ContentType: accounts.MimetypeTextPlain,
   990  			Rawdata:     []byte(msg),
   991  			Messages:    messages,
   992  			Hash:        sighash})
   993  	}
   994  	{ // Sign plain text response
   995  		add("SignDataResponse - approve", "Response to SignDataRequest",
   996  			&core.SignDataResponse{Approved: true})
   997  		add("SignDataResponse - deny", "Response to SignDataRequest",
   998  			&core.SignDataResponse{})
   999  	}
  1000  	{ // Sign transaction request
  1001  		desc := "SignTxRequest contains information about a pending request to sign a transaction. " +
  1002  			"Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " +
  1003  			"messages of various types, that the user should be informed of." +
  1004  			"\n\n" +
  1005  			"As in any request, it's important to consider that the `meta` info also contains untrusted data." +
  1006  			"\n\n" +
  1007  			"The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " +
  1008  			"they must be identical, otherwise an error is generated. " +
  1009  			"However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)"
  1010  
  1011  		data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04})
  1012  		add("SignTxRequest", desc, &core.SignTxRequest{
  1013  			Meta: meta,
  1014  			Callinfo: []core.ValidationInfo{
  1015  				{Typ: "Warning", Message: "Something looks odd, show this message as a warning"},
  1016  				{Typ: "Info", Message: "User should see this as well"},
  1017  			},
  1018  			Transaction: core.SendTxArgs{
  1019  				Data:     &data,
  1020  				Nonce:    0x1,
  1021  				Value:    hexutil.Big(*big.NewInt(6)),
  1022  				From:     common.NewMixedcaseAddress(a),
  1023  				To:       nil,
  1024  				GasPrice: hexutil.Big(*big.NewInt(5)),
  1025  				Gas:      1000,
  1026  				Input:    nil,
  1027  			}})
  1028  	}
  1029  	{ // Sign tx response
  1030  		data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
  1031  		add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
  1032  			", because the UI is free to make modifications to the transaction.",
  1033  			&core.SignTxResponse{Approved: true,
  1034  				Transaction: core.SendTxArgs{
  1035  					Data:     &data,
  1036  					Nonce:    0x4,
  1037  					Value:    hexutil.Big(*big.NewInt(6)),
  1038  					From:     common.NewMixedcaseAddress(a),
  1039  					To:       nil,
  1040  					GasPrice: hexutil.Big(*big.NewInt(5)),
  1041  					Gas:      1000,
  1042  					Input:    nil,
  1043  				}})
  1044  		add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
  1045  			"provide the transaction in return",
  1046  			&core.SignTxResponse{})
  1047  	}
  1048  	{ // WHen a signed tx is ready to go out
  1049  		desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
  1050  			"\n\n" +
  1051  			"This occurs _after_ successful completion of the entire signing procedure, but right before the signed " +
  1052  			"transaction is passed to the external caller. This method (and data) can be used by the UI to signal " +
  1053  			"to the user that the transaction was signed, but it is primarily useful for ruleset implementations." +
  1054  			"\n\n" +
  1055  			"A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " +
  1056  			"interface. By hooking into this methods, the ruleset can maintain track of that count." +
  1057  			"\n\n" +
  1058  			"**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" +
  1059  			" (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " +
  1060  			"\n\n" +
  1061  			"The `OnApproved` method cannot be responded to, it's purely informative"
  1062  
  1063  		rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed")
  1064  		var tx types.Transaction
  1065  		rlp.DecodeBytes(rlpdata, &tx)
  1066  		add("OnApproved - SignTransactionResult", desc, &ethapi.SignTransactionResult{Raw: rlpdata, Tx: &tx})
  1067  
  1068  	}
  1069  	{ // User input
  1070  		add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
  1071  			&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
  1072  		add("UserInputResponse", "Response to UserInputRequest",
  1073  			&core.UserInputResponse{Text: "The textual response from user"})
  1074  	}
  1075  	{ // List request
  1076  		add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+
  1077  			"full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+
  1078  			"who only sees the `address`es. ",
  1079  			&core.ListRequest{
  1080  				Meta: meta,
  1081  				Accounts: []accounts.Account{
  1082  					{Address: a, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}},
  1083  					{Address: b, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
  1084  			})
  1085  
  1086  		add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
  1087  			"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
  1088  			&core.ListResponse{
  1089  				Accounts: []accounts.Account{
  1090  					{
  1091  						Address: common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"),
  1092  						URL:     accounts.URL{Path: ".. ignored .."},
  1093  					},
  1094  					{
  1095  						Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
  1096  					},
  1097  				}})
  1098  	}
  1099  
  1100  	fmt.Println(`## UI Client interface
  1101  
  1102  These data types are defined in the channel between clef and the UI`)
  1103  	for _, elem := range output {
  1104  		fmt.Println(elem)
  1105  	}
  1106  }