func (m *Manager) CoinType(dbtx walletdb.ReadTx) (uint32, error) {
	ns := dbtx.ReadBucket(waddrmgrBucketKey)
	mainBucket := ns.NestedReadBucket(mainBucketName)
	legacyCoinType, slip0044CoinType := CoinTypes(m.chainParams)
	if mainBucket.Get(coinTypeLegacyPubKeyName) != nil {
		return legacyCoinType, nil
	}
	if mainBucket.Get(coinTypeSLIP0044PubKeyName) != nil {
		return slip0044CoinType, nil
	}
	return 0, errors.E(errors.WatchingOnly, "watching wallets do not record coin type keys")
}
CoinType returns the BIP0044 coin type currently in use.  Early versions of
the wallet used coin types that conflicted with other coins, preventing use
of the same seed in multicurrency wallets.  New (not restored) wallets are
now created using the coin types assigned to Decred in SLIP0044:

https://github.com/satoshilabs/slips/blob/master/slip-0044.md

The address manager should be upgraded to the SLIP0044 coin type if it is
currently using the legacy coin type and there are no used accounts or
addresses.  This procedure must be performed for seed restored wallets which
save both coin types and, for backwards compatibility reasons, default to the
legacy coin type.  Both coin types for a network may be queried using the
CoinTypes func and upgrades are performed using the UpgradeToSLIP0044CoinType
method.

Watching-only wallets that are created using an account xpub do not save the
coin type keys and this method will return an error with code
WatchingOnly on these wallets.
package udb
import (
	"crypto/rand"
	"crypto/subtle"
	"fmt"
	"hash"
	"io"
	"sync"
	"decred.org/dcrwallet/v2/errors"
	"decred.org/dcrwallet/v2/internal/compat"
	"decred.org/dcrwallet/v2/kdf"
	"decred.org/dcrwallet/v2/wallet/internal/snacl"
	"decred.org/dcrwallet/v2/wallet/walletdb"
	"github.com/decred/dcrd/chaincfg/v3"
	"github.com/decred/dcrd/dcrec/secp256k1/v4"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/hdkeychain/v3"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
	"github.com/decred/dcrd/wire"
	"golang.org/x/crypto/blake2b"
	"golang.org/x/crypto/chacha20poly1305"
)
const (
	MaxAccountNum = hdkeychain.HardenedKeyStart - 2  
	MaxAddressesPerAccount = hdkeychain.HardenedKeyStart - 1
	ImportedAddrAccount = MaxAccountNum + 1  
	ImportedAddrAccountName = "imported"
	DefaultAccountNum = 0
	defaultAccountName = "default"
	maxCoinType = hdkeychain.HardenedKeyStart - 1
	ExternalBranch uint32 = 0
	InternalBranch uint32 = 1
)
func isReservedAccountName(name string) bool {
	return name == ImportedAddrAccountName
}
func isReservedAccountNum(acct uint32) bool {
	return acct == ImportedAddrAccount
}
func normalizeAddress(addr stdaddr.Address) stdaddr.Address {
	switch addr := addr.(type) {
	case *stdaddr.AddressPubKeyEcdsaSecp256k1V0:
		return addr.AddressPubKeyHash()
	default:
		return addr
	}
}
type scryptOptions struct {
	N, R, P int
}
func scryptOptionsForNet(net wire.CurrencyNet) *scryptOptions {
	if net == wire.SimNet {
		return &scryptOptions{N: 2, R: 1, P: 1}
	}
	return &scryptOptions{
		N: 262144,  
		R: 8,
		P: 1,
	}
}
type accountInfo struct {
	acctName string
	acctKeyEncrypted []byte
	acctKeyPriv      *hdkeychain.ExtendedKey
	acctKeyPub       *hdkeychain.ExtendedKey
	uniqueKey        *kdf.Argon2idParams
	uniquePassHasher hash.Hash  
	uniquePassHash   []byte
}
func argon2idKey(password []byte, k *kdf.Argon2idParams) keyType {
	return keyType(kdf.DeriveKey(password, k, 32))
}
const (
	xchacha20NonceSize        = 24
	poly1305TagSize           = 16
	xchacha20poly1305Overhead = xchacha20NonceSize + poly1305TagSize
)
type keyType []byte
func seal(rand io.Reader, key keyType, plaintext []byte) ([]byte, error) {
	sealedLen := len(plaintext) + xchacha20poly1305Overhead
	nonce := make([]byte, xchacha20NonceSize, sealedLen)
	_, err := io.ReadFull(rand, nonce)
	if err != nil {
		return nil, errors.E(errors.IO, err)
	}
	aead, err := chacha20poly1305.NewX(key)
	if err != nil {
		panic(err)
	}
	sealed := aead.Seal(nonce, nonce, plaintext, nil)
	return sealed, nil
}
func unseal(key keyType, ciphertext []byte) ([]byte, error) {
	aead, err := chacha20poly1305.NewX(key)
	if err != nil {
		panic(err)
	}
	if len(ciphertext) < xchacha20poly1305Overhead {
		e := errors.Errorf("ciphertext too short (len %d) "+
			"to encode nonce and MAC tag", len(ciphertext))
		return nil, errors.E(errors.Crypto, e)
	}
	nonce := ciphertext[:xchacha20NonceSize]
	ciphertext = ciphertext[xchacha20NonceSize:]
	plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, errors.E(errors.Passphrase)
	}
	return plaintext, nil
}
type AccountProperties = struct {
	AccountNumber             uint32
	AccountName               string
	LastUsedExternalIndex     uint32
	LastUsedInternalIndex     uint32
	LastReturnedExternalIndex uint32
	LastReturnedInternalIndex uint32
	ImportedKeyCount          uint32
	AccountEncrypted          bool
	AccountUnlocked           bool
}
func defaultNewSecretKey(passphrase *[]byte, config *scryptOptions) (*snacl.SecretKey, error) {
	return snacl.NewSecretKey(passphrase, config.N, config.R, config.P)
}
var newSecretKey = defaultNewSecretKey
type EncryptorDecryptor interface {
	Encrypt(in []byte) ([]byte, error)
	Decrypt(in []byte) ([]byte, error)
	Bytes() []byte
	CopyBytes([]byte)
	Zero()
}
type cryptoKey struct {
	snacl.CryptoKey
}
func (ck *cryptoKey) Bytes() []byte {
	return ck.CryptoKey[:]
}
func (ck *cryptoKey) CopyBytes(from []byte) {
	copy(ck.CryptoKey[:], from)
}
func defaultNewCryptoKey() (EncryptorDecryptor, error) {
	key, err := snacl.GenerateCryptoKey()
	if err != nil {
		return nil, err
	}
	return &cryptoKey{*key}, nil
}
type CryptoKeyType byte
const (
	CKTPrivate CryptoKeyType = iota
	_  
	CKTPublic
)
var newCryptoKey = defaultNewCryptoKey
type Manager struct {
	mtx sync.RWMutex
	chainParams  *chaincfg.Params
	watchingOnly bool
	locked       bool
	closed       bool
	acctInfo map[uint32]*accountInfo
	masterKeyPub  *snacl.SecretKey
	masterKeyPriv *snacl.SecretKey
	cryptoKeyPub EncryptorDecryptor
	cryptoKeyPrivEncrypted []byte
	cryptoKeyPriv          EncryptorDecryptor
	privPassphraseHasher   hash.Hash
	privPassphraseHasherMu sync.Mutex  
	privPassphraseHash     []byte      
}
func zero(b []byte) {
	for i := range b {
		b[i] = 0
	}
}
func (m *Manager) lock() {
	for _, acctInfo := range m.acctInfo {
		if acctInfo.acctKeyPriv != nil {
			acctInfo.acctKeyPriv.Zero()
		}
		acctInfo.acctKeyPriv = nil
	}
	m.cryptoKeyPriv.Zero()
	m.masterKeyPriv.Zero()
	m.locked = true
	m.privPassphraseHash = nil
}
func (m *Manager) zeroSensitivePublicData() {
	for _, acctInfo := range m.acctInfo {
		acctInfo.acctKeyPub.Zero()
		acctInfo.acctKeyPub = nil
	}
	m.cryptoKeyPub.Zero()
	m.masterKeyPub.Zero()
}
func (m *Manager) WatchingOnly() bool {
	return m.watchingOnly
}
func (m *Manager) Close() error {
	m.mtx.Lock()
	defer m.mtx.Unlock()
	if !m.watchingOnly && !m.locked {
		m.lock()
	}
	m.zeroSensitivePublicData()
	m.closed = true
	return nil
}
func (m *Manager) keyToManaged(pubKey []byte, account, branch, index uint32) (ManagedAddress, error) {
	ma, err := newManagedAddressWithoutPrivKey(m, account, pubKey)
	if err != nil {
		return nil, err
	}
	if branch == InternalBranch {
		ma.internal = true
	}
	ma.index = index
	return ma, nil
}
func deriveKey(acctInfo *accountInfo, branch, index uint32, private bool) (*hdkeychain.ExtendedKey, error) {
	acctKey := acctInfo.acctKeyPub
	if private {
		if acctInfo.acctKeyPriv == nil {
			if acctInfo.uniqueKey != nil {
				return nil, errors.E(errors.Locked,
					"account with unique passphrase is locked")
			}
			if len(acctInfo.acctKeyEncrypted) != 0 {
				return nil, errors.E(errors.Locked,
					"private key %s/%d/%d is locked",
					acctInfo.acctName, branch, index)
			}
			return nil, errors.Errorf("no private key for %s/%d/%d",
				acctInfo.acctName, branch, index)
		}
		acctKey = acctInfo.acctKeyPriv
	}
	branchKey, err := acctKey.Child(branch)
	if err != nil {
		return nil, err
	}
	addressKey, err := branchKey.Child(index)
	branchKey.Zero()  
	return addressKey, err
}
func (m *Manager) loadAccountInfo(ns walletdb.ReadBucket, account uint32) (*accountInfo, error) {
	if acctInfo, ok := m.acctInfo[account]; ok {
		return acctInfo, nil
	}
	row, err := fetchDBAccount(ns, account, DBVersion)
	if err != nil {
		return nil, err
	}
	acctInfo := new(accountInfo)
	switch row := row.(type) {
	case *dbBIP0044Account:
		serializedKeyPub, err := m.cryptoKeyPub.Decrypt(row.pubKeyEncrypted)
		if err != nil {
			err := errors.Errorf("decrypt account %d pubkey: %v", account, err)
			return nil, errors.E(errors.Crypto, err)
		}
		acctKeyPub, err := hdkeychain.NewKeyFromString(string(serializedKeyPub), m.chainParams)
		if err != nil {
			return nil, errors.E(errors.IO, err)
		}
		acctInfo.acctName = row.name
		acctInfo.acctKeyEncrypted = row.privKeyEncrypted
		acctInfo.acctKeyPub = acctKeyPub
		acctInfo.uniqueKey = row.uniqueKey
		if acctInfo.uniqueKey != nil {  
			hashKey := make([]byte, 32)
			_, err = io.ReadFull(rand.Reader, hashKey)
			if err != nil {
				return nil, errors.E(errors.IO, err)
			}
			hasher, err := blake2b.New256(hashKey)
			if err != nil {
				return nil, errors.E(errors.IO, err)
			}
			acctInfo.uniquePassHasher = hasher
		}
	default:
		return nil, errors.Errorf("unknown account type %T", row)
	}
	if !m.locked && len(acctInfo.acctKeyEncrypted) != 0 && acctInfo.uniqueKey == nil {
		decrypted, err := m.cryptoKeyPriv.Decrypt(acctInfo.acctKeyEncrypted)
		if err != nil {
			return nil, errors.E(errors.Crypto, errors.Errorf("decrypt account %d privkey: %v", account, err))
		}
		acctKeyPriv, err := hdkeychain.NewKeyFromString(string(decrypted), m.chainParams)
		if err != nil {
			return nil, errors.E(errors.IO, err)
		}
		acctInfo.acctKeyPriv = acctKeyPriv
	}
	m.acctInfo[account] = acctInfo
	return acctInfo, nil
}
func (m *Manager) AccountProperties(ns walletdb.ReadBucket, account uint32) (*AccountProperties, error) {
	defer m.mtx.RUnlock()
	m.mtx.RLock()
	props := &AccountProperties{AccountNumber: account}
	if account != ImportedAddrAccount {
		acctInfo, err := m.loadAccountInfo(ns, account)
		if err != nil {
			return nil, err
		}
		props.AccountName = acctInfo.acctName
		a, err := fetchDBAccount(ns, account, DBVersion)
		if err != nil {
			return nil, errors.E(errors.IO, err)
		}
		switch a := a.(type) {
		case *dbBIP0044Account:
			props.LastUsedExternalIndex = a.lastUsedExternalIndex
			props.LastUsedInternalIndex = a.lastUsedInternalIndex
			props.LastReturnedExternalIndex = a.lastReturnedExternalIndex
			props.LastReturnedInternalIndex = a.lastReturnedInternalIndex
		default:
			return nil, errors.Errorf("unknown account type %T", a)
		}
	} else {
		props.AccountName = ImportedAddrAccountName  
		var importedKeyCount uint32
		count := func(interface{}) error {
			importedKeyCount++
			return nil
		}
		err := forEachAccountAddress(ns, ImportedAddrAccount, count)
		if err != nil {
			return nil, err
		}
		props.ImportedKeyCount = importedKeyCount
	}
	props.AccountEncrypted, props.AccountUnlocked = m.accountHasPassphrase(ns, account)
	return props, nil
}
func (m *Manager) AccountExtendedPubKey(dbtx walletdb.ReadTx, account uint32) (*hdkeychain.ExtendedKey, error) {
	ns := dbtx.ReadBucket(waddrmgrBucketKey)
	if account == ImportedAddrAccount {
		return nil, errors.E(errors.Invalid, "imported account has no extended pubkey")
	}
	m.mtx.Lock()
	acctInfo, err := m.loadAccountInfo(ns, account)
	m.mtx.Unlock()
	if err != nil {
		return nil, err
	}
	if acctInfo.acctKeyPub == nil && account > ImportedAddrAccount {
		return nil, errors.E(errors.Invalid, "hardened account xpub usage is forbidden")
	}
	return acctInfo.acctKeyPub, nil
}
func (m *Manager) AccountExtendedPrivKey(dbtx walletdb.ReadTx, account uint32) (*hdkeychain.ExtendedKey, error) {
	if account == ImportedAddrAccount {
		return nil, errors.E(errors.Invalid, "imported address account has no extended privkey")
	}
	ns := dbtx.ReadBucket(waddrmgrBucketKey)
	defer m.mtx.Unlock()
	m.mtx.Lock()
	acctInfo, err := m.loadAccountInfo(ns, account)
	if err != nil {
		return nil, err
	}
	if acctInfo.acctKeyPriv == nil && account > ImportedAddrAccount {
		return nil, errors.E(errors.Invalid, "imported xpub account has no extended privkey")
	}
	if acctInfo.acctKeyPriv == nil {
		return nil, errors.E(errors.Locked, "unable to access account extended privkey")
	}
	return acctInfo.acctKeyPriv, nil
}
func (m *Manager) AccountBranchExtendedPubKey(dbtx walletdb.ReadTx, account, branch uint32) (*hdkeychain.ExtendedKey, error) {
	acctXpub, err := m.AccountExtendedPubKey(dbtx, account)
	if err != nil {
		return nil, err
	}
	return acctXpub.Child(branch)
}
func (m *Manager) CoinTypePrivKey(dbtx walletdb.ReadTx) (*hdkeychain.ExtendedKey, error) {
	defer m.mtx.RUnlock()
	m.mtx.RLock()
	if m.locked {
		return nil, errors.E(errors.Locked)
	}
	if m.watchingOnly {
		return nil, errors.E(errors.WatchingOnly)
	}
	ns := dbtx.ReadBucket(waddrmgrBucketKey)
	_, coinTypePrivEnc, err := fetchCoinTypeKeys(ns)
	if err != nil {
		return nil, err
	}
	serializedKeyPriv, err := m.cryptoKeyPriv.Decrypt(coinTypePrivEnc)
	if err != nil {
		return nil, errors.E(errors.Crypto, errors.Errorf("decrypt cointype privkey: %v", err))
	}
	coinTypeKeyPriv, err := hdkeychain.NewKeyFromString(string(serializedKeyPriv), m.chainParams)
	zero(serializedKeyPriv)
	if err != nil {
		return nil, errors.E(errors.IO, err)
	}
	return coinTypeKeyPriv, nil
}