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