OSDN Git Service

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