github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/cmd/clef/main.go (about)

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