github.com/snowblossomcoin/go-ethereum@v1.9.25/cmd/clef/main.go (about)

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