signer: implement custom 4byte databsae that saves submitted signatures

This commit is contained in:
Martin Holst Swende 2018-02-17 15:39:36 +01:00
parent 8bc021386f
commit 76a8c7caf0
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
18 changed files with 326 additions and 241 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -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")
}
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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"`

View file

@ -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
}

View file

@ -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)

View file

@ -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);

View file

@ -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.

View file

@ -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.

View file

@ -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)

View file

@ -106,9 +106,9 @@ func (r *rulesetUi) execute(jsfunc string, jsarg interface{}) (otto.Value, error
}
// Now, we call foobar(JSON.parse(<jsondata>)).
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)

View file

@ -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

View file

@ -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
}

View file

@ -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));