func (s *ScopedKeyManager) accountAddrType(acctInfo *accountInfo,
	internal bool) AddressType {
	addrSchema := s.addrSchema
	if acctInfo.addrSchema != nil {
		addrSchema = *acctInfo.addrSchema
	}
	if internal {
		return addrSchema.InternalAddrType
	}
	return addrSchema.ExternalAddrType
}
accountAddrType determines the type of address that should be generated for
an account based on whether it's an internal address or not.
package waddrmgr
import (
	"encoding/binary"
	"fmt"
	"sync"
	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/btcsuite/btcutil/hdkeychain"
	"github.com/btcsuite/btcwallet/internal/zero"
	"github.com/btcsuite/btcwallet/netparams"
	"github.com/btcsuite/btcwallet/walletdb"
	"github.com/lightninglabs/neutrino/cache/lru"
)
type HDVersion uint32
const (
	HDVersionMainNetBIP0044 HDVersion = 0x0488b21e  
	HDVersionMainNetBIP0049 HDVersion = 0x049d7cb2  
	HDVersionMainNetBIP0084 HDVersion = 0x04b24746  
	HDVersionTestNetBIP0044 HDVersion = 0x043587cf  
	HDVersionTestNetBIP0049 HDVersion = 0x044a5262  
	HDVersionTestNetBIP0084 HDVersion = 0x045f1cf6  
	HDVersionSimNetBIP0044 HDVersion = 0x0420bd3a  
)
const (
	defaultPrivKeyCacheSize = 10_000
)
type DerivationPath struct {
	InternalAccount uint32
	Account uint32
	Branch uint32
	Index uint32
	MasterKeyFingerprint uint32
}
type KeyScope struct {
	Purpose uint32
	Coin uint32
}
type ScopedIndex struct {
	Scope KeyScope
	Index uint32
}
func (k KeyScope) String() string {
	return fmt.Sprintf("m/%v'/%v'", k.Purpose, k.Coin)
}
type ScopeAddrSchema struct {
	ExternalAddrType AddressType
	InternalAddrType AddressType
}
var (
	KeyScopeBIP0049Plus = KeyScope{
		Purpose: 49,
		Coin:    0,
	}
	KeyScopeBIP0084 = KeyScope{
		Purpose: 84,
		Coin:    0,
	}
	KeyScopeBIP0044 = KeyScope{
		Purpose: 44,
		Coin:    0,
	}
	DefaultKeyScopes = []KeyScope{
		KeyScopeBIP0049Plus,
		KeyScopeBIP0084,
		KeyScopeBIP0044,
	}
	ScopeAddrMap = map[KeyScope]ScopeAddrSchema{
		KeyScopeBIP0049Plus: {
			ExternalAddrType: NestedWitnessPubKey,
			InternalAddrType: WitnessPubKey,
		},
		KeyScopeBIP0084: {
			ExternalAddrType: WitnessPubKey,
			InternalAddrType: WitnessPubKey,
		},
		KeyScopeBIP0044: {
			InternalAddrType: PubKeyHash,
			ExternalAddrType: PubKeyHash,
		},
	}
	KeyScopeBIP0049AddrSchema = ScopeAddrSchema{
		ExternalAddrType: NestedWitnessPubKey,
		InternalAddrType: NestedWitnessPubKey,
	}
	ImportedDerivationPath = DerivationPath{
		InternalAccount: ImportedAddrAccount,
	}
)
type ScopedKeyManager struct {
	scope KeyScope
	addrSchema ScopeAddrSchema
	rootManager *Manager
	addrs map[addrKey]ManagedAddress
	acctInfo map[uint32]*accountInfo
	deriveOnUnlock []*unlockDeriveInfo
	privKeyCache *lru.Cache
	mtx sync.RWMutex
}
func (s *ScopedKeyManager) Scope() KeyScope {
	return s.scope
}
func (s *ScopedKeyManager) AddrSchema() ScopeAddrSchema {
	return s.addrSchema
}
func (s *ScopedKeyManager) zeroSensitivePublicData() {
	for _, acctInfo := range s.acctInfo {
		acctInfo.acctKeyPub.Zero()
		acctInfo.acctKeyPub = nil
	}
}
func (s *ScopedKeyManager) Close() {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	s.zeroSensitivePublicData()
}
func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
	derivationPath DerivationPath, acctInfo *accountInfo) (
	ManagedAddress, error) {
	internal := derivationPath.Branch == InternalBranch
	addrType := s.accountAddrType(acctInfo, internal)
	ma, err := newManagedAddressFromExtKey(
		s, derivationPath, derivedKey, addrType,
	)
	defer derivedKey.Zero()
	if err != nil {
		return nil, err
	}
	if !derivedKey.IsPrivate() {
		info := unlockDeriveInfo{
			managedAddr: ma,
			branch:      derivationPath.Branch,
			index:       derivationPath.Index,
		}
		s.deriveOnUnlock = append(s.deriveOnUnlock, &info)
	}
	if derivationPath.Branch == InternalBranch {
		ma.internal = true
	}
	return ma, nil
}
func (s *ScopedKeyManager) deriveKey(acctInfo *accountInfo, branch,
	index uint32, private bool) (*hdkeychain.ExtendedKey, error) {
	acctKey := acctInfo.acctKeyPub
	if private {
		acctKey = acctInfo.acctKeyPriv
	}
	branchKey, err := acctKey.DeriveNonStandard(branch)  
	if err != nil {
		str := fmt.Sprintf("failed to derive extended key branch %d",
			branch)
		return nil, managerError(ErrKeyChain, str, err)
	}
	addressKey, err := branchKey.DeriveNonStandard(index)  
	branchKey.Zero()
	if err != nil {
		str := fmt.Sprintf("failed to derive child extended key -- "+
			"branch %d, child %d",
			branch, index)
		return nil, managerError(ErrKeyChain, str, err)
	}
	return addressKey, nil
}
func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
	account uint32) (*accountInfo, error) {
	if acctInfo, ok := s.acctInfo[account]; ok {
		return acctInfo, nil
	}
	rowInterface, err := fetchAccountInfo(ns, &s.scope, account)
	if err != nil {
		return nil, maybeConvertDbError(err)
	}
	decryptKey := func(cryptoKey EncryptorDecryptor,
		encryptedKey []byte) (*hdkeychain.ExtendedKey, error) {
		serializedKey, err := cryptoKey.Decrypt(encryptedKey)
		if err != nil {
			return nil, err
		}
		return hdkeychain.NewKeyFromString(string(serializedKey))
	}
	watchOnly := s.rootManager.watchOnly()
	hasPrivateKey := !s.rootManager.isLocked() && !watchOnly
	var acctInfo *accountInfo
	switch row := rowInterface.(type) {
	case *dbDefaultAccountRow:
		acctInfo = &accountInfo{
			acctName:          row.name,
			acctType:          row.acctType,
			acctKeyEncrypted:  row.privKeyEncrypted,
			nextExternalIndex: row.nextExternalIndex,
			nextInternalIndex: row.nextInternalIndex,
		}
		acctInfo.acctKeyPub, err = decryptKey(
			s.rootManager.cryptoKeyPub, row.pubKeyEncrypted,
		)
		if err != nil {
			str := fmt.Sprintf("failed to decrypt public key for "+
				"account %d", account)
			return nil, managerError(ErrCrypto, str, err)
		}
		if hasPrivateKey {
			acctInfo.acctKeyPriv, err = decryptKey(
				s.rootManager.cryptoKeyPriv, row.privKeyEncrypted,
			)
			if err != nil {
				str := fmt.Sprintf("failed to decrypt private "+
					"key for account %d", account)
				return nil, managerError(ErrCrypto, str, err)
			}
		}
	case *dbWatchOnlyAccountRow:
		acctInfo = &accountInfo{
			acctName:             row.name,
			acctType:             row.acctType,
			nextExternalIndex:    row.nextExternalIndex,
			nextInternalIndex:    row.nextInternalIndex,
			addrSchema:           row.addrSchema,
			masterKeyFingerprint: row.masterKeyFingerprint,
		}
		acctInfo.acctKeyPub, err = decryptKey(
			s.rootManager.cryptoKeyPub, row.pubKeyEncrypted,
		)
		if err != nil {
			str := fmt.Sprintf("failed to decrypt public key for "+
				"account %d", account)
			return nil, managerError(ErrCrypto, str, err)
		}
		hasPrivateKey = false
	default:
		str := fmt.Sprintf("unsupported account type %T", row)
		return nil, managerError(ErrDatabase, str, nil)
	}
	branch, index := ExternalBranch, acctInfo.nextExternalIndex
	if index > 0 {
		index--
	}
	lastExtAddrPath := DerivationPath{
		InternalAccount:      account,
		Account:              acctInfo.acctKeyPub.ChildIndex(),
		Branch:               branch,
		Index:                index,
		MasterKeyFingerprint: acctInfo.masterKeyFingerprint,
	}
	lastExtKey, err := s.deriveKey(acctInfo, branch, index, hasPrivateKey)
	if err != nil {
		return nil, err
	}
	lastExtAddr, err := s.keyToManaged(lastExtKey, lastExtAddrPath, acctInfo)
	if err != nil {
		return nil, err
	}
	acctInfo.lastExternalAddr = lastExtAddr
	branch, index = InternalBranch, acctInfo.nextInternalIndex
	if index > 0 {
		index--
	}
	lastIntAddrPath := DerivationPath{
		InternalAccount:      account,
		Account:              acctInfo.acctKeyPub.ChildIndex(),
		Branch:               branch,
		Index:                index,
		MasterKeyFingerprint: acctInfo.masterKeyFingerprint,
	}
	lastIntKey, err := s.deriveKey(acctInfo, branch, index, hasPrivateKey)
	if err != nil {
		return nil, err
	}
	lastIntAddr, err := s.keyToManaged(lastIntKey, lastIntAddrPath, acctInfo)
	if err != nil {
		return nil, err
	}
	acctInfo.lastInternalAddr = lastIntAddr
	s.acctInfo[account] = acctInfo
	return acctInfo, nil
}
func (s *ScopedKeyManager) AccountProperties(ns walletdb.ReadBucket,
	account uint32) (*AccountProperties, error) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	props := &AccountProperties{
		AccountNumber: account,
		KeyScope:      s.scope,
	}
	if account != ImportedAddrAccount {
		acctInfo, err := s.loadAccountInfo(ns, account)
		if err != nil {
			return nil, err
		}
		props.AccountName = acctInfo.acctName
		props.ExternalKeyCount = acctInfo.nextExternalIndex
		props.InternalKeyCount = acctInfo.nextInternalIndex
		props.AccountPubKey = acctInfo.acctKeyPub
		props.MasterKeyFingerprint = acctInfo.masterKeyFingerprint
		props.IsWatchOnly = s.rootManager.WatchOnly() ||
			acctInfo.acctKeyPriv == nil
		props.AddrSchema = acctInfo.addrSchema
		isDefaultKeyScope := false
		for _, scope := range DefaultKeyScopes {
			if s.scope == scope {
				isDefaultKeyScope = true
				break
			}
		}
		if acctInfo.acctType == accountDefault && isDefaultKeyScope {
			props.AccountPubKey, err = s.cloneKeyWithVersion(
				acctInfo.acctKeyPub,
			)
			if err != nil {
				return nil, fmt.Errorf("failed to retrieve "+
					"account public key: %v", err)
			}
		}
	} else {
		props.AccountName = ImportedAddrAccountName  
		props.IsWatchOnly = s.rootManager.WatchOnly()
		var importedKeyCount uint32
		count := func(interface{}) error {
			importedKeyCount++
			return nil
		}
		err := forEachAccountAddress(ns, &s.scope, ImportedAddrAccount, count)
		if err != nil {
			return nil, err
		}
		props.ImportedKeyCount = importedKeyCount
	}
	return props, nil
}
type cachedKey struct {
	key *btcec.PrivateKey
}
func (c *cachedKey) Size() (uint64, error) {
	return 1, nil
}
func (s *ScopedKeyManager) DeriveFromKeyPathCache(
	kp DerivationPath) (*btcec.PrivateKey, error) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	privKeyVal, err := s.privKeyCache.Get(kp)
	if err == nil {
		return privKeyVal.(*cachedKey).key, nil
	}
	acctInfo, ok := s.acctInfo[kp.InternalAccount]
	if !ok {
		return nil, managerError(
			ErrAccountNotCached,
			"", fmt.Errorf("acct %v not cached", kp.InternalAccount),
		)
	}
	watchOnly := s.rootManager.WatchOnly()
	private := !s.rootManager.IsLocked() && !watchOnly
	addrKey, err := s.deriveKey(acctInfo, kp.Branch, kp.Index, private)
	if err != nil {
		return nil, err
	}
	privKey, err := addrKey.ECPrivKey()
	if err != nil {
		return nil, err
	}
	_, err = s.privKeyCache.Put(kp, &cachedKey{key: privKey})
	if err != nil {
		return nil, err
	}
	return privKey, nil
}
func (s *ScopedKeyManager) DeriveFromKeyPath(ns walletdb.ReadBucket,
	kp DerivationPath) (ManagedAddress, error) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	watchOnly := s.rootManager.WatchOnly()
	private := !s.rootManager.IsLocked() && !watchOnly
	addrKey, _, _, err := s.deriveKeyFromPath(
		ns, kp.InternalAccount, kp.Branch, kp.Index, private,
	)
	if err != nil {
		return nil, err
	}
	acctInfo, err := s.loadAccountInfo(ns, kp.InternalAccount)
	if err != nil {
		return nil, err
	}
	return s.keyToManaged(addrKey, kp, acctInfo)
}
func (s *ScopedKeyManager) deriveKeyFromPath(ns walletdb.ReadBucket,
	internalAccount, branch, index uint32, private bool) (
	*hdkeychain.ExtendedKey, *hdkeychain.ExtendedKey, uint32, error) {
	acctInfo, err := s.loadAccountInfo(ns, internalAccount)
	if err != nil {
		return nil, nil, 0, err
	}
	private = private && acctInfo.acctKeyPriv != nil
	addrKey, err := s.deriveKey(acctInfo, branch, index, private)
	if err != nil {
		return nil, nil, 0, err
	}
	acctKey := acctInfo.acctKeyPub
	if private {
		acctKey = acctInfo.acctKeyPriv
	}
	return addrKey, acctKey, acctInfo.masterKeyFingerprint, nil
}
func (s *ScopedKeyManager) chainAddressRowToManaged(ns walletdb.ReadBucket,
	row *dbChainAddressRow) (ManagedAddress, error) {
	private := !s.rootManager.isLocked() && !s.rootManager.watchOnly()
	addressKey, acctKey, masterKeyFingerprint, err := s.deriveKeyFromPath(
		ns, row.account, row.branch, row.index, private,
	)
	if err != nil {
		return nil, err
	}
	acctInfo, err := s.loadAccountInfo(ns, row.account)
	if err != nil {
		return nil, err
	}
	return s.keyToManaged(
		addressKey, DerivationPath{
			InternalAccount:      row.account,
			Account:              acctKey.ChildIndex(),
			Branch:               row.branch,
			Index:                row.index,
			MasterKeyFingerprint: masterKeyFingerprint,
		}, acctInfo,
	)
}
func (s *ScopedKeyManager) importedAddressRowToManaged(row *dbImportedAddressRow) (ManagedAddress, error) {
	pubBytes, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedPubKey)
	if err != nil {
		str := "failed to decrypt public key for imported address"
		return nil, managerError(ErrCrypto, str, err)
	}
	pubKey, err := btcec.ParsePubKey(pubBytes, btcec.S256())
	if err != nil {
		str := "invalid public key for imported address"
		return nil, managerError(ErrCrypto, str, err)
	}
	compressed := len(pubBytes) == btcec.PubKeyBytesLenCompressed
	ma, err := newManagedAddressWithoutPrivKey(
		s, ImportedDerivationPath, pubKey, compressed,
		s.addrSchema.ExternalAddrType,
	)
	if err != nil {
		return nil, err
	}
	ma.privKeyEncrypted = row.encryptedPrivKey
	ma.imported = true
	return ma, nil
}
func (s *ScopedKeyManager) scriptAddressRowToManaged(row *dbScriptAddressRow) (ManagedAddress, error) {
	scriptHash, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedHash)
	if err != nil {
		str := "failed to decrypt imported script hash"
		return nil, managerError(ErrCrypto, str, err)
	}
	return newScriptAddress(s, row.account, scriptHash, row.encryptedScript)
}
func (s *ScopedKeyManager) rowInterfaceToManaged(ns walletdb.ReadBucket,
	rowInterface interface{}) (ManagedAddress, error) {
	switch row := rowInterface.(type) {
	case *dbChainAddressRow:
		return s.chainAddressRowToManaged(ns, row)
	case *dbImportedAddressRow:
		return s.importedAddressRowToManaged(row)
	case *dbScriptAddressRow:
		return s.scriptAddressRowToManaged(row)
	}
	str := fmt.Sprintf("unsupported address type %T", rowInterface)
	return nil, managerError(ErrDatabase, str, nil)
}
func (s *ScopedKeyManager) loadAndCacheAddress(ns walletdb.ReadBucket,
	address btcutil.Address) (ManagedAddress, error) {
	rowInterface, err := fetchAddress(ns, &s.scope, address.ScriptAddress())
	if err != nil {
		if merr, ok := err.(*ManagerError); ok {
			desc := fmt.Sprintf("failed to fetch address '%s': %v",
				address.ScriptAddress(), merr.Description)
			merr.Description = desc
			return nil, merr
		}
		return nil, maybeConvertDbError(err)
	}
	managedAddr, err := s.rowInterfaceToManaged(ns, rowInterface)
	if err != nil {
		return nil, err
	}
	s.addrs[addrKey(managedAddr.Address().ScriptAddress())] = managedAddr
	return managedAddr, nil
}
func (s *ScopedKeyManager) existsAddress(ns walletdb.ReadBucket, addressID []byte) bool {
	if _, ok := s.addrs[addrKey(addressID)]; ok {
		return true
	}
	return existsAddress(ns, &s.scope, addressID)
}
func (s *ScopedKeyManager) Address(ns walletdb.ReadBucket,
	address btcutil.Address) (ManagedAddress, error) {
	if pka, ok := address.(*btcutil.AddressPubKey); ok {
		address = pka.AddressPubKeyHash()
	}
	s.mtx.RLock()
	if ma, ok := s.addrs[addrKey(address.ScriptAddress())]; ok {
		s.mtx.RUnlock()
		return ma, nil
	}
	s.mtx.RUnlock()
	s.mtx.Lock()
	defer s.mtx.Unlock()
	return s.loadAndCacheAddress(ns, address)
}
func (s *ScopedKeyManager) AddrAccount(ns walletdb.ReadBucket,
	address btcutil.Address) (uint32, error) {
	account, err := fetchAddrAccount(ns, &s.scope, address.ScriptAddress())
	if err != nil {
		return 0, maybeConvertDbError(err)
	}
	return account, nil
}