aboutsummaryrefslogtreecommitdiffstats
path: root/internal
diff options
context:
space:
mode:
authorPéter Szilágyi <peterke@gmail.com>2017-02-13 21:03:16 +0800
committerGitHub <noreply@github.com>2017-02-13 21:03:16 +0800
commitf8f428cc18c5f70814d7b3937128781bac14bffd (patch)
treed93d285d2ec22bd8ed646695c3db116c69fa3329 /internal
parente23e86921b55cb1ee2fca6b6fb9ed91f5532f9fd (diff)
parente99c788155ddd754c73d2c81b6051dcbd42e6575 (diff)
downloadgo-tangerine-f8f428cc18c5f70814d7b3937128781bac14bffd.tar.gz
go-tangerine-f8f428cc18c5f70814d7b3937128781bac14bffd.tar.zst
go-tangerine-f8f428cc18c5f70814d7b3937128781bac14bffd.zip
Merge pull request #3592 from karalabe/hw-wallets
accounts: initial support for Ledger hardware wallets
Diffstat (limited to 'internal')
-rw-r--r--internal/ethapi/api.go180
-rw-r--r--internal/guide/guide_test.go35
-rw-r--r--internal/web3ext/web3ext.go12
3 files changed, 164 insertions, 63 deletions
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 4c8e784c5..f49434e17 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -28,6 +28,7 @@ import (
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
@@ -187,8 +188,14 @@ func NewPublicAccountAPI(am *accounts.Manager) *PublicAccountAPI {
}
// Accounts returns the collection of accounts this node manages
-func (s *PublicAccountAPI) Accounts() []accounts.Account {
- return s.am.Accounts()
+func (s *PublicAccountAPI) Accounts() []common.Address {
+ var addresses []common.Address
+ for _, wallet := range s.am.Wallets() {
+ for _, account := range wallet.Accounts() {
+ addresses = append(addresses, account.Address)
+ }
+ }
+ return addresses
}
// PrivateAccountAPI provides an API to access accounts managed by this node.
@@ -209,23 +216,67 @@ func NewPrivateAccountAPI(b Backend) *PrivateAccountAPI {
// ListAccounts will return a list of addresses for accounts this node manages.
func (s *PrivateAccountAPI) ListAccounts() []common.Address {
- accounts := s.am.Accounts()
- addresses := make([]common.Address, len(accounts))
- for i, acc := range accounts {
- addresses[i] = acc.Address
+ var addresses []common.Address
+ for _, wallet := range s.am.Wallets() {
+ for _, account := range wallet.Accounts() {
+ addresses = append(addresses, account.Address)
+ }
}
return addresses
}
+// rawWallet is a JSON representation of an accounts.Wallet interface, with its
+// data contents extracted into plain fields.
+type rawWallet struct {
+ URL string `json:"url"`
+ Status string `json:"status"`
+ Accounts []accounts.Account `json:"accounts"`
+}
+
+// ListWallets will return a list of wallets this node manages.
+func (s *PrivateAccountAPI) ListWallets() []rawWallet {
+ var wallets []rawWallet
+ for _, wallet := range s.am.Wallets() {
+ wallets = append(wallets, rawWallet{
+ URL: wallet.URL().String(),
+ Status: wallet.Status(),
+ Accounts: wallet.Accounts(),
+ })
+ }
+ return wallets
+}
+
+// DeriveAccount requests a HD wallet to derive a new account, optionally pinning
+// it for later reuse.
+func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
+ wallet, err := s.am.Wallet(url)
+ if err != nil {
+ return accounts.Account{}, err
+ }
+ derivPath, err := accounts.ParseDerivationPath(path)
+ if err != nil {
+ return accounts.Account{}, err
+ }
+ if pin == nil {
+ pin = new(bool)
+ }
+ return wallet.Derive(derivPath, *pin)
+}
+
// NewAccount will create a new account and returns the address for the new account.
func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
- acc, err := s.am.NewAccount(password)
+ acc, err := fetchKeystore(s.am).NewAccount(password)
if err == nil {
return acc.Address, nil
}
return common.Address{}, err
}
+// fetchKeystore retrives the encrypted keystore from the account manager.
+func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
+ return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+}
+
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
// encrypting it with the passphrase.
func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) {
@@ -234,7 +285,7 @@ func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (commo
return common.Address{}, err
}
- acc, err := s.am.ImportECDSA(crypto.ToECDSA(hexkey), password)
+ acc, err := fetchKeystore(s.am).ImportECDSA(crypto.ToECDSA(hexkey), password)
return acc.Address, err
}
@@ -251,30 +302,42 @@ func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string,
} else {
d = time.Duration(*duration) * time.Second
}
- err := s.am.TimedUnlock(accounts.Account{Address: addr}, password, d)
+ err := fetchKeystore(s.am).TimedUnlock(accounts.Account{Address: addr}, password, d)
return err == nil, err
}
// LockAccount will lock the account associated with the given address when it's unlocked.
func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool {
- return s.am.Lock(addr) == nil
+ return fetchKeystore(s.am).Lock(addr) == nil
}
// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.To. If the given passwd isn't
// able to decrypt the key it fails.
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
+ // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
- tx := args.toTransaction()
- signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number())
- signature, err := s.am.SignWithPassphrase(accounts.Account{Address: args.From}, passwd, signer.Hash(tx).Bytes())
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: args.From}
+
+ wallet, err := s.am.Find(account)
if err != nil {
return common.Hash{}, err
}
+ // Assemble the transaction and sign with the wallet
+ tx := args.toTransaction()
- return submitTransaction(ctx, s.b, tx, signature)
+ var chainID *big.Int
+ if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
+ chainID = config.ChainId
+ }
+ signed, err := wallet.SignTxWithPassphrase(account, passwd, tx, chainID)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ return submitTransaction(ctx, s.b, signed)
}
// signHash is a helper function that calculates a hash for the given message that can be
@@ -299,7 +362,15 @@ func signHash(data []byte) []byte {
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (s *PrivateAccountAPI) Sign(ctx context.Context, data hexutil.Bytes, addr common.Address, passwd string) (hexutil.Bytes, error) {
- signature, err := s.b.AccountManager().SignWithPassphrase(accounts.Account{Address: addr}, passwd, signHash(data))
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr}
+
+ wallet, err := s.b.AccountManager().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
}
@@ -512,16 +583,15 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr
if state == nil || err != nil {
return nil, common.Big0, err
}
-
// Set sender address or use a default if none specified
addr := args.From
if addr == (common.Address{}) {
- accounts := s.b.AccountManager().Accounts()
- if len(accounts) > 0 {
- addr = accounts[0].Address
+ if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 {
+ if accounts := wallets[0].Accounts(); len(accounts) > 0 {
+ addr = accounts[0].Address
+ }
}
}
-
// Set default gas & gas price if none were set
gas, gasPrice := args.Gas.ToInt(), args.GasPrice.ToInt()
if gas.BitLen() == 0 {
@@ -530,7 +600,6 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr
if gasPrice.BitLen() == 0 {
gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon)
}
-
// Create new call message
msg := types.NewMessage(addr, args.To, 0, args.Value.ToInt(), gas, gasPrice, args.Data, false)
@@ -1023,13 +1092,19 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(txHash common.Hash) (ma
// sign is a helper function that signs a transaction with the private key of the given address.
func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) {
- signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number())
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr}
- signature, err := s.b.AccountManager().Sign(addr, signer.Hash(tx).Bytes())
+ wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return nil, err
}
- return tx.WithSignature(signer, signature)
+ // Request the wallet to sign the transaction
+ var chainID *big.Int
+ if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
+ chainID = config.ChainId
+ }
+ return wallet.SignTx(account, tx, chainID)
}
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
@@ -1076,42 +1151,47 @@ func (args *SendTxArgs) toTransaction() *types.Transaction {
}
// submitTransaction is a helper function that submits tx to txPool and logs a message.
-func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction, signature []byte) (common.Hash, error) {
- signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
-
- signedTx, err := tx.WithSignature(signer, signature)
- if err != nil {
+func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
+ if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
-
- if err := b.SendTx(ctx, signedTx); err != nil {
- return common.Hash{}, err
- }
-
- if signedTx.To() == nil {
- from, _ := types.Sender(signer, signedTx)
- addr := crypto.CreateAddress(from, signedTx.Nonce())
- glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signedTx.Hash().Hex(), addr.Hex())
+ if tx.To() == nil {
+ signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
+ from, _ := types.Sender(signer, tx)
+ addr := crypto.CreateAddress(from, tx.Nonce())
+ glog.V(logger.Info).Infof("Tx(%s) created: %s\n", tx.Hash().Hex(), addr.Hex())
} else {
- glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signedTx.Hash().Hex(), tx.To().Hex())
+ glog.V(logger.Info).Infof("Tx(%s) to: %s\n", tx.Hash().Hex(), tx.To().Hex())
}
-
- return signedTx.Hash(), nil
+ return tx.Hash(), nil
}
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
+ // Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: args.From}
+
+ wallet, err := s.b.AccountManager().Find(account)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ // Assemble the transaction and sign with the wallet
tx := args.toTransaction()
- signer := types.MakeSigner(s.b.ChainConfig(), s.b.CurrentBlock().Number())
- signature, err := s.b.AccountManager().Sign(args.From, signer.Hash(tx).Bytes())
+
+ var chainID *big.Int
+ if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
+ chainID = config.ChainId
+ }
+ signed, err := wallet.SignTx(account, tx, chainID)
if err != nil {
return common.Hash{}, err
}
- return submitTransaction(ctx, s.b, tx, signature)
+ return submitTransaction(ctx, s.b, signed)
}
// SendRawTransaction will add the signed transaction to the transaction pool.
@@ -1151,7 +1231,15 @@ func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encod
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
func (s *PublicTransactionPoolAPI) Sign(addr common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
- signature, err := s.b.AccountManager().Sign(addr, signHash(data))
+ // Look up the wallet containing the requested signer
+ account := accounts.Account{Address: addr}
+
+ wallet, err := s.b.AccountManager().Find(account)
+ if err != nil {
+ return nil, err
+ }
+ // Sign the requested hash with the wallet
+ signature, err := wallet.SignHash(account, signHash(data))
if err == nil {
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
}
@@ -1197,7 +1285,7 @@ func (s *PublicTransactionPoolAPI) PendingTransactions() ([]*RPCTransaction, err
signer = types.NewEIP155Signer(tx.ChainId())
}
from, _ := types.Sender(signer, tx)
- if s.b.AccountManager().HasAddress(from) {
+ if _, err := s.b.AccountManager().Find(accounts.Account{Address: from}); err == nil {
transactions = append(transactions, newRPCPendingTransaction(tx))
}
}
diff --git a/internal/guide/guide_test.go b/internal/guide/guide_test.go
index 8f89037bd..9c7ad16d1 100644
--- a/internal/guide/guide_test.go
+++ b/internal/guide/guide_test.go
@@ -24,13 +24,14 @@ package guide
import (
"io/ioutil"
+ "math/big"
"os"
"path/filepath"
"testing"
"time"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
+ "github.com/ethereum/go-ethereum/core/types"
)
// Tests that the account management snippets work correctly.
@@ -42,59 +43,59 @@ func TestAccountManagement(t *testing.T) {
}
defer os.RemoveAll(workdir)
- // Create an encrypted keystore manager with standard crypto parameters
- am := accounts.NewManager(filepath.Join(workdir, "keystore"), accounts.StandardScryptN, accounts.StandardScryptP)
+ // Create an encrypted keystore with standard crypto parameters
+ ks := keystore.NewKeyStore(filepath.Join(workdir, "keystore"), keystore.StandardScryptN, keystore.StandardScryptP)
// Create a new account with the specified encryption passphrase
- newAcc, err := am.NewAccount("Creation password")
+ newAcc, err := ks.NewAccount("Creation password")
if err != nil {
t.Fatalf("Failed to create new account: %v", err)
}
// Export the newly created account with a different passphrase. The returned
// data from this method invocation is a JSON encoded, encrypted key-file
- jsonAcc, err := am.Export(newAcc, "Creation password", "Export password")
+ jsonAcc, err := ks.Export(newAcc, "Creation password", "Export password")
if err != nil {
t.Fatalf("Failed to export account: %v", err)
}
// Update the passphrase on the account created above inside the local keystore
- if err := am.Update(newAcc, "Creation password", "Update password"); err != nil {
+ if err := ks.Update(newAcc, "Creation password", "Update password"); err != nil {
t.Fatalf("Failed to update account: %v", err)
}
// Delete the account updated above from the local keystore
- if err := am.Delete(newAcc, "Update password"); err != nil {
+ if err := ks.Delete(newAcc, "Update password"); err != nil {
t.Fatalf("Failed to delete account: %v", err)
}
// Import back the account we've exported (and then deleted) above with yet
// again a fresh passphrase
- if _, err := am.Import(jsonAcc, "Export password", "Import password"); err != nil {
+ if _, err := ks.Import(jsonAcc, "Export password", "Import password"); err != nil {
t.Fatalf("Failed to import account: %v", err)
}
// Create a new account to sign transactions with
- signer, err := am.NewAccount("Signer password")
+ signer, err := ks.NewAccount("Signer password")
if err != nil {
t.Fatalf("Failed to create signer account: %v", err)
}
- txHash := common.HexToHash("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
+ tx, chain := new(types.Transaction), big.NewInt(1)
// Sign a transaction with a single authorization
- if _, err := am.SignWithPassphrase(signer, "Signer password", txHash.Bytes()); err != nil {
+ if _, err := ks.SignTxWithPassphrase(signer, "Signer password", tx, chain); err != nil {
t.Fatalf("Failed to sign with passphrase: %v", err)
}
// Sign a transaction with multiple manually cancelled authorizations
- if err := am.Unlock(signer, "Signer password"); err != nil {
+ if err := ks.Unlock(signer, "Signer password"); err != nil {
t.Fatalf("Failed to unlock account: %v", err)
}
- if _, err := am.Sign(signer.Address, txHash.Bytes()); err != nil {
+ if _, err := ks.SignTx(signer, tx, chain); err != nil {
t.Fatalf("Failed to sign with unlocked account: %v", err)
}
- if err := am.Lock(signer.Address); err != nil {
+ if err := ks.Lock(signer.Address); err != nil {
t.Fatalf("Failed to lock account: %v", err)
}
// Sign a transaction with multiple automatically cancelled authorizations
- if err := am.TimedUnlock(signer, "Signer password", time.Second); err != nil {
+ if err := ks.TimedUnlock(signer, "Signer password", time.Second); err != nil {
t.Fatalf("Failed to time unlock account: %v", err)
}
- if _, err := am.Sign(signer.Address, txHash.Bytes()); err != nil {
+ if _, err := ks.SignTx(signer, tx, chain); err != nil {
t.Fatalf("Failed to sign with time unlocked account: %v", err)
}
}
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index edbe45fa3..2012c2517 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -448,6 +448,18 @@ web3._extend({
name: 'ecRecover',
call: 'personal_ecRecover',
params: 2
+ }),
+ new web3._extend.Method({
+ name: 'deriveAccount',
+ call: 'personal_deriveAccount',
+ params: 3
+ })
+ ],
+ properties:
+ [
+ new web3._extend.Property({
+ name: 'listWallets',
+ getter: 'personal_listWallets'
})
]
})