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: