github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/cmd/clef/main.go (about)

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