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