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