From e2d54f001b22224c448a93987bb83a236c098436 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Wed, 10 Mar 2021 17:03:45 -0800 Subject: [PATCH] waddrmgr: derive account addresses with schema override This change was motivated by the need to support importing BIP-0049 keys that use the standard address derivation scheme, where nested witness pubkeys are used for both the external and internal branches. Our BIP-0049 key scope is slightly different, in that addresses derived from the internal branch use the witness pubkey address type. By having the option of overriding the address schema for a particular account, we can support importing standard BIP-0049 keys. --- waddrmgr/manager.go | 6 ++++ waddrmgr/scoped_manager.go | 65 +++++++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index aa2710e..bf1ca6f 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -160,6 +160,12 @@ type accountInfo struct { // intended for internal wallet use such as change addresses. nextInternalIndex uint32 lastInternalAddr ManagedAddress + + // addrSchema serves as a way for an account to override its + // corresponding address schema with a custom one. For example, this + // could be used to import accounts that use the traditional BIP-0049 + // derivation scheme into our KeyScopeBIP-0049Plus manager. + addrSchema *ScopeAddrSchema } // AccountProperties contains properties associated with each account, such as diff --git a/waddrmgr/scoped_manager.go b/waddrmgr/scoped_manager.go index 298d175..239cda6 100644 --- a/waddrmgr/scoped_manager.go +++ b/waddrmgr/scoped_manager.go @@ -217,14 +217,14 @@ func (s *ScopedKeyManager) Close() { // // This function MUST be called with the manager lock held for writes. func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, - derivationPath DerivationPath) (ManagedAddress, error) { + derivationPath DerivationPath, acctInfo *accountInfo) ( + ManagedAddress, error) { - var addrType AddressType - if derivationPath.Branch == InternalBranch { - addrType = s.addrSchema.InternalAddrType - } else { - addrType = s.addrSchema.ExternalAddrType - } + // 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 @@ -365,6 +365,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, acctName: row.name, nextExternalIndex: row.nextExternalIndex, nextInternalIndex: row.nextInternalIndex, + addrSchema: row.addrSchema, } // Use the crypto public key to decrypt the account public @@ -401,7 +402,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, if err != nil { return nil, err } - lastExtAddr, err := s.keyToManaged(lastExtKey, lastExtAddrPath) + lastExtAddr, err := s.keyToManaged(lastExtKey, lastExtAddrPath, acctInfo) if err != nil { return nil, err } @@ -422,7 +423,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, if err != nil { return nil, err } - lastIntAddr, err := s.keyToManaged(lastIntKey, lastIntAddrPath) + lastIntAddr, err := s.keyToManaged(lastIntKey, lastIntAddrPath, acctInfo) if err != nil { return nil, err } @@ -503,7 +504,11 @@ func (s *ScopedKeyManager) DeriveFromKeyPath(ns walletdb.ReadBucket, return nil, err } - return s.keyToManaged(addrKey, kp) + 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 @@ -552,13 +557,18 @@ func (s *ScopedKeyManager) chainAddressRowToManaged(ns walletdb.ReadBucket, 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, - }, + }, acctInfo, ) } @@ -728,6 +738,23 @@ func (s *ScopedKeyManager) AddrAccount(ns walletdb.ReadBucket, 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. // @@ -758,10 +785,10 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket, nextIndex = acctInfo.nextInternalIndex } - addrType := s.addrSchema.ExternalAddrType - if internal { - addrType = s.addrSchema.InternalAddrType - } + // 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. @@ -955,10 +982,10 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket, nextIndex = acctInfo.nextInternalIndex } - addrType := s.addrSchema.ExternalAddrType - if internal { - addrType = s.addrSchema.InternalAddrType - } + // 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.