diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go
index e51f0bd8ea..a8ca8e70f8 100644
--- a/accounts/abi/bind/auth.go
+++ b/accounts/abi/bind/auth.go
@@ -46,7 +46,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
// an decrypted key from a keystore
-func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
+func NewKeyStoreTransactor(keystore keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
return &TransactOpts{
From: account.Address,
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go
index fe9233c046..6d5f4528a7 100644
--- a/accounts/keystore/account_cache_test.go
+++ b/accounts/keystore/account_cache_test.go
@@ -97,7 +97,7 @@ func TestWatchNoDir(t *testing.T) {
// Create ks but not the directory that it watches.
rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
- ks := NewKeyStore(dir, LightScryptN, LightScryptP)
+ ks := newKeyStore(dir, LightScryptN, LightScryptP)
list := ks.Accounts()
if len(list) > 0 {
@@ -297,7 +297,7 @@ func TestCacheFind(t *testing.T) {
}
}
-func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
+func waitForAccounts(wantAccounts []accounts.Account, ks *keyStoreFS) error {
var list []accounts.Account
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
list = ks.Accounts()
@@ -323,7 +323,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
// Create a temporary kesytore to test with
rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
- ks := NewKeyStore(dir, LightScryptN, LightScryptP)
+ ks := newKeyStore(dir, LightScryptN, LightScryptP)
list := ks.Accounts()
if len(list) > 0 {
diff --git a/accounts/keystore/db_keystore.go b/accounts/keystore/db_keystore.go
new file mode 100644
index 0000000000..b2a1d6f4fb
--- /dev/null
+++ b/accounts/keystore/db_keystore.go
@@ -0,0 +1,9 @@
+package keystore
+
+import "reflect"
+
+// DBKeyStoreType is the reflect type of a keystore backend.
+var DBKeyStoreType = reflect.TypeOf(&keyStoreDB{})
+
+type keyStoreDB struct {
+}
diff --git a/accounts/keystore/fs_keystore.go b/accounts/keystore/fs_keystore.go
new file mode 100644
index 0000000000..7300dd789d
--- /dev/null
+++ b/accounts/keystore/fs_keystore.go
@@ -0,0 +1,462 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package keystore implements encrypted storage of secp256k1 private keys.
+//
+// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
+// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
+package keystore
+
+import (
+ "crypto/ecdsa"
+ crand "crypto/rand"
+ "fmt"
+ "math/big"
+ "os"
+ "reflect"
+ "runtime"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/event"
+)
+
+// FSKeyStoreType is the reflect type of a keystore backend.
+var FSKeyStoreType = reflect.TypeOf(&keyStoreFS{})
+
+// Maximum time between wallet refreshes (if filesystem notifications don't work).
+const walletRefreshCycle = 3 * time.Second
+
+// KeyStore manages a key storage directory on disk.
+type keyStoreFS struct {
+ storage keyStore // Storage backend, might be cleartext or encrypted
+ cache *accountCache // In-memory account cache over the filesystem storage
+ changes chan struct{} // Channel receiving change notifications from the cache
+ unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
+
+ wallets []accounts.Wallet // Wallet wrappers around the individual key files
+ updateFeed event.Feed // Event feed to notify wallet additions/removals
+ updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
+ updating bool // Whether the event notification loop is running
+
+ mu sync.RWMutex
+}
+
+func (ks *keyStoreFS) init(keydir string) {
+ // Lock the mutex since the account cache might call back with events
+ ks.mu.Lock()
+ defer ks.mu.Unlock()
+
+ // Initialize the set of unlocked keys and the account cache
+ ks.unlocked = make(map[common.Address]*unlocked)
+ ks.cache, ks.changes = newAccountCache(keydir)
+
+ // TODO: In order for this finalizer to work, there must be no references
+ // to ks. addressCache doesn't keep a reference but unlocked keys do,
+ // so the finalizer will not trigger until all timed unlocks have expired.
+ runtime.SetFinalizer(ks, func(m *keyStoreFS) {
+ m.cache.close()
+ })
+ // Create the initial list of wallets from the cache
+ accs := ks.cache.accounts()
+ ks.wallets = make([]accounts.Wallet, len(accs))
+ for i := 0; i < len(accs); i++ {
+ ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
+ }
+}
+
+// Wallets implements accounts.Backend, returning all single-key wallets from the
+// keystore directory.
+func (ks *keyStoreFS) Wallets() []accounts.Wallet {
+ // Make sure the list of wallets is in sync with the account cache
+ ks.refreshWallets()
+
+ ks.mu.RLock()
+ defer ks.mu.RUnlock()
+
+ cpy := make([]accounts.Wallet, len(ks.wallets))
+ copy(cpy, ks.wallets)
+ return cpy
+}
+
+// refreshWallets retrieves the current account list and based on that does any
+// necessary wallet refreshes.
+func (ks *keyStoreFS) refreshWallets() {
+ // Retrieve the current list of accounts
+ ks.mu.Lock()
+ accs := ks.cache.accounts()
+
+ // Transform the current list of wallets into the new one
+ var (
+ wallets = make([]accounts.Wallet, 0, len(accs))
+ events []accounts.WalletEvent
+ )
+
+ for _, account := range accs {
+ // Drop wallets while they were in front of the next account
+ for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
+ events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
+ ks.wallets = ks.wallets[1:]
+ }
+ // If there are no more wallets or the account is before the next, wrap new wallet
+ if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
+ wallet := &keystoreWallet{account: account, keystore: ks}
+
+ events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
+ wallets = append(wallets, wallet)
+ continue
+ }
+ // If the account is the same as the first wallet, keep it
+ if ks.wallets[0].Accounts()[0] == account {
+ wallets = append(wallets, ks.wallets[0])
+ ks.wallets = ks.wallets[1:]
+ continue
+ }
+ }
+ // Drop any leftover wallets and set the new batch
+ for _, wallet := range ks.wallets {
+ events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
+ }
+ ks.wallets = wallets
+ ks.mu.Unlock()
+
+ // Fire all wallet events and return
+ for _, event := range events {
+ ks.updateFeed.Send(event)
+ }
+}
+
+// Subscribe implements accounts.Backend, creating an async subscription to
+// receive notifications on the addition or removal of keystore wallets.
+func (ks *keyStoreFS) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
+ // We need the mutex to reliably start/stop the update loop
+ ks.mu.Lock()
+ defer ks.mu.Unlock()
+
+ // Subscribe the caller and track the subscriber count
+ sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
+
+ // Subscribers require an active notification loop, start it
+ if !ks.updating {
+ ks.updating = true
+ go ks.updater()
+ }
+ return sub
+}
+
+// updater is responsible for maintaining an up-to-date list of wallets stored in
+// the keystore, and for firing wallet addition/removal events. It listens for
+// account change events from the underlying account cache, and also periodically
+// forces a manual refresh (only triggers for systems where the filesystem notifier
+// is not running).
+func (ks *keyStoreFS) updater() {
+ for {
+ // Wait for an account update or a refresh timeout
+ select {
+ case <-ks.changes:
+ case <-time.After(walletRefreshCycle):
+ }
+ // Run the wallet refresher
+ ks.refreshWallets()
+
+ // If all our subscribers left, stop the updater
+ ks.mu.Lock()
+ if ks.updateScope.Count() == 0 {
+ ks.updating = false
+ ks.mu.Unlock()
+ return
+ }
+ ks.mu.Unlock()
+ }
+}
+
+// HasAddress reports whether a key with the given address is present.
+func (ks *keyStoreFS) HasAddress(addr common.Address) bool {
+ return ks.cache.hasAddress(addr)
+}
+
+// Accounts returns all key files present in the directory.
+func (ks *keyStoreFS) Accounts() []accounts.Account {
+ return ks.cache.accounts()
+}
+
+// Delete deletes the key matched by account if the passphrase is correct.
+// If the account contains no filename, the address must match a unique key.
+func (ks *keyStoreFS) Delete(a accounts.Account, passphrase string) error {
+ // Decrypting the key isn't really necessary, but we do
+ // it anyway to check the password and zero out the key
+ // immediately afterwards.
+ a, key, err := ks.getDecryptedKey(a, passphrase)
+ if key != nil {
+ zeroKey(key.PrivateKey)
+ }
+ if err != nil {
+ return err
+ }
+ // The order is crucial here. The key is dropped from the
+ // cache after the file is gone so that a reload happening in
+ // between won't insert it into the cache again.
+ err = os.Remove(a.URL.Path)
+ if err == nil {
+ ks.cache.delete(a)
+ ks.refreshWallets()
+ }
+ return err
+}
+
+// SignHash calculates a ECDSA signature for the given hash. The produced
+// signature is in the [R || S || V] format where V is 0 or 1.
+func (ks *keyStoreFS) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
+ // Look up the key to sign with and abort if it cannot be found
+ ks.mu.RLock()
+ defer ks.mu.RUnlock()
+
+ unlockedKey, found := ks.unlocked[a.Address]
+ if !found {
+ return nil, ErrLocked
+ }
+ // Sign the hash using plain ECDSA operations
+ return crypto.Sign(hash, unlockedKey.PrivateKey)
+}
+
+// SignTx signs the given transaction with the requested account.
+func (ks *keyStoreFS) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
+ // Look up the key to sign with and abort if it cannot be found
+ ks.mu.RLock()
+ defer ks.mu.RUnlock()
+
+ unlockedKey, found := ks.unlocked[a.Address]
+ if !found {
+ return nil, ErrLocked
+ }
+ // Depending on the presence of the chain ID, sign with EIP155 or homestead
+ if chainID != nil {
+ return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
+ }
+ return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)
+}
+
+// SignHashWithPassphrase signs hash if the private key matching the given address
+// can be decrypted with the given passphrase. The produced signature is in the
+// [R || S || V] format where V is 0 or 1.
+func (ks *keyStoreFS) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
+ _, key, err := ks.getDecryptedKey(a, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ defer zeroKey(key.PrivateKey)
+ return crypto.Sign(hash, key.PrivateKey)
+}
+
+// SignTxWithPassphrase signs the transaction if the private key matching the
+// given address can be decrypted with the given passphrase.
+func (ks *keyStoreFS) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
+ _, key, err := ks.getDecryptedKey(a, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ defer zeroKey(key.PrivateKey)
+
+ // Depending on the presence of the chain ID, sign with EIP155 or homestead
+ if chainID != nil {
+ return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey)
+ }
+ return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey)
+}
+
+// Unlock unlocks the given account indefinitely.
+func (ks *keyStoreFS) Unlock(a accounts.Account, passphrase string) error {
+ return ks.TimedUnlock(a, passphrase, 0)
+}
+
+// Lock removes the private key with the given address from memory.
+func (ks *keyStoreFS) Lock(addr common.Address) error {
+ ks.mu.Lock()
+ if unl, found := ks.unlocked[addr]; found {
+ ks.mu.Unlock()
+ ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
+ } else {
+ ks.mu.Unlock()
+ }
+ return nil
+}
+
+// TimedUnlock unlocks the given account with the passphrase. The account
+// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
+// until the program exits. The account must match a unique key file.
+//
+// If the account address is already unlocked for a duration, TimedUnlock extends or
+// shortens the active unlock timeout. If the address was previously unlocked
+// indefinitely the timeout is not altered.
+func (ks *keyStoreFS) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
+ a, key, err := ks.getDecryptedKey(a, passphrase)
+ if err != nil {
+ return err
+ }
+
+ ks.mu.Lock()
+ defer ks.mu.Unlock()
+ u, found := ks.unlocked[a.Address]
+ if found {
+ if u.abort == nil {
+ // The address was unlocked indefinitely, so unlocking
+ // it with a timeout would be confusing.
+ zeroKey(key.PrivateKey)
+ return nil
+ }
+ // Terminate the expire goroutine and replace it below.
+ close(u.abort)
+ }
+ if timeout > 0 {
+ u = &unlocked{Key: key, abort: make(chan struct{})}
+ go ks.expire(a.Address, u, timeout)
+ } else {
+ u = &unlocked{Key: key}
+ }
+ ks.unlocked[a.Address] = u
+ return nil
+}
+
+// Find resolves the given account into a unique entry in the keystore.
+func (ks *keyStoreFS) Find(a accounts.Account) (accounts.Account, error) {
+ ks.cache.maybeReload()
+ ks.cache.mu.Lock()
+ a, err := ks.cache.find(a)
+ ks.cache.mu.Unlock()
+ return a, err
+}
+
+func (ks *keyStoreFS) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
+ a, err := ks.Find(a)
+ if err != nil {
+ return a, nil, err
+ }
+ key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
+ return a, key, err
+}
+
+func (ks *keyStoreFS) expire(addr common.Address, u *unlocked, timeout time.Duration) {
+ t := time.NewTimer(timeout)
+ defer t.Stop()
+ select {
+ case <-u.abort:
+ // just quit
+ case <-t.C:
+ ks.mu.Lock()
+ // only drop if it's still the same key instance that dropLater
+ // was launched with. we can check that using pointer equality
+ // because the map stores a new pointer every time the key is
+ // unlocked.
+ if ks.unlocked[addr] == u {
+ zeroKey(u.PrivateKey)
+ delete(ks.unlocked, addr)
+ }
+ ks.mu.Unlock()
+ }
+}
+
+// NewAccount generates a new key and stores it into the key directory,
+// encrypting it with the passphrase.
+func (ks *keyStoreFS) NewAccount(passphrase string) (accounts.Account, error) {
+ _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
+ if err != nil {
+ return accounts.Account{}, err
+ }
+ // Add the account to the cache immediately rather
+ // than waiting for file system notifications to pick it up.
+ ks.cache.add(account)
+ ks.refreshWallets()
+ return account, nil
+}
+
+// Export exports as a JSON key, encrypted with newPassphrase.
+func (ks *keyStoreFS) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
+ _, key, err := ks.getDecryptedKey(a, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ var N, P int
+ if store, ok := ks.storage.(*keyStorePassphrase); ok {
+ N, P = store.scryptN, store.scryptP
+ } else {
+ N, P = StandardScryptN, StandardScryptP
+ }
+ return EncryptKey(key, newPassphrase, N, P)
+}
+
+// Import stores the given encrypted JSON key into the key directory.
+func (ks *keyStoreFS) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
+ key, err := DecryptKey(keyJSON, passphrase)
+ if key != nil && key.PrivateKey != nil {
+ defer zeroKey(key.PrivateKey)
+ }
+ if err != nil {
+ return accounts.Account{}, err
+ }
+ return ks.importKey(key, newPassphrase)
+}
+
+// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
+func (ks *keyStoreFS) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
+ key := newKeyFromECDSA(priv)
+ if ks.cache.hasAddress(key.Address) {
+ return accounts.Account{}, fmt.Errorf("account already exists")
+ }
+ return ks.importKey(key, passphrase)
+}
+
+func (ks *keyStoreFS) importKey(key *Key, passphrase string) (accounts.Account, error) {
+ a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
+ if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
+ return accounts.Account{}, err
+ }
+ ks.cache.add(a)
+ ks.refreshWallets()
+ return a, nil
+}
+
+// Update changes the passphrase of an existing account.
+func (ks *keyStoreFS) Update(a accounts.Account, passphrase, newPassphrase string) error {
+ a, key, err := ks.getDecryptedKey(a, passphrase)
+ if err != nil {
+ return err
+ }
+ return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
+}
+
+// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
+// a key file in the key directory. The key file is encrypted with the same passphrase.
+func (ks *keyStoreFS) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
+ a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
+ if err != nil {
+ return a, err
+ }
+ ks.cache.add(a)
+ ks.refreshWallets()
+ return a, nil
+}
+
+// zeroKey zeroes a private key in memory.
+func zeroKey(k *ecdsa.PrivateKey) {
+ b := k.D.Bits()
+ for i := range b {
+ b[i] = 0
+ }
+}
diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/fs_keystore_test.go
similarity index 93%
rename from accounts/keystore/keystore_test.go
rename to accounts/keystore/fs_keystore_test.go
index a691c50627..93d453ddcb 100644
--- a/accounts/keystore/keystore_test.go
+++ b/accounts/keystore/fs_keystore_test.go
@@ -20,6 +20,7 @@ import (
"io/ioutil"
"math/rand"
"os"
+ "path/filepath"
"runtime"
"sort"
"strings"
@@ -374,14 +375,31 @@ func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
}
}
-func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
+func tmpKeyStore(t *testing.T, encrypted bool) (string, *keyStoreFS) {
d, err := ioutil.TempDir("", "eth-keystore-test")
if err != nil {
t.Fatal(err)
}
- newKs := NewPlaintextKeyStore
+ newKs := newPlaintextKeyStore
if encrypted {
- newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
+ newKs = func(kd string) *keyStoreFS { return newKeyStore(kd, veryLightScryptN, veryLightScryptP) }
}
return d, newKs(d)
}
+
+// NewKeyStore creates a keystore for the given directory.
+func newKeyStore(keydir string, scryptN, scryptP int) *keyStoreFS {
+ keydir, _ = filepath.Abs(keydir)
+ ks := &keyStoreFS{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
+ ks.init(keydir)
+ return ks
+}
+
+// NewPlaintextKeyStore creates a keystore for the given directory.
+// Deprecated: Use NewKeyStore.
+func newPlaintextKeyStore(keydir string) *keyStoreFS {
+ keydir, _ = filepath.Abs(keydir)
+ ks := &keyStoreFS{storage: &keyStorePlain{keydir}}
+ ks.init(keydir)
+ return ks
+}
diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go
index 5b55175b1f..d3faf7b77a 100644
--- a/accounts/keystore/keystore.go
+++ b/accounts/keystore/keystore.go
@@ -1,42 +1,15 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library 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 Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package keystore implements encrypted storage of secp256k1 private keys.
-//
-// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
-// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
package keystore
import (
"crypto/ecdsa"
- crand "crypto/rand"
"errors"
- "fmt"
"math/big"
- "os"
"path/filepath"
- "reflect"
- "runtime"
- "sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
)
@@ -46,28 +19,81 @@ var (
ErrDecrypt = errors.New("could not decrypt key with given password")
)
-// KeyStoreType is the reflect type of a keystore backend.
-var KeyStoreType = reflect.TypeOf(&KeyStore{})
-
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
const KeyStoreScheme = "keystore"
-// Maximum time between wallet refreshes (if filesystem notifications don't work).
-const walletRefreshCycle = 3 * time.Second
+// KeyStore is the interface which abstracts all needed operations required
+type KeyStore interface {
+ // Wallets implements accounts.Backend, returning all single-key wallets from the KeyStore.
+ Wallets() []accounts.Wallet
-// KeyStore manages a key storage directory on disk.
-type KeyStore struct {
- storage keyStore // Storage backend, might be cleartext or encrypted
- cache *accountCache // In-memory account cache over the filesystem storage
- changes chan struct{} // Channel receiving change notifications from the cache
- unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
+ // Subscribe implements accounts.Backend, creating an async subscription to
+ // receive notifications on the addition or removal of KeyStore wallets.
+ Subscribe(sink chan<- accounts.WalletEvent) event.Subscription
- wallets []accounts.Wallet // Wallet wrappers around the individual key files
- updateFeed event.Feed // Event feed to notify wallet additions/removals
- updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
- updating bool // Whether the event notification loop is running
+ // HasAddress reports whether a key with the given address is present.
+ HasAddress(addr common.Address) bool
- mu sync.RWMutex
+ // Accounts returns all key files present in the KeyStore.
+ Accounts() []accounts.Account
+
+ // Delete deletes the key matched by account if the passphrase is correct.
+ // If the account contains no filename, the address must match a unique key.
+ Delete(a accounts.Account, passphrase string) error
+
+ // SignHash calculates an ECDSA signature for the given hash. The produced
+ // signature is in the [R || S || V] format where V is 0 or 1.
+ SignHash(a accounts.Account, hash []byte) ([]byte, error)
+
+ // SignTx signs the given transaction with the requested account.
+ SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
+
+ // SignHashWithPassphrase signs hash if the private key matching the given address
+ // can be decrypted with the given passphrase. The produced signature is in the
+ // [R || S || V] format where V is 0 or 1.
+ SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error)
+
+ // SignTxWithPassphrase signs the transaction if the private key matching the
+ // given address can be decrypted with the given passphrase.
+ SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
+
+ // Unlock unlocks the given account indefinitely.
+ Unlock(a accounts.Account, passphrase string) error
+
+ // Lock removes the private key with the given address from memory.
+ Lock(addr common.Address) error
+
+ // TimedUnlock unlocks the given account with the passphrase. The account
+ // stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
+ // until the program exits. The account must match a unique key file.
+ //
+ // If the account address is already unlocked for a duration, TimedUnlock extends or
+ // shortens the active unlock timeout. If the address was previously unlocked
+ // indefinitely the timeout is not altered.
+ TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error
+
+ // Find resolves the given account into a unique entry in the KeyStore.
+ Find(a accounts.Account) (accounts.Account, error)
+
+ // NewAccount generates a new key and stores it into the KeyStore,
+ // encrypting it with the passphrase.
+ NewAccount(passphrase string) (accounts.Account, error)
+
+ // Export exports as a JSON key, encrypted with newPassphrase.
+ Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error)
+
+ // Import stores the given encrypted JSON key into the KeyStore.
+ Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error)
+
+ // ImportECDSA stores the given key into the KeyStore, encrypting it with the passphrase.
+ ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error)
+
+ // Update changes the passphrase of an existing account.
+ Update(a accounts.Account, passphrase, newPassphrase string) error
+
+ // ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
+ // a key file in the KeyStore. The key file is encrypted with the same passphrase.
+ ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error)
}
type unlocked struct {
@@ -76,420 +102,18 @@ type unlocked struct {
}
// NewKeyStore creates a keystore for the given directory.
-func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
+func NewKeyStore(keydir string, scryptN, scryptP int) KeyStore {
keydir, _ = filepath.Abs(keydir)
- ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
+ ks := &keyStoreFS{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
ks.init(keydir)
return ks
}
// NewPlaintextKeyStore creates a keystore for the given directory.
// Deprecated: Use NewKeyStore.
-func NewPlaintextKeyStore(keydir string) *KeyStore {
+func NewPlaintextKeyStore(keydir string) KeyStore {
keydir, _ = filepath.Abs(keydir)
- ks := &KeyStore{storage: &keyStorePlain{keydir}}
+ ks := &keyStoreFS{storage: &keyStorePlain{keydir}}
ks.init(keydir)
return ks
}
-
-func (ks *KeyStore) init(keydir string) {
- // Lock the mutex since the account cache might call back with events
- ks.mu.Lock()
- defer ks.mu.Unlock()
-
- // Initialize the set of unlocked keys and the account cache
- ks.unlocked = make(map[common.Address]*unlocked)
- ks.cache, ks.changes = newAccountCache(keydir)
-
- // TODO: In order for this finalizer to work, there must be no references
- // to ks. addressCache doesn't keep a reference but unlocked keys do,
- // so the finalizer will not trigger until all timed unlocks have expired.
- runtime.SetFinalizer(ks, func(m *KeyStore) {
- m.cache.close()
- })
- // Create the initial list of wallets from the cache
- accs := ks.cache.accounts()
- ks.wallets = make([]accounts.Wallet, len(accs))
- for i := 0; i < len(accs); i++ {
- ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
- }
-}
-
-// Wallets implements accounts.Backend, returning all single-key wallets from the
-// keystore directory.
-func (ks *KeyStore) Wallets() []accounts.Wallet {
- // Make sure the list of wallets is in sync with the account cache
- ks.refreshWallets()
-
- ks.mu.RLock()
- defer ks.mu.RUnlock()
-
- cpy := make([]accounts.Wallet, len(ks.wallets))
- copy(cpy, ks.wallets)
- return cpy
-}
-
-// refreshWallets retrieves the current account list and based on that does any
-// necessary wallet refreshes.
-func (ks *KeyStore) refreshWallets() {
- // Retrieve the current list of accounts
- ks.mu.Lock()
- accs := ks.cache.accounts()
-
- // Transform the current list of wallets into the new one
- var (
- wallets = make([]accounts.Wallet, 0, len(accs))
- events []accounts.WalletEvent
- )
-
- for _, account := range accs {
- // Drop wallets while they were in front of the next account
- for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
- events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
- ks.wallets = ks.wallets[1:]
- }
- // If there are no more wallets or the account is before the next, wrap new wallet
- if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
- wallet := &keystoreWallet{account: account, keystore: ks}
-
- events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
- wallets = append(wallets, wallet)
- continue
- }
- // If the account is the same as the first wallet, keep it
- if ks.wallets[0].Accounts()[0] == account {
- wallets = append(wallets, ks.wallets[0])
- ks.wallets = ks.wallets[1:]
- continue
- }
- }
- // Drop any leftover wallets and set the new batch
- for _, wallet := range ks.wallets {
- events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
- }
- ks.wallets = wallets
- ks.mu.Unlock()
-
- // Fire all wallet events and return
- for _, event := range events {
- ks.updateFeed.Send(event)
- }
-}
-
-// Subscribe implements accounts.Backend, creating an async subscription to
-// receive notifications on the addition or removal of keystore wallets.
-func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
- // We need the mutex to reliably start/stop the update loop
- ks.mu.Lock()
- defer ks.mu.Unlock()
-
- // Subscribe the caller and track the subscriber count
- sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
-
- // Subscribers require an active notification loop, start it
- if !ks.updating {
- ks.updating = true
- go ks.updater()
- }
- return sub
-}
-
-// updater is responsible for maintaining an up-to-date list of wallets stored in
-// the keystore, and for firing wallet addition/removal events. It listens for
-// account change events from the underlying account cache, and also periodically
-// forces a manual refresh (only triggers for systems where the filesystem notifier
-// is not running).
-func (ks *KeyStore) updater() {
- for {
- // Wait for an account update or a refresh timeout
- select {
- case <-ks.changes:
- case <-time.After(walletRefreshCycle):
- }
- // Run the wallet refresher
- ks.refreshWallets()
-
- // If all our subscribers left, stop the updater
- ks.mu.Lock()
- if ks.updateScope.Count() == 0 {
- ks.updating = false
- ks.mu.Unlock()
- return
- }
- ks.mu.Unlock()
- }
-}
-
-// HasAddress reports whether a key with the given address is present.
-func (ks *KeyStore) HasAddress(addr common.Address) bool {
- return ks.cache.hasAddress(addr)
-}
-
-// Accounts returns all key files present in the directory.
-func (ks *KeyStore) Accounts() []accounts.Account {
- return ks.cache.accounts()
-}
-
-// Delete deletes the key matched by account if the passphrase is correct.
-// If the account contains no filename, the address must match a unique key.
-func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
- // Decrypting the key isn't really necessary, but we do
- // it anyway to check the password and zero out the key
- // immediately afterwards.
- a, key, err := ks.getDecryptedKey(a, passphrase)
- if key != nil {
- zeroKey(key.PrivateKey)
- }
- if err != nil {
- return err
- }
- // The order is crucial here. The key is dropped from the
- // cache after the file is gone so that a reload happening in
- // between won't insert it into the cache again.
- err = os.Remove(a.URL.Path)
- if err == nil {
- ks.cache.delete(a)
- ks.refreshWallets()
- }
- return err
-}
-
-// SignHash calculates a ECDSA signature for the given hash. The produced
-// signature is in the [R || S || V] format where V is 0 or 1.
-func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
- // Look up the key to sign with and abort if it cannot be found
- ks.mu.RLock()
- defer ks.mu.RUnlock()
-
- unlockedKey, found := ks.unlocked[a.Address]
- if !found {
- return nil, ErrLocked
- }
- // Sign the hash using plain ECDSA operations
- return crypto.Sign(hash, unlockedKey.PrivateKey)
-}
-
-// SignTx signs the given transaction with the requested account.
-func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- // Look up the key to sign with and abort if it cannot be found
- ks.mu.RLock()
- defer ks.mu.RUnlock()
-
- unlockedKey, found := ks.unlocked[a.Address]
- if !found {
- return nil, ErrLocked
- }
- // Depending on the presence of the chain ID, sign with EIP155 or homestead
- if chainID != nil {
- return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
- }
- return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)
-}
-
-// SignHashWithPassphrase signs hash if the private key matching the given address
-// can be decrypted with the given passphrase. The produced signature is in the
-// [R || S || V] format where V is 0 or 1.
-func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
- _, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return nil, err
- }
- defer zeroKey(key.PrivateKey)
- return crypto.Sign(hash, key.PrivateKey)
-}
-
-// SignTxWithPassphrase signs the transaction if the private key matching the
-// given address can be decrypted with the given passphrase.
-func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- _, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return nil, err
- }
- defer zeroKey(key.PrivateKey)
-
- // Depending on the presence of the chain ID, sign with EIP155 or homestead
- if chainID != nil {
- return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey)
- }
- return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey)
-}
-
-// Unlock unlocks the given account indefinitely.
-func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
- return ks.TimedUnlock(a, passphrase, 0)
-}
-
-// Lock removes the private key with the given address from memory.
-func (ks *KeyStore) Lock(addr common.Address) error {
- ks.mu.Lock()
- if unl, found := ks.unlocked[addr]; found {
- ks.mu.Unlock()
- ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
- } else {
- ks.mu.Unlock()
- }
- return nil
-}
-
-// TimedUnlock unlocks the given account with the passphrase. The account
-// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
-// until the program exits. The account must match a unique key file.
-//
-// If the account address is already unlocked for a duration, TimedUnlock extends or
-// shortens the active unlock timeout. If the address was previously unlocked
-// indefinitely the timeout is not altered.
-func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
- a, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return err
- }
-
- ks.mu.Lock()
- defer ks.mu.Unlock()
- u, found := ks.unlocked[a.Address]
- if found {
- if u.abort == nil {
- // The address was unlocked indefinitely, so unlocking
- // it with a timeout would be confusing.
- zeroKey(key.PrivateKey)
- return nil
- }
- // Terminate the expire goroutine and replace it below.
- close(u.abort)
- }
- if timeout > 0 {
- u = &unlocked{Key: key, abort: make(chan struct{})}
- go ks.expire(a.Address, u, timeout)
- } else {
- u = &unlocked{Key: key}
- }
- ks.unlocked[a.Address] = u
- return nil
-}
-
-// Find resolves the given account into a unique entry in the keystore.
-func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) {
- ks.cache.maybeReload()
- ks.cache.mu.Lock()
- a, err := ks.cache.find(a)
- ks.cache.mu.Unlock()
- return a, err
-}
-
-func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
- a, err := ks.Find(a)
- if err != nil {
- return a, nil, err
- }
- key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
- return a, key, err
-}
-
-func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
- t := time.NewTimer(timeout)
- defer t.Stop()
- select {
- case <-u.abort:
- // just quit
- case <-t.C:
- ks.mu.Lock()
- // only drop if it's still the same key instance that dropLater
- // was launched with. we can check that using pointer equality
- // because the map stores a new pointer every time the key is
- // unlocked.
- if ks.unlocked[addr] == u {
- zeroKey(u.PrivateKey)
- delete(ks.unlocked, addr)
- }
- ks.mu.Unlock()
- }
-}
-
-// NewAccount generates a new key and stores it into the key directory,
-// encrypting it with the passphrase.
-func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
- _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
- if err != nil {
- return accounts.Account{}, err
- }
- // Add the account to the cache immediately rather
- // than waiting for file system notifications to pick it up.
- ks.cache.add(account)
- ks.refreshWallets()
- return account, nil
-}
-
-// Export exports as a JSON key, encrypted with newPassphrase.
-func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
- _, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return nil, err
- }
- var N, P int
- if store, ok := ks.storage.(*keyStorePassphrase); ok {
- N, P = store.scryptN, store.scryptP
- } else {
- N, P = StandardScryptN, StandardScryptP
- }
- return EncryptKey(key, newPassphrase, N, P)
-}
-
-// Import stores the given encrypted JSON key into the key directory.
-func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
- key, err := DecryptKey(keyJSON, passphrase)
- if key != nil && key.PrivateKey != nil {
- defer zeroKey(key.PrivateKey)
- }
- if err != nil {
- return accounts.Account{}, err
- }
- return ks.importKey(key, newPassphrase)
-}
-
-// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
-func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
- key := newKeyFromECDSA(priv)
- if ks.cache.hasAddress(key.Address) {
- return accounts.Account{}, fmt.Errorf("account already exists")
- }
- return ks.importKey(key, passphrase)
-}
-
-func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
- a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
- if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
- return accounts.Account{}, err
- }
- ks.cache.add(a)
- ks.refreshWallets()
- return a, nil
-}
-
-// Update changes the passphrase of an existing account.
-func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error {
- a, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return err
- }
- return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
-}
-
-// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
-// a key file in the key directory. The key file is encrypted with the same passphrase.
-func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
- a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
- if err != nil {
- return a, err
- }
- ks.cache.add(a)
- ks.refreshWallets()
- return a, nil
-}
-
-// zeroKey zeroes a private key in memory.
-func zeroKey(k *ecdsa.PrivateKey) {
- b := k.D.Bits()
- for i := range b {
- b[i] = 0
- }
-}
diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go
index 498067d497..9e32ca6282 100644
--- a/accounts/keystore/wallet.go
+++ b/accounts/keystore/wallet.go
@@ -29,7 +29,7 @@ import (
// keystore.
type keystoreWallet struct {
account accounts.Account // Single account contained in this wallet
- keystore *KeyStore // Keystore where the account originates from
+ keystore *keyStoreFS // Keystore where the account originates from
}
// URL implements accounts.Wallet, returning the URL of the account within.
diff --git a/accounts/manager.go b/accounts/manager.go
index 731d12ea30..e235cb5b13 100644
--- a/accounts/manager.go
+++ b/accounts/manager.go
@@ -132,8 +132,12 @@ func (am *Manager) update() {
}
// Backends retrieves the backend(s) with the given type from the account manager.
-func (am *Manager) Backends(kind reflect.Type) []Backend {
- return am.backends[kind]
+func (am *Manager) Backends(kinds ...reflect.Type) []Backend {
+ backends := make([]Backend, 0)
+ for _, kind := range kinds {
+ backends = append(backends, am.backends[kind]...)
+ }
+ return backends
}
// Wallets returns all signer accounts registered under this account manager.
diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go
index 77938efabd..ec98fe6cd6 100644
--- a/cmd/faucet/faucet.go
+++ b/cmd/faucet/faucet.go
@@ -202,12 +202,12 @@ type faucet struct {
client *ethclient.Client // Client connection to the Ethereum chain
index []byte // Index page to serve up on the web
- keystore *keystore.KeyStore // Keystore containing the single signer
- account accounts.Account // Account funding user faucet requests
- head *types.Header // Current head header of the faucet
- balance *big.Int // Current balance of the faucet
- nonce uint64 // Current pending nonce of the faucet
- price *big.Int // Current gas price to issue funds with
+ keystore keystore.KeyStore // Keystore containing the single signer
+ account accounts.Account // Account funding user faucet requests
+ head *types.Header // Current head header of the faucet
+ balance *big.Int // Current balance of the faucet
+ nonce uint64 // Current pending nonce of the faucet
+ price *big.Int // Current gas price to issue funds with
conns []*websocket.Conn // Currently live websocket connections
timeouts map[string]time.Time // History of users and their funding timeouts
@@ -217,7 +217,7 @@ type faucet struct {
lock sync.RWMutex // Lock protecting the faucet's internals
}
-func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
+func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks keystore.KeyStore, index []byte) (*faucet, error) {
// Assemble the raw devp2p protocol stack
stack, err := node.New(&node.Config{
Name: "geth",
diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go
index bf0e583187..b81d6201db 100644
--- a/cmd/geth/accountcmd.go
+++ b/cmd/geth/accountcmd.go
@@ -205,7 +205,7 @@ func accountList(ctx *cli.Context) error {
}
// tries unlocking the specified account a few times.
-func unlockAccount(ks *keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) {
+func unlockAccount(ks keystore.KeyStore, address string, i int, passwords []string) (accounts.Account, string) {
account, err := utils.MakeAddress(ks, address)
if err != nil {
utils.Fatalf("Could not list accounts: %v", err)
@@ -263,7 +263,7 @@ func getPassPhrase(prompt string, confirmation bool, i int, passwords []string)
return password
}
-func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account {
+func ambiguousAddrRecovery(ks keystore.KeyStore, err *keystore.AmbiguousAddrError, auth string) accounts.Account {
fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
for _, a := range err.Matches {
fmt.Println(" ", a.URL)
@@ -329,7 +329,7 @@ func accountUpdate(ctx *cli.Context) error {
utils.Fatalf("No accounts specified to update")
}
stack, _ := makeConfigNode(ctx)
- ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ ks := stack.AccountManager().Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
for _, addr := range ctx.Args() {
account, oldPassword := unlockAccount(ks, addr, 0, nil)
@@ -354,7 +354,7 @@ func importWallet(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
- ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ ks := stack.AccountManager().Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
acct, err := ks.ImportPreSaleKey(keyJSON, passphrase)
if err != nil {
utils.Fatalf("%v", err)
@@ -375,7 +375,7 @@ func accountImport(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
- ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ ks := stack.AccountManager().Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
acct, err := ks.ImportECDSA(key, passphrase)
if err != nil {
utils.Fatalf("Could not create the account: %v", err)
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index d6ded24b31..05a0a11186 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -452,7 +452,7 @@ func unlockAccounts(ctx *cli.Context, stack *node.Node) {
if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() {
utils.Fatalf("Account unlock with HTTP access is forbidden!")
}
- ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+ ks := stack.AccountManager().Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
passwords := utils.MakePasswordList(ctx)
for i, account := range unlocks {
unlockAccount(ks, account, i, passwords)
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index d019c1fdcc..0eeedcda84 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1023,7 +1023,7 @@ func makeDatabaseHandles() int {
// MakeAddress converts an account specified directly as a hex encoded string or
// a key index in the key store to an internal account representation.
-func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error) {
+func MakeAddress(ks keystore.KeyStore, account string) (accounts.Account, error) {
// If the specified account is a valid address, return it
if common.IsHexAddress(account) {
return accounts.Account{Address: common.HexToAddress(account)}, nil
@@ -1048,7 +1048,7 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
// setEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed.
-func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
+func setEtherbase(ctx *cli.Context, ks keystore.KeyStore, cfg *eth.Config) {
// Extract the current etherbase, new flag overriding legacy one
var etherbase string
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) {
@@ -1418,9 +1418,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
CheckExclusive(ctx, LightLegacyServFlag, LightServeFlag, SyncModeFlag, "light")
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
- var ks *keystore.KeyStore
- if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
- ks = keystores[0].(*keystore.KeyStore)
+ var ks keystore.KeyStore
+ if keystores := stack.AccountManager().Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType); len(keystores) > 0 {
+ ks = keystores[0].(keystore.KeyStore)
}
setEtherbase(ctx, ks, cfg)
setGPO(ctx, &cfg.GPO)
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 37433f335a..5dabdc1a65 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -291,8 +291,8 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error)
}
// fetchKeystore retrives the encrypted keystore from the account manager.
-func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
- return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+func fetchKeystore(am *accounts.Manager) keystore.KeyStore {
+ return am.Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
}
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
diff --git a/mobile/accounts.go b/mobile/accounts.go
index 4d979bffff..1ede3832ed 100644
--- a/mobile/accounts.go
+++ b/mobile/accounts.go
@@ -86,7 +86,7 @@ func (a *Account) GetURL() string {
}
// KeyStore manages a key storage directory on disk.
-type KeyStore struct{ keystore *keystore.KeyStore }
+type KeyStore struct{ keystore keystore.KeyStore }
// NewKeyStore creates a keystore for the given directory.
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
diff --git a/signer/core/api.go b/signer/core/api.go
index c10bf578d2..33bbd4b615 100644
--- a/signer/core/api.go
+++ b/signer/core/api.go
@@ -395,7 +395,7 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) {
// 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) (common.Address, error) {
- be := api.am.Backends(keystore.KeyStoreType)
+ be := api.am.Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)
if len(be) == 0 {
return common.Address{}, errors.New("password based accounts not supported")
}
@@ -419,7 +419,7 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) {
api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
} else {
// No error
- acc, err := be[0].(*keystore.KeyStore).NewAccount(resp.Text)
+ acc, err := be[0].(keystore.KeyStore).NewAccount(resp.Text)
log.Info("Your new key was generated", "address", acc.Address)
log.Warn("Please backup your key file!", "path", acc.URL.Path)
log.Warn("Please remember your password!")
diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go
index 36735d37ee..4699e4f3f0 100644
--- a/signer/core/uiapi.go
+++ b/signer/core/uiapi.go
@@ -110,8 +110,8 @@ func (s *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (account
}
// fetchKeystore retrives the encrypted keystore from the account manager.
-func fetchKeystore(am *accounts.Manager) *keystore.KeyStore {
- return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
+func fetchKeystore(am *accounts.Manager) keystore.KeyStore {
+ return am.Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
}
// ImportRawKey stores the given hex encoded ECDSA key into the key directory,
@@ -184,7 +184,7 @@ func (s *UIServerAPI) Export(ctx context.Context, addr common.Address) (json.Raw
// Example (the address in question has privkey `11...11`):
// {"jsonrpc":"2.0","method":"clef_import","params":[{"address":"19e7e376e7c213b7e7e7e46cc70a5dd086daff2a","crypto":{"cipher":"aes-128-ctr","ciphertext":"33e4cd3756091d037862bb7295e9552424a391a6e003272180a455ca2a9fb332","cipherparams":{"iv":"b54b263e8f89c42bb219b6279fba5cce"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"e4ca94644fd30569c1b1afbbc851729953c92637b7fe4bb9840bbb31ffbc64a5"},"mac":"f4092a445c2b21c0ef34f17c9cd0d873702b2869ec5df4439a0c2505823217e7"},"id":"216c7eac-e8c1-49af-a215-fa0036f29141","version":3},"test","yaddayadda"], "id":4}
func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, oldPassphrase, newPassphrase string) (accounts.Account, error) {
- be := api.am.Backends(keystore.KeyStoreType)
+ be := api.am.Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)
if len(be) == 0 {
return accounts.Account{}, errors.New("password based accounts not supported")
@@ -192,7 +192,7 @@ func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, old
if err := ValidatePasswordFormat(newPassphrase); err != nil {
return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err)
}
- return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase)
+ return be[0].(keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase)
}
// Other methods to be added, not yet implemented are: