cmd/signer: fix bugs, improve abi detection, abi argument display

This commit is contained in:
Martin Holst Swende 2017-12-13 11:21:05 +01:00
parent b8c5657235
commit 4fa2cb0332
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
7 changed files with 157 additions and 39 deletions

View file

@ -24,10 +24,12 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"io/ioutil" "io/ioutil"
"strings" "strings"
"regexp"
) )
type decodedArgument struct { type decodedArgument struct {
soltype string soltype abi.Argument
value interface{} value interface{}
} }
type decodedCallData struct { type decodedCallData struct {
@ -36,9 +38,19 @@ type decodedCallData struct {
inputs []decodedArgument inputs []decodedArgument
} }
// String implements stringer interface, tries to use the underlying value-type
func (arg decodedArgument) String() string { func (arg decodedArgument) String() string {
return fmt.Sprintf("%v: %v", arg.soltype, arg.value) var value string
switch arg.value.(type) {
case fmt.Stringer:
value = arg.value.(fmt.Stringer).String()
default:
value = fmt.Sprintf("%v", arg.value)
} }
return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value)
}
// String implements stringer interface for decodedCallData
func (cd decodedCallData) String() string { func (cd decodedCallData) String() string {
args := make([]string, len(cd.inputs)) args := make([]string, len(cd.inputs))
for i, arg := range cd.inputs { for i, arg := range cd.inputs {
@ -55,16 +67,16 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata)) return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata))
} }
abispec, err := abi.JSON(strings.NewReader(abidata))
if err != nil {
return nil, fmt.Errorf("Failed parsing JSON ABI: %v", err)
}
sigdata, argdata := calldata[:4], calldata[4:] sigdata, argdata := calldata[:4], calldata[4:]
if len(argdata)%32 != 0 { if len(argdata)%32 != 0 {
return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata)) return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata))
} }
abispec, err := abi.JSON(strings.NewReader(abidata))
if err != nil {
return nil, fmt.Errorf("Failed parsing JSON ABI: %v, abidata: %v", err, abidata)
}
method := abispec.MethodById(sigdata) method := abispec.MethodById(sigdata)
if method == nil { if method == nil {
return nil, fmt.Errorf("Supplied ABI spec does not contain method signature in data: 0x%x", sigdata) return nil, fmt.Errorf("Supplied ABI spec does not contain method signature in data: 0x%x", sigdata)
@ -79,7 +91,7 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err) return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err)
} else { } else {
decodedArg := decodedArgument{ decodedArg := decodedArgument{
soltype: argument.Type.String(), soltype: argument,
value: value, value: value,
} }
decoded.inputs = append(decoded.inputs, decodedArg) decoded.inputs = append(decoded.inputs, decodedArg)
@ -102,15 +114,50 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) {
if !bytes.Equal(encoded, calldata) { if !bytes.Equal(encoded, calldata) {
exp := common.Bytes2Hex(encoded) exp := common.Bytes2Hex(encoded)
was := common.Bytes2Hex(calldata) was := common.Bytes2Hex(calldata)
return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. %v \nWant %s\nHave %s", decoded, was, exp) return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig())
} }
return &decoded, nil return &decoded, nil
} }
// MethodSelectorToAbi converts a method selector into an ABI struct. The returned data is a valid json string
// which can be consumed by the standard abi package.
func MethodSelectorToAbi(selector string) ([]byte, error) {
re := regexp.MustCompile("^([^\\)]+)\\(([a-z0-9,\\[\\]]*)\\)")
type fakeArg struct {
Type string `json:"type"`
}
type fakeABI struct {
Name string `json:"name"`
Type string `json:"type"`
Inputs []fakeArg `json:"inputs"`
}
groups := re.FindStringSubmatch(selector)
if len(groups) != 3 {
return nil, fmt.Errorf("Did not match: %v (%v matches)", selector, len(groups))
}
name := groups[1]
args := groups[2]
arguments := make([]fakeArg, 0)
if len(args) > 0 {
for _, arg := range strings.Split(args, ",") {
arguments = append(arguments, fakeArg{arg})
}
}
abicheat := fakeABI{
name, "function", arguments,
}
return json.Marshal([]fakeABI{abicheat})
}
type abiDb struct { type abiDb struct {
db map[string]string db map[string]string
} }
// NewAbiDBFromFile loads signature database from file, and
// errors if the file is not valid json. Does no other validation of contents
func NewAbiDBFromFile(path string) (*abiDb, error) { func NewAbiDBFromFile(path string) (*abiDb, error) {
raw, err := ioutil.ReadFile(path) raw, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
@ -121,9 +168,9 @@ func NewAbiDBFromFile(path string) (*abiDb, error) {
return db, nil return db, nil
} }
// LookupABI checks the given 4byte-sequence against the known ABI methods. // LookupMethodSelector checks the given 4byte-sequence against the known ABI methods.
// OBS: This method does not validate the match, it's assumed the caller will do so // OBS: This method does not validate the match, it's assumed the caller will do so
func (db *abiDb) LookupABI(id []byte) (string, error) { func (db *abiDb) LookupMethodSelector(id []byte) (string, error) {
if len(id) != 4 { if len(id) != 4 {
return "", fmt.Errorf("Expected 4-byte id, got %d", len(id)) return "", fmt.Errorf("Expected 4-byte id, got %d", len(id))
} }

View file

@ -17,7 +17,10 @@
package main package main
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"strings"
"testing" "testing"
) )
@ -84,3 +87,36 @@ func TestCalldataDecoding(t *testing.T) {
} }
} }
} }
func TestSelectorUnmarshalling(t *testing.T) {
var (
db *abiDb
err error
abistring []byte
abistruct abi.ABI
)
db, err = NewAbiDBFromFile("4byte.json")
if err != nil {
t.Fatal(err)
}
fmt.Printf("DB size %v\n", db.Size())
for id, selector := range db.db {
abistring, err = MethodSelectorToAbi(selector)
if err != nil {
t.Error(err)
return
}
abistruct, err = abi.JSON(strings.NewReader(string(abistring)))
if err != nil {
t.Error(err)
return
}
m := abistruct.MethodById(common.Hex2Bytes(id[2:]))
if m.Sig() != selector {
t.Errorf("Expected equality: %v != %v", m.Sig(), selector)
}
}
}

View file

@ -56,7 +56,7 @@ type (
SignTxRequest struct { SignTxRequest struct {
Transaction TransactionArg `json:"transaction"` Transaction TransactionArg `json:"transaction"`
From common.Address `json:"fromaccount"` From common.Address `json:"fromaccount"`
Callinfo fmt.Stringer `json:"call_info"` Callinfo string `json:"call_info"`
Meta Metadata `json:"meta"` Meta Metadata `json:"meta"`
} }
// SignTxResponse result from SignTxRequest // SignTxResponse result from SignTxRequest
@ -299,7 +299,7 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
// SignTransaction signs the given Transaction and returns it in an RLP encoded form // SignTransaction signs the given Transaction and returns it in an RLP encoded form
// that can be posted to `eth_sendRawTransaction`. // that can be posted to `eth_sendRawTransaction`.
func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address, args TransactionArg, methodSig *string) (hexutil.Bytes, error) { func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address, args TransactionArg, methodSelector *string) (hexutil.Bytes, error) {
var ( var (
err error err error
@ -311,19 +311,27 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
data := args.Data data := args.Data
if len(data) > 3 { if len(data) > 3 {
// Try to make sense of the data // Try to make sense of the data
var abidata string var selector string
if methodSig == nil { if methodSelector == nil {
abidata, err = api.abidb.LookupABI(data[:4]) selector, err = api.abidb.LookupMethodSelector(data[:4])
if err != nil { if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not locate ABI", err} req.Callinfo = errorWrapper{"Warning! Could not locate ABI", err}.String()
} }
} else { } else {
abidata = *methodSig selector = *methodSelector
} }
if abidata != "" { if selector != "" {
req.Callinfo, err = parseCallData(data, abidata) abidata, err := MethodSelectorToAbi(selector)
if err != nil { if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err} req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else {
var info *decodedCallData
info, err = parseCallData(data, string(abidata))
if err != nil {
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}.String()
} else {
req.Callinfo = info.String()
}
} }
} }
} }
@ -338,8 +346,12 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
// 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)
acc := accounts.Account{Address: result.From} var (
wallet, err := api.am.Find(acc) acc accounts.Account
wallet accounts.Wallet
)
acc = accounts.Account{Address: result.From}
wallet, err = api.am.Find(acc)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -8,6 +8,8 @@ import (
"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"
"github.com/ethereum/go-ethereum/rlp"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
"os" "os"
@ -291,10 +293,12 @@ func TestSignTx(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if h == nil || len(h) != 118 { parsedTx := &types.Transaction{}
t.Errorf("Expected 181 byte rlp-data (got %d bytes)", len(h)) rlp.Decode(bytes.NewReader(h), parsedTx)
//The tx should NOT be modified by the UI
if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 {
t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value())
} }
//The tx is NOT modified by the UI
control <- "Y" control <- "Y"
control <- "apassword" control <- "apassword"
@ -314,6 +318,14 @@ func TestSignTx(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
parsedTx2 := &types.Transaction{}
rlp.Decode(bytes.NewReader(h), parsedTx2)
//The tx should NOT be modified by the UI
if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 {
t.Errorf("Expected value to be changed, got %v", parsedTx.Value())
}
if bytes.Equal(h, h2) { if bytes.Equal(h, h2) {
t.Error("Expected tx to be modified by UI") t.Error("Expected tx to be modified by UI")
} }

View file

@ -97,24 +97,29 @@ func showMetadata(metadata Metadata) {
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
ui.mu.Lock() ui.mu.Lock()
defer ui.mu.Unlock() defer ui.mu.Unlock()
weival := request.Transaction.Value weival := request.Transaction.Value.ToInt()
toval := ""
if request.Transaction.To != nil {
toval = request.Transaction.To.Hex()
}
fmt.Printf("--------- Transaction request-------------\n") fmt.Printf("--------- Transaction request-------------\n")
fmt.Printf("to: %v\n", request.Transaction.To) fmt.Printf("to: %v\n", toval)
fmt.Printf("from: %v\n", request.From.Hex()) fmt.Printf("from: %v\n", request.From.Hex())
fmt.Printf("value: %v wei\n", weival) fmt.Printf("value: %v wei\n", weival)
if len(request.Transaction.Data) > 0 { if len(request.Transaction.Data) > 0 {
fmt.Printf("data: %v\n", common.Bytes2Hex(request.Transaction.Data)) fmt.Printf("data: %v\n", common.Bytes2Hex(request.Transaction.Data))
} }
if request.Callinfo != nil { 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:")
fmt.Printf("\nCall info:\n\t%v\n", request.Callinfo.String()) fmt.Printf("\nCall info:\n\t%v\n", request.Callinfo)
} }
fmt.Printf("\n") fmt.Printf("\n")
showMetadata(request.Meta) showMetadata(request.Meta)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
if !ui.confirm() {
return SignTxResponse{request.Transaction, request.From, false, ""}, nil
}
return SignTxResponse{request.Transaction, request.From, true, ui.readPassword()}, nil return SignTxResponse{request.Transaction, request.From, true, ui.readPassword()}, nil
} }
@ -130,6 +135,9 @@ func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResp
fmt.Printf("message hash: %v\n", request.Hash) fmt.Printf("message hash: %v\n", request.Hash)
fmt.Printf("-------------------------------------------\n") fmt.Printf("-------------------------------------------\n")
showMetadata(request.Meta) showMetadata(request.Meta)
if !ui.confirm() {
return SignDataResponse{false, ""}, nil
}
return SignDataResponse{true, ui.readPassword()}, nil return SignDataResponse{true, ui.readPassword()}, nil
} }
@ -196,7 +204,10 @@ func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccou
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(request.Meta) showMetadata(request.Meta)
return NewAccountResponse{ui.confirm(), ui.readPassword()}, nil if !ui.confirm() {
return NewAccountResponse{false, ""}, nil
}
return NewAccountResponse{true, ui.readPassword()}, nil
} }
// ShowError displays error message to user // ShowError displays error message to user

View file

@ -190,7 +190,7 @@ func testExternalUI(api *SignerAPI) {
} }
var err error var err error
_, err = api.SignTransaction(ctx, common.Address{}, TransactionArg{}, nil); _, err = api.SignTransaction(ctx, common.Address{}, TransactionArg{}, nil)
checkErr("SignTransaction", err) checkErr("SignTransaction", err)
_, err = api.Sign(ctx, common.Address{}, common.Hex2Bytes("01020304")) _, err = api.Sign(ctx, common.Address{}, common.Hex2Bytes("01020304"))
checkErr("Sign", err) checkErr("Sign", err)