signer: implement OnApprovedTx, change signing response (API BREAKAGE)

This commit is contained in:
Martin Holst Swende 2018-02-15 00:33:16 +01:00
parent da312a1dcb
commit a8e68b9cc3
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
9 changed files with 280 additions and 131 deletions

View file

@ -33,8 +33,8 @@ import (
"github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet"
"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"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -46,7 +46,7 @@ type ExternalAPI interface {
// New request to create a new account // New request to create a new account
New(ctx context.Context) (accounts.Account, error) New(ctx context.Context) (accounts.Account, error)
// SignTransaction request to sign the specified transaction // SignTransaction request to sign the specified transaction
SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error)
// Sign - request to sign the given data (plus prefix) // Sign - request to sign the given data (plus prefix)
Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error)
// EcRecover - request to perform ecrecover // EcRecover - request to perform ecrecover
@ -77,6 +77,9 @@ type SignerUI interface {
ShowError(message string) ShowError(message string)
// ShowInfo displays info message to user // ShowInfo displays info message to user
ShowInfo(message string) ShowInfo(message string)
// OnApprovedTx notifies the UI about a transaction having been successfully signed.
// This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient.
OnApprovedTx(tx ethapi.SignTransactionResult)
} }
// SignerAPI defines the actual implementation of ExternalAPI // SignerAPI defines the actual implementation of ExternalAPI
@ -123,16 +126,14 @@ func (m Metadata) String() string {
type ( type (
// SignTxRequest contains info about a Transaction to sign // SignTxRequest contains info about a Transaction to sign
SignTxRequest struct { SignTxRequest struct {
Transaction TransactionArg `json:"transaction"` Transaction SendTxArgs `json:"transaction"`
From common.MixedcaseAddress `json:"from"`
Callinfo string `json:"call_info"` Callinfo string `json:"call_info"`
Meta Metadata `json:"meta"` Meta Metadata `json:"meta"`
} }
// SignTxResponse result from SignTxRequest // SignTxResponse result from SignTxRequest
SignTxResponse struct { SignTxResponse struct {
//The UI may make changes to the TX //The UI may make changes to the TX
Transaction TransactionArg `json:"transaction"` Transaction SendTxArgs `json:"transaction"`
From common.MixedcaseAddress `json:"from"`
Approved bool `json:"approved"` Approved bool `json:"approved"`
Password string `json:"password"` Password string `json:"password"`
} }
@ -271,23 +272,17 @@ func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
return be[0].(*keystore.KeyStore).NewAccount(resp.Password) return be[0].(*keystore.KeyStore).NewAccount(resp.Password)
} }
func toTransaction(args *TransactionArg) *types.Transaction {
if args.To == nil {
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
} else {
return types.NewTransaction(uint64(*args.Nonce), args.To.Address(), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
}
}
// logDiff logs the difference between the incoming (original) transaction and the one returned from the signer. // logDiff logs the difference between the incoming (original) transaction and the one returned from the signer.
// it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow // it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow
// UI-modifications to requests // UI-modifications to requests
func logDiff(original *SignTxRequest, new *SignTxResponse) bool { func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
modified := false modified := false
if f0, f1 := original.From, new.From; f0 != f1 {
modified = true if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) {
log.Info("Sender-account changed by UI", "was", f0, "is", f1) log.Info("Sender-account changed by UI", "was", f0, "is", f1)
modified = true
} }
if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) { if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) {
log.Info("Recipient-account changed by UI", "was", t0, "is", t1) log.Info("Recipient-account changed by UI", "was", t0, "is", t1)
modified = true modified = true
@ -310,9 +305,19 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
log.Info("Value changed by UI", "was", v0, "is", v1) log.Info("Value changed by UI", "was", v0, "is", v1)
} }
} }
if d0, d1 := original.Transaction.Data, new.Transaction.Data; !bytes.Equal(d0, d1) { if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 {
d0s := ""
d1s := ""
if d0 != nil {
d0s = common.ToHex(*d0)
}
if d1 != nil {
d1s = common.ToHex(*d1)
}
if d1s != d0s {
modified = true modified = true
log.Info("Data changed by UI", "was", common.ToHex(d0), "is", common.ToHex(d1)) log.Info("Data changed by UI", "was", d0s, "is", d1s)
}
} }
if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 { if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 {
@ -324,41 +329,68 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
return modified return modified
} }
// SignTransaction signs the given Transaction and returns it in an RLP encoded form // determineCallInfo turns ABI-data + methodselector (if given) into a string suitable
// that can be posted to `eth_sendRawTransaction`. // to present to the user.
func (api *SignerAPI) SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) { func (api *SignerAPI) determineCallInfo(data []byte, methodSelector *string) string {
if len(data) < 4 {
return ""
}
var ( var (
selector string
err error err error
result SignTxResponse
) )
req := SignTxRequest{Transaction: args, From: from, Meta: MetadataFromContext(ctx)}
data := args.Data
if len(data) > 3 {
// Try to make sense of the data // Try to make sense of the data
var selector string
if methodSelector == nil { if methodSelector == nil {
selector, err = api.abidb.LookupMethodSelector(data[:4]) selector, err = api.abidb.LookupMethodSelector(data[:4])
if err != nil { if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not locate ABI", err}.String() return errorWrapper{"Warning! Could not locate ABI", err}.String()
} }
} else { } else {
selector = *methodSelector selector = *methodSelector
} }
if selector != "" { if selector != "" {
abidata, err := MethodSelectorToAbi(selector) abiData, err := MethodSelectorToAbi(selector)
if err != nil { if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String() return errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else { } else {
var info *decodedCallData var info *decodedCallData
info, err = parseCallData(data, string(abidata)) info, err = parseCallData(data, string(abiData))
if err != nil { if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String() return errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else { } else {
req.Callinfo = info.String() return info.String()
} }
} }
} }
return ""
}
// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form
func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
var (
err error
result SignTxResponse
data []byte
)
// Prevent accidental erroneous usage of both 'input' and 'data'
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return nil, errors.New(`Ambiguous request: moth "data" and "input" are set and are not identical`)
} }
if args.Data != nil {
data = *args.Data
} else if args.Input != nil {
data = *args.Input
*args.Data = data
*args.Input = nil
}
req := SignTxRequest{
Transaction: args,
Meta: MetadataFromContext(ctx),
Callinfo:api.determineCallInfo(data, methodSelector),
}
// Process approval
result, err = api.ui.ApproveTx(&req) result, err = api.ui.ApproveTx(&req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -368,25 +400,33 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Mixedcase
} }
// Log changes made by the UI to the signing-request // Log changes made by the UI to the signing-request
logDiff(&req, &result) logDiff(&req, &result)
var ( var (
acc accounts.Account acc accounts.Account
wallet accounts.Wallet wallet accounts.Wallet
) )
acc = accounts.Account{Address: result.From.Address()} acc = accounts.Account{Address: result.Transaction.From.Address()}
wallet, err = api.am.Find(acc) wallet, err = api.am.Find(acc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var tx = toTransaction(&result.Transaction) // Convert fields into a real transaction
var unsignedTx = result.Transaction.toTransaction()
// The one to sign is the one that was returned from the UI // The one to sign is the one that was returned from the UI
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, tx, api.chainID) signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID)
if err != nil { if err != nil {
api.ui.ShowError(err.Error()) api.ui.ShowError(err.Error())
return nil, err return nil, err
} }
return rlp.EncodeToBytes(signedTx)
rlpdata, err := rlp.EncodeToBytes(signedTx)
response := ethapi.SignTransactionResult{rlpdata, signedTx}
// Finally, send the signed tx to the UI
api.ui.OnApprovedTx(response)
// ...and to the external caller
return &response, nil
} }
// Sign calculates an Ethereum ECDSA signature for: // Sign calculates an Ethereum ECDSA signature for:

View file

@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/internal/ethapi"
) )
//Used for testing //Used for testing
@ -24,18 +25,22 @@ type HeadlessUI struct {
controller chan string controller chan string
} }
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
fmt.Printf("OnApproved called")
}
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
switch <-ui.controller { switch <-ui.controller {
case "Y": case "Y":
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
case "M": //Modify case "M": //Modify
old := (*big.Int)(request.Transaction.Value) old := (*big.Int)(request.Transaction.Value)
newVal := big.NewInt(0).Add(old, big.NewInt(1)) newVal := big.NewInt(0).Add(old, big.NewInt(1))
request.Transaction.Value = (*hexutil.Big)(newVal) request.Transaction.Value = (*hexutil.Big)(newVal)
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil return SignTxResponse{request.Transaction, true, <-ui.controller}, nil
default: default:
return SignTxResponse{request.Transaction, request.From, false, ""}, nil return SignTxResponse{request.Transaction, false, ""}, nil
} }
} }
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
@ -231,19 +236,21 @@ func TestSignData(t *testing.T) {
t.Errorf("Expected 65 byte signature (got %d bytes)", len(h)) t.Errorf("Expected 65 byte signature (got %d bytes)", len(h))
} }
} }
func mkTestTx() TransactionArg { func mkTestTx(from common.MixedcaseAddress) SendTxArgs {
to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) to := common.NewMixedcaseAddress(common.HexToAddress("0x1337"))
gas := (*hexutil.Big)(big.NewInt(21000)) gas := (*hexutil.Big)(big.NewInt(21000))
gasPrice := (*hexutil.Big)(big.NewInt(2000000000)) gasPrice := (*hexutil.Big)(big.NewInt(2000000000))
value := (*hexutil.Big)(big.NewInt(1e18)) value := (*hexutil.Big)(big.NewInt(1e18))
nonce := (hexutil.Uint64)(0) nonce := (hexutil.Uint64)(0)
tx := TransactionArg{ data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a"))
&to, tx := SendTxArgs{
gas, From:from,
gasPrice, To: &to,
value, Gas: gas,
common.Hex2Bytes("01020304050607080a"), GasPrice: gasPrice,
&nonce} Value: value,
Data: &data,
Nonce: &nonce}
return tx return tx
} }
@ -251,8 +258,7 @@ func TestSignTx(t *testing.T) {
var ( var (
list Accounts list Accounts
h []byte res, res2 *ethapi.SignTransactionResult
h2 []byte
err error err error
) )
@ -266,22 +272,22 @@ func TestSignTx(t *testing.T) {
a := common.NewMixedcaseAddress(list[0].Address) a := common.NewMixedcaseAddress(list[0].Address)
methodSig := "test(uint)" methodSig := "test(uint)"
tx := mkTestTx() tx := mkTestTx(a)
control <- "Y" control <- "Y"
control <- "wrongpassword" control <- "wrongpassword"
h, err = api.SignTransaction(context.Background(), a, tx, &methodSig) res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if h != nil { if res != nil {
t.Errorf("Expected nil-data, got %h", h) t.Errorf("Expected nil-response, got %v", res)
} }
if err != keystore.ErrDecrypt { if err != keystore.ErrDecrypt {
t.Errorf("Expected ErrLocked! %v", err) t.Errorf("Expected ErrLocked! %v", err)
} }
control <- "No way" control <- "No way"
h, err = api.SignTransaction(context.Background(), a, tx, &methodSig) res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if h != nil { if res != nil {
t.Errorf("Expected nil-data, got %h", h) t.Errorf("Expected nil-response, got %v", res)
} }
if err != ErrRequestDenied { if err != ErrRequestDenied {
t.Errorf("Expected ErrRequestDenied! %v", err) t.Errorf("Expected ErrRequestDenied! %v", err)
@ -289,13 +295,13 @@ func TestSignTx(t *testing.T) {
control <- "Y" control <- "Y"
control <- "apassword" control <- "apassword"
h, err = api.SignTransaction(context.Background(), a, tx, &methodSig) res, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
parsedTx := &types.Transaction{} parsedTx := &types.Transaction{}
rlp.Decode(bytes.NewReader(h), parsedTx) rlp.Decode(bytes.NewReader(res.Raw), parsedTx)
//The tx should NOT be modified by the UI //The tx should NOT be modified by the UI
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
@ -303,11 +309,11 @@ func TestSignTx(t *testing.T) {
control <- "Y" control <- "Y"
control <- "apassword" control <- "apassword"
h2, err = api.SignTransaction(context.Background(), a, tx, &methodSig) res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(h, h2) { if !bytes.Equal(res.Raw, res2.Raw) {
t.Error("Expected tx to be unmodified by UI") t.Error("Expected tx to be unmodified by UI")
} }
@ -315,19 +321,19 @@ func TestSignTx(t *testing.T) {
control <- "M" control <- "M"
control <- "apassword" control <- "apassword"
h2, err = api.SignTransaction(context.Background(), a, tx, &methodSig) res2, err = api.SignTransaction(context.Background(), tx, &methodSig)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
parsedTx2 := &types.Transaction{} parsedTx2 := &types.Transaction{}
rlp.Decode(bytes.NewReader(h), parsedTx2) rlp.Decode(bytes.NewReader(res.Raw), parsedTx2)
//The tx should NOT be modified by the UI //The tx should be modified by the UI
if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
t.Errorf("Expected value to be changed, got %v", parsedTx.Value()) t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value())
} }
if bytes.Equal(h, h2) { if bytes.Equal(res.Raw, res2.Raw) {
t.Error("Expected tx to be modified by UI") t.Error("Expected tx to be modified by UI")
} }

View file

@ -24,6 +24,7 @@ import (
"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/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/internal/ethapi"
) )
type AuditLogger struct { type AuditLogger struct {
@ -41,15 +42,17 @@ func (l *AuditLogger) New(ctx context.Context) (accounts.Account, error) {
return l.api.New(ctx) return l.api.New(ctx)
} }
func (l *AuditLogger) SignTransaction(ctx context.Context, from common.MixedcaseAddress, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) { func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) {
l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(), l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(),
"from", from.String(), "tx", args.String(), "tx", args.String(),
"methodSelector", methodSelector) "methodSelector", methodSelector)
b, e := l.api.SignTransaction(ctx, from, args, methodSelector) res, e := l.api.SignTransaction(ctx, args, methodSelector)
if res != nil{
l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(b), "error", e) l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(res.Raw), "error", e)
}else{
return b, e l.log.Info("SignTransaction", "type", "response", "data", res, "error", e)
}
return res, e
} }
func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) {

View file

@ -24,6 +24,7 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -108,10 +109,13 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
} else { } else {
fmt.Printf("to: <contact creation>\n") fmt.Printf("to: <contact creation>\n")
} }
fmt.Printf("from: %v\n", request.From.String()) fmt.Printf("from: %v\n", request.Transaction.From.String())
fmt.Printf("value: %v wei\n", weival) fmt.Printf("value: %v wei\n", weival)
if len(request.Transaction.Data) > 0 { if request.Transaction.Data != nil{
fmt.Printf("data: %v\n", common.Bytes2Hex(request.Transaction.Data)) d := *request.Transaction.Data
if len(d) > 0 {
fmt.Printf("data: %v\n", common.Bytes2Hex(d))
}
} }
if request.Callinfo != "" { if request.Callinfo != "" {
fmt.Printf("\nNote: This Transaction contains data. Review abi-decoding info below:") fmt.Printf("\nNote: This Transaction contains data. Review abi-decoding info below:")
@ -122,9 +126,9 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro
showMetadata(request.Meta) showMetadata(request.Meta)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
if !ui.confirm() { if !ui.confirm() {
return SignTxResponse{request.Transaction, request.From, false, ""}, nil return SignTxResponse{request.Transaction, false, ""}, nil
} }
return SignTxResponse{request.Transaction, request.From, true, ui.readPassword()}, nil return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil
} }
// ApproveSignData prompt the user for confirmation to request to sign data // ApproveSignData prompt the user for confirmation to request to sign data
@ -222,6 +226,9 @@ func (ui *CommandlineUI) ShowError(message string) {
// ShowInfo displays info message to user // ShowInfo displays info message to user
func (ui *CommandlineUI) ShowInfo(message string) { func (ui *CommandlineUI) ShowInfo(message string) {
fmt.Printf("Info: %v\n", message) fmt.Printf("Info: %v\n", message)
} }
func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
fmt.Printf("Transaction signed: %v", tx.Tx.String())
}

View file

@ -188,7 +188,7 @@ func testExternalUI(api *SignerAPI) {
} }
var err error var err error
_, err = api.SignTransaction(ctx, common.MixedcaseAddress{}, TransactionArg{}, nil) _, err = api.SignTransaction(ctx, SendTxArgs{From:common.MixedcaseAddress{}}, nil)
checkErr("SignTransaction", err) checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err) checkErr("Sign", err)

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/signer" "github.com/ethereum/go-ethereum/cmd/signer"
"github.com/ethereum/go-ethereum/cmd/signer/rules/deps" "github.com/ethereum/go-ethereum/cmd/signer/rules/deps"
"github.com/ethereum/go-ethereum/cmd/signer/storage" "github.com/ethereum/go-ethereum/cmd/signer/storage"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"os" "os"
@ -84,7 +85,7 @@ func (r *rulesetUi) checkApproval(jsfunc string, jsarg []byte, err error) error
if err != nil { if err != nil {
return err return err
} }
v, err := r.vm.Call("ApproveTx", nil, string(jsarg)) v, err := r.vm.Call(jsfunc, nil, string(jsarg))
if err != nil { if err != nil {
log.Info("error occurred during execution", "error", err) log.Info("error occurred during execution", "error", err)
@ -106,7 +107,7 @@ func (r *rulesetUi) checkApproval(jsfunc string, jsarg []byte, err error) error
func (r *rulesetUi) ApproveTx(request *signer.SignTxRequest) (signer.SignTxResponse, error) { func (r *rulesetUi) ApproveTx(request *signer.SignTxRequest) (signer.SignTxResponse, error) {
jsonreq, err := json.Marshal(request) jsonreq, err := json.Marshal(request)
if err = r.checkApproval("ApproveTx", jsonreq, err); err == nil { if err = r.checkApproval("ApproveTx", jsonreq, err); err == nil {
return signer.SignTxResponse{Transaction: request.Transaction, From: request.From, Approved: true, Password: ""}, nil return signer.SignTxResponse{Transaction: request.Transaction, Approved: true, Password: ""}, nil
} }
return signer.SignTxResponse{Approved: false}, err return signer.SignTxResponse{Approved: false}, err
} }
@ -156,3 +157,17 @@ func (r *rulesetUi) ShowInfo(message string) {
log.Info(message) log.Info(message)
r.next.ShowInfo(message) r.next.ShowInfo(message)
} }
func (r *rulesetUi) OnApprovedTx(tx ethapi.SignTransactionResult) {
jsonTx, err := json.Marshal(tx)
if err != nil {
log.Warn("failed marshalling transaction", "tx", tx)
return
}
_, err = r.vm.Call("OnApprovedTx", nil, string(jsonTx))
if err != nil {
fmt.Printf("Error in onapprove %v", err)
log.Warn("error occurred during execution", "error", err)
}
}

View file

@ -6,6 +6,8 @@ import (
"github.com/ethereum/go-ethereum/cmd/signer" "github.com/ethereum/go-ethereum/cmd/signer"
"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"
"github.com/ethereum/go-ethereum/internal/ethapi"
"math/big" "math/big"
"testing" "testing"
) )
@ -75,7 +77,7 @@ func TestListRequest(t *testing.T) {
accs[i] = acc accs[i] = acc
} }
js := `function ApproveListing(accounts, meta){ return "Approve" }` js := `function ApproveListing(){ return "Approve" }`
r, err := initRuleEngine(js) r, err := initRuleEngine(js)
if err != nil { if err != nil {
@ -99,12 +101,12 @@ func TestSignTxRequest(t *testing.T) {
function ApproveTx(jsonstr){ function ApproveTx(jsonstr){
console.log(jsonstr) console.log(jsonstr)
r = JSON.parse(jsonstr) r = JSON.parse(jsonstr)
console.log("from", r.from) console.log("transaction.from", r.transaction.from);
console.log("transaction.to", r.transaction.to); console.log("transaction.to", r.transaction.to);
console.log("transaction.value", r.transaction.value); console.log("transaction.value", r.transaction.value);
console.log("transaction.nonce", r.transaction.nonce); console.log("transaction.nonce", r.transaction.nonce);
if(r.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
if(r.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
}` }`
r, err := initRuleEngine(js) r, err := initRuleEngine(js)
@ -125,8 +127,9 @@ func TestSignTxRequest(t *testing.T) {
} }
fmt.Printf("to %v", to.Address().String()) fmt.Printf("to %v", to.Address().String())
resp, err := r.ApproveTx(&signer.SignTxRequest{ resp, err := r.ApproveTx(&signer.SignTxRequest{
Transaction: signer.TransactionArg{To: to}, Transaction: signer.SendTxArgs{
From: *from, From: *from,
To: to},
Callinfo: "", Callinfo: "",
Meta: signer.Metadata{"remoteip", "localip", "inproc"}, Meta: signer.Metadata{"remoteip", "localip", "inproc"},
}) })
@ -243,46 +246,79 @@ const ExampleTxWindow = `
sum = new BigNumber(0) sum = new BigNumber(0)
sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
console.log("Sum so far", sum); console.log("ApproveTx > Sum so far", sum);
console.log("Requested", value.toNumber()); console.log("ApproveTx > Requested", value.toNumber());
// Would we exceed weekly limit ? // Would we exceed weekly limit ?
if (sum.plus(value).lt(limit)){ return sum.plus(value).lt(limit)
// Add this to the storage
newtxs.push({tstamp: new Date().getTime(), value: value});
storage.Put("txs", JSON.stringify(newtxs));
return true;
}
return false;
} }
function ApproveTx(jsonstr){ function ApproveTx(jsonstr){
r = JSON.parse(jsonstr); var r = JSON.parse(jsonstr)
console.log("Requested value ", r.transaction.value)
if (isLimitOk(r.transaction)){ if (isLimitOk(r.transaction)){
return "Approve" return "Approve"
} }
return "Nope" return "Nope"
} }
/**
* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
* 'response_str' contains the return value that will be sent to the external caller.
* The return value from this method is ignore - the reason for having this callback is to allow the
* ruleset to keep track of approved transactions.
*
* When implementing rate-limited rules, this callback should be used.
* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
* then accepts the transaction, this method will be called.
*
* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
*/
function OnApprovedTx(response_str){
console.log("OnApprovedTx > called with data\n\t "+response_str)
var resp = JSON.parse(response_str)
var value = big(resp.tx.value)
var txs = []
// Load stored transactions
var stored = storage.Get('txs');
if(stored != ""){
txs = JSON.parse(stored)
}
// Add this to the storage
txs.push({tstamp: new Date().getTime(), value: value});
storage.Put("txs", JSON.stringify(txs));
}
` `
func dummyTx(value *hexutil.Big) *signer.SignTxRequest { func dummyTx(value *hexutil.Big) *signer.SignTxRequest {
to, _ := mixAddr("000000000000000000000000000000000000dead") to, _ := mixAddr("000000000000000000000000000000000000dead")
from, _ := mixAddr("000000000000000000000000000000000000dead") from, _ := mixAddr("000000000000000000000000000000000000dead")
n := hexutil.Uint64(3)
gas := hexutil.Big(*big.NewInt(21000))
gasPrice := hexutil.Big(*big.NewInt(2000000))
return &signer.SignTxRequest{ return &signer.SignTxRequest{
Transaction: signer.TransactionArg{ Transaction: signer.SendTxArgs{
From: *from,
To: to, To: to,
Value: value, Value: value,
Nonce: &n,
GasPrice: &gas,
Gas: &gasPrice,
}, },
From: *from,
Callinfo: "Warning, all your base are bellong to us", Callinfo: "Warning, all your base are bellong to us",
Meta: signer.Metadata{"remoteip", "localip", "inproc"}, Meta: signer.Metadata{"remoteip", "localip", "inproc"},
} }
} }
func dummySigned(value *big.Int) *types.Transaction {
to := common.HexToAddress("000000000000000000000000000000000000dead")
gas := big.NewInt(21000)
gasPrice := big.NewInt(2000000)
data := make([]byte, 0)
return types.NewTransaction(3, to, value, gas, gasPrice, data)
}
func TestLimitWindow(t *testing.T) { func TestLimitWindow(t *testing.T) {
r, err := initRuleEngine(ExampleTxWindow) r, err := initRuleEngine(ExampleTxWindow)
@ -299,13 +335,21 @@ func TestLimitWindow(t *testing.T) {
h := hexutil.Big(*v) h := hexutil.Big(*v)
// The first three should succeed // The first three should succeed
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
resp, err := r.ApproveTx(dummyTx(&h)) unsigned := dummyTx(&h)
resp, err := r.ApproveTx(unsigned)
if err != nil { if err != nil {
t.Errorf("Unexpected error %v", err) t.Errorf("Unexpected error %v", err)
} }
if !resp.Approved { if !resp.Approved {
t.Errorf("Expected check to resolve to 'Approve'") t.Errorf("Expected check to resolve to 'Approve'")
} }
// Create a dummy signed transaction
response := ethapi.SignTransactionResult{
Tx: dummySigned(v),
Raw: common.Hex2Bytes("deadbeef"),
}
r.OnApprovedTx(response)
} }
// Fourth should fail // Fourth should fail
resp, err := r.ApproveTx(dummyTx(&h)) resp, err := r.ApproveTx(dummyTx(&h))

View file

@ -18,13 +18,12 @@
package signer package signer
import ( import (
"github.com/ethereum/go-ethereum/log" "context"
"sync" "sync"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"context"
) )
type StdIOUI struct { type StdIOUI struct {
@ -114,3 +113,9 @@ func (ui *StdIOUI) ShowInfo(message string) {
log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message) log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message)
} }
} }
func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
err := ui.dispatch("OnApprovedTx", tx, nil)
if err != nil {
log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx)
}
}

View file

@ -23,6 +23,8 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"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"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
) )
type Accounts []Account type Accounts []Account
@ -48,7 +50,7 @@ func (a Account) String() string {
} }
return err.Error() return err.Error()
} }
/*
// TransactionArg represents a Transaction for the signer. // TransactionArg represents a Transaction for the signer.
type TransactionArg struct { type TransactionArg struct {
To *common.MixedcaseAddress `json:"to"` To *common.MixedcaseAddress `json:"to"`
@ -58,11 +60,38 @@ type TransactionArg struct {
Data hexutil.Bytes `json:"data"` Data hexutil.Bytes `json:"data"`
Nonce *hexutil.Uint64 `json:"nonce"` Nonce *hexutil.Uint64 `json:"nonce"`
} }
*/
func (t TransactionArg) String() string { // SendTxArgs represents the arguments to submit a transaction
type SendTxArgs struct {
From common.MixedcaseAddress `json:"from"`
To *common.MixedcaseAddress `json:"to"`
Gas *hexutil.Big `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Nonce *hexutil.Uint64 `json:"nonce"`
// We accept "data" and "input" for backwards-compatibility reasons.
Data *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
}
func (t SendTxArgs) String() string {
s, err := json.Marshal(t) s, err := json.Marshal(t)
if err == nil { if err == nil {
return string(s) return string(s)
} }
return err.Error() return err.Error()
} }
func (args *SendTxArgs) toTransaction() *types.Transaction {
var input []byte
if args.Data != nil {
input = *args.Data
} else if args.Input != nil {
input = *args.Input
}
if args.To == nil {
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), input)
}
return types.NewTransaction(uint64(*args.Nonce), (*args.To).Address(), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), input)
}