aboutsummaryrefslogtreecommitdiffstats
path: root/accounts/account_manager.go
blob: c9e06261a8d0ff79c25e7d055a2dd58b0e1d5dc4 (plain) (blame)
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// Copyright 2015 The go-ethereum Authors
// 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 Lesser 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/>.

// Package implements a private key management facility.
//
// This abstracts part of a user's interaction with an account she controls.
package accounts

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

import (
    "crypto/ecdsa"
    crand "crypto/rand"
    "errors"
    "fmt"
    "os"
    "sync"
    "time"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
)

var (
    ErrLocked = errors.New("account is locked")
    ErrNoKeys = errors.New("no keys in store")
)

type Account struct {
    Address common.Address
}

type Manager struct {
    keyStore crypto.KeyStore
    unlocked map[common.Address]*unlocked
    mutex    sync.RWMutex
}

type unlocked struct {
    *crypto.Key
    abort chan struct{}
}

func NewManager(keyStore crypto.KeyStore) *Manager {
    return &Manager{
        keyStore: keyStore,
        unlocked: make(map[common.Address]*unlocked),
    }
}

func (am *Manager) HasAccount(addr common.Address) bool {
    accounts, _ := am.Accounts()
    for _, acct := range accounts {
        if acct.Address == addr {
            return true
        }
    }
    return false
}

func (am *Manager) DeleteAccount(address common.Address, auth string) error {
    return am.keyStore.DeleteKey(address, auth)
}

func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
    am.mutex.RLock()
    unlockedKey, found := am.unlocked[a.Address]
    am.mutex.RUnlock()
    if !found {
        return nil, ErrLocked
    }
    signature, err = crypto.Sign(toSign, unlockedKey.PrivateKey)
    return signature, err
}

// unlock indefinitely
func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
    return am.TimedUnlock(addr, keyAuth, 0)
}

// Unlock unlocks the account with the given address. The account
// stays unlocked for the duration of timeout
// it timeout is 0 the account is unlocked for the entire session
func (am *Manager) TimedUnlock(addr common.Address, keyAuth string, timeout time.Duration) error {
    key, err := am.keyStore.GetKey(addr, keyAuth)
    if err != nil {
        return err
    }
    var u *unlocked
    am.mutex.Lock()
    defer am.mutex.Unlock()
    var found bool
    u, found = am.unlocked[addr]
    if found {
        // terminate dropLater for this key to avoid unexpected drops.
        if u.abort != nil {
            close(u.abort)
        }
    }
    if timeout > 0 {
        u = &unlocked{Key: key, abort: make(chan struct{})}
        go am.expire(addr, u, timeout)
    } else {
        u = &unlocked{Key: key}
    }
    am.unlocked[addr] = u
    return nil
}

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.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[addr] == u {
            zeroKey(u.PrivateKey)
            delete(am.unlocked, addr)
        }
        am.mutex.Unlock()
    }
}

func (am *Manager) NewAccount(auth string) (Account, error) {
    key, err := am.keyStore.GenerateNewKey(crand.Reader, auth)
    if err != nil {
        return Account{}, err
    }
    return Account{Address: key.Address}, nil
}

func (am *Manager) AddressByIndex(index int) (addr string, err error) {
    var addrs []common.Address
    addrs, err = am.keyStore.GetKeyAddresses()
    if err != nil {
        return
    }
    if index < 0 || index >= len(addrs) {
        err = fmt.Errorf("index out of range: %d (should be 0-%d)", index, len(addrs)-1)
    } else {
        addr = addrs[index].Hex()
    }
    return
}

func (am *Manager) Accounts() ([]Account, error) {
    addresses, err := am.keyStore.GetKeyAddresses()
    if os.IsNotExist(err) {
        return nil, ErrNoKeys
    } else if err != nil {
        return nil, err
    }
    accounts := make([]Account, len(addresses))
    for i, addr := range addresses {
        accounts[i] = Account{
            Address: addr,
        }
    }
    return accounts, err
}

// zeroKey zeroes a private key in memory.
func zeroKey(k *ecdsa.PrivateKey) {
    b := k.D.Bits()
    for i := range b {
        b[i] = 0
    }
}

// USE WITH CAUTION = this will save an unencrypted private key on disk
// no cli or js interface
func (am *Manager) Export(path string, addr common.Address, keyAuth string) error {
    key, err := am.keyStore.GetKey(addr, keyAuth)
    if err != nil {
        return err
    }
    return crypto.SaveECDSA(path, key.PrivateKey)
}

func (am *Manager) Import(path string, keyAuth string) (Account, error) {
    privateKeyECDSA, err := crypto.LoadECDSA(path)
    if err != nil {
        return Account{}, err
    }
    key := crypto.NewKeyFromECDSA(privateKeyECDSA)
    if err = am.keyStore.StoreKey(key, keyAuth); err != nil {
        return Account{}, err
    }
    return Account{Address: key.Address}, nil
}

func (am *Manager) Update(addr common.Address, authFrom, authTo string) (err error) {
    var key *crypto.Key
    key, err = am.keyStore.GetKey(addr, authFrom)

    if err == nil {
        err = am.keyStore.StoreKey(key, authTo)
        if err == nil {
            am.keyStore.Cleanup(addr)
        }
    }
    return
}

func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) {
    var key *crypto.Key
    key, err = crypto.ImportPreSaleKey(am.keyStore, keyJSON, password)
    if err != nil {
        return
    }
    if err = am.keyStore.StoreKey(key, password); err != nil {
        return
    }
    return Account{Address: key.Address}, nil
}