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