lbcwallet/waddrmgr/scoped_manager.go

2482 lines
79 KiB
Go
Raw Normal View History

package waddrmgr
import (
2021-12-15 01:54:55 +01:00
"container/list"
"crypto/sha256"
"encoding/binary"
"fmt"
"sync"
"github.com/lbryio/lbcd/btcec"
"github.com/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/txscript"
"github.com/lbryio/lbcd/wire"
btcutil "github.com/lbryio/lbcutil"
"github.com/lbryio/lbcutil/hdkeychain"
"github.com/lbryio/lbcwallet/internal/zero"
"github.com/lbryio/lbcwallet/netparams"
"github.com/lbryio/lbcwallet/walletdb"
)
// HDVersion represents the different supported schemes of hierarchical
// derivation.
type HDVersion uint32
const (
// HDVersionMainNetBIP0044 is the HDVersion for BIP-0044 on the main
// network.
HDVersionMainNetBIP0044 HDVersion = 0x0488b21e // xpub
// HDVersionMainNetBIP0049 is the HDVersion for BIP-0049 on the main
// network.
HDVersionMainNetBIP0049 HDVersion = 0x049d7cb2 // ypub
// HDVersionMainNetBIP0084 is the HDVersion for BIP-0084 on the main
// network.
HDVersionMainNetBIP0084 HDVersion = 0x04b24746 // zpub
// HDVersionTestNetBIP0044 is the HDVersion for BIP-0044 on the test
// network.
HDVersionTestNetBIP0044 HDVersion = 0x043587cf // tpub
// HDVersionTestNetBIP0049 is the HDVersion for BIP-0049 on the test
// network.
HDVersionTestNetBIP0049 HDVersion = 0x044a5262 // upub
// HDVersionTestNetBIP0084 is the HDVersion for BIP-0084 on the test
// network.
HDVersionTestNetBIP0084 HDVersion = 0x045f1cf6 // vpub
// HDVersionSimNetBIP0044 is the HDVersion for BIP-0044 on the
// simulation test network. There aren't any other versions defined for
// the simulation test network.
HDVersionSimNetBIP0044 HDVersion = 0x0420bd3a // spub
)
const (
// privKeyCacheSize is the default size of the LRU cache that we'll use
// to cache private keys to avoid DB and EC operations within the
// wallet. With the default sisize, we'll allocate up to 320 KB to
// caching private keys (ignoring pointer overhead, etc).
defaultPrivKeyCacheSize = 10_000
)
// DerivationPath represents a derivation path from a particular key manager's
// scope. Each ScopedKeyManager starts key derivation from the end of their
// cointype hardened key: m/purpose'/cointype'. The fields in this struct allow
// further derivation to the next three child levels after the coin type key.
// This restriction is in the spriti of BIP0044 type derivation. We maintain a
// degree of coherency with the standard, but allow arbitrary derivations
// beyond the cointype key. The key derived using this path will be exactly:
// m/purpose'/cointype'/account/branch/index, where purpose' and cointype' are
// bound by the scope of a particular manager.
type DerivationPath struct {
// InternalAccount is the internal account number used within the
// wallet's database to identify accounts.
InternalAccount uint32
// Account is the account, or the first immediate child from the scoped
// manager's hardened coin type key.
Account uint32
// Branch is the branch to be derived from the account index above. For
// BIP0044-like derivation, this is either 0 (external) or 1
// (internal). However, we allow this value to vary arbitrarily within
// its size range.
Branch uint32
// Index is the final child in the derivation path. This denotes the
// key index within as a child of the account and branch.
Index uint32
// MasterKeyFingerprint represents the fingerprint of the root key (also
// known as the key with derivation path m/) corresponding to the
// account public key. This may be required by some hardware wallets for
// proper identification and signing.
MasterKeyFingerprint uint32
}
// KeyScope represents a restricted key scope from the primary root key within
// the HD chain. From the root manager (m/) we can create a nearly arbitrary
// number of ScopedKeyManagers of key derivation path: m/purpose'/cointype'.
// These scoped managers can then me managed indecently, as they house the
// encrypted cointype key and can derive any child keys from there on.
type KeyScope struct {
// Purpose is the purpose of this key scope. This is the first child of
// the master HD key.
Purpose uint32
// Coin is a value that represents the particular coin which is the
// child of the purpose key. With this key, any accounts, or other
// children can be derived at all.
Coin uint32
}
// ScopedIndex is a tuple of KeyScope and child Index. This is used to compactly
// identify a particular child key, when the account and branch can be inferred
// from context.
type ScopedIndex struct {
// Scope is the BIP44 account' used to derive the child key.
Scope KeyScope
// Index is the BIP44 address_index used to derive the child key.
Index uint32
}
// String returns a human readable version describing the keypath encapsulated
// by the target key scope.
func (k KeyScope) String() string {
return fmt.Sprintf("m/%v'/%v'", k.Purpose, k.Coin)
}
// ScopeAddrSchema is the address schema of a particular KeyScope. This will be
// persisted within the database, and will be consulted when deriving any keys
// for a particular scope to know how to encode the public keys as addresses.
type ScopeAddrSchema struct {
// ExternalAddrType is the address type for all keys within branch 0.
ExternalAddrType AddressType
// InternalAddrType is the address type for all keys within branch 1
// (change addresses).
InternalAddrType AddressType
}
var (
// KeyScopeBIP0049Plus is the key scope of our modified BIP0049
// derivation. We say this is BIP0049 "plus", as we'll actually use
// p2wkh change all change addresses.
KeyScopeBIP0049Plus = KeyScope{
Purpose: 49,
2021-09-10 07:21:23 +02:00
Coin: 140,
}
// KeyScopeBIP0084 is the key scope for BIP0084 derivation. BIP0084
// will be used to derive all p2wkh addresses.
KeyScopeBIP0084 = KeyScope{
Purpose: 84,
2021-09-10 07:21:23 +02:00
Coin: 140,
}
// KeyScopeBIP0044 is the key scope for BIP0044 derivation. Legacy
// wallets will only be able to use this key scope, and no keys beyond
// it.
KeyScopeBIP0044 = KeyScope{
Purpose: 44,
2021-09-10 07:21:23 +02:00
Coin: 140,
}
// DefaultKeyScopes is the set of default key scopes that will be
// created by the root manager upon initial creation.
DefaultKeyScopes = []KeyScope{
KeyScopeBIP0049Plus,
KeyScopeBIP0084,
KeyScopeBIP0044,
}
// ScopeAddrMap is a map from the default key scopes to the scope
// address schema for each scope type. This will be consulted during
// the initial creation of the root key manager.
ScopeAddrMap = map[KeyScope]ScopeAddrSchema{
KeyScopeBIP0049Plus: {
ExternalAddrType: NestedWitnessPubKey,
InternalAddrType: WitnessPubKey,
},
KeyScopeBIP0084: {
ExternalAddrType: WitnessPubKey,
InternalAddrType: WitnessPubKey,
},
KeyScopeBIP0044: {
InternalAddrType: PubKeyHash,
ExternalAddrType: PubKeyHash,
},
}
// KeyScopeBIP0049AddrSchema is the address schema for the traditional
// BIP-0049 derivation scheme. This exists in order to support importing
// accounts from other wallets that don't use our modified BIP-0049
// derivation scheme (internal addresses are P2WKH instead of NP2WKH).
KeyScopeBIP0049AddrSchema = ScopeAddrSchema{
ExternalAddrType: NestedWitnessPubKey,
InternalAddrType: NestedWitnessPubKey,
}
// ImportedDerivationPath is the derivation path for an imported
// address. The Account, Branch, and Index members are not known, so
// they are left blank.
ImportedDerivationPath = DerivationPath{
InternalAccount: ImportedAddrAccount,
}
)
// ScopedKeyManager is a sub key manager under the main root key manager. The
// root key manager will handle the root HD key (m/), while each sub scoped key
// manager will handle the cointype key for a particular key scope
// (m/purpose'/cointype'). This abstraction allows higher-level applications
// built upon the root key manager to perform their own arbitrary key
// derivation, while still being protected under the encryption of the root key
// manager.
type ScopedKeyManager struct {
// scope is the scope of this key manager. We can only generate keys
// that are direct children of this scope.
scope KeyScope
// addrSchema is the address schema for this sub manager. This will be
// consulted when encoding addresses from derived keys.
addrSchema ScopeAddrSchema
// rootManager is a pointer to the root key manager. We'll maintain
// this as we need access to the crypto encryption keys before we can
// derive any new accounts of child keys of accounts.
rootManager *Manager
// addrs is a cached map of all the addresses that we currently
// manage.
addrs map[addrKey]ManagedAddress
// acctInfo houses information about accounts including what is needed
// to generate deterministic chained keys for each created account.
acctInfo map[uint32]*accountInfo
// deriveOnUnlock is a list of private keys which needs to be derived
// on the next unlock. This occurs when a public address is derived
// while the address manager is locked since it does not have access to
// the private extended key (hence nor the underlying private key) in
// order to encrypt it.
deriveOnUnlock []*unlockDeriveInfo
// privKeyCache stores the set of private keys that have been marked as
// items to be cached to allow us to avoid the database and EC
// operations each time a key need to be obtained.
2021-12-15 01:54:55 +01:00
privKeyCache map[DerivationPath]*list.Element
privKeyLru *list.List
mtx sync.RWMutex
}
// Scope returns the exact KeyScope of this scoped key manager.
func (s *ScopedKeyManager) Scope() KeyScope {
return s.scope
}
// AddrSchema returns the set address schema for the target ScopedKeyManager.
func (s *ScopedKeyManager) AddrSchema() ScopeAddrSchema {
return s.addrSchema
}
// zeroSensitivePublicData performs a best try effort to remove and zero all
// sensitive public data associated with the address manager such as
// hierarchical deterministic extended public keys and the crypto public keys.
func (s *ScopedKeyManager) zeroSensitivePublicData() {
// Clear all of the account private keys.
for _, acctInfo := range s.acctInfo {
acctInfo.acctKeyPub.Zero()
acctInfo.acctKeyPub = nil
}
}
// Close cleanly shuts down the manager. It makes a best try effort to remove
// and zero all private key and sensitive public key material associated with
// the address manager from memory.
func (s *ScopedKeyManager) Close() {
s.mtx.Lock()
defer s.mtx.Unlock()
// Attempt to clear sensitive public key material from memory too.
s.zeroSensitivePublicData()
}
// keyToManaged returns a new managed address for the provided derived key and
// its derivation path which consists of the account, branch, and index.
//
// The passed derivedKey is zeroed after the new address is created.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
derivationPath DerivationPath, acctInfo *accountInfo) (
ManagedAddress, error) {
// Choose the appropriate type of address to derive since it's possible
// for a watch-only account to have a different schema from the
// manager's.
internal := derivationPath.Branch == InternalBranch
addrType := s.accountAddrType(acctInfo, internal)
// Create a new managed address based on the public or private key
// depending on whether the passed key is private. Also, zero the key
// after creating the managed address from it.
ma, err := newManagedAddressFromExtKey(
s, derivationPath, derivedKey, addrType,
)
defer derivedKey.Zero()
if err != nil {
return nil, err
}
if !derivedKey.IsPrivate() {
// Add the managed address to the list of addresses that need
// their private keys derived when the address manager is next
// unlocked.
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
}
// deriveKey returns either a public or private derived extended key based on
// the private flag for the given an account info, branch, and index.
func (s *ScopedKeyManager) deriveKey(acctInfo *accountInfo, branch,
index uint32, private bool) (*hdkeychain.ExtendedKey, error) {
// Choose the public or private extended key based on whether or not
// the private flag was specified. This, in turn, allows for public or
// private child derivation.
acctKey := acctInfo.acctKeyPub
if private {
acctKey = acctInfo.acctKeyPriv
}
// Derive and return the key.
2021-03-24 14:43:24 +01:00
branchKey, err := acctKey.DeriveNonStandard(branch) // nolint:staticcheck
if err != nil {
str := fmt.Sprintf("failed to derive extended key branch %d",
branch)
return nil, managerError(ErrKeyChain, str, err)
}
2021-03-24 14:43:24 +01:00
addressKey, err := branchKey.DeriveNonStandard(index) // nolint:staticcheck
// Zero branch key after it's used.
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
}
// loadAccountInfo attempts to load and cache information about the given
// account from the database. This includes what is necessary to derive new
// keys for it and track the state of the internal and external branches.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
account uint32) (*accountInfo, error) {
// Return the account info from cache if it's available.
if acctInfo, ok := s.acctInfo[account]; ok {
return acctInfo, nil
}
// The account is either invalid or just wasn't cached, so attempt to
// load the information from the database.
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))
}
// The wallet will only contain private keys for default accounts if the
// wallet's not set up as watch-only and it's been unlocked.
watchOnly := s.rootManager.watchOnly()
hasPrivateKey := !s.rootManager.isLocked() && !watchOnly
// Create the new account info with the known information. The rest of
// the fields are filled out below.
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,
}
// Use the crypto public key to decrypt the account public
// extended key.
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 {
// Use the crypto private key to decrypt the account
// private extended keys.
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,
}
// Use the crypto public key to decrypt the account public
// extended key.
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)
}
// Derive and cache the managed address for the last external address.
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
// Derive and cache the managed address for the last internal address.
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
// Add it to the cache and return it when everything is successful.
s.acctInfo[account] = acctInfo
return acctInfo, nil
}
// AccountProperties returns properties associated with the account, such as
// the account number, name, and the number of derived and imported keys.
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,
}
// Until keys can be imported into any account, special handling is
// required for the imported account.
//
// loadAccountInfo errors when using it on the imported account since
// the accountInfo struct is filled with a BIP0044 account's extended
// keys, and the imported accounts has none.
//
// Since only the imported account allows imports currently, the number
// of imported keys for any other account is zero, and since the
// imported account cannot contain non-imported keys, the external and
// internal key counts for it are zero.
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
// Export the account public key with the correct version
// corresponding to the manager's key scope for non-watch-only
// accounts. This isn't done for watch-only accounts to maintain
// the account public key consistent with what the caller
// provided. Note that his is only done for the default key
// scopes, as we only know the HD versions for those.
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 // reserved, nonchangable
props.IsWatchOnly = s.rootManager.WatchOnly()
// Could be more efficient if this was tracked by the db.
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
}
// cachedKey is an entry within the LRU map that stores private keys that are
// to be used frequently. We use this wrapper struct to be able too report the
// size of a given element to the cache.
type cachedKey struct {
2021-12-15 01:54:55 +01:00
path DerivationPath
key *btcec.PrivateKey
}
// Size returns the size of this element. Rather than have the cache limit
// based on bytes, we simply report that each element is of size 1, meaning we
// can set our cached based on the amount of keys we want to store, rather than
// the total size of all the keys.
func (c *cachedKey) Size() (uint64, error) {
return 1, nil
}
// DeriveFromKeyPathCache is identical to DeriveFromKeyPath, however it'll fail
// if the account refracted in the DerivationPath isn't already in the
// in-memory cache. Callers looking for faster private key retrieval can opt to
// call this method, which may fail if things aren't in the cache, then fall
// back to the normal variant. The account can information can be drawn into
// the cache if the normal DeriveFromKeyPath method is used, or the account is
// looked up via any other means.
func (s *ScopedKeyManager) DeriveFromKeyPathCache(
kp DerivationPath) (*btcec.PrivateKey, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
// First, try to look up the key itself in the proper cache, if the key
// is here, then we don't need to do anything further.
2021-12-15 01:54:55 +01:00
element := s.privKeyCache[kp]
if element != nil {
privKeyVal := element.Value.(*cachedKey)
s.privKeyLru.MoveToFront(element)
return privKeyVal.key, nil
}
// If the key isn't already in the cache, then we'll try to look up the
// account info in the cache, if this fails, then we exit here as we
// can't move forward without creating a DB transaction, and the point
// of this method is to avoid that.
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
// Now that we have the account information, we can derive the key
// directly.
addrKey, err := s.deriveKey(acctInfo, kp.Branch, kp.Index, private)
if err != nil {
return nil, err
}
// Now that we have the key, we'll attempt to insert it into the cache,
// and return it as is.
privKey, err := addrKey.ECPrivKey()
if err != nil {
return nil, err
}
2021-12-15 01:54:55 +01:00
if s.privKeyLru.Len() >= defaultPrivKeyCacheSize {
element = s.privKeyLru.Back()
delete(s.privKeyCache, element.Value.(*cachedKey).path)
element.Value = &cachedKey{key: privKey, path: kp}
s.privKeyLru.MoveToFront(element)
} else {
s.privKeyLru.PushFront(&cachedKey{key: privKey, path: kp})
}
2021-12-15 01:54:55 +01:00
s.privKeyCache[kp] = element
return privKey, nil
}
// DeriveFromKeyPath attempts to derive a maximal child key (under the BIP0044
// scheme) from a given key path. If key derivation isn't possible, then an
// error will be returned.
//
// NOTE: The key will be derived from the account stored in the database under
// the InternalAccount number.
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)
}
// deriveKeyFromPath returns either a public or private derived extended key
// based on the private flag for an address given an account, branch, and index.
// The account master key is also returned.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) deriveKeyFromPath(ns walletdb.ReadBucket,
internalAccount, branch, index uint32, private bool) (
*hdkeychain.ExtendedKey, *hdkeychain.ExtendedKey, uint32, error) {
// Look up the account key information.
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
}
// chainAddressRowToManaged returns a new managed address based on chained
// address data loaded from the database.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) chainAddressRowToManaged(ns walletdb.ReadBucket,
row *dbChainAddressRow) (ManagedAddress, error) {
// Since the manger's mutex is assumed to held when invoking this
// function, we use the internal isLocked to avoid a deadlock.
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,
)
}
// importedAddressRowToManaged returns a new managed address based on imported
// address data loaded from the database.
func (s *ScopedKeyManager) importedAddressRowToManaged(row *dbImportedAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported public key.
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)
}
// TODO: Handle imported key being part of internal branch.
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
}
// scriptAddressRowToManaged returns a new managed address based on script
// address data loaded from the database.
func (s *ScopedKeyManager) scriptAddressRowToManaged(
row *dbScriptAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported script hash.
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)
}
// witnessScriptAddressRowToManaged returns a new managed address based on
// witness script address data loaded from the database.
func (s *ScopedKeyManager) witnessScriptAddressRowToManaged(
row *dbWitnessScriptAddressRow) (ManagedAddress, error) {
// Use the crypto public key to decrypt the imported script hash.
scriptHash, err := s.rootManager.cryptoKeyPub.Decrypt(row.encryptedHash)
if err != nil {
str := "failed to decrypt imported witness script hash"
return nil, managerError(ErrCrypto, str, err)
}
return newWitnessScriptAddress(
s, row.account, scriptHash, row.encryptedScript,
row.witnessVersion, row.isSecretScript,
)
}
// rowInterfaceToManaged returns a new managed address based on the given
// address data loaded from the database. It will automatically select the
// appropriate type.
//
// This function MUST be called with the manager lock held for writes.
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)
case *dbWitnessScriptAddressRow:
return s.witnessScriptAddressRowToManaged(row)
}
str := fmt.Sprintf("unsupported address type %T", rowInterface)
return nil, managerError(ErrDatabase, str, nil)
}
// loadAndCacheAddress attempts to load the passed address from the database
// and caches the associated managed address.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) loadAndCacheAddress(ns walletdb.ReadBucket,
address btcutil.Address) (ManagedAddress, error) {
// Attempt to load the raw address information from the database.
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)
}
// Create a new managed address for the specific type of address based
// on type.
managedAddr, err := s.rowInterfaceToManaged(ns, rowInterface)
if err != nil {
return nil, err
}
// Cache and return the new managed address.
s.addrs[addrKey(managedAddr.Address().ScriptAddress())] = managedAddr
return managedAddr, nil
}
// existsAddress returns whether or not the passed address is known to the
// address manager.
//
// This function MUST be called with the manager lock held for reads.
func (s *ScopedKeyManager) existsAddress(ns walletdb.ReadBucket, addressID []byte) bool {
// Check the in-memory map first since it's faster than a db access.
if _, ok := s.addrs[addrKey(addressID)]; ok {
return true
}
// Check the database if not already found above.
return existsAddress(ns, &s.scope, addressID)
}
// Address returns a managed address given the passed address if it is known to
// the address manager. A managed address differs from the passed address in
// that it also potentially contains extra information needed to sign
// transactions such as the associated private key for pay-to-pubkey and
// pay-to-pubkey-hash addresses and the script associated with
// pay-to-script-hash addresses.
func (s *ScopedKeyManager) Address(ns walletdb.ReadBucket,
address btcutil.Address) (ManagedAddress, error) {
// ScriptAddress will only return a script hash if we're accessing an
// address that is either PKH or SH. In the event we're passed a PK
// address, convert the PK to PKH address so that we can access it from
// the addrs map and database.
if pka, ok := address.(*btcutil.AddressPubKey); ok {
address = pka.AddressPubKeyHash()
}
// Return the address from cache if it's available.
//
// NOTE: Not using a defer on the lock here since a write lock is
// needed if the lookup fails.
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()
// Attempt to load the address from the database.
return s.loadAndCacheAddress(ns, address)
}
// AddrAccount returns the account to which the given address belongs.
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
}
// accountAddrType determines the type of address that should be generated for
// an account based on whether it's an internal address or not.
func (s *ScopedKeyManager) accountAddrType(acctInfo *accountInfo,
internal bool) AddressType {
// If the account has a custom address schema, use it.
addrSchema := s.addrSchema
if acctInfo.addrSchema != nil {
addrSchema = *acctInfo.addrSchema
}
if internal {
return addrSchema.InternalAddrType
}
return addrSchema.ExternalAddrType
}
// nextAddresses returns the specified number of next chained address from the
// branch indicated by the internal flag.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
account uint32, numAddresses uint32, internal bool) ([]ManagedAddress, error) {
// The next address can only be generated for accounts that have
// already been created.
acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
return nil, err
}
// Choose the account key to used based on whether the address manager
// is locked.
acctKey := acctInfo.acctKeyPub
watchOnly := s.rootManager.WatchOnly() || len(acctInfo.acctKeyEncrypted) == 0
if !s.rootManager.IsLocked() && !watchOnly {
acctKey = acctInfo.acctKeyPriv
}
// Choose the branch key and index depending on whether or not this is
// an internal address.
branchNum, nextIndex := ExternalBranch, acctInfo.nextExternalIndex
if internal {
branchNum = InternalBranch
nextIndex = acctInfo.nextInternalIndex
}
// Choose the appropriate type of address to derive since it's possible
// for a watch-only account to have a different schema from the
// manager's.
addrType := s.accountAddrType(acctInfo, internal)
// Ensure the requested number of addresses doesn't exceed the maximum
// allowed for this account.
if numAddresses > MaxAddressesPerAccount || nextIndex+numAddresses >
MaxAddressesPerAccount {
str := fmt.Sprintf("%d new addresses would exceed the maximum "+
"allowed number of addresses per account of %d",
numAddresses, MaxAddressesPerAccount)
return nil, managerError(ErrTooManyAddresses, str, nil)
}
// Derive the appropriate branch key and ensure it is zeroed when done.
2021-03-24 14:43:24 +01:00
branchKey, err := acctKey.DeriveNonStandard(branchNum) // nolint:staticcheck
if err != nil {
str := fmt.Sprintf("failed to derive extended key branch %d",
branchNum)
return nil, managerError(ErrKeyChain, str, err)
}
defer branchKey.Zero() // Ensure branch key is zeroed when done.
// Create the requested number of addresses and keep track of the index
// with each one.
addressInfo := make([]*unlockDeriveInfo, 0, numAddresses)
for i := uint32(0); i < numAddresses; i++ {
// There is an extremely small chance that a particular child is
// invalid, so use a loop to derive the next valid child.
var nextKey *hdkeychain.ExtendedKey
for {
// Derive the next child in the external chain branch.
2021-03-24 14:43:24 +01:00
key, err := branchKey.DeriveNonStandard(nextIndex) // nolint:staticcheck
if err != nil {
// When this particular child is invalid, skip to the
// next index.
if err == hdkeychain.ErrInvalidChild {
nextIndex++
continue
}
str := fmt.Sprintf("failed to generate child %d",
nextIndex)
return nil, managerError(ErrKeyChain, str, err)
}
key.SetNet(s.rootManager.chainParams)
nextIndex++
nextKey = key
break
}
// Now that we know this key can be used, we'll create the
// proper derivation path so this information can be available
// to callers.
derivationPath := DerivationPath{
InternalAccount: account,
Account: acctKey.ChildIndex(),
Branch: branchNum,
Index: nextIndex - 1,
}
// Create a new managed address based on the public or private
// key depending on whether the generated key is private.
// Also, zero the next key after creating the managed address
// from it.
addr, err := newManagedAddressFromExtKey(
s, derivationPath, nextKey, addrType,
)
if err != nil {
return nil, err
}
if internal {
addr.internal = true
}
managedAddr := addr
nextKey.Zero()
info := unlockDeriveInfo{
managedAddr: managedAddr,
branch: branchNum,
index: nextIndex - 1,
}
addressInfo = append(addressInfo, &info)
}
// Now that all addresses have been successfully generated, update the
// database in a single transaction.
for _, info := range addressInfo {
ma := info.managedAddr
addressID := ma.Address().ScriptAddress()
switch a := ma.(type) {
case *managedAddress:
err := putChainedAddress(
ns, &s.scope, addressID, account, ssFull,
info.branch, info.index, adtChain,
)
if err != nil {
return nil, maybeConvertDbError(err)
}
case *scriptAddress:
encryptedHash, err := s.rootManager.cryptoKeyPub.Encrypt(a.AddrHash())
if err != nil {
str := fmt.Sprintf("failed to encrypt script hash %x",
a.AddrHash())
return nil, managerError(ErrCrypto, str, err)
}
err = putScriptAddress(
ns, &s.scope, a.AddrHash(), ImportedAddrAccount,
ssNone, encryptedHash, a.scriptEncrypted,
)
if err != nil {
return nil, maybeConvertDbError(err)
}
}
}
managedAddresses := make([]ManagedAddress, 0, len(addressInfo))
for _, info := range addressInfo {
ma := info.managedAddr
managedAddresses = append(managedAddresses, ma)
}
// Finally, create a closure that will update the next address tracking
// and add the addresses to the cache after the newly generated
// addresses have been successfully committed to the db.
onCommit := func() {
// Since this closure will be called when the DB transaction
// gets committed, we won't longer be holding the manager's
// mutex at that point. We must therefore re-acquire it before
// continuing.
s.mtx.Lock()
defer s.mtx.Unlock()
for _, info := range addressInfo {
ma := info.managedAddr
s.addrs[addrKey(ma.Address().ScriptAddress())] = ma
// Add the new managed address to the list of addresses
// that need their private keys derived when the
// address manager is next unlocked.
if s.rootManager.isLocked() && !watchOnly {
s.deriveOnUnlock = append(s.deriveOnUnlock, info)
}
}
// Set the last address and next address for tracking.
ma := addressInfo[len(addressInfo)-1].managedAddr
if internal {
acctInfo.nextInternalIndex = nextIndex
acctInfo.lastInternalAddr = ma
} else {
acctInfo.nextExternalIndex = nextIndex
acctInfo.lastExternalAddr = ma
}
}
ns.Tx().OnCommit(onCommit)
return managedAddresses, nil
}
// extendAddresses ensures that all addresses up to and including the lastIndex
// are derived for either an internal or external branch. If the child at
// lastIndex is invalid, this method will proceed until the next valid child is
// found. An error is returned if method failed to properly extend addresses
// up to the requested index.
//
// This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket,
account uint32, lastIndex uint32, internal bool) error {
// The next address can only be generated for accounts that have
// already been created.
acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
return err
}
// Choose the account key to used based on whether the address manager
// is locked.
acctKey := acctInfo.acctKeyPub
watchOnly := s.rootManager.WatchOnly() || acctInfo.acctKeyPriv != nil
if !s.rootManager.IsLocked() && !watchOnly {
acctKey = acctInfo.acctKeyPriv
}
// Choose the branch key and index depending on whether or not this is
// an internal address.
branchNum, nextIndex := ExternalBranch, acctInfo.nextExternalIndex
if internal {
branchNum = InternalBranch
nextIndex = acctInfo.nextInternalIndex
}
// Choose the appropriate type of address to derive since it's possible
// for a watch-only account to have a different schema from the
// manager's.
addrType := s.accountAddrType(acctInfo, internal)
// If the last index requested is already lower than the next index, we
// can return early.
if lastIndex < nextIndex {
return nil
}
// Ensure the requested number of addresses doesn't exceed the maximum
// allowed for this account.
if lastIndex > MaxAddressesPerAccount {
str := fmt.Sprintf("last index %d would exceed the maximum "+
"allowed number of addresses per account of %d",
lastIndex, MaxAddressesPerAccount)
return managerError(ErrTooManyAddresses, str, nil)
}
// Derive the appropriate branch key and ensure it is zeroed when done.
2021-03-24 14:43:24 +01:00
branchKey, err := acctKey.DeriveNonStandard(branchNum) // nolint:staticcheck
if err != nil {
str := fmt.Sprintf("failed to derive extended key branch %d",
branchNum)
return managerError(ErrKeyChain, str, err)
}
defer branchKey.Zero() // Ensure branch key is zeroed when done.
// Starting from this branch's nextIndex, derive all child indexes up to
// and including the requested lastIndex. If a invalid child is
// detected, this loop will continue deriving until it finds the next
// subsequent index.
addressInfo := make([]*unlockDeriveInfo, 0, lastIndex-nextIndex)
for nextIndex <= lastIndex {
// There is an extremely small chance that a particular child is
// invalid, so use a loop to derive the next valid child.
var nextKey *hdkeychain.ExtendedKey
for {
// Derive the next child in the external chain branch.
2021-03-24 14:43:24 +01:00
key, err := branchKey.DeriveNonStandard(nextIndex) // nolint:staticcheck
if err != nil {
// When this particular child is invalid, skip to the
// next index.
if err == hdkeychain.ErrInvalidChild {
nextIndex++
continue
}
str := fmt.Sprintf("failed to generate child %d",
nextIndex)
return managerError(ErrKeyChain, str, err)
}
key.SetNet(s.rootManager.chainParams)
nextIndex++
nextKey = key
break
}
// Now that we know this key can be used, we'll create the
// proper derivation path so this information can be available
// to callers.
derivationPath := DerivationPath{
InternalAccount: account,
Account: acctInfo.acctKeyPub.ChildIndex(),
Branch: branchNum,
Index: nextIndex - 1,
}
// Create a new managed address based on the public or private
// key depending on whether the generated key is private.
// Also, zero the next key after creating the managed address
// from it.
addr, err := newManagedAddressFromExtKey(
s, derivationPath, nextKey, addrType,
)
if err != nil {
return err
}
if internal {
addr.internal = true
}
managedAddr := addr
nextKey.Zero()
info := unlockDeriveInfo{
managedAddr: managedAddr,
branch: branchNum,
index: nextIndex - 1,
}
addressInfo = append(addressInfo, &info)
}
// Now that all addresses have been successfully generated, update the
// database in a single transaction.
for _, info := range addressInfo {
ma := info.managedAddr
addressID := ma.Address().ScriptAddress()
switch a := ma.(type) {
case *managedAddress:
err := putChainedAddress(
ns, &s.scope, addressID, account, ssFull,
info.branch, info.index, adtChain,
)
if err != nil {
return maybeConvertDbError(err)
}
case *scriptAddress:
encryptedHash, err := s.rootManager.cryptoKeyPub.Encrypt(a.AddrHash())
if err != nil {
str := fmt.Sprintf("failed to encrypt script hash %x",
a.AddrHash())
return managerError(ErrCrypto, str, err)
}
err = putScriptAddress(
ns, &s.scope, a.AddrHash(), ImportedAddrAccount,
ssNone, encryptedHash, a.scriptEncrypted,
)
if err != nil {
return maybeConvertDbError(err)
}
}
}
// Finally update the next address tracking and add the addresses to
// the cache after the newly generated addresses have been successfully
// added to the db.
for _, info := range addressInfo {
ma := info.managedAddr
s.addrs[addrKey(ma.Address().ScriptAddress())] = ma
// Add the new managed address to the list of addresses that
// need their private keys derived when the address manager is
// next unlocked.
if s.rootManager.IsLocked() && !watchOnly {
s.deriveOnUnlock = append(s.deriveOnUnlock, info)
}
}
// Set the last address and next address for tracking.
ma := addressInfo[len(addressInfo)-1].managedAddr
if internal {
acctInfo.nextInternalIndex = nextIndex
acctInfo.lastInternalAddr = ma
} else {
acctInfo.nextExternalIndex = nextIndex
acctInfo.lastExternalAddr = ma
}
return nil
}
// NextExternalAddresses returns the specified number of next chained addresses
// that are intended for external use from the address manager.
func (s *ScopedKeyManager) NextExternalAddresses(ns walletdb.ReadWriteBucket,
account uint32, numAddresses uint32) ([]ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
s.mtx.Lock()
defer s.mtx.Unlock()
return s.nextAddresses(ns, account, numAddresses, false)
}
// NextInternalAddresses returns the specified number of next chained addresses
// that are intended for internal use such as change from the address manager.
func (s *ScopedKeyManager) NextInternalAddresses(ns walletdb.ReadWriteBucket,
account uint32, numAddresses uint32) ([]ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
s.mtx.Lock()
defer s.mtx.Unlock()
return s.nextAddresses(ns, account, numAddresses, true)
}
// ExtendExternalAddresses ensures that all valid external keys through
// lastIndex are derived and stored in the wallet. This is used to ensure that
// wallet's persistent state catches up to a external child that was found
// during recovery.
func (s *ScopedKeyManager) ExtendExternalAddresses(ns walletdb.ReadWriteBucket,
account uint32, lastIndex uint32) error {
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return err
}
s.mtx.Lock()
defer s.mtx.Unlock()
return s.extendAddresses(ns, account, lastIndex, false)
}
// ExtendInternalAddresses ensures that all valid internal keys through
// lastIndex are derived and stored in the wallet. This is used to ensure that
// wallet's persistent state catches up to an internal child that was found
// during recovery.
func (s *ScopedKeyManager) ExtendInternalAddresses(ns walletdb.ReadWriteBucket,
account uint32, lastIndex uint32) error {
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return err
}
s.mtx.Lock()
defer s.mtx.Unlock()
return s.extendAddresses(ns, account, lastIndex, true)
}
// LastExternalAddress returns the most recently requested chained external
// address from calling NextExternalAddress for the given account. The first
// external address for the account will be returned if none have been
// previously requested.
//
// This function will return an error if the provided account number is greater
// than the MaxAccountNum constant or there is no account information for the
// passed account. Any other errors returned are generally unexpected.
func (s *ScopedKeyManager) LastExternalAddress(ns walletdb.ReadBucket,
account uint32) (ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
s.mtx.Lock()
defer s.mtx.Unlock()
// Load account information for the passed account. It is typically
// cached, but if not it will be loaded from the database.
acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
return nil, err
}
if acctInfo.nextExternalIndex > 0 {
return acctInfo.lastExternalAddr, nil
}
return nil, managerError(ErrAddressNotFound, "no previous external address", nil)
}
// LastInternalAddress returns the most recently requested chained internal
// address from calling NextInternalAddress for the given account. The first
// internal address for the account will be returned if none have been
// previously requested.
//
// This function will return an error if the provided account number is greater
// than the MaxAccountNum constant or there is no account information for the
// passed account. Any other errors returned are generally unexpected.
func (s *ScopedKeyManager) LastInternalAddress(ns walletdb.ReadBucket,
account uint32) (ManagedAddress, error) {
// Enforce maximum account number.
if account > MaxAccountNum {
err := managerError(ErrAccountNumTooHigh, errAcctTooHigh, nil)
return nil, err
}
s.mtx.Lock()
defer s.mtx.Unlock()
// Load account information for the passed account. It is typically
// cached, but if not it will be loaded from the database.
acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
return nil, err
}
if acctInfo.nextInternalIndex > 0 {
return acctInfo.lastInternalAddr, nil
}
return nil, managerError(ErrAddressNotFound, "no previous internal address", nil)
}
// NewRawAccount creates a new account for the scoped manager. This method
// differs from the NewAccount method in that this method takes the account
// number *directly*, rather than taking a string name for the account, then
// mapping that to the next highest account number.
func (s *ScopedKeyManager) NewRawAccount(ns walletdb.ReadWriteBucket, number uint32) error {
if s.rootManager.WatchOnly() {
return managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
s.mtx.Lock()
defer s.mtx.Unlock()
if s.rootManager.IsLocked() {
return managerError(ErrLocked, errLocked, nil)
}
// As this is an ad hoc account that may not follow our normal linear
// derivation, we'll create a new name for this account based off of
// the account number.
name := fmt.Sprintf("act:%v", number)
return s.newAccount(ns, number, name)
}
// NewRawAccountWatchingOnly creates a new watching only account for the scoped
// manager. This method differs from the NewAccountWatchingOnly method in that
// this method takes the account number *directly*, rather than taking a string
// name for the account, then mapping that to the next highest account number.
//
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for proper
// identification and signing.
//
// An optional address schema may also be provided to override the
// ScopedKeyManager's address schema. This will affect all addresses derived
// from the account.
func (s *ScopedKeyManager) NewRawAccountWatchingOnly(
ns walletdb.ReadWriteBucket, number uint32,
pubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32,
addrSchema *ScopeAddrSchema) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// As this is an ad hoc account that may not follow our normal linear
// derivation, we'll create a new name for this account based off of
// the account number.
name := fmt.Sprintf("act:%v", number)
return s.newAccountWatchingOnly(
ns, number, name, pubKey, masterKeyFingerprint, addrSchema,
)
}
// NewAccount creates and returns a new account stored in the manager based on
// the given account name. If an account with the same name already exists,
// ErrDuplicateAccount will be returned. Since creating a new account requires
// access to the cointype keys (from which extended account keys are derived),
// it requires the manager to be unlocked.
func (s *ScopedKeyManager) NewAccount(ns walletdb.ReadWriteBucket, name string) (uint32, error) {
if s.rootManager.WatchOnly() {
return 0, managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
s.mtx.Lock()
defer s.mtx.Unlock()
if s.rootManager.IsLocked() {
return 0, managerError(ErrLocked, errLocked, nil)
}
// Fetch latest account, and create a new account in the same
// transaction Fetch the latest account number to generate the next
// account number
account, err := fetchLastAccount(ns, &s.scope)
if err != nil {
return 0, err
}
account++
// With the name validated, we'll create a new account for the new
// contiguous account.
if err := s.newAccount(ns, account, name); err != nil {
return 0, err
}
return account, nil
}
// newAccount is a helper function that derives a new precise account number,
// and creates a mapping from the passed name to the account number in the
// database.
//
// NOTE: This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) newAccount(ns walletdb.ReadWriteBucket,
account uint32, name string) error {
// Validate the account name.
if err := ValidateAccountName(name); err != nil {
return err
}
// Check that account with the same name does not exist
_, err := s.lookupAccount(ns, name)
if err == nil {
str := fmt.Sprintf("account with the same name already exists")
return managerError(ErrDuplicateAccount, str, err)
}
// Fetch the cointype key which will be used to derive the next account
// extended keys
_, coinTypePrivEnc, err := fetchCoinTypeKeys(ns, &s.scope)
if err != nil {
return err
}
// Decrypt the cointype key.
serializedKeyPriv, err := s.rootManager.cryptoKeyPriv.Decrypt(coinTypePrivEnc)
if err != nil {
str := fmt.Sprintf("failed to decrypt cointype serialized private key")
return managerError(ErrLocked, str, err)
}
coinTypeKeyPriv, err := hdkeychain.NewKeyFromString(string(serializedKeyPriv))
zero.Bytes(serializedKeyPriv)
if err != nil {
str := fmt.Sprintf("failed to create cointype extended private key")
return managerError(ErrKeyChain, str, err)
}
// Derive the account key using the cointype key
acctKeyPriv, err := deriveAccountKey(coinTypeKeyPriv, account)
coinTypeKeyPriv.Zero()
if err != nil {
str := "failed to convert private key for account"
return managerError(ErrKeyChain, str, err)
}
acctKeyPub, err := acctKeyPriv.Neuter()
if err != nil {
str := "failed to convert public key for account"
return managerError(ErrKeyChain, str, err)
}
// Encrypt the default account keys with the associated crypto keys.
acctPubEnc, err := s.rootManager.cryptoKeyPub.Encrypt(
[]byte(acctKeyPub.String()),
)
if err != nil {
str := "failed to encrypt public key for account"
return managerError(ErrCrypto, str, err)
}
acctPrivEnc, err := s.rootManager.cryptoKeyPriv.Encrypt(
[]byte(acctKeyPriv.String()),
)
if err != nil {
str := "failed to encrypt private key for account"
return managerError(ErrCrypto, str, err)
}
// We have the encrypted account extended keys, so save them to the
// database
err = putDefaultAccountInfo(
ns, &s.scope, account, acctPubEnc, acctPrivEnc, 0, 0, name,
)
if err != nil {
return err
}
// Save last account metadata
return putLastAccount(ns, &s.scope, account)
}
// NewAccountWatchingOnly is similar to NewAccount, but for watch-only wallets.
//
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for proper
// identification and signing.
//
// An optional address schema may also be provided to override the
// ScopedKeyManager's address schema. This will affect all addresses derived
// from the account.
func (s *ScopedKeyManager) NewAccountWatchingOnly(ns walletdb.ReadWriteBucket,
name string, pubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32,
addrSchema *ScopeAddrSchema) (uint32, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
// Fetch latest account, and create a new account in the same
// transaction Fetch the latest account number to generate the next
// account number
account, err := fetchLastAccount(ns, &s.scope)
if err != nil {
return 0, err
}
account++
// With the name validated, we'll create a new account for the new
// contiguous account.
err = s.newAccountWatchingOnly(
ns, account, name, pubKey, masterKeyFingerprint, addrSchema,
)
if err != nil {
return 0, err
}
return account, nil
}
// newAccountWatchingOnly is similar to newAccount, but for watching-only wallets.
//
// The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with
// derivation path m/). This may be required by some hardware wallets for proper
// identification and signing.
//
// An optional address schema may also be provided to override the
// ScopedKeyManager's address schema. This will affect all addresses derived
// from the account.
//
// NOTE: This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) newAccountWatchingOnly(ns walletdb.ReadWriteBucket,
account uint32, name string, pubKey *hdkeychain.ExtendedKey,
masterKeyFingerprint uint32, addrSchema *ScopeAddrSchema) error {
// Validate the account name.
if err := ValidateAccountName(name); err != nil {
return err
}
// Check that account with the same name does not exist
_, err := s.lookupAccount(ns, name)
if err == nil {
str := fmt.Sprintf("account with the same name already exists")
return managerError(ErrDuplicateAccount, str, err)
}
// Encrypt the default account keys with the associated crypto keys.
acctPubEnc, err := s.rootManager.cryptoKeyPub.Encrypt(
[]byte(pubKey.String()),
)
if err != nil {
str := "failed to encrypt public key for account"
return managerError(ErrCrypto, str, err)
}
// We have the encrypted account extended keys, so save them to the
// database
err = putWatchOnlyAccountInfo(
ns, &s.scope, account, acctPubEnc, masterKeyFingerprint, 0, 0,
name, addrSchema,
)
if err != nil {
return err
}
// Save last account metadata
return putLastAccount(ns, &s.scope, account)
}
// RenameAccount renames an account stored in the manager based on the given
// account number with the given name. If an account with the same name
// already exists, ErrDuplicateAccount will be returned.
func (s *ScopedKeyManager) RenameAccount(ns walletdb.ReadWriteBucket,
account uint32, name string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// Ensure that a reserved account is not being renamed.
if isReservedAccountNum(account) {
str := "reserved account cannot be renamed"
return managerError(ErrInvalidAccount, str, nil)
}
// Check that account with the new name does not exist
_, err := s.lookupAccount(ns, name)
if err == nil {
str := fmt.Sprintf("account with the same name already exists")
return managerError(ErrDuplicateAccount, str, err)
}
// Validate account name
if err := ValidateAccountName(name); err != nil {
return err
}
rowInterface, err := fetchAccountInfo(ns, &s.scope, account)
if err != nil {
return err
}
// Remove the old name key from the account id index.
if err = deleteAccountIDIndex(ns, &s.scope, account); err != nil {
return err
}
switch row := rowInterface.(type) {
case *dbDefaultAccountRow:
// Remove the old name key from the account name index.
if err = deleteAccountNameIndex(ns, &s.scope, row.name); err != nil {
return err
}
err = putDefaultAccountInfo(
ns, &s.scope, account, row.pubKeyEncrypted,
row.privKeyEncrypted, row.nextExternalIndex,
row.nextInternalIndex, name,
)
if err != nil {
return err
}
case *dbWatchOnlyAccountRow:
// Remove the old name key from the account name index.
if err = deleteAccountNameIndex(ns, &s.scope, row.name); err != nil {
return err
}
err = putWatchOnlyAccountInfo(
ns, &s.scope, account, row.pubKeyEncrypted,
row.masterKeyFingerprint, row.nextExternalIndex,
row.nextInternalIndex, name, row.addrSchema,
)
if err != nil {
return err
}
default:
str := fmt.Sprintf("unsupported account type %T", row)
return managerError(ErrDatabase, str, nil)
}
// Update in-memory account info with new name if cached and the db
// write was successful.
if err == nil {
if acctInfo, ok := s.acctInfo[account]; ok {
acctInfo.acctName = name
}
}
return err
}
// ImportPrivateKey imports a WIF private key into the address manager. The
// imported address is created using either a compressed or uncompressed
// serialized public key, depending on the CompressPubKey bool of the WIF.
//
// All imported addresses will be part of the account defined by the
// ImportedAddrAccount constant.
//
// NOTE: When the address manager is watching-only, the private key itself will
// not be stored or available since it is private data. Instead, only the
// public key will be stored. This means it is paramount the private key is
// kept elsewhere as the watching-only address manager will NOT ever have access
// to it.
//
// This function will return an error if the address manager is locked and not
// watching-only, or not for the same network as the key trying to be imported.
// It will also return an error if the address already exists. Any other
// errors returned are generally unexpected.
func (s *ScopedKeyManager) ImportPrivateKey(ns walletdb.ReadWriteBucket,
wif *btcutil.WIF, bs *BlockStamp) (ManagedPubKeyAddress, error) {
// Ensure the address is intended for network the address manager is
// associated with.
if !wif.IsForNet(s.rootManager.chainParams) {
str := fmt.Sprintf("private key is not for the same network the "+
"address manager is configured for (%s)",
s.rootManager.chainParams.Name)
return nil, managerError(ErrWrongNet, str, nil)
}
s.mtx.Lock()
defer s.mtx.Unlock()
// The manager must be unlocked to encrypt the imported private key.
if s.rootManager.IsLocked() && !s.rootManager.WatchOnly() {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Encrypt the private key when not a watching-only address manager.
var encryptedPrivKey []byte
if !s.rootManager.WatchOnly() {
privKeyBytes := wif.PrivKey.Serialize()
var err error
encryptedPrivKey, err = s.rootManager.cryptoKeyPriv.Encrypt(privKeyBytes)
zero.Bytes(privKeyBytes)
if err != nil {
str := fmt.Sprintf("failed to encrypt private key for %x",
wif.PrivKey.PubKey().SerializeCompressed())
return nil, managerError(ErrCrypto, str, err)
}
}
2021-02-17 02:01:08 +01:00
err := s.importPublicKey(
ns, wif.SerializePubKey(), encryptedPrivKey,
s.addrSchema.ExternalAddrType, bs,
)
if err != nil {
return nil, err
}
// Create a new managed address based on the imported address.
if !s.rootManager.WatchOnly() {
return s.toImportedPrivateManagedAddress(wif)
}
pubKey := (*btcec.PublicKey)(&wif.PrivKey.PublicKey)
return s.toImportedPublicManagedAddress(pubKey, wif.CompressPubKey)
}
2021-02-17 02:01:08 +01:00
// ImportPublicKey imports a public key into the address manager.
//
// All imported addresses will be part of the account defined by the
// ImportedAddrAccount constant.
func (s *ScopedKeyManager) ImportPublicKey(ns walletdb.ReadWriteBucket,
pubKey *btcec.PublicKey, bs *BlockStamp) (ManagedAddress, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
serializedPubKey := pubKey.SerializeCompressed()
err := s.importPublicKey(
ns, serializedPubKey, nil, s.addrSchema.ExternalAddrType, bs,
)
if err != nil {
return nil, err
}
return s.toImportedPublicManagedAddress(pubKey, true)
2021-02-17 02:01:08 +01:00
}
// importPublicKey imports a public key into the address manager and updates the
// wallet's start block if necessary. An error is returned if the public key
// already exists.
func (s *ScopedKeyManager) importPublicKey(ns walletdb.ReadWriteBucket,
2021-02-17 02:01:08 +01:00
serializedPubKey, encryptedPrivKey []byte, addrType AddressType,
bs *BlockStamp) error {
// Compute the addressID for our key based on its address type.
var addressID []byte
switch addrType {
case PubKeyHash, WitnessPubKey:
addressID = btcutil.Hash160(serializedPubKey)
case NestedWitnessPubKey:
pubKeyHash := btcutil.Hash160(serializedPubKey)
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(
pubKeyHash, s.rootManager.chainParams,
)
if err != nil {
return err
}
witnessScript, err := txscript.PayToAddrScript(p2wkhAddr)
if err != nil {
return err
}
addressID = btcutil.Hash160(witnessScript)
default:
return fmt.Errorf("unsupported address type %v", addrType)
}
// Prevent duplicates.
2021-02-17 02:01:08 +01:00
alreadyExists := s.existsAddress(ns, addressID)
if alreadyExists {
str := fmt.Sprintf("address for public key %x already exists",
serializedPubKey)
return managerError(ErrDuplicateAddress, str, nil)
}
// Encrypt public key.
encryptedPubKey, err := s.rootManager.cryptoKeyPub.Encrypt(
serializedPubKey,
)
if err != nil {
str := fmt.Sprintf("failed to encrypt public key for %x",
serializedPubKey)
return managerError(ErrCrypto, str, err)
}
// The start block needs to be updated when the newly imported address
// is before the current one.
s.rootManager.mtx.Lock()
updateStartBlock := bs != nil &&
bs.Height < s.rootManager.syncState.startBlock.Height
s.rootManager.mtx.Unlock()
// Save the new imported address to the db and update start block (if
// needed) in a single transaction.
err = putImportedAddress(
2021-02-17 02:01:08 +01:00
ns, &s.scope, addressID, ImportedAddrAccount, ssNone,
encryptedPubKey, encryptedPrivKey,
)
if err != nil {
return err
}
if updateStartBlock {
err := putStartBlock(ns, bs)
if err != nil {
return err
}
}
// Now that the database has been updated, update the start block in
// memory too if needed.
if updateStartBlock {
s.rootManager.mtx.Lock()
s.rootManager.syncState.startBlock = *bs
s.rootManager.mtx.Unlock()
}
return nil
}
// toImportedPrivateManagedAddress converts an imported private key to an
// imported managed address.
func (s *ScopedKeyManager) toImportedPrivateManagedAddress(
wif *btcutil.WIF) (*managedAddress, error) {
// Create a new managed address based on the imported address.
//
// TODO: Handle imported key being part of internal branch.
managedAddr, err := newManagedAddress(
s, ImportedDerivationPath, wif.PrivKey, wif.CompressPubKey,
s.addrSchema.ExternalAddrType,
)
if err != nil {
return nil, err
}
managedAddr.imported = true
// Add the new managed address to the cache of recent addresses and
// return it.
s.addrs[addrKey(managedAddr.Address().ScriptAddress())] = managedAddr
return managedAddr, nil
}
// toPublicManagedAddress converts an imported public key to an imported managed
// address.
func (s *ScopedKeyManager) toImportedPublicManagedAddress(
pubKey *btcec.PublicKey, compressed bool) (*managedAddress, error) {
// Create a new managed address based on the imported address.
//
// TODO: Handle imported key being part of internal branch.
managedAddr, err := newManagedAddressWithoutPrivKey(
s, ImportedDerivationPath, pubKey, compressed,
s.addrSchema.ExternalAddrType,
)
if err != nil {
return nil, err
}
managedAddr.imported = true
// Add the new managed address to the cache of recent addresses and
// return it.
s.addrs[addrKey(managedAddr.Address().ScriptAddress())] = managedAddr
return managedAddr, nil
}
// ImportScript imports a user-provided script into the address manager. The
// imported script will act as a pay-to-script-hash address.
//
// All imported script addresses will be part of the account defined by the
// ImportedAddrAccount constant.
//
// When the address manager is watching-only, the script itself will not be
// stored or available since it is considered private data.
//
// This function will return an error if the address manager is locked and not
// watching-only, or the address already exists. Any other errors returned are
// generally unexpected.
func (s *ScopedKeyManager) ImportScript(ns walletdb.ReadWriteBucket,
script []byte, bs *BlockStamp) (ManagedScriptAddress, error) {
return s.importScriptAddress(ns, script, bs, Script, 0, true)
}
// ImportWitnessScript imports a user-provided script into the address manager.
// The imported script will act as a pay-to-witness-script-hash address.
//
// All imported script addresses will be part of the account defined by the
// ImportedAddrAccount constant.
//
// When the address manager is watching-only, the script itself will not be
// stored or available since it is considered private data.
//
// This function will return an error if the address manager is locked and not
// watching-only, or the address already exists. Any other errors returned are
// generally unexpected.
func (s *ScopedKeyManager) ImportWitnessScript(ns walletdb.ReadWriteBucket,
script []byte, bs *BlockStamp, witnessVersion byte,
isSecretScript bool) (ManagedScriptAddress, error) {
return s.importScriptAddress(
ns, script, bs, WitnessScript, witnessVersion, isSecretScript,
)
}
// importScriptAddress imports a new pay-to-script or pay-to-witness-script
// address.
func (s *ScopedKeyManager) importScriptAddress(ns walletdb.ReadWriteBucket,
script []byte, bs *BlockStamp, addrType AddressType,
witnessVersion byte, isSecretScript bool) (ManagedScriptAddress,
error) {
s.mtx.Lock()
defer s.mtx.Unlock()
// The manager must be unlocked to encrypt the imported script.
if isSecretScript && s.rootManager.IsLocked() {
return nil, managerError(ErrLocked, errLocked, nil)
}
// A secret script can only be used with a non-watch only manager. If
// a wallet is watch-only then the script must be encrypted with the
// public encryption key.
if isSecretScript && s.rootManager.WatchOnly() {
return nil, managerError(ErrWatchingOnly, errWatchingOnly, nil)
}
// Witness script addresses use a SHA256.
var scriptHash []byte
switch addrType {
case WitnessScript:
digest := sha256.Sum256(script)
scriptHash = digest[:]
default:
scriptHash = btcutil.Hash160(script)
}
// Prevent duplicates.
alreadyExists := s.existsAddress(ns, scriptHash)
if alreadyExists {
str := fmt.Sprintf("address for script hash %x already exists",
scriptHash)
return nil, managerError(ErrDuplicateAddress, str, nil)
}
// Encrypt the script hash using the crypto public key so it is
// accessible when the address manager is locked or watching-only.
encryptedHash, err := s.rootManager.cryptoKeyPub.Encrypt(scriptHash)
if err != nil {
str := fmt.Sprintf("failed to encrypt script hash %x",
scriptHash)
return nil, managerError(ErrCrypto, str, err)
}
// If a key isn't considered to be "secret", we encrypt it with the
// public key, so we can create script addresses that also work in
// watch-only mode.
cryptoKey := s.rootManager.cryptoKeyScript
if !isSecretScript {
cryptoKey = s.rootManager.cryptoKeyPub
}
// Encrypt the script for storage in database using the selected crypto
// key.
encryptedScript, err := cryptoKey.Encrypt(script)
if err != nil {
str := fmt.Sprintf("failed to encrypt script for %x",
scriptHash)
return nil, managerError(ErrCrypto, str, err)
}
// The start block needs to be updated when the newly imported address
// is before the current one.
updateStartBlock := false
s.rootManager.mtx.Lock()
if bs.Height < s.rootManager.syncState.startBlock.Height {
updateStartBlock = true
}
s.rootManager.mtx.Unlock()
// Save the new imported address to the db and update start block (if
// needed) in a single transaction.
switch addrType {
case WitnessScript:
err = putWitnessScriptAddress(
ns, &s.scope, scriptHash, ImportedAddrAccount, ssNone,
witnessVersion, isSecretScript, encryptedHash,
encryptedScript,
)
default:
err = putScriptAddress(
ns, &s.scope, scriptHash, ImportedAddrAccount, ssNone,
encryptedHash, encryptedScript,
)
}
if err != nil {
return nil, maybeConvertDbError(err)
}
if updateStartBlock {
err := putStartBlock(ns, bs)
if err != nil {
return nil, maybeConvertDbError(err)
}
}
// Now that the database has been updated, update the start block in
// memory too if needed.
if updateStartBlock {
s.rootManager.mtx.Lock()
s.rootManager.syncState.startBlock = *bs
s.rootManager.mtx.Unlock()
}
// Create a new managed address based on the imported script. Also,
// when not a watching-only address manager, make a copy of the script
// since it will be cleared on lock and the script the caller passed
// should not be cleared out from under the caller.
var (
managedAddr ManagedScriptAddress
baseScriptAddr *baseScriptAddress
)
switch addrType {
case WitnessScript:
witnessAddr, err := newWitnessScriptAddress(
s, ImportedAddrAccount, scriptHash, encryptedScript,
witnessVersion, isSecretScript,
)
if err != nil {
return nil, err
}
managedAddr = witnessAddr
baseScriptAddr = &witnessAddr.baseScriptAddress
default:
scriptAddr, err := newScriptAddress(
s, ImportedAddrAccount, scriptHash, encryptedScript,
)
if err != nil {
return nil, err
}
managedAddr = scriptAddr
baseScriptAddr = &scriptAddr.baseScriptAddress
}
// Even if the script is secret, we are currently unlocked, so we keep a
// clear text copy of the script around to avoid decrypting it on each
// access.
baseScriptAddr.scriptClearText = make([]byte, len(script))
copy(baseScriptAddr.scriptClearText, script)
// Add the new managed address to the cache of recent addresses and
// return it.
s.addrs[addrKey(scriptHash)] = managedAddr
return managedAddr, nil
}
// lookupAccount loads account number stored in the manager for the given
// account name
//
// This function MUST be called with the manager lock held for reads.
func (s *ScopedKeyManager) lookupAccount(ns walletdb.ReadBucket, name string) (uint32, error) {
return fetchAccountByName(ns, &s.scope, name)
}
// LookupAccount loads account number stored in the manager for the given
// account name
func (s *ScopedKeyManager) LookupAccount(ns walletdb.ReadBucket, name string) (uint32, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.lookupAccount(ns, name)
}
// fetchUsed returns true if the provided address id was flagged used.
func (s *ScopedKeyManager) fetchUsed(ns walletdb.ReadBucket,
addressID []byte) bool {
return fetchAddressUsed(ns, &s.scope, addressID)
}
// MarkUsed updates the used flag for the provided address.
func (s *ScopedKeyManager) MarkUsed(ns walletdb.ReadWriteBucket,
address btcutil.Address) error {
addressID := address.ScriptAddress()
err := markAddressUsed(ns, &s.scope, addressID)
if err != nil {
return maybeConvertDbError(err)
}
// Clear caches which might have stale entries for used addresses
s.mtx.Lock()
delete(s.addrs, addrKey(addressID))
s.mtx.Unlock()
return nil
}
// ChainParams returns the chain parameters for this address manager.
func (s *ScopedKeyManager) ChainParams() *chaincfg.Params {
// NOTE: No need for mutex here since the net field does not change
// after the manager instance is created.
return s.rootManager.chainParams
}
// AccountName returns the account name for the given account number stored in
// the manager.
func (s *ScopedKeyManager) AccountName(ns walletdb.ReadBucket, account uint32) (string, error) {
return fetchAccountName(ns, &s.scope, account)
}
// ForEachAccount calls the given function with each account stored in the
// manager, breaking early on error.
func (s *ScopedKeyManager) ForEachAccount(ns walletdb.ReadBucket,
fn func(account uint32) error) error {
return forEachAccount(ns, &s.scope, fn)
}
// LastAccount returns the last account stored in the manager.
// If no accounts, returns twos-complement representation of -1
func (s *ScopedKeyManager) LastAccount(ns walletdb.ReadBucket) (uint32, error) {
return fetchLastAccount(ns, &s.scope)
}
// ForEachAccountAddress calls the given function with each address of the
// given account stored in the manager, breaking early on error.
func (s *ScopedKeyManager) ForEachAccountAddress(ns walletdb.ReadBucket,
account uint32, fn func(maddr ManagedAddress) error) error {
s.mtx.Lock()
defer s.mtx.Unlock()
addrFn := func(rowInterface interface{}) error {
managedAddr, err := s.rowInterfaceToManaged(ns, rowInterface)
if err != nil {
return err
}
return fn(managedAddr)
}
err := forEachAccountAddress(ns, &s.scope, account, addrFn)
if err != nil {
return maybeConvertDbError(err)
}
return nil
}
// ForEachActiveAccountAddress calls the given function with each active
// address of the given account stored in the manager, breaking early on error.
//
// TODO(tuxcanfly): actually return only active addresses
func (s *ScopedKeyManager) ForEachActiveAccountAddress(ns walletdb.ReadBucket, account uint32,
fn func(maddr ManagedAddress) error) error {
return s.ForEachAccountAddress(ns, account, fn)
}
// ForEachActiveAddress calls the given function with each active address
// stored in the manager, breaking early on error.
func (s *ScopedKeyManager) ForEachActiveAddress(ns walletdb.ReadBucket,
fn func(addr btcutil.Address) error) error {
s.mtx.Lock()
defer s.mtx.Unlock()
addrFn := func(rowInterface interface{}) error {
managedAddr, err := s.rowInterfaceToManaged(ns, rowInterface)
if err != nil {
return err
}
return fn(managedAddr.Address())
}
err := forEachActiveAddress(ns, &s.scope, addrFn)
if err != nil {
return maybeConvertDbError(err)
}
return nil
}
// ForEachInternalActiveAddress invokes the given closure on each _internal_
// active address belonging to the scoped key manager, breaking early on error.
func (s *ScopedKeyManager) ForEachInternalActiveAddress(ns walletdb.ReadBucket,
fn func(addr btcutil.Address) error) error {
s.mtx.Lock()
defer s.mtx.Unlock()
addrFn := func(rowInterface interface{}) error {
managedAddr, err := s.rowInterfaceToManaged(ns, rowInterface)
if err != nil {
return err
}
// Skip any non-internal branch addresses.
if !managedAddr.Internal() {
return nil
}
return fn(managedAddr.Address())
}
if err := forEachActiveAddress(ns, &s.scope, addrFn); err != nil {
return maybeConvertDbError(err)
}
return nil
}
// IsWatchOnlyAccount determines if the given account belonging to this scoped
// manager is set up as watch-only.
func (s *ScopedKeyManager) IsWatchOnlyAccount(ns walletdb.ReadBucket,
account uint32) (bool, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
acctInfo, err := s.loadAccountInfo(ns, account)
if err != nil {
return false, err
}
return acctInfo.acctKeyPriv == nil, nil
}
// cloneKeyWithVersion clones an extended key to use the version corresponding
// to the manager's key scope. This should only be used for non-watch-only
// accounts as they are stored within the database using the legacy BIP-0044
// version by default.
func (s *ScopedKeyManager) cloneKeyWithVersion(key *hdkeychain.ExtendedKey) (
*hdkeychain.ExtendedKey, error) {
// Determine the appropriate version based on the current network and
// key scope.
var version HDVersion
net := s.rootManager.ChainParams().Net
switch net {
case wire.MainNet:
switch s.scope {
case KeyScopeBIP0044:
version = HDVersionMainNetBIP0044
case KeyScopeBIP0049Plus:
version = HDVersionMainNetBIP0049
case KeyScopeBIP0084:
version = HDVersionMainNetBIP0084
default:
return nil, fmt.Errorf("unsupported scope %v", s.scope)
}
case wire.TestNet, wire.TestNet3,
netparams.SigNetWire(s.rootManager.ChainParams()):
switch s.scope {
case KeyScopeBIP0044:
version = HDVersionTestNetBIP0044
case KeyScopeBIP0049Plus:
version = HDVersionTestNetBIP0049
case KeyScopeBIP0084:
version = HDVersionTestNetBIP0084
default:
return nil, fmt.Errorf("unsupported scope %v", s.scope)
}
case wire.SimNet:
switch s.scope {
case KeyScopeBIP0044:
version = HDVersionSimNetBIP0044
// We use the mainnet versions for simnet keys when the keys
// belong to a key scope which simnet doesn't have a defined
// version for.
case KeyScopeBIP0049Plus:
version = HDVersionMainNetBIP0049
case KeyScopeBIP0084:
version = HDVersionMainNetBIP0084
default:
return nil, fmt.Errorf("unsupported scope %v", s.scope)
}
default:
return nil, fmt.Errorf("unsupported net %v", net)
}
var versionBytes [4]byte
binary.BigEndian.PutUint32(versionBytes[:], uint32(version))
return key.CloneWithVersion(versionBytes[:])
}
// InvalidateAccountCache invalidates the cache for the given account, forcing a
// database read to retrieve the account information.
func (s *ScopedKeyManager) InvalidateAccountCache(account uint32) {
s.mtx.Lock()
defer s.mtx.Unlock()
delete(s.acctInfo, account)
}