Skip to content

Commit 440ea1a

Browse files
committed
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.
1 parent b777e84 commit 440ea1a

File tree

2 files changed

+52
-45
lines changed

2 files changed

+52
-45
lines changed

src/wallet/scriptpubkeyman.cpp

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,59 +1700,62 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
17001700
return set_address;
17011701
}
17021702

1703-
std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetScriptPubKeys() const
1703+
std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetCandidateScriptPubKeys() const
17041704
{
17051705
LOCK(cs_KeyStore);
1706-
std::unordered_set<CScript, SaltedSipHasher> spks;
1706+
std::unordered_set<CScript, SaltedSipHasher> candidate_spks;
17071707

1708-
// All keys have at least P2PK and P2PKH
1709-
for (const auto& key_pair : mapKeys) {
1710-
const CPubKey& pub = key_pair.second.GetPubKey();
1711-
spks.insert(GetScriptForRawPubKey(pub));
1712-
spks.insert(GetScriptForDestination(PKHash(pub)));
1708+
// For every private key in the wallet, there should be a P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH
1709+
const auto& add_pubkey = [&candidate_spks](const CPubKey& pub) -> void {
1710+
candidate_spks.insert(GetScriptForRawPubKey(pub));
1711+
candidate_spks.insert(GetScriptForDestination(PKHash(pub)));
1712+
1713+
CScript wpkh = GetScriptForDestination(WitnessV0KeyHash(pub));
1714+
candidate_spks.insert(wpkh);
1715+
candidate_spks.insert(GetScriptForDestination(ScriptHash(wpkh)));
1716+
};
1717+
for (const auto& [_, key] : mapKeys) {
1718+
add_pubkey(key.GetPubKey());
17131719
}
1714-
for (const auto& key_pair : mapCryptedKeys) {
1715-
const CPubKey& pub = key_pair.second.first;
1716-
spks.insert(GetScriptForRawPubKey(pub));
1717-
spks.insert(GetScriptForDestination(PKHash(pub)));
1720+
for (const auto& [_, ckeypair] : mapCryptedKeys) {
1721+
add_pubkey(ckeypair.first);
17181722
}
17191723

1720-
// For every script in mapScript, only the ISMINE_SPENDABLE ones are being tracked.
1721-
// The watchonly ones will be in setWatchOnly which we deal with later
1722-
// For all keys, if they have segwit scripts, those scripts will end up in mapScripts
1723-
for (const auto& script_pair : mapScripts) {
1724-
const CScript& script = script_pair.second;
1725-
if (IsMine(script) == ISMINE_SPENDABLE) {
1726-
// Add ScriptHash for scripts that are not already P2SH
1727-
if (!script.IsPayToScriptHash()) {
1728-
spks.insert(GetScriptForDestination(ScriptHash(script)));
1729-
}
1730-
// For segwit scripts, we only consider them spendable if we have the segwit spk
1731-
int wit_ver = -1;
1732-
std::vector<unsigned char> witprog;
1733-
if (script.IsWitnessProgram(wit_ver, witprog) && wit_ver == 0) {
1734-
spks.insert(script);
1735-
}
1736-
} else {
1737-
// Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH
1738-
// So check the P2SH of a multisig to see if we should insert it
1739-
std::vector<std::vector<unsigned char>> sols;
1740-
TxoutType type = Solver(script, sols);
1741-
if (type == TxoutType::MULTISIG) {
1742-
CScript ms_spk = GetScriptForDestination(ScriptHash(script));
1743-
if (IsMine(ms_spk) != ISMINE_NO) {
1744-
spks.insert(ms_spk);
1745-
}
1746-
}
1747-
}
1724+
// mapScripts contains all redeemScripts and witnessScripts. Therefore each script in it has
1725+
// itself, P2SH, P2WSH, and P2SH-P2WSH as a candidate.
1726+
// Invalid scripts such as P2SH-P2SH and P2WSH-P2SH, among others, will be added as candidates.
1727+
// Callers of this function will need to remove such scripts.
1728+
const auto& add_script = [&candidate_spks](const CScript& script) -> void {
1729+
candidate_spks.insert(script);
1730+
candidate_spks.insert(GetScriptForDestination(ScriptHash(script)));
1731+
1732+
CScript wsh = GetScriptForDestination(WitnessV0ScriptHash(script));
1733+
candidate_spks.insert(wsh);
1734+
candidate_spks.insert(GetScriptForDestination(ScriptHash(wsh)));
1735+
};
1736+
for (const auto& [_, script] : mapScripts) {
1737+
add_script(script);
17481738
}
17491739

1750-
// All watchonly scripts are raw
1751-
for (const CScript& script : setWatchOnly) {
1752-
// As the legacy wallet allowed to import any script, we need to verify the validity here.
1753-
// LegacyScriptPubKeyMan::IsMine() return 'ISMINE_NO' for invalid or not watched scripts (IsMineResult::INVALID or IsMineResult::NO).
1754-
// e.g. a "sh(sh(pkh()))" which legacy wallets allowed to import!.
1755-
if (IsMine(script) != ISMINE_NO) spks.insert(script);
1740+
// Although setWatchOnly should only contain output scripts, we will also include each script's
1741+
// P2SH, P2WSH, and P2SH-P2WSH as a precaution.
1742+
for (const auto& script : setWatchOnly) {
1743+
add_script(script);
1744+
}
1745+
1746+
return candidate_spks;
1747+
}
1748+
1749+
std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetScriptPubKeys() const
1750+
{
1751+
// Run IsMine() on each candidate output script. Any script that is not ISMINE_NO is an output
1752+
// script to return.
1753+
// This both filters out things that are not watched by the wallet, and things that are invalid.
1754+
std::unordered_set<CScript, SaltedSipHasher> spks;
1755+
for (const CScript& script : GetCandidateScriptPubKeys()) {
1756+
if (IsMine(script) != ISMINE_NO) {
1757+
spks.insert(script);
1758+
}
17561759
}
17571760

17581761
return spks;

src/wallet/scriptpubkeyman.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ class LegacyDataSPKM : public ScriptPubKeyMan, public FillableSigningProvider
302302
virtual bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
303303
bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
304304

305+
// Helper function to retrieve a conservative superset of all output scripts that may be relevant to this LegacyDataSPKM.
306+
// It may include scripts that are invalid or not actually watched by this LegacyDataSPKM.
307+
// Used only in migration.
308+
std::unordered_set<CScript, SaltedSipHasher> GetCandidateScriptPubKeys() const;
305309
public:
306310
using ScriptPubKeyMan::ScriptPubKeyMan;
307311

0 commit comments

Comments
 (0)