waddrmgr/scoped_manager: adds extend in/ex ternal addrs
This commit is contained in:
parent
5e07326784
commit
57b37d38f6
1 changed files with 231 additions and 7 deletions
|
@ -53,6 +53,17 @@ type KeyScope struct {
|
|||
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 {
|
||||
|
@ -204,7 +215,7 @@ func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
|
|||
account, branch, index uint32) (ManagedAddress, error) {
|
||||
|
||||
var addrType AddressType
|
||||
if branch == internalBranch {
|
||||
if branch == InternalBranch {
|
||||
addrType = s.addrSchema.InternalAddrType
|
||||
} else {
|
||||
addrType = s.addrSchema.ExternalAddrType
|
||||
|
@ -233,7 +244,7 @@ func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
|
|||
s.deriveOnUnlock = append(s.deriveOnUnlock, &info)
|
||||
}
|
||||
|
||||
if branch == internalBranch {
|
||||
if branch == InternalBranch {
|
||||
ma.internal = true
|
||||
}
|
||||
|
||||
|
@ -297,7 +308,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
|
|||
row, ok := rowInterface.(*dbDefaultAccountRow)
|
||||
if !ok {
|
||||
str := fmt.Sprintf("unsupported account type %T", row)
|
||||
err = managerError(ErrDatabase, str, nil)
|
||||
return nil, managerError(ErrDatabase, str, nil)
|
||||
}
|
||||
|
||||
// Use the crypto public key to decrypt the account public extended
|
||||
|
@ -345,7 +356,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
|
|||
}
|
||||
|
||||
// Derive and cache the managed address for the last external address.
|
||||
branch, index := externalBranch, row.nextExternalIndex
|
||||
branch, index := ExternalBranch, row.nextExternalIndex
|
||||
if index > 0 {
|
||||
index--
|
||||
}
|
||||
|
@ -362,7 +373,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
|
|||
acctInfo.lastExternalAddr = lastExtAddr
|
||||
|
||||
// Derive and cache the managed address for the last internal address.
|
||||
branch, index = internalBranch, row.nextInternalIndex
|
||||
branch, index = InternalBranch, row.nextInternalIndex
|
||||
if index > 0 {
|
||||
index--
|
||||
}
|
||||
|
@ -670,9 +681,9 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
|
|||
|
||||
// Choose the branch key and index depending on whether or not this is
|
||||
// an internal address.
|
||||
branchNum, nextIndex := externalBranch, acctInfo.nextExternalIndex
|
||||
branchNum, nextIndex := ExternalBranch, acctInfo.nextExternalIndex
|
||||
if internal {
|
||||
branchNum = internalBranch
|
||||
branchNum = InternalBranch
|
||||
nextIndex = acctInfo.nextInternalIndex
|
||||
}
|
||||
|
||||
|
@ -817,6 +828,183 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
|
|||
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
|
||||
if !s.rootManager.IsLocked() {
|
||||
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
|
||||
}
|
||||
|
||||
addrType := s.addrSchema.ExternalAddrType
|
||||
if internal {
|
||||
addrType = s.addrSchema.InternalAddrType
|
||||
}
|
||||
|
||||
// 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.Child(branchNum)
|
||||
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.Child(nextIndex)
|
||||
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
|
||||
}
|
||||
|
||||
// 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, account, 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() && !s.rootManager.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,
|
||||
|
@ -851,6 +1039,42 @@ func (s *ScopedKeyManager) NextInternalAddresses(ns walletdb.ReadWriteBucket,
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue