accounts,cmd,internal,mobile,signer abstract KeyStore as an interface

This commit is contained in:
Huiyi Li 2020-02-09 19:47:47 -08:00
parent 6835af0aa0
commit cf875b7d69
16 changed files with 603 additions and 486 deletions

View file

@ -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 // NewKeyStoreTransactor is a utility method to easily create a transaction signer from
// an decrypted key from a keystore // 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{ return &TransactOpts{
From: account.Address, From: account.Address,
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {

View file

@ -97,7 +97,7 @@ func TestWatchNoDir(t *testing.T) {
// Create ks but not the directory that it watches. // Create ks but not the directory that it watches.
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 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() list := ks.Accounts()
if len(list) > 0 { 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 var list []accounts.Account
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
list = ks.Accounts() list = ks.Accounts()
@ -323,7 +323,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
// Create a temporary kesytore to test with // Create a temporary kesytore to test with
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int())) 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() list := ks.Accounts()
if len(list) > 0 { if len(list) > 0 {

View file

@ -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 {
}

View file

@ -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 <http://www.gnu.org/licenses/>.
// 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
}
}

View file

@ -20,6 +20,7 @@ import (
"io/ioutil" "io/ioutil"
"math/rand" "math/rand"
"os" "os"
"path/filepath"
"runtime" "runtime"
"sort" "sort"
"strings" "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") d, err := ioutil.TempDir("", "eth-keystore-test")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
newKs := NewPlaintextKeyStore newKs := newPlaintextKeyStore
if encrypted { 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) 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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
// 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 package keystore
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
crand "crypto/rand"
"errors" "errors"
"fmt"
"math/big" "math/big"
"os"
"path/filepath" "path/filepath"
"reflect"
"runtime"
"sync"
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
) )
@ -46,28 +19,81 @@ var (
ErrDecrypt = errors.New("could not decrypt key with given password") 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. // KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
const KeyStoreScheme = "keystore" const KeyStoreScheme = "keystore"
// Maximum time between wallet refreshes (if filesystem notifications don't work). // KeyStore is the interface which abstracts all needed operations required
const walletRefreshCycle = 3 * time.Second 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. // Subscribe implements accounts.Backend, creating an async subscription to
type KeyStore struct { // receive notifications on the addition or removal of KeyStore wallets.
storage keyStore // Storage backend, might be cleartext or encrypted Subscribe(sink chan<- accounts.WalletEvent) event.Subscription
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 // HasAddress reports whether a key with the given address is present.
updateFeed event.Feed // Event feed to notify wallet additions/removals HasAddress(addr common.Address) bool
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
updating bool // Whether the event notification loop is running
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 { type unlocked struct {
@ -76,420 +102,18 @@ type unlocked struct {
} }
// NewKeyStore creates a keystore for the given directory. // 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) keydir, _ = filepath.Abs(keydir)
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}} ks := &keyStoreFS{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
ks.init(keydir) ks.init(keydir)
return ks return ks
} }
// NewPlaintextKeyStore creates a keystore for the given directory. // NewPlaintextKeyStore creates a keystore for the given directory.
// Deprecated: Use NewKeyStore. // Deprecated: Use NewKeyStore.
func NewPlaintextKeyStore(keydir string) *KeyStore { func NewPlaintextKeyStore(keydir string) KeyStore {
keydir, _ = filepath.Abs(keydir) keydir, _ = filepath.Abs(keydir)
ks := &KeyStore{storage: &keyStorePlain{keydir}} ks := &keyStoreFS{storage: &keyStorePlain{keydir}}
ks.init(keydir) ks.init(keydir)
return ks 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
}
}

View file

@ -29,7 +29,7 @@ import (
// keystore. // keystore.
type keystoreWallet struct { type keystoreWallet struct {
account accounts.Account // Single account contained in this wallet 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. // URL implements accounts.Wallet, returning the URL of the account within.

View file

@ -132,8 +132,12 @@ func (am *Manager) update() {
} }
// Backends retrieves the backend(s) with the given type from the account manager. // Backends retrieves the backend(s) with the given type from the account manager.
func (am *Manager) Backends(kind reflect.Type) []Backend { func (am *Manager) Backends(kinds ...reflect.Type) []Backend {
return am.backends[kind] 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. // Wallets returns all signer accounts registered under this account manager.

View file

@ -202,12 +202,12 @@ type faucet struct {
client *ethclient.Client // Client connection to the Ethereum chain client *ethclient.Client // Client connection to the Ethereum chain
index []byte // Index page to serve up on the web index []byte // Index page to serve up on the web
keystore *keystore.KeyStore // Keystore containing the single signer keystore keystore.KeyStore // Keystore containing the single signer
account accounts.Account // Account funding user faucet requests account accounts.Account // Account funding user faucet requests
head *types.Header // Current head header of the faucet head *types.Header // Current head header of the faucet
balance *big.Int // Current balance of the faucet balance *big.Int // Current balance of the faucet
nonce uint64 // Current pending nonce of the faucet nonce uint64 // Current pending nonce of the faucet
price *big.Int // Current gas price to issue funds with price *big.Int // Current gas price to issue funds with
conns []*websocket.Conn // Currently live websocket connections conns []*websocket.Conn // Currently live websocket connections
timeouts map[string]time.Time // History of users and their funding timeouts 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 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 // Assemble the raw devp2p protocol stack
stack, err := node.New(&node.Config{ stack, err := node.New(&node.Config{
Name: "geth", Name: "geth",

View file

@ -205,7 +205,7 @@ func accountList(ctx *cli.Context) error {
} }
// tries unlocking the specified account a few times. // 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) account, err := utils.MakeAddress(ks, address)
if err != nil { if err != nil {
utils.Fatalf("Could not list accounts: %v", err) utils.Fatalf("Could not list accounts: %v", err)
@ -263,7 +263,7 @@ func getPassPhrase(prompt string, confirmation bool, i int, passwords []string)
return password 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) fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
for _, a := range err.Matches { for _, a := range err.Matches {
fmt.Println(" ", a.URL) fmt.Println(" ", a.URL)
@ -329,7 +329,7 @@ func accountUpdate(ctx *cli.Context) error {
utils.Fatalf("No accounts specified to update") utils.Fatalf("No accounts specified to update")
} }
stack, _ := makeConfigNode(ctx) 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() { for _, addr := range ctx.Args() {
account, oldPassword := unlockAccount(ks, addr, 0, nil) account, oldPassword := unlockAccount(ks, addr, 0, nil)
@ -354,7 +354,7 @@ func importWallet(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx) stack, _ := makeConfigNode(ctx)
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(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) acct, err := ks.ImportPreSaleKey(keyJSON, passphrase)
if err != nil { if err != nil {
utils.Fatalf("%v", err) utils.Fatalf("%v", err)
@ -375,7 +375,7 @@ func accountImport(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx) 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)) 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) acct, err := ks.ImportECDSA(key, passphrase)
if err != nil { if err != nil {
utils.Fatalf("Could not create the account: %v", err) utils.Fatalf("Could not create the account: %v", err)

View file

@ -452,7 +452,7 @@ func unlockAccounts(ctx *cli.Context, stack *node.Node) {
if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() { if !stack.Config().InsecureUnlockAllowed && stack.Config().ExtRPCEnabled() {
utils.Fatalf("Account unlock with HTTP access is forbidden!") 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) passwords := utils.MakePasswordList(ctx)
for i, account := range unlocks { for i, account := range unlocks {
unlockAccount(ks, account, i, passwords) unlockAccount(ks, account, i, passwords)

View file

@ -1023,7 +1023,7 @@ func makeDatabaseHandles() int {
// MakeAddress converts an account specified directly as a hex encoded string or // MakeAddress converts an account specified directly as a hex encoded string or
// a key index in the key store to an internal account representation. // 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 the specified account is a valid address, return it
if common.IsHexAddress(account) { if common.IsHexAddress(account) {
return accounts.Account{Address: common.HexToAddress(account)}, nil 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 // setEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed. // 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 // Extract the current etherbase, new flag overriding legacy one
var etherbase string var etherbase string
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) { 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, LightLegacyServFlag, LightServeFlag, SyncModeFlag, "light")
CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
var ks *keystore.KeyStore var ks keystore.KeyStore
if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 { if keystores := stack.AccountManager().Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType); len(keystores) > 0 {
ks = keystores[0].(*keystore.KeyStore) ks = keystores[0].(keystore.KeyStore)
} }
setEtherbase(ctx, ks, cfg) setEtherbase(ctx, ks, cfg)
setGPO(ctx, &cfg.GPO) setGPO(ctx, &cfg.GPO)

View file

@ -291,8 +291,8 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error)
} }
// fetchKeystore retrives the encrypted keystore from the account manager. // fetchKeystore retrives the encrypted keystore from the account manager.
func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { func fetchKeystore(am *accounts.Manager) keystore.KeyStore {
return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) return am.Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
} }
// ImportRawKey stores the given hex encoded ECDSA key into the key directory, // ImportRawKey stores the given hex encoded ECDSA key into the key directory,

View file

@ -86,7 +86,7 @@ func (a *Account) GetURL() string {
} }
// KeyStore manages a key storage directory on disk. // 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. // NewKeyStore creates a keystore for the given directory.
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {

View file

@ -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 // 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. // in the keystore location thas was specified when this API was created.
func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { 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 { if len(be) == 0 {
return common.Address{}, errors.New("password based accounts not supported") 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)) api.UI.ShowError(fmt.Sprintf("Account creation attempt #%d failed due to password requirements: %v", (i + 1), pwErr))
} else { } else {
// No error // 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.Info("Your new key was generated", "address", acc.Address)
log.Warn("Please backup your key file!", "path", acc.URL.Path) log.Warn("Please backup your key file!", "path", acc.URL.Path)
log.Warn("Please remember your password!") log.Warn("Please remember your password!")

View file

@ -110,8 +110,8 @@ func (s *UIServerAPI) DeriveAccount(url string, path string, pin *bool) (account
} }
// fetchKeystore retrives the encrypted keystore from the account manager. // fetchKeystore retrives the encrypted keystore from the account manager.
func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { func fetchKeystore(am *accounts.Manager) keystore.KeyStore {
return am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) return am.Backends(keystore.FSKeyStoreType, keystore.DBKeyStoreType)[0].(keystore.KeyStore)
} }
// ImportRawKey stores the given hex encoded ECDSA key into the key directory, // 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`): // 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} // {"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) { 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 { if len(be) == 0 {
return accounts.Account{}, errors.New("password based accounts not supported") 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 { if err := ValidatePasswordFormat(newPassphrase); err != nil {
return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err) 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: // Other methods to be added, not yet implemented are: