github.com/carter-ya/go-ethereum@v0.0.0-20230628080049-d2309be3983b/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 app = flags.NewApp("Manage Ethereum account operations")
   219  
   220  func init() {
   221  	app.Name = "Clef"
   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  	}
   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.String(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.Bool(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 = utils.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 = os.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  
   330  func attestFile(ctx *cli.Context) error {
   331  	if ctx.NArg() < 1 {
   332  		utils.Fatalf("This command requires an argument.")
   333  	}
   334  	if err := initialize(ctx); err != nil {
   335  		return err
   336  	}
   337  
   338  	stretchedKey, err := readMasterKey(ctx, nil)
   339  	if err != nil {
   340  		utils.Fatalf(err.Error())
   341  	}
   342  	configDir := ctx.String(configdirFlag.Name)
   343  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   344  	confKey := crypto.Keccak256([]byte("config"), stretchedKey)
   345  
   346  	// Initialize the encrypted storages
   347  	configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
   348  	val := ctx.Args().First()
   349  	configStorage.Put("ruleset_sha256", val)
   350  	log.Info("Ruleset attestation updated", "sha256", val)
   351  	return nil
   352  }
   353  
   354  func setCredential(ctx *cli.Context) error {
   355  	if ctx.NArg() < 1 {
   356  		utils.Fatalf("This command requires an address to be passed as an argument")
   357  	}
   358  	if err := initialize(ctx); err != nil {
   359  		return err
   360  	}
   361  	addr := ctx.Args().First()
   362  	if !common.IsHexAddress(addr) {
   363  		utils.Fatalf("Invalid address specified: %s", addr)
   364  	}
   365  	address := common.HexToAddress(addr)
   366  	password := utils.GetPassPhrase("Please enter a password to store for this address:", true)
   367  	fmt.Println()
   368  
   369  	stretchedKey, err := readMasterKey(ctx, nil)
   370  	if err != nil {
   371  		utils.Fatalf(err.Error())
   372  	}
   373  	configDir := ctx.String(configdirFlag.Name)
   374  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   375  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   376  
   377  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   378  	pwStorage.Put(address.Hex(), password)
   379  
   380  	log.Info("Credential store updated", "set", address)
   381  	return nil
   382  }
   383  
   384  func removeCredential(ctx *cli.Context) error {
   385  	if ctx.NArg() < 1 {
   386  		utils.Fatalf("This command requires an address to be passed as an argument")
   387  	}
   388  	if err := initialize(ctx); err != nil {
   389  		return err
   390  	}
   391  	addr := ctx.Args().First()
   392  	if !common.IsHexAddress(addr) {
   393  		utils.Fatalf("Invalid address specified: %s", addr)
   394  	}
   395  	address := common.HexToAddress(addr)
   396  
   397  	stretchedKey, err := readMasterKey(ctx, nil)
   398  	if err != nil {
   399  		utils.Fatalf(err.Error())
   400  	}
   401  	configDir := ctx.String(configdirFlag.Name)
   402  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   403  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   404  
   405  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   406  	pwStorage.Del(address.Hex())
   407  
   408  	log.Info("Credential store updated", "unset", address)
   409  	return nil
   410  }
   411  
   412  func newAccount(c *cli.Context) error {
   413  	if err := initialize(c); err != nil {
   414  		return err
   415  	}
   416  	// The newaccount is meant for users using the CLI, since 'real' external
   417  	// UIs can use the UI-api instead. So we'll just use the native CLI UI here.
   418  	var (
   419  		ui                        = core.NewCommandlineUI()
   420  		pwStorage storage.Storage = &storage.NoStorage{}
   421  		ksLoc                     = c.String(keystoreFlag.Name)
   422  		lightKdf                  = c.Bool(utils.LightKDFFlag.Name)
   423  	)
   424  	log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf)
   425  	am := core.StartClefAccountManager(ksLoc, true, lightKdf, "")
   426  	// This gives is us access to the external API
   427  	apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage)
   428  	// This gives us access to the internal API
   429  	internalApi := core.NewUIServerAPI(apiImpl)
   430  	addr, err := internalApi.New(context.Background())
   431  	if err == nil {
   432  		fmt.Printf("Generated account %v\n", addr.String())
   433  	}
   434  	return err
   435  }
   436  
   437  func initialize(c *cli.Context) error {
   438  	// Set up the logger to print everything
   439  	logOutput := os.Stdout
   440  	if c.Bool(stdiouiFlag.Name) {
   441  		logOutput = os.Stderr
   442  		// If using the stdioui, we can't do the 'confirm'-flow
   443  		if !c.Bool(acceptFlag.Name) {
   444  			fmt.Fprint(logOutput, legalWarning)
   445  		}
   446  	} else if !c.Bool(acceptFlag.Name) {
   447  		if !confirm(legalWarning) {
   448  			return fmt.Errorf("aborted by user")
   449  		}
   450  		fmt.Println()
   451  	}
   452  	usecolor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb"
   453  	output := io.Writer(logOutput)
   454  	if usecolor {
   455  		output = colorable.NewColorable(logOutput)
   456  	}
   457  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor))))
   458  
   459  	return nil
   460  }
   461  
   462  // ipcEndpoint resolves an IPC endpoint based on a configured value, taking into
   463  // account the set data folders as well as the designated platform we're currently
   464  // running on.
   465  func ipcEndpoint(ipcPath, datadir string) string {
   466  	// On windows we can only use plain top-level pipes
   467  	if runtime.GOOS == "windows" {
   468  		if strings.HasPrefix(ipcPath, `\\.\pipe\`) {
   469  			return ipcPath
   470  		}
   471  		return `\\.\pipe\` + ipcPath
   472  	}
   473  	// Resolve names into the data directory full paths otherwise
   474  	if filepath.Base(ipcPath) == ipcPath {
   475  		if datadir == "" {
   476  			return filepath.Join(os.TempDir(), ipcPath)
   477  		}
   478  		return filepath.Join(datadir, ipcPath)
   479  	}
   480  	return ipcPath
   481  }
   482  
   483  func signer(c *cli.Context) error {
   484  	// If we have some unrecognized command, bail out
   485  	if c.NArg() > 0 {
   486  		return fmt.Errorf("invalid command: %q", c.Args().First())
   487  	}
   488  	if err := initialize(c); err != nil {
   489  		return err
   490  	}
   491  	var (
   492  		ui core.UIClientAPI
   493  	)
   494  	if c.Bool(stdiouiFlag.Name) {
   495  		log.Info("Using stdin/stdout as UI-channel")
   496  		ui = core.NewStdIOUI()
   497  	} else {
   498  		log.Info("Using CLI as UI-channel")
   499  		ui = core.NewCommandlineUI()
   500  	}
   501  	// 4bytedb data
   502  	fourByteLocal := c.String(customDBFlag.Name)
   503  	db, err := fourbyte.NewWithFile(fourByteLocal)
   504  	if err != nil {
   505  		utils.Fatalf(err.Error())
   506  	}
   507  	embeds, locals := db.Size()
   508  	log.Info("Loaded 4byte database", "embeds", embeds, "locals", locals, "local", fourByteLocal)
   509  
   510  	var (
   511  		api       core.ExternalAPI
   512  		pwStorage storage.Storage = &storage.NoStorage{}
   513  	)
   514  	configDir := c.String(configdirFlag.Name)
   515  	if stretchedKey, err := readMasterKey(c, ui); err != nil {
   516  		log.Warn("Failed to open master, rules disabled", "err", err)
   517  	} else {
   518  		vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   519  
   520  		// Generate domain specific keys
   521  		pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   522  		jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
   523  		confkey := crypto.Keccak256([]byte("config"), stretchedKey)
   524  
   525  		// Initialize the encrypted storages
   526  		pwStorage = storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   527  		jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
   528  		configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
   529  
   530  		// Do we have a rule-file?
   531  		if ruleFile := c.String(ruleFlag.Name); ruleFile != "" {
   532  			ruleJS, err := os.ReadFile(ruleFile)
   533  			if err != nil {
   534  				log.Warn("Could not load rules, disabling", "file", ruleFile, "err", err)
   535  			} else {
   536  				shasum := sha256.Sum256(ruleJS)
   537  				foundShaSum := hex.EncodeToString(shasum[:])
   538  				storedShasum, _ := configStorage.Get("ruleset_sha256")
   539  				if storedShasum != foundShaSum {
   540  					log.Warn("Rule hash not attested, disabling", "hash", foundShaSum, "attested", storedShasum)
   541  				} else {
   542  					// Initialize rules
   543  					ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage)
   544  					if err != nil {
   545  						utils.Fatalf(err.Error())
   546  					}
   547  					ruleEngine.Init(string(ruleJS))
   548  					ui = ruleEngine
   549  					log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
   550  				}
   551  			}
   552  		}
   553  	}
   554  	var (
   555  		chainId  = c.Int64(chainIdFlag.Name)
   556  		ksLoc    = c.String(keystoreFlag.Name)
   557  		lightKdf = c.Bool(utils.LightKDFFlag.Name)
   558  		advanced = c.Bool(advancedMode.Name)
   559  		nousb    = c.Bool(utils.NoUSBFlag.Name)
   560  		scpath   = c.String(utils.SmartCardDaemonPathFlag.Name)
   561  	)
   562  	log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
   563  		"light-kdf", lightKdf, "advanced", advanced)
   564  	am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
   565  	apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
   566  
   567  	// Establish the bidirectional communication, by creating a new UI backend and registering
   568  	// it with the UI.
   569  	ui.RegisterUIServer(core.NewUIServerAPI(apiImpl))
   570  	api = apiImpl
   571  	// Audit logging
   572  	if logfile := c.String(auditLogFlag.Name); logfile != "" {
   573  		api, err = core.NewAuditLogger(logfile, api)
   574  		if err != nil {
   575  			utils.Fatalf(err.Error())
   576  		}
   577  		log.Info("Audit logs configured", "file", logfile)
   578  	}
   579  	// register signer API with server
   580  	var (
   581  		extapiURL = "n/a"
   582  		ipcapiURL = "n/a"
   583  	)
   584  	rpcAPI := []rpc.API{
   585  		{
   586  			Namespace: "account",
   587  			Service:   api,
   588  		},
   589  	}
   590  	if c.Bool(utils.HTTPEnabledFlag.Name) {
   591  		vhosts := utils.SplitAndTrim(c.String(utils.HTTPVirtualHostsFlag.Name))
   592  		cors := utils.SplitAndTrim(c.String(utils.HTTPCORSDomainFlag.Name))
   593  
   594  		srv := rpc.NewServer()
   595  		err := node.RegisterApis(rpcAPI, []string{"account"}, srv)
   596  		if err != nil {
   597  			utils.Fatalf("Could not register API: %w", err)
   598  		}
   599  		handler := node.NewHTTPHandlerStack(srv, cors, vhosts, nil)
   600  
   601  		// set port
   602  		port := c.Int(rpcPortFlag.Name)
   603  
   604  		// start http server
   605  		httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.HTTPListenAddrFlag.Name), port)
   606  		httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler)
   607  		if err != nil {
   608  			utils.Fatalf("Could not start RPC api: %v", err)
   609  		}
   610  		extapiURL = fmt.Sprintf("http://%v/", addr)
   611  		log.Info("HTTP endpoint opened", "url", extapiURL)
   612  
   613  		defer func() {
   614  			// Don't bother imposing a timeout here.
   615  			httpServer.Shutdown(context.Background())
   616  			log.Info("HTTP endpoint closed", "url", extapiURL)
   617  		}()
   618  	}
   619  	if !c.Bool(utils.IPCDisabledFlag.Name) {
   620  		givenPath := c.String(utils.IPCPathFlag.Name)
   621  		ipcapiURL = ipcEndpoint(filepath.Join(givenPath, "clef.ipc"), configDir)
   622  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   623  		if err != nil {
   624  			utils.Fatalf("Could not start IPC api: %v", err)
   625  		}
   626  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   627  		defer func() {
   628  			listener.Close()
   629  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   630  		}()
   631  	}
   632  
   633  	if c.Bool(testFlag.Name) {
   634  		log.Info("Performing UI test")
   635  		go testExternalUI(apiImpl)
   636  	}
   637  	ui.OnSignerStartup(core.StartupInfo{
   638  		Info: map[string]interface{}{
   639  			"intapi_version": core.InternalAPIVersion,
   640  			"extapi_version": core.ExternalAPIVersion,
   641  			"extapi_http":    extapiURL,
   642  			"extapi_ipc":     ipcapiURL,
   643  		},
   644  	})
   645  
   646  	abortChan := make(chan os.Signal, 1)
   647  	signal.Notify(abortChan, os.Interrupt)
   648  
   649  	sig := <-abortChan
   650  	log.Info("Exiting...", "signal", sig)
   651  
   652  	return nil
   653  }
   654  
   655  // DefaultConfigDir is the default config directory to use for the vaults and other
   656  // persistence requirements.
   657  func DefaultConfigDir() string {
   658  	// Try to place the data folder in the user's home dir
   659  	home := flags.HomeDir()
   660  	if home != "" {
   661  		if runtime.GOOS == "darwin" {
   662  			return filepath.Join(home, "Library", "Signer")
   663  		} else if runtime.GOOS == "windows" {
   664  			appdata := os.Getenv("APPDATA")
   665  			if appdata != "" {
   666  				return filepath.Join(appdata, "Signer")
   667  			}
   668  			return filepath.Join(home, "AppData", "Roaming", "Signer")
   669  		}
   670  		return filepath.Join(home, ".clef")
   671  	}
   672  	// As we cannot guess a stable location, return empty and handle later
   673  	return ""
   674  }
   675  
   676  func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
   677  	var (
   678  		file      string
   679  		configDir = ctx.String(configdirFlag.Name)
   680  	)
   681  	if ctx.IsSet(signerSecretFlag.Name) {
   682  		file = ctx.String(signerSecretFlag.Name)
   683  	} else {
   684  		file = filepath.Join(configDir, "masterseed.json")
   685  	}
   686  	if err := checkFile(file); err != nil {
   687  		return nil, err
   688  	}
   689  	cipherKey, err := os.ReadFile(file)
   690  	if err != nil {
   691  		return nil, err
   692  	}
   693  	var password string
   694  	// If ui is not nil, get the password from ui.
   695  	if ui != nil {
   696  		resp, err := ui.OnInputRequired(core.UserInputRequest{
   697  			Title:      "Master Password",
   698  			Prompt:     "Please enter the password to decrypt the master seed",
   699  			IsPassword: true})
   700  		if err != nil {
   701  			return nil, err
   702  		}
   703  		password = resp.Text
   704  	} else {
   705  		password = utils.GetPassPhrase("Decrypt master seed of clef", false)
   706  	}
   707  	masterSeed, err := decryptSeed(cipherKey, password)
   708  	if err != nil {
   709  		return nil, fmt.Errorf("failed to decrypt the master seed of clef")
   710  	}
   711  	if len(masterSeed) < 256 {
   712  		return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
   713  	}
   714  	// Create vault location
   715  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
   716  	err = os.Mkdir(vaultLocation, 0700)
   717  	if err != nil && !os.IsExist(err) {
   718  		return nil, err
   719  	}
   720  	return masterSeed, nil
   721  }
   722  
   723  // checkFile is a convenience function to check if a file
   724  // * exists
   725  // * is mode 0400 (unix only)
   726  func checkFile(filename string) error {
   727  	info, err := os.Stat(filename)
   728  	if err != nil {
   729  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   730  	}
   731  	// Check the unix permission bits
   732  	// However, on windows, we cannot use the unix perm-bits, see
   733  	// https://github.com/ethereum/go-ethereum/issues/20123
   734  	if runtime.GOOS != "windows" && info.Mode().Perm()&0377 != 0 {
   735  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   736  	}
   737  	return nil
   738  }
   739  
   740  // confirm displays a text and asks for user confirmation
   741  func confirm(text string) bool {
   742  	fmt.Print(text)
   743  	fmt.Printf("\nEnter 'ok' to proceed:\n> ")
   744  
   745  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   746  	if err != nil {
   747  		log.Crit("Failed to read user input", "err", err)
   748  	}
   749  	if text := strings.TrimSpace(text); text == "ok" {
   750  		return true
   751  	}
   752  	return false
   753  }
   754  
   755  func testExternalUI(api *core.SignerAPI) {
   756  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   757  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   758  	ctx = context.WithValue(ctx, "local", "main")
   759  	errs := make([]string, 0)
   760  
   761  	a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   762  	addErr := func(errStr string) {
   763  		log.Info("Test error", "err", errStr)
   764  		errs = append(errs, errStr)
   765  	}
   766  
   767  	queryUser := func(q string) string {
   768  		resp, err := api.UI.OnInputRequired(core.UserInputRequest{
   769  			Title:  "Testing",
   770  			Prompt: q,
   771  		})
   772  		if err != nil {
   773  			addErr(err.Error())
   774  		}
   775  		return resp.Text
   776  	}
   777  	expectResponse := func(testcase, question, expect string) {
   778  		if got := queryUser(question); got != expect {
   779  			addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
   780  		}
   781  	}
   782  	expectApprove := func(testcase string, err error) {
   783  		if err == nil || err == accounts.ErrUnknownAccount {
   784  			return
   785  		}
   786  		addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
   787  	}
   788  	expectDeny := func(testcase string, err error) {
   789  		if err == nil || err != core.ErrRequestDenied {
   790  			addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
   791  		}
   792  	}
   793  	var delay = 1 * time.Second
   794  	// Test display of info and error
   795  	{
   796  		api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
   797  		time.Sleep(delay)
   798  		expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
   799  		api.UI.ShowError("If you see this message, enter 'yes' to the next question")
   800  		time.Sleep(delay)
   801  		expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
   802  	}
   803  	{ // Sign data test - clique header
   804  		api.UI.ShowInfo("Please approve the next request for signing a clique header")
   805  		time.Sleep(delay)
   806  		cliqueHeader := types.Header{
   807  			ParentHash:  common.HexToHash("0000H45H"),
   808  			UncleHash:   common.HexToHash("0000H45H"),
   809  			Coinbase:    common.HexToAddress("0000H45H"),
   810  			Root:        common.HexToHash("0000H00H"),
   811  			TxHash:      common.HexToHash("0000H45H"),
   812  			ReceiptHash: common.HexToHash("0000H45H"),
   813  			Difficulty:  big.NewInt(1337),
   814  			Number:      big.NewInt(1337),
   815  			GasLimit:    1338,
   816  			GasUsed:     1338,
   817  			Time:        1338,
   818  			Extra:       []byte("Extra data Extra data Extra data  Extra data  Extra data  Extra data  Extra data Extra data"),
   819  			MixDigest:   common.HexToHash("0x0000H45H"),
   820  		}
   821  		cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
   822  		if err != nil {
   823  			utils.Fatalf("Should not error: %v", err)
   824  		}
   825  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   826  		_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
   827  		expectApprove("signdata - clique header", err)
   828  	}
   829  	{ // Sign data test - typed data
   830  		api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
   831  		time.Sleep(delay)
   832  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   833  		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!"}}`
   834  		//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
   835  		var typedData apitypes.TypedData
   836  		json.Unmarshal([]byte(data), &typedData)
   837  		_, err := api.SignTypedData(ctx, *addr, typedData)
   838  		expectApprove("sign 712 typed data", err)
   839  	}
   840  	{ // Sign data test - plain text
   841  		api.UI.ShowInfo("Please approve the next request for signing text")
   842  		time.Sleep(delay)
   843  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   844  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   845  		expectApprove("signdata - text", err)
   846  	}
   847  	{ // Sign data test - plain text reject
   848  		api.UI.ShowInfo("Please deny the next request for signing text")
   849  		time.Sleep(delay)
   850  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   851  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   852  		expectDeny("signdata - text", err)
   853  	}
   854  	{ // Sign transaction
   855  		api.UI.ShowInfo("Please reject next transaction")
   856  		time.Sleep(delay)
   857  		data := hexutil.Bytes([]byte{})
   858  		to := common.NewMixedcaseAddress(a)
   859  		tx := apitypes.SendTxArgs{
   860  			Data:     &data,
   861  			Nonce:    0x1,
   862  			Value:    hexutil.Big(*big.NewInt(6)),
   863  			From:     common.NewMixedcaseAddress(a),
   864  			To:       &to,
   865  			GasPrice: (*hexutil.Big)(big.NewInt(5)),
   866  			Gas:      1000,
   867  			Input:    nil,
   868  		}
   869  		_, err := api.SignTransaction(ctx, tx, nil)
   870  		expectDeny("signtransaction [1]", err)
   871  		expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no")
   872  	}
   873  	{ // Listing
   874  		api.UI.ShowInfo("Please reject listing-request")
   875  		time.Sleep(delay)
   876  		_, err := api.List(ctx)
   877  		expectDeny("list", err)
   878  	}
   879  	{ // Import
   880  		api.UI.ShowInfo("Please reject new account-request")
   881  		time.Sleep(delay)
   882  		_, err := api.New(ctx)
   883  		expectDeny("newaccount", err)
   884  	}
   885  	{ // Metadata
   886  		api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
   887  		time.Sleep(delay)
   888  		api.List(context.WithValue(ctx, "Origin", "origin.com"))
   889  		expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
   890  	}
   891  
   892  	for _, e := range errs {
   893  		log.Error(e)
   894  	}
   895  	result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n"))
   896  	api.UI.ShowInfo(result)
   897  }
   898  
   899  type encryptedSeedStorage struct {
   900  	Description string              `json:"description"`
   901  	Version     int                 `json:"version"`
   902  	Params      keystore.CryptoJSON `json:"params"`
   903  }
   904  
   905  // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
   906  // to encrypt the master seed
   907  func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
   908  	cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
   909  	if err != nil {
   910  		return nil, err
   911  	}
   912  	return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
   913  }
   914  
   915  // decryptSeed decrypts the master seed
   916  func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
   917  	var encSeed encryptedSeedStorage
   918  	if err := json.Unmarshal(keyjson, &encSeed); err != nil {
   919  		return nil, err
   920  	}
   921  	if encSeed.Version != 1 {
   922  		log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
   923  	}
   924  	seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
   925  	if err != nil {
   926  		return nil, err
   927  	}
   928  	return seed, err
   929  }
   930  
   931  // GenDoc outputs examples of all structures used in json-rpc communication
   932  func GenDoc(ctx *cli.Context) error {
   933  	var (
   934  		a    = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   935  		b    = common.HexToAddress("0x1111111122222222222233333333334444444444")
   936  		meta = core.Metadata{
   937  			Scheme:    "http",
   938  			Local:     "localhost:8545",
   939  			Origin:    "www.malicious.ru",
   940  			Remote:    "localhost:9999",
   941  			UserAgent: "Firefox 3.2",
   942  		}
   943  		output []string
   944  		add    = func(name, desc string, v interface{}) {
   945  			if data, err := json.MarshalIndent(v, "", "  "); err == nil {
   946  				output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data))
   947  			} else {
   948  				log.Error("Error generating output", "err", err)
   949  			}
   950  		}
   951  	)
   952  
   953  	{ // Sign plain text request
   954  		desc := "SignDataRequest contains information about a pending request to sign some data. " +
   955  			"The data to be signed can be of various types, defined by content-type. Clef has done most " +
   956  			"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
   957  			"the user with the contents of the `message`"
   958  		sighash, msg := accounts.TextAndHash([]byte("hello world"))
   959  		messages := []*apitypes.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}}
   960  
   961  		add("SignDataRequest", desc, &core.SignDataRequest{
   962  			Address:     common.NewMixedcaseAddress(a),
   963  			Meta:        meta,
   964  			ContentType: accounts.MimetypeTextPlain,
   965  			Rawdata:     []byte(msg),
   966  			Messages:    messages,
   967  			Hash:        sighash})
   968  	}
   969  	{ // Sign plain text response
   970  		add("SignDataResponse - approve", "Response to SignDataRequest",
   971  			&core.SignDataResponse{Approved: true})
   972  		add("SignDataResponse - deny", "Response to SignDataRequest",
   973  			&core.SignDataResponse{})
   974  	}
   975  	{ // Sign transaction request
   976  		desc := "SignTxRequest contains information about a pending request to sign a transaction. " +
   977  			"Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " +
   978  			"messages of various types, that the user should be informed of." +
   979  			"\n\n" +
   980  			"As in any request, it's important to consider that the `meta` info also contains untrusted data." +
   981  			"\n\n" +
   982  			"The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " +
   983  			"they must be identical, otherwise an error is generated. " +
   984  			"However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)"
   985  
   986  		data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04})
   987  		add("SignTxRequest", desc, &core.SignTxRequest{
   988  			Meta: meta,
   989  			Callinfo: []apitypes.ValidationInfo{
   990  				{Typ: "Warning", Message: "Something looks odd, show this message as a warning"},
   991  				{Typ: "Info", Message: "User should see this as well"},
   992  			},
   993  			Transaction: apitypes.SendTxArgs{
   994  				Data:     &data,
   995  				Nonce:    0x1,
   996  				Value:    hexutil.Big(*big.NewInt(6)),
   997  				From:     common.NewMixedcaseAddress(a),
   998  				To:       nil,
   999  				GasPrice: (*hexutil.Big)(big.NewInt(5)),
  1000  				Gas:      1000,
  1001  				Input:    nil,
  1002  			}})
  1003  	}
  1004  	{ // Sign tx response
  1005  		data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
  1006  		add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
  1007  			", because the UI is free to make modifications to the transaction.",
  1008  			&core.SignTxResponse{Approved: true,
  1009  				Transaction: apitypes.SendTxArgs{
  1010  					Data:     &data,
  1011  					Nonce:    0x4,
  1012  					Value:    hexutil.Big(*big.NewInt(6)),
  1013  					From:     common.NewMixedcaseAddress(a),
  1014  					To:       nil,
  1015  					GasPrice: (*hexutil.Big)(big.NewInt(5)),
  1016  					Gas:      1000,
  1017  					Input:    nil,
  1018  				}})
  1019  		add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
  1020  			"provide the transaction in return",
  1021  			&core.SignTxResponse{})
  1022  	}
  1023  	{ // WHen a signed tx is ready to go out
  1024  		desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
  1025  			"\n\n" +
  1026  			"This occurs _after_ successful completion of the entire signing procedure, but right before the signed " +
  1027  			"transaction is passed to the external caller. This method (and data) can be used by the UI to signal " +
  1028  			"to the user that the transaction was signed, but it is primarily useful for ruleset implementations." +
  1029  			"\n\n" +
  1030  			"A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " +
  1031  			"interface. By hooking into this methods, the ruleset can maintain track of that count." +
  1032  			"\n\n" +
  1033  			"**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" +
  1034  			" (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " +
  1035  			"\n\n" +
  1036  			"The `OnApproved` method cannot be responded to, it's purely informative"
  1037  
  1038  		rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed")
  1039  		var tx types.Transaction
  1040  		tx.UnmarshalBinary(rlpdata)
  1041  		add("OnApproved - SignTransactionResult", desc, &ethapi.SignTransactionResult{Raw: rlpdata, Tx: &tx})
  1042  	}
  1043  	{ // User input
  1044  		add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
  1045  			&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
  1046  		add("UserInputResponse", "Response to UserInputRequest",
  1047  			&core.UserInputResponse{Text: "The textual response from user"})
  1048  	}
  1049  	{ // List request
  1050  		add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+
  1051  			"full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+
  1052  			"who only sees the `address`es. ",
  1053  			&core.ListRequest{
  1054  				Meta: meta,
  1055  				Accounts: []accounts.Account{
  1056  					{Address: a, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}},
  1057  					{Address: b, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
  1058  			})
  1059  
  1060  		add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
  1061  			"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
  1062  			&core.ListResponse{
  1063  				Accounts: []accounts.Account{
  1064  					{
  1065  						Address: common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"),
  1066  						URL:     accounts.URL{Path: ".. ignored .."},
  1067  					},
  1068  					{
  1069  						Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
  1070  					},
  1071  				}})
  1072  	}
  1073  
  1074  	fmt.Println(`## UI Client interface
  1075  
  1076  These data types are defined in the channel between clef and the UI`)
  1077  	for _, elem := range output {
  1078  		fmt.Println(elem)
  1079  	}
  1080  	return nil
  1081  }