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