mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 01:43:47 +00:00
signer: implement custom 4byte databsae that saves submitted signatures
This commit is contained in:
parent
8bc021386f
commit
76a8c7caf0
18 changed files with 326 additions and 241 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
25
cmd/signer/extapi_changelog.md
Normal file
25
cmd/signer/extapi_changelog.md
Normal 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.
|
||||
45
cmd/signer/intapi_changelog.md
Normal file
45
cmd/signer/intapi_changelog.md
Normal 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.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
Loading…
Reference in a new issue