github.com/theQRL/go-zond@v0.2.1/cmd/clef/main.go (about)

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