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
}
// 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 {
transaction *types.Transaction
from accounts.Account
}
type SignTxResponse struct {
hash common.Hash
approved bool
pw string
}
type ExportRequest struct {
account accounts.Account
file string
}
type ExportResponse struct {
approved bool
}
type ImportRequest struct {
account accounts.Account
}
type ImportResponse struct {
approved bool
oldPassword string
newPassword string
}
type SignDataRequest struct {
account accounts.Account
rawdata hexutil.Bytes
message string
hash hexutil.Bytes
}
type ApprovalStatus struct {
hash common.Hash
type SignDataResponse struct {
approved bool
pw string
}
type NewAccountRequest struct{}
type NewAccountResponse struct {
approved bool
pw string
}
type ListRequest struct {
accounts []Account
}
type ListApproval struct {
type ListResponse struct {
accounts []Account
}
// SignerUI specifies what method a UI needs to implement to be able to be used as a UI
// for the signer
type SignerUI interface {
ApproveTx(request *SignTxRequest, metadata Metadata, ch chan ApprovalStatus)
ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan ApprovalStatus)
ApproveExport(request *ExportRequest, metadata Metadata, ch chan ApprovalStatus)
ApproveImport(request *ImportRequest, metadata Metadata, ch chan ApprovalStatus)
ApproveListing(request *ListRequest, metadata Metadata, ch chan ListApproval)
ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan bool)
// In case signing fails, bad password etc
// ApproveTx prompt the user for confirmation to request to sign transaction
ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse)
// ApproveSignData prompt the user for confirmation to request to sign data
ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse)
// ApproveExport prompt the user for confirmation to export encrypted account json
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)
// ShowInfo displays info message to user
ShowInfo(message string)
}
type HeadlessUI struct {
}
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan ApprovalStatus) {
ch <- ApprovalStatus{request.transaction.Hash(), true, ""}
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
ch <- SignTxResponse{request.transaction.Hash(), true, ""}
}
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan ApprovalStatus) {
ch <- ApprovalStatus{common.Hash{}, true, ""}
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
ch <- SignDataResponse{true, ""}
}
func (ui *HeadlessUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ApprovalStatus) {
ch <- ApprovalStatus{common.Hash{}, true, ""}
func (ui *HeadlessUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse) {
ch <- ExportResponse{true}
}
func (ui *HeadlessUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ApprovalStatus) {
ch <- ApprovalStatus{common.Hash{}, true, ""}
func (ui *HeadlessUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
ch <- ImportResponse{true, "", ""}
}
func (ui *HeadlessUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListApproval) {
ch <- ListApproval{request.accounts}
func (ui *HeadlessUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse) {
ch <- ListResponse{request.accounts}
}
func (ui *HeadlessUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan bool) {
ch <- true
func (ui *HeadlessUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan ImportResponse) {
ch <- ImportResponse{true, "", ""}
}
func (ui *HeadlessUI) ShowError(message string) {
//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 {
m := Metadata{"NA", "NA", "NA"}
if v := ctx.Value("remote"); v != nil{
if v := ctx.Value("remote"); v != nil {
m.remote = v.(string)
}
if v := ctx.Value("scheme"); v != nil{
if v := ctx.Value("scheme"); v != nil {
m.scheme = v.(string)
}
if v := ctx.Value("local"); v != nil{
if v := ctx.Value("local"); v != nil {
m.local = v.(string)
}
return m
@ -179,7 +218,7 @@ func metaData(ctx context.Context) Metadata {
// multiple accounts.
func (api *SignerAPI) List(ctx context.Context) ([]Account, error) {
ch := make(chan ListApproval, 1)
ch := make(chan ListResponse, 1)
var accounts []Account
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 {
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)
if <-ch {
if resp := <-ch; resp.approved {
return be[0].(*keystore.KeyStore).NewAccount(passphrase)
}
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)
}
ch := make(chan ApprovalStatus, 1)
ch := make(chan SignTxResponse, 1)
api.ui.ApproveTx(&SignTxRequest{transaction: tx, from: acc}, metaData(ctx), ch)
if result := <-ch; result.approved {
@ -266,10 +305,9 @@ func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, passwd stri
if err != nil {
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 := crypto.Keccak256([]byte(msg))
sighash, msg := signHash(data)
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
rpk, err := crypto.Ecrecover(signHash(data), sig)
hash, _ := signHash(data)
rpk, err := crypto.Ecrecover(hash, sig)
if err != nil {
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}).
//
// 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)
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.
@ -341,7 +380,7 @@ func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.Raw
if url.Scheme != keystore.KeyStoreScheme {
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)
@ -361,7 +400,7 @@ func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage, passp
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)
if resp := <-ch; resp.approved {

View file

@ -18,31 +18,64 @@ package main
import (
"bufio"
"fmt"
"github.com/ethereum/go-ethereum/common"
"os"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/ssh/terminal"
)
type CommandlineUI struct {
in *bufio.Reader
}
func NewCommandlineUI() *CommandlineUI {
return &CommandlineUI{}
return &CommandlineUI{bufio.NewReader(os.Stdin)}
}
func confirm() bool {
fmt.Printf("Type 'Yes' to approve\n$>")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
answer := scanner.Text()
if answer == "Yes" {
// readString reads a single line from stdin, trimming if from spaces, enforcing
// non-emptyness.
func (ui *CommandlineUI) readString() string {
for {
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 false
}
func showMetadata(metadata Metadata) {
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("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("-------------------------------------------\n")
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("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("-------------------------------------------\n")
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("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")
@ -73,16 +110,21 @@ func (ui *CommandlineUI) ApproveExport(request *ExportRequest, metadata Metadata
fmt.Printf("keyfile: \n%v\n", request.file)
fmt.Printf("-------------------------------------------\n")
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("A request has been made to import an encrypted keyfile\n")
fmt.Printf("-------------------------------------------\n")
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("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")
showMetadata(metadata)
if confirm() {
ch <- ListApproval{request.accounts}
if ui.confirm() {
ch <- ListResponse{request.accounts}
} 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("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("and the address show to the caller\n")
showMetadata(metadata)
ch <- confirm()
ch <- NewAccountResponse{ui.confirm(), ""}
}
// ShowError displays error message to user
func (ui *CommandlineUI) ShowError(message string) {
//stdout is used by communication
fmt.Printf("ERROR: %v", message)
}
// ShowInfo displays info message to user
func (ui *CommandlineUI) ShowInfo(message string) {
//stdout is used by communication
fmt.Printf("Info: %v", message)
}

View file

@ -19,54 +19,79 @@
package main
import (
"flag"
"io"
"os"
"path/filepath"
"fmt"
"net"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/log"
"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")
"gopkg.in/urfave/cli.v1"
)
func main() {
flag.Parse()
var (
server = rpc.NewServer()
api = NewSignerAPI(*chainID, *ksLocation, true, NewCommandlineUI())
listener net.Listener
err error
)
// register signer API with server
if err = server.RegisterName("account", api); err != nil {
utils.Fatalf("Could not register signer API: %v", err)
app := cli.NewApp()
app.Name = "signer"
app.Usage = "Manage ethereum account operations"
app.Flags = []cli.Flag{
cli.Int64Flag{
Name: "chainid",
Value: params.MainnetChainConfig.ChainId.Int64(),
Usage: "chain identifier",
},
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 {
utils.Fatalf("Could not start http listener: %v", err)
// register signer API with server
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))
fmt.Printf("HTTP endpoint opened: http://%s\n", endpoint)
cors := []string{"*"}
app.Run(os.Args)
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 {
io.Reader
io.Writer