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