mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 01:43:47 +00:00
cmd/signer: refactors, removed channels in ui comms, added UI-api via stdin/out
This commit is contained in:
parent
d17a51829d
commit
d0c0b9bacd
8 changed files with 605 additions and 243 deletions
|
|
@ -25,6 +25,7 @@ The signer accepts the following command line options:
|
||||||
--rpcaddr value HTTP-RPC server listening interface (default: "localhost")
|
--rpcaddr value HTTP-RPC server listening interface (default: "localhost")
|
||||||
--rpcport value HTTP-RPC server listening port (default: 8550)
|
--rpcport value HTTP-RPC server listening port (default: 8550)
|
||||||
--4bytedb value File containing 4byte-identifiers (default: "./4byte.json")
|
--4bytedb value File containing 4byte-identifiers (default: "./4byte.json")
|
||||||
|
--stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when the signer is started by an external process.
|
||||||
--help, -h show help
|
--help, -h show help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -34,15 +35,39 @@ Example:
|
||||||
signer -keystore /my/keystore -chainid 4
|
signer -keystore /my/keystore -chainid 4
|
||||||
```
|
```
|
||||||
|
|
||||||
## Communicating
|
## Communication
|
||||||
|
|
||||||
The signer listens to HTTP requests on `rpcaddr`:`rpcport`. The messages are expected to be JSON
|
### External API
|
||||||
[jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
|
||||||
|
|
||||||
Some of these call can require user interaction. Clients must be aware that responses may be deplayed significanlty or
|
The signer listens to HTTP requests on `rpcaddr`:`rpcport`. The messages are
|
||||||
may never be received if a users decideds to ignore the confirmation request.
|
expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
||||||
|
|
||||||
## API
|
Some of these call can require user interaction. Clients must be aware that responses
|
||||||
|
may be deplayed significanlty or may never be received if a users decideds to ignore the confirmation request.
|
||||||
|
|
||||||
|
The External API is **untrusted** : it does not accept credentials over this api, nor does it expect
|
||||||
|
that requests have any authority.
|
||||||
|
|
||||||
|
### UI API
|
||||||
|
|
||||||
|
The signer has one native console-based UI, for operation without any standalone tools.
|
||||||
|
However, there is also an API to communicate with an external UI. To enable that UI,
|
||||||
|
the signer needs to be executed with the `--stdio-ui` option, which allocates the
|
||||||
|
`stdin`/`stdout` for the UI-api.
|
||||||
|
|
||||||
|
An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`.
|
||||||
|
|
||||||
|
The model is as follows:
|
||||||
|
|
||||||
|
* The user starts the UI app (`pythonsigner.py`).
|
||||||
|
* The UI app starts the `signer` with `--stdio-ui`, and listens to the
|
||||||
|
process output for confirmation-requests.
|
||||||
|
* The `signer` opens the external http api.
|
||||||
|
* When the `signer` receives requests, it sends a `jsonrpc` request via `stdout`.
|
||||||
|
* The UI app prompts the user accordingly, and responds to the `signer`
|
||||||
|
* The `signer` signs (or not), and responds to the original request.
|
||||||
|
|
||||||
|
## External API
|
||||||
|
|
||||||
### Encoding
|
### Encoding
|
||||||
- number: positive integers that are hex encoded
|
- number: positive integers that are hex encoded
|
||||||
|
|
@ -348,3 +373,8 @@ None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## UI API
|
||||||
|
|
||||||
|
These methods needs to be implemented by a UI listener.
|
||||||
|
|
||||||
|
still work in progress
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||||
|
|
@ -51,57 +52,63 @@ type Metadata struct {
|
||||||
|
|
||||||
// types for the requests/response types
|
// types for the requests/response types
|
||||||
type (
|
type (
|
||||||
// SignTxRequest contains info about a transaction to sign
|
// SignTxRequest contains info about a Transaction to sign
|
||||||
SignTxRequest struct {
|
SignTxRequest struct {
|
||||||
transaction types.Transaction
|
Transaction TransactionArg
|
||||||
from accounts.Account
|
From common.Address
|
||||||
callinfo fmt.Stringer
|
Callinfo fmt.Stringer
|
||||||
|
Meta Metadata
|
||||||
}
|
}
|
||||||
// SignTxResponse result from SignTxRequest
|
// SignTxResponse result from SignTxRequest
|
||||||
SignTxResponse struct {
|
SignTxResponse struct {
|
||||||
//The UI may make changes to the TX
|
//The UI may make changes to the TX
|
||||||
transaction types.Transaction
|
Transaction TransactionArg
|
||||||
approved bool
|
From common.Address
|
||||||
pw string
|
Approved bool
|
||||||
|
Password string
|
||||||
}
|
}
|
||||||
// ExportRequest info about query to export accounts
|
// ExportRequest info about query to export accounts
|
||||||
ExportRequest struct {
|
ExportRequest struct {
|
||||||
account accounts.Account
|
Address common.Address
|
||||||
file string
|
Meta Metadata
|
||||||
}
|
}
|
||||||
// ExportResponse response to export-request
|
// ExportResponse response to export-request
|
||||||
ExportResponse struct {
|
ExportResponse struct {
|
||||||
approved bool
|
Approved bool
|
||||||
}
|
}
|
||||||
// ImportRequest info about request to import an account
|
// ImportRequest info about request to import an Account
|
||||||
ImportRequest struct {
|
ImportRequest struct {
|
||||||
account accounts.Account
|
Meta Metadata
|
||||||
}
|
}
|
||||||
ImportResponse struct {
|
ImportResponse struct {
|
||||||
approved bool
|
Approved bool
|
||||||
oldPassword string
|
OldPassword string
|
||||||
newPassword string
|
NewPassword string
|
||||||
}
|
}
|
||||||
SignDataRequest struct {
|
SignDataRequest struct {
|
||||||
account accounts.Account
|
Address common.Address
|
||||||
rawdata hexutil.Bytes
|
Rawdata hexutil.Bytes
|
||||||
message string
|
Message string
|
||||||
hash hexutil.Bytes
|
Hash hexutil.Bytes
|
||||||
|
Meta Metadata
|
||||||
}
|
}
|
||||||
SignDataResponse struct {
|
SignDataResponse struct {
|
||||||
approved bool
|
Approved bool
|
||||||
pw string
|
Password string
|
||||||
|
}
|
||||||
|
NewAccountRequest struct {
|
||||||
|
Meta Metadata
|
||||||
}
|
}
|
||||||
NewAccountRequest struct{}
|
|
||||||
NewAccountResponse struct {
|
NewAccountResponse struct {
|
||||||
approved bool
|
Approved bool
|
||||||
pw string
|
Password string
|
||||||
}
|
}
|
||||||
ListRequest struct {
|
ListRequest struct {
|
||||||
accounts []Account
|
Accounts []Account
|
||||||
|
Meta Metadata
|
||||||
}
|
}
|
||||||
ListResponse struct {
|
ListResponse struct {
|
||||||
accounts []Account
|
Accounts []Account
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -120,28 +127,28 @@ func (ew errorWrapper) String() string {
|
||||||
// for the signer
|
// for the signer
|
||||||
type SignerUI interface {
|
type SignerUI interface {
|
||||||
|
|
||||||
// ApproveTx prompt the user for confirmation to request to sign transaction
|
// ApproveTx prompt the user for confirmation to request to sign Transaction
|
||||||
ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse)
|
ApproveTx(request *SignTxRequest) (SignTxResponse, error)
|
||||||
// ApproveSignData prompt the user for confirmation to request to sign data
|
// ApproveSignData prompt the user for confirmation to request to sign data
|
||||||
ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse)
|
ApproveSignData(request *SignDataRequest) (SignDataResponse, error)
|
||||||
// ApproveExport prompt the user for confirmation to export encrypted account json
|
// ApproveExport prompt the user for confirmation to export encrypted Account json
|
||||||
ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse)
|
ApproveExport(request *ExportRequest) (ExportResponse, error)
|
||||||
// ApproveImport prompt the user for confirmation to import account json
|
// ApproveImport prompt the user for confirmation to import Account json
|
||||||
ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse)
|
ApproveImport(request *ImportRequest) (ImportResponse, error)
|
||||||
// ApproveListing prompt the user for confirmation to list accounts
|
// ApproveListing prompt the user for confirmation to list accounts
|
||||||
// the list of accounts to list can be modified by the ui
|
// the list of accounts to list can be modified by the ui
|
||||||
ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse)
|
ApproveListing(request *ListRequest) (ListResponse, error)
|
||||||
// ApproveNewAccount prompt the user for confirmation to create new account, and reveal to caller
|
// ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller
|
||||||
ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan NewAccountResponse)
|
ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error)
|
||||||
// ShowError displays error message to user
|
// ShowError displays error message to user
|
||||||
ShowError(message string)
|
ShowError(message string)
|
||||||
// ShowInfo displays info message to user
|
// ShowInfo displays info message to user
|
||||||
ShowInfo(message string)
|
ShowInfo(message string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSignerAPI creates a new API that can be used for account management.
|
// NewSignerAPI creates a new API that can be used for Account management.
|
||||||
// ksLocation specifies the directory where to store the password protected private
|
// ksLocation specifies the directory where to store the password protected private
|
||||||
// key that is generated when a new account is created.
|
// key that is generated when a new Account is created.
|
||||||
// noUSB disables USB support that is required to support hardware devices such as
|
// noUSB disables USB support that is required to support hardware devices such as
|
||||||
// ledger and trezor.
|
// ledger and trezor.
|
||||||
func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abidb *abiDb, lightKDF bool) *SignerAPI {
|
func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abidb *abiDb, lightKDF bool) *SignerAPI {
|
||||||
|
|
@ -194,23 +201,26 @@ func metaData(ctx context.Context) Metadata {
|
||||||
// multiple accounts.
|
// multiple accounts.
|
||||||
func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
|
func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
|
||||||
|
|
||||||
ch := make(chan ListResponse, 1)
|
|
||||||
var accs []Account
|
var accs []Account
|
||||||
for _, wallet := range api.am.Wallets() {
|
for _, wallet := range api.am.Wallets() {
|
||||||
for _, acc := range wallet.Accounts() {
|
for _, acc := range wallet.Accounts() {
|
||||||
acc := Account{Typ: "account", URL: wallet.URL(), Address: acc.Address}
|
acc := Account{Typ: "Account", URL: wallet.URL(), Address: acc.Address}
|
||||||
accs = append(accs, acc)
|
accs = append(accs, acc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
api.ui.ApproveListing(&ListRequest{accounts: accs}, metaData(ctx), ch)
|
result, err := api.ui.ApproveListing(&ListRequest{Accounts: accs, Meta: metaData(ctx)})
|
||||||
if result := <-ch; result.accounts != nil {
|
if err != nil {
|
||||||
return result.accounts, nil
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, ErrRequestDenied
|
if result.Accounts == nil {
|
||||||
|
return nil, ErrRequestDenied
|
||||||
|
|
||||||
|
}
|
||||||
|
return result.Accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new password protected account. The private key is protected with
|
// New creates a new password protected Account. The private key is protected with
|
||||||
// the given password. Users are responsible to backup the private key that is stored
|
// the given password. Users are responsible to backup the private key that is stored
|
||||||
// in the keystore location thas was specified when this API was created.
|
// in the keystore location thas was specified when this API was created.
|
||||||
func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
|
func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
|
||||||
|
|
@ -218,72 +228,128 @@ func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) {
|
||||||
if len(be) == 0 {
|
if len(be) == 0 {
|
||||||
return accounts.Account{}, errors.New("password based accounts not supported")
|
return accounts.Account{}, errors.New("password based accounts not supported")
|
||||||
}
|
}
|
||||||
ch := make(chan NewAccountResponse, 1)
|
resp, err := api.ui.ApproveNewAccount(&NewAccountRequest{metaData(ctx)})
|
||||||
api.ui.ApproveNewAccount(&NewAccountRequest{}, metaData(ctx), ch)
|
|
||||||
|
|
||||||
if resp := <-ch; resp.approved {
|
|
||||||
return be[0].(*keystore.KeyStore).NewAccount(resp.pw)
|
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
return accounts.Account{}, ErrRequestDenied
|
if !resp.Approved {
|
||||||
|
return accounts.Account{}, ErrRequestDenied
|
||||||
|
}
|
||||||
|
return be[0].(*keystore.KeyStore).NewAccount(resp.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTransaction signs the given transaction and returns it in an RLP encoded form
|
func toTransaction(args *TransactionArg) *types.Transaction {
|
||||||
|
if args.To == nil {
|
||||||
|
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
|
||||||
|
} else {
|
||||||
|
return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logDiff logs the difference between the incoming (original) transaction and the one returned from the signer.
|
||||||
|
// it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow
|
||||||
|
// UI-modifications to requests
|
||||||
|
func logDiff(original *SignTxRequest, new *SignTxResponse) bool {
|
||||||
|
modified := false
|
||||||
|
if f0, f1 := original.From, new.From; f0 != f1 {
|
||||||
|
modified = true
|
||||||
|
log.Info("Sender-account changed by UI", "was", f0, "is", f1)
|
||||||
|
}
|
||||||
|
if t0, t1 := original.Transaction.To, new.Transaction.To; t0 != t1 {
|
||||||
|
if t0 == nil || t1 == nil || !bytes.Equal(t0.Bytes(), t1.Bytes()) {
|
||||||
|
log.Info("Recipient-account changed by UI", "was", t0, "is", t1)
|
||||||
|
modified = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if g0, g1 := (*big.Int)(original.Transaction.Gas), (*big.Int)(new.Transaction.Gas); g0 != g1 {
|
||||||
|
if g0 == nil || g1 == nil || g0.Cmp(g1) != 0 {
|
||||||
|
modified = true
|
||||||
|
log.Info("Gas changed by UI", "was", g0, "is", g1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if g0, g1 := (*big.Int)(original.Transaction.GasPrice), (*big.Int)(new.Transaction.GasPrice); g0 != g1 {
|
||||||
|
if g0 == nil || g1 == nil || g0.Cmp(g1) != 0 {
|
||||||
|
modified = true
|
||||||
|
log.Info("GasPrice changed by UI", "was", g0, "is", g1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v0, v1 := (*big.Int)(original.Transaction.Value), (*big.Int)(new.Transaction.Value); v0 != v1 {
|
||||||
|
if v0 == nil || v1 == nil || v0.Cmp(v1) != 0 {
|
||||||
|
modified = true
|
||||||
|
log.Info("Value changed by UI", "was", v0, "is", v1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d0, d1 := original.Transaction.Data, new.Transaction.Data; !bytes.Equal(d0, d1) {
|
||||||
|
modified = true
|
||||||
|
log.Info("Data changed by UI", "was", common.ToHex(d0), "is", common.ToHex(d1))
|
||||||
|
}
|
||||||
|
if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 {
|
||||||
|
|
||||||
|
if n0 == nil || n1 == nil || (*n0) != (*n1) {
|
||||||
|
modified = true
|
||||||
|
log.Info("Nonce changed by UI", "was", n0, "is", n1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modified
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTransaction signs the given Transaction and returns it in an RLP encoded form
|
||||||
// that can be posted to `eth_sendRawTransaction`.
|
// that can be posted to `eth_sendRawTransaction`.
|
||||||
func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address, args TransactionArg, methodSig *string) (hexutil.Bytes, error) {
|
func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address, args TransactionArg, methodSig *string) (hexutil.Bytes, error) {
|
||||||
acc := accounts.Account{Address: from}
|
|
||||||
|
|
||||||
wallet, err := api.am.Find(acc)
|
var (
|
||||||
if err != nil {
|
err error
|
||||||
return nil, err
|
result SignTxResponse
|
||||||
}
|
)
|
||||||
|
|
||||||
var tx *types.Transaction
|
req := SignTxRequest{Transaction: args, From: from, Meta: metaData(ctx)}
|
||||||
if args.To == nil {
|
|
||||||
tx = types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
|
|
||||||
} else {
|
|
||||||
tx = types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), (*big.Int)(args.Gas), (*big.Int)(args.GasPrice), args.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
req := SignTxRequest{transaction: *tx, from: acc}
|
data := args.Data
|
||||||
if len(tx.Data()) > 3 {
|
if len(data) > 3 {
|
||||||
// Try to make sense of the data
|
// Try to make sense of the data
|
||||||
var abidata string
|
var abidata string
|
||||||
if methodSig == nil {
|
if methodSig == nil {
|
||||||
abidata, err = api.abidb.LookupABI(tx.Data()[:4])
|
abidata, err = api.abidb.LookupABI(data[:4])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.callinfo = errorWrapper{"Warning! Could not locate ABI", err}
|
req.Callinfo = errorWrapper{"Warning! Could not locate ABI", err}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
abidata = *methodSig
|
abidata = *methodSig
|
||||||
}
|
}
|
||||||
if abidata != "" {
|
if abidata != "" {
|
||||||
req.callinfo, err = parseCallData(tx.Data(), abidata)
|
req.Callinfo, err = parseCallData(data, abidata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}
|
req.Callinfo = errorWrapper{"Warning! Could not validate ABI-data against calldata", err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan SignTxResponse, 1)
|
result, err = api.ui.ApproveTx(&req)
|
||||||
api.ui.ApproveTx(&req, metaData(ctx), ch)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !result.Approved {
|
||||||
|
return nil, ErrRequestDenied
|
||||||
|
}
|
||||||
|
// Log changes made by the UI to the signing-request
|
||||||
|
logDiff(&req, &result)
|
||||||
|
|
||||||
if result := <-ch; result.approved {
|
acc := accounts.Account{Address: result.From}
|
||||||
//Sanity check
|
wallet, err := api.am.Find(acc)
|
||||||
if result.transaction.Hash() != tx.Hash() {
|
if err != nil {
|
||||||
api.ui.ShowInfo("Transaction modified by UI")
|
return nil, err
|
||||||
}
|
|
||||||
// The one to sign is the one that was returned from the UI
|
|
||||||
signedTx, err := wallet.SignTxWithPassphrase(acc, result.pw, &result.transaction, api.chainID)
|
|
||||||
if err != nil {
|
|
||||||
api.ui.ShowError(err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return rlp.EncodeToBytes(signedTx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, ErrRequestDenied
|
var tx = toTransaction(&result.Transaction)
|
||||||
|
|
||||||
|
// The one to sign is the one that was returned from the UI
|
||||||
|
signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, tx, api.chainID)
|
||||||
|
if err != nil {
|
||||||
|
api.ui.ShowError(err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rlp.EncodeToBytes(signedTx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign calculates an Ethereum ECDSA signature for:
|
// Sign calculates an Ethereum ECDSA signature for:
|
||||||
|
|
@ -296,35 +362,39 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, from common.Address,
|
||||||
//
|
//
|
||||||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
||||||
func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||||
|
|
||||||
|
sighash, msg := signHash(data)
|
||||||
|
|
||||||
|
// We make the request prior to looking up if we actually have the account, to prevent
|
||||||
|
// account-enumeration via the API
|
||||||
|
req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: metaData(ctx)}
|
||||||
|
res, err := api.ui.ApproveSignData(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !res.Approved {
|
||||||
|
return nil, ErrRequestDenied
|
||||||
|
}
|
||||||
|
|
||||||
// Look up the wallet containing the requested signer
|
// Look up the wallet containing the requested signer
|
||||||
account := accounts.Account{Address: addr}
|
account := accounts.Account{Address: addr}
|
||||||
|
|
||||||
wallet, err := api.am.Find(account)
|
wallet, err := api.am.Find(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ch := make(chan SignDataResponse, 1)
|
// Assemble sign the data with the wallet
|
||||||
|
signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash)
|
||||||
sighash, msg := signHash(data)
|
if err != nil {
|
||||||
|
api.ui.ShowError(err.Error())
|
||||||
api.ui.ApproveSignData(&SignDataRequest{account: account, rawdata: data, message: msg, hash: sighash}, metaData(ctx), ch)
|
return nil, err
|
||||||
|
|
||||||
if res := <-ch; res.approved {
|
|
||||||
|
|
||||||
// Assemble sign the data with the wallet
|
|
||||||
signature, err := wallet.SignHashWithPassphrase(account, res.pw, sighash)
|
|
||||||
if err != nil {
|
|
||||||
api.ui.ShowError(err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
|
||||||
return signature, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, ErrRequestDenied
|
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
return signature, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EcRecover returns the address for the account that was used to create the signature.
|
// EcRecover returns the address for the Account that was used to create the signature.
|
||||||
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
|
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
|
||||||
// the address of:
|
// the address of:
|
||||||
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
|
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
|
||||||
|
|
@ -367,26 +437,27 @@ func signHash(data []byte) ([]byte, string) {
|
||||||
|
|
||||||
// Export returns encrypted private key associated with the given address in web3 keystore format.
|
// Export returns encrypted private key associated with the given address in web3 keystore format.
|
||||||
func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
|
func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) {
|
||||||
// Look up the wallet containing the requested signer
|
|
||||||
account := accounts.Account{Address: addr}
|
|
||||||
|
|
||||||
wallet, err := api.am.Find(account)
|
res, err := api.ui.ApproveExport(&ExportRequest{Address: addr, Meta: metaData(ctx)})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !res.Approved {
|
||||||
|
return nil, ErrRequestDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the wallet containing the requested signer
|
||||||
|
wallet, err := api.am.Find(accounts.Account{Address: addr})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
url := wallet.URL()
|
if wallet.URL().Scheme != keystore.KeyStoreScheme {
|
||||||
if url.Scheme != keystore.KeyStoreScheme {
|
return nil, fmt.Errorf("Account is not a keystore-account")
|
||||||
return nil, fmt.Errorf("account is not a password protected account")
|
|
||||||
}
|
}
|
||||||
ch := make(chan ExportResponse, 1)
|
|
||||||
|
|
||||||
api.ui.ApproveExport(&ExportRequest{account: account, file: url.Path}, metaData(ctx), ch)
|
return ioutil.ReadFile(wallet.URL().Path)
|
||||||
|
|
||||||
if (<-ch).approved {
|
|
||||||
return ioutil.ReadFile(url.Path)
|
|
||||||
}
|
|
||||||
return nil, ErrRequestDenied
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be
|
// Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be
|
||||||
|
|
@ -400,20 +471,21 @@ func (api *SignerAPI) Import(ctx context.Context, string, keyJSON json.RawMessag
|
||||||
return Account{}, errors.New("password based accounts not supported")
|
return Account{}, errors.New("password based accounts not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan ImportResponse, 1)
|
res, err := api.ui.ApproveImport(&ImportRequest{Meta: metaData(ctx)})
|
||||||
|
|
||||||
api.ui.ApproveImport(&ImportRequest{}, metaData(ctx), ch)
|
if err != nil {
|
||||||
|
return Account{}, err
|
||||||
if resp := <-ch; resp.approved {
|
|
||||||
|
|
||||||
acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, resp.oldPassword, resp.newPassword)
|
|
||||||
if err != nil {
|
|
||||||
api.ui.ShowError(err.Error())
|
|
||||||
return Account{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return Account{Typ: "account", URL: acc.URL, Address: acc.Address}, nil
|
|
||||||
}
|
}
|
||||||
return Account{}, ErrRequestDenied
|
if !res.Approved {
|
||||||
|
return Account{}, ErrRequestDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, res.OldPassword, res.NewPassword)
|
||||||
|
if err != nil {
|
||||||
|
api.ui.ShowError(err.Error())
|
||||||
|
return Account{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Account{Typ: "Account", URL: acc.URL, Address: acc.Address}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -22,70 +21,57 @@ type HeadlessUI struct {
|
||||||
controller chan string
|
controller chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
|
func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
||||||
|
|
||||||
switch <-ui.controller {
|
switch <-ui.controller {
|
||||||
case "Y":
|
case "Y":
|
||||||
ch <- SignTxResponse{request.transaction, true, <-ui.controller}
|
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil
|
||||||
case "M": //Modify
|
case "M": //Modify
|
||||||
old := request.transaction
|
old := (*big.Int)(request.Transaction.Value)
|
||||||
newVal := big.NewInt(0).Add(old.Value(), big.NewInt(1))
|
newVal := big.NewInt(0).Add(old, big.NewInt(1))
|
||||||
tx := types.NewTransaction(old.Nonce(), *old.To(), newVal, old.Gas(), old.GasPrice(), old.Data())
|
request.Transaction.Value = (*hexutil.Big)(newVal)
|
||||||
ch <- SignTxResponse{*tx, true, <-ui.controller}
|
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil
|
||||||
default:
|
default:
|
||||||
ch <- SignTxResponse{request.transaction, false, ""}
|
return SignTxResponse{request.Transaction, request.From, false, ""}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
|
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||||
switch <-ui.controller {
|
if "Y" == <-ui.controller {
|
||||||
case "Y":
|
return SignDataResponse{true, <-ui.controller}, nil
|
||||||
ch <- SignDataResponse{true, <-ui.controller}
|
|
||||||
default:
|
|
||||||
ch <- SignDataResponse{false, ""}
|
|
||||||
}
|
}
|
||||||
|
return SignDataResponse{false, ""}, nil
|
||||||
}
|
}
|
||||||
func (ui *HeadlessUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse) {
|
func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
||||||
|
|
||||||
switch <-ui.controller {
|
return ExportResponse{<-ui.controller == "Y"}, nil
|
||||||
case "Y":
|
|
||||||
ch <- ExportResponse{true}
|
|
||||||
default:
|
|
||||||
ch <- ExportResponse{false}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
func (ui *HeadlessUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
|
func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
||||||
|
|
||||||
switch <-ui.controller {
|
if "Y" == <-ui.controller {
|
||||||
case "Y":
|
return ImportResponse{true, <-ui.controller, <-ui.controller}, nil
|
||||||
ch <- ImportResponse{true, <-ui.controller, <-ui.controller}
|
|
||||||
default:
|
|
||||||
ch <- ImportResponse{false, "", ""}
|
|
||||||
}
|
}
|
||||||
|
return ImportResponse{false, "", ""}, nil
|
||||||
}
|
}
|
||||||
func (ui *HeadlessUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse) {
|
func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
||||||
|
|
||||||
switch <-ui.controller {
|
switch <-ui.controller {
|
||||||
case "A":
|
case "A":
|
||||||
ch <- ListResponse{request.accounts}
|
return ListResponse{request.Accounts}, nil
|
||||||
case "1":
|
case "1":
|
||||||
l := make([]Account, 1)
|
l := make([]Account, 1)
|
||||||
l[0] = request.accounts[1]
|
l[0] = request.Accounts[1]
|
||||||
ch <- ListResponse{l}
|
return ListResponse{l}, nil
|
||||||
default:
|
default:
|
||||||
ch <- ListResponse{nil}
|
return ListResponse{nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
func (ui *HeadlessUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan NewAccountResponse) {
|
func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
||||||
|
|
||||||
switch <-ui.controller {
|
if "Y" == <-ui.controller {
|
||||||
case "Y":
|
return NewAccountResponse{true, <-ui.controller}, nil
|
||||||
ch <- NewAccountResponse{true, <-ui.controller}
|
|
||||||
default:
|
|
||||||
ch <- NewAccountResponse{false, ""}
|
|
||||||
}
|
}
|
||||||
|
return NewAccountResponse{false, ""}, nil
|
||||||
}
|
}
|
||||||
func (ui *HeadlessUI) ShowError(message string) {
|
func (ui *HeadlessUI) ShowError(message string) {
|
||||||
//stdout is used by communication
|
//stdout is used by communication
|
||||||
|
|
@ -179,14 +165,14 @@ func TestNewAcc(t *testing.T) {
|
||||||
verifyNum(4)
|
verifyNum(4)
|
||||||
|
|
||||||
// Testing listing:
|
// Testing listing:
|
||||||
// Listing one account
|
// Listing one Account
|
||||||
control <- "1"
|
control <- "1"
|
||||||
list, err := api.List(context.Background())
|
list, err := api.List(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(list) != 1 {
|
if len(list) != 1 {
|
||||||
t.Fatalf("List should only show one account")
|
t.Fatalf("List should only show one Account")
|
||||||
}
|
}
|
||||||
// Listing denied
|
// Listing denied
|
||||||
control <- "Nope"
|
control <- "Nope"
|
||||||
|
|
@ -333,3 +319,31 @@ func TestSignTx(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func TestAsyncronousResponses(t *testing.T){
|
||||||
|
|
||||||
|
//Set up one account
|
||||||
|
api, control := setup(t)
|
||||||
|
createAccount(control, api, t)
|
||||||
|
|
||||||
|
// Two transactions, the second one with larger value than the first
|
||||||
|
tx1 := mkTestTx()
|
||||||
|
newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1))
|
||||||
|
tx2 := mkTestTx()
|
||||||
|
tx2.Value = (*hexutil.Big)(newVal)
|
||||||
|
|
||||||
|
control <- "W" //wait
|
||||||
|
control <- "Y" //
|
||||||
|
control <- "apassword"
|
||||||
|
control <- "Y" //
|
||||||
|
control <- "apassword"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil)
|
||||||
|
h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,11 @@ func (ui *CommandlineUI) readString() string {
|
||||||
func (ui *CommandlineUI) readPassword() string {
|
func (ui *CommandlineUI) readPassword() string {
|
||||||
fmt.Printf("Enter password to approve:\n")
|
fmt.Printf("Enter password to approve:\n")
|
||||||
fmt.Printf("> ")
|
fmt.Printf("> ")
|
||||||
//TODO; remove this, only for debuggging within IDE
|
|
||||||
text := "foobar"
|
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||||
//TODO: Use this
|
if err != nil {
|
||||||
// text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
log.Crit("Failed to read password", "err", err)
|
||||||
//if err != nil {
|
}
|
||||||
// log.Crit("Failed to read password", "err", err)
|
|
||||||
//}
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("-----------------------")
|
fmt.Println("-----------------------")
|
||||||
return string(text)
|
return string(text)
|
||||||
|
|
@ -95,112 +93,110 @@ func showMetadata(metadata Metadata) {
|
||||||
fmt.Printf("Request info:\n\t%v -> %v -> %v\n", metadata.remote, metadata.scheme, metadata.local)
|
fmt.Printf("Request info:\n\t%v -> %v -> %v\n", metadata.remote, metadata.scheme, metadata.local)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveTx prompt the user for confirmation to request to sign transaction
|
// ApproveTx prompt the user for confirmation to request to sign Transaction
|
||||||
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
|
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
defer ui.mu.Unlock()
|
defer ui.mu.Unlock()
|
||||||
weival := request.transaction.Value()
|
weival := request.Transaction.Value
|
||||||
|
|
||||||
fmt.Printf("--------- Transaction request-------------\n")
|
fmt.Printf("--------- Transaction request-------------\n")
|
||||||
fmt.Printf("to: %v\n", request.transaction.To().Hex())
|
fmt.Printf("to: %v\n", request.Transaction.To.Hex())
|
||||||
fmt.Printf("from: %v\n", request.from.Address.Hex())
|
fmt.Printf("from: %v\n", request.From.Hex())
|
||||||
fmt.Printf("value: %v wei\n", weival)
|
fmt.Printf("value: %v wei\n", weival)
|
||||||
if len(request.transaction.Data()) > 0 {
|
if len(request.Transaction.Data) > 0 {
|
||||||
fmt.Printf("data: %v\n", common.Bytes2Hex(request.transaction.Data()))
|
fmt.Printf("data: %v\n", common.Bytes2Hex(request.Transaction.Data))
|
||||||
}
|
}
|
||||||
if request.callinfo != nil {
|
if request.Callinfo != nil {
|
||||||
fmt.Printf("\nNote: This transaction contains data. Review abi-decoding info below:")
|
fmt.Printf("\nNote: This Transaction contains data. Review abi-decoding info below:")
|
||||||
fmt.Printf("\nCall info:\n\t%v\n", request.callinfo.String())
|
fmt.Printf("\nCall info:\n\t%v\n", request.Callinfo.String())
|
||||||
|
|
||||||
}
|
}
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
showMetadata(metadata)
|
showMetadata(request.Meta)
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
|
|
||||||
ch <- SignTxResponse{request.transaction, true, ui.readPassword()}
|
return SignTxResponse{request.Transaction, request.From, true, ui.readPassword()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveSignData prompt the user for confirmation to request to sign data
|
// ApproveSignData prompt the user for confirmation to request to sign data
|
||||||
func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
|
func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
defer ui.mu.Unlock()
|
defer ui.mu.Unlock()
|
||||||
|
|
||||||
fmt.Printf("-------- Sign data request--------------\n")
|
fmt.Printf("-------- Sign data request--------------\n")
|
||||||
fmt.Printf("account: %x\n", request.account.Address)
|
fmt.Printf("Account: %x\n", request.Address)
|
||||||
fmt.Printf("message: \n%v\n", request.message)
|
fmt.Printf("message: \n%v\n", request.Message)
|
||||||
fmt.Printf("raw data: \n%v\n", request.rawdata)
|
fmt.Printf("raw data: \n%v\n", request.Rawdata)
|
||||||
fmt.Printf("message hash: %v\n", request.hash)
|
fmt.Printf("message hash: %v\n", request.Hash)
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
showMetadata(metadata)
|
showMetadata(request.Meta)
|
||||||
ch <- SignDataResponse{true, ui.readPassword()}
|
return SignDataResponse{true, ui.readPassword()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveExport prompt the user for confirmation to export encrypted account json
|
// ApproveExport prompt the user for confirmation to export encrypted Account json
|
||||||
func (ui *CommandlineUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse) {
|
func (ui *CommandlineUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
defer ui.mu.Unlock()
|
defer ui.mu.Unlock()
|
||||||
|
|
||||||
fmt.Printf("-------- Export account request--------------\n")
|
fmt.Printf("-------- Export Account request--------------\n")
|
||||||
fmt.Printf("A request has been made to export the (encrypted) keyfile\n")
|
fmt.Printf("A request has been made to export the (encrypted) keyfile\n")
|
||||||
fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n")
|
fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n")
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
fmt.Printf("account: %x\n", request.account.Address)
|
fmt.Printf("Account: %x\n", request.Address)
|
||||||
fmt.Printf("keyfile: \n%v\n", request.file)
|
//fmt.Printf("keyfile: \n%v\n", request.file)
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
showMetadata(metadata)
|
showMetadata(request.Meta)
|
||||||
ch <- ExportResponse{ui.confirm()}
|
return ExportResponse{ui.confirm()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveImport prompt the user for confirmation to import account json
|
// ApproveImport prompt the user for confirmation to import Account json
|
||||||
func (ui *CommandlineUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
|
func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
defer ui.mu.Unlock()
|
defer ui.mu.Unlock()
|
||||||
|
|
||||||
fmt.Printf("-------- Export account request--------------\n")
|
fmt.Printf("-------- Export Account request--------------\n")
|
||||||
fmt.Printf("A request has been made to import an encrypted keyfile\n")
|
fmt.Printf("A request has been made to import an encrypted keyfile\n")
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
showMetadata(metadata)
|
showMetadata(request.Meta)
|
||||||
if ui.confirm() {
|
if !ui.confirm() {
|
||||||
ch <- ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}
|
return ImportResponse{false, "", ""}, nil
|
||||||
} else {
|
|
||||||
ch <- ImportResponse{false, "", ""}
|
|
||||||
}
|
}
|
||||||
|
return ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveListing prompt the user for confirmation to list accounts
|
// ApproveListing prompt the user for confirmation to list accounts
|
||||||
// the list of accounts to list can be modified by the ui
|
// the list of accounts to list can be modified by the ui
|
||||||
func (ui *CommandlineUI) ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse) {
|
func (ui *CommandlineUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
||||||
|
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
defer ui.mu.Unlock()
|
defer ui.mu.Unlock()
|
||||||
|
|
||||||
fmt.Printf("-------- List account request--------------\n")
|
fmt.Printf("-------- List Account request--------------\n")
|
||||||
fmt.Printf("A request has been made to list all accounts. \n")
|
fmt.Printf("A request has been made to list all accounts. \n")
|
||||||
fmt.Printf("You can select which accounts the caller can see\n")
|
fmt.Printf("You can select which accounts the caller can see\n")
|
||||||
for _, account := range request.accounts {
|
for _, account := range request.Accounts {
|
||||||
fmt.Printf("\t[x] %v\n", account.Address.Hex())
|
fmt.Printf("\t[x] %v\n", account.Address.Hex())
|
||||||
}
|
}
|
||||||
fmt.Printf("-------------------------------------------\n")
|
fmt.Printf("-------------------------------------------\n")
|
||||||
showMetadata(metadata)
|
showMetadata(request.Meta)
|
||||||
if ui.confirm() {
|
if !ui.confirm() {
|
||||||
ch <- ListResponse{request.accounts}
|
return ListResponse{nil}, nil
|
||||||
} else {
|
|
||||||
ch <- ListResponse{nil}
|
|
||||||
}
|
}
|
||||||
|
return ListResponse{request.Accounts}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveNewAccount prompt the user for confirmation to create new account, and reveal to caller
|
// ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller
|
||||||
func (ui *CommandlineUI) ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan NewAccountResponse) {
|
func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
||||||
|
|
||||||
ui.mu.Lock()
|
ui.mu.Lock()
|
||||||
defer ui.mu.Unlock()
|
defer ui.mu.Unlock()
|
||||||
|
|
||||||
fmt.Printf("-------- New account request--------------\n")
|
fmt.Printf("-------- New Account request--------------\n")
|
||||||
fmt.Printf("A request has been made to create a new. \n")
|
fmt.Printf("A request has been made to create a new. \n")
|
||||||
fmt.Printf("Approving this operation means that a new account is created,\n")
|
fmt.Printf("Approving this operation means that a new Account is created,\n")
|
||||||
fmt.Printf("and the address show to the caller\n")
|
fmt.Printf("and the address show to the caller\n")
|
||||||
showMetadata(metadata)
|
showMetadata(request.Meta)
|
||||||
ch <- NewAccountResponse{ui.confirm(), ui.readPassword()}
|
return NewAccountResponse{ui.confirm(), ui.readPassword()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShowError displays error message to user
|
// ShowError displays error message to user
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,14 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "signer"
|
app.Name = "signer"
|
||||||
app.Usage = "Manage ethereum account operations"
|
app.Usage = "Manage ethereum Account operations"
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.Int64Flag{
|
cli.Int64Flag{
|
||||||
Name: "chainid",
|
Name: "chainid",
|
||||||
|
|
@ -77,12 +78,33 @@ func main() {
|
||||||
Usage: "File containing requests to handle",
|
Usage: "File containing requests to handle",
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "stdio-ui",
|
||||||
|
Usage: "Use STDIN/STDOUT as a channel for an external UI. " +
|
||||||
|
"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
|
||||||
|
"interface, and can be used when the signer is started by an external process.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = func(c *cli.Context) error {
|
app.Action = func(c *cli.Context) error {
|
||||||
// Set up the logger to print everything and the random generator
|
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
|
|
||||||
|
|
||||||
|
var (
|
||||||
|
ui SignerUI
|
||||||
|
logOutput io.Writer
|
||||||
|
)
|
||||||
|
|
||||||
|
if c.Bool("stdio-ui") {
|
||||||
|
logOutput = os.Stderr
|
||||||
|
ui = NewStdIOUI()
|
||||||
|
} else {
|
||||||
|
ui = NewCommandlineUI()
|
||||||
|
logOutput = os.Stdout
|
||||||
|
}
|
||||||
|
// Set up the logger to print everything
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(logOutput, log.TerminalFormat(true))))
|
||||||
|
if c.Bool("stdio-ui") {
|
||||||
|
log.Info("Using stdin/stdout as UI-channel")
|
||||||
|
}
|
||||||
db, err := NewAbiDBFromFile(c.String("4bytedb"))
|
db, err := NewAbiDBFromFile(c.String("4bytedb"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -97,17 +119,18 @@ func main() {
|
||||||
c.Int64(utils.NetworkIdFlag.Name),
|
c.Int64(utils.NetworkIdFlag.Name),
|
||||||
c.String("keystore"),
|
c.String("keystore"),
|
||||||
c.Bool(utils.NoUSBFlag.Name),
|
c.Bool(utils.NoUSBFlag.Name),
|
||||||
NewCommandlineUI(), db,
|
ui, db,
|
||||||
c.Bool(utils.LightKDFFlag.Name))
|
c.Bool(utils.LightKDFFlag.Name))
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
)
|
)
|
||||||
|
// Audit logging
|
||||||
if logfile := c.String("auditlog"); logfile != "" {
|
if logfile := c.String("auditlog"); logfile != "" {
|
||||||
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not open %v for audit logging", logfile)
|
utils.Fatalf("Could not open %v for audit logging", logfile)
|
||||||
}
|
}
|
||||||
server.SetAuditLogger(NewAuditLogger(f))
|
server.SetAuditLogger(NewAuditLogger(f))
|
||||||
log.Info("Writing audit logs to %v", logfile)
|
log.Info("Audit logs configured", "file", logfile)
|
||||||
}
|
}
|
||||||
// register signer API with server
|
// register signer API with server
|
||||||
if err = server.RegisterName("account", api); err != nil {
|
if err = server.RegisterName("account", api); err != nil {
|
||||||
|
|
@ -125,24 +148,23 @@ func main() {
|
||||||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||||
utils.Fatalf("Could not start http listener: %v", err)
|
utils.Fatalf("Could not start http listener: %v", err)
|
||||||
}
|
}
|
||||||
log.Info(fmt.Sprintf("HTTP endpoint opened: http://%s", endpoint))
|
log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint))
|
||||||
cors := []string{"*"}
|
cors := []string{"*"}
|
||||||
|
|
||||||
rpc.NewHTTPServer(cors, server).Serve(listener)
|
rpc.NewHTTPServer(cors, server).Serve(listener)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create account
|
// Create Account
|
||||||
// curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
|
// curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
|
||||||
|
|
||||||
// List accounts
|
// List accounts
|
||||||
// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
|
// curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
// Make transaction
|
// Make Transaction
|
||||||
// safeSend(0x12)
|
// safeSend(0x12)
|
||||||
// 4401a6e40000000000000000000000000000000000000000000000000000000000000012
|
// 4401a6e40000000000000000000000000000000000000000000000000000000000000012
|
||||||
|
|
||||||
|
|
|
||||||
100
cmd/signer/pythonsigner.py
Normal file
100
cmd/signer/pythonsigner.py
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import os,sys, subprocess
|
||||||
|
from tinyrpc.transports import ServerTransport
|
||||||
|
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
|
||||||
|
from tinyrpc.dispatch import RPCDispatcher
|
||||||
|
from tinyrpc.server import RPCServer
|
||||||
|
|
||||||
|
""" This is a POC example of how to write a custom UI for the signer. The UI starts the
|
||||||
|
signer process with the '--stdio-ui' option, and communicates with the signer binary
|
||||||
|
using standard input / output.
|
||||||
|
|
||||||
|
The standard input/output is a relatively secure way to communicate, as it does not require opening any ports
|
||||||
|
or IPC files. Needless to say, it does not protect against memory inspection mechanisms where an attacker
|
||||||
|
can access process memory."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
except ImportError:
|
||||||
|
import urllib as urlparse
|
||||||
|
|
||||||
|
class StdIOTransport(ServerTransport):
|
||||||
|
""" Uses std input/output for RPC """
|
||||||
|
def receive_message(self):
|
||||||
|
return None, urlparse.unquote(sys.stdin.readline())
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
print(reply)
|
||||||
|
|
||||||
|
class PipeTransport(ServerTransport):
|
||||||
|
""" Uses std a pipe for RPC """
|
||||||
|
|
||||||
|
def __init__(self,input, output):
|
||||||
|
self.input = input
|
||||||
|
self.output = output
|
||||||
|
|
||||||
|
def receive_message(self):
|
||||||
|
data = self.input.readline()
|
||||||
|
print("IN ->\n{}".format( data))
|
||||||
|
return None, urlparse.unquote(data)
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
print("OUT <-\n{}".format( reply))
|
||||||
|
self.output.write(reply)
|
||||||
|
self.output.write("\n")
|
||||||
|
|
||||||
|
dispatcher = RPCDispatcher()
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ApproveTx(Transaction = None, From = None, Callinfo = None, Meta = None):
|
||||||
|
return {
|
||||||
|
"Approved" : True,
|
||||||
|
"Transaction" : Transaction,
|
||||||
|
"From" : From,
|
||||||
|
"Password" : None,
|
||||||
|
}
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ApproveSignData():
|
||||||
|
return {"Approved": False,
|
||||||
|
"Password" : None}
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ApproveExport():
|
||||||
|
return {"Approved" : False}
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ApproveImport():
|
||||||
|
return {"Approved" : False, "OldPassword": "", "NewPassword": ""}
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ApproveListing():
|
||||||
|
return []
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ApproveNewAccount():
|
||||||
|
return {"Approved": False, "Password": ""}
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ShowError(text = ""):
|
||||||
|
sys.err.println("Error: %s", text)
|
||||||
|
return
|
||||||
|
|
||||||
|
@dispatcher.public
|
||||||
|
def ShowInfo(text = ""):
|
||||||
|
sys.err.println("Info: %s", text)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# line buffered
|
||||||
|
p = subprocess.Popen(["./signer", "--stdio-ui"], bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
transport = PipeTransport(p.stdout, p.stdin)
|
||||||
|
rpc_server = RPCServer(
|
||||||
|
transport,
|
||||||
|
JSONRPCProtocol(),
|
||||||
|
dispatcher
|
||||||
|
)
|
||||||
|
rpc_server.serve_forever()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
128
cmd/signer/stdioui.go
Normal file
128
cmd/signer/stdioui.go
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/powerman/rpc-codec/jsonrpc2"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StdIOUI struct {
|
||||||
|
client *jsonrpc2.Client
|
||||||
|
// codec rpc.ClientCodec
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdIOUI() *StdIOUI {
|
||||||
|
in, out := bufio.NewReader(os.Stdin), os.Stdout
|
||||||
|
|
||||||
|
//codec := rpc2.NewJSONCodec()
|
||||||
|
return &StdIOUI{client: jsonrpc2.NewClient(&rwc{in, out})}
|
||||||
|
//return &StdIOUI{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) dispatch(serviceMethod string, args interface{}, reply interface{}) error {
|
||||||
|
|
||||||
|
// ui.mu.Lock()
|
||||||
|
// defer ui.mu.Unlock()
|
||||||
|
//This is not synchronized, which should not be necssary. Ideally, the UI should be able
|
||||||
|
// to get requests and send responses out-of-order -- thus the rpc has an ID.
|
||||||
|
|
||||||
|
// in, out := bufio.NewReader(os.Stdin), os.Stdout
|
||||||
|
// codec := jsonrpc.NewClientCodec(&rwc{in, out})
|
||||||
|
|
||||||
|
// c := rpc.NewClientWithCodec(codec)
|
||||||
|
// return c.Call(serviceMethod, args, &reply)
|
||||||
|
|
||||||
|
err := ui.client.Call(serviceMethod, args, &reply)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error", "exc", err.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
||||||
|
result := SignTxResponse{}
|
||||||
|
if err := ui.dispatch("ApproveTx", request, &result); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||||
|
var result SignDataResponse
|
||||||
|
if err := ui.dispatch("ApproveSignData", request, &result); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
||||||
|
var result ExportResponse
|
||||||
|
if err := ui.dispatch("ApproveExport", request, &result); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
||||||
|
var result ImportResponse
|
||||||
|
if err := ui.dispatch("ApproveImport", request, &result); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) {
|
||||||
|
var result ListResponse
|
||||||
|
if err := ui.dispatch("ApproveListing", request, &result); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
||||||
|
var result NewAccountResponse
|
||||||
|
if err := ui.dispatch("ApproveNewAccount", request, &result); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ShowError(message string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui StdIOUI) ShowInfo(message string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type rwc struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rwc) Close() error {
|
||||||
|
if err := os.Stdin.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Stdout.Close()
|
||||||
|
//return nil
|
||||||
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ func (a Account) String() string {
|
||||||
return err.Error()
|
return err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionArg represents a transaction for the signer.
|
// TransactionArg represents a Transaction for the signer.
|
||||||
type TransactionArg struct {
|
type TransactionArg struct {
|
||||||
To *common.Address `json:"to"`
|
To *common.Address `json:"to"`
|
||||||
Gas *hexutil.Big `json:"gas"`
|
Gas *hexutil.Big `json:"gas"`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue