OSDN Git Service

53a6dee8dec64559a75d49302a16ebdfeaa4c95d
[bytom/vapor.git] / wallet / recovery.go
1 package wallet
2
3 import (
4         "encoding/json"
5         "fmt"
6         "reflect"
7         "sync"
8         "sync/atomic"
9         "time"
10
11         "github.com/vapor/account"
12         "github.com/vapor/blockchain/signers"
13         "github.com/vapor/crypto/ed25519/chainkd"
14         "github.com/vapor/crypto/sha3pool"
15         dbm "github.com/vapor/database/leveldb"
16         "github.com/vapor/errors"
17         "github.com/vapor/protocol/bc"
18         "github.com/vapor/protocol/bc/types"
19 )
20
21 const (
22         // acctRecoveryWindow defines the account derivation lookahead used when
23         // attempting to recover the set of used accounts.
24         acctRecoveryWindow = uint64(6)
25
26         // addrRecoveryWindow defines the address derivation lookahead used when
27         // attempting to recover the set of used addresses.
28         addrRecoveryWindow = uint64(128)
29 )
30
31 //recoveryKey key for db store recovery info.
32 var (
33         recoveryKey = []byte("RecoveryInfo")
34
35         // ErrRecoveryBusy another recovery in progress, can not get recovery manager lock
36         ErrRecoveryBusy = errors.New("another recovery in progress")
37
38         // ErrInvalidAcctID can not find account by account id
39         ErrInvalidAcctID = errors.New("invalid account id")
40 )
41
42 // branchRecoveryState maintains the required state in-order to properly
43 // recover addresses derived from a particular account's internal or external
44 // derivation branch.
45 //
46 // A branch recovery state supports operations for:
47 //  - Expanding the look-ahead horizon based on which indexes have been found.
48 //  - Registering derived addresses with indexes within the horizon.
49 //  - Reporting an invalid child index that falls into the horizon.
50 //  - Reporting that an address has been found.
51 //  - Retrieving all currently derived addresses for the branch.
52 //  - Looking up a particular address by its child index.
53 type branchRecoveryState struct {
54         // recoveryWindow defines the key-derivation lookahead used when
55         // attempting to recover the set of addresses on this branch.
56         RecoveryWindow uint64
57
58         // horizion records the highest child index watched by this branch.
59         Horizon uint64
60
61         // nextUnfound maintains the child index of the successor to the highest
62         // index that has been found during recovery of this branch.
63         NextUnfound uint64
64 }
65
66 // newBranchRecoveryState creates a new branchRecoveryState that can be used to
67 // track either the external or internal branch of an account's derivation path.
68 func newBranchRecoveryState(recoveryWindow uint64) *branchRecoveryState {
69         return &branchRecoveryState{
70                 RecoveryWindow: recoveryWindow,
71                 Horizon:        1,
72                 NextUnfound:    1,
73         }
74 }
75
76 // extendHorizon returns the current horizon and the number of addresses that
77 // must be derived in order to maintain the desired recovery window.
78 func (brs *branchRecoveryState) extendHorizon() (uint64, uint64) {
79         // Compute the new horizon, which should surpass our last found address
80         // by the recovery window.
81         curHorizon := brs.Horizon
82
83         minValidHorizon := brs.NextUnfound + brs.RecoveryWindow
84
85         // If the current horizon is sufficient, we will not have to derive any
86         // new keys.
87         if curHorizon >= minValidHorizon {
88                 return curHorizon, 0
89         }
90
91         // Otherwise, the number of addresses we should derive corresponds to
92         // the delta of the two horizons, and we update our new horizon.
93         delta := minValidHorizon - curHorizon
94         brs.Horizon = minValidHorizon
95
96         return curHorizon, delta
97 }
98
99 // reportFound updates the last found index if the reported index exceeds the
100 // current value.
101 func (brs *branchRecoveryState) reportFound(index uint64) {
102         if index >= brs.NextUnfound {
103                 brs.NextUnfound = index + 1
104         }
105 }
106
107 // addressRecoveryState is used to manage the recovery of addresses generated
108 // under a particular BIP32/BIP44 account. Each account tracks both an external and
109 // internal branch recovery state, both of which use the same recovery window.
110 type addressRecoveryState struct {
111         // ExternalBranch is the recovery state of addresses generated for
112         // external use, i.e. receiving addresses.
113         ExternalBranch *branchRecoveryState
114
115         // InternalBranch is the recovery state of addresses generated for
116         // internal use, i.e. change addresses.
117         InternalBranch *branchRecoveryState
118
119         Account *account.Account
120 }
121
122 func newAddressRecoveryState(recoveryWindow uint64, account *account.Account) *addressRecoveryState {
123         return &addressRecoveryState{
124                 ExternalBranch: newBranchRecoveryState(recoveryWindow),
125                 InternalBranch: newBranchRecoveryState(recoveryWindow),
126                 Account:        account,
127         }
128 }
129
130 // recoveryState used to record the status of a recovery process.
131 type recoveryState struct {
132         // XPubs recovery account xPubs
133         XPubs []chainkd.XPub
134
135         // The time to start the recovery task, used to detemine whether
136         // recovery task is completed.
137         StartTime time.Time
138
139         // XPubsStatus maintains a map of each requested XPub to its active
140         // account recovery state.
141         XPubsStatus *branchRecoveryState
142
143         // AcctStatus maintains a map of each requested key scope to its active
144         // recovery state.
145         AccountsStatus map[string]*addressRecoveryState
146 }
147
148 func newRecoveryState() *recoveryState {
149         return &recoveryState{
150                 AccountsStatus: make(map[string]*addressRecoveryState),
151                 StartTime:      time.Now(),
152         }
153 }
154
155 // stateForScope returns a ScopeRecoveryState for the provided key scope. If one
156 // does not already exist, a new one will be generated with the RecoveryState's
157 // recoveryWindow.
158 func (rs *recoveryState) stateForScope(account *account.Account) {
159         // If the account recovery state already exists, return it.
160         if _, ok := rs.AccountsStatus[account.ID]; ok {
161                 return
162         }
163
164         // Otherwise, initialize the recovery state for this scope with the
165         // chosen recovery window.
166         rs.AccountsStatus[account.ID] = newAddressRecoveryState(addrRecoveryWindow, account)
167 }
168
169 // recoveryManager manage recovery wallet from key.
170 type recoveryManager struct {
171         mu sync.Mutex
172
173         db         dbm.DB
174         accountMgr *account.Manager
175
176         locked int32
177
178         started bool
179
180         // state encapsulates and allocates the necessary recovery state for all
181         // key scopes and subsidiary derivation paths.
182         state *recoveryState
183
184         //addresses all addresses derivation lookahead used when
185         // attempting to recover the set of used addresses.
186         addresses map[bc.Hash]*account.CtrlProgram
187 }
188
189 // newRecoveryManager create recovery manger.
190 func newRecoveryManager(db dbm.DB, accountMgr *account.Manager) *recoveryManager {
191         return &recoveryManager{
192                 db:         db,
193                 accountMgr: accountMgr,
194                 addresses:  make(map[bc.Hash]*account.CtrlProgram),
195                 state:      newRecoveryState(),
196         }
197 }
198
199 func (m *recoveryManager) AddrResurrect(accts []*account.Account) error {
200         m.mu.Lock()
201         defer m.mu.Unlock()
202
203         for _, acct := range accts {
204                 m.state.stateForScope(acct)
205                 if err := m.extendScanAddresses(acct.ID, false); err != nil {
206                         return err
207                 }
208
209                 //Bip32 path no change field, no need to create addresses repeatedly.
210                 if acct.DeriveRule == signers.BIP0032 {
211                         continue
212                 }
213                 if err := m.extendScanAddresses(acct.ID, true); err != nil {
214                         return err
215                 }
216         }
217
218         m.state.StartTime = time.Now()
219         if err := m.commitStatusInfo(); err != nil {
220                 return err
221         }
222
223         m.started = true
224         return nil
225 }
226
227 func (m *recoveryManager) AcctResurrect(xPubs []chainkd.XPub) error {
228         m.mu.Lock()
229         defer m.mu.Unlock()
230
231         if !m.tryStartXPubsRec() {
232                 return ErrRecoveryBusy
233         }
234
235         m.state.XPubs = xPubs
236         m.state.XPubsStatus = newBranchRecoveryState(acctRecoveryWindow)
237
238         if err := m.extendScanAccounts(); err != nil {
239                 m.stopXPubsRec()
240                 return err
241         }
242         m.state.StartTime = time.Now()
243         if err := m.commitStatusInfo(); err != nil {
244                 return err
245         }
246
247         m.started = true
248         return nil
249 }
250
251 func (m *recoveryManager) commitStatusInfo() error {
252         rawStatus, err := json.Marshal(m.state)
253         if err != nil {
254                 return err
255         }
256
257         m.db.Set(recoveryKey, rawStatus)
258         return nil
259 }
260
261 func genAcctAlias(xPubs []chainkd.XPub, index uint64) string {
262         var tmp []byte
263         for _, xPub := range xPubs {
264                 tmp = append(tmp, xPub[:6]...)
265         }
266         return fmt.Sprintf("%x:%x", tmp, index)
267 }
268
269 func (m *recoveryManager) extendScanAccounts() error {
270         if m.state.XPubsStatus == nil {
271                 return nil
272         }
273
274         curHorizon, delta := m.state.XPubsStatus.extendHorizon()
275         for index := curHorizon; index < curHorizon+delta; index++ {
276                 alias := genAcctAlias(m.state.XPubs, index)
277                 account, err := account.CreateAccount(m.state.XPubs, len(m.state.XPubs), alias, index, signers.BIP0044)
278                 if err != nil {
279                         return err
280                 }
281
282                 m.state.stateForScope(account)
283                 //generate resurrect address for new account.
284                 if err := m.extendScanAddresses(account.ID, true); err != nil {
285                         return err
286                 }
287
288                 if err := m.extendScanAddresses(account.ID, false); err != nil {
289                         return err
290                 }
291         }
292
293         return nil
294 }
295
296 func getCPHash(cp []byte) bc.Hash {
297         var h [32]byte
298         sha3pool.Sum256(h[:], cp)
299         return bc.NewHash(h)
300 }
301
302 func (m *recoveryManager) extendAddress(acct *account.Account, index uint64, change bool) error {
303         cp, err := account.CreateCtrlProgram(acct, index, change)
304         if err != nil {
305                 return err
306         }
307
308         m.addresses[getCPHash(cp.ControlProgram)] = cp
309         return nil
310 }
311
312 func (m *recoveryManager) extendScanAddresses(accountID string, change bool) error {
313         state, ok := m.state.AccountsStatus[accountID]
314         if !ok {
315                 return ErrInvalidAcctID
316         }
317
318         var curHorizon, delta uint64
319         if change {
320                 curHorizon, delta = state.InternalBranch.extendHorizon()
321         } else {
322                 curHorizon, delta = state.ExternalBranch.extendHorizon()
323         }
324         for index := curHorizon; index < curHorizon+delta; index++ {
325                 if err := m.extendAddress(state.Account, index, change); err != nil {
326                         return err
327                 }
328         }
329         return nil
330 }
331
332 func (m *recoveryManager) processBlock(b *types.Block) error {
333         for _, tx := range b.Transactions {
334                 for _, output := range tx.Outputs {
335                         if cp, ok := m.addresses[getCPHash(output.ControlProgram)]; ok {
336                                 status, ok := m.state.AccountsStatus[cp.AccountID]
337                                 if !ok {
338                                         return ErrInvalidAcctID
339                                 }
340
341                                 if err := m.reportFound(status.Account, cp); err != nil {
342                                         return err
343                                 }
344                         }
345                 }
346         }
347
348         return nil
349 }
350
351 // FilterRecoveryTxs Filter transactions that meet the recovery address
352 func (m *recoveryManager) FilterRecoveryTxs(b *types.Block) error {
353         m.mu.Lock()
354         defer m.mu.Unlock()
355
356         if !m.started {
357                 return nil
358         }
359         if b.Time().After(m.state.StartTime) {
360                 m.finished()
361                 return nil
362         }
363         return m.processBlock(b)
364 }
365
366 func (m *recoveryManager) finished() {
367         m.db.Delete(recoveryKey)
368         m.started = false
369         m.addresses = make(map[bc.Hash]*account.CtrlProgram)
370         m.state = newRecoveryState()
371         m.stopXPubsRec()
372 }
373
374 func (m *recoveryManager) LoadStatusInfo() error {
375         m.mu.Lock()
376         defer m.mu.Unlock()
377
378         rawStatus := m.db.Get(recoveryKey)
379         if rawStatus == nil {
380                 return nil
381         }
382
383         if err := json.Unmarshal(rawStatus, m.state); err != nil {
384                 return err
385         }
386
387         if m.state.XPubs != nil && !m.tryStartXPubsRec() {
388                 return ErrRecoveryBusy
389         }
390
391         if err := m.restoreAddresses(); err != nil {
392                 m.stopXPubsRec()
393                 return err
394         }
395
396         m.started = true
397         return nil
398 }
399
400 // restoreAddresses resume addresses for unfinished tasks
401 func (m *recoveryManager) restoreAddresses() error {
402         for _, state := range m.state.AccountsStatus {
403                 for index := uint64(1); index <= state.InternalBranch.Horizon; index++ {
404                         if err := m.extendAddress(state.Account, index, true); err != nil {
405                                 return err
406                         }
407                 }
408
409                 for index := uint64(1); index <= state.ExternalBranch.Horizon; index++ {
410                         if err := m.extendAddress(state.Account, index, false); err != nil {
411                                 return err
412                         }
413                 }
414         }
415         return nil
416 }
417
418 // reportFound found your own address operation.
419 func (m *recoveryManager) reportFound(account *account.Account, cp *account.CtrlProgram) error {
420         if m.state.XPubsStatus != nil && reflect.DeepEqual(m.state.XPubs, account.XPubs) {
421                 //recovery from XPubs need save account to db.
422                 if err := m.saveAccount(account); err != nil {
423                         return err
424                 }
425
426                 m.state.XPubsStatus.reportFound(account.KeyIndex)
427                 if err := m.extendScanAccounts(); err != nil {
428                         return err
429                 }
430         }
431
432         if cp.Change {
433                 m.state.AccountsStatus[account.ID].InternalBranch.reportFound(cp.KeyIndex)
434         } else {
435                 m.state.AccountsStatus[account.ID].ExternalBranch.reportFound(cp.KeyIndex)
436         }
437
438         if err := m.extendScanAddresses(account.ID, cp.Change); err != nil {
439                 return err
440         }
441
442         if err := m.accountMgr.CreateBatchAddresses(account.ID, cp.Change, cp.KeyIndex); err != nil {
443                 return err
444         }
445
446         return m.commitStatusInfo()
447 }
448
449 func (m *recoveryManager) saveAccount(acct *account.Account) error {
450         tmp, err := m.accountMgr.FindByID(acct.ID)
451         if err != nil && errors.Root(err) != account.ErrFindAccount {
452                 return err
453         }
454
455         if tmp != nil {
456                 return nil
457         }
458         return m.accountMgr.SaveAccount(acct)
459 }
460
461 //tryStartXPubsRec guarantee that only one xPubs recovery is in progress.
462 func (m *recoveryManager) tryStartXPubsRec() bool {
463         return atomic.CompareAndSwapInt32(&m.locked, 0, 1)
464 }
465
466 //stopXPubsRec release xPubs recovery lock.
467 func (m *recoveryManager) stopXPubsRec() {
468         m.state.XPubs = nil
469         atomic.StoreInt32(&m.locked, 0)
470 }