OSDN Git Service

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