OSDN Git Service

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