From 440ea1ab6393638a49a2ba6daefe0b29778bea7e Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Fri, 13 Dec 2024 14:41:32 -0500 Subject: [PATCH] legacy spkm: use IsMine() to extract watched output scripts Instead of (partially) trying to reverse IsMine() to get the output scripts that a LegacySPKM would track, we can preserve it in migration only code and utilize it to get an accurate set of output scripts. This is accomplished by computing a set of output script candidates from map(Crypted)Keys, mapScripts, and setWatchOnly. This candidate set is an upper bound on the scripts tracked by the wallet. Then IsMine() is used to filter to the exact output scripts that LegacySPKM would track. By changing GetScriptPubKeys() this way, we can avoid complexities in reversing IsMine() and get a more complete set of output scripts. --- src/wallet/scriptpubkeyman.cpp | 101 +++++++++++++++++---------------- src/wallet/scriptpubkeyman.h | 4 ++ 2 files changed, 56 insertions(+), 49 deletions(-) diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 115b4b0b67e..504d5db811e 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -1700,61 +1700,64 @@ std::set LegacyScriptPubKeyMan::GetKeys() const return set_address; } -std::unordered_set LegacyDataSPKM::GetScriptPubKeys() const +std::unordered_set LegacyDataSPKM::GetCandidateScriptPubKeys() const { LOCK(cs_KeyStore); + std::unordered_set candidate_spks; + + // For every private key in the wallet, there should be a P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH + const auto& add_pubkey = [&candidate_spks](const CPubKey& pub) -> void { + candidate_spks.insert(GetScriptForRawPubKey(pub)); + candidate_spks.insert(GetScriptForDestination(PKHash(pub))); + + CScript wpkh = GetScriptForDestination(WitnessV0KeyHash(pub)); + candidate_spks.insert(wpkh); + candidate_spks.insert(GetScriptForDestination(ScriptHash(wpkh))); + }; + for (const auto& [_, key] : mapKeys) { + add_pubkey(key.GetPubKey()); + } + for (const auto& [_, ckeypair] : mapCryptedKeys) { + add_pubkey(ckeypair.first); + } + + // mapScripts contains all redeemScripts and witnessScripts. Therefore each script in it has + // itself, P2SH, P2WSH, and P2SH-P2WSH as a candidate. + // Invalid scripts such as P2SH-P2SH and P2WSH-P2SH, among others, will be added as candidates. + // Callers of this function will need to remove such scripts. + const auto& add_script = [&candidate_spks](const CScript& script) -> void { + candidate_spks.insert(script); + candidate_spks.insert(GetScriptForDestination(ScriptHash(script))); + + CScript wsh = GetScriptForDestination(WitnessV0ScriptHash(script)); + candidate_spks.insert(wsh); + candidate_spks.insert(GetScriptForDestination(ScriptHash(wsh))); + }; + for (const auto& [_, script] : mapScripts) { + add_script(script); + } + + // Although setWatchOnly should only contain output scripts, we will also include each script's + // P2SH, P2WSH, and P2SH-P2WSH as a precaution. + for (const auto& script : setWatchOnly) { + add_script(script); + } + + return candidate_spks; +} + +std::unordered_set LegacyDataSPKM::GetScriptPubKeys() const +{ + // Run IsMine() on each candidate output script. Any script that is not ISMINE_NO is an output + // script to return. + // This both filters out things that are not watched by the wallet, and things that are invalid. std::unordered_set spks; - - // All keys have at least P2PK and P2PKH - for (const auto& key_pair : mapKeys) { - const CPubKey& pub = key_pair.second.GetPubKey(); - spks.insert(GetScriptForRawPubKey(pub)); - spks.insert(GetScriptForDestination(PKHash(pub))); - } - for (const auto& key_pair : mapCryptedKeys) { - const CPubKey& pub = key_pair.second.first; - spks.insert(GetScriptForRawPubKey(pub)); - spks.insert(GetScriptForDestination(PKHash(pub))); - } - - // For every script in mapScript, only the ISMINE_SPENDABLE ones are being tracked. - // The watchonly ones will be in setWatchOnly which we deal with later - // For all keys, if they have segwit scripts, those scripts will end up in mapScripts - for (const auto& script_pair : mapScripts) { - const CScript& script = script_pair.second; - if (IsMine(script) == ISMINE_SPENDABLE) { - // Add ScriptHash for scripts that are not already P2SH - if (!script.IsPayToScriptHash()) { - spks.insert(GetScriptForDestination(ScriptHash(script))); - } - // For segwit scripts, we only consider them spendable if we have the segwit spk - int wit_ver = -1; - std::vector witprog; - if (script.IsWitnessProgram(wit_ver, witprog) && wit_ver == 0) { - spks.insert(script); - } - } else { - // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH - // So check the P2SH of a multisig to see if we should insert it - std::vector> sols; - TxoutType type = Solver(script, sols); - if (type == TxoutType::MULTISIG) { - CScript ms_spk = GetScriptForDestination(ScriptHash(script)); - if (IsMine(ms_spk) != ISMINE_NO) { - spks.insert(ms_spk); - } - } + for (const CScript& script : GetCandidateScriptPubKeys()) { + if (IsMine(script) != ISMINE_NO) { + spks.insert(script); } } - // All watchonly scripts are raw - for (const CScript& script : setWatchOnly) { - // As the legacy wallet allowed to import any script, we need to verify the validity here. - // LegacyScriptPubKeyMan::IsMine() return 'ISMINE_NO' for invalid or not watched scripts (IsMineResult::INVALID or IsMineResult::NO). - // e.g. a "sh(sh(pkh()))" which legacy wallets allowed to import!. - if (IsMine(script) != ISMINE_NO) spks.insert(script); - } - return spks; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index c5e45e4fa8f..c88abbda6d6 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -302,6 +302,10 @@ protected: virtual bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); + // Helper function to retrieve a conservative superset of all output scripts that may be relevant to this LegacyDataSPKM. + // It may include scripts that are invalid or not actually watched by this LegacyDataSPKM. + // Used only in migration. + std::unordered_set GetCandidateScriptPubKeys() const; public: using ScriptPubKeyMan::ScriptPubKeyMan;