github.com/CommerciumBlockchain/go-commercium@v0.0.0-20220709212705-b46438a77516/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/CommerciumBlockchain/go-commercium/accounts"
    39  	"github.com/CommerciumBlockchain/go-commercium/accounts/keystore"
    40  	"github.com/CommerciumBlockchain/go-commercium/cmd/utils"
    41  	"github.com/CommerciumBlockchain/go-commercium/common"
    42  	"github.com/CommerciumBlockchain/go-commercium/common/hexutil"
    43  	"github.com/CommerciumBlockchain/go-commercium/core/types"
    44  	"github.com/CommerciumBlockchain/go-commercium/crypto"
    45  	"github.com/CommerciumBlockchain/go-commercium/internal/ethapi"
    46  	"github.com/CommerciumBlockchain/go-commercium/internal/flags"
    47  	"github.com/CommerciumBlockchain/go-commercium/log"
    48  	"github.com/CommerciumBlockchain/go-commercium/node"
    49  	"github.com/CommerciumBlockchain/go-commercium/params"
    50  	"github.com/CommerciumBlockchain/go-commercium/rlp"
    51  	"github.com/CommerciumBlockchain/go-commercium/rpc"
    52  	"github.com/CommerciumBlockchain/go-commercium/signer/core"
    53  	"github.com/CommerciumBlockchain/go-commercium/signer/fourbyte"
    54  	"github.com/CommerciumBlockchain/go-commercium/signer/rules"
    55  	"github.com/CommerciumBlockchain/go-commercium/signer/storage"
    56  
    57  	"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  			}
   751  			return filepath.Join(home, "AppData", "Roaming", "Signer")
   752  		}
   753  		return filepath.Join(home, ".clef")
   754  	}
   755  	// As we cannot guess a stable location, return empty and handle later
   756  	return ""
   757  }
   758  
   759  func readMasterKey(ctx *cli.Context, ui core.UIClientAPI) ([]byte, error) {
   760  	var (
   761  		file      string
   762  		configDir = ctx.GlobalString(configdirFlag.Name)
   763  	)
   764  	if ctx.GlobalIsSet(signerSecretFlag.Name) {
   765  		file = ctx.GlobalString(signerSecretFlag.Name)
   766  	} else {
   767  		file = filepath.Join(configDir, "masterseed.json")
   768  	}
   769  	if err := checkFile(file); err != nil {
   770  		return nil, err
   771  	}
   772  	cipherKey, err := ioutil.ReadFile(file)
   773  	if err != nil {
   774  		return nil, err
   775  	}
   776  	var password string
   777  	// If ui is not nil, get the password from ui.
   778  	if ui != nil {
   779  		resp, err := ui.OnInputRequired(core.UserInputRequest{
   780  			Title:      "Master Password",
   781  			Prompt:     "Please enter the password to decrypt the master seed",
   782  			IsPassword: true})
   783  		if err != nil {
   784  			return nil, err
   785  		}
   786  		password = resp.Text
   787  	} else {
   788  		password = utils.GetPassPhrase("Decrypt master seed of clef", false)
   789  	}
   790  	masterSeed, err := decryptSeed(cipherKey, password)
   791  	if err != nil {
   792  		return nil, fmt.Errorf("failed to decrypt the master seed of clef")
   793  	}
   794  	if len(masterSeed) < 256 {
   795  		return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
   796  	}
   797  	// Create vault location
   798  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
   799  	err = os.Mkdir(vaultLocation, 0700)
   800  	if err != nil && !os.IsExist(err) {
   801  		return nil, err
   802  	}
   803  	return masterSeed, nil
   804  }
   805  
   806  // checkFile is a convenience function to check if a file
   807  // * exists
   808  // * is mode 0400
   809  func checkFile(filename string) error {
   810  	info, err := os.Stat(filename)
   811  	if err != nil {
   812  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   813  	}
   814  	// Check the unix permission bits
   815  	if info.Mode().Perm()&0377 != 0 {
   816  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   817  	}
   818  	return nil
   819  }
   820  
   821  // confirm displays a text and asks for user confirmation
   822  func confirm(text string) bool {
   823  	fmt.Print(text)
   824  	fmt.Printf("\nEnter 'ok' to proceed:\n> ")
   825  
   826  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   827  	if err != nil {
   828  		log.Crit("Failed to read user input", "err", err)
   829  	}
   830  	if text := strings.TrimSpace(text); text == "ok" {
   831  		return true
   832  	}
   833  	return false
   834  }
   835  
   836  func testExternalUI(api *core.SignerAPI) {
   837  
   838  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   839  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   840  	ctx = context.WithValue(ctx, "local", "main")
   841  	errs := make([]string, 0)
   842  
   843  	a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
   844  	addErr := func(errStr string) {
   845  		log.Info("Test error", "err", errStr)
   846  		errs = append(errs, errStr)
   847  	}
   848  
   849  	queryUser := func(q string) string {
   850  		resp, err := api.UI.OnInputRequired(core.UserInputRequest{
   851  			Title:  "Testing",
   852  			Prompt: q,
   853  		})
   854  		if err != nil {
   855  			addErr(err.Error())
   856  		}
   857  		return resp.Text
   858  	}
   859  	expectResponse := func(testcase, question, expect string) {
   860  		if got := queryUser(question); got != expect {
   861  			addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
   862  		}
   863  	}
   864  	expectApprove := func(testcase string, err error) {
   865  		if err == nil || err == accounts.ErrUnknownAccount {
   866  			return
   867  		}
   868  		addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
   869  	}
   870  	expectDeny := func(testcase string, err error) {
   871  		if err == nil || err != core.ErrRequestDenied {
   872  			addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
   873  		}
   874  	}
   875  	var delay = 1 * time.Second
   876  	// Test display of info and error
   877  	{
   878  		api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
   879  		time.Sleep(delay)
   880  		expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
   881  		api.UI.ShowError("If you see this message, enter 'yes' to the next question")
   882  		time.Sleep(delay)
   883  		expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
   884  	}
   885  	{ // Sign data test - clique header
   886  		api.UI.ShowInfo("Please approve the next request for signing a clique header")
   887  		time.Sleep(delay)
   888  		cliqueHeader := types.Header{
   889  			ParentHash:  common.HexToHash("0000H45H"),
   890  			UncleHash:   common.HexToHash("0000H45H"),
   891  			Coinbase:    common.HexToAddress("0000H45H"),
   892  			Root:        common.HexToHash("0000H00H"),
   893  			TxHash:      common.HexToHash("0000H45H"),
   894  			ReceiptHash: common.HexToHash("0000H45H"),
   895  			Difficulty:  big.NewInt(1337),
   896  			Number:      big.NewInt(1337),
   897  			GasLimit:    1338,
   898  			GasUsed:     1338,
   899  			Time:        1338,
   900  			Extra:       []byte("Extra data Extra data Extra data  Extra data  Extra data  Extra data  Extra data Extra data"),
   901  			MixDigest:   common.HexToHash("0x0000H45H"),
   902  		}
   903  		cliqueRlp, err := rlp.EncodeToBytes(cliqueHeader)
   904  		if err != nil {
   905  			utils.Fatalf("Should not error: %v", err)
   906  		}
   907  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   908  		_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
   909  		expectApprove("signdata - clique header", err)
   910  	}
   911  	{ // Sign data test - typed data
   912  		api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
   913  		time.Sleep(delay)
   914  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   915  		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!"}}`
   916  		//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
   917  		var typedData core.TypedData
   918  		json.Unmarshal([]byte(data), &typedData)
   919  		_, err := api.SignTypedData(ctx, *addr, typedData)
   920  		expectApprove("sign 712 typed data", err)
   921  	}
   922  	{ // Sign data test - plain text
   923  		api.UI.ShowInfo("Please approve the next request for signing text")
   924  		time.Sleep(delay)
   925  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   926  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   927  		expectApprove("signdata - text", err)
   928  	}
   929  	{ // Sign data test - plain text reject
   930  		api.UI.ShowInfo("Please deny the next request for signing text")
   931  		time.Sleep(delay)
   932  		addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
   933  		_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
   934  		expectDeny("signdata - text", err)
   935  	}
   936  	{ // Sign transaction
   937  
   938  		api.UI.ShowInfo("Please reject next transaction")
   939  		time.Sleep(delay)
   940  		data := hexutil.Bytes([]byte{})
   941  		to := common.NewMixedcaseAddress(a)
   942  		tx := core.SendTxArgs{
   943  			Data:     &data,
   944  			Nonce:    0x1,
   945  			Value:    hexutil.Big(*big.NewInt(6)),
   946  			From:     common.NewMixedcaseAddress(a),
   947  			To:       &to,
   948  			GasPrice: hexutil.Big(*big.NewInt(5)),
   949  			Gas:      1000,
   950  			Input:    nil,
   951  		}
   952  		_, err := api.SignTransaction(ctx, tx, nil)
   953  		expectDeny("signtransaction [1]", err)
   954  		expectResponse("signtransaction [2]", "Did you see any warnings for the last transaction? (yes/no)", "no")
   955  	}
   956  	{ // Listing
   957  		api.UI.ShowInfo("Please reject listing-request")
   958  		time.Sleep(delay)
   959  		_, err := api.List(ctx)
   960  		expectDeny("list", err)
   961  	}
   962  	{ // Import
   963  		api.UI.ShowInfo("Please reject new account-request")
   964  		time.Sleep(delay)
   965  		_, err := api.New(ctx)
   966  		expectDeny("newaccount", err)
   967  	}
   968  	{ // Metadata
   969  		api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
   970  		time.Sleep(delay)
   971  		api.List(context.WithValue(ctx, "Origin", "origin.com"))
   972  		expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
   973  	}
   974  
   975  	for _, e := range errs {
   976  		log.Error(e)
   977  	}
   978  	result := fmt.Sprintf("Tests completed. %d errors:\n%s\n", len(errs), strings.Join(errs, "\n"))
   979  	api.UI.ShowInfo(result)
   980  
   981  }
   982  
   983  type encryptedSeedStorage struct {
   984  	Description string              `json:"description"`
   985  	Version     int                 `json:"version"`
   986  	Params      keystore.CryptoJSON `json:"params"`
   987  }
   988  
   989  // encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
   990  // to encrypt the master seed
   991  func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
   992  	cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
   993  	if err != nil {
   994  		return nil, err
   995  	}
   996  	return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
   997  }
   998  
   999  // decryptSeed decrypts the master seed
  1000  func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
  1001  	var encSeed encryptedSeedStorage
  1002  	if err := json.Unmarshal(keyjson, &encSeed); err != nil {
  1003  		return nil, err
  1004  	}
  1005  	if encSeed.Version != 1 {
  1006  		log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
  1007  	}
  1008  	seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
  1009  	if err != nil {
  1010  		return nil, err
  1011  	}
  1012  	return seed, err
  1013  }
  1014  
  1015  // GenDoc outputs examples of all structures used in json-rpc communication
  1016  func GenDoc(ctx *cli.Context) {
  1017  
  1018  	var (
  1019  		a    = common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
  1020  		b    = common.HexToAddress("0x1111111122222222222233333333334444444444")
  1021  		meta = core.Metadata{
  1022  			Scheme:    "http",
  1023  			Local:     "localhost:8545",
  1024  			Origin:    "www.malicious.ru",
  1025  			Remote:    "localhost:9999",
  1026  			UserAgent: "Firefox 3.2",
  1027  		}
  1028  		output []string
  1029  		add    = func(name, desc string, v interface{}) {
  1030  			if data, err := json.MarshalIndent(v, "", "  "); err == nil {
  1031  				output = append(output, fmt.Sprintf("### %s\n\n%s\n\nExample:\n```json\n%s\n```", name, desc, data))
  1032  			} else {
  1033  				log.Error("Error generating output", "err", err)
  1034  			}
  1035  		}
  1036  	)
  1037  
  1038  	{ // Sign plain text request
  1039  		desc := "SignDataRequest contains information about a pending request to sign some data. " +
  1040  			"The data to be signed can be of various types, defined by content-type. Clef has done most " +
  1041  			"of the work in canonicalizing and making sense of the data, and it's up to the UI to present" +
  1042  			"the user with the contents of the `message`"
  1043  		sighash, msg := accounts.TextAndHash([]byte("hello world"))
  1044  		messages := []*core.NameValueType{{Name: "message", Value: msg, Typ: accounts.MimetypeTextPlain}}
  1045  
  1046  		add("SignDataRequest", desc, &core.SignDataRequest{
  1047  			Address:     common.NewMixedcaseAddress(a),
  1048  			Meta:        meta,
  1049  			ContentType: accounts.MimetypeTextPlain,
  1050  			Rawdata:     []byte(msg),
  1051  			Messages:    messages,
  1052  			Hash:        sighash})
  1053  	}
  1054  	{ // Sign plain text response
  1055  		add("SignDataResponse - approve", "Response to SignDataRequest",
  1056  			&core.SignDataResponse{Approved: true})
  1057  		add("SignDataResponse - deny", "Response to SignDataRequest",
  1058  			&core.SignDataResponse{})
  1059  	}
  1060  	{ // Sign transaction request
  1061  		desc := "SignTxRequest contains information about a pending request to sign a transaction. " +
  1062  			"Aside from the transaction itself, there is also a `call_info`-struct. That struct contains " +
  1063  			"messages of various types, that the user should be informed of." +
  1064  			"\n\n" +
  1065  			"As in any request, it's important to consider that the `meta` info also contains untrusted data." +
  1066  			"\n\n" +
  1067  			"The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, " +
  1068  			"they must be identical, otherwise an error is generated. " +
  1069  			"However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)"
  1070  
  1071  		data := hexutil.Bytes([]byte{0x01, 0x02, 0x03, 0x04})
  1072  		add("SignTxRequest", desc, &core.SignTxRequest{
  1073  			Meta: meta,
  1074  			Callinfo: []core.ValidationInfo{
  1075  				{Typ: "Warning", Message: "Something looks odd, show this message as a warning"},
  1076  				{Typ: "Info", Message: "User should see this as well"},
  1077  			},
  1078  			Transaction: core.SendTxArgs{
  1079  				Data:     &data,
  1080  				Nonce:    0x1,
  1081  				Value:    hexutil.Big(*big.NewInt(6)),
  1082  				From:     common.NewMixedcaseAddress(a),
  1083  				To:       nil,
  1084  				GasPrice: hexutil.Big(*big.NewInt(5)),
  1085  				Gas:      1000,
  1086  				Input:    nil,
  1087  			}})
  1088  	}
  1089  	{ // Sign tx response
  1090  		data := hexutil.Bytes([]byte{0x04, 0x03, 0x02, 0x01})
  1091  		add("SignTxResponse - approve", "Response to request to sign a transaction. This response needs to contain the `transaction`"+
  1092  			", because the UI is free to make modifications to the transaction.",
  1093  			&core.SignTxResponse{Approved: true,
  1094  				Transaction: core.SendTxArgs{
  1095  					Data:     &data,
  1096  					Nonce:    0x4,
  1097  					Value:    hexutil.Big(*big.NewInt(6)),
  1098  					From:     common.NewMixedcaseAddress(a),
  1099  					To:       nil,
  1100  					GasPrice: hexutil.Big(*big.NewInt(5)),
  1101  					Gas:      1000,
  1102  					Input:    nil,
  1103  				}})
  1104  		add("SignTxResponse - deny", "Response to SignTxRequest. When denying a request, there's no need to "+
  1105  			"provide the transaction in return",
  1106  			&core.SignTxResponse{})
  1107  	}
  1108  	{ // WHen a signed tx is ready to go out
  1109  		desc := "SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`" +
  1110  			"\n\n" +
  1111  			"This occurs _after_ successful completion of the entire signing procedure, but right before the signed " +
  1112  			"transaction is passed to the external caller. This method (and data) can be used by the UI to signal " +
  1113  			"to the user that the transaction was signed, but it is primarily useful for ruleset implementations." +
  1114  			"\n\n" +
  1115  			"A ruleset that implements a rate limitation needs to know what transactions are sent out to the external " +
  1116  			"interface. By hooking into this methods, the ruleset can maintain track of that count." +
  1117  			"\n\n" +
  1118  			"**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time" +
  1119  			" (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content. " +
  1120  			"\n\n" +
  1121  			"The `OnApproved` method cannot be responded to, it's purely informative"
  1122  
  1123  		rlpdata := common.FromHex("0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed")
  1124  		var tx types.Transaction
  1125  		rlp.DecodeBytes(rlpdata, &tx)
  1126  		add("OnApproved - SignTransactionResult", desc, &ethapi.SignTransactionResult{Raw: rlpdata, Tx: &tx})
  1127  
  1128  	}
  1129  	{ // User input
  1130  		add("UserInputRequest", "Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)",
  1131  			&core.UserInputRequest{IsPassword: true, Title: "The title here", Prompt: "The question to ask the user"})
  1132  		add("UserInputResponse", "Response to UserInputRequest",
  1133  			&core.UserInputResponse{Text: "The textual response from user"})
  1134  	}
  1135  	{ // List request
  1136  		add("ListRequest", "Sent when a request has been made to list addresses. The UI is provided with the "+
  1137  			"full `account`s, including local directory names. Note: this information is not passed back to the external caller, "+
  1138  			"who only sees the `address`es. ",
  1139  			&core.ListRequest{
  1140  				Meta: meta,
  1141  				Accounts: []accounts.Account{
  1142  					{Address: a, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/a"}},
  1143  					{Address: b, URL: accounts.URL{Scheme: "keystore", Path: "/path/to/keyfile/b"}}},
  1144  			})
  1145  
  1146  		add("ListResponse", "Response to list request. The response contains a list of all addresses to show to the caller. "+
  1147  			"Note: the UI is free to respond with any address the caller, regardless of whether it exists or not",
  1148  			&core.ListResponse{
  1149  				Accounts: []accounts.Account{
  1150  					{
  1151  						Address: common.HexToAddress("0xcowbeef000000cowbeef00000000000000000c0w"),
  1152  						URL:     accounts.URL{Path: ".. ignored .."},
  1153  					},
  1154  					{
  1155  						Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"),
  1156  					},
  1157  				}})
  1158  	}
  1159  
  1160  	fmt.Println(`## UI Client interface
  1161  
  1162  These data types are defined in the channel between clef and the UI`)
  1163  	for _, elem := range output {
  1164  		fmt.Println(elem)
  1165  	}
  1166  }