Release v0.3.0

This commit is contained in:
Manu Herrera
2020-11-09 10:05:29 -03:00
parent 4e9aa7a3c5
commit 8107c4478b
1265 changed files with 440488 additions and 107809 deletions

View File

@@ -32,8 +32,9 @@ report. Package waddrmgr is licensed under the liberal ISC license.
- Import WIF keys
- Import pay-to-script-hash scripts for things such as multi-signature
transactions
- Ability to export a watching-only version which does not contain any private
- Ability to start in watching-only mode which does not contain any private
key material
- Ability to convert to watching-only mode
- Programmatically detectable errors, including encapsulation of errors from
packages it relies on
- Address synchronization capabilities

View File

@@ -850,6 +850,7 @@ func forEachAccount(ns walletdb.ReadBucket, scope *KeyScope,
}
// fetchLastAccount retrieves the last account from the database.
// If no accounts, returns twos-complement representation of -1, so that the next account is zero
func fetchLastAccount(ns walletdb.ReadBucket, scope *KeyScope) (uint32, error) {
scopedBucket, err := fetchReadScopeBucket(ns, scope)
if err != nil {
@@ -859,6 +860,9 @@ func fetchLastAccount(ns walletdb.ReadBucket, scope *KeyScope) (uint32, error) {
metaBucket := scopedBucket.NestedReadBucket(metaBucketName)
val := metaBucket.Get(lastAccountName)
if val == nil {
return (1 << 32) - 1, nil
}
if len(val) != 4 {
str := fmt.Sprintf("malformed metadata '%s' stored in database",
lastAccountName)

View File

@@ -123,6 +123,14 @@ var DefaultScryptOptions = ScryptOptions{
P: 1,
}
// FastScryptOptions are the scrypt options that should be used for testing
// purposes only where speed is more important than security.
var FastScryptOptions = ScryptOptions{
N: 16,
R: 8,
P: 1,
}
// addrKey is used to uniquely identify an address even when those addresses
// would end up being the same bitcoin address (as is the case for
// pay-to-pubkey and pay-to-pubkey-hash style of addresses).
@@ -430,53 +438,61 @@ func (m *Manager) Close() {
//
// TODO(roasbeef): addrtype of raw key means it'll look in scripts to possibly
// mark as gucci?
func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, scope KeyScope,
addrSchema ScopeAddrSchema) (*ScopedKeyManager, error) {
func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket,
scope KeyScope, addrSchema ScopeAddrSchema) (*ScopedKeyManager, error) {
m.mtx.Lock()
defer m.mtx.Unlock()
// If the manager is locked, then we can't create a new scoped manager.
if m.locked {
return nil, managerError(ErrLocked, errLocked, nil)
}
var rootPriv *hdkeychain.ExtendedKey
if !m.watchingOnly {
// If the manager is locked, then we can't create a new scoped
// manager.
if m.locked {
return nil, managerError(ErrLocked, errLocked, nil)
}
// Now that we know the manager is unlocked, we'll need to fetch the
// root master HD private key. This is required as we'll be attempting
// the following derivation: m/purpose'/cointype'
//
// Note that the path to the coin type is requires hardened derivation,
// therefore this can only be done if the wallet's root key hasn't been
// neutered.
masterRootPrivEnc, _, err := fetchMasterHDKeys(ns)
if err != nil {
return nil, err
}
// Now that we know the manager is unlocked, we'll need to
// fetch the root master HD private key. This is required as
// we'll be attempting the following derivation:
// m/purpose'/cointype'
//
// Note that the path to the coin type is requires hardened
// derivation, therefore this can only be done if the wallet's
// root key hasn't been neutered.
masterRootPrivEnc, _, err := fetchMasterHDKeys(ns)
if err != nil {
return nil, err
}
// If the master root private key isn't found within the database, but
// we need to bail here as we can't create the cointype key without the
// master root private key.
if masterRootPrivEnc == nil {
return nil, managerError(ErrWatchingOnly, "", nil)
}
// If the master root private key isn't found within the
// database, but we need to bail here as we can't create the
// cointype key without the master root private key.
if masterRootPrivEnc == nil {
return nil, managerError(ErrWatchingOnly, "", nil)
}
// Before we can derive any new scoped managers using this key, we'll
// need to fully decrypt it.
serializedMasterRootPriv, err := m.cryptoKeyPriv.Decrypt(masterRootPrivEnc)
if err != nil {
str := fmt.Sprintf("failed to decrypt master root serialized private key")
return nil, managerError(ErrLocked, str, err)
}
// Before we can derive any new scoped managers using this
// key, we'll need to fully decrypt it.
serializedMasterRootPriv, err :=
m.cryptoKeyPriv.Decrypt(masterRootPrivEnc)
if err != nil {
str := fmt.Sprintf("failed to decrypt master root " +
"serialized private key")
return nil, managerError(ErrLocked, str, err)
}
// Now that we know the root priv is within the database, we'll decode
// it into a usable object.
rootPriv, err := hdkeychain.NewKeyFromString(
string(serializedMasterRootPriv),
)
zero.Bytes(serializedMasterRootPriv)
if err != nil {
str := fmt.Sprintf("failed to create master extended private key")
return nil, managerError(ErrKeyChain, str, err)
// Now that we know the root priv is within the database,
// we'll decode it into a usable object.
rootPriv, err = hdkeychain.NewKeyFromString(
string(serializedMasterRootPriv),
)
zero.Bytes(serializedMasterRootPriv)
if err != nil {
str := fmt.Sprintf("failed to create master extended " +
"private key")
return nil, managerError(ErrKeyChain, str, err)
}
}
// Now that we have the root private key, we'll fetch the scope bucket
@@ -498,19 +514,21 @@ func (m *Manager) NewScopedKeyManager(ns walletdb.ReadWriteBucket, scope KeyScop
}
scopeKey := scopeToBytes(&scope)
schemaBytes := scopeSchemaToBytes(&addrSchema)
err = scopeSchemas.Put(scopeKey[:], schemaBytes)
err := scopeSchemas.Put(scopeKey[:], schemaBytes)
if err != nil {
return nil, err
}
// With the database state created, we'll now derive the cointype key
// using the master HD private key, then encrypt it along with the
// first account using our crypto keys.
err = createManagerKeyScope(
ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv,
)
if err != nil {
return nil, err
if !m.watchingOnly {
// With the database state created, we'll now derive the
// cointype key using the master HD private key, then encrypt
// it along with the first account using our crypto keys.
err = createManagerKeyScope(
ns, scope, rootPriv, m.cryptoKeyPub, m.cryptoKeyPriv,
)
if err != nil {
return nil, err
}
}
// Finally, we'll register this new scoped manager with the root
@@ -730,6 +748,43 @@ func (m *Manager) ForEachActiveAddress(ns walletdb.ReadBucket, fn func(addr btcu
return nil
}
// ForEachRelevantActiveAddress invokes the given closure on each active
// address relevant to the wallet. Ideally, only addresses within the default
// key scopes would be relevant, but due to a bug (now fixed) in which change
// addresses could be created outside of the default key scopes, we now need to
// check for those as well.
func (m *Manager) ForEachRelevantActiveAddress(ns walletdb.ReadBucket,
fn func(addr btcutil.Address) error) error {
m.mtx.RLock()
defer m.mtx.RUnlock()
for _, scopedMgr := range m.scopedManagers {
// If the manager is for a default key scope, we'll return all
// addresses, otherwise we'll only return internal addresses, as
// that's the branch used for change addresses.
isDefaultKeyScope := false
for _, defaultKeyScope := range DefaultKeyScopes {
if scopedMgr.Scope() == defaultKeyScope {
isDefaultKeyScope = true
break
}
}
var err error
if isDefaultKeyScope {
err = scopedMgr.ForEachActiveAddress(ns, fn)
} else {
err = scopedMgr.ForEachInternalActiveAddress(ns, fn)
}
if err != nil {
return err
}
}
return nil
}
// ForEachAccountAddress calls the given function with each address of
// the given account stored in the manager, breaking early on error.
func (m *Manager) ForEachAccountAddress(ns walletdb.ReadBucket, account uint32,
@@ -1253,7 +1308,7 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey,
masterKeyPriv *snacl.SecretKey, cryptoKeyPub EncryptorDecryptor,
cryptoKeyPrivEncrypted, cryptoKeyScriptEncrypted []byte, syncInfo *syncState,
birthday time.Time, privPassphraseSalt [saltSize]byte,
scopedManagers map[KeyScope]*ScopedKeyManager) *Manager {
scopedManagers map[KeyScope]*ScopedKeyManager, watchingOnly bool) *Manager {
m := &Manager{
chainParams: chainParams,
@@ -1271,6 +1326,7 @@ func newManager(chainParams *chaincfg.Params, masterKeyPub *snacl.SecretKey,
scopedManagers: scopedManagers,
externalAddrSchemas: make(map[AddressType][]KeyScope),
internalAddrSchemas: make(map[AddressType][]KeyScope),
watchingOnly: watchingOnly,
}
for _, sMgr := range m.scopedManagers {
@@ -1495,9 +1551,8 @@ func loadManager(ns walletdb.ReadBucket, pubPassphrase []byte,
mgr := newManager(
chainParams, &masterKeyPub, &masterKeyPriv,
cryptoKeyPub, cryptoKeyPrivEnc, cryptoKeyScriptEnc, syncInfo,
birthday, privPassphraseSalt, scopedManagers,
birthday, privPassphraseSalt, scopedManagers, watchingOnly,
)
mgr.watchingOnly = watchingOnly
for _, scopedManager := range scopedManagers {
scopedManager.rootManager = mgr
@@ -1631,27 +1686,40 @@ func createManagerKeyScope(ns walletdb.ReadWriteBucket,
)
}
// Create creates a new address manager in the given namespace. The seed must
// conform to the standards described in hdkeychain.NewMaster and will be used
// to create the master root node from which all hierarchical deterministic
// addresses are derived. This allows all chained addresses in the address
// manager to be recovered by using the same seed.
// Create creates a new address manager in the given namespace.
//
// All private and public keys and information are protected by secret keys
// derived from the provided private and public passphrases. The public
// passphrase is required on subsequent opens of the address manager, and the
// private passphrase is required to unlock the address manager in order to
// gain access to any private keys and information.
// The seed must conform to the standards described in
// hdkeychain.NewMaster and will be used to create the master root
// node from which all hierarchical deterministic addresses are
// derived. This allows all chained addresses in the address manager
// to be recovered by using the same seed.
//
// If a config structure is passed to the function, that configuration will
// override the defaults.
// If the provided seed value is nil the address manager will be
// created in watchingOnly mode in which case no default accounts or
// scoped managers are created - it is up to the caller to create a
// new one with NewAccountWatchingOnly and NewScopedKeyManager.
//
// A ManagerError with an error code of ErrAlreadyExists will be returned the
// address manager already exists in the specified namespace.
func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []byte,
// All private and public keys and information are protected by secret
// keys derived from the provided private and public passphrases. The
// public passphrase is required on subsequent opens of the address
// manager, and the private passphrase is required to unlock the
// address manager in order to gain access to any private keys and
// information.
//
// If a config structure is passed to the function, that configuration
// will override the defaults.
//
// A ManagerError with an error code of ErrAlreadyExists will be
// returned the address manager already exists in the specified
// namespace.
func Create(ns walletdb.ReadWriteBucket,
seed, pubPassphrase, privPassphrase []byte,
chainParams *chaincfg.Params, config *ScryptOptions,
birthday time.Time) error {
// If the seed argument is nil we create in watchingOnly mode.
isWatchingOnly := seed == nil
// Return an error if the manager has already been created in
// the given database namespace.
exists := managerExists(ns)
@@ -1660,13 +1728,17 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
}
// Ensure the private passphrase is not empty.
if len(privPassphrase) == 0 {
if !isWatchingOnly && len(privPassphrase) == 0 {
str := "private passphrase may not be empty"
return managerError(ErrEmptyPassphrase, str, nil)
}
// Perform the initial bucket creation and database namespace setup.
if err := createManagerNS(ns, ScopeAddrMap); err != nil {
defaultScopes := map[KeyScope]ScopeAddrSchema{}
if !isWatchingOnly {
defaultScopes = ScopeAddrMap
}
if err := createManagerNS(ns, defaultScopes); err != nil {
return maybeConvertDbError(err)
}
@@ -1681,22 +1753,6 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
str := "failed to master public key"
return managerError(ErrCrypto, str, err)
}
masterKeyPriv, err := newSecretKey(&privPassphrase, config)
if err != nil {
str := "failed to master private key"
return managerError(ErrCrypto, str, err)
}
defer masterKeyPriv.Zero()
// Generate the private passphrase salt. This is used when hashing
// passwords to detect whether an unlock can be avoided when the manager
// is already unlocked.
var privPassphraseSalt [saltSize]byte
_, err = rand.Read(privPassphraseSalt[:])
if err != nil {
str := "failed to read random source for passphrase salt"
return managerError(ErrCrypto, str, err)
}
// Generate new crypto public, private, and script keys. These keys are
// used to protect the actual public and private data such as addresses,
@@ -1706,18 +1762,6 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
str := "failed to generate crypto public key"
return managerError(ErrCrypto, str, err)
}
cryptoKeyPriv, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto private key"
return managerError(ErrCrypto, str, err)
}
defer cryptoKeyPriv.Zero()
cryptoKeyScript, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto script key"
return managerError(ErrCrypto, str, err)
}
defer cryptoKeyScript.Zero()
// Encrypt the crypto keys with the associated master keys.
cryptoKeyPubEnc, err := masterKeyPub.Encrypt(cryptoKeyPub.Bytes())
@@ -1725,70 +1769,120 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
str := "failed to encrypt crypto public key"
return managerError(ErrCrypto, str, err)
}
cryptoKeyPrivEnc, err := masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
if err != nil {
str := "failed to encrypt crypto private key"
return managerError(ErrCrypto, str, err)
}
cryptoKeyScriptEnc, err := masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
if err != nil {
str := "failed to encrypt crypto script key"
return managerError(ErrCrypto, str, err)
}
// Use the genesis block for the passed chain as the created at block
// for the default.
createdAt := &BlockStamp{Hash: *chainParams.GenesisHash, Height: 0}
createdAt := &BlockStamp{
Hash: *chainParams.GenesisHash,
Height: 0,
Timestamp: chainParams.GenesisBlock.Header.Timestamp,
}
// Create the initial sync state.
syncInfo := newSyncState(createdAt, createdAt)
// Save the master key params to the database.
pubParams := masterKeyPub.Marshal()
privParams := masterKeyPriv.Marshal()
err = putMasterKeyParams(ns, pubParams, privParams)
if err != nil {
return maybeConvertDbError(err)
}
// Generate the BIP0044 HD key structure to ensure the provided seed
// can generate the required structure with no issues.
var privParams []byte = nil
var masterKeyPriv *snacl.SecretKey
var cryptoKeyPrivEnc []byte = nil
var cryptoKeyScriptEnc []byte = nil
if !isWatchingOnly {
masterKeyPriv, err = newSecretKey(&privPassphrase, config)
if err != nil {
str := "failed to master private key"
return managerError(ErrCrypto, str, err)
}
defer masterKeyPriv.Zero()
// Derive the master extended key from the seed.
rootKey, err := hdkeychain.NewMaster(seed, chainParams)
if err != nil {
str := "failed to derive master extended key"
return managerError(ErrKeyChain, str, err)
}
rootPubKey, err := rootKey.Neuter()
if err != nil {
str := "failed to neuter master extended key"
return managerError(ErrKeyChain, str, err)
}
// Generate the private passphrase salt. This is used when
// hashing passwords to detect whether an unlock can be
// avoided when the manager is already unlocked.
var privPassphraseSalt [saltSize]byte
_, err = rand.Read(privPassphraseSalt[:])
if err != nil {
str := "failed to read random source for passphrase salt"
return managerError(ErrCrypto, str, err)
}
// Next, for each registers default manager scope, we'll create the
// hardened cointype key for it, as well as the first default account.
for _, defaultScope := range DefaultKeyScopes {
err := createManagerKeyScope(
ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv,
)
cryptoKeyPriv, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto private key"
return managerError(ErrCrypto, str, err)
}
defer cryptoKeyPriv.Zero()
cryptoKeyScript, err := newCryptoKey()
if err != nil {
str := "failed to generate crypto script key"
return managerError(ErrCrypto, str, err)
}
defer cryptoKeyScript.Zero()
cryptoKeyPrivEnc, err =
masterKeyPriv.Encrypt(cryptoKeyPriv.Bytes())
if err != nil {
str := "failed to encrypt crypto private key"
return managerError(ErrCrypto, str, err)
}
cryptoKeyScriptEnc, err =
masterKeyPriv.Encrypt(cryptoKeyScript.Bytes())
if err != nil {
str := "failed to encrypt crypto script key"
return managerError(ErrCrypto, str, err)
}
// Generate the BIP0044 HD key structure to ensure the
// provided seed can generate the required structure with no
// issues.
// Derive the master extended key from the seed.
rootKey, err := hdkeychain.NewMaster(seed, chainParams)
if err != nil {
str := "failed to derive master extended key"
return managerError(ErrKeyChain, str, err)
}
rootPubKey, err := rootKey.Neuter()
if err != nil {
str := "failed to neuter master extended key"
return managerError(ErrKeyChain, str, err)
}
// Next, for each registers default manager scope, we'll
// create the hardened cointype key for it, as well as the
// first default account.
for _, defaultScope := range DefaultKeyScopes {
err := createManagerKeyScope(
ns, defaultScope, rootKey, cryptoKeyPub, cryptoKeyPriv,
)
if err != nil {
return maybeConvertDbError(err)
}
}
// Before we proceed, we'll also store the root master private
// key within the database in an encrypted format. This is
// required as in the future, we may need to create additional
// scoped key managers.
masterHDPrivKeyEnc, err :=
cryptoKeyPriv.Encrypt([]byte(rootKey.String()))
if err != nil {
return maybeConvertDbError(err)
}
masterHDPubKeyEnc, err :=
cryptoKeyPub.Encrypt([]byte(rootPubKey.String()))
if err != nil {
return maybeConvertDbError(err)
}
err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc)
if err != nil {
return maybeConvertDbError(err)
}
privParams = masterKeyPriv.Marshal()
}
// Before we proceed, we'll also store the root master private key
// within the database in an encrypted format. This is required as in
// the future, we may need to create additional scoped key managers.
masterHDPrivKeyEnc, err := cryptoKeyPriv.Encrypt([]byte(rootKey.String()))
if err != nil {
return maybeConvertDbError(err)
}
masterHDPubKeyEnc, err := cryptoKeyPub.Encrypt([]byte(rootPubKey.String()))
if err != nil {
return maybeConvertDbError(err)
}
err = putMasterHDKeys(ns, masterHDPrivKeyEnc, masterHDPubKeyEnc)
// Save the master key params to the database.
err = putMasterKeyParams(ns, pubParams, privParams)
if err != nil {
return maybeConvertDbError(err)
}
@@ -1800,9 +1894,9 @@ func Create(ns walletdb.ReadWriteBucket, seed, pubPassphrase, privPassphrase []b
return maybeConvertDbError(err)
}
// Save the fact this is not a watching-only address manager to the
// Save the watching-only mode of the address manager to the
// database.
err = putWatchingOnly(ns, false)
err = putWatchingOnly(ns, isWatchingOnly)
if err != nil {
return maybeConvertDbError(err)
}

View File

@@ -1187,7 +1187,7 @@ func (s *ScopedKeyManager) LastInternalAddress(ns walletdb.ReadBucket,
}
// NewRawAccount creates a new account for the scoped manager. This method
// differs from the NewAccount method in that this method takes the acount
// 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 {
@@ -1209,6 +1209,24 @@ func (s *ScopedKeyManager) NewRawAccount(ns walletdb.ReadWriteBucket, number uin
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.
func (s *ScopedKeyManager) NewRawAccountWatchingOnly(
ns walletdb.ReadWriteBucket, number uint32,
pubKey *hdkeychain.ExtendedKey) 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)
}
// 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
@@ -1326,6 +1344,70 @@ func (s *ScopedKeyManager) newAccount(ns walletdb.ReadWriteBucket,
return putLastAccount(ns, &s.scope, account)
}
// NewAccountWatchingOnly is similar to NewAccount, but for watch-only wallets.
func (s *ScopedKeyManager) NewAccountWatchingOnly(ns walletdb.ReadWriteBucket, name string,
pubKey *hdkeychain.ExtendedKey) (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.
if err := s.newAccountWatchingOnly(ns, account, name, pubKey); err != nil {
return 0, err
}
return account, nil
}
// newAccountWatchingOnly is similar to newAccount, but for watching-only wallets.
//
// 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) 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 = putAccountInfo(
ns, &s.scope, account, acctPubEnc, nil, 0, 0, name,
)
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.
@@ -1700,6 +1782,7 @@ func (s *ScopedKeyManager) ForEachAccount(ns walletdb.ReadBucket,
}
// 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)
}
@@ -1760,3 +1843,30 @@ func (s *ScopedKeyManager) ForEachActiveAddress(ns walletdb.ReadBucket,
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
}