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:
parent
89e1671f0c
commit
e2d54f001b
2 changed files with 52 additions and 19 deletions
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue