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