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.
This commit is contained in:
Wilmer Paulino 2021-03-10 17:03:45 -08:00
parent 89e1671f0c
commit e2d54f001b
No known key found for this signature in database
GPG key ID: 6DF57B9F9514972F
2 changed files with 52 additions and 19 deletions

View file

@ -160,6 +160,12 @@ type accountInfo struct {
// intended for internal wallet use such as change addresses. // intended for internal wallet use such as change addresses.
nextInternalIndex uint32 nextInternalIndex uint32
lastInternalAddr ManagedAddress 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 // AccountProperties contains properties associated with each account, such as

View file

@ -217,14 +217,14 @@ func (s *ScopedKeyManager) Close() {
// //
// This function MUST be called with the manager lock held for writes. // This function MUST be called with the manager lock held for writes.
func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, func (s *ScopedKeyManager) keyToManaged(derivedKey *hdkeychain.ExtendedKey,
derivationPath DerivationPath) (ManagedAddress, error) { derivationPath DerivationPath, acctInfo *accountInfo) (
ManagedAddress, error) {
var addrType AddressType // Choose the appropriate type of address to derive since it's possible
if derivationPath.Branch == InternalBranch { // for a watch-only account to have a different schema from the
addrType = s.addrSchema.InternalAddrType // manager's.
} else { internal := derivationPath.Branch == InternalBranch
addrType = s.addrSchema.ExternalAddrType addrType := s.accountAddrType(acctInfo, internal)
}
// Create a new managed address based on the public or private key // Create a new managed address based on the public or private key
// depending on whether the passed key is private. Also, zero the 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, acctName: row.name,
nextExternalIndex: row.nextExternalIndex, nextExternalIndex: row.nextExternalIndex,
nextInternalIndex: row.nextInternalIndex, nextInternalIndex: row.nextInternalIndex,
addrSchema: row.addrSchema,
} }
// Use the crypto public key to decrypt the account public // Use the crypto public key to decrypt the account public
@ -401,7 +402,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
if err != nil { if err != nil {
return nil, err return nil, err
} }
lastExtAddr, err := s.keyToManaged(lastExtKey, lastExtAddrPath) lastExtAddr, err := s.keyToManaged(lastExtKey, lastExtAddrPath, acctInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -422,7 +423,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket,
if err != nil { if err != nil {
return nil, err return nil, err
} }
lastIntAddr, err := s.keyToManaged(lastIntKey, lastIntAddrPath) lastIntAddr, err := s.keyToManaged(lastIntKey, lastIntAddrPath, acctInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -503,7 +504,11 @@ func (s *ScopedKeyManager) DeriveFromKeyPath(ns walletdb.ReadBucket,
return nil, err 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 // deriveKeyFromPath returns either a public or private derived extended key
@ -552,13 +557,18 @@ func (s *ScopedKeyManager) chainAddressRowToManaged(ns walletdb.ReadBucket,
if err != nil { if err != nil {
return nil, err return nil, err
} }
acctInfo, err := s.loadAccountInfo(ns, row.account)
if err != nil {
return nil, err
}
return s.keyToManaged( return s.keyToManaged(
addressKey, DerivationPath{ addressKey, DerivationPath{
InternalAccount: row.account, InternalAccount: row.account,
Account: acctKey.ChildIndex(), Account: acctKey.ChildIndex(),
Branch: row.branch, Branch: row.branch,
Index: row.index, Index: row.index,
}, }, acctInfo,
) )
} }
@ -728,6 +738,23 @@ func (s *ScopedKeyManager) AddrAccount(ns walletdb.ReadBucket,
return account, nil 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 // nextAddresses returns the specified number of next chained address from the
// branch indicated by the internal flag. // branch indicated by the internal flag.
// //
@ -758,10 +785,10 @@ func (s *ScopedKeyManager) nextAddresses(ns walletdb.ReadWriteBucket,
nextIndex = acctInfo.nextInternalIndex nextIndex = acctInfo.nextInternalIndex
} }
addrType := s.addrSchema.ExternalAddrType // Choose the appropriate type of address to derive since it's possible
if internal { // for a watch-only account to have a different schema from the
addrType = s.addrSchema.InternalAddrType // manager's.
} addrType := s.accountAddrType(acctInfo, internal)
// Ensure the requested number of addresses doesn't exceed the maximum // Ensure the requested number of addresses doesn't exceed the maximum
// allowed for this account. // allowed for this account.
@ -955,10 +982,10 @@ func (s *ScopedKeyManager) extendAddresses(ns walletdb.ReadWriteBucket,
nextIndex = acctInfo.nextInternalIndex nextIndex = acctInfo.nextInternalIndex
} }
addrType := s.addrSchema.ExternalAddrType // Choose the appropriate type of address to derive since it's possible
if internal { // for a watch-only account to have a different schema from the
addrType = s.addrSchema.InternalAddrType // manager's.
} addrType := s.accountAddrType(acctInfo, internal)
// If the last index requested is already lower than the next index, we // If the last index requested is already lower than the next index, we
// can return early. // can return early.