github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/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  // signer is a utility that can be used so sign transactions and
    18  // arbitrary data.
    19  package main
    20  
    21  import (
    22  	"bufio"
    23  	"context"
    24  	"crypto/rand"
    25  	"crypto/sha256"
    26  	"encoding/hex"
    27  	"encoding/json"
    28  	"fmt"
    29  	"io"
    30  	"io/ioutil"
    31  	"os"
    32  	"os/signal"
    33  	"os/user"
    34  	"path/filepath"
    35  	"runtime"
    36  	"strings"
    37  
    38  	"github.com/vntchain/go-vnt/cmd/utils"
    39  	"github.com/vntchain/go-vnt/common"
    40  	"github.com/vntchain/go-vnt/crypto"
    41  	"github.com/vntchain/go-vnt/log"
    42  	"github.com/vntchain/go-vnt/node"
    43  	"github.com/vntchain/go-vnt/rpc"
    44  	"github.com/vntchain/go-vnt/signer/core"
    45  	"github.com/vntchain/go-vnt/signer/rules"
    46  	"github.com/vntchain/go-vnt/signer/storage"
    47  	"gopkg.in/urfave/cli.v1"
    48  )
    49  
    50  // ExternalAPIVersion -- see extapi_changelog.md
    51  const ExternalAPIVersion = "2.0.0"
    52  
    53  // InternalAPIVersion -- see intapi_changelog.md
    54  const InternalAPIVersion = "2.0.0"
    55  
    56  const legalWarning = `
    57  WARNING! 
    58  
    59  Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there
    60  are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software
    61  unless you agree to take full responsibility for doing so, and know what you are doing. 
    62  
    63  TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! 
    64  
    65  `
    66  
    67  var (
    68  	logLevelFlag = cli.IntFlag{
    69  		Name:  "loglevel",
    70  		Value: 4,
    71  		Usage: "log level to emit to the screen",
    72  	}
    73  	keystoreFlag = cli.StringFlag{
    74  		Name:  "keystore",
    75  		Value: filepath.Join(node.DefaultDataDir(), "keystore"),
    76  		Usage: "Directory for the keystore",
    77  	}
    78  	configdirFlag = cli.StringFlag{
    79  		Name:  "configdir",
    80  		Value: DefaultConfigDir(),
    81  		Usage: "Directory for Clef configuration",
    82  	}
    83  	rpcPortFlag = cli.IntFlag{
    84  		Name:  "rpcport",
    85  		Usage: "HTTP-RPC server listening port",
    86  		Value: node.DefaultHTTPPort + 5,
    87  	}
    88  	signerSecretFlag = cli.StringFlag{
    89  		Name:  "signersecret",
    90  		Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash",
    91  	}
    92  	dBFlag = cli.StringFlag{
    93  		Name:  "4bytedb",
    94  		Usage: "File containing 4byte-identifiers",
    95  		Value: "./4byte.json",
    96  	}
    97  	customDBFlag = cli.StringFlag{
    98  		Name:  "4bytedb-custom",
    99  		Usage: "File used for writing new 4byte-identifiers submitted via API",
   100  		Value: "./4byte-custom.json",
   101  	}
   102  	auditLogFlag = cli.StringFlag{
   103  		Name:  "auditlog",
   104  		Usage: "File used to emit audit logs. Set to \"\" to disable",
   105  		Value: "audit.log",
   106  	}
   107  	ruleFlag = cli.StringFlag{
   108  		Name:  "rules",
   109  		Usage: "Enable rule-engine",
   110  		Value: "rules.json",
   111  	}
   112  	stdiouiFlag = cli.BoolFlag{
   113  		Name: "stdio-ui",
   114  		Usage: "Use STDIN/STDOUT as a channel for an external UI. " +
   115  			"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
   116  			"interface, and can be used when Clef is started by an external process.",
   117  	}
   118  	testFlag = cli.BoolFlag{
   119  		Name:  "stdio-ui-test",
   120  		Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.",
   121  	}
   122  	app         = cli.NewApp()
   123  	initCommand = cli.Command{
   124  		Action:    utils.MigrateFlags(initializeSecrets),
   125  		Name:      "init",
   126  		Usage:     "Initialize the signer, generate secret storage",
   127  		ArgsUsage: "",
   128  		Flags: []cli.Flag{
   129  			logLevelFlag,
   130  			configdirFlag,
   131  		},
   132  		Description: `
   133  The init command generates a master seed which Clef can use to store credentials and data needed for 
   134  the rule-engine to work.`,
   135  	}
   136  	attestCommand = cli.Command{
   137  		Action:    utils.MigrateFlags(attestFile),
   138  		Name:      "attest",
   139  		Usage:     "Attest that a js-file is to be used",
   140  		ArgsUsage: "<sha256sum>",
   141  		Flags: []cli.Flag{
   142  			logLevelFlag,
   143  			configdirFlag,
   144  			signerSecretFlag,
   145  		},
   146  		Description: `
   147  The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of 
   148  incoming requests. 
   149  
   150  Whenever you make an edit to the rule file, you need to use attestation to tell 
   151  Clef that the file is 'safe' to execute.`,
   152  	}
   153  
   154  	addCredentialCommand = cli.Command{
   155  		Action:    utils.MigrateFlags(addCredential),
   156  		Name:      "addpw",
   157  		Usage:     "Store a credential for a keystore file",
   158  		ArgsUsage: "<address> <password>",
   159  		Flags: []cli.Flag{
   160  			logLevelFlag,
   161  			configdirFlag,
   162  			signerSecretFlag,
   163  		},
   164  		Description: `
   165  The addpw command stores a password for a given address (keyfile). If you invoke it with only one parameter, it will 
   166  remove any stored credential for that address (keyfile)
   167  `,
   168  	}
   169  )
   170  
   171  func init() {
   172  	app.Name = "Clef"
   173  	app.Usage = "Manage VNT account operations"
   174  	app.Flags = []cli.Flag{
   175  		logLevelFlag,
   176  		keystoreFlag,
   177  		configdirFlag,
   178  		utils.NetworkIdFlag,
   179  		utils.LightKDFFlag,
   180  		utils.RPCListenAddrFlag,
   181  		utils.RPCVirtualHostsFlag,
   182  		utils.IPCDisabledFlag,
   183  		utils.IPCPathFlag,
   184  		utils.RPCEnabledFlag,
   185  		rpcPortFlag,
   186  		signerSecretFlag,
   187  		dBFlag,
   188  		customDBFlag,
   189  		auditLogFlag,
   190  		ruleFlag,
   191  		stdiouiFlag,
   192  		testFlag,
   193  	}
   194  	app.Action = signer
   195  	app.Commands = []cli.Command{initCommand, attestCommand, addCredentialCommand}
   196  
   197  }
   198  func main() {
   199  	if err := app.Run(os.Args); err != nil {
   200  		fmt.Fprintln(os.Stderr, err)
   201  		os.Exit(1)
   202  	}
   203  }
   204  
   205  func initializeSecrets(c *cli.Context) error {
   206  	if err := initialize(c); err != nil {
   207  		return err
   208  	}
   209  	configDir := c.String(configdirFlag.Name)
   210  
   211  	masterSeed := make([]byte, 256)
   212  	n, err := io.ReadFull(rand.Reader, masterSeed)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	if n != len(masterSeed) {
   217  		return fmt.Errorf("failed to read enough random")
   218  	}
   219  	err = os.Mkdir(configDir, 0700)
   220  	if err != nil && !os.IsExist(err) {
   221  		return err
   222  	}
   223  	location := filepath.Join(configDir, "secrets.dat")
   224  	if _, err := os.Stat(location); err == nil {
   225  		return fmt.Errorf("file %v already exists, will not overwrite", location)
   226  	}
   227  	err = ioutil.WriteFile(location, masterSeed, 0700)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	fmt.Printf("A master seed has been generated into %s\n", location)
   232  	fmt.Printf(`
   233  This is required to be able to store credentials, such as : 
   234  * Passwords for keystores (used by rule engine)
   235  * Storage for javascript rules
   236  * Hash of rule-file
   237  
   238  You should treat that file with utmost secrecy, and make a backup of it. 
   239  NOTE: This file does not contain your accounts. Those need to be backed up separately!
   240  
   241  `)
   242  	return nil
   243  }
   244  func attestFile(ctx *cli.Context) error {
   245  	if len(ctx.Args()) < 1 {
   246  		utils.Fatalf("This command requires an argument.")
   247  	}
   248  	if err := initialize(ctx); err != nil {
   249  		return err
   250  	}
   251  
   252  	stretchedKey, err := readMasterKey(ctx)
   253  	if err != nil {
   254  		utils.Fatalf(err.Error())
   255  	}
   256  	configDir := ctx.String(configdirFlag.Name)
   257  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   258  	confKey := crypto.Keccak256([]byte("config"), stretchedKey)
   259  
   260  	// Initialize the encrypted storages
   261  	configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
   262  	val := ctx.Args().First()
   263  	configStorage.Put("ruleset_sha256", val)
   264  	log.Info("Ruleset attestation updated", "sha256", val)
   265  	return nil
   266  }
   267  
   268  func addCredential(ctx *cli.Context) error {
   269  	if len(ctx.Args()) < 1 {
   270  		utils.Fatalf("This command requires at leaste one argument.")
   271  	}
   272  	if err := initialize(ctx); err != nil {
   273  		return err
   274  	}
   275  
   276  	stretchedKey, err := readMasterKey(ctx)
   277  	if err != nil {
   278  		utils.Fatalf(err.Error())
   279  	}
   280  	configDir := ctx.String(configdirFlag.Name)
   281  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   282  	pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   283  
   284  	// Initialize the encrypted storages
   285  	pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   286  	key := ctx.Args().First()
   287  	value := ""
   288  	if len(ctx.Args()) > 1 {
   289  		value = ctx.Args().Get(1)
   290  	}
   291  	pwStorage.Put(key, value)
   292  	log.Info("Credential store updated", "key", key)
   293  	return nil
   294  }
   295  
   296  func initialize(c *cli.Context) error {
   297  	// Set up the logger to print everything
   298  	logOutput := os.Stdout
   299  	if c.Bool(stdiouiFlag.Name) {
   300  		logOutput = os.Stderr
   301  		// If using the stdioui, we can't do the 'confirm'-flow
   302  		fmt.Fprintf(logOutput, legalWarning)
   303  	} else {
   304  		if !confirm(legalWarning) {
   305  			return fmt.Errorf("aborted by user")
   306  		}
   307  	}
   308  
   309  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true))))
   310  	return nil
   311  }
   312  
   313  func signer(c *cli.Context) error {
   314  	if err := initialize(c); err != nil {
   315  		return err
   316  	}
   317  	var (
   318  		ui core.SignerUI
   319  	)
   320  	if c.Bool(stdiouiFlag.Name) {
   321  		log.Info("Using stdin/stdout as UI-channel")
   322  		ui = core.NewStdIOUI()
   323  	} else {
   324  		log.Info("Using CLI as UI-channel")
   325  		ui = core.NewCommandlineUI()
   326  	}
   327  	db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name))
   328  	if err != nil {
   329  		utils.Fatalf(err.Error())
   330  	}
   331  	log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
   332  
   333  	var (
   334  		api core.ExternalAPI
   335  	)
   336  
   337  	configDir := c.String(configdirFlag.Name)
   338  	if stretchedKey, err := readMasterKey(c); err != nil {
   339  		log.Info("No master seed provided, rules disabled")
   340  	} else {
   341  
   342  		if err != nil {
   343  			utils.Fatalf(err.Error())
   344  		}
   345  		vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
   346  
   347  		// Generate domain specific keys
   348  		pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
   349  		jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
   350  		confkey := crypto.Keccak256([]byte("config"), stretchedKey)
   351  
   352  		// Initialize the encrypted storages
   353  		pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
   354  		jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
   355  		configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
   356  
   357  		//Do we have a rule-file?
   358  		ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name))
   359  		if err != nil {
   360  			log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
   361  		} else {
   362  			hasher := sha256.New()
   363  			hasher.Write(ruleJS)
   364  			shasum := hasher.Sum(nil)
   365  			storedShasum := configStorage.Get("ruleset_sha256")
   366  			if storedShasum != hex.EncodeToString(shasum) {
   367  				log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum)
   368  			} else {
   369  				// Initialize rules
   370  				ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage)
   371  				if err != nil {
   372  					utils.Fatalf(err.Error())
   373  				}
   374  				ruleEngine.Init(string(ruleJS))
   375  				ui = ruleEngine
   376  				log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
   377  			}
   378  		}
   379  	}
   380  
   381  	apiImpl := core.NewSignerAPI(
   382  		c.Int64(utils.NetworkIdFlag.Name),
   383  		c.String(keystoreFlag.Name),
   384  		ui, db,
   385  		c.Bool(utils.LightKDFFlag.Name))
   386  
   387  	api = apiImpl
   388  
   389  	// Audit logging
   390  	if logfile := c.String(auditLogFlag.Name); logfile != "" {
   391  		api, err = core.NewAuditLogger(logfile, api)
   392  		if err != nil {
   393  			utils.Fatalf(err.Error())
   394  		}
   395  		log.Info("Audit logs configured", "file", logfile)
   396  	}
   397  	// register signer API with server
   398  	var (
   399  		extapiURL = "n/a"
   400  		ipcapiURL = "n/a"
   401  	)
   402  	rpcAPI := []rpc.API{
   403  		{
   404  			Namespace: "account",
   405  			Public:    true,
   406  			Service:   api,
   407  			Version:   "1.0"},
   408  	}
   409  	if c.Bool(utils.RPCEnabledFlag.Name) {
   410  
   411  		vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
   412  		cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
   413  
   414  		// start http server
   415  		httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
   416  		listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts)
   417  		if err != nil {
   418  			utils.Fatalf("Could not start RPC api: %v", err)
   419  		}
   420  		extapiURL = fmt.Sprintf("http://%s", httpEndpoint)
   421  		log.Info("HTTP endpoint opened", "url", extapiURL)
   422  
   423  		defer func() {
   424  			listener.Close()
   425  			log.Info("HTTP endpoint closed", "url", httpEndpoint)
   426  		}()
   427  
   428  	}
   429  	if !c.Bool(utils.IPCDisabledFlag.Name) {
   430  		if c.IsSet(utils.IPCPathFlag.Name) {
   431  			ipcapiURL = c.String(utils.IPCPathFlag.Name)
   432  		} else {
   433  			ipcapiURL = filepath.Join(configDir, "clef.ipc")
   434  		}
   435  
   436  		listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
   437  		if err != nil {
   438  			utils.Fatalf("Could not start IPC api: %v", err)
   439  		}
   440  		log.Info("IPC endpoint opened", "url", ipcapiURL)
   441  		defer func() {
   442  			listener.Close()
   443  			log.Info("IPC endpoint closed", "url", ipcapiURL)
   444  		}()
   445  
   446  	}
   447  
   448  	if c.Bool(testFlag.Name) {
   449  		log.Info("Performing UI test")
   450  		go testExternalUI(apiImpl)
   451  	}
   452  	ui.OnSignerStartup(core.StartupInfo{
   453  		Info: map[string]interface{}{
   454  			"extapi_version": ExternalAPIVersion,
   455  			"intapi_version": InternalAPIVersion,
   456  			"extapi_http":    extapiURL,
   457  			"extapi_ipc":     ipcapiURL,
   458  		},
   459  	})
   460  
   461  	abortChan := make(chan os.Signal)
   462  	signal.Notify(abortChan, os.Interrupt)
   463  
   464  	sig := <-abortChan
   465  	log.Info("Exiting...", "signal", sig)
   466  
   467  	return nil
   468  }
   469  
   470  // splitAndTrim splits input separated by a comma
   471  // and trims excessive white space from the substrings.
   472  func splitAndTrim(input string) []string {
   473  	result := strings.Split(input, ",")
   474  	for i, r := range result {
   475  		result[i] = strings.TrimSpace(r)
   476  	}
   477  	return result
   478  }
   479  
   480  // DefaultConfigDir is the default config directory to use for the vaults and other
   481  // persistence requirements.
   482  func DefaultConfigDir() string {
   483  	// Try to place the data folder in the user's home dir
   484  	home := homeDir()
   485  	if home != "" {
   486  		if runtime.GOOS == "darwin" {
   487  			return filepath.Join(home, "Library", "Signer")
   488  		} else if runtime.GOOS == "windows" {
   489  			return filepath.Join(home, "AppData", "Roaming", "Signer")
   490  		} else {
   491  			return filepath.Join(home, ".clef")
   492  		}
   493  	}
   494  	// As we cannot guess a stable location, return empty and handle later
   495  	return ""
   496  }
   497  
   498  func homeDir() string {
   499  	if home := os.Getenv("HOME"); home != "" {
   500  		return home
   501  	}
   502  	if usr, err := user.Current(); err == nil {
   503  		return usr.HomeDir
   504  	}
   505  	return ""
   506  }
   507  func readMasterKey(ctx *cli.Context) ([]byte, error) {
   508  	var (
   509  		file      string
   510  		configDir = ctx.String(configdirFlag.Name)
   511  	)
   512  	if ctx.IsSet(signerSecretFlag.Name) {
   513  		file = ctx.String(signerSecretFlag.Name)
   514  	} else {
   515  		file = filepath.Join(configDir, "secrets.dat")
   516  	}
   517  	if err := checkFile(file); err != nil {
   518  		return nil, err
   519  	}
   520  	masterKey, err := ioutil.ReadFile(file)
   521  	if err != nil {
   522  		return nil, err
   523  	}
   524  	if len(masterKey) < 256 {
   525  		return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey))
   526  	}
   527  	// Create vault location
   528  	vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10]))
   529  	err = os.Mkdir(vaultLocation, 0700)
   530  	if err != nil && !os.IsExist(err) {
   531  		return nil, err
   532  	}
   533  	//!TODO, use KDF to stretch the master key
   534  	//			stretched_key := stretch_key(master_key)
   535  
   536  	return masterKey, nil
   537  }
   538  
   539  // checkFile is a convenience function to check if a file
   540  // * exists
   541  // * is mode 0600
   542  func checkFile(filename string) error {
   543  	info, err := os.Stat(filename)
   544  	if err != nil {
   545  		return fmt.Errorf("failed stat on %s: %v", filename, err)
   546  	}
   547  	// Check the unix permission bits
   548  	if info.Mode().Perm()&077 != 0 {
   549  		return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
   550  	}
   551  	return nil
   552  }
   553  
   554  // confirm displays a text and asks for user confirmation
   555  func confirm(text string) bool {
   556  	fmt.Printf(text)
   557  	fmt.Printf("\nEnter 'ok' to proceed:\n>")
   558  
   559  	text, err := bufio.NewReader(os.Stdin).ReadString('\n')
   560  	if err != nil {
   561  		log.Crit("Failed to read user input", "err", err)
   562  	}
   563  
   564  	if text := strings.TrimSpace(text); text == "ok" {
   565  		return true
   566  	}
   567  	return false
   568  }
   569  
   570  func testExternalUI(api *core.SignerAPI) {
   571  
   572  	ctx := context.WithValue(context.Background(), "remote", "clef binary")
   573  	ctx = context.WithValue(ctx, "scheme", "in-proc")
   574  	ctx = context.WithValue(ctx, "local", "main")
   575  
   576  	errs := make([]string, 0)
   577  
   578  	api.UI.ShowInfo("Testing 'ShowInfo'")
   579  	api.UI.ShowError("Testing 'ShowError'")
   580  
   581  	checkErr := func(method string, err error) {
   582  		if err != nil && err != core.ErrRequestDenied {
   583  			errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error()))
   584  		}
   585  	}
   586  	var err error
   587  
   588  	_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
   589  	checkErr("SignTransaction", err)
   590  	_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
   591  	checkErr("Sign", err)
   592  	_, err = api.List(ctx)
   593  	checkErr("List", err)
   594  	_, err = api.New(ctx)
   595  	checkErr("New", err)
   596  	_, err = api.Export(ctx, common.Address{})
   597  	checkErr("Export", err)
   598  	_, err = api.Import(ctx, json.RawMessage{})
   599  	checkErr("Import", err)
   600  
   601  	api.UI.ShowInfo("Tests completed")
   602  
   603  	if len(errs) > 0 {
   604  		log.Error("Got errors")
   605  		for _, e := range errs {
   606  			log.Error(e)
   607  		}
   608  	} else {
   609  		log.Info("No errors")
   610  	}
   611  
   612  }
   613  
   614  /**
   615  //Create Account
   616  
   617  curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
   618  
   619  // List accounts
   620  
   621  curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
   622  
   623  // Make Transaction
   624  // safeSend(0x12)
   625  // 4401a6e40000000000000000000000000000000000000000000000000000000000000012
   626  
   627  // supplied abi
   628  curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/
   629  
   630  // Not supplied
   631  curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/
   632  
   633  // Sign data
   634  
   635  curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_sign","params":["0x694267f14675d7e1b9494fd8d72fefe1755710fa","bazonk gaz baz"],"id":67}' http://localhost:8550/
   636  
   637  
   638  **/