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