diff --git a/cmd/signer/README.md b/cmd/signer/README.md index 104d163425..9a09870dba 100644 --- a/cmd/signer/README.md +++ b/cmd/signer/README.md @@ -377,7 +377,175 @@ None These methods needs to be implemented by a UI listener. -still work in progress + +By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with +denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented. +See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'. + +**work in progress** + + +### ApproveTx + +Invoked when there's a transaction for approval. + + +#### Sample call + +```json + +{ + "jsonrpc": "2.0", + "method": "ApproveTx", + "params": [{ + "transaction": { + "to": "0xae967917c465db8578ca9024c205720b1a3651A9", + "gas": "0x333", + "gasPrice": "0x123", + "value": "0x10", + "data": "0xd7a5865800000000000000000000000000000000000000000000000000000000000000ff", + "nonce": "0x0" + }, + "fromaccount": "0xAe967917c465db8578ca9024c205720b1a3651A9", + "call_info": "Warning! Could not validate ABI-data against calldata\nSupplied ABI spec does not contain method signature in data: 0xd7a58658", + "meta": { + "remote": "127.0.0.1:34572", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + }], + "id": 1 +} +``` + +### ApproveExport + +Invoked when a request to export an account has been made. + +#### Sample call + +```json + +{ + "jsonrpc": "2.0", + "id": 7, + "method": "ApproveExport", + "params": [ + { + "address": "0x0000000000000000000000000000000000000000", + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + +### ApproveListing + +Invoked when a request for account listing has been made. + +#### Sample call + +```json + +{ + "jsonrpc": "2.0", + "id": 5, + "method": "ApproveListing", + "params": [ + { + "accounts": [ + { + "type": "Account", + "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42", + "address": "0x123409812340981234098123409812deadbeef42" + }, + { + "type": "Account", + "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42", + "address": "0xcafebabedeadbeef34098123409812deadbeef42" + } + ], + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + + +### ApproveSignData + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "ApproveSignData", + "params": [ + { + "address": "0x123409812340981234098123409812deadbeef42", + "raw_data": "0x01020304", + "message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310", + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + +### ShowInfo + +The UI should show the info to the user. Does not expect response. + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 9, + "method": "ShowInfo", + "params": [ + { + "text": "Tests completed" + } + ] +} + +``` + +### ShowError + +The UI should show the info to the user. Does not expect response. + +```json + +{ + "jsonrpc": "2.0", + "id": 2, + "method": "ShowError", + "params": [ + { + "text": "Testing 'ShowError'" + } + ] +} + +``` ### Rules for UI apis @@ -398,6 +566,9 @@ A UI should conform to the following rules. along with the UI. + + + ## TODOs Some snags and todos diff --git a/cmd/signer/abihelper.go b/cmd/signer/abihelper.go index eca12eba2c..b317b54064 100644 --- a/cmd/signer/abihelper.go +++ b/cmd/signer/abihelper.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "regexp" + "reflect" ) type decodedArgument struct { @@ -82,18 +83,29 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) { if method == nil { return nil, fmt.Errorf("Supplied ABI spec does not contain method signature in data: 0x%x", sigdata) } + var v interface{} + method.Inputs.Unpack(v, argdata) + + ref := reflect.ValueOf(v) + values := make([]interface{}, ref.NumField()) + + for i := 0; i < ref.NumField(); i++ { + values[i] = ref.Field(i).Interface() + } + fmt.Println(values) + + decoded := decodedCallData{signature: method.Sig(), name: method.Name} +/* for n, argument := range method.Inputs { - value, err := abi.ToGoType(n*32, argument.Type, argdata) - if err != nil { return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err) } else { decodedArg := decodedArgument{ soltype: argument, - value: value, + value: reflect.ValueOf(v,) , } decoded.inputs = append(decoded.inputs, decodedArg) } @@ -103,20 +115,19 @@ func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) { // original data. If we didn't do that, it would e.g. be possible to stuff extra data into the arguments, which // is not detected by merely decoding the data. + var ( - gotypedArguments = make([]interface{}, len(decoded.inputs)) encoded []byte ) - for i, arg := range decoded.inputs { - gotypedArguments[i] = arg.value - } - encoded, err = abispec.Pack(method.Name, gotypedArguments...) + encoded, err = method.Inputs.Pack(v) + if !bytes.Equal(encoded, calldata) { exp := common.Bytes2Hex(encoded) was := common.Bytes2Hex(calldata) 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 } diff --git a/cmd/signer/api.go b/cmd/signer/api.go index 5f653d80e4..163fef41c9 100644 --- a/cmd/signer/api.go +++ b/cmd/signer/api.go @@ -26,6 +26,8 @@ import ( "bytes" + "reflect" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/usbwallet" @@ -56,7 +58,7 @@ type ( // SignTxRequest contains info about a Transaction to sign SignTxRequest struct { Transaction TransactionArg `json:"transaction"` - From common.MixedcaseAddress `json:"fromaccount"` + From common.MixedcaseAddress `json:"from"` Callinfo string `json:"call_info"` Meta Metadata `json:"meta"` } @@ -64,7 +66,7 @@ type ( SignTxResponse struct { //The UI may make changes to the TX Transaction TransactionArg `json:"transaction"` - From common.MixedcaseAddress `json:"fromaccount"` + From common.MixedcaseAddress `json:"from"` Approved bool `json:"approved"` Password string `json:"password"` } @@ -260,7 +262,7 @@ func logDiff(original *SignTxRequest, new *SignTxResponse) bool { modified = true log.Info("Sender-account changed by UI", "was", f0, "is", f1) } - if t0, t1 := original.Transaction.To, new.Transaction.To; 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) modified = true } diff --git a/cmd/signer/pythonsigner.py b/cmd/signer/pythonsigner.py index 636fbd7acf..0a216310e9 100644 --- a/cmd/signer/pythonsigner.py +++ b/cmd/signer/pythonsigner.py @@ -48,71 +48,87 @@ class StdIOHandler(): pass @public - def ApproveTx(self,transaction = None, fromaccount = None, call_info = None, meta = None): + def ApproveTx(self,req): """ Example request: - - {"jsonrpc":"2.0","method":"ApproveTx","params":{"transaction":{"to":null,"gas":null,"gasPrice":null,"value":null,"data":"0x","nonce":null},"from":"0x0000000000000000000000000000000000000000","call_info":null,"meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":2} + { + "jsonrpc": "2.0", + "method": "ApproveTx", + "params": [{ + "transaction": { + "to": "0xae967917c465db8578ca9024c205720b1a3651A9", + "gas": "0x333", + "gasPrice": "0x123", + "value": "0x10", + "data": "0xd7a5865800000000000000000000000000000000000000000000000000000000000000ff", + "nonce": "0x0" + }, + "from": "0xAe967917c465db8578ca9024c205720b1a3651A9", + "call_info": "Warning! Could not validate ABI-data against calldata\nSupplied ABI spec does not contain method signature in data: 0xd7a58658", + "meta": { + "remote": "127.0.0.1:34572", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + }], + "id": 1 + } :param transaction: transaction info :param call_info: info abou the call, e.g. if ABI info could not be :param meta: metadata about the request, e.g. where the call comes from :return: """ + transaction = req.get('transaction') + _from = req.get('from') + call_info = req.get('call_info') + meta = req.get('meta') + return { "approved" : False, - "transaction" : None, - #"fromaccount" : fromaccount, - "password" : None, + "transaction" : transaction, + "from" : _from, +# "password" : None, } @public - def ApproveSignData(self,address=None, raw_data = None, message = None, hash = None, meta = None): + def ApproveSignData(self, req): """ Example request - {"jsonrpc":"2.0","method":"ApproveSignData","params":{"address":"0x0000000000000000000000000000000000000000","raw_data":"0x01020304","message":"\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004","hash":"0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310","meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":3} - - """ - return {"approved": False, - "password" : None} + return {"approved": False, "password" : None} @public - def ApproveExport(self,address = None, meta = None): + def ApproveExport(self, req): """ Example request - {"jsonrpc":"2.0","method":"ApproveExport","params":{"address":"0x0000000000000000000000000000000000000000","meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":5} - """ return {"approved" : False} @public - def ApproveImport(self,meta = None): + def ApproveImport(self, req): """ Example request - {"jsonrpc":"2.0","method":"ApproveImport","params":{"Meta":{}},"id":4} - """ - return {"approved" : False, "old_password": "", "new_password": ""} + return { "approved" : False, "old_password": "", "new_password": ""} @public - def ApproveListing(self,accounts=None, meta = None): + def ApproveListing(self, req): """ Example request - {"jsonrpc":"2.0","method":"ApproveListing","params":{"accounts":[{"type":"Account","url":"keystore:///home/user/ethereum/keystore/file","address":"0x010101010101010010101010101abcdef0001337"}],"Meta":{}},"id":2} """ return {'accounts': []} @public - def ApproveNewAccount(self,meta = None): + def ApproveNewAccount(self, req): """ Example request - {"jsonrpc":"2.0","method":"ApproveNewAccount","params":{"meta":{"remote":"signer binary","local":"main","scheme":"in-proc"}},"id":5} - :return: """ - return {"approved": False, "password": ""} + return {"approved": False, + #"password": "" + } @public def ShowError(self,message = {}): @@ -137,6 +153,7 @@ class StdIOHandler(): :param message: to display :return:nothing """ + if 'text' in message.keys(): sys.stdout.write("Error: {}\n".format( message['text'])) return