github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/cmd/clef/main.go (about)

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