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