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")
|
||||
--rpcport value HTTP-RPC server listening port (default: 8550)
|
||||
--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
|
||||
```
|
||||
|
||||
|
|
@ -34,15 +35,39 @@ Example:
|
|||
signer -keystore /my/keystore -chainid 4
|
||||
```
|
||||
|
||||
## Communicating
|
||||
## Communication
|
||||
|
||||
The signer listens to HTTP requests on `rpcaddr`:`rpcport`. The messages are expected to be JSON
|
||||
[jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
||||
### External 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 signer listens to HTTP requests on `rpcaddr`:`rpcport`. The messages are
|
||||
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
|
||||
- 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"
|
||||
"math/big"
|
||||
|
||||
"bytes"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||
|
|
@ -51,57 +52,63 @@ type Metadata struct {
|
|||
|
||||
// types for the requests/response types
|
||||
type (
|
||||
// SignTxRequest contains info about a transaction to sign
|
||||
// SignTxRequest contains info about a Transaction to sign
|
||||
SignTxRequest struct {
|
||||
transaction types.Transaction
|
||||
from accounts.Account
|
||||
callinfo fmt.Stringer
|
||||
Transaction TransactionArg
|
||||
From common.Address
|
||||
Callinfo fmt.Stringer
|
||||
Meta Metadata
|
||||
}
|
||||
// SignTxResponse result from SignTxRequest
|
||||
SignTxResponse struct {
|
||||
//The UI may make changes to the TX
|
||||
transaction types.Transaction
|
||||
approved bool
|
||||
pw string
|
||||
Transaction TransactionArg
|
||||
From common.Address
|
||||
Approved bool
|
||||
Password string
|
||||
}
|
||||
// ExportRequest info about query to export accounts
|
||||
ExportRequest struct {
|
||||
account accounts.Account
|
||||
file string
|
||||
Address common.Address
|
||||
Meta Metadata
|
||||
}
|
||||
// ExportResponse response to export-request
|
||||
ExportResponse struct {
|
||||
approved bool
|
||||
Approved bool
|
||||
}
|
||||
// ImportRequest info about request to import an account
|
||||
// ImportRequest info about request to import an Account
|
||||
ImportRequest struct {
|
||||
account accounts.Account
|
||||
Meta Metadata
|
||||
}
|
||||
ImportResponse struct {
|
||||
approved bool
|
||||
oldPassword string
|
||||
newPassword string
|
||||
Approved bool
|
||||
OldPassword string
|
||||
NewPassword string
|
||||
}
|
||||
SignDataRequest struct {
|
||||
account accounts.Account
|
||||
rawdata hexutil.Bytes
|
||||
message string
|
||||
hash hexutil.Bytes
|
||||
Address common.Address
|
||||
Rawdata hexutil.Bytes
|
||||
Message string
|
||||
Hash hexutil.Bytes
|
||||
Meta Metadata
|
||||
}
|
||||
SignDataResponse struct {
|
||||
approved bool
|
||||
pw string
|
||||
Approved bool
|
||||
Password string
|
||||
}
|
||||
NewAccountRequest struct {
|
||||
Meta Metadata
|
||||
}
|
||||
NewAccountRequest struct{}
|
||||
NewAccountResponse struct {
|
||||
approved bool
|
||||
pw string
|
||||
Approved bool
|
||||
Password string
|
||||
}
|
||||
ListRequest struct {
|
||||
accounts []Account
|
||||
Accounts []Account
|
||||
Meta Metadata
|
||||
}
|
||||
ListResponse struct {
|
||||
accounts []Account
|
||||
Accounts []Account
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -120,28 +127,28 @@ func (ew errorWrapper) String() string {
|
|||
// for the signer
|
||||
type SignerUI interface {
|
||||
|
||||
// ApproveTx prompt the user for confirmation to request to sign transaction
|
||||
ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse)
|
||||
// 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
|
||||
ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse)
|
||||
// ApproveExport prompt the user for confirmation to export encrypted account json
|
||||
ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse)
|
||||
// ApproveImport prompt the user for confirmation to import account json
|
||||
ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse)
|
||||
ApproveSignData(request *SignDataRequest) (SignDataResponse, error)
|
||||
// ApproveExport prompt the user for confirmation to export encrypted Account json
|
||||
ApproveExport(request *ExportRequest) (ExportResponse, error)
|
||||
// ApproveImport prompt the user for confirmation to import Account json
|
||||
ApproveImport(request *ImportRequest) (ImportResponse, error)
|
||||
// ApproveListing prompt the user for confirmation to list accounts
|
||||
// the list of accounts to list can be modified by the ui
|
||||
ApproveListing(request *ListRequest, metadata Metadata, ch chan ListResponse)
|
||||
// ApproveNewAccount prompt the user for confirmation to create new account, and reveal to caller
|
||||
ApproveNewAccount(requst *NewAccountRequest, metadata Metadata, ch chan NewAccountResponse)
|
||||
ApproveListing(request *ListRequest) (ListResponse, error)
|
||||
// ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller
|
||||
ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error)
|
||||
// ShowError displays error message to user
|
||||
ShowError(message string)
|
||||
// ShowInfo displays info message to user
|
||||
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
|
||||
// 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
|
||||
// ledger and trezor.
|
||||
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.
|
||||
func (api *SignerAPI) List(ctx context.Context) (Accounts, error) {
|
||||
|
||||
ch := make(chan ListResponse, 1)
|
||||
var accs []Account
|
||||
for _, wallet := range api.am.Wallets() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
api.ui.ApproveListing(&ListRequest{accounts: accs}, metaData(ctx), ch)
|
||||
if result := <-ch; result.accounts != nil {
|
||||
return result.accounts, nil
|
||||
result, err := api.ui.ApproveListing(&ListRequest{Accounts: accs, Meta: metaData(ctx)})
|
||||
if err != 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
|
||||
// in the keystore location thas was specified when this API was created.
|
||||
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 {
|
||||
return accounts.Account{}, errors.New("password based accounts not supported")
|
||||
}
|
||||
ch := make(chan NewAccountResponse, 1)
|
||||
api.ui.ApproveNewAccount(&NewAccountRequest{}, metaData(ctx), ch)
|
||||
|
||||
if resp := <-ch; resp.approved {
|
||||
return be[0].(*keystore.KeyStore).NewAccount(resp.pw)
|
||||
resp, err := api.ui.ApproveNewAccount(&NewAccountRequest{metaData(ctx)})
|
||||
|
||||
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`.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
err error
|
||||
result SignTxResponse
|
||||
)
|
||||
|
||||
var tx *types.Transaction
|
||||
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: args, From: from, Meta: metaData(ctx)}
|
||||
|
||||
req := SignTxRequest{transaction: *tx, from: acc}
|
||||
if len(tx.Data()) > 3 {
|
||||
data := args.Data
|
||||
if len(data) > 3 {
|
||||
// Try to make sense of the data
|
||||
var abidata string
|
||||
if methodSig == nil {
|
||||
abidata, err = api.abidb.LookupABI(tx.Data()[:4])
|
||||
abidata, err = api.abidb.LookupABI(data[:4])
|
||||
if err != nil {
|
||||
req.callinfo = errorWrapper{"Warning! Could not locate ABI", err}
|
||||
req.Callinfo = errorWrapper{"Warning! Could not locate ABI", err}
|
||||
}
|
||||
} else {
|
||||
abidata = *methodSig
|
||||
}
|
||||
if abidata != "" {
|
||||
req.callinfo, err = parseCallData(tx.Data(), abidata)
|
||||
req.Callinfo, err = parseCallData(data, abidata)
|
||||
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)
|
||||
api.ui.ApproveTx(&req, metaData(ctx), ch)
|
||||
result, err = api.ui.ApproveTx(&req)
|
||||
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 {
|
||||
//Sanity check
|
||||
if result.transaction.Hash() != tx.Hash() {
|
||||
api.ui.ShowInfo("Transaction modified by UI")
|
||||
}
|
||||
// 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)
|
||||
acc := accounts.Account{Address: result.From}
|
||||
wallet, err := api.am.Find(acc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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:
|
||||
|
|
@ -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
|
||||
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
|
||||
account := accounts.Account{Address: addr}
|
||||
|
||||
wallet, err := api.am.Find(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ch := make(chan SignDataResponse, 1)
|
||||
|
||||
sighash, msg := signHash(data)
|
||||
|
||||
api.ui.ApproveSignData(&SignDataRequest{account: account, rawdata: data, message: msg, hash: sighash}, metaData(ctx), ch)
|
||||
|
||||
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
|
||||
|
||||
// Assemble sign the data with the wallet
|
||||
signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash)
|
||||
if err != nil {
|
||||
api.ui.ShowError(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
// the address of:
|
||||
// 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.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := wallet.URL()
|
||||
if url.Scheme != keystore.KeyStoreScheme {
|
||||
return nil, fmt.Errorf("account is not a password protected account")
|
||||
if wallet.URL().Scheme != keystore.KeyStoreScheme {
|
||||
return nil, fmt.Errorf("Account is not a keystore-account")
|
||||
}
|
||||
ch := make(chan ExportResponse, 1)
|
||||
|
||||
api.ui.ApproveExport(&ExportRequest{account: account, file: url.Path}, metaData(ctx), ch)
|
||||
|
||||
if (<-ch).approved {
|
||||
return ioutil.ReadFile(url.Path)
|
||||
}
|
||||
return nil, ErrRequestDenied
|
||||
return ioutil.ReadFile(wallet.URL().Path)
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
ch := make(chan ImportResponse, 1)
|
||||
res, err := api.ui.ApproveImport(&ImportRequest{Meta: metaData(ctx)})
|
||||
|
||||
api.ui.ApproveImport(&ImportRequest{}, metaData(ctx), ch)
|
||||
|
||||
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
|
||||
if err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
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/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
|
|
@ -22,70 +21,57 @@ type HeadlessUI struct {
|
|||
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 {
|
||||
case "Y":
|
||||
ch <- SignTxResponse{request.transaction, true, <-ui.controller}
|
||||
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil
|
||||
case "M": //Modify
|
||||
old := request.transaction
|
||||
newVal := big.NewInt(0).Add(old.Value(), big.NewInt(1))
|
||||
tx := types.NewTransaction(old.Nonce(), *old.To(), newVal, old.Gas(), old.GasPrice(), old.Data())
|
||||
ch <- SignTxResponse{*tx, true, <-ui.controller}
|
||||
old := (*big.Int)(request.Transaction.Value)
|
||||
newVal := big.NewInt(0).Add(old, big.NewInt(1))
|
||||
request.Transaction.Value = (*hexutil.Big)(newVal)
|
||||
return SignTxResponse{request.Transaction, request.From, true, <-ui.controller}, nil
|
||||
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) {
|
||||
switch <-ui.controller {
|
||||
case "Y":
|
||||
ch <- SignDataResponse{true, <-ui.controller}
|
||||
default:
|
||||
ch <- SignDataResponse{false, ""}
|
||||
func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||
if "Y" == <-ui.controller {
|
||||
return SignDataResponse{true, <-ui.controller}, nil
|
||||
}
|
||||
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 {
|
||||
case "Y":
|
||||
ch <- ExportResponse{true}
|
||||
default:
|
||||
ch <- ExportResponse{false}
|
||||
}
|
||||
return ExportResponse{<-ui.controller == "Y"}, nil
|
||||
|
||||
}
|
||||
func (ui *HeadlessUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
|
||||
func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
||||
|
||||
switch <-ui.controller {
|
||||
case "Y":
|
||||
ch <- ImportResponse{true, <-ui.controller, <-ui.controller}
|
||||
default:
|
||||
ch <- ImportResponse{false, "", ""}
|
||||
if "Y" == <-ui.controller {
|
||||
return ImportResponse{true, <-ui.controller, <-ui.controller}, nil
|
||||
}
|
||||
|
||||
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 {
|
||||
case "A":
|
||||
ch <- ListResponse{request.accounts}
|
||||
return ListResponse{request.Accounts}, nil
|
||||
case "1":
|
||||
l := make([]Account, 1)
|
||||
l[0] = request.accounts[1]
|
||||
ch <- ListResponse{l}
|
||||
l[0] = request.Accounts[1]
|
||||
return ListResponse{l}, nil
|
||||
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 {
|
||||
case "Y":
|
||||
ch <- NewAccountResponse{true, <-ui.controller}
|
||||
default:
|
||||
ch <- NewAccountResponse{false, ""}
|
||||
if "Y" == <-ui.controller {
|
||||
return NewAccountResponse{true, <-ui.controller}, nil
|
||||
}
|
||||
return NewAccountResponse{false, ""}, nil
|
||||
}
|
||||
func (ui *HeadlessUI) ShowError(message string) {
|
||||
//stdout is used by communication
|
||||
|
|
@ -179,14 +165,14 @@ func TestNewAcc(t *testing.T) {
|
|||
verifyNum(4)
|
||||
|
||||
// Testing listing:
|
||||
// Listing one account
|
||||
// Listing one Account
|
||||
control <- "1"
|
||||
list, err := api.List(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("List should only show one account")
|
||||
t.Fatalf("List should only show one Account")
|
||||
}
|
||||
// Listing denied
|
||||
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 {
|
||||
fmt.Printf("Enter password to approve:\n")
|
||||
fmt.Printf("> ")
|
||||
//TODO; remove this, only for debuggging within IDE
|
||||
text := "foobar"
|
||||
//TODO: Use this
|
||||
// text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
//if err != nil {
|
||||
// log.Crit("Failed to read password", "err", err)
|
||||
//}
|
||||
|
||||
text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Crit("Failed to read password", "err", err)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("-----------------------")
|
||||
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)
|
||||
}
|
||||
|
||||
// ApproveTx prompt the user for confirmation to request to sign transaction
|
||||
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest, metadata Metadata, ch chan SignTxResponse) {
|
||||
// ApproveTx prompt the user for confirmation to request to sign Transaction
|
||||
func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) {
|
||||
ui.mu.Lock()
|
||||
defer ui.mu.Unlock()
|
||||
weival := request.transaction.Value()
|
||||
weival := request.Transaction.Value
|
||||
|
||||
fmt.Printf("--------- Transaction request-------------\n")
|
||||
fmt.Printf("to: %v\n", request.transaction.To().Hex())
|
||||
fmt.Printf("from: %v\n", request.from.Address.Hex())
|
||||
fmt.Printf("to: %v\n", request.Transaction.To.Hex())
|
||||
fmt.Printf("from: %v\n", request.From.Hex())
|
||||
fmt.Printf("value: %v wei\n", weival)
|
||||
if len(request.transaction.Data()) > 0 {
|
||||
fmt.Printf("data: %v\n", common.Bytes2Hex(request.transaction.Data()))
|
||||
if len(request.Transaction.Data) > 0 {
|
||||
fmt.Printf("data: %v\n", common.Bytes2Hex(request.Transaction.Data))
|
||||
}
|
||||
if request.callinfo != nil {
|
||||
fmt.Printf("\nNote: This transaction contains data. Review abi-decoding info below:")
|
||||
fmt.Printf("\nCall info:\n\t%v\n", request.callinfo.String())
|
||||
if request.Callinfo != nil {
|
||||
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("\n")
|
||||
showMetadata(metadata)
|
||||
showMetadata(request.Meta)
|
||||
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
|
||||
func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest, metadata Metadata, ch chan SignDataResponse) {
|
||||
func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) {
|
||||
ui.mu.Lock()
|
||||
defer ui.mu.Unlock()
|
||||
|
||||
fmt.Printf("-------- Sign data request--------------\n")
|
||||
fmt.Printf("account: %x\n", request.account.Address)
|
||||
fmt.Printf("message: \n%v\n", request.message)
|
||||
fmt.Printf("raw data: \n%v\n", request.rawdata)
|
||||
fmt.Printf("message hash: %v\n", request.hash)
|
||||
fmt.Printf("Account: %x\n", request.Address)
|
||||
fmt.Printf("message: \n%v\n", request.Message)
|
||||
fmt.Printf("raw data: \n%v\n", request.Rawdata)
|
||||
fmt.Printf("message hash: %v\n", request.Hash)
|
||||
fmt.Printf("-------------------------------------------\n")
|
||||
showMetadata(metadata)
|
||||
ch <- SignDataResponse{true, ui.readPassword()}
|
||||
showMetadata(request.Meta)
|
||||
return SignDataResponse{true, ui.readPassword()}, nil
|
||||
}
|
||||
|
||||
// ApproveExport prompt the user for confirmation to export encrypted account json
|
||||
func (ui *CommandlineUI) ApproveExport(request *ExportRequest, metadata Metadata, ch chan ExportResponse) {
|
||||
// ApproveExport prompt the user for confirmation to export encrypted Account json
|
||||
func (ui *CommandlineUI) ApproveExport(request *ExportRequest) (ExportResponse, error) {
|
||||
ui.mu.Lock()
|
||||
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("Approving this operation means that the caller obtains the (encrypted) contents\n")
|
||||
fmt.Printf("\n")
|
||||
fmt.Printf("account: %x\n", request.account.Address)
|
||||
fmt.Printf("keyfile: \n%v\n", request.file)
|
||||
fmt.Printf("Account: %x\n", request.Address)
|
||||
//fmt.Printf("keyfile: \n%v\n", request.file)
|
||||
fmt.Printf("-------------------------------------------\n")
|
||||
showMetadata(metadata)
|
||||
ch <- ExportResponse{ui.confirm()}
|
||||
showMetadata(request.Meta)
|
||||
return ExportResponse{ui.confirm()}, nil
|
||||
}
|
||||
|
||||
// ApproveImport prompt the user for confirmation to import account json
|
||||
func (ui *CommandlineUI) ApproveImport(request *ImportRequest, metadata Metadata, ch chan ImportResponse) {
|
||||
// ApproveImport prompt the user for confirmation to import Account json
|
||||
func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, error) {
|
||||
ui.mu.Lock()
|
||||
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("-------------------------------------------\n")
|
||||
showMetadata(metadata)
|
||||
if ui.confirm() {
|
||||
ch <- ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}
|
||||
} else {
|
||||
ch <- ImportResponse{false, "", ""}
|
||||
showMetadata(request.Meta)
|
||||
if !ui.confirm() {
|
||||
return ImportResponse{false, "", ""}, nil
|
||||
}
|
||||
return ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}, nil
|
||||
}
|
||||
|
||||
// ApproveListing prompt the user for confirmation to list accounts
|
||||
// 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()
|
||||
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("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("-------------------------------------------\n")
|
||||
showMetadata(metadata)
|
||||
if ui.confirm() {
|
||||
ch <- ListResponse{request.accounts}
|
||||
} else {
|
||||
ch <- ListResponse{nil}
|
||||
showMetadata(request.Meta)
|
||||
if !ui.confirm() {
|
||||
return ListResponse{nil}, nil
|
||||
}
|
||||
return ListResponse{request.Accounts}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller
|
||||
func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) {
|
||||
|
||||
ui.mu.Lock()
|
||||
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("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")
|
||||
showMetadata(metadata)
|
||||
ch <- NewAccountResponse{ui.confirm(), ui.readPassword()}
|
||||
showMetadata(request.Meta)
|
||||
return NewAccountResponse{ui.confirm(), ui.readPassword()}, nil
|
||||
}
|
||||
|
||||
// ShowError displays error message to user
|
||||
|
|
|
|||
|
|
@ -30,13 +30,14 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
"io"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "signer"
|
||||
app.Usage = "Manage ethereum account operations"
|
||||
app.Usage = "Manage ethereum Account operations"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.Int64Flag{
|
||||
Name: "chainid",
|
||||
|
|
@ -77,12 +78,33 @@ func main() {
|
|||
Usage: "File containing requests to handle",
|
||||
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 {
|
||||
// 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"))
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -97,17 +119,18 @@ func main() {
|
|||
c.Int64(utils.NetworkIdFlag.Name),
|
||||
c.String("keystore"),
|
||||
c.Bool(utils.NoUSBFlag.Name),
|
||||
NewCommandlineUI(), db,
|
||||
ui, db,
|
||||
c.Bool(utils.LightKDFFlag.Name))
|
||||
listener net.Listener
|
||||
)
|
||||
// Audit logging
|
||||
if logfile := c.String("auditlog"); logfile != "" {
|
||||
f, err := os.OpenFile(logfile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
utils.Fatalf("Could not open %v for audit logging", logfile)
|
||||
}
|
||||
server.SetAuditLogger(NewAuditLogger(f))
|
||||
log.Info("Writing audit logs to %v", logfile)
|
||||
log.Info("Audit logs configured", "file", logfile)
|
||||
}
|
||||
// register signer API with server
|
||||
if err = server.RegisterName("account", api); err != nil {
|
||||
|
|
@ -125,24 +148,23 @@ func main() {
|
|||
if listener, err = net.Listen("tcp", endpoint); err != nil {
|
||||
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{"*"}
|
||||
|
||||
rpc.NewHTTPServer(cors, server).Serve(listener)
|
||||
|
||||
return nil
|
||||
}
|
||||
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
|
||||
|
||||
// List accounts
|
||||
// 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)
|
||||
// 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()
|
||||
}
|
||||
|
||||
// TransactionArg represents a transaction for the signer.
|
||||
// TransactionArg represents a Transaction for the signer.
|
||||
type TransactionArg struct {
|
||||
To *common.Address `json:"to"`
|
||||
Gas *hexutil.Big `json:"gas"`
|
||||
|
|
|
|||
Loading…
Reference in a new issue