OSDN Git Service

befbe50941b28d74883ca3c5db819d0a29cb9cc9
[bytom/vapor.git] / account / accounts.go
1 // Package account stores and tracks accounts within a Bytom Core.
2 package account
3
4 import (
5         "encoding/json"
6         "reflect"
7         "sort"
8         "strings"
9         "sync"
10
11         "github.com/golang/groupcache/lru"
12         log "github.com/sirupsen/logrus"
13
14         "github.com/vapor/blockchain/signers"
15         "github.com/vapor/blockchain/txbuilder"
16         "github.com/vapor/common"
17         "github.com/vapor/consensus"
18         "github.com/vapor/consensus/segwit"
19         "github.com/vapor/crypto"
20         "github.com/vapor/crypto/ed25519/chainkd"
21         "github.com/vapor/crypto/sha3pool"
22         dbm "github.com/vapor/database/leveldb"
23         "github.com/vapor/errors"
24         "github.com/vapor/protocol"
25         "github.com/vapor/protocol/bc"
26         "github.com/vapor/protocol/vm/vmutil"
27 )
28
29 const (
30         maxAccountCache = 1000
31
32         // HardenedKeyStart bip32 hierarchical deterministic wallets
33         // keys with index ≥ 0x80000000 are hardened keys
34         HardenedKeyStart = 0x80000000
35         logModule        = "account"
36 )
37
38 var (
39         accountIndexPrefix  = []byte("AccountIndex:")
40         accountPrefix       = []byte("Account:")
41         aliasPrefix         = []byte("AccountAlias:")
42         contractIndexPrefix = []byte("ContractIndex")
43         contractPrefix      = []byte("Contract:")
44         miningAddressKey    = []byte("MiningAddress")
45         CoinbaseAbKey       = []byte("CoinbaseArbitrary")
46 )
47
48 // pre-define errors for supporting bytom errorFormatter
49 var (
50         ErrDuplicateAlias  = errors.New("Duplicate account alias")
51         ErrDuplicateIndex  = errors.New("Duplicate account with same xPubs and index")
52         ErrFindAccount     = errors.New("Failed to find account")
53         ErrMarshalAccount  = errors.New("Failed to marshal account")
54         ErrInvalidAddress  = errors.New("Invalid address")
55         ErrFindCtrlProgram = errors.New("Failed to find account control program")
56         ErrDeriveRule      = errors.New("Invalid key derivation rule")
57         ErrContractIndex   = errors.New("Exceeded maximum addresses per account")
58         ErrAccountIndex    = errors.New("Exceeded maximum accounts per xpub")
59         ErrFindTransaction = errors.New("No transaction")
60         ErrAccountIDEmpty  = errors.New("account_id is empty")
61 )
62
63 // ContractKey account control promgram store prefix
64 func ContractKey(hash common.Hash) []byte {
65         return append(contractPrefix, hash[:]...)
66 }
67
68 // Key account store prefix
69 func Key(name string) []byte {
70         return append(accountPrefix, []byte(name)...)
71 }
72
73 func aliasKey(name string) []byte {
74         return append(aliasPrefix, []byte(name)...)
75 }
76
77 func bip44ContractIndexKey(accountID string, change bool) []byte {
78         key := append(contractIndexPrefix, accountID...)
79         if change {
80                 return append(key, []byte{1}...)
81         }
82         return append(key, []byte{0}...)
83 }
84
85 func contractIndexKey(accountID string) []byte {
86         return append(contractIndexPrefix, []byte(accountID)...)
87 }
88
89 // Account is structure of Bytom account
90 type Account struct {
91         *signers.Signer
92         ID    string `json:"id"`
93         Alias string `json:"alias"`
94 }
95
96 //CtrlProgram is structure of account control program
97 type CtrlProgram struct {
98         AccountID      string
99         Address        string
100         KeyIndex       uint64
101         ControlProgram []byte
102         Change         bool // Mark whether this control program is for UTXO change
103 }
104
105 // Manager stores accounts and their associated control programs.
106 type Manager struct {
107         db         dbm.DB
108         chain      *protocol.Chain
109         utxoKeeper *utxoKeeper
110
111         cacheMu    sync.Mutex
112         cache      *lru.Cache
113         aliasCache *lru.Cache
114
115         delayedACPsMu sync.Mutex
116         delayedACPs   map[*txbuilder.TemplateBuilder][]*CtrlProgram
117
118         addressMu sync.Mutex
119         accountMu sync.Mutex
120 }
121
122 // NewManager creates a new account manager
123 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
124         return &Manager{
125                 db:          walletDB,
126                 chain:       chain,
127                 utxoKeeper:  newUtxoKeeper(chain.BestBlockHeight, walletDB),
128                 cache:       lru.New(maxAccountCache),
129                 aliasCache:  lru.New(maxAccountCache),
130                 delayedACPs: make(map[*txbuilder.TemplateBuilder][]*CtrlProgram),
131         }
132 }
133
134 // AddUnconfirmedUtxo add untxo list to utxoKeeper
135 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
136         m.utxoKeeper.AddUnconfirmedUtxo(utxos)
137 }
138
139 // CreateAccount creates a new Account.
140 func CreateAccount(xpubs []chainkd.XPub, quorum int, alias string, acctIndex uint64, deriveRule uint8) (*Account, error) {
141         if acctIndex >= HardenedKeyStart {
142                 return nil, ErrAccountIndex
143         }
144
145         signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
146         if err != nil {
147                 return nil, errors.Wrap(err)
148         }
149
150         id := signers.IDGenerate()
151         return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
152 }
153
154 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
155         rawAccount, err := json.Marshal(account)
156         if err != nil {
157                 return ErrMarshalAccount
158         }
159
160         storeBatch := m.db.NewBatch()
161         storeBatch.Set(Key(account.ID), rawAccount)
162         storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
163         if updateIndex {
164                 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
165         }
166         storeBatch.Write()
167         return nil
168 }
169
170 // SaveAccount save a new account.
171 func (m *Manager) SaveAccount(account *Account) error {
172         m.accountMu.Lock()
173         defer m.accountMu.Unlock()
174
175         if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
176                 return ErrDuplicateAlias
177         }
178
179         acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
180         if err != nil {
181                 return err
182         }
183
184         if acct != nil {
185                 return ErrDuplicateIndex
186         }
187
188         currentIndex := uint64(0)
189         if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
190                 currentIndex = common.BytesToUnit64(rawIndexBytes)
191         }
192         return m.saveAccount(account, account.KeyIndex > currentIndex)
193 }
194
195 // Create creates and save a new Account.
196 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
197         m.accountMu.Lock()
198         defer m.accountMu.Unlock()
199
200         if existed := m.db.Get(aliasKey(alias)); existed != nil {
201                 return nil, ErrDuplicateAlias
202         }
203
204         acctIndex := uint64(1)
205         if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
206                 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
207         }
208         account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
209         if err != nil {
210                 return nil, err
211         }
212
213         if err := m.saveAccount(account, true); err != nil {
214                 return nil, err
215         }
216
217         return account, nil
218 }
219
220 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
221         m.accountMu.Lock()
222         defer m.accountMu.Unlock()
223
224         account, err := m.FindByID(accountID)
225         if err != nil {
226                 return err
227         }
228         oldAlias := account.Alias
229
230         normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
231         if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
232                 return ErrDuplicateAlias
233         }
234
235         m.cacheMu.Lock()
236         m.aliasCache.Remove(oldAlias)
237         m.cacheMu.Unlock()
238
239         account.Alias = normalizedAlias
240         rawAccount, err := json.Marshal(account)
241         if err != nil {
242                 return ErrMarshalAccount
243         }
244
245         storeBatch := m.db.NewBatch()
246         storeBatch.Delete(aliasKey(oldAlias))
247         storeBatch.Set(Key(accountID), rawAccount)
248         storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
249         storeBatch.Write()
250         return nil
251 }
252
253 // CreateAddress generate an address for the select account
254 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
255         m.addressMu.Lock()
256         defer m.addressMu.Unlock()
257
258         account, err := m.FindByID(accountID)
259         if err != nil {
260                 return nil, err
261         }
262
263         currentIdx, err := m.getCurrentContractIndex(account, change)
264         if err != nil {
265                 return nil, err
266         }
267
268         cp, err = CreateCtrlProgram(account, currentIdx+1, change)
269         if err != nil {
270                 return nil, err
271         }
272
273         return cp, m.saveControlProgram(cp, true)
274 }
275
276 // CreateBatchAddresses generate a batch of addresses for the select account
277 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
278         m.addressMu.Lock()
279         defer m.addressMu.Unlock()
280
281         account, err := m.FindByID(accountID)
282         if err != nil {
283                 return err
284         }
285
286         currentIndex, err := m.getCurrentContractIndex(account, change)
287         if err != nil {
288                 return err
289         }
290
291         for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
292                 cp, err := CreateCtrlProgram(account, currentIndex, change)
293                 if err != nil {
294                         return err
295                 }
296
297                 if err := m.saveControlProgram(cp, true); err != nil {
298                         return err
299                 }
300         }
301
302         return nil
303 }
304
305 // deleteAccountControlPrograms deletes control program matching accountID
306 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
307         cps, err := m.ListControlProgram()
308         if err != nil {
309                 return err
310         }
311
312         var hash common.Hash
313         for _, cp := range cps {
314                 if cp.AccountID == accountID {
315                         sha3pool.Sum256(hash[:], cp.ControlProgram)
316                         m.db.Delete(ContractKey(hash))
317                 }
318         }
319         m.db.Delete(bip44ContractIndexKey(accountID, false))
320         m.db.Delete(bip44ContractIndexKey(accountID, true))
321         m.db.Delete(contractIndexKey(accountID))
322         return nil
323 }
324
325 // deleteAccountUtxos deletes utxos matching accountID
326 func (m *Manager) deleteAccountUtxos(accountID string) error {
327         accountUtxoIter := m.db.IteratorPrefix([]byte(UTXOPreFix))
328         defer accountUtxoIter.Release()
329         for accountUtxoIter.Next() {
330                 accountUtxo := &UTXO{}
331                 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
332                         return err
333                 }
334
335                 if accountID == accountUtxo.AccountID {
336                         m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
337                 }
338         }
339         return nil
340 }
341
342 // DeleteAccount deletes the account's ID or alias matching account ID.
343 func (m *Manager) DeleteAccount(accountID string) (err error) {
344         m.accountMu.Lock()
345         defer m.accountMu.Unlock()
346
347         account, err := m.FindByID(accountID)
348         if err != nil {
349                 return err
350         }
351
352         if err := m.deleteAccountControlPrograms(accountID); err != nil {
353                 return err
354         }
355         if err := m.deleteAccountUtxos(accountID); err != nil {
356                 return err
357         }
358
359         m.cacheMu.Lock()
360         m.aliasCache.Remove(account.Alias)
361         m.cacheMu.Unlock()
362
363         storeBatch := m.db.NewBatch()
364         storeBatch.Delete(aliasKey(account.Alias))
365         storeBatch.Delete(Key(account.ID))
366         storeBatch.Write()
367         return nil
368 }
369
370 // FindByAlias retrieves an account's Signer record by its alias
371 func (m *Manager) FindByAlias(alias string) (*Account, error) {
372         m.cacheMu.Lock()
373         cachedID, ok := m.aliasCache.Get(alias)
374         m.cacheMu.Unlock()
375         if ok {
376                 return m.FindByID(cachedID.(string))
377         }
378
379         rawID := m.db.Get(aliasKey(alias))
380         if rawID == nil {
381                 return nil, ErrFindAccount
382         }
383
384         accountID := string(rawID)
385         m.cacheMu.Lock()
386         m.aliasCache.Add(alias, accountID)
387         m.cacheMu.Unlock()
388         return m.FindByID(accountID)
389 }
390
391 // FindByID returns an account's Signer record by its ID.
392 func (m *Manager) FindByID(id string) (*Account, error) {
393         m.cacheMu.Lock()
394         cachedAccount, ok := m.cache.Get(id)
395         m.cacheMu.Unlock()
396         if ok {
397                 return cachedAccount.(*Account), nil
398         }
399
400         rawAccount := m.db.Get(Key(id))
401         if rawAccount == nil {
402                 return nil, ErrFindAccount
403         }
404
405         account := &Account{}
406         if err := json.Unmarshal(rawAccount, account); err != nil {
407                 return nil, err
408         }
409
410         m.cacheMu.Lock()
411         m.cache.Add(id, account)
412         m.cacheMu.Unlock()
413         return account, nil
414 }
415
416 // GetAccountByProgram return Account by given CtrlProgram
417 func (m *Manager) GetAccountByProgram(program *CtrlProgram) (*Account, error) {
418         rawAccount := m.db.Get(Key(program.AccountID))
419         if rawAccount == nil {
420                 return nil, ErrFindAccount
421         }
422
423         account := &Account{}
424         return account, json.Unmarshal(rawAccount, account)
425 }
426
427 // GetAccountByXPubsIndex get account by xPubs and index
428 func (m *Manager) GetAccountByXPubsIndex(xPubs []chainkd.XPub, index uint64) (*Account, error) {
429         accounts, err := m.ListAccounts("")
430         if err != nil {
431                 return nil, err
432         }
433
434         for _, account := range accounts {
435                 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
436                         return account, nil
437                 }
438         }
439         return nil, nil
440 }
441
442 // GetAliasByID return the account alias by given ID
443 func (m *Manager) GetAliasByID(id string) string {
444         rawAccount := m.db.Get(Key(id))
445         if rawAccount == nil {
446                 log.Warn("GetAliasByID fail to find account")
447                 return ""
448         }
449
450         account := &Account{}
451         if err := json.Unmarshal(rawAccount, account); err != nil {
452                 log.Warn(err)
453         }
454         return account.Alias
455 }
456
457 func (m *Manager) GetCoinbaseArbitrary() []byte {
458         if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
459                 return arbitrary
460         }
461         return []byte{}
462 }
463
464 // GetCoinbaseControlProgram will return a coinbase script
465 func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) {
466         cp, err := m.GetCoinbaseCtrlProgram()
467         if err == ErrFindAccount {
468                 log.Warningf("GetCoinbaseControlProgram: can't find any account in db")
469                 return vmutil.DefaultCoinbaseProgram()
470         }
471         if err != nil {
472                 return nil, err
473         }
474         return cp.ControlProgram, nil
475 }
476
477 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
478 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
479         if data := m.db.Get(miningAddressKey); data != nil {
480                 cp := &CtrlProgram{}
481                 return cp, json.Unmarshal(data, cp)
482         }
483
484         accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
485         defer accountIter.Release()
486         if !accountIter.Next() {
487                 return nil, ErrFindAccount
488         }
489
490         account := &Account{}
491         if err := json.Unmarshal(accountIter.Value(), account); err != nil {
492                 return nil, err
493         }
494
495         program, err := m.CreateAddress(account.ID, false)
496         if err != nil {
497                 return nil, err
498         }
499
500         rawCP, err := json.Marshal(program)
501         if err != nil {
502                 return nil, err
503         }
504
505         m.db.Set(miningAddressKey, rawCP)
506         return program, nil
507 }
508
509 // GetContractIndex return the current index
510 func (m *Manager) GetContractIndex(accountID string) uint64 {
511         index := uint64(0)
512         if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
513                 index = common.BytesToUnit64(rawIndexBytes)
514         }
515         return index
516 }
517
518 // GetBip44ContractIndex return the current bip44 contract index
519 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
520         index := uint64(0)
521         if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
522                 index = common.BytesToUnit64(rawIndexBytes)
523         }
524         return index
525 }
526
527 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
528 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
529         program, err := m.getProgramByAddress(address)
530         if err != nil {
531                 return nil, err
532         }
533
534         var hash [32]byte
535         sha3pool.Sum256(hash[:], program)
536         rawProgram := m.db.Get(ContractKey(hash))
537         if rawProgram == nil {
538                 return nil, ErrFindCtrlProgram
539         }
540
541         cp := &CtrlProgram{}
542         return cp, json.Unmarshal(rawProgram, cp)
543 }
544
545 // GetMiningAddress will return the mining address
546 func (m *Manager) GetMiningAddress() (string, error) {
547         cp, err := m.GetCoinbaseCtrlProgram()
548         if err != nil {
549                 return "", err
550         }
551         return cp.Address, nil
552 }
553
554 // IsLocalControlProgram check is the input control program belong to local
555 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
556         var hash common.Hash
557         sha3pool.Sum256(hash[:], prog)
558         bytes := m.db.Get(ContractKey(hash))
559         return bytes != nil
560 }
561
562 // ListAccounts will return the accounts in the db
563 func (m *Manager) ListAccounts(id string) ([]*Account, error) {
564         accounts := []*Account{}
565         accountIter := m.db.IteratorPrefix(Key(strings.TrimSpace(id)))
566         defer accountIter.Release()
567
568         for accountIter.Next() {
569                 account := &Account{}
570                 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
571                         return nil, err
572                 }
573                 accounts = append(accounts, account)
574         }
575         return accounts, nil
576 }
577
578 // ListControlProgram return all the local control program
579 func (m *Manager) ListControlProgram() ([]*CtrlProgram, error) {
580         cps := []*CtrlProgram{}
581         cpIter := m.db.IteratorPrefix(contractPrefix)
582         defer cpIter.Release()
583
584         for cpIter.Next() {
585                 cp := &CtrlProgram{}
586                 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
587                         return nil, err
588                 }
589                 cps = append(cps, cp)
590         }
591         return cps, nil
592 }
593
594 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
595         utxos := m.utxoKeeper.ListUnconfirmed()
596         result := []*UTXO{}
597         for _, utxo := range utxos {
598                 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
599                         result = append(result, utxo)
600                 }
601         }
602         return result
603 }
604
605 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
606 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
607         m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
608 }
609
610 // SetMiningAddress will set the mining address
611 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
612         program, err := m.getProgramByAddress(miningAddress)
613         if err != nil {
614                 return "", err
615         }
616
617         cp := &CtrlProgram{
618                 Address:        miningAddress,
619                 ControlProgram: program,
620         }
621         rawCP, err := json.Marshal(cp)
622         if err != nil {
623                 return "", err
624         }
625
626         m.db.Set(miningAddressKey, rawCP)
627         return m.GetMiningAddress()
628 }
629
630 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
631         m.db.Set(CoinbaseAbKey, arbitrary)
632 }
633
634 // CreateCtrlProgram generate an address for the select account
635 func CreateCtrlProgram(account *Account, addrIdx uint64, change bool) (cp *CtrlProgram, err error) {
636         path, err := signers.Path(account.Signer, signers.AccountKeySpace, change, addrIdx)
637         if err != nil {
638                 return nil, err
639         }
640
641         if len(account.XPubs) == 1 {
642                 cp, err = createP2PKH(account, path)
643         } else {
644                 cp, err = createP2SH(account, path)
645         }
646         if err != nil {
647                 return nil, err
648         }
649         cp.KeyIndex, cp.Change = addrIdx, change
650         return cp, nil
651 }
652
653 func createP2PKH(account *Account, path [][]byte) (*CtrlProgram, error) {
654         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
655         derivedPK := derivedXPubs[0].PublicKey()
656         pubHash := crypto.Ripemd160(derivedPK)
657
658         address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
659         if err != nil {
660                 return nil, err
661         }
662
663         control, err := vmutil.P2WPKHProgram([]byte(pubHash))
664         if err != nil {
665                 return nil, err
666         }
667
668         return &CtrlProgram{
669                 AccountID:      account.ID,
670                 Address:        address.EncodeAddress(),
671                 ControlProgram: control,
672         }, nil
673 }
674
675 func createP2SH(account *Account, path [][]byte) (*CtrlProgram, error) {
676         derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
677         derivedPKs := chainkd.XPubKeys(derivedXPubs)
678         signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
679         if err != nil {
680                 return nil, err
681         }
682         scriptHash := crypto.Sha256(signScript)
683
684         address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
685         if err != nil {
686                 return nil, err
687         }
688
689         control, err := vmutil.P2WSHProgram(scriptHash)
690         if err != nil {
691                 return nil, err
692         }
693
694         return &CtrlProgram{
695                 AccountID:      account.ID,
696                 Address:        address.EncodeAddress(),
697                 ControlProgram: control,
698         }, nil
699 }
700
701 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
702         var hash [32]byte
703         var xPubs []byte
704         cpy := append([]chainkd.XPub{}, xpubs[:]...)
705         sort.Sort(signers.SortKeys(cpy))
706         for _, xpub := range cpy {
707                 xPubs = append(xPubs, xpub[:]...)
708         }
709         sha3pool.Sum256(hash[:], xPubs)
710         return append(accountIndexPrefix, hash[:]...)
711 }
712
713 func (m *Manager) getCurrentContractIndex(account *Account, change bool) (uint64, error) {
714         switch account.DeriveRule {
715         case signers.BIP0032:
716                 return m.GetContractIndex(account.ID), nil
717         case signers.BIP0044:
718                 return m.GetBip44ContractIndex(account.ID, change), nil
719         }
720         return 0, ErrDeriveRule
721 }
722
723 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
724         addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
725         if err != nil {
726                 return nil, err
727         }
728         redeemContract := addr.ScriptAddress()
729         program := []byte{}
730         switch addr.(type) {
731         case *common.AddressWitnessPubKeyHash:
732                 program, err = vmutil.P2WPKHProgram(redeemContract)
733         case *common.AddressWitnessScriptHash:
734                 program, err = vmutil.P2WSHProgram(redeemContract)
735         default:
736                 return nil, ErrInvalidAddress
737         }
738         if err != nil {
739                 return nil, err
740         }
741         return program, nil
742 }
743
744 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
745         var hash common.Hash
746
747         sha3pool.Sum256(hash[:], prog.ControlProgram)
748         acct, err := m.GetAccountByProgram(prog)
749         if err != nil {
750                 return err
751         }
752
753         accountCP, err := json.Marshal(prog)
754         if err != nil {
755                 return err
756         }
757
758         storeBatch := m.db.NewBatch()
759         storeBatch.Set(ContractKey(hash), accountCP)
760         if updateIndex {
761                 switch acct.DeriveRule {
762                 case signers.BIP0032:
763                         storeBatch.Set(contractIndexKey(acct.ID), common.Unit64ToBytes(prog.KeyIndex))
764                 case signers.BIP0044:
765                         storeBatch.Set(bip44ContractIndexKey(acct.ID, prog.Change), common.Unit64ToBytes(prog.KeyIndex))
766                 }
767         }
768         storeBatch.Write()
769
770         return nil
771 }
772
773 // SaveControlPrograms save account control programs
774 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
775         m.addressMu.Lock()
776         defer m.addressMu.Unlock()
777
778         for _, prog := range progs {
779                 acct, err := m.GetAccountByProgram(prog)
780                 if err != nil {
781                         return err
782                 }
783
784                 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
785                 if err != nil {
786                         return err
787                 }
788
789                 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)
790         }
791         return nil
792 }