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