github.com/bigzoro/my_simplechain@v0.0.0-20240315012955-8ad0a2a29bb9/cmd/clef/main.go (about)

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