diff options
author | Péter Szilágyi <peterke@gmail.com> | 2017-01-24 17:49:20 +0800 |
---|---|---|
committer | Péter Szilágyi <peterke@gmail.com> | 2017-02-13 20:00:02 +0800 |
commit | 833e4d1319336fbb66fd8f1e473c24472d65b17b (patch) | |
tree | 74d45d86d0c59c19f0ff67831f04e0cad9776eef /accounts | |
parent | 564b60520c68a1f06171abd705c01946b932492f (diff) | |
download | go-tangerine-833e4d1319336fbb66fd8f1e473c24472d65b17b.tar.gz go-tangerine-833e4d1319336fbb66fd8f1e473c24472d65b17b.tar.zst go-tangerine-833e4d1319336fbb66fd8f1e473c24472d65b17b.zip |
accounts, cmd, eth, internal, mobile, node: split account backends
Diffstat (limited to 'accounts')
-rw-r--r-- | accounts/abi/bind/auth.go | 4 | ||||
-rw-r--r-- | accounts/account_manager.go | 350 | ||||
-rw-r--r-- | accounts/accounts.go | 179 | ||||
-rw-r--r-- | accounts/backend.go | 88 | ||||
-rw-r--r-- | accounts/errors.go | 41 | ||||
-rw-r--r-- | accounts/keystore/address_cache.go (renamed from accounts/addrcache.go) | 71 | ||||
-rw-r--r-- | accounts/keystore/address_cache_test.go (renamed from accounts/addrcache_test.go) | 107 | ||||
-rw-r--r-- | accounts/keystore/key.go (renamed from accounts/key.go) | 11 | ||||
-rw-r--r-- | accounts/keystore/keystore.go | 362 | ||||
-rw-r--r-- | accounts/keystore/keystore_passphrase.go (renamed from accounts/key_store_passphrase.go) | 2 | ||||
-rw-r--r-- | accounts/keystore/keystore_passphrase_test.go (renamed from accounts/key_store_passphrase_test.go) | 2 | ||||
-rw-r--r-- | accounts/keystore/keystore_plain.go (renamed from accounts/key_store_plain.go) | 2 | ||||
-rw-r--r-- | accounts/keystore/keystore_plain_test.go (renamed from accounts/key_store_test.go) | 30 | ||||
-rw-r--r-- | accounts/keystore/keystore_test.go (renamed from accounts/accounts_test.go) | 103 | ||||
-rw-r--r-- | accounts/keystore/presale.go (renamed from accounts/presale.go) | 11 | ||||
-rw-r--r-- | accounts/keystore/testdata/dupes/1 (renamed from accounts/testdata/dupes/1) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/dupes/2 (renamed from accounts/testdata/dupes/2) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/dupes/foo (renamed from accounts/testdata/dupes/foo) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/.hiddenfile (renamed from accounts/testdata/keystore/.hiddenfile) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/README (renamed from accounts/testdata/keystore/README) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 (renamed from accounts/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/aaa (renamed from accounts/testdata/keystore/aaa) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/empty (renamed from accounts/testdata/keystore/empty) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e (renamed from accounts/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/garbage (renamed from accounts/testdata/keystore/garbage) | bin | 300 -> 300 bytes | |||
-rw-r--r-- | accounts/keystore/testdata/keystore/no-address (renamed from accounts/testdata/keystore/no-address) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/zero (renamed from accounts/testdata/keystore/zero) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/keystore/zzz (renamed from accounts/testdata/keystore/zzz) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e (renamed from accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/v1_test_vector.json (renamed from accounts/testdata/v1_test_vector.json) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/v3_test_vector.json (renamed from accounts/testdata/v3_test_vector.json) | 0 | ||||
-rw-r--r-- | accounts/keystore/testdata/very-light-scrypt.json (renamed from accounts/testdata/very-light-scrypt.json) | 0 | ||||
-rw-r--r-- | accounts/keystore/watch.go (renamed from accounts/watch.go) | 6 | ||||
-rw-r--r-- | accounts/keystore/watch_fallback.go (renamed from accounts/watch_fallback.go) | 8 |
34 files changed, 851 insertions, 526 deletions
diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index dbb235c14..e6bb0c3b5 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -22,7 +22,7 @@ import ( "io" "io/ioutil" - "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -35,7 +35,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { if err != nil { return nil, err } - key, err := accounts.DecryptKey(json, passphrase) + key, err := keystore.DecryptKey(json, passphrase) if err != nil { return nil, err } diff --git a/accounts/account_manager.go b/accounts/account_manager.go deleted file mode 100644 index 01dd62e25..000000000 --- a/accounts/account_manager.go +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2015 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 accounts 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 accounts - -import ( - "crypto/ecdsa" - crand "crypto/rand" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "runtime" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -var ( - ErrLocked = errors.New("account is locked") - ErrNoMatch = errors.New("no key for given address or file") - ErrDecrypt = errors.New("could not decrypt key with given passphrase") -) - -// Account represents a stored key. -// When used as an argument, it selects a unique key file to act on. -type Account struct { - Address common.Address // Ethereum account address derived from the key - - // File contains the key file name. - // When Acccount is used as an argument to select a key, File can be left blank to - // select just by address or set to the basename or absolute path of a file in the key - // directory. Accounts returned by Manager will always contain an absolute path. - File string -} - -func (acc *Account) MarshalJSON() ([]byte, error) { - return []byte(`"` + acc.Address.Hex() + `"`), nil -} - -func (acc *Account) UnmarshalJSON(raw []byte) error { - return json.Unmarshal(raw, &acc.Address) -} - -// Manager manages a key storage directory on disk. -type Manager struct { - cache *addrCache - keyStore keyStore - mu sync.RWMutex - unlocked map[common.Address]*unlocked -} - -type unlocked struct { - *Key - abort chan struct{} -} - -// NewManager creates a manager for the given directory. -func NewManager(keydir string, scryptN, scryptP int) *Manager { - keydir, _ = filepath.Abs(keydir) - am := &Manager{keyStore: &keyStorePassphrase{keydir, scryptN, scryptP}} - am.init(keydir) - return am -} - -// NewPlaintextManager creates a manager for the given directory. -// Deprecated: Use NewManager. -func NewPlaintextManager(keydir string) *Manager { - keydir, _ = filepath.Abs(keydir) - am := &Manager{keyStore: &keyStorePlain{keydir}} - am.init(keydir) - return am -} - -func (am *Manager) init(keydir string) { - am.unlocked = make(map[common.Address]*unlocked) - am.cache = newAddrCache(keydir) - // TODO: In order for this finalizer to work, there must be no references - // to am. addrCache doesn't keep a reference but unlocked keys do, - // so the finalizer will not trigger until all timed unlocks have expired. - runtime.SetFinalizer(am, func(m *Manager) { - m.cache.close() - }) -} - -// HasAddress reports whether a key with the given address is present. -func (am *Manager) HasAddress(addr common.Address) bool { - return am.cache.hasAddress(addr) -} - -// Accounts returns all key files present in the directory. -func (am *Manager) Accounts() []Account { - return am.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 (am *Manager) Delete(a 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 := am.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.File) - if err == nil { - am.cache.delete(a) - } - return err -} - -// Sign 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 (am *Manager) Sign(addr common.Address, hash []byte) ([]byte, error) { - am.mu.RLock() - defer am.mu.RUnlock() - - unlockedKey, found := am.unlocked[addr] - if !found { - return nil, ErrLocked - } - return crypto.Sign(hash, unlockedKey.PrivateKey) -} - -// SignWithPassphrase 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 (am *Manager) SignWithPassphrase(a Account, passphrase string, hash []byte) (signature []byte, err error) { - _, key, err := am.getDecryptedKey(a, passphrase) - if err != nil { - return nil, err - } - defer zeroKey(key.PrivateKey) - return crypto.Sign(hash, key.PrivateKey) -} - -// Unlock unlocks the given account indefinitely. -func (am *Manager) Unlock(a Account, passphrase string) error { - return am.TimedUnlock(a, passphrase, 0) -} - -// Lock removes the private key with the given address from memory. -func (am *Manager) Lock(addr common.Address) error { - am.mu.Lock() - if unl, found := am.unlocked[addr]; found { - am.mu.Unlock() - am.expire(addr, unl, time.Duration(0)*time.Nanosecond) - } else { - am.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 (am *Manager) TimedUnlock(a Account, passphrase string, timeout time.Duration) error { - a, key, err := am.getDecryptedKey(a, passphrase) - if err != nil { - return err - } - - am.mu.Lock() - defer am.mu.Unlock() - u, found := am.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 - } else { - // Terminate the expire goroutine and replace it below. - close(u.abort) - } - } - if timeout > 0 { - u = &unlocked{Key: key, abort: make(chan struct{})} - go am.expire(a.Address, u, timeout) - } else { - u = &unlocked{Key: key} - } - am.unlocked[a.Address] = u - return nil -} - -// Find resolves the given account into a unique entry in the keystore. -func (am *Manager) Find(a Account) (Account, error) { - am.cache.maybeReload() - am.cache.mu.Lock() - a, err := am.cache.find(a) - am.cache.mu.Unlock() - return a, err -} - -func (am *Manager) getDecryptedKey(a Account, auth string) (Account, *Key, error) { - a, err := am.Find(a) - if err != nil { - return a, nil, err - } - key, err := am.keyStore.GetKey(a.Address, a.File, auth) - return a, key, err -} - -func (am *Manager) 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: - am.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 am.unlocked[addr] == u { - zeroKey(u.PrivateKey) - delete(am.unlocked, addr) - } - am.mu.Unlock() - } -} - -// NewAccount generates a new key and stores it into the key directory, -// encrypting it with the passphrase. -func (am *Manager) NewAccount(passphrase string) (Account, error) { - _, account, err := storeNewKey(am.keyStore, crand.Reader, passphrase) - if err != nil { - return Account{}, err - } - // Add the account to the cache immediately rather - // than waiting for file system notifications to pick it up. - am.cache.add(account) - return account, nil -} - -// AccountByIndex returns the ith account. -func (am *Manager) AccountByIndex(i int) (Account, error) { - accounts := am.Accounts() - if i < 0 || i >= len(accounts) { - return Account{}, fmt.Errorf("account index %d out of range [0, %d]", i, len(accounts)-1) - } - return accounts[i], nil -} - -// Export exports as a JSON key, encrypted with newPassphrase. -func (am *Manager) Export(a Account, passphrase, newPassphrase string) (keyJSON []byte, err error) { - _, key, err := am.getDecryptedKey(a, passphrase) - if err != nil { - return nil, err - } - var N, P int - if store, ok := am.keyStore.(*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 (am *Manager) Import(keyJSON []byte, passphrase, newPassphrase string) (Account, error) { - key, err := DecryptKey(keyJSON, passphrase) - if key != nil && key.PrivateKey != nil { - defer zeroKey(key.PrivateKey) - } - if err != nil { - return Account{}, err - } - return am.importKey(key, newPassphrase) -} - -// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. -func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error) { - key := newKeyFromECDSA(priv) - if am.cache.hasAddress(key.Address) { - return Account{}, fmt.Errorf("account already exists") - } - - return am.importKey(key, passphrase) -} - -func (am *Manager) importKey(key *Key, passphrase string) (Account, error) { - a := Account{Address: key.Address, File: am.keyStore.JoinPath(keyFileName(key.Address))} - if err := am.keyStore.StoreKey(a.File, key, passphrase); err != nil { - return Account{}, err - } - am.cache.add(a) - return a, nil -} - -// Update changes the passphrase of an existing account. -func (am *Manager) Update(a Account, passphrase, newPassphrase string) error { - a, key, err := am.getDecryptedKey(a, passphrase) - if err != nil { - return err - } - return am.keyStore.StoreKey(a.File, 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 (am *Manager) ImportPreSaleKey(keyJSON []byte, passphrase string) (Account, error) { - a, _, err := importPreSaleKey(am.keyStore, keyJSON, passphrase) - if err != nil { - return a, err - } - am.cache.add(a) - 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/accounts.go b/accounts/accounts.go new file mode 100644 index 000000000..234b6e456 --- /dev/null +++ b/accounts/accounts.go @@ -0,0 +1,179 @@ +// 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 accounts implements high level Ethereum account management. +package accounts + +import ( + "encoding/json" + "errors" + "math/big" + "reflect" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// ErrUnknownAccount is returned for any requested operation for which no backend +// provides the specified account. +var ErrUnknownAccount = errors.New("unknown account") + +// ErrNotSupported is returned when an operation is requested from an account +// backend that it does not support. +var ErrNotSupported = errors.New("not supported") + +// Account represents a stored key. +// When used as an argument, it selects a unique key to act on. +type Account struct { + Address common.Address // Ethereum account address derived from the key + URL string // Optional resource locator within a backend + backend Backend // Backend where this account originates from +} + +func (acc *Account) MarshalJSON() ([]byte, error) { + return []byte(`"` + acc.Address.Hex() + `"`), nil +} + +func (acc *Account) UnmarshalJSON(raw []byte) error { + return json.Unmarshal(raw, &acc.Address) +} + +// Manager is an overarching account manager that can communicate with various +// backends for signing transactions. +type Manager struct { + backends []Backend // List of currently registered backends (ordered by registration) + index map[reflect.Type]Backend // Set of currently registered backends + lock sync.RWMutex +} + +// NewManager creates a generic account manager to sign transaction via various +// supported backends. +func NewManager(backends ...Backend) *Manager { + am := &Manager{ + backends: backends, + index: make(map[reflect.Type]Backend), + } + for _, backend := range backends { + am.index[reflect.TypeOf(backend)] = backend + } + return am +} + +// Backend retrieves the backend with the given type from the account manager. +func (am *Manager) Backend(backend reflect.Type) Backend { + return am.index[backend] +} + +// Accounts returns all signer accounts registered under this account manager. +func (am *Manager) Accounts() []Account { + am.lock.RLock() + defer am.lock.RUnlock() + + var all []Account + for _, backend := range am.backends { // TODO(karalabe): cache these after subscriptions are in + accounts := backend.Accounts() + for i := 0; i < len(accounts); i++ { + accounts[i].backend = backend + } + all = append(all, accounts...) + } + return all +} + +// HasAddress reports whether a key with the given address is present. +func (am *Manager) HasAddress(addr common.Address) bool { + am.lock.RLock() + defer am.lock.RUnlock() + + for _, backend := range am.backends { + if backend.HasAddress(addr) { + return true + } + } + return false +} + +// SignHash requests the account manager to get the hash signed with an arbitrary +// signing backend holding the authorization for the specified account. +func (am *Manager) SignHash(acc Account, hash []byte) ([]byte, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + if err := am.ensureBackend(&acc); err != nil { + return nil, err + } + return acc.backend.SignHash(acc, hash) +} + +// SignTx requests the account manager to get the transaction signed with an +// arbitrary signing backend holding the authorization for the specified account. +func (am *Manager) SignTx(acc Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + if err := am.ensureBackend(&acc); err != nil { + return nil, err + } + return acc.backend.SignTx(acc, tx, chainID) +} + +// SignHashWithPassphrase requests the account manager to get the hash signed with +// an arbitrary signing backend holding the authorization for the specified account. +func (am *Manager) SignHashWithPassphrase(acc Account, passphrase string, hash []byte) ([]byte, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + if err := am.ensureBackend(&acc); err != nil { + return nil, err + } + return acc.backend.SignHashWithPassphrase(acc, passphrase, hash) +} + +// SignTxWithPassphrase requests the account manager to get the transaction signed +// with an arbitrary signing backend holding the authorization for the specified +// account. +func (am *Manager) SignTxWithPassphrase(acc Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + am.lock.RLock() + defer am.lock.RUnlock() + + if err := am.ensureBackend(&acc); err != nil { + return nil, err + } + return acc.backend.SignTxWithPassphrase(acc, passphrase, tx, chainID) +} + +// ensureBackend ensures that the account has a correctly set backend and that +// it is still alive. +// +// Please note, this method assumes the manager lock is held! +func (am *Manager) ensureBackend(acc *Account) error { + // If we have a backend, make sure it's still live + if acc.backend != nil { + if _, exists := am.index[reflect.TypeOf(acc.backend)]; !exists { + return ErrUnknownAccount + } + return nil + } + // If we don't have a known backend, look up one that can service it + for _, backend := range am.backends { + if backend.HasAddress(acc.Address) { // TODO(karalabe): this assumes unique addresses per backend + acc.backend = backend + return nil + } + } + return ErrUnknownAccount +} diff --git a/accounts/backend.go b/accounts/backend.go new file mode 100644 index 000000000..5f7ac0717 --- /dev/null +++ b/accounts/backend.go @@ -0,0 +1,88 @@ +// 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 accounts + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Backend is an "account provider" that can specify a batch of accounts it can +// sign transactions with and upon request, do so. +type Backend interface { + // Accounts retrieves the list of signing accounts the backend is currently aware of. + Accounts() []Account + + // HasAddress reports whether an account with the given address is present. + HasAddress(addr common.Address) bool + + // SignHash requests the backend to sign the given hash. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the backend requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code o verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignHash(acc Account, hash []byte) ([]byte, error) + + // SignTx requests the backend to sign the given transaction. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + // + // If the backend requires additional authentication to sign the request (e.g. + // a password to decrypt the account, or a PIN code o verify the transaction), + // an AuthNeededError instance will be returned, containing infos for the user + // about which fields or actions are needed. The user may retry by providing + // the needed details via SignTxWithPassphrase, or by other means (e.g. unlock + // the account in a keystore). + SignTx(acc Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) + + // SignHashWithPassphrase requests the backend to sign the given transaction with + // the given passphrase as extra authentication information. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + SignHashWithPassphrase(acc Account, passphrase string, hash []byte) ([]byte, error) + + // SignTxWithPassphrase requests the backend to sign the given transaction, with the + // given passphrase as extra authentication information. + // + // It looks up the account specified either solely via its address contained within, + // or optionally with the aid of any location metadata from the embedded URL field. + SignTxWithPassphrase(acc Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) + + // TODO(karalabe,fjl): watching and caching needs the Go subscription system + // Watch requests the backend to send a notification to the specified channel whenever + // an new account appears or an existing one disappears. + //Watch(chan AccountEvent) error + + // Unwatch requests the backend stop sending notifications to the given channel. + //Unwatch(chan AccountEvent) error +} + +// TODO(karalabe,fjl): watching and caching needs the Go subscription system +// type AccountEvent struct { +// Account Account +// Added bool +// } diff --git a/accounts/errors.go b/accounts/errors.go new file mode 100644 index 000000000..d6f578b52 --- /dev/null +++ b/accounts/errors.go @@ -0,0 +1,41 @@ +// 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 accounts + +import "fmt" + +// AuthNeededError is returned by backends for signing requests where the user +// is required to provide further authentication before signing can succeed. +// +// This usually means either that a password needs to be supplied, or perhaps a +// one time PIN code displayed by some hardware device. +type AuthNeededError struct { + Needed string // Extra authentication the user needs to provide +} + +// NewAuthNeededError creates a new authentication error with the extra details +// about the needed fields set. +func NewAuthNeededError(needed string) error { + return &AuthNeededError{ + Needed: needed, + } +} + +// Error implements the standard error interfacel. +func (err *AuthNeededError) Error() string { + return fmt.Sprintf("authentication needed: %s", err.Needed) +} diff --git a/accounts/addrcache.go b/accounts/keystore/address_cache.go index a99f23606..eb3e3263b 100644 --- a/accounts/addrcache.go +++ b/accounts/keystore/address_cache.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "bufio" @@ -28,6 +28,7 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -38,23 +39,23 @@ import ( // exist yet, the code will attempt to create a watcher at most this often. const minReloadInterval = 2 * time.Second -type accountsByFile []Account +type accountsByFile []accounts.Account func (s accountsByFile) Len() int { return len(s) } -func (s accountsByFile) Less(i, j int) bool { return s[i].File < s[j].File } +func (s accountsByFile) Less(i, j int) bool { return s[i].URL < s[j].URL } func (s accountsByFile) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // AmbiguousAddrError is returned when attempting to unlock // an address for which more than one file exists. type AmbiguousAddrError struct { Addr common.Address - Matches []Account + Matches []accounts.Account } func (err *AmbiguousAddrError) Error() string { files := "" for i, a := range err.Matches { - files += a.File + files += a.URL if i < len(err.Matches)-1 { files += ", " } @@ -62,58 +63,58 @@ func (err *AmbiguousAddrError) Error() string { return fmt.Sprintf("multiple keys match address (%s)", files) } -// addrCache is a live index of all accounts in the keystore. -type addrCache struct { +// addressCache is a live index of all accounts in the keystore. +type addressCache struct { keydir string watcher *watcher mu sync.Mutex all accountsByFile - byAddr map[common.Address][]Account + byAddr map[common.Address][]accounts.Account throttle *time.Timer } -func newAddrCache(keydir string) *addrCache { - ac := &addrCache{ +func newAddrCache(keydir string) *addressCache { + ac := &addressCache{ keydir: keydir, - byAddr: make(map[common.Address][]Account), + byAddr: make(map[common.Address][]accounts.Account), } ac.watcher = newWatcher(ac) return ac } -func (ac *addrCache) accounts() []Account { +func (ac *addressCache) accounts() []accounts.Account { ac.maybeReload() ac.mu.Lock() defer ac.mu.Unlock() - cpy := make([]Account, len(ac.all)) + cpy := make([]accounts.Account, len(ac.all)) copy(cpy, ac.all) return cpy } -func (ac *addrCache) hasAddress(addr common.Address) bool { +func (ac *addressCache) hasAddress(addr common.Address) bool { ac.maybeReload() ac.mu.Lock() defer ac.mu.Unlock() return len(ac.byAddr[addr]) > 0 } -func (ac *addrCache) add(newAccount Account) { +func (ac *addressCache) add(newAccount accounts.Account) { ac.mu.Lock() defer ac.mu.Unlock() - i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].File >= newAccount.File }) + i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL >= newAccount.URL }) if i < len(ac.all) && ac.all[i] == newAccount { return } // newAccount is not in the cache. - ac.all = append(ac.all, Account{}) + ac.all = append(ac.all, accounts.Account{}) copy(ac.all[i+1:], ac.all[i:]) ac.all[i] = newAccount ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount) } // note: removed needs to be unique here (i.e. both File and Address must be set). -func (ac *addrCache) delete(removed Account) { +func (ac *addressCache) delete(removed accounts.Account) { ac.mu.Lock() defer ac.mu.Unlock() ac.all = removeAccount(ac.all, removed) @@ -124,7 +125,7 @@ func (ac *addrCache) delete(removed Account) { } } -func removeAccount(slice []Account, elem Account) []Account { +func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account { for i := range slice { if slice[i] == elem { return append(slice[:i], slice[i+1:]...) @@ -134,41 +135,41 @@ func removeAccount(slice []Account, elem Account) []Account { } // find returns the cached account for address if there is a unique match. -// The exact matching rules are explained by the documentation of Account. +// The exact matching rules are explained by the documentation of accounts.Account. // Callers must hold ac.mu. -func (ac *addrCache) find(a Account) (Account, error) { +func (ac *addressCache) find(a accounts.Account) (accounts.Account, error) { // Limit search to address candidates if possible. matches := ac.all if (a.Address != common.Address{}) { matches = ac.byAddr[a.Address] } - if a.File != "" { + if a.URL != "" { // If only the basename is specified, complete the path. - if !strings.ContainsRune(a.File, filepath.Separator) { - a.File = filepath.Join(ac.keydir, a.File) + if !strings.ContainsRune(a.URL, filepath.Separator) { + a.URL = filepath.Join(ac.keydir, a.URL) } for i := range matches { - if matches[i].File == a.File { + if matches[i].URL == a.URL { return matches[i], nil } } if (a.Address == common.Address{}) { - return Account{}, ErrNoMatch + return accounts.Account{}, ErrNoMatch } } switch len(matches) { case 1: return matches[0], nil case 0: - return Account{}, ErrNoMatch + return accounts.Account{}, ErrNoMatch default: - err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]Account, len(matches))} + err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} copy(err.Matches, matches) - return Account{}, err + return accounts.Account{}, err } } -func (ac *addrCache) maybeReload() { +func (ac *addressCache) maybeReload() { ac.mu.Lock() defer ac.mu.Unlock() if ac.watcher.running { @@ -188,7 +189,7 @@ func (ac *addrCache) maybeReload() { ac.throttle.Reset(minReloadInterval) } -func (ac *addrCache) close() { +func (ac *addressCache) close() { ac.mu.Lock() ac.watcher.close() if ac.throttle != nil { @@ -199,7 +200,7 @@ func (ac *addrCache) close() { // reload caches addresses of existing accounts. // Callers must hold ac.mu. -func (ac *addrCache) reload() { +func (ac *addressCache) reload() { accounts, err := ac.scan() if err != nil && glog.V(logger.Debug) { glog.Errorf("can't load keys: %v", err) @@ -215,7 +216,7 @@ func (ac *addrCache) reload() { glog.V(logger.Debug).Infof("reloaded keys, cache has %d accounts", len(ac.all)) } -func (ac *addrCache) scan() ([]Account, error) { +func (ac *addressCache) scan() ([]accounts.Account, error) { files, err := ioutil.ReadDir(ac.keydir) if err != nil { return nil, err @@ -223,7 +224,7 @@ func (ac *addrCache) scan() ([]Account, error) { var ( buf = new(bufio.Reader) - addrs []Account + addrs []accounts.Account keyJSON struct { Address string `json:"address"` } @@ -250,7 +251,7 @@ func (ac *addrCache) scan() ([]Account, error) { case (addr == common.Address{}): glog.V(logger.Debug).Infof("can't decode key %s: missing or zero address", path) default: - addrs = append(addrs, Account{Address: addr, File: path}) + addrs = append(addrs, accounts.Account{Address: addr, URL: path}) } fd.Close() } diff --git a/accounts/addrcache_test.go b/accounts/keystore/address_cache_test.go index e5f08cffc..68af74338 100644 --- a/accounts/addrcache_test.go +++ b/accounts/keystore/address_cache_test.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "fmt" @@ -28,23 +28,24 @@ import ( "github.com/cespare/cp" "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" ) var ( cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore")) - cachetestAccounts = []Account{ + cachetestAccounts = []accounts.Account{ { Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), - File: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), + URL: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), }, { Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), - File: filepath.Join(cachetestDir, "aaa"), + URL: filepath.Join(cachetestDir, "aaa"), }, { Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), - File: filepath.Join(cachetestDir, "zzz"), + URL: filepath.Join(cachetestDir, "zzz"), }, } ) @@ -52,7 +53,7 @@ var ( func TestWatchNewFile(t *testing.T) { t.Parallel() - dir, am := tmpManager(t, false) + dir, am := tmpKeyStore(t, false) defer os.RemoveAll(dir) // Ensure the watcher is started before adding any files. @@ -60,18 +61,18 @@ func TestWatchNewFile(t *testing.T) { time.Sleep(200 * time.Millisecond) // Move in the files. - wantAccounts := make([]Account, len(cachetestAccounts)) + wantAccounts := make([]accounts.Account, len(cachetestAccounts)) for i := range cachetestAccounts { a := cachetestAccounts[i] - a.File = filepath.Join(dir, filepath.Base(a.File)) + a.URL = filepath.Join(dir, filepath.Base(a.URL)) wantAccounts[i] = a - if err := cp.CopyFile(a.File, cachetestAccounts[i].File); err != nil { + if err := cp.CopyFile(a.URL, cachetestAccounts[i].URL); err != nil { t.Fatal(err) } } // am should see the accounts. - var list []Account + var list []accounts.Account for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 { list = am.Accounts() if reflect.DeepEqual(list, wantAccounts) { @@ -88,7 +89,7 @@ func TestWatchNoDir(t *testing.T) { // Create am 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())) - am := NewManager(dir, LightScryptN, LightScryptP) + am := NewKeyStore(dir, LightScryptN, LightScryptP) list := am.Accounts() if len(list) > 0 { @@ -100,13 +101,13 @@ func TestWatchNoDir(t *testing.T) { os.MkdirAll(dir, 0700) defer os.RemoveAll(dir) file := filepath.Join(dir, "aaa") - if err := cp.CopyFile(file, cachetestAccounts[0].File); err != nil { + if err := cp.CopyFile(file, cachetestAccounts[0].URL); err != nil { t.Fatal(err) } // am should see the account. - wantAccounts := []Account{cachetestAccounts[0]} - wantAccounts[0].File = file + wantAccounts := []accounts.Account{cachetestAccounts[0]} + wantAccounts[0].URL = file for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 { list = am.Accounts() if reflect.DeepEqual(list, wantAccounts) { @@ -129,52 +130,52 @@ func TestCacheAddDeleteOrder(t *testing.T) { cache := newAddrCache("testdata/no-such-dir") cache.watcher.running = true // prevent unexpected reloads - accounts := []Account{ + accs := []accounts.Account{ { Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), - File: "-309830980", + URL: "-309830980", }, { Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), - File: "ggg", + URL: "ggg", }, { Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"), - File: "zzzzzz-the-very-last-one.keyXXX", + URL: "zzzzzz-the-very-last-one.keyXXX", }, { Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), - File: "SOMETHING.key", + URL: "SOMETHING.key", }, { Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), - File: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8", + URL: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8", }, { Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), - File: "aaa", + URL: "aaa", }, { Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"), - File: "zzz", + URL: "zzz", }, } - for _, a := range accounts { + for _, a := range accs { cache.add(a) } // Add some of them twice to check that they don't get reinserted. - cache.add(accounts[0]) - cache.add(accounts[2]) + cache.add(accs[0]) + cache.add(accs[2]) // Check that the account list is sorted by filename. - wantAccounts := make([]Account, len(accounts)) - copy(wantAccounts, accounts) + wantAccounts := make([]accounts.Account, len(accs)) + copy(wantAccounts, accs) sort.Sort(accountsByFile(wantAccounts)) list := cache.accounts() if !reflect.DeepEqual(list, wantAccounts) { - t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accounts), spew.Sdump(wantAccounts)) + t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) } - for _, a := range accounts { + for _, a := range accs { if !cache.hasAddress(a.Address) { t.Errorf("expected hasAccount(%x) to return true", a.Address) } @@ -184,13 +185,13 @@ func TestCacheAddDeleteOrder(t *testing.T) { } // Delete a few keys from the cache. - for i := 0; i < len(accounts); i += 2 { + for i := 0; i < len(accs); i += 2 { cache.delete(wantAccounts[i]) } - cache.delete(Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), File: "something"}) + cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: "something"}) // Check content again after deletion. - wantAccountsAfterDelete := []Account{ + wantAccountsAfterDelete := []accounts.Account{ wantAccounts[1], wantAccounts[3], wantAccounts[5], @@ -214,60 +215,60 @@ func TestCacheFind(t *testing.T) { cache := newAddrCache(dir) cache.watcher.running = true // prevent unexpected reloads - accounts := []Account{ + accs := []accounts.Account{ { Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), - File: filepath.Join(dir, "a.key"), + URL: filepath.Join(dir, "a.key"), }, { Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"), - File: filepath.Join(dir, "b.key"), + URL: filepath.Join(dir, "b.key"), }, { Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), - File: filepath.Join(dir, "c.key"), + URL: filepath.Join(dir, "c.key"), }, { Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"), - File: filepath.Join(dir, "c2.key"), + URL: filepath.Join(dir, "c2.key"), }, } - for _, a := range accounts { + for _, a := range accs { cache.add(a) } - nomatchAccount := Account{ + nomatchAccount := accounts.Account{ Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"), - File: filepath.Join(dir, "something"), + URL: filepath.Join(dir, "something"), } tests := []struct { - Query Account - WantResult Account + Query accounts.Account + WantResult accounts.Account WantError error }{ // by address - {Query: Account{Address: accounts[0].Address}, WantResult: accounts[0]}, + {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]}, // by file - {Query: Account{File: accounts[0].File}, WantResult: accounts[0]}, + {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]}, // by basename - {Query: Account{File: filepath.Base(accounts[0].File)}, WantResult: accounts[0]}, + {Query: accounts.Account{URL: filepath.Base(accs[0].URL)}, WantResult: accs[0]}, // by file and address - {Query: accounts[0], WantResult: accounts[0]}, + {Query: accs[0], WantResult: accs[0]}, // ambiguous address, tie resolved by file - {Query: accounts[2], WantResult: accounts[2]}, + {Query: accs[2], WantResult: accs[2]}, // ambiguous address error { - Query: Account{Address: accounts[2].Address}, + Query: accounts.Account{Address: accs[2].Address}, WantError: &AmbiguousAddrError{ - Addr: accounts[2].Address, - Matches: []Account{accounts[2], accounts[3]}, + Addr: accs[2].Address, + Matches: []accounts.Account{accs[2], accs[3]}, }, }, // no match error {Query: nomatchAccount, WantError: ErrNoMatch}, - {Query: Account{File: nomatchAccount.File}, WantError: ErrNoMatch}, - {Query: Account{File: filepath.Base(nomatchAccount.File)}, WantError: ErrNoMatch}, - {Query: Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch}, + {Query: accounts.Account{URL: filepath.Base(nomatchAccount.URL)}, WantError: ErrNoMatch}, + {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch}, } for i, test := range tests { a, err := cache.find(test.Query) diff --git a/accounts/key.go b/accounts/keystore/key.go index dbcb49dcf..1cf5f9366 100644 --- a/accounts/key.go +++ b/accounts/keystore/key.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "bytes" @@ -29,6 +29,7 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" @@ -175,13 +176,13 @@ func newKey(rand io.Reader) (*Key, error) { return newKeyFromECDSA(privateKeyECDSA), nil } -func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, Account, error) { +func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { key, err := newKey(rand) if err != nil { - return nil, Account{}, err + return nil, accounts.Account{}, err } - a := Account{Address: key.Address, File: ks.JoinPath(keyFileName(key.Address))} - if err := ks.StoreKey(a.File, key, auth); err != nil { + a := accounts.Account{Address: key.Address, URL: ks.JoinPath(keyFileName(key.Address))} + if err := ks.StoreKey(a.URL, key, auth); err != nil { zeroKey(key.PrivateKey) return nil, a, err } diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go new file mode 100644 index 000000000..d125f7d62 --- /dev/null +++ b/accounts/keystore/keystore.go @@ -0,0 +1,362 @@ +// Copyright 2015 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" + "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" +) + +var ( + ErrNeedPasswordOrUnlock = accounts.NewAuthNeededError("password or unlock") + ErrNoMatch = errors.New("no key for given address or file") + ErrDecrypt = errors.New("could not decrypt key with given passphrase") +) + +// BackendType can be used to query the account manager for encrypted keystores. +var BackendType = reflect.TypeOf(new(KeyStore)) + +// KeyStore manages a key storage directory on disk. +type KeyStore struct { + cache *addressCache + keyStore keyStore + mu sync.RWMutex + unlocked map[common.Address]*unlocked +} + +type unlocked struct { + *Key + abort chan struct{} +} + +// NewKeyStore creates a keystore for the given directory. +func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{keyStore: &keyStorePassphrase{keydir, scryptN, scryptP}} + ks.init(keydir) + return ks +} + +// NewPlaintextKeyStore creates a keystore for the given directory. +// Deprecated: Use NewKeyStore. +func NewPlaintextKeyStore(keydir string) *KeyStore { + keydir, _ = filepath.Abs(keydir) + ks := &KeyStore{keyStore: &keyStorePlain{keydir}} + ks.init(keydir) + return ks +} + +func (ks *KeyStore) init(keydir string) { + ks.unlocked = make(map[common.Address]*unlocked) + ks.cache = newAddrCache(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() + }) +} + +// 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) + if err == nil { + ks.cache.delete(a) + } + 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, ErrNeedPasswordOrUnlock + } + // 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, ErrNeedPasswordOrUnlock + } + // 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 + } else { + // 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.keyStore.GetKey(a.Address, a.URL, 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.keyStore, 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) + 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.keyStore.(*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: ks.keyStore.JoinPath(keyFileName(key.Address))} + if err := ks.keyStore.StoreKey(a.URL, key, passphrase); err != nil { + return accounts.Account{}, err + } + ks.cache.add(a) + 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.keyStore.StoreKey(a.URL, 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.keyStore, keyJSON, passphrase) + if err != nil { + return a, err + } + ks.cache.add(a) + 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/key_store_passphrase.go b/accounts/keystore/keystore_passphrase.go index 4a777956d..8ef510fcf 100644 --- a/accounts/key_store_passphrase.go +++ b/accounts/keystore/keystore_passphrase.go @@ -23,7 +23,7 @@ The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-St */ -package accounts +package keystore import ( "bytes" diff --git a/accounts/key_store_passphrase_test.go b/accounts/keystore/keystore_passphrase_test.go index 217393fa5..086addbc1 100644 --- a/accounts/key_store_passphrase_test.go +++ b/accounts/keystore/keystore_passphrase_test.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "io/ioutil" diff --git a/accounts/key_store_plain.go b/accounts/keystore/keystore_plain.go index 2cbaa94df..b490ca72b 100644 --- a/accounts/key_store_plain.go +++ b/accounts/keystore/keystore_plain.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "encoding/json" diff --git a/accounts/key_store_test.go b/accounts/keystore/keystore_plain_test.go index d0713caa0..dcb2ab901 100644 --- a/accounts/key_store_test.go +++ b/accounts/keystore/keystore_plain_test.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "crypto/rand" @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -func tmpKeyStore(t *testing.T, encrypted bool) (dir string, ks keyStore) { +func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) { d, err := ioutil.TempDir("", "geth-keystore-test") if err != nil { t.Fatal(err) @@ -44,7 +44,7 @@ func tmpKeyStore(t *testing.T, encrypted bool) (dir string, ks keyStore) { } func TestKeyStorePlain(t *testing.T) { - dir, ks := tmpKeyStore(t, false) + dir, ks := tmpKeyStoreIface(t, false) defer os.RemoveAll(dir) pass := "" // not used but required by API @@ -52,7 +52,7 @@ func TestKeyStorePlain(t *testing.T) { if err != nil { t.Fatal(err) } - k2, err := ks.GetKey(k1.Address, account.File, pass) + k2, err := ks.GetKey(k1.Address, account.URL, pass) if err != nil { t.Fatal(err) } @@ -65,7 +65,7 @@ func TestKeyStorePlain(t *testing.T) { } func TestKeyStorePassphrase(t *testing.T) { - dir, ks := tmpKeyStore(t, true) + dir, ks := tmpKeyStoreIface(t, true) defer os.RemoveAll(dir) pass := "foo" @@ -73,7 +73,7 @@ func TestKeyStorePassphrase(t *testing.T) { if err != nil { t.Fatal(err) } - k2, err := ks.GetKey(k1.Address, account.File, pass) + k2, err := ks.GetKey(k1.Address, account.URL, pass) if err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func TestKeyStorePassphrase(t *testing.T) { } func TestKeyStorePassphraseDecryptionFail(t *testing.T) { - dir, ks := tmpKeyStore(t, true) + dir, ks := tmpKeyStoreIface(t, true) defer os.RemoveAll(dir) pass := "foo" @@ -94,13 +94,13 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) { if err != nil { t.Fatal(err) } - if _, err = ks.GetKey(k1.Address, account.File, "bar"); err != ErrDecrypt { + if _, err = ks.GetKey(k1.Address, account.URL, "bar"); err != ErrDecrypt { t.Fatalf("wrong error for invalid passphrase\ngot %q\nwant %q", err, ErrDecrypt) } } func TestImportPreSaleKey(t *testing.T) { - dir, ks := tmpKeyStore(t, true) + dir, ks := tmpKeyStoreIface(t, true) defer os.RemoveAll(dir) // file content of a presale key file generated with: @@ -115,8 +115,8 @@ func TestImportPreSaleKey(t *testing.T) { if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") { t.Errorf("imported account has wrong address %x", account.Address) } - if !strings.HasPrefix(account.File, dir) { - t.Errorf("imported account file not in keystore directory: %q", account.File) + if !strings.HasPrefix(account.URL, dir) { + t.Errorf("imported account file not in keystore directory: %q", account.URL) } } @@ -142,19 +142,19 @@ func TestV3_PBKDF2_1(t *testing.T) { func TestV3_PBKDF2_2(t *testing.T) { t.Parallel() - tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t) + tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t) testDecryptV3(tests["test1"], t) } func TestV3_PBKDF2_3(t *testing.T) { t.Parallel() - tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t) + tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t) testDecryptV3(tests["python_generated_test_with_odd_iv"], t) } func TestV3_PBKDF2_4(t *testing.T) { t.Parallel() - tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t) + tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t) testDecryptV3(tests["evilnonce"], t) } @@ -166,7 +166,7 @@ func TestV3_Scrypt_1(t *testing.T) { func TestV3_Scrypt_2(t *testing.T) { t.Parallel() - tests := loadKeyStoreTestV3("../tests/files/KeyStoreTests/basic_tests.json", t) + tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t) testDecryptV3(tests["test2"], t) } diff --git a/accounts/accounts_test.go b/accounts/keystore/keystore_test.go index b3ab87d50..af2140c31 100644 --- a/accounts/accounts_test.go +++ b/accounts/keystore/keystore_test.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "io/ioutil" @@ -24,183 +24,184 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" ) var testSigData = make([]byte, 32) -func TestManager(t *testing.T) { - dir, am := tmpManager(t, true) +func TestKeyStore(t *testing.T) { + dir, ks := tmpKeyStore(t, true) defer os.RemoveAll(dir) - a, err := am.NewAccount("foo") + a, err := ks.NewAccount("foo") if err != nil { t.Fatal(err) } - if !strings.HasPrefix(a.File, dir) { - t.Errorf("account file %s doesn't have dir prefix", a.File) + if !strings.HasPrefix(a.URL, dir) { + t.Errorf("account file %s doesn't have dir prefix", a.URL) } - stat, err := os.Stat(a.File) + stat, err := os.Stat(a.URL) if err != nil { - t.Fatalf("account file %s doesn't exist (%v)", a.File, err) + t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) } if runtime.GOOS != "windows" && stat.Mode() != 0600 { t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) } - if !am.HasAddress(a.Address) { + if !ks.HasAddress(a.Address) { t.Errorf("HasAccount(%x) should've returned true", a.Address) } - if err := am.Update(a, "foo", "bar"); err != nil { + if err := ks.Update(a, "foo", "bar"); err != nil { t.Errorf("Update error: %v", err) } - if err := am.Delete(a, "bar"); err != nil { + if err := ks.Delete(a, "bar"); err != nil { t.Errorf("Delete error: %v", err) } - if common.FileExist(a.File) { - t.Errorf("account file %s should be gone after Delete", a.File) + if common.FileExist(a.URL) { + t.Errorf("account file %s should be gone after Delete", a.URL) } - if am.HasAddress(a.Address) { + if ks.HasAddress(a.Address) { t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) } } func TestSign(t *testing.T) { - dir, am := tmpManager(t, true) + dir, ks := tmpKeyStore(t, true) defer os.RemoveAll(dir) pass := "" // not used but required by API - a1, err := am.NewAccount(pass) + a1, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) } - if err := am.Unlock(a1, ""); err != nil { + if err := ks.Unlock(a1, ""); err != nil { t.Fatal(err) } - if _, err := am.Sign(a1.Address, testSigData); err != nil { + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { t.Fatal(err) } } func TestSignWithPassphrase(t *testing.T) { - dir, am := tmpManager(t, true) + dir, ks := tmpKeyStore(t, true) defer os.RemoveAll(dir) pass := "passwd" - acc, err := am.NewAccount(pass) + acc, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) } - if _, unlocked := am.unlocked[acc.Address]; unlocked { + if _, unlocked := ks.unlocked[acc.Address]; unlocked { t.Fatal("expected account to be locked") } - _, err = am.SignWithPassphrase(acc, pass, testSigData) + _, err = ks.SignHashWithPassphrase(acc, pass, testSigData) if err != nil { t.Fatal(err) } - if _, unlocked := am.unlocked[acc.Address]; unlocked { + if _, unlocked := ks.unlocked[acc.Address]; unlocked { t.Fatal("expected account to be locked") } - if _, err = am.SignWithPassphrase(acc, "invalid passwd", testSigData); err == nil { - t.Fatal("expected SignHash to fail with invalid password") + if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { + t.Fatal("expected SignHashWithPassphrase to fail with invalid password") } } func TestTimedUnlock(t *testing.T) { - dir, am := tmpManager(t, true) + dir, ks := tmpKeyStore(t, true) defer os.RemoveAll(dir) pass := "foo" - a1, err := am.NewAccount(pass) + a1, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) } // Signing without passphrase fails because account is locked - _, err = am.Sign(a1.Address, testSigData) - if err != ErrLocked { - t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrNeedPasswordOrUnlock { + t.Fatal("Signing should've failed with ErrNeedPasswordOrUnlock before unlocking, got ", err) } // Signing with passphrase works - if err = am.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { t.Fatal(err) } // Signing without passphrase works because account is temp unlocked - _, err = am.Sign(a1.Address, testSigData) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) if err != nil { t.Fatal("Signing shouldn't return an error after unlocking, got ", err) } // Signing fails again after automatic locking time.Sleep(250 * time.Millisecond) - _, err = am.Sign(a1.Address, testSigData) - if err != ErrLocked { - t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrNeedPasswordOrUnlock { + t.Fatal("Signing should've failed with ErrNeedPasswordOrUnlock timeout expired, got ", err) } } func TestOverrideUnlock(t *testing.T) { - dir, am := tmpManager(t, false) + dir, ks := tmpKeyStore(t, false) defer os.RemoveAll(dir) pass := "foo" - a1, err := am.NewAccount(pass) + a1, err := ks.NewAccount(pass) if err != nil { t.Fatal(err) } // Unlock indefinitely. - if err = am.TimedUnlock(a1, pass, 5*time.Minute); err != nil { + if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { t.Fatal(err) } // Signing without passphrase works because account is temp unlocked - _, err = am.Sign(a1.Address, testSigData) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) if err != nil { t.Fatal("Signing shouldn't return an error after unlocking, got ", err) } // reset unlock to a shorter period, invalidates the previous unlock - if err = am.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { + if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { t.Fatal(err) } // Signing without passphrase still works because account is temp unlocked - _, err = am.Sign(a1.Address, testSigData) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) if err != nil { t.Fatal("Signing shouldn't return an error after unlocking, got ", err) } // Signing fails again after automatic locking time.Sleep(250 * time.Millisecond) - _, err = am.Sign(a1.Address, testSigData) - if err != ErrLocked { - t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) + _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) + if err != ErrNeedPasswordOrUnlock { + t.Fatal("Signing should've failed with ErrNeedPasswordOrUnlock timeout expired, got ", err) } } // This test should fail under -race if signing races the expiration goroutine. func TestSignRace(t *testing.T) { - dir, am := tmpManager(t, false) + dir, ks := tmpKeyStore(t, false) defer os.RemoveAll(dir) // Create a test account. - a1, err := am.NewAccount("") + a1, err := ks.NewAccount("") if err != nil { t.Fatal("could not create the test account", err) } - if err := am.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { + if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { t.Fatal("could not unlock the test account", err) } end := time.Now().Add(500 * time.Millisecond) for time.Now().Before(end) { - if _, err := am.Sign(a1.Address, testSigData); err == ErrLocked { + if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrNeedPasswordOrUnlock { return } else if err != nil { t.Errorf("Sign error: %v", err) @@ -211,14 +212,14 @@ func TestSignRace(t *testing.T) { t.Errorf("Account did not lock within the timeout") } -func tmpManager(t *testing.T, encrypted bool) (string, *Manager) { +func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { d, err := ioutil.TempDir("", "eth-keystore-test") if err != nil { t.Fatal(err) } - new := NewPlaintextManager + new := NewPlaintextKeyStore if encrypted { - new = func(kd string) *Manager { return NewManager(kd, veryLightScryptN, veryLightScryptP) } + new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } } return d, new(d) } diff --git a/accounts/presale.go b/accounts/keystore/presale.go index f00b4f502..948320e0a 100644 --- a/accounts/presale.go +++ b/accounts/keystore/presale.go @@ -14,7 +14,7 @@ // 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 accounts +package keystore import ( "crypto/aes" @@ -25,20 +25,21 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" "github.com/pborman/uuid" "golang.org/x/crypto/pbkdf2" ) // creates a Key and stores that in the given KeyStore by decrypting a presale key JSON -func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (Account, *Key, error) { +func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) { key, err := decryptPreSaleKey(keyJSON, password) if err != nil { - return Account{}, nil, err + return accounts.Account{}, nil, err } key.Id = uuid.NewRandom() - a := Account{Address: key.Address, File: keyStore.JoinPath(keyFileName(key.Address))} - err = keyStore.StoreKey(a.File, key, password) + a := accounts.Account{Address: key.Address, URL: keyStore.JoinPath(keyFileName(key.Address))} + err = keyStore.StoreKey(a.URL, key, password) return a, key, err } diff --git a/accounts/testdata/dupes/1 b/accounts/keystore/testdata/dupes/1 index a3868ec6d..a3868ec6d 100644 --- a/accounts/testdata/dupes/1 +++ b/accounts/keystore/testdata/dupes/1 diff --git a/accounts/testdata/dupes/2 b/accounts/keystore/testdata/dupes/2 index a3868ec6d..a3868ec6d 100644 --- a/accounts/testdata/dupes/2 +++ b/accounts/keystore/testdata/dupes/2 diff --git a/accounts/testdata/dupes/foo b/accounts/keystore/testdata/dupes/foo index c57060aea..c57060aea 100644 --- a/accounts/testdata/dupes/foo +++ b/accounts/keystore/testdata/dupes/foo diff --git a/accounts/testdata/keystore/.hiddenfile b/accounts/keystore/testdata/keystore/.hiddenfile index d91faccde..d91faccde 100644 --- a/accounts/testdata/keystore/.hiddenfile +++ b/accounts/keystore/testdata/keystore/.hiddenfile diff --git a/accounts/testdata/keystore/README b/accounts/keystore/testdata/keystore/README index a5a86f964..a5a86f964 100644 --- a/accounts/testdata/keystore/README +++ b/accounts/keystore/testdata/keystore/README diff --git a/accounts/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 index c57060aea..c57060aea 100644 --- a/accounts/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 +++ b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 diff --git a/accounts/testdata/keystore/aaa b/accounts/keystore/testdata/keystore/aaa index a3868ec6d..a3868ec6d 100644 --- a/accounts/testdata/keystore/aaa +++ b/accounts/keystore/testdata/keystore/aaa diff --git a/accounts/testdata/keystore/empty b/accounts/keystore/testdata/keystore/empty index e69de29bb..e69de29bb 100644 --- a/accounts/testdata/keystore/empty +++ b/accounts/keystore/testdata/keystore/empty diff --git a/accounts/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e index 309841e52..309841e52 100644 --- a/accounts/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e +++ b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e diff --git a/accounts/testdata/keystore/garbage b/accounts/keystore/testdata/keystore/garbage Binary files differindex ff45091e7..ff45091e7 100644 --- a/accounts/testdata/keystore/garbage +++ b/accounts/keystore/testdata/keystore/garbage diff --git a/accounts/testdata/keystore/no-address b/accounts/keystore/testdata/keystore/no-address index ad51269ea..ad51269ea 100644 --- a/accounts/testdata/keystore/no-address +++ b/accounts/keystore/testdata/keystore/no-address diff --git a/accounts/testdata/keystore/zero b/accounts/keystore/testdata/keystore/zero index b52617f8a..b52617f8a 100644 --- a/accounts/testdata/keystore/zero +++ b/accounts/keystore/testdata/keystore/zero diff --git a/accounts/testdata/keystore/zzz b/accounts/keystore/testdata/keystore/zzz index cfd8a4701..cfd8a4701 100644 --- a/accounts/testdata/keystore/zzz +++ b/accounts/keystore/testdata/keystore/zzz diff --git a/accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e b/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e index 498d8131e..498d8131e 100644 --- a/accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e +++ b/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e diff --git a/accounts/testdata/v1_test_vector.json b/accounts/keystore/testdata/v1_test_vector.json index 3d09b55b5..3d09b55b5 100644 --- a/accounts/testdata/v1_test_vector.json +++ b/accounts/keystore/testdata/v1_test_vector.json diff --git a/accounts/testdata/v3_test_vector.json b/accounts/keystore/testdata/v3_test_vector.json index 1e7f790c0..1e7f790c0 100644 --- a/accounts/testdata/v3_test_vector.json +++ b/accounts/keystore/testdata/v3_test_vector.json diff --git a/accounts/testdata/very-light-scrypt.json b/accounts/keystore/testdata/very-light-scrypt.json index d23b9b2b9..d23b9b2b9 100644 --- a/accounts/testdata/very-light-scrypt.json +++ b/accounts/keystore/testdata/very-light-scrypt.json diff --git a/accounts/watch.go b/accounts/keystore/watch.go index 472be2df7..04a87b12e 100644 --- a/accounts/watch.go +++ b/accounts/keystore/watch.go @@ -16,7 +16,7 @@ // +build darwin,!ios freebsd linux,!arm64 netbsd solaris -package accounts +package keystore import ( "time" @@ -27,14 +27,14 @@ import ( ) type watcher struct { - ac *addrCache + ac *addressCache starting bool running bool ev chan notify.EventInfo quit chan struct{} } -func newWatcher(ac *addrCache) *watcher { +func newWatcher(ac *addressCache) *watcher { return &watcher{ ac: ac, ev: make(chan notify.EventInfo, 10), diff --git a/accounts/watch_fallback.go b/accounts/keystore/watch_fallback.go index bf971cb1b..6412f3b33 100644 --- a/accounts/watch_fallback.go +++ b/accounts/keystore/watch_fallback.go @@ -19,10 +19,10 @@ // This is the fallback implementation of directory watching. // It is used on unsupported platforms. -package accounts +package keystore type watcher struct{ running bool } -func newWatcher(*addrCache) *watcher { return new(watcher) } -func (*watcher) start() {} -func (*watcher) close() {} +func newWatcher(*addressCache) *watcher { return new(watcher) } +func (*watcher) start() {} +func (*watcher) close() {} |