account_manager.go 5.17 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
/*
	This file is part of go-ethereum

	go-ethereum 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.

	go-ethereum 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 General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
*/
/**
 * @authors
 * 	Gustav Simonsson <gustav.simonsson@gmail.com>
 * @date 2015
 *
 */
/*

This abstracts part of a user's interaction with an account she controls.
It's not an abstraction of core Ethereum accounts data type / logic -
for that see the core processing code of blocks / txs.

Currently this is pretty much a passthrough to the KeyStore2 interface,
and accounts persistence is derived from stored keys' addresses

*/
package accounts

import (
36
	"bytes"
37
	"crypto/ecdsa"
38
	crand "crypto/rand"
39
	"os"
obscuren's avatar
obscuren committed
40

41 42 43
	"errors"
	"sync"
	"time"
44

45 46 47
	"github.com/ethereum/go-ethereum/crypto"
)

48 49 50 51
var (
	ErrLocked = errors.New("account is locked")
	ErrNoKeys = errors.New("no keys in store")
)
52

53 54
type Account struct {
	Address []byte
55 56
}

57
type Manager struct {
58 59 60
	keyStore crypto.KeyStore2
	unlocked map[string]*unlocked
	mutex    sync.RWMutex
61 62 63 64
}

type unlocked struct {
	*crypto.Key
65
	abort chan struct{}
66 67
}

68
func NewManager(keyStore crypto.KeyStore2) *Manager {
69
	return &Manager{
70 71
		keyStore: keyStore,
		unlocked: make(map[string]*unlocked),
72 73 74
	}
}

75 76 77 78 79 80 81 82 83 84
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
}

85
// Coinbase returns the account address that mining rewards are sent to.
86
func (am *Manager) Coinbase() (addr []byte, err error) {
87 88 89 90
	// TODO: persist coinbase address on disk
	return am.firstAddr()
}

91
func (am *Manager) firstAddr() ([]byte, error) {
92
	addrs, err := am.keyStore.GetKeyAddresses()
93 94 95
	if os.IsNotExist(err) {
		return nil, ErrNoKeys
	} else if err != nil {
96 97 98 99 100 101 102 103
		return nil, err
	}
	if len(addrs) == 0 {
		return nil, ErrNoKeys
	}
	return addrs[0], nil
}

104
func (am *Manager) DeleteAccount(address []byte, auth string) error {
105 106 107
	return am.keyStore.DeleteKey(address, auth)
}

108
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
109
	am.mutex.RLock()
110
	unlockedKey, found := am.unlocked[string(a.Address)]
111
	am.mutex.RUnlock()
112
	if !found {
113 114 115 116 117 118
		return nil, ErrLocked
	}
	signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey)
	return signature, err
}

119 120 121 122
// TimedUnlock unlocks the account with the given address.
// When timeout has passed, the account will be locked again.
func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error {
	key, err := am.keyStore.GetKey(addr, keyAuth)
123
	if err != nil {
124
		return err
125
	}
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	u := am.addUnlocked(addr, key)
	go am.dropLater(addr, u, timeout)
	return nil
}

// Unlock unlocks the account with the given address. The account
// stays unlocked until the program exits or until a TimedUnlock
// timeout (started after the call to Unlock) expires.
func (am *Manager) Unlock(addr []byte, keyAuth string) error {
	key, err := am.keyStore.GetKey(addr, keyAuth)
	if err != nil {
		return err
	}
	am.addUnlocked(addr, key)
	return nil
141 142
}

143
func (am *Manager) NewAccount(auth string) (Account, error) {
144 145
	key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
	if err != nil {
146
		return Account{}, err
147
	}
148
	return Account{Address: key.Address}, nil
149 150
}

151
func (am *Manager) Accounts() ([]Account, error) {
152
	addresses, err := am.keyStore.GetKeyAddresses()
153 154 155
	if os.IsNotExist(err) {
		return nil, ErrNoKeys
	} else if err != nil {
156 157
		return nil, err
	}
158
	accounts := make([]Account, len(addresses))
159
	for i, addr := range addresses {
160 161
		accounts[i] = Account{
			Address: addr,
162 163 164 165
		}
	}
	return accounts, err
}
166

167
func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
168
	u := &unlocked{Key: key, abort: make(chan struct{})}
169
	am.mutex.Lock()
170 171 172 173
	prev, found := am.unlocked[string(addr)]
	if found {
		// terminate dropLater for this key to avoid unexpected drops.
		close(prev.abort)
174 175 176
		// the key is zeroed here instead of in dropLater because
		// there might not actually be a dropLater running for this
		// key, i.e. when Unlock was used.
177 178 179
		zeroKey(prev.PrivateKey)
	}
	am.unlocked[string(addr)] = u
180
	am.mutex.Unlock()
181
	return u
182 183
}

184 185
func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
	t := time.NewTimer(timeout)
186 187 188 189 190 191
	defer t.Stop()
	select {
	case <-u.abort:
		// just quit
	case <-t.C:
		am.mutex.Lock()
192 193 194 195 196
		// 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 {
197
			zeroKey(u.PrivateKey)
198
			delete(am.unlocked, string(addr))
199 200 201 202 203 204 205 206 207 208 209
		}
		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
	}
210
}