lbcwallet/waddrmgr/scoped_manager.go
Wilmer Paulino e1dfc4d1b7
waddrmgr+wallet: allow account import for simnet wallets
Simnet was previously left out as it didn't have defined HD versions for
some of our key scopes. To allow testing importing accounts into simnet
wallets, we fall back to use the mainnet HD versions.

This commit also addresses an issue with simnet wallets that would arise
whenever ScopedKeyManager.AccountProperties was invoked:

`failed to retrieve account public key: unsupported net SimNet`
2021-04-14 17:21:07 -07:00

2269 lines
72 KiB
Go

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/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
)
// 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,
Coin: 0,
}
// KeyScopeBIP0084 is the key scope for BIP0084 derivation. BIP0084
// will be used to derive all p2wkh addresses.
KeyScopeBIP0084 = KeyScope{
Purpose: 84,
Coin: 0,
}
// 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,
Coin: 0,
}
// 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
// manager.
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
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.
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)
}
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) {
defer s.mtx.RUnlock()
s.mtx.RLock()
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
}
// 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)
}
// 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)
}
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.
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.
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.
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.
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)
}
}
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)
}
// 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)
}
// 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,
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.
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(
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) {
s.mtx.Lock()
defer s.mtx.Unlock()
// The manager must be unlocked to encrypt the imported script.
if s.rootManager.IsLocked() {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Prevent duplicates.
scriptHash := btcutil.Hash160(script)
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)
}
// Encrypt the script for storage in database using the crypto script
// key when not a watching-only address manager.
var encryptedScript []byte
if !s.rootManager.WatchOnly() {
encryptedScript, err = s.rootManager.cryptoKeyScript.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.
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.
scriptAddr, err := newScriptAddress(
s, ImportedAddrAccount, scriptHash, encryptedScript,
)
if err != nil {
return nil, err
}
if !s.rootManager.WatchOnly() {
scriptAddr.scriptCT = make([]byte, len(script))
copy(scriptAddr.scriptCT, script)
}
// Add the new managed address to the cache of recent addresses and
// return it.
s.addrs[addrKey(scriptHash)] = scriptAddr
return scriptAddr, 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:
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[:])
}