From 9d909110f9cdbfa832af373170774a816acf6dd6 Mon Sep 17 00:00:00 2001 From: Wilmer Paulino Date: Thu, 11 Mar 2021 13:50:09 -0800 Subject: [PATCH] waddrmgr: use proper version for account key of non-watch-only accounts --- waddrmgr/manager.go | 2 + waddrmgr/scoped_manager.go | 103 +++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index 5de694f..72a7984 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -144,6 +144,8 @@ type addrKey string type accountInfo struct { acctName string + acctType accountType + // The account key is used to derive the branches which in turn derive // the internal and external addresses. The accountKeyPriv will be nil // when the address manager is locked. diff --git a/waddrmgr/scoped_manager.go b/waddrmgr/scoped_manager.go index 41b1fba..d9b40f5 100644 --- a/waddrmgr/scoped_manager.go +++ b/waddrmgr/scoped_manager.go @@ -1,18 +1,50 @@ package waddrmgr import ( + "encoding/binary" "fmt" "sync" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/internal/zero" "github.com/btcsuite/btcwallet/walletdb" ) +// HDVersion represents the different supported schemes of hierarchical +// derivation. +type HDVersion uint32 + +const ( + // HDVersionMainNetBIP0044 is the HDVersion for BIP-0044 on the main + // network. + HDVersionMainNetBIP0044 HDVersion = 0x0488b21e // xpub + + // HDVersionMainNetBIP0049 is the HDVersion for BIP-0049 on the main + // network. + HDVersionMainNetBIP0049 HDVersion = 0x049d7cb2 // ypub + + // HDVersionMainNetBIP0084 is the HDVersion for BIP-0084 on the main + // network. + HDVersionMainNetBIP0084 HDVersion = 0x04b24746 // zpub + + // HDVersionTestNetBIP0044 is the HDVersion for BIP-0044 on the test + // network. + HDVersionTestNetBIP0044 HDVersion = 0x043587cf // tpub + + // HDVersionTestNetBIP0049 is the HDVersion for BIP-0049 on the test + // network. + HDVersionTestNetBIP0049 HDVersion = 0x044a5262 // upub + + // HDVersionTestNetBIP0084 is the HDVersion for BIP-0084 on the test + // network. + HDVersionTestNetBIP0084 HDVersion = 0x045f1cf6 // vpub +) + // DerivationPath represents a derivation path from a particular key manager's // scope. Each ScopedKeyManager starts key derivation from the end of their // cointype hardened key: m/purpose'/cointype'. The fields in this struct allow @@ -331,6 +363,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, case *dbDefaultAccountRow: acctInfo = &accountInfo{ acctName: row.name, + acctType: row.acctType, acctKeyEncrypted: row.privKeyEncrypted, nextExternalIndex: row.nextExternalIndex, nextInternalIndex: row.nextInternalIndex, @@ -363,6 +396,7 @@ func (s *ScopedKeyManager) loadAccountInfo(ns walletdb.ReadBucket, case *dbWatchOnlyAccountRow: acctInfo = &accountInfo{ acctName: row.name, + acctType: row.acctType, nextExternalIndex: row.nextExternalIndex, nextInternalIndex: row.nextInternalIndex, addrSchema: row.addrSchema, @@ -472,6 +506,29 @@ func (s *ScopedKeyManager) AccountProperties(ns walletdb.ReadBucket, props.IsWatchOnly = s.rootManager.WatchOnly() || acctInfo.acctKeyPriv == nil props.AddrSchema = acctInfo.addrSchema + + // Export the account public key with the correct version + // corresponding to the manager's key scope for non-watch-only + // accounts. This isn't done for watch-only accounts to maintain + // the account public key consistent with what the caller + // provided. Note that his is only done for the default key + // scopes, as we only know the HD versions for those. + isDefaultKeyScope := false + for _, scope := range DefaultKeyScopes { + if s.scope == scope { + isDefaultKeyScope = true + break + } + } + if acctInfo.acctType == accountDefault && isDefaultKeyScope { + props.AccountPubKey, err = s.cloneKeyWithVersion( + acctInfo.acctKeyPub, + ) + if err != nil { + return nil, fmt.Errorf("failed to retrieve "+ + "account public key: %v", err) + } + } } else { props.AccountName = ImportedAddrAccountName // reserved, nonchangable props.IsWatchOnly = s.rootManager.WatchOnly() @@ -2118,3 +2175,49 @@ func (s *ScopedKeyManager) ForEachInternalActiveAddress(ns walletdb.ReadBucket, return nil } + +// cloneKeyWithVersion clones an extended key to use the version corresponding +// to the manager's key scope. This should only be used for non-watch-only +// accounts as they are stored within the database using the legacy BIP-0044 +// version by default. +func (s *ScopedKeyManager) cloneKeyWithVersion(key *hdkeychain.ExtendedKey) ( + *hdkeychain.ExtendedKey, error) { + + // Determine the appropriate version based on the current network and + // key scope. + var version HDVersion + net := s.rootManager.ChainParams().Net + switch net { + case wire.MainNet: + switch s.scope { + case KeyScopeBIP0044: + version = HDVersionMainNetBIP0044 + case KeyScopeBIP0049Plus: + version = HDVersionMainNetBIP0049 + case KeyScopeBIP0084: + version = HDVersionMainNetBIP0084 + default: + return nil, fmt.Errorf("unsupported scope %v", s.scope) + } + + case wire.TestNet, wire.TestNet3: + switch s.scope { + case KeyScopeBIP0044: + version = HDVersionTestNetBIP0044 + case KeyScopeBIP0049Plus: + version = HDVersionTestNetBIP0049 + case KeyScopeBIP0084: + version = HDVersionTestNetBIP0084 + default: + return nil, fmt.Errorf("unsupported scope %v", s.scope) + } + + default: + return nil, fmt.Errorf("unsupported net %v", net) + } + + var versionBytes [4]byte + binary.BigEndian.PutUint32(versionBytes[:], uint32(version)) + + return key.CloneWithVersion(versionBytes[:]) +}