signer: refactored request/response, made use of urfave.cli

This commit is contained in:
Martin Holst Swende 2017-11-24 12:11:17 +01:00
parent 53d6353ae1
commit fad33b1936
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
3 changed files with 202 additions and 91 deletions

View file

@ -49,74 +49,113 @@ type Metadata struct {
scheme string scheme string
} }
// The SignTxRequest contains info about a transaction tos sign type credPreference int
const (
forgetPw credPreference = iota
rememberPwNow
)
type Credentials struct {
password string
}
// SignTxRequest contains info about a transaction to sign
type SignTxRequest struct { type SignTxRequest struct {
transaction *types.Transaction transaction *types.Transaction
from accounts.Account from accounts.Account
} }
type SignTxResponse struct {
hash common.Hash
approved bool
pw string
}
type ExportRequest struct { type ExportRequest struct {
account accounts.Account account accounts.Account
file string file string
} }
type ExportResponse struct {
approved bool
}
type ImportRequest struct { type ImportRequest struct {
account accounts.Account account accounts.Account
} }
type ImportResponse struct {
approved bool
oldPassword string
newPassword string
}
type SignDataRequest struct { type SignDataRequest struct {
account accounts.Account account accounts.Account
rawdata hexutil.Bytes rawdata hexutil.Bytes
message string message string
hash hexutil.Bytes hash hexutil.Bytes
} }
type ApprovalStatus struct { type SignDataResponse struct {
hash common.Hash
approved bool approved bool
pw string pw string
} }
type NewAccountRequest struct{} type NewAccountRequest struct{}
type NewAccountResponse struct {
approved bool
pw string
}
type ListRequest struct { type ListRequest struct {
accounts []Account accounts []Account
} }
type ListApproval struct { type ListResponse struct {
accounts []Account accounts []Account
} }
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI // SignerUI specifies what method a UI needs to implement to be able to be used as a UI
// for the signer // for the signer
type SignerUI interface { type SignerUI interface {
ApproveTx(request *SignTxRequest, metadata Metadata, ch chan ApprovalStatus)
ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan ApprovalStatus) // ApproveTx prompt the user for confirmation to request to sign transaction
ApproveExport(request *ExportRequest, metadata Metadata, ch chan ApprovalStatus) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse)
ApproveImport(request *ImportRequest, metadata Metadata, ch chan ApprovalStatus) // ApproveSignData prompt the user for confirmation to request to sign data
ApproveListing(request *ListRequest, metadata Metadata, ch chan ListApproval) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse)
ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan bool) // ApproveExport prompt the user for confirmation to export encrypted account json
// In case signing fails, bad password etc ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse)
// ApproveImport prompt the user for confirmation to import account json
ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse)
// ApproveListing prompt the user for confirmation to list accounts
// the list of accounts to list can be modified by the ui
ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse)
// ApproveNewAccount prompt the user for confirmation to create new account, and reveal to caller
ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan NewAccountResponse)
// ShowError displays error message to user
ShowError(message string) ShowError(message string)
// ShowInfo displays info message to user
ShowInfo(message string) ShowInfo(message string)
} }
type HeadlessUI struct { type HeadlessUI struct {
} }
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan ApprovalStatus) { func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
ch <- ApprovalStatus{request.transaction.Hash(), true, ""} ch <- SignTxResponse{request.transaction.Hash(), true, ""}
} }
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan ApprovalStatus) { func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
ch <- ApprovalStatus{common.Hash{}, true, ""} ch <- SignDataResponse{true, ""}
} }
func (ui *HeadlessUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ApprovalStatus) { func (ui *HeadlessUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse) {
ch <- ApprovalStatus{common.Hash{}, true, ""} ch <- ExportResponse{true}
} }
func (ui *HeadlessUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ApprovalStatus) { func (ui *HeadlessUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
ch <- ApprovalStatus{common.Hash{}, true, ""} ch <- ImportResponse{true, "", ""}
} }
func (ui *HeadlessUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListApproval) { func (ui *HeadlessUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse) {
ch <- ListApproval{request.accounts} ch <- ListResponse{request.accounts}
} }
func (ui *HeadlessUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan bool) { func (ui *HeadlessUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan ImportResponse) {
ch <- true ch <- ImportResponse{true, "", ""}
} }
func (ui *HeadlessUI) ShowError(message string) { func (ui *HeadlessUI) ShowError(message string) {
//stdout is used by communication //stdout is used by communication
@ -163,13 +202,13 @@ func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI) *Si
func metaData(ctx context.Context) Metadata { func metaData(ctx context.Context) Metadata {
m := Metadata{"NA", "NA", "NA"} m := Metadata{"NA", "NA", "NA"}
if v := ctx.Value("remote"); v != nil{ if v := ctx.Value("remote"); v != nil {
m.remote = v.(string) m.remote = v.(string)
} }
if v := ctx.Value("scheme"); v != nil{ if v := ctx.Value("scheme"); v != nil {
m.scheme = v.(string) m.scheme = v.(string)
} }
if v := ctx.Value("local"); v != nil{ if v := ctx.Value("local"); v != nil {
m.local = v.(string) m.local = v.(string)
} }
return m return m
@ -179,7 +218,7 @@ func metaData(ctx context.Context) Metadata {
// multiple accounts. // multiple accounts.
func (api *SignerAPI) List(ctx context.Context) ([]Account, error) { func (api *SignerAPI) List(ctx context.Context) ([]Account, error) {
ch := make(chan ListApproval, 1) ch := make(chan ListResponse, 1)
var accounts []Account var accounts []Account
for _, wallet := range api.am.Wallets() { for _, wallet := range api.am.Wallets() {
@ -204,10 +243,10 @@ func (api *SignerAPI) New(ctx context.Context, passphrase string) (accounts.Acco
if len(be) == 0 { if len(be) == 0 {
return accounts.Account{}, errors.New("password based accounts not supported") return accounts.Account{}, errors.New("password based accounts not supported")
} }
ch := make(chan bool, 1) ch := make(chan NewAccountResponse, 1)
api.ui.ApproveNewAccount(&NewAccountRequest{}, metaData(ctx), ch) api.ui.ApproveNewAccount(&NewAccountRequest{}, metaData(ctx), ch)
if <-ch { if resp := <-ch; resp.approved {
return be[0].(*keystore.KeyStore).NewAccount(passphrase) return be[0].(*keystore.KeyStore).NewAccount(passphrase)
} }
return accounts.Account{}, fmt.Errorf("Request denied") return accounts.Account{}, fmt.Errorf("Request denied")
@ -230,7 +269,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
tx = types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data) tx = types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
} }
ch := make(chan ApprovalStatus, 1) ch := make(chan SignTxResponse, 1)
api.ui.ApproveTx(&SignTxRequest{transaction: tx, from: acc}, metaData(ctx), ch) api.ui.ApproveTx(&SignTxRequest{transaction: tx, from: acc}, metaData(ctx), ch)
if result := <-ch; result.approved { if result := <-ch; result.approved {
@ -266,10 +305,9 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, passwd stri
if err != nil { if err != nil {
return nil, err return nil, err
} }
ch := make(chan ApprovalStatus, 1) ch := make(chan SignDataResponse, 1)
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) sighash, msg := signHash(data)
sighash := crypto.Keccak256([]byte(msg))
api.ui.ApproveSignData(&SignDataRequest{account: account, rawdata: data, message: msg, hash: sighash}, metaData(ctx), ch) api.ui.ApproveSignData(&SignDataRequest{account: account, rawdata: data, message: msg, hash: sighash}, metaData(ctx), ch)
@ -306,7 +344,8 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (c
} }
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
rpk, err := crypto.Ecrecover(signHash(data), sig) hash, _ := signHash(data)
rpk, err := crypto.Ecrecover(hash, sig)
if err != nil { if err != nil {
return common.Address{}, err return common.Address{}, err
} }
@ -322,9 +361,9 @@ func (api *SignerAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (c
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). // keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
// //
// This gives context to the signed message and prevents signing of transactions. // This gives context to the signed message and prevents signing of transactions.
func signHash(data []byte) []byte { func signHash(data []byte) ([]byte, string) {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
return crypto.Keccak256([]byte(msg)) return crypto.Keccak256([]byte(msg)), msg
} }
// Export returns encrypted private key associated with the given address in web3 keystore format. // Export returns encrypted private key associated with the given address in web3 keystore format.
@ -341,7 +380,7 @@ func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.Raw
if url.Scheme != keystore.KeyStoreScheme { if url.Scheme != keystore.KeyStoreScheme {
return nil, fmt.Errorf("account is not a password protected account") return nil, fmt.Errorf("account is not a password protected account")
} }
ch := make(chan ApprovalStatus, 1) ch := make(chan ExportResponse, 1)
api.ui.ApproveExport(&ExportRequest{account: account, file: url.Path}, metaData(ctx), ch) api.ui.ApproveExport(&ExportRequest{account: account, file: url.Path}, metaData(ctx), ch)
@ -361,7 +400,7 @@ func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage, passp
return Account{}, errors.New("password based accounts not supported") return Account{}, errors.New("password based accounts not supported")
} }
ch := make(chan ApprovalStatus, 1) ch := make(chan ImportResponse, 1)
api.ui.ApproveImport(&ImportRequest{}, metaData(ctx), ch) api.ui.ApproveImport(&ImportRequest{}, metaData(ctx), ch)
if resp := <-ch; resp.approved { if resp := <-ch; resp.approved {

View file

@ -18,31 +18,64 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"os" "os"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/ssh/terminal"
) )
type CommandlineUI struct { type CommandlineUI struct {
in *bufio.Reader
} }
func NewCommandlineUI() *CommandlineUI { func NewCommandlineUI() *CommandlineUI {
return &CommandlineUI{} return &CommandlineUI{bufio.NewReader(os.Stdin)}
} }
func confirm() bool {
fmt.Printf("Type 'Yes' to approve\n$>") // readString reads a single line from stdin, trimming if from spaces, enforcing
scanner := bufio.NewScanner(os.Stdin) // non-emptyness.
scanner.Scan() func (ui *CommandlineUI) readString() string {
answer := scanner.Text() for {
if answer == "Yes" { fmt.Printf("> ")
text, err := ui.in.ReadString('\n')
if err != nil {
log.Crit("Failed to read user input", "err", err)
}
if text = strings.TrimSpace(text); text != "" {
return text
}
}
}
// readPassword reads a single line from stdin, trimming it from the trailing new
// line and returns it. The input will not be echoed.
func (ui *CommandlineUI) readPassword() string {
fmt.Printf("> ")
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
log.Crit("Failed to read password", "err", err)
}
fmt.Println()
return string(text)
}
// confirm returns true if user enters 'Yes', otherwise false
func (ui *CommandlineUI) confirm() bool {
fmt.Printf("Type 'Yes' to approve\n")
if ui.readString() == "Yes" {
return true return true
} }
return false return false
} }
func showMetadata(metadata Metadata) { func showMetadata(metadata Metadata) {
fmt.Printf("Request info: %v -> %v -> %v\n", metadata.remote, metadata.scheme, metadata.local) fmt.Printf("Request info: %v -> %v -> %v\n", metadata.remote, metadata.scheme, metadata.local)
} }
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan ApprovalStatus) { // ApproveTx prompt the user for confirmation to request to sign transaction
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
fmt.Printf("--------- Transaction request-------------\n") fmt.Printf("--------- Transaction request-------------\n")
fmt.Printf("to: %v\n", request.transaction.To()) fmt.Printf("to: %v\n", request.transaction.To())
@ -51,9 +84,11 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch
fmt.Printf("data: %v\n", common.Bytes2Hex(request.transaction.Data())) fmt.Printf("data: %v\n", common.Bytes2Hex(request.transaction.Data()))
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(metadata) showMetadata(metadata)
ch <- ApprovalStatus{common.Hash{}, confirm(), ""} ch <- SignTxResponse{request.transaction.Hash(), ui.confirm(), ""}
} }
func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan ApprovalStatus) {
// ApproveSignData prompt the user for confirmation to request to sign data
func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
fmt.Printf("-------- Sign data request--------------\n") fmt.Printf("-------- Sign data request--------------\n")
fmt.Printf("account: %x\n", request.account.Address) fmt.Printf("account: %x\n", request.account.Address)
@ -62,9 +97,11 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest, metadata Meta
fmt.Printf("message hash: %v\n", request.hash) fmt.Printf("message hash: %v\n", request.hash)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(metadata) showMetadata(metadata)
ch <- ApprovalStatus{common.Hash{}, confirm(), ""} ch <- SignDataResponse{ui.confirm(), ""}
} }
func (ui *CommandlineUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ApprovalStatus) {
// ApproveExport prompt the user for confirmation to export encrypted account json
func (ui *CommandlineUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse) {
fmt.Printf("-------- Export account request--------------\n") fmt.Printf("-------- Export account request--------------\n")
fmt.Printf("A request has been made to export the (encrypted) keyfile\n") fmt.Printf("A request has been made to export the (encrypted) keyfile\n")
fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n") fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n")
@ -73,16 +110,21 @@ func (ui *CommandlineUI) ApproveExport(request *ExportRequest, metadata Metadata
fmt.Printf("keyfile: \n%v\n", request.file) fmt.Printf("keyfile: \n%v\n", request.file)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(metadata) showMetadata(metadata)
ch <- ApprovalStatus{common.Hash{}, confirm(), ""} ch <- ExportResponse{ui.confirm()}
} }
func (ui *CommandlineUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ApprovalStatus) {
// ApproveImport prompt the user for confirmation to import account json
func (ui *CommandlineUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
fmt.Printf("-------- Export account request--------------\n") fmt.Printf("-------- Export account request--------------\n")
fmt.Printf("A request has been made to import an encrypted keyfile\n") fmt.Printf("A request has been made to import an encrypted keyfile\n")
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(metadata) showMetadata(metadata)
ch <- ApprovalStatus{common.Hash{}, confirm(), ""} ch <- ImportResponse{ui.confirm(), "", ""}
} }
func (ui *CommandlineUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListApproval) {
// ApproveListing prompt the user for confirmation to list accounts
// the list of accounts to list can be modified by the ui
func (ui *CommandlineUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse) {
fmt.Printf("-------- List account request--------------\n") fmt.Printf("-------- List account request--------------\n")
fmt.Printf("A request has been made to list all accounts. \n") fmt.Printf("A request has been made to list all accounts. \n")
@ -92,26 +134,31 @@ func (ui *CommandlineUI) ApproveListing(request *ListRequest, metadata Metadata,
} }
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(metadata) showMetadata(metadata)
if confirm() { if ui.confirm() {
ch <- ListApproval{request.accounts} ch <- ListResponse{request.accounts}
} else { } else {
ch <- ListApproval{nil} ch <- ListResponse{nil}
} }
} }
func (ui *CommandlineUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan bool) {
// ApproveNewAccount prompt the user for confirmation to create new account, and reveal to caller
func (ui *CommandlineUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan NewAccountResponse) {
fmt.Printf("-------- New account request--------------\n") fmt.Printf("-------- New account request--------------\n")
fmt.Printf("A request has been made to create a new. \n") fmt.Printf("A request has been made to create a new. \n")
fmt.Printf("Approving this operation means that a new account is created,\n") fmt.Printf("Approving this operation means that a new account is created,\n")
fmt.Printf("and the address show to the caller\n") fmt.Printf("and the address show to the caller\n")
showMetadata(metadata) showMetadata(metadata)
ch <- confirm() ch <- NewAccountResponse{ui.confirm(), ""}
} }
// ShowError displays error message to user
func (ui *CommandlineUI) ShowError(message string) { func (ui *CommandlineUI) ShowError(message string) {
//stdout is used by communication
fmt.Printf("ERROR: %v", message) fmt.Printf("ERROR: %v", message)
} }
// ShowInfo displays info message to user
func (ui *CommandlineUI) ShowInfo(message string) { func (ui *CommandlineUI) ShowInfo(message string) {
//stdout is used by communication
fmt.Printf("Info: %v", message) fmt.Printf("Info: %v", message)
} }

View file

@ -19,54 +19,79 @@
package main package main
import ( import (
"flag"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"fmt"
"net"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1"
"net"
"fmt"
)
var (
ksLocation = flag.String("keystore", filepath.Join(node.DefaultDataDir(), "keystore"), "Directory for the keystore")
chainID = flag.Int64("chainid", params.MainnetChainConfig.ChainId.Int64(), "chain identifier")
) )
func main() { func main() {
flag.Parse()
var ( app := cli.NewApp()
server = rpc.NewServer() app.Name = "signer"
api = NewSignerAPI(*chainID, *ksLocation, true, NewCommandlineUI()) app.Usage = "Manage ethereum account operations"
listener net.Listener app.Flags = []cli.Flag{
err error cli.Int64Flag{
) Name: "chainid",
Value: params.MainnetChainConfig.ChainId.Int64(),
// register signer API with server Usage: "chain identifier",
if err = server.RegisterName("account", api); err != nil { },
utils.Fatalf("Could not register signer API: %v", err) cli.IntFlag{
Name: "loglevel",
Value: 4,
Usage: "log level to emit to the screen",
},
cli.StringFlag{
Name: "keystore",
Value: filepath.Join(node.DefaultDataDir(), "keystore"),
Usage: "Directory for the keystore",
},
} }
app.Action = func(c *cli.Context) error {
// Set up the logger to print everything and the random generator
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
endpoint := "localhost:8550" var (
server = rpc.NewServer()
api = NewSignerAPI(c.Int64("chainid"), c.String("keystore"), true, NewCommandlineUI())
listener net.Listener
err error
)
if listener, err = net.Listen("tcp", endpoint); err != nil { // register signer API with server
utils.Fatalf("Could not start http listener: %v", err) if err = server.RegisterName("account", api); err != nil {
utils.Fatalf("Could not register signer API: %v", err)
}
endpoint := "localhost:8550"
if listener, err = net.Listen("tcp", endpoint); err != nil {
utils.Fatalf("Could not start http listener: %v", err)
}
log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint))
cors := []string{"*"}
rpc.NewHTTPServer(cors, server).Serve(listener)
return nil
} }
log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint)) app.Run(os.Args)
fmt.Printf("HTTP endpoint opened: http://%s\n", endpoint)
cors := []string{"*"}
rpc.NewHTTPServer(cors, server).Serve(listener)
} }
// Create account
// #curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
// Create account
// curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
// List accounts
// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
type rwc struct { type rwc struct {
io.Reader io.Reader
io.Writer io.Writer