github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/account/accounts.go (about)

     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/bytom/bytom/blockchain/signers"
    15  	"github.com/bytom/bytom/blockchain/txbuilder"
    16  	"github.com/bytom/bytom/common"
    17  	"github.com/bytom/bytom/consensus"
    18  	"github.com/bytom/bytom/consensus/segwit"
    19  	"github.com/bytom/bytom/crypto"
    20  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    21  	"github.com/bytom/bytom/crypto/sha3pool"
    22  	"github.com/bytom/bytom/errors"
    23  	"github.com/bytom/bytom/protocol"
    24  	"github.com/bytom/bytom/protocol/bc"
    25  	"github.com/bytom/bytom/protocol/vm/vmutil"
    26  	dbm "github.com/bytom/bytom/database/leveldb"
    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  }