cmd/signer: made possible for UI to modify tx parameters

This commit is contained in:
Martin Holst Swende 2017-12-04 11:57:01 +01:00
parent eb61c52626
commit d17a51829d
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
8 changed files with 136 additions and 48 deletions

View file

@ -136,14 +136,17 @@ None
Signs a transactions and responds with the signed transaction in RLP encoded form. Signs a transactions and responds with the signed transaction in RLP encoded form.
#### Arguments #### Arguments
- from [address]: account to send the transaction from 1. from [address]: account to send the transaction from
- Transaction object: 2. transaction object:
- transaction.to [address]: receiver account - `to` [address]: receiver account
- gas [number]: maximum amount of gas to burn - `gas` [number]: maximum amount of gas to burn
- gasPrice [number]: gas price - `gasPrice` [number]: gas price
- value [number:optional]: amount of Wei to send with the transaction - `value` [number:optional]: amount of Wei to send with the transaction
- data [data:optional]: input data - `data` [data:optional]: input data
- transaction.nonce [number]: account nonce - `nonce` [number]: account nonce
3. method signature [string:optional]
- The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected.
#### Result #### Result
- signed transaction in RLP encoded form [data] - signed transaction in RLP encoded form [data]

View file

@ -133,6 +133,6 @@ func (db *abiDb) LookupABI(id []byte) (string, error) {
} }
return "", fmt.Errorf("Signature %v not found", sig) return "", fmt.Errorf("Signature %v not found", sig)
} }
func (db *abiDb) Size() int{ func (db *abiDb) Size() int {
return len(db.db) return len(db.db)
} }

View file

@ -53,15 +53,16 @@ type Metadata struct {
type ( type (
// SignTxRequest contains info about a transaction to sign // SignTxRequest contains info about a transaction to sign
SignTxRequest struct { SignTxRequest struct {
transaction *types.Transaction transaction types.Transaction
from accounts.Account from accounts.Account
callinfo fmt.Stringer callinfo fmt.Stringer
} }
// SignTxResponse result from SignTxRequest // SignTxResponse result from SignTxRequest
SignTxResponse struct { SignTxResponse struct {
hash common.Hash //The UI may make changes to the TX
approved bool transaction types.Transaction
pw string approved bool
pw string
} }
// ExportRequest info about query to export accounts // ExportRequest info about query to export accounts
ExportRequest struct { ExportRequest struct {
@ -244,7 +245,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)
} }
req := SignTxRequest{transaction: tx, from: acc} req := SignTxRequest{transaction: *tx, from: acc}
if len(tx.Data()) > 3 { if len(tx.Data()) > 3 {
// Try to make sense of the data // Try to make sense of the data
var abidata string var abidata string
@ -269,10 +270,11 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
if result := <-ch; result.approved { if result := <-ch; result.approved {
//Sanity check //Sanity check
if result.hash != tx.Hash() { if result.transaction.Hash() != tx.Hash() {
return nil, fmt.Errorf("Transaction hash mismatch") api.ui.ShowInfo("Transaction modified by UI")
} }
signedTx, err := wallet.SignTxWithPassphrase(acc, result.pw, tx, api.chainID) // The one to sign is the one that was returned from the UI
signedTx, err := wallet.SignTxWithPassphrase(acc, result.pw, &result.transaction, api.chainID)
if err != nil { if err != nil {
api.ui.ShowError(err.Error()) api.ui.ShowError(err.Error())
return nil, err return nil, err

View file

@ -1,12 +1,14 @@
package main package main
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
@ -21,10 +23,17 @@ type HeadlessUI struct {
} }
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) { func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
if "Y" == <-ui.controller {
ch <- SignTxResponse{request.transaction.Hash(), true, <-ui.controller} switch <-ui.controller {
} else { case "Y":
ch <- SignTxResponse{request.transaction.Hash(), false, ""} ch <- SignTxResponse{request.transaction, true, <-ui.controller}
case "M": //Modify
old := request.transaction
newVal := big.NewInt(0).Add(old.Value(), big.NewInt(1))
tx := types.NewTransaction(old.Nonce(), *old.To(), newVal, old.Gas(), old.GasPrice(), old.Data())
ch <- SignTxResponse{*tx, true, <-ui.controller}
default:
ch <- SignTxResponse{request.transaction, false, ""}
} }
} }
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) { func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
@ -250,10 +259,18 @@ func mkTestTx() TransactionArg {
} }
func TestSignTx(t *testing.T) { func TestSignTx(t *testing.T) {
var (
list Accounts
h []byte
h2 []byte
err error
)
api, control := setup(t) api, control := setup(t)
createAccount(control, api, t) createAccount(control, api, t)
control <- "A" control <- "A"
list, err := api.List(context.Background()) list, err = api.List(context.Background())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -264,7 +281,7 @@ func TestSignTx(t *testing.T) {
control <- "Y" control <- "Y"
control <- "wrongpassword" control <- "wrongpassword"
h, err := api.SignTransaction(context.Background(), a, tx, &methodSig) h, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
if h != nil { if h != nil {
t.Errorf("Expected nil-data, got %h", h) t.Errorf("Expected nil-data, got %h", h)
} }
@ -291,4 +308,28 @@ func TestSignTx(t *testing.T) {
if h == nil || len(h) != 118 { if h == nil || len(h) != 118 {
t.Errorf("Expected 181 byte rlp-data (got %d bytes)", len(h)) t.Errorf("Expected 181 byte rlp-data (got %d bytes)", len(h))
} }
//The tx is NOT modified by the UI
control <- "Y"
control <- "apassword"
h2, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(h, h2) {
t.Error("Expected tx to be unmodified by UI")
}
//The tx is modified by the UI
control <- "M"
control <- "apassword"
h2, err = api.SignTransaction(context.Background(), a, tx, &methodSig)
if err != nil {
t.Fatal(err)
}
if bytes.Equal(h, h2) {
t.Error("Expected tx to be modified by UI")
}
} }

26
cmd/signer/auditlog.go Normal file
View file

@ -0,0 +1,26 @@
package main
import (
"bufio"
"fmt"
"github.com/ethereum/go-ethereum/rpc"
"io"
"time"
)
type AuditLogger struct {
writer *bufio.Writer
}
func (l AuditLogger) Store(record *rpc.RPCInvocationRecord) {
l.writer.WriteString(fmt.Sprintf("%v\n%v\n", time.Now().Format(time.RFC3339), record.Method))
for i, arg := range record.Args {
l.writer.WriteString(fmt.Sprintf("\t%d: %v\n", i, arg))
}
l.writer.WriteString(fmt.Sprintf("%v\n", record.Response))
l.writer.Flush()
}
func NewAuditLogger(writer io.Writer) *AuditLogger {
return &AuditLogger{bufio.NewWriter(writer)}
}

View file

@ -56,10 +56,13 @@ func (ui *CommandlineUI) readString() string {
func (ui *CommandlineUI) readPassword() string { func (ui *CommandlineUI) readPassword() string {
fmt.Printf("Enter password to approve:\n") fmt.Printf("Enter password to approve:\n")
fmt.Printf("> ") fmt.Printf("> ")
text, err := terminal.ReadPassword(int(os.Stdin.Fd())) //TODO; remove this, only for debuggging within IDE
if err != nil { text := "foobar"
log.Crit("Failed to read password", "err", err) //TODO: Use this
} // text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
//if err != nil {
// log.Crit("Failed to read password", "err", err)
//}
fmt.Println() fmt.Println()
fmt.Println("-----------------------") fmt.Println("-----------------------")
return string(text) return string(text)
@ -114,7 +117,7 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch
showMetadata(metadata) showMetadata(metadata)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
ch <- SignTxResponse{request.transaction.Hash(), true ,ui.readPassword()} ch <- SignTxResponse{request.transaction, true, ui.readPassword()}
} }
// ApproveSignData prompt the user for confirmation to request to sign data // ApproveSignData prompt the user for confirmation to request to sign data
@ -157,9 +160,9 @@ func (ui *CommandlineUI) ApproveImport(request *ImportRequest, metadata Metadata
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)
if ui.confirm(){ if ui.confirm() {
ch <- ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")} ch <- ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}
}else{ } else {
ch <- ImportResponse{false, "", ""} ch <- ImportResponse{false, "", ""}
} }
} }

View file

@ -20,7 +20,6 @@ package main
import ( import (
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@ -70,9 +69,14 @@ func main() {
}, },
cli.StringFlag{ cli.StringFlag{
Name: "auditlog", Name: "auditlog",
Usage: "File used to emit audit logs. Set to '' to disable", Usage: "File used to emit audit logs. Set to \"\" to disable",
Value: "audit.log", Value: "audit.log",
}, },
cli.StringFlag{
Name: "requestfile",
Usage: "File containing requests to handle",
Value: "",
},
} }
app.Action = func(c *cli.Context) error { app.Action = func(c *cli.Context) error {
@ -88,21 +92,34 @@ func main() {
var ( var (
server = rpc.NewServer() server = rpc.NewServer()
api = NewSignerAPI(
api = NewSignerAPI(
c.Int64(utils.NetworkIdFlag.Name), c.Int64(utils.NetworkIdFlag.Name),
c.String("keystore"), c.String("keystore"),
c.Bool(utils.NoUSBFlag.Name), c.Bool(utils.NoUSBFlag.Name),
NewCommandlineUI(), db, NewCommandlineUI(), db,
c.Bool(utils.LightKDFFlag.Name)) c.Bool(utils.LightKDFFlag.Name))
listener net.Listener listener net.Listener
//err error
) )
if logfile := c.String("auditlog"); logfile != "" {
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
utils.Fatalf("Could not open %v for audit logging", logfile)
}
server.SetAuditLogger(NewAuditLogger(f))
log.Info("Writing audit logs to %v", logfile)
}
// register signer API with server // register signer API with server
if err = server.RegisterName("account", api); err != nil { if err = server.RegisterName("account", api); err != nil {
utils.Fatalf("Could not register signer API: %v", err) utils.Fatalf("Could not register signer API: %v", err)
} }
// Import from file
if rfile := c.String("requestfile"); rfile != "" {
//Each line of file represents one request
log.Warn("Import from file not yet implemented")
}
// start http server // start http server
endpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int("rpcport")) endpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int("rpcport"))
if listener, err = net.Listen("tcp", endpoint); err != nil { if listener, err = net.Listen("tcp", endpoint); err != nil {
@ -112,6 +129,7 @@ func main() {
cors := []string{"*"} cors := []string{"*"}
rpc.NewHTTPServer(cors, server).Serve(listener) rpc.NewHTTPServer(cors, server).Serve(listener)
return nil return nil
} }
app.Run(os.Args) app.Run(os.Args)
@ -127,16 +145,11 @@ func main() {
// Make transaction // Make transaction
// safeSend(0x12) // safeSend(0x12)
// 4401a6e40000000000000000000000000000000000000000000000000000000000000012 // 4401a6e40000000000000000000000000000000000000000000000000000000000000012
// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813","pw",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/
type rwc struct { /*
io.Reader
io.Writer
}
func (r *rwc) Close() error { curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/
if err := os.Stdin.Close(); err != nil {
return err
} curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":["0x82A2A876D39022B3019932D30Cd9c97ad5616813",{"gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/
return os.Stdout.Close() */
}

View file

@ -26,9 +26,9 @@ import (
type Accounts []Account type Accounts []Account
func (as Accounts) String() string{ func (as Accounts) String() string {
var output []string var output []string
for _,a := range as{ for _, a := range as {
output = append(output, a.String()) output = append(output, a.String())
} }
return strings.Join(output, "\n") return strings.Join(output, "\n")