signer: introduce external signer command

This commit is contained in:
Bas van Kervel 2017-07-12 15:28:08 +02:00 committed by Martin Holst Swende
parent 1100e8ba63
commit 6a49fd22c5
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
8 changed files with 845 additions and 2 deletions

View file

@ -127,7 +127,7 @@ func (hub *Hub) refreshWallets() {
// breaking the Ledger protocol if that is waiting for user confirmation. This // breaking the Ledger protocol if that is waiting for user confirmation. This
// is a bug acknowledged at Ledger, but it won't be fixed on old devices so we // is a bug acknowledged at Ledger, but it won't be fixed on old devices so we
// need to prevent concurrent comms ourselves. The more elegant solution would // need to prevent concurrent comms ourselves. The more elegant solution would
// be to ditch enumeration in favor of hutplug events, but that don't work yet // be to ditch enumeration in favor of hotplug events, but that don't work yet
// on Windows so if we need to hack it anyway, this is more elegant for now. // on Windows so if we need to hack it anyway, this is more elegant for now.
hub.commsLock.Lock() hub.commsLock.Lock()
if hub.commsPend > 0 { // A confirmation is pending, don't refresh if hub.commsPend > 0 { // A confirmation is pending, don't refresh

View file

@ -99,7 +99,7 @@ type wallet struct {
// //
// As such, a hardware wallet needs two locks to function correctly. A state // As such, a hardware wallet needs two locks to function correctly. A state
// lock can be used to protect the wallet's software-side internal state, which // lock can be used to protect the wallet's software-side internal state, which
// must not be held exlusively during hardware communication. A communication // must not be held exclusively during hardware communication. A communication
// lock can be used to achieve exclusive access to the device itself, this one // lock can be used to achieve exclusive access to the device itself, this one
// however should allow "skipping" waiting for operations that might want to // however should allow "skipping" waiting for operations that might want to
// use the device, but can live without too (e.g. account self-derivation). // use the device, but can live without too (e.g. account self-derivation).

342
cmd/signer/README.md Normal file
View file

@ -0,0 +1,342 @@
**Signer API**
----
The signer utility can be used to sign transactions and data and is meant as a replacement for geth's account management.
This allows DApp's not to depend on geth's account management. When a DApp wants to sign data it can send the data to
the signer, the signer will than provide the user with context and asks the user for permission to sign the data. If
the users grants the signing request the signer will send the signature back to the DApp.
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can
help in situations when a DApp is connected to a remote node because a local Ethereum node is not available, not
synchronised with the chain or a particular Ethereum node that has no build in, or limited account management.
In its current form the signer is very limited and designed to work with Mist. It hasn't got a connection to an
Ethereum node. This restriction imposed many limitations such as the lack of ability to keep track of nonces, balances
or fetching additional information that can help the user to make a decision to sign a transaction or data. Currently
the signer only supports password protected accounts. Support for hardware tokens such as Trezor and Legder is planned.
## Command line flags
The signer accepts the following command line options:
- keystore, the directory where the password protected keystore stores keyfiles. The default directory is within geth's datadir. It is OS dependand, use `signer -h` to see where the location is on your system.
- chainid, the chain identifier. Default value is the Ethereum mainnet. See of a list of chain identifiers that are used https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md.
Example:
```
signer -keystore /my/keystore -chainid 4
```
## Communicating
The signer listens on stdin for incoming requests and sends responses on stdout. Messages are expected to follow the
[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
may never be received if a users decideds to ignore the confirmation request.
## API
### Encoding
- number: positive integers that are hex encoded
- data: hex encoded data
- string: ASCII string
All hex encoded values must be prefixed with `0x`.
## Methods
### account_new
#### Create new password protected account
The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory.
The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving
lost accounts.
#### Arguments
- passphrase [string]: passphrase that is used to protect the private key that is stored within the keystore
#### Result
- address [string]: account address that is derived from the generated key
- url [string]: location of the keyfile
#### Sample call
```
{
"id": 0,
"jsonrpc": "2.0",
"method": "account_new",
"params": [
"my password"
]
}
{
"id": 0,
"jsonrpc": "2.0",
"result": {
"address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133",
"url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133"
}
}
```
### account_list
#### List available accounts
List all accounts that this signer currently manages
#### Arguments
none
#### Result
- array with account records:
- account.address [string]: account address that is derived from the generated key
- account.type [string]: type of the
- account.url [string]: location of the account
#### Sample call
```
{
"id": 1,
"jsonrpc": "2.0",
"method": "account_list"
}
{
"id": 1,
"jsonrpc": "2.0",
"result": [
{
"address": "0xafb2f771f58513609765698f65d3f2f0224a956f",
"type": "account",
"url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f"
},
{
"address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133",
"type": "account",
"url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133"
}
]
}
```
### account_signTransaction
#### Sign transactions
Signs a transactions and respons with the signed transaction in RLP encoded form.
#### Arguments
- from [address]: account to send the transaction from
- passphrase [string]: passphrase to unlock the from account
- Transaction object:
- transaction.to [address]: receiver account
- gas [number]: maximum amount of gas to burn
- gasPrice [number]: gas price
- value [number:optional]: amount of Wei to send with the transaction
- data [data:optional]: input data
- transaction.nonce [number]: account nonce
#### Result
- signed transaction in RLP encoded form [data]
#### Sample call
```
{
"id": 2,
"jsonrpc": "2.0",
"method": "account_signTransaction",
"params": [
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
"my password",
{
"gas": "0x55555",
"gasPrice": "0x1234",
"input": "0xabcd",
"nonce": "0x0",
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
"value": "0x1234"
}
]
}
{
"id": 2,
"jsonrpc": "2.0",
"result": "0xf86480821234830555559407a565b7ed7d7a678680a4c162885bedbb695fe0821234802ea028f9ebeff90732eae45692a11c4ca2ef7f631a0a25bf8763d093e770c4ec464aa01fae77b24617913e718b989be78bc1aabb2fed3f2d4e3b93bd36759f1b5b4904"
}
```
### account_sign
#### Sign data
Signs a chunk of data and returns the calculated signature.
#### Arguments
- account [address]: account to sign with
- passphrase [string]: passphrase to unlock the account
- data [data]: data to sign
#### Result
- calculated signature [data]
#### Sample call
```
{
"id": 3,
"jsonrpc": "2.0",
"method": "account_sign",
"params": [
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
"my password",
"0xaabbccdd"
]
}
{
"id": 3,
"jsonrpc": "2.0",
"result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
}
```
### account_ecRecover
#### Recover address
Derive the address from the account that was used to sign data from the data and signature.
#### Arguments
- data [data]: data that was signed
- signature [data]: the signature to verify
#### Result
- derived account [address]
#### Sample call
```
{
"id": 4,
"jsonrpc": "2.0",
"method": "account_ecRecover",
"params": [
"0xaabbccdd",
"0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
]
}
{
"id": 4,
"jsonrpc": "2.0",
"result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db"
}
```
### account_import
#### Import account
Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore
format.
#### Arguments
- account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export)
- passphrase [string]: password to decrypt the given account
- newPassphrase [string]: password to encrypt the imported key in the keystore with
#### Result
- imported key [object]:
- key.address [address]: address of the imported key
- key.type [string]: type of the account
- key.url [string]: key URL
#### Sample call
```
{
"id": 6,
"jsonrpc": "2.0",
"method": "account_import",
"params": [
{
"address": "c7412fc59930fd90099c917a50e5f11d0934b2f5",
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": "401c39a7c7af0388491c3d3ecb39f532"
},
"ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a"
},
"mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806"
},
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
"version": 3
},
"my password",
"my password"
]
}
{
"id": 6,
"jsonrpc": "2.0",
"result": {
"address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5",
"type": "account",
"url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5"
}
}
```
### account_export
#### Export account from keystore
Export a private key from the keystore. The exported private key is encrypted with the original passphrase. When the
key is imported later this passphrase is required.
#### Arguments
- account [address]: export private key that is associated with this account
#### Result
- exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for
more information
#### Sample call
```
{
"id": 5,
"jsonrpc": "2.0",
"method": "account_export",
"params": [
"0xc7412fc59930fd90099c917a50e5f11d0934b2f5"
]
}
{
"id": 5,
"jsonrpc": "2.0",
"result": {
"address": "c7412fc59930fd90099c917a50e5f11d0934b2f5",
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": "401c39a7c7af0388491c3d3ecb39f532"
},
"ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"p": 1,
"r": 8,
"salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a"
},
"mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806"
},
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
"version": 3
}
}
```

229
cmd/signer/api.go Normal file
View file

@ -0,0 +1,229 @@
// 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 (
"fmt"
"errors"
"context"
"math/big"
"io/ioutil"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/usbwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"encoding/json"
)
type SignerAPI struct {
chainID *big.Int
am *accounts.Manager
}
// 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.
// noUSB disables USB support that is required to support hardware devices such as
// ledger and trezor.
func NewSignerAPI(chainID int64, ksLocation string, noUSB bool) *SignerAPI {
var backends []accounts.Backend
// support password based accounts
if len(ksLocation) > 0 {
backends = append(backends, keystore.NewKeyStore(ksLocation, keystore.StandardScryptN, keystore.StandardScryptP))
}
if !noUSB {
// Start a USB hub for Ledger hardware wallets
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
} else {
backends = append(backends, ledgerhub)
log.Debug("Ledger support enabled")
}
// Start a USB hub for Trezor hardware wallets
if trezorhub, err := usbwallet.NewTrezorHub(); err != nil {
log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
} else {
backends = append(backends, trezorhub)
log.Debug("Trezor support enabled")
}
}
return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...)}
}
// List returns the set of wallet this signer manages. Each wallet can contain
// multiple accounts.
func (api *SignerAPI) List(ctx context.Context) []Account {
var accounts []Account
for _, wallet := range api.am.Wallets() {
for _, acc := range wallet.Accounts() {
acc := Account{Typ: "account", URL: wallet.URL(), Address: acc.Address}
accounts = append(accounts, acc)
}
}
return accounts
}
// 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, passphrase string) (accounts.Account, error) {
be := api.am.Backends(keystore.KeyStoreType)
if len(be) == 0 {
return accounts.Account{}, errors.New("password based accounts not supported")
}
acc, err := be[0].(*keystore.KeyStore).NewAccount(passphrase)
return acc, err
}
// 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, passwd string, args TransactionArg) (hexutil.Bytes, error) {
acc := accounts.Account{Address: from}
wallet, err := api.am.Find(acc)
if err != nil {
return nil, err
}
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)
}
signedTx, err := wallet.SignTxWithPassphrase(acc, passwd, tx, api.chainID)
if err != nil {
return nil, err
}
return rlp.EncodeToBytes(signedTx)
}
// Sign calculates an Ethereum ECDSA signature for:
// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message))
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The key used to calculate the signature is decrypted with the given password.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (api *SignerAPI) Sign(ctx context.Context, addr common.Address, passwd string, data hexutil.Bytes) (hexutil.Bytes, error) {
// 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
}
// Assemble sign the data with the wallet
signature, err := wallet.SignHashWithPassphrase(account, passwd, signHash(data))
if err != nil {
return nil, err
}
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.
// 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})
// addr = ecrecover(hash, signature)
//
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
// the V value must be be 27 or 28 for legacy reasons.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
func (api *SignerAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
if len(sig) != 65 {
return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
}
if sig[64] != 27 && sig[64] != 28 {
return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
}
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
rpk, err := crypto.Ecrecover(signHash(data), sig)
if err != nil {
return common.Address{}, err
}
pubKey := crypto.ToECDSAPub(rpk)
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return recoveredAddr, nil
}
// signHash is a helper function that calculates a hash for the given message that can be
// safely used to calculate a signature from.
//
// The hash is calulcated as
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
//
// This gives context to the signed message and prevents signing of transactions.
func signHash(data []byte) []byte {
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
return crypto.Keccak256([]byte(msg))
}
// 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)
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")
}
return ioutil.ReadFile(url.Path)
}
// Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be
// in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful
// decryption it will encrypt the key with the given newPassphrase and store it in the keystore.
func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage, passphrase, newPassphrase string) (Account, error) {
be := api.am.Backends(keystore.KeyStoreType)
if len(be) == 0 {
return Account{}, errors.New("password based accounts not supported")
}
acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, passphrase, newPassphrase)
if err != nil {
return Account{}, err
}
return Account{Typ: "account", URL: acc.URL, Address: acc.Address}, nil
}

141
cmd/signer/demo.js Normal file
View file

@ -0,0 +1,141 @@
/* Demonstrates the following signer methods
- account_new: generate new password protected account
1. password: string
returns account object with address and URL
- account_list: listing of accounts
no args
returns array with accounts
- account_signTransaction: sign transaction and get tx in RLP encoded form back
1. from: address
2. passwd: string
3. transaction: object
returns signed transaction in RLP form (can be used with eth_sendRawTransaction)
- account_sign: calculate signature
1. from: address
2. passwd: string
3. data: hex string
returns signature
- account_ecRecover: derive address from signature
1. data: hex string
2. signature: hex string
returns address
*/
var spawn = require('child_process').spawn;
// by default the signer used the keystore for the mainnet, in this case it is pointed to a non-standard location.
// also it accepts the chainid, by default it uses the chainid for the mainnet.
const signer = spawn('./signer', ['-keystore', '/tmp/keystore', '-chainid', 5]);
const passwd = 'my password';
var createdAccountAddress = '0x';
var signData = '0xaabbccdd';
var signSignature = '0x';
var keystoreKeyData = '0x';
var currentRequest = -1;
function nextRequest() {
currentRequest++;
var req = null;
if (currentRequest < 7) {
req = {
id: currentRequest,
jsonrpc: "2.0"
};
switch (currentRequest) {
case 0:
req.method = 'account_new';
req.params = [passwd];
break
case 1:
req.method = 'account_list';
break;
case 2:
req.method = 'account_signTransaction';
req.params = [createdAccountAddress, passwd, {
nonce: "0x0",
gasPrice: "0x1234",
gas: "0x55555",
value: "0x1234",
input: "0xabcd",
to: "0x07a565b7ed7d7a678680a4c162885bedbb695fe0"
}];
break;
case 3:
req.method = 'account_sign';
req.params = [createdAccountAddress, passwd, signData];
break;
case 4:
req.method = 'account_ecRecover';
req.params = [signData, signSignature];
break;
case 5:
req.method = 'account_export';
req.params = [createdAccountAddress]
break;
case 6:
req.method = 'account_import';
req.params = [keystoreKeyData, passwd, passwd];
break;
}
}
return req;
}
signer.stdout.on('data', (data) => {
console.log(`${data}`);
response = JSON.parse(`${data}`);
switch (response.id) {
case 0:
createdAccountAddress = response.result.address;
break;
case 3:
signSignature = response.result;
break
case 4:
if (createdAccountAddress !== response.result) {
console.error("expected address", createdAccountAddress, "got", response.result);
} else {
//console.log("Address recovered correct");
}
break;
case 5:
keystoreKeyData = response.result;
break;
}
var req = nextRequest();
if (req !== null) {
req = JSON.stringify(req);
console.log(req);
signer.stdin.write(req);
} else {
signer.kill();
}
});
signer.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
signer.on('close', (code) => {
//console.log(`signer process exited with code ${code}`);
});
// kickstart request cycle
req = JSON.stringify(nextRequest());
console.log(req);
signer.stdin.write(req);

68
cmd/signer/main.go Normal file
View file

@ -0,0 +1,68 @@
// 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/>.
// signer is a utility that can be used so sign transactions and
// arbitrary data.
package main
import (
"bufio"
"flag"
"io"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
var (
ksLocation = flag.String("keystore", filepath.Join(node.DefaultDataDir(), "keystore"), "Directory for the keystore")
chainID = flag.Int64("chainid", params.MainnetChainConfig.ChainId.Int64(), "chain identifier")
)
func main() {
flag.Parse()
var (
server = rpc.NewServer()
api = NewSignerAPI(*chainID, *ksLocation, true)
)
// register signer API with server
if err := server.RegisterName("account", api); err != nil {
utils.Fatalf("Could not register signer API: %v", err)
}
// start server with in-/output connected to stdin/stdout
in, out := bufio.NewReader(os.Stdin), os.Stdout
codec := rpc.NewJSONCodec(&rwc{in, out})
server.ServeCodec(codec, rpc.OptionMethodInvocation|rpc.OptionSubscriptions)
}
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()
}

24
cmd/signer/test.js Normal file
View file

@ -0,0 +1,24 @@
const net = require('net');
const path = require('path');
let conn = net.connect(path.join('\\\\?\\pipe', 'ethereum-signer'))
const req = {
id: 1234,
jsonrpc: '2.0',
//method: 'account_list',
method: 'account_signTransaction',
params: ['0xaabbccddaabbccddaabbccddaabbccddaabbccdd', {
to: '0x0011223344556677889900112233445566778899',
value: '0x123450000',
data: '0xabcdef',
gas: '0x12345',
gasPrice: '0x67890'
}]
};
conn.on('data', (data) => {
console.log(data.toString());
});
conn.write(JSON.stringify(req));

39
cmd/signer/types.go Normal file
View file

@ -0,0 +1,39 @@
// 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 (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type Account struct {
Typ string `json:"type"`
URL accounts.URL `json:"url"`
Address common.Address `json:"address"`
}
// TransactionArg represents a transaction for the signer.
type TransactionArg struct {
To *common.Address `json:"to"`
Gas *hexutil.Big `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"`
Nonce *hexutil.Uint64 `json:"nonce"`
}