diff options
Diffstat (limited to 'accounts')
-rw-r--r-- | accounts/account_manager.go | 137 | ||||
-rw-r--r-- | accounts/accounts_test.go | 11 |
2 files changed, 104 insertions, 44 deletions
diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 3e9fa7799..4575334bf 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -33,6 +33,8 @@ and accounts persistence is derived from stored keys' addresses package accounts import ( + "bytes" + "crypto/ecdsa" crand "crypto/rand" "errors" @@ -42,77 +44,102 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -var ErrLocked = errors.New("account is locked; please request passphrase") +var ( + ErrLocked = errors.New("account is locked") + ErrNoKeys = errors.New("no keys in store") +) -// TODO: better name for this struct? type Account struct { Address []byte } -type AccountManager struct { - keyStore crypto.KeyStore2 - unlockedKeys map[string]crypto.Key - unlockMilliseconds time.Duration - mutex sync.RWMutex +type Manager struct { + keyStore crypto.KeyStore2 + unlocked map[string]*unlocked + unlockTime time.Duration + mutex sync.RWMutex +} + +type unlocked struct { + *crypto.Key + abort chan struct{} +} + +func NewManager(keyStore crypto.KeyStore2, unlockTime time.Duration) *Manager { + return &Manager{ + keyStore: keyStore, + unlocked: make(map[string]*unlocked), + unlockTime: unlockTime, + } } -func NewAccountManager(keyStore crypto.KeyStore2, unlockMilliseconds time.Duration) AccountManager { - keysMap := make(map[string]crypto.Key) - am := &AccountManager{ - keyStore: keyStore, - unlockedKeys: keysMap, - unlockMilliseconds: unlockMilliseconds, +func (am *Manager) HasAccount(addr []byte) bool { + accounts, _ := am.Accounts() + for _, acct := range accounts { + if bytes.Compare(acct.Address, addr) == 0 { + return true + } + } + return false +} + +// Coinbase returns the account address that mining rewards are sent to. +func (am *Manager) Coinbase() (addr []byte, err error) { + // TODO: persist coinbase address on disk + return am.firstAddr() +} + +func (am *Manager) firstAddr() ([]byte, error) { + addrs, err := am.keyStore.GetKeyAddresses() + if err != nil { + return nil, err + } + if len(addrs) == 0 { + return nil, ErrNoKeys } - return *am + return addrs[0], nil } -func (am AccountManager) DeleteAccount(address []byte, auth string) error { +func (am *Manager) DeleteAccount(address []byte, auth string) error { return am.keyStore.DeleteKey(address, auth) } -func (am *AccountManager) Sign(fromAccount *Account, toSign []byte) (signature []byte, err error) { +func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) { am.mutex.RLock() - unlockedKey := am.unlockedKeys[string(fromAccount.Address)] + unlockedKey, found := am.unlocked[string(a.Address)] am.mutex.RUnlock() - if unlockedKey.Address == nil { + if !found { return nil, ErrLocked } signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey) return signature, err } -func (am *AccountManager) SignLocked(fromAccount *Account, keyAuth string, toSign []byte) (signature []byte, err error) { - key, err := am.keyStore.GetKey(fromAccount.Address, keyAuth) +func (am *Manager) SignLocked(a Account, keyAuth string, toSign []byte) (signature []byte, err error) { + key, err := am.keyStore.GetKey(a.Address, keyAuth) if err != nil { return nil, err } - am.mutex.RLock() - am.unlockedKeys[string(fromAccount.Address)] = *key - am.mutex.RUnlock() - go unlockLater(am, fromAccount.Address) + u := am.addUnlocked(a.Address, key) + go am.dropLater(a.Address, u) signature, err = crypto.Sign(toSign, key.PrivateKey) return signature, err } -func (am AccountManager) NewAccount(auth string) (*Account, error) { +func (am *Manager) NewAccount(auth string) (Account, error) { key, err := am.keyStore.GenerateNewKey(crand.Reader, auth) if err != nil { - return nil, err - } - ua := &Account{ - Address: key.Address, + return Account{}, err } - return ua, err + return Account{Address: key.Address}, nil } -func (am *AccountManager) Accounts() ([]Account, error) { +func (am *Manager) Accounts() ([]Account, error) { addresses, err := am.keyStore.GetKeyAddresses() if err != nil { return nil, err } - accounts := make([]Account, len(addresses)) - for i, addr := range addresses { accounts[i] = Account{ Address: addr, @@ -121,12 +148,44 @@ func (am *AccountManager) Accounts() ([]Account, error) { return accounts, err } -func unlockLater(am *AccountManager, addr []byte) { +func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked { + u := &unlocked{Key: key, abort: make(chan struct{})} + am.mutex.Lock() + prev, found := am.unlocked[string(addr)] + if found { + // terminate dropLater for this key to avoid unexpected drops. + close(prev.abort) + zeroKey(prev.PrivateKey) + } + am.unlocked[string(addr)] = u + am.mutex.Unlock() + return u +} + +func (am *Manager) dropLater(addr []byte, u *unlocked) { + t := time.NewTimer(am.unlockTime) + defer t.Stop() select { - case <-time.After(time.Millisecond * am.unlockMilliseconds): + case <-u.abort: + // just quit + case <-t.C: + am.mutex.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[string(addr)] == u { + zeroKey(u.PrivateKey) + delete(am.unlocked, string(addr)) + } + am.mutex.Unlock() + } +} + +// zeroKey zeroes a private key in memory. +func zeroKey(k *ecdsa.PrivateKey) { + b := k.D.Bits() + for i := range b { + b[i] = 0 } - am.mutex.RLock() - // TODO: how do we know the key is actually gone from memory? - delete(am.unlockedKeys, string(addr)) - am.mutex.RUnlock() } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 44d1d72f1..b90da2892 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -3,15 +3,16 @@ package accounts import ( "testing" + "time" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/ethereum/go-ethereum/ethutil" - "time" ) func TestAccountManager(t *testing.T) { ks := crypto.NewKeyStorePlain(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 100) + am := NewManager(ks, 100*time.Millisecond) pass := "" // not used but required by API a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -21,7 +22,7 @@ func TestAccountManager(t *testing.T) { } // Cleanup - time.Sleep(time.Millisecond * 150) // wait for locking + time.Sleep(150 * time.Millisecond) // wait for locking accounts, err := am.Accounts() if err != nil { @@ -37,7 +38,7 @@ func TestAccountManager(t *testing.T) { func TestAccountManagerLocking(t *testing.T) { ks := crypto.NewKeyStorePassphrase(ethutil.DefaultDataDir() + "/testaccounts") - am := NewAccountManager(ks, 200) + am := NewManager(ks, 200*time.Millisecond) pass := "foo" a1, err := am.NewAccount(pass) toSign := randentropy.GetEntropyCSPRNG(32) @@ -61,7 +62,7 @@ func TestAccountManagerLocking(t *testing.T) { } // Signing without passphrase fails after automatic locking - time.Sleep(time.Millisecond * time.Duration(250)) + time.Sleep(250 * time.Millisecond) _, err = am.Sign(a1, toSign) if err != ErrLocked { |