diff --git a/cmd/signer/README.md b/cmd/signer/README.md index 66490aaf74..2f25b8b8bb 100644 --- a/cmd/signer/README.md +++ b/cmd/signer/README.md @@ -58,14 +58,14 @@ Some snags and todos to perform changes to things, only approve/deny. Such a UI should be able to start the signer in a more secure mode by telling it that it only wants approve/deny capabilities. -* It would be nice if the signer could collect new 4byte-id:s/method selectors, and have a +* [x] DONE: It would be nice if the signer could collect new 4byte-id:s/method selectors, and have a secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for inclusion upstream. * It should be possible to configure the signer to check if an account is indeed known to it, before passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate -accounts if it immediately returned "unknown account". Similarly, it should be possible to configure -the signer to auto-allow listing (certain) accounts, instead of asking every time. +accounts if it immediately returned "unknown account". + * [x] DONE: Similarly, it should be possible to configure the signer to auto-allow listing (certain) accounts, instead of asking every time. * Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), invoking methods with the following info: @@ -131,6 +131,8 @@ process output for confirmation-requests. ## External API +See the [external api changelog](extapi_changelog.md) for information about changes to this API. + ### Encoding - number: positive integers that are hex encoded - data: hex encoded data @@ -497,7 +499,7 @@ See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to p All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key. - +See the [ui api changelog](intapi_changelog.md) for information about changes to this API. ### ApproveTx @@ -777,6 +779,32 @@ When implementing rate-limited rules, this callback should be used. TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`. +### OnSignerStartup + +This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api, +in k/v-form. + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "OnSignerStartup", + "params": [ + { + "info": { + "extapi_http": "http://localhost:8550", + "extapi_ipc": null, + "extapi_version": "2.0.0", + "intapi_version": "1.2.0" + } + } + ] +} + +``` + ### Rules for UI apis diff --git a/cmd/signer/core/abihelper.go b/cmd/signer/core/abihelper.go index 61dea02ebd..9f7a0f2320 100644 --- a/cmd/signer/core/abihelper.go +++ b/cmd/signer/core/abihelper.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "bytes" + "os" "regexp" ) @@ -158,12 +159,14 @@ func MethodSelectorToAbi(selector string) ([]byte, error) { } type AbiDb struct { - db map[string]string + db map[string]string + customdb map[string]string + customdbPath string } // NewEmptyAbiDB exists for test purposes func NewEmptyAbiDB() (*AbiDb, error) { - return &AbiDb{make(map[string]string)}, nil + return &AbiDb{make(map[string]string), make(map[string]string), ""}, nil } // NewAbiDBFromFile loads signature database from file, and @@ -178,18 +181,74 @@ func NewAbiDBFromFile(path string) (*AbiDb, error) { return db, nil } +// NewAbiDBFromFiles loads both the standard signature database and a custom database. The latter will be used +// to write new values into if they are submitted via the API +func NewAbiDBFromFiles(standard, custom string) (*AbiDb, error) { + + db := &AbiDb{make(map[string]string), make(map[string]string), custom} + db.customdbPath = custom + + raw, err := ioutil.ReadFile(standard) + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.db) + // Custom file may not exist. Will be created during save, if needed + if _, err := os.Stat(custom); err == nil { + raw, err = ioutil.ReadFile(custom) + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.customdb) + } + + return db, nil +} + // 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 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)) } - sig := common.ToHex(id) + sig := common.ToHex(id[:4]) if key, exists := db.db[sig]; exists { return key, nil } + if key, exists := db.customdb[sig]; exists { + return key, nil + } return "", fmt.Errorf("Signature %v not found", sig) } func (db *AbiDb) Size() int { return len(db.db) } + +// saveCustomAbi saves a signature ephemerally. If custom file is used, also saves to disk +func (db *AbiDb) saveCustomAbi(selector, signature string) error { + db.customdb[signature] = selector + if db.customdbPath == "" { + return nil //Not an error per se, just not used + } + d, err := json.Marshal(db.customdb) + if err != nil { + return err + } + err = ioutil.WriteFile(db.customdbPath, d, 0600) + return err +} + +// Adds a signature to the database, if custom database saving is enabled. +// OBS: This method does _not_ validate the correctness of the data, +// it is assumed that the caller has already done so +func (db *AbiDb) AddSignature(selector string, data []byte) error { + if len(data) < 4 { + return nil + } + _, err := db.LookupMethodSelector(data[:4]) + if err == nil { + return nil + } + sig := common.ToHex(data[:4]) + return db.saveCustomAbi(selector, sig) +} diff --git a/cmd/signer/core/abihelper_test.go b/cmd/signer/core/abihelper_test.go index b3a662c71d..00aa8e942b 100644 --- a/cmd/signer/core/abihelper_test.go +++ b/cmd/signer/core/abihelper_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" // "reflect" // "math/big" + "io/ioutil" "math/big" "reflect" ) @@ -210,3 +211,35 @@ func TestSelectorUnmarshalling(t *testing.T) { } } + +func TestCustomABI(t *testing.T) { + d, err := ioutil.TempDir("", "signer-4byte-test") + if err != nil { + t.Fatal(err) + } + filename := fmt.Sprintf("%s/4byte_custom.json", d) + abidb, err := NewAbiDBFromFiles("../4byte.json", filename) + if err != nil { + t.Fatal(err) + } + // Now we'll remove all existing signatures + abidb.db = make(map[string]string) + calldata := common.Hex2Bytes("a52c101edeadbeef") + _, err = abidb.LookupMethodSelector(calldata) + if err == nil { + t.Fatalf("Should not find a match on empty db") + } + if err = abidb.AddSignature("send(uint256)", calldata); err != nil { + t.Fatalf("Failed to save file: %v", err) + } + _, err = abidb.LookupMethodSelector(calldata) + if err != nil { + t.Fatalf("Should find a match for abi signature, got: %v", err) + } + //Check that it wrote to file + abidb2, err := NewAbiDBFromFile(filename) + _, err = abidb2.LookupMethodSelector(calldata) + if err != nil { + t.Fatalf("Save failed: should find a match for abi signature after loading from disk") + } +} diff --git a/cmd/signer/core/api.go b/cmd/signer/core/api.go index 6b96bae094..15c043c496 100644 --- a/cmd/signer/core/api.go +++ b/cmd/signer/core/api.go @@ -56,7 +56,6 @@ type ExternalAPI interface { // 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 prompt the user for confirmation to request to sign Transaction ApproveTx(request *SignTxRequest) (SignTxResponse, error) // ApproveSignData prompt the user for confirmation to request to sign data @@ -77,6 +76,9 @@ type SignerUI interface { // 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) + // OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version + // information + OnSignerStartup(info StartupInfo) } // SignerAPI defines the actual implementation of ExternalAPI @@ -123,9 +125,9 @@ func (m Metadata) String() string { type ( // SignTxRequest contains info about a Transaction to sign SignTxRequest struct { - Transaction SendTxArgs `json:"transaction"` + Transaction SendTxArgs `json:"transaction"` Callinfo *ValidationMessages `json:"call_info"` - Meta Metadata `json:"meta"` + Meta Metadata `json:"meta"` } // SignTxResponse result from SignTxRequest SignTxResponse struct { @@ -180,6 +182,9 @@ type ( Message struct { Text string `json:"text"` } + StartupInfo struct { + Info map[string]interface{} `json:"info"` + } ) var ErrRequestDenied = errors.New("Request denied") @@ -321,7 +326,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, meth err error result SignTxResponse ) - msgs, err:= api.validator.ValidateTransaction(&args, methodSelector) + msgs, err := api.validator.ValidateTransaction(&args, methodSelector) if err != nil { return nil, err } diff --git a/cmd/signer/core/api_test.go b/cmd/signer/core/api_test.go index 2c8e35cc21..c98f8d89cc 100644 --- a/cmd/signer/core/api_test.go +++ b/cmd/signer/core/api_test.go @@ -16,8 +16,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rlp" ) //Used for testing @@ -25,6 +25,9 @@ type HeadlessUI struct { controller chan string } +func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) { +} + func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) { fmt.Printf("OnApproved called") } @@ -33,7 +36,7 @@ func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) switch <-ui.controller { case "Y": - return SignTxResponse{request.Transaction, true, <-ui.controller}, nil + return SignTxResponse{request.Transaction, true, <-ui.controller}, nil case "M": //Modify old := big.Int(request.Transaction.Value) newVal := big.NewInt(0).Add(&old, big.NewInt(1)) @@ -244,22 +247,22 @@ func mkTestTx(from common.MixedcaseAddress) SendTxArgs { nonce := (hexutil.Uint64)(0) data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) tx := SendTxArgs{ - From:from, - To: &to, - Gas: gas, + From: from, + To: &to, + Gas: gas, GasPrice: gasPrice, - Value: value, - Data: &data, - Nonce: nonce} + Value: value, + Data: &data, + Nonce: nonce} return tx } func TestSignTx(t *testing.T) { var ( - list Accounts + list Accounts res, res2 *ethapi.SignTransactionResult - err error + err error ) api, control := setup(t) @@ -276,7 +279,7 @@ func TestSignTx(t *testing.T) { control <- "Y" control <- "wrongpassword" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) + res, err = api.SignTransaction(context.Background(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } @@ -285,7 +288,7 @@ func TestSignTx(t *testing.T) { } control <- "No way" - res, err = api.SignTransaction(context.Background(), tx, &methodSig) + res, err = api.SignTransaction(context.Background(), tx, &methodSig) if res != nil { t.Errorf("Expected nil-response, got %v", res) } diff --git a/cmd/signer/core/cliui.go b/cmd/signer/core/cliui.go index 11e85a1bcb..e84a74d555 100644 --- a/cmd/signer/core/cliui.go +++ b/cmd/signer/core/cliui.go @@ -111,7 +111,7 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro } fmt.Printf("from: %v\n", request.Transaction.From.String()) fmt.Printf("value: %v wei\n", weival) - if request.Transaction.Data != nil{ + if request.Transaction.Data != nil { d := *request.Transaction.Data if len(d) > 0 { fmt.Printf("data: %v\n", common.Bytes2Hex(d)) @@ -119,7 +119,7 @@ func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, erro } if request.Callinfo != nil { fmt.Printf("\nTransaction validation:\n") - for _,m := range request.Callinfo.Messages{ + for _, m := range request.Callinfo.Messages { fmt.Printf(" * %s : %s", m.Typ, m.Message) } fmt.Println() @@ -235,3 +235,11 @@ func (ui *CommandlineUI) ShowInfo(message string) { func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) { fmt.Printf("Transaction signed: %v", tx.Tx.String()) } + +func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) { + + fmt.Printf("------- Signer info ------- ") + for k, v := range info.Info { + fmt.Printf("* %v : %v", k, v) + } +} diff --git a/cmd/signer/core/stdioui.go b/cmd/signer/core/stdioui.go index 26818d7f66..3f11939ca8 100644 --- a/cmd/signer/core/stdioui.go +++ b/cmd/signer/core/stdioui.go @@ -44,11 +44,11 @@ func NewStdIOUI() *StdIOUI { } // dispatch sends a request over the stdio -func (ui *StdIOUI) dispatch(serviceMethod string, args interface{}, reply interface{}) error{ +func (ui *StdIOUI) dispatch(serviceMethod string, args interface{}, reply interface{}) error { var err error - if reply != nil{ + if reply != nil { err = ui.client.Call(nil, serviceMethod, args) - }else{ + } else { err = ui.client.Call(&reply, serviceMethod, args) } if err != nil { @@ -124,3 +124,10 @@ func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) { log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx) } } + +func (ui *StdIOUI) OnSignerStartup(info StartupInfo) { + err := ui.dispatch("OnSignerStartup", info, nil) + if err != nil { + log.Info("Error calling 'OnSignerStartup'", "exc", err.Error(), "info", info) + } +} diff --git a/cmd/signer/core/types.go b/cmd/signer/core/types.go index 63dd9f3a61..9ee15e3486 100644 --- a/cmd/signer/core/types.go +++ b/cmd/signer/core/types.go @@ -23,8 +23,8 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "math/big" "github.com/ethereum/go-ethereum/core/types" + "math/big" ) type Accounts []Account @@ -50,6 +50,7 @@ func (a Account) String() string { } return err.Error() } + type ValidationInfo struct { Typ string `json:"type"` Message string `json:"message"` @@ -74,10 +75,10 @@ type TransactionArg struct { 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"` + 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"` diff --git a/cmd/signer/core/validation.go b/cmd/signer/core/validation.go index 64974e664b..5b57b3afba 100644 --- a/cmd/signer/core/validation.go +++ b/cmd/signer/core/validation.go @@ -46,6 +46,21 @@ type Validator struct { func NewValidator(db *AbiDb) *Validator { return &Validator{db} } +func testSelector(selector string, data []byte) (*decodedCallData, error) { + if selector == "" { + return nil, fmt.Errorf("selector not found") + } + abiData, err := MethodSelectorToAbi(selector) + if err != nil { + return nil, err + } + info, err := parseCallData(data, string(abiData)) + if err != nil { + return nil, err + } + return info, nil + +} // validateCallData checks if the ABI-data + methodselector (if given) can be parsed and seems to match func (v *Validator) validateCallData(msgs *ValidationMessages, data []byte, methodSelector *string) { @@ -57,34 +72,30 @@ func (v *Validator) validateCallData(msgs *ValidationMessages, data []byte, meth return } var ( - selector string - err error + info *decodedCallData + err error ) - // Try to make sense of the data + // Check the provided one if methodSelector != nil { - selector = *methodSelector - } - - if selector == "" { - selector, err = v.db.LookupMethodSelector(data[:4]) + info, err = testSelector(*methodSelector, data) if err != nil { - msgs.warn(fmt.Sprintf("Tx contains data, but the ABI signature could not be found: %v", err)) - return + msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err)) + } else { + msgs.info(info.String()) + //Successfull match. add to db if not there already (ignore errors there) + v.db.AddSignature(*methodSelector, data[:4]) } - } - if selector == "" { - // No more to do that this stage return } - abiData, err := MethodSelectorToAbi(selector) + // Check the db + selector, err := v.db.LookupMethodSelector(data[:4]) if err != nil { - msgs.warn(fmt.Sprintf("Transaction data did not match ABI-interface: %v", err)) + msgs.warn(fmt.Sprintf("Tx contains data, but the ABI signature could not be found: %v", err)) return } - - info, err := parseCallData(data, string(abiData)) + info, err = testSelector(selector, data) if err != nil { - msgs.warn(fmt.Sprintf("Transaction data did not match ABI-interface: %v", err)) + msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err)) } else { msgs.info(info.String()) } @@ -139,7 +150,7 @@ func (v *Validator) validate(msgs *ValidationMessages, txargs *SendTxArgs, metho msgs.crit("Tx destination is the zero address!") } // Validate calldata - v.validateCallData(msgs, data, methodSelector); + v.validateCallData(msgs, data, methodSelector) } return nil } diff --git a/cmd/signer/core/validation_test.go b/cmd/signer/core/validation_test.go index 9b599d36c7..8f58c2bb63 100644 --- a/cmd/signer/core/validation_test.go +++ b/cmd/signer/core/validation_test.go @@ -43,14 +43,14 @@ func dummyTxArgs(t txtestcase) *SendTxArgs { gas := toHexBig(t.g) gasPrice := toHexBig(t.gp) value := toHexBig(t.value) - var( + var ( data, input *hexutil.Bytes ) - if t.d != ""{ + if t.d != "" { a := hexutil.Bytes(common.FromHex(t.d)) data = &a } - if t.i != ""{ + if t.i != "" { a := hexutil.Bytes(common.FromHex(t.i)) input = &a @@ -83,18 +83,18 @@ func TestValidator(t *testing.T) { // Invalid to checksum {from: "000000000000000000000000000000000000dead", to: "000000000000000000000000000000000000dead", n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1}, - // valid 0x000000000000000000000000000000000000dEaD + // valid 0x000000000000000000000000000000000000dEaD {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 0}, - // conflicting input and data + // conflicting input and data {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", - n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", i: "0x02", expectErr: true, }, - // Data can't be parsed + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", i: "0x02", expectErr: true}, + // Data can't be parsed {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", - n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x0102", numMessages:1 }, + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x0102", numMessages: 1}, // Data (on Input) can't be parsed {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", - n: "0x01", g: "0x20", gp: "0x40", value: "0x01", i: "0x0102", numMessages:1 }, + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", i: "0x0102", numMessages: 1}, // Send to 0 {from: "000000000000000000000000000000000000dead", to: "0x0000000000000000000000000000000000000000", n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1}, @@ -106,9 +106,7 @@ func TestValidator(t *testing.T) { n: "0x01", g: "0x20", gp: "0x40", value: "0x01", expectErr: true}, // Small payload for create {from: "000000000000000000000000000000000000dead", to: "", - n: "0x01", g: "0x20", gp: "0x40", value: "0x01",d:"0x01", numMessages: 1}, - - + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", numMessages: 1}, } for i, test := range testcases { msgs, err := v.ValidateTransaction(dummyTxArgs(test), nil) @@ -123,12 +121,12 @@ func TestValidator(t *testing.T) { } if err == nil { got := len(msgs.Messages) - if got != test.numMessages { + if got != test.numMessages { for _, msg := range msgs.Messages { fmt.Printf("* %s: %s\n", msg.Typ, msg.Message) } - t.Errorf("Test %d, expected %d messages, got %d", i,test.numMessages, got) - }else{ + t.Errorf("Test %d, expected %d messages, got %d", i, test.numMessages, got) + } else { //Debug printout, remove later for _, msg := range msgs.Messages { fmt.Printf("* [%d] %s: %s\n", i, msg.Typ, msg.Message) diff --git a/cmd/signer/demo.js b/cmd/signer/demo.js deleted file mode 100644 index 921a35bbbe..0000000000 --- a/cmd/signer/demo.js +++ /dev/null @@ -1,141 +0,0 @@ -/* Demonstrates the following signer methods - -- account_new: generate new password protected account - 1. password: string - - returns account object with address and URL - -- account_list: listing of accounts - no args - - returns array with accounts - -- account_signTransaction: sign transaction and get tx in RLP encoded form back - 1. from: address - 2. passwd: string - 3. transaction: object - - returns signed transaction in RLP form (can be used with eth_sendRawTransaction) - -- account_sign: calculate signature - 1. from: address - 2. passwd: string - 3. data: hex string - - returns signature - -- account_ecRecover: derive address from signature - 1. data: hex string - 2. signature: hex string - - returns address - */ - -var spawn = require('child_process').spawn; - -// by default the signer used the keystore for the mainnet, in this case it is pointed to a non-standard location. -// also it accepts the chainid, by default it uses the chainid for the mainnet. -const signer = spawn('./signer', ['-keystore', '/tmp/keystore', '-chainid', 5]); -const passwd = 'my password'; -var createdAccountAddress = '0x'; -var signData = '0xaabbccdd'; -var signSignature = '0x'; -var keystoreKeyData = '0x'; - -var currentRequest = -1; -function nextRequest() { - currentRequest++; - var req = null; - - if (currentRequest < 7) { - req = { - id: currentRequest, - jsonrpc: "2.0" - }; - - switch (currentRequest) { - case 0: - req.method = 'account_new'; - req.params = [passwd]; - break - case 1: - req.method = 'account_list'; - break; - case 2: - req.method = 'account_signTransaction'; - req.params = [createdAccountAddress, passwd, { - nonce: "0x0", - gasPrice: "0x1234", - gas: "0x55555", - value: "0x1234", - input: "0xabcd", - to: "0x07a565b7ed7d7a678680a4c162885bedbb695fe0" - }]; - break; - case 3: - req.method = 'account_sign'; - req.params = [createdAccountAddress, passwd, signData]; - break; - case 4: - req.method = 'account_ecRecover'; - req.params = [signData, signSignature]; - break; - case 5: - req.method = 'account_export'; - req.params = [createdAccountAddress] - break; - case 6: - req.method = 'account_import'; - req.params = [keystoreKeyData, passwd, passwd]; - break; - } - } - - return req; -} -signer.stdout.on('data', (data) => { - console.log(`${data}`); - - response = JSON.parse(`${data}`); - - switch (response.id) { - case 0: - createdAccountAddress = response.result.address; - break; - case 3: - signSignature = response.result; - break - case 4: - if (createdAccountAddress !== response.result) { - console.error("expected address", createdAccountAddress, "got", response.result); - } else { - //console.log("Address recovered correct"); - } - break; - case 5: - keystoreKeyData = response.result; - break; - } - - var req = nextRequest(); - if (req !== null) { - req = JSON.stringify(req); - console.log(req); - signer.stdin.write(req); - } else { - signer.kill(); - } -}); - -signer.stderr.on('data', (data) => { - console.log(`stderr: ${data}`); -}); - -signer.on('close', (code) => { - //console.log(`signer process exited with code ${code}`); -}); - -// kickstart request cycle -req = JSON.stringify(nextRequest()); -console.log(req); -signer.stdin.write(req); diff --git a/cmd/signer/extapi_changelog.md b/cmd/signer/extapi_changelog.md new file mode 100644 index 0000000000..2014e90ae4 --- /dev/null +++ b/cmd/signer/extapi_changelog.md @@ -0,0 +1,25 @@ +### Changelog for external API + + + +#### 2.0.0 + +* Commit `73abaf04b1372fa4c43201fb1b8019fe6b0a6f8d`, move `from` into `transaction` object in `signTransaction`. This +makes the `accounts_signTransaction` identical to the old `eth_signTransaction`. + + +#### 1.0.0 + +Initial release. + +### Versioning + +The API uses [semantic versioning](https://semver.org/). + +TLDR; Given a version number MAJOR.MINOR.PATCH, increment the: + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. diff --git a/cmd/signer/intapi_changelog.md b/cmd/signer/intapi_changelog.md new file mode 100644 index 0000000000..85103b89a9 --- /dev/null +++ b/cmd/signer/intapi_changelog.md @@ -0,0 +1,45 @@ +### Changelog for internal API (ui-api) + +#### 1.2.0 + +* Add `OnStartup` method, to provide the UI with information about what API version +the signer uses (both internal and external) aswell as build-info and external api. + +Example call: +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "OnSignerStartup", + "params": [ + { + "info": { + "extapi_http": "http://localhost:8550", + "extapi_ipc": null, + "extapi_version": "2.0.0", + "intapi_version": "1.2.0" + } + } + ] +} +``` + +#### 1.1.0 + +* Add `OnApproved` method + +#### 1.0.0 + +Initial release. + +### Versioning + +The API uses [semantic versioning](https://semver.org/). + +TLDR; Given a version number MAJOR.MINOR.PATCH, increment the: + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. diff --git a/cmd/signer/main.go b/cmd/signer/main.go index fa612f0258..65a50046b6 100644 --- a/cmd/signer/main.go +++ b/cmd/signer/main.go @@ -27,15 +27,21 @@ import ( "context" "encoding/json" + "github.com/ethereum/go-ethereum/cmd/signer/core" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" "gopkg.in/urfave/cli.v1" - "github.com/ethereum/go-ethereum/cmd/signer/core" ) +// EXT_API_VERSION -- see extapi_changelog.md +const EXT_API_VERSION = "2.0.0" + +// INT_API_VERSION -- see intapi_changelog.md +const INT_API_VERSION = "1.2.0" + func main() { app := cli.NewApp() @@ -66,6 +72,11 @@ func main() { Usage: "File containing 4byte-identifiers", Value: "./4byte.json", }, + cli.StringFlag{ + Name: "4bytedb-custom", + Usage: "File used for writing new 4byte-identifiers submitted via API", + Value: "./4byte-custom.json", + }, cli.StringFlag{ Name: "auditlog", Usage: "File used to emit audit logs. Set to \"\" to disable", @@ -108,7 +119,7 @@ func main() { if c.Bool("stdio-ui") { log.Info("Using stdin/stdout as UI-channel") } - db, err := core.NewAbiDBFromFile(c.String("4bytedb")) + db, err := core.NewAbiDBFromFiles(c.String("4bytedb"), c.String("4bytedb-custom")) if err != nil { utils.Fatalf(err.Error()) @@ -142,7 +153,6 @@ func main() { if err = server.RegisterName("account", api); err != nil { utils.Fatalf("Could not register signer API: %v", err) } - //server.ListServices() // Import from file if rfile := c.String("requestfile"); rfile != "" { @@ -155,13 +165,22 @@ func main() { if listener, err = net.Listen("tcp", endpoint); err != nil { utils.Fatalf("Could not start http listener: %v", err) } - log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint)) + extapi_url := fmt.Sprintf("http://%s", endpoint) + log.Info("HTTP endpoint opened", "url", extapi_url) cors := []string{"*"} if c.Bool("stdio-ui-test") { log.Info("Performing UI test") go testExternalUI(api_impl) } + ui.OnSignerStartup(core.StartupInfo{ + Info: map[string]interface{}{ + "extapi_version": EXT_API_VERSION, + "intapi_version": INT_API_VERSION, + "extapi_http": extapi_url, + "extapi_ipc": nil, + }, + }) rpc.NewHTTPServer(cors, server).Serve(listener) @@ -189,7 +208,7 @@ func testExternalUI(api *core.SignerAPI) { } var err error - _, err = api.SignTransaction(ctx, core.SendTxArgs{From:common.MixedcaseAddress{}}, nil) + _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) checkErr("SignTransaction", err) _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) checkErr("Sign", err) diff --git a/cmd/signer/rules/rules.go b/cmd/signer/rules/rules.go index 2cac0b721f..cdab8c776f 100644 --- a/cmd/signer/rules/rules.go +++ b/cmd/signer/rules/rules.go @@ -106,9 +106,9 @@ func (r *rulesetUi) execute(jsfunc string, jsarg interface{}) (otto.Value, error } // Now, we call foobar(JSON.parse()). var call string - if(len(jsonbytes) > 0){ + if len(jsonbytes) > 0 { call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes)) - }else{ + } else { call = fmt.Sprintf("%v()", jsfunc) } return vm.Run(call) diff --git a/cmd/signer/rules/rules_test.go b/cmd/signer/rules/rules_test.go index 80024e9801..44e47f6f30 100644 --- a/cmd/signer/rules/rules_test.go +++ b/cmd/signer/rules/rules_test.go @@ -57,6 +57,9 @@ func mixAddr(a string) (*common.MixedcaseAddress, error) { type alwaysDenyUi struct{} +func (alwaysDenyUi) OnSignerStartup(info core.StartupInfo) { +} + func (alwaysDenyUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { return core.SignTxResponse{request.Transaction, false, ""}, nil } @@ -223,6 +226,8 @@ func (d *dummyUi) ShowInfo(message string) { func (d *dummyUi) OnApprovedTx(tx ethapi.SignTransactionResult) { d.calls = append(d.calls, "OnApprovedTx") } +func (d *dummyUi) OnSignerStartup(info core.StartupInfo) { +} //TestForwarding tests that the rule-engine correctly dispatches requests to the next caller func TestForwarding(t *testing.T) { @@ -492,6 +497,9 @@ type dontCallMe struct { t *testing.T } +func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) { +} + func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { d.t.Fatalf("Did not expect next-handler to be called") return core.SignTxResponse{}, core.ErrRequestDenied diff --git a/cmd/signer/storage/storage.go b/cmd/signer/storage/storage.go index 502e20d23d..6b2f8c6660 100644 --- a/cmd/signer/storage/storage.go +++ b/cmd/signer/storage/storage.go @@ -62,7 +62,7 @@ func (s *EphemeralStorage) Get(key string) string { func (s *EphemeralStorage) New(namespace string) Storage { child := &EphemeralStorage{ data: make(map[string]string), - namespace: fmt.Sprintf("%s.%s", namespace), + namespace: fmt.Sprintf("%s.%s", s.namespace, namespace), } return child } diff --git a/cmd/signer/test.js b/cmd/signer/test.js deleted file mode 100644 index 4f50f1834b..0000000000 --- a/cmd/signer/test.js +++ /dev/null @@ -1,24 +0,0 @@ -const net = require('net'); -const path = require('path'); - -let conn = net.connect(path.join('\\\\?\\pipe', 'ethereum-signer')) - -const req = { - id: 1234, - jsonrpc: '2.0', - //method: 'account_list', - method: 'account_signTransaction', - params: ['0xaabbccddaabbccddaabbccddaabbccddaabbccdd', { - to: '0x0011223344556677889900112233445566778899', - value: '0x123450000', - data: '0xabcdef', - gas: '0x12345', - gasPrice: '0x67890' - }] -}; - -conn.on('data', (data) => { - console.log(data.toString()); -}); - -conn.write(JSON.stringify(req));