diff options
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 257 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.h | 6 | ||||
-rw-r--r-- | src/wallet/walletutil.h | 14 |
3 files changed, 275 insertions, 2 deletions
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 5919783ce1..3e6830a08d 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -999,9 +999,10 @@ bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& inf { LOCK(cs_KeyStore); auto it = mapKeyMetadata.find(keyID); - if (it != mapKeyMetadata.end()) { - meta = it->second; + if (it == mapKeyMetadata.end()) { + return false; } + meta = it->second; } if (meta.has_key_origin) { std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint); @@ -1711,6 +1712,258 @@ const std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScr return spks; } +std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor() +{ + LOCK(cs_KeyStore); + if (m_storage.IsLocked()) { + return std::nullopt; + } + + MigrationData out; + + std::unordered_set<CScript, SaltedSipHasher> spks{GetScriptPubKeys()}; + + // Get all key ids + std::set<CKeyID> keyids; + for (const auto& key_pair : mapKeys) { + keyids.insert(key_pair.first); + } + for (const auto& key_pair : mapCryptedKeys) { + keyids.insert(key_pair.first); + } + + // Get key metadata and figure out which keys don't have a seed + // Note that we do not ignore the seeds themselves because they are considered IsMine! + for (auto keyid_it = keyids.begin(); keyid_it != keyids.end();) { + const CKeyID& keyid = *keyid_it; + const auto& it = mapKeyMetadata.find(keyid); + if (it != mapKeyMetadata.end()) { + const CKeyMetadata& meta = it->second; + if (meta.hdKeypath == "s" || meta.hdKeypath == "m") { + keyid_it++; + continue; + } + if (m_hd_chain.seed_id == meta.hd_seed_id || m_inactive_hd_chains.count(meta.hd_seed_id) > 0) { + keyid_it = keyids.erase(keyid_it); + continue; + } + } + keyid_it++; + } + + // keyids is now all non-HD keys. Each key will have its own combo descriptor + for (const CKeyID& keyid : keyids) { + CKey key; + if (!GetKey(keyid, key)) { + assert(false); + } + + // Get birthdate from key meta + uint64_t creation_time = 0; + const auto& it = mapKeyMetadata.find(keyid); + if (it != mapKeyMetadata.end()) { + creation_time = it->second.nCreateTime; + } + + // Get the key origin + // Maybe this doesn't matter because floating keys here shouldn't have origins + KeyOriginInfo info; + bool has_info = GetKeyOrigin(keyid, info); + std::string origin_str = has_info ? "[" + HexStr(info.fingerprint) + FormatHDKeypath(info.path) + "]" : ""; + + // Construct the combo descriptor + std::string desc_str = "combo(" + origin_str + HexStr(key.GetPubKey()) + ")"; + FlatSigningProvider keys; + std::string error; + std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false); + WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); + + // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); + desc_spk_man->TopUp(); + auto desc_spks = desc_spk_man->GetScriptPubKeys(); + + // Remove the scriptPubKeys from our current set + for (const CScript& spk : desc_spks) { + size_t erased = spks.erase(spk); + assert(erased == 1); + assert(IsMine(spk) == ISMINE_SPENDABLE); + } + + out.desc_spkms.push_back(std::move(desc_spk_man)); + } + + // Handle HD keys by using the CHDChains + std::vector<CHDChain> chains; + chains.push_back(m_hd_chain); + for (const auto& chain_pair : m_inactive_hd_chains) { + chains.push_back(chain_pair.second); + } + for (const CHDChain& chain : chains) { + for (int i = 0; i < 2; ++i) { + // Skip if doing internal chain and split chain is not supported + if (chain.seed_id.IsNull() || (i == 1 && !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))) { + continue; + } + // Get the master xprv + CKey seed_key; + if (!GetKey(chain.seed_id, seed_key)) { + assert(false); + } + CExtKey master_key; + master_key.SetSeed(seed_key); + + // Make the combo descriptor + std::string xpub = EncodeExtPubKey(master_key.Neuter()); + std::string desc_str = "combo(" + xpub + "/0'/" + ToString(i) + "'/*')"; + FlatSigningProvider keys; + std::string error; + std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false); + uint32_t chain_counter = std::max((i == 1 ? chain.nInternalChainCounter : chain.nExternalChainCounter), (uint32_t)0); + WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0); + + // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey()); + desc_spk_man->TopUp(); + auto desc_spks = desc_spk_man->GetScriptPubKeys(); + + // Remove the scriptPubKeys from our current set + for (const CScript& spk : desc_spks) { + size_t erased = spks.erase(spk); + assert(erased == 1); + assert(IsMine(spk) == ISMINE_SPENDABLE); + } + + out.desc_spkms.push_back(std::move(desc_spk_man)); + } + } + // Add the current master seed to the migration data + if (!m_hd_chain.seed_id.IsNull()) { + CKey seed_key; + if (!GetKey(m_hd_chain.seed_id, seed_key)) { + assert(false); + } + out.master_key.SetSeed(seed_key); + } + + // Handle the rest of the scriptPubKeys which must be imports and may not have all info + for (auto it = spks.begin(); it != spks.end();) { + const CScript& spk = *it; + + // Get birthdate from script meta + uint64_t creation_time = 0; + const auto& mit = m_script_metadata.find(CScriptID(spk)); + if (mit != m_script_metadata.end()) { + creation_time = mit->second.nCreateTime; + } + + // InferDescriptor as that will get us all the solving info if it is there + std::unique_ptr<Descriptor> desc = InferDescriptor(spk, *GetSolvingProvider(spk)); + // Get the private keys for this descriptor + std::vector<CScript> scripts; + FlatSigningProvider keys; + if (!desc->Expand(0, DUMMY_SIGNING_PROVIDER, scripts, keys)) { + assert(false); + } + std::set<CKeyID> privkeyids; + for (const auto& key_orig_pair : keys.origins) { + privkeyids.insert(key_orig_pair.first); + } + + std::vector<CScript> desc_spks; + + // Make the descriptor string with private keys + std::string desc_str; + bool watchonly = !desc->ToPrivateString(*this, desc_str); + if (watchonly && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + out.watch_descs.push_back({desc->ToString(), creation_time}); + + // Get the scriptPubKeys without writing this to the wallet + FlatSigningProvider provider; + desc->Expand(0, provider, desc_spks, provider); + } else { + // Make the DescriptorScriptPubKeyMan and get the scriptPubKeys + WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0); + auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc)); + for (const auto& keyid : privkeyids) { + CKey key; + if (!GetKey(keyid, key)) { + continue; + } + desc_spk_man->AddDescriptorKey(key, key.GetPubKey()); + } + desc_spk_man->TopUp(); + auto desc_spks_set = desc_spk_man->GetScriptPubKeys(); + desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end()); + + out.desc_spkms.push_back(std::move(desc_spk_man)); + } + + // Remove the scriptPubKeys from our current set + for (const CScript& desc_spk : desc_spks) { + auto del_it = spks.find(desc_spk); + assert(del_it != spks.end()); + assert(IsMine(desc_spk) != ISMINE_NO); + it = spks.erase(del_it); + } + } + + // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH + // So we have to check if any of our scripts are a multisig and if so, add the P2SH + for (const auto& script_pair : mapScripts) { + const CScript script = script_pair.second; + + // Get birthdate from script meta + uint64_t creation_time = 0; + const auto& it = m_script_metadata.find(CScriptID(script)); + if (it != m_script_metadata.end()) { + creation_time = it->second.nCreateTime; + } + + std::vector<std::vector<unsigned char>> sols; + TxoutType type = Solver(script, sols); + if (type == TxoutType::MULTISIG) { + CScript sh_spk = GetScriptForDestination(ScriptHash(script)); + CTxDestination witdest = WitnessV0ScriptHash(script); + CScript witprog = GetScriptForDestination(witdest); + CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog)); + + // We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable + // For P2SH, a multisig is not ISMINE_NO when: + // * All keys are in the wallet + // * The multisig itself is watch only + // * The P2SH is watch only + // For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH. + // For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions: + // * The P2WSH script is in the wallet and it is being watched + std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1); + if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) { + // The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine + assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO); + continue; + } + assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO); + + std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk)); + out.solvable_descs.push_back({sh_desc->ToString(), creation_time}); + + const auto desc = InferDescriptor(witprog, *this); + if (desc->IsSolvable()) { + std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog)); + out.solvable_descs.push_back({wsh_desc->ToString(), creation_time}); + std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk)); + out.solvable_descs.push_back({sh_wsh_desc->ToString(), creation_time}); + } + } + } + + // Make sure that we have accounted for all scriptPubKeys + assert(spks.size() == 0); + return out; +} + util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type) { // Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 8de6fd2b7d..19ec277c56 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -265,6 +265,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES { OutputType::BECH32, }; +class DescriptorScriptPubKeyMan; + class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: @@ -511,6 +513,10 @@ public: std::set<CKeyID> GetKeys() const override; const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override; + + /** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan. + * Does not modify this ScriptPubKeyMan. */ + std::optional<MigrationData> MigrateToDescriptor(); }; /** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */ diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index 788d41ceb7..8434d64fb5 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -104,6 +104,20 @@ public: WalletDescriptor() {} WalletDescriptor(std::shared_ptr<Descriptor> descriptor, uint64_t creation_time, int32_t range_start, int32_t range_end, int32_t next_index) : descriptor(descriptor), creation_time(creation_time), range_start(range_start), range_end(range_end), next_index(next_index) {} }; + +class CWallet; +class DescriptorScriptPubKeyMan; + +/** struct containing information needed for migrating legacy wallets to descriptor wallets */ +struct MigrationData +{ + CExtKey master_key; + std::vector<std::pair<std::string, int64_t>> watch_descs; + std::vector<std::pair<std::string, int64_t>> solvable_descs; + std::vector<std::unique_ptr<DescriptorScriptPubKeyMan>> desc_spkms; + std::shared_ptr<CWallet> watchonly_wallet{nullptr}; + std::shared_ptr<CWallet> solvable_wallet{nullptr}; +}; } // namespace wallet #endif // BITCOIN_WALLET_WALLETUTIL_H |