diff options
author | Péter Szilágyi <peterke@gmail.com> | 2017-02-13 21:03:16 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-13 21:03:16 +0800 |
commit | f8f428cc18c5f70814d7b3937128781bac14bffd (patch) | |
tree | d93d285d2ec22bd8ed646695c3db116c69fa3329 /internal | |
parent | e23e86921b55cb1ee2fca6b6fb9ed91f5532f9fd (diff) | |
parent | e99c788155ddd754c73d2c81b6051dcbd42e6575 (diff) | |
download | go-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.go | 180 | ||||
-rw-r--r-- | internal/guide/guide_test.go | 35 | ||||
-rw-r--r-- | internal/web3ext/web3ext.go | 12 |
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' }) ] }) |