1 // Package account stores and tracks accounts within a Bytom Core.
11 "github.com/google/uuid"
12 log "github.com/sirupsen/logrus"
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"
29 // HardenedKeyStart bip32 hierarchical deterministic wallets
30 // keys with index ≥ 0x80000000 are hardened keys
31 HardenedKeyStart = 0x80000000
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")
45 // pre-define errors for supporting bytom errorFormatter
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")
59 // ContractKey account control promgram store prefix
60 func ContractKey(hash common.Hash) []byte {
61 return append(contractPrefix, hash[:]...)
64 // Key account store prefix
65 func Key(name string) []byte {
66 return append(accountPrefix, []byte(name)...)
69 func aliasKey(name string) []byte {
70 return append(aliasPrefix, []byte(name)...)
73 func bip44ContractIndexKey(accountID string, change bool) []byte {
74 key := append(contractIndexPrefix, accountID...)
76 return append(key, []byte{1}...)
78 return append(key, []byte{0}...)
81 func contractIndexKey(accountID string) []byte {
82 return append(contractIndexPrefix, []byte(accountID)...)
85 // Account is structure of Bytom account
89 Alias string `json:"alias"`
92 //CtrlProgram is structure of account control program
93 type CtrlProgram struct {
98 Change bool // Mark whether this control program is for UTXO change
101 // Manager stores accounts and their associated control programs.
102 type Manager struct {
104 chain *protocol.Chain
105 utxoKeeper *utxoKeeper
111 // NewManager creates a new account manager
112 func NewManager(walletDB dbm.DB, chain *protocol.Chain) *Manager {
116 utxoKeeper: newUtxoKeeper(chain.BestBlockHeight, walletDB),
120 // AddUnconfirmedUtxo add untxo list to utxoKeeper
121 func (m *Manager) AddUnconfirmedUtxo(utxos []*UTXO) {
122 m.utxoKeeper.AddUnconfirmedUtxo(utxos)
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
131 signer, err := signers.Create("account", xpubs, quorum, acctIndex, deriveRule)
133 return nil, errors.Wrap(err)
136 id := uuid.New().String()
137 return &Account{Signer: signer, ID: id, Alias: strings.ToLower(strings.TrimSpace(alias))}, nil
140 func (m *Manager) saveAccount(account *Account, updateIndex bool) error {
141 rawAccount, err := json.Marshal(account)
143 return ErrMarshalAccount
146 storeBatch := m.db.NewBatch()
147 storeBatch.Set(Key(account.ID), rawAccount)
148 storeBatch.Set(aliasKey(account.Alias), []byte(account.ID))
150 storeBatch.Set(GetAccountIndexKey(account.XPubs), common.Unit64ToBytes(account.KeyIndex))
156 // SaveAccount save a new account.
157 func (m *Manager) SaveAccount(account *Account) error {
159 defer m.accountMu.Unlock()
161 if existed := m.db.Get(aliasKey(account.Alias)); existed != nil {
162 return ErrDuplicateAlias
165 acct, err := m.GetAccountByXPubsIndex(account.XPubs, account.KeyIndex)
171 return ErrDuplicateIndex
174 currentIndex := uint64(0)
175 if rawIndexBytes := m.db.Get(GetAccountIndexKey(account.XPubs)); rawIndexBytes != nil {
176 currentIndex = common.BytesToUnit64(rawIndexBytes)
178 return m.saveAccount(account, account.KeyIndex > currentIndex)
181 // Create creates and save a new Account.
182 func (m *Manager) Create(xpubs []chainkd.XPub, quorum int, alias string, deriveRule uint8) (*Account, error) {
184 defer m.accountMu.Unlock()
186 if existed := m.db.Get(aliasKey(alias)); existed != nil {
187 return nil, ErrDuplicateAlias
190 acctIndex := uint64(1)
191 if rawIndexBytes := m.db.Get(GetAccountIndexKey(xpubs)); rawIndexBytes != nil {
192 acctIndex = common.BytesToUnit64(rawIndexBytes) + 1
194 account, err := CreateAccount(xpubs, quorum, alias, acctIndex, deriveRule)
199 if err := m.saveAccount(account, true); err != nil {
206 func (m *Manager) UpdateAccountAlias(accountID string, newAlias string) (err error) {
208 defer m.accountMu.Unlock()
210 account, err := m.FindByID(accountID)
214 oldAlias := account.Alias
216 normalizedAlias := strings.ToLower(strings.TrimSpace(newAlias))
217 if existed := m.db.Get(aliasKey(normalizedAlias)); existed != nil {
218 return ErrDuplicateAlias
221 account.Alias = normalizedAlias
222 rawAccount, err := json.Marshal(account)
224 return ErrMarshalAccount
227 storeBatch := m.db.NewBatch()
228 storeBatch.Delete(aliasKey(oldAlias))
229 storeBatch.Set(Key(accountID), rawAccount)
230 storeBatch.Set(aliasKey(normalizedAlias), []byte(accountID))
235 // CreateAddress generate an address for the select account
236 func (m *Manager) CreateAddress(accountID string, change bool) (cp *CtrlProgram, err error) {
238 defer m.addressMu.Unlock()
240 account, err := m.FindByID(accountID)
245 currentIdx, err := m.getCurrentContractIndex(account, change)
250 cp, err = CreateCtrlProgram(account, currentIdx+1, change)
255 return cp, m.saveControlProgram(cp, true)
258 // CreateBatchAddresses generate a batch of addresses for the select account
259 func (m *Manager) CreateBatchAddresses(accountID string, change bool, stopIndex uint64) error {
261 defer m.addressMu.Unlock()
263 account, err := m.FindByID(accountID)
268 currentIndex, err := m.getCurrentContractIndex(account, change)
273 for currentIndex++; currentIndex <= stopIndex; currentIndex++ {
274 cp, err := CreateCtrlProgram(account, currentIndex, change)
279 if err := m.saveControlProgram(cp, true); err != nil {
287 // deleteAccountControlPrograms deletes control program matching accountID
288 func (m *Manager) deleteAccountControlPrograms(accountID string) error {
289 cps, err := m.ListControlProgram()
295 for _, cp := range cps {
296 if cp.AccountID == accountID {
297 sha3pool.Sum256(hash[:], cp.ControlProgram)
298 m.db.Delete(ContractKey(hash))
301 m.db.Delete(bip44ContractIndexKey(accountID, false))
302 m.db.Delete(bip44ContractIndexKey(accountID, true))
303 m.db.Delete(contractIndexKey(accountID))
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 {
317 if accountID == accountUtxo.AccountID {
318 m.db.Delete(StandardUTXOKey(accountUtxo.OutputID))
324 // DeleteAccount deletes the account's ID or alias matching account ID.
325 func (m *Manager) DeleteAccount(accountID string) (err error) {
327 defer m.accountMu.Unlock()
329 account, err := m.FindByID(accountID)
334 if err := m.deleteAccountControlPrograms(accountID); err != nil {
338 if err := m.deleteAccountUtxos(accountID); err != nil {
342 storeBatch := m.db.NewBatch()
343 storeBatch.Delete(aliasKey(account.Alias))
344 storeBatch.Delete(Key(account.ID))
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))
353 return nil, ErrFindAccount
356 accountID := string(rawID)
357 return m.FindByID(accountID)
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
367 account := &Account{}
368 return account, json.Unmarshal(rawAccount, account)
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
378 account := &Account{}
379 return account, json.Unmarshal(rawAccount, account)
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("")
389 for _, account := range accounts {
390 if reflect.DeepEqual(account.XPubs, xPubs) && account.KeyIndex == index {
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")
405 account := &Account{}
406 if err := json.Unmarshal(rawAccount, account); err != nil {
412 func (m *Manager) GetCoinbaseArbitrary() []byte {
413 if arbitrary := m.db.Get(CoinbaseAbKey); arbitrary != nil {
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()
429 return cp.ControlProgram, nil
432 // GetCoinbaseCtrlProgram will return the coinbase CtrlProgram
433 func (m *Manager) GetCoinbaseCtrlProgram() (*CtrlProgram, error) {
434 if data := m.db.Get(miningAddressKey); data != nil {
436 return cp, json.Unmarshal(data, cp)
439 accountIter := m.db.IteratorPrefix([]byte(accountPrefix))
440 defer accountIter.Release()
441 if !accountIter.Next() {
442 return nil, ErrFindAccount
445 account := &Account{}
446 if err := json.Unmarshal(accountIter.Value(), account); err != nil {
450 program, err := m.CreateAddress(account.ID, false)
455 rawCP, err := json.Marshal(program)
460 m.db.Set(miningAddressKey, rawCP)
464 // GetContractIndex return the current index
465 func (m *Manager) GetContractIndex(accountID string) uint64 {
467 if rawIndexBytes := m.db.Get(contractIndexKey(accountID)); rawIndexBytes != nil {
468 index = common.BytesToUnit64(rawIndexBytes)
473 // GetBip44ContractIndex return the current bip44 contract index
474 func (m *Manager) GetBip44ContractIndex(accountID string, change bool) uint64 {
476 if rawIndexBytes := m.db.Get(bip44ContractIndexKey(accountID, change)); rawIndexBytes != nil {
477 index = common.BytesToUnit64(rawIndexBytes)
482 // GetLocalCtrlProgramByAddress return CtrlProgram by given address
483 func (m *Manager) GetLocalCtrlProgramByAddress(address string) (*CtrlProgram, error) {
484 program, err := m.getProgramByAddress(address)
490 sha3pool.Sum256(hash[:], program)
491 rawProgram := m.db.Get(ContractKey(hash))
492 if rawProgram == nil {
493 return nil, ErrFindCtrlProgram
497 return cp, json.Unmarshal(rawProgram, cp)
500 // GetMiningAddress will return the mining address
501 func (m *Manager) GetMiningAddress() (string, error) {
502 cp, err := m.GetCoinbaseCtrlProgram()
507 return cp.Address, nil
510 // SetMiningAddress will set the mining address
511 func (m *Manager) SetMiningAddress(miningAddress string) (string, error) {
512 program, err := m.getProgramByAddress(miningAddress)
518 Address: miningAddress,
519 ControlProgram: program,
521 rawCP, err := json.Marshal(cp)
526 m.db.Set(miningAddressKey, rawCP)
527 return m.GetMiningAddress()
530 // IsLocalControlProgram check is the input control program belong to local
531 func (m *Manager) IsLocalControlProgram(prog []byte) bool {
533 sha3pool.Sum256(hash[:], prog)
534 bytes := m.db.Get(ContractKey(hash))
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()
544 for accountIter.Next() {
545 account := &Account{}
546 if err := json.Unmarshal(accountIter.Value(), &account); err != nil {
549 accounts = append(accounts, account)
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()
562 if err := json.Unmarshal(cpIter.Value(), cp); err != nil {
565 cps = append(cps, cp)
570 func (m *Manager) ListUnconfirmedUtxo(accountID string, isSmartContract bool) []*UTXO {
571 utxos := m.utxoKeeper.ListUnconfirmed()
573 for _, utxo := range utxos {
574 if segwit.IsP2WScript(utxo.ControlProgram) != isSmartContract && (accountID == utxo.AccountID || accountID == "") {
575 result = append(result, utxo)
581 // RemoveUnconfirmedUtxo remove utxos from the utxoKeeper
582 func (m *Manager) RemoveUnconfirmedUtxo(hashes []*bc.Hash) {
583 m.utxoKeeper.RemoveUnconfirmedUtxo(hashes)
586 func (m *Manager) SetCoinbaseArbitrary(arbitrary []byte) {
587 m.db.Set(CoinbaseAbKey, arbitrary)
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)
597 if len(account.XPubs) == 1 {
598 cp, err = createP2PKH(account, path)
600 cp, err = createP2SH(account, path)
605 cp.KeyIndex, cp.Change = addrIdx, change
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)
614 address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.ActiveNetParams)
619 control, err := vmutil.P2WPKHProgram([]byte(pubHash))
625 AccountID: account.ID,
626 Address: address.EncodeAddress(),
627 ControlProgram: control,
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)
638 scriptHash := crypto.Sha256(signScript)
640 address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.ActiveNetParams)
645 control, err := vmutil.P2WSHProgram(scriptHash)
651 AccountID: account.ID,
652 Address: address.EncodeAddress(),
653 ControlProgram: control,
657 func GetAccountIndexKey(xpubs []chainkd.XPub) []byte {
660 cpy := append([]chainkd.XPub{}, xpubs[:]...)
661 sort.Sort(signers.SortKeys(cpy))
662 for _, xpub := range cpy {
663 xPubs = append(xPubs, xpub[:]...)
665 sha3pool.Sum256(hash[:], xPubs)
666 return append(accountIndexPrefix, hash[:]...)
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
676 return 0, ErrDeriveRule
679 func (m *Manager) getProgramByAddress(address string) ([]byte, error) {
680 addr, err := common.DecodeAddress(address, &consensus.ActiveNetParams)
684 redeemContract := addr.ScriptAddress()
687 case *common.AddressWitnessPubKeyHash:
688 program, err = vmutil.P2WPKHProgram(redeemContract)
689 case *common.AddressWitnessScriptHash:
690 program, err = vmutil.P2WSHProgram(redeemContract)
692 return nil, ErrInvalidAddress
700 func (m *Manager) saveControlProgram(prog *CtrlProgram, updateIndex bool) error {
703 sha3pool.Sum256(hash[:], prog.ControlProgram)
704 acct, err := m.GetAccountByProgram(prog)
709 accountCP, err := json.Marshal(prog)
714 storeBatch := m.db.NewBatch()
715 storeBatch.Set(ContractKey(hash), accountCP)
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))
729 // SaveControlPrograms save account control programs
730 func (m *Manager) SaveControlPrograms(progs ...*CtrlProgram) error {
732 defer m.addressMu.Unlock()
734 for _, prog := range progs {
735 acct, err := m.GetAccountByProgram(prog)
740 currentIndex, err := m.getCurrentContractIndex(acct, prog.Change)
745 m.saveControlProgram(prog, prog.KeyIndex > currentIndex)