Skip to content

Commit f66fa34

Browse files
committed
wallet: Remove IsMine from migration
As IsMine will be removed, the relevant components of IsMine are inlined into the migration functions.
1 parent e6342ff commit f66fa34

File tree

1 file changed

+170
-26
lines changed

1 file changed

+170
-26
lines changed

src/wallet/scriptpubkeyman.cpp

+170-26
Original file line numberDiff line numberDiff line change
@@ -1717,42 +1717,191 @@ std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetScriptPubKeys()
17171717
spks.insert(GetScriptForDestination(PKHash(pub)));
17181718
}
17191719

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
1720+
// Lambda helper to check that all keys found by the solver are compressed
1721+
const auto& all_keys_compressed = [](const std::vector<valtype>& keys) -> bool {
1722+
return std::all_of(keys.cbegin(), keys.cend(),
1723+
[](const auto& key) { return key.size() == CPubKey::COMPRESSED_SIZE; });
1724+
};
1725+
1726+
// mapScripts is iterated to compute all additional spendable output scripts that utilize the contained scripts
1727+
// as redeemScripts and witnessScripts.
1728+
//
1729+
// mapScripts contains redeemScripts and witnessScripts. It may also contain output scripts which,
1730+
// in addition to being treated as output scripts, are also treated as redeemScripts and witnessScripts.
1731+
// All scripts in mapScripts are treated as redeemScripts, unless that script is also a P2SH.
1732+
// A script is only treated as a witnessScript if there its corresponding P2WSH output script is in the map.
17231733
for (const auto& script_pair : mapScripts) {
17241734
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()) {
1735+
std::vector<std::vector<unsigned char>> solutions;
1736+
TxoutType type = Solver(script, solutions);
1737+
switch (type) {
1738+
// We don't care about these types because they are not spendable
1739+
case TxoutType::NONSTANDARD:
1740+
case TxoutType::NULL_DATA:
1741+
case TxoutType::WITNESS_UNKNOWN:
1742+
case TxoutType::SCRIPTHASH:
1743+
case TxoutType::ANCHOR:
1744+
// P2TR are not spendable as the legacy wallet never supported them.
1745+
case TxoutType::WITNESS_V1_TAPROOT:
1746+
// These are only spendable if the witness script is also spendable as a scriptPubKey
1747+
// We will check these later after "spks" has been updated with the computed output scripts from mapScripts.
1748+
case TxoutType::WITNESS_V0_SCRIPTHASH:
1749+
break;
1750+
// Any P2PK or P2PKH scripts found in mapScripts can be spent as P2SH-P2PK or P2SH-P2PKH respectively,
1751+
// if we have the private key.
1752+
// Since all private keys were iterated earlier and their corresponding P2PK and P2PKH scripts inserted
1753+
// to "spks", we can simply check whether this P2PK or P2PKH script is in "spk" to determine spendability.
1754+
case TxoutType::PUBKEY:
1755+
case TxoutType::PUBKEYHASH:
1756+
if (spks.count(script) > 0) {
17281757
spks.insert(GetScriptForDestination(ScriptHash(script)));
17291758
}
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) {
1759+
break;
1760+
// P2WPKH scripts are only spendable if we have the private key.
1761+
case TxoutType::WITNESS_V0_KEYHASH:
1762+
{
1763+
CKeyID key_id{uint160(solutions[0])};
1764+
CPubKey pubkey;
1765+
if (GetPubKey(key_id, pubkey) && pubkey.IsCompressed() && HaveKey(key_id)) {
17341766
spks.insert(script);
1767+
// Also insert P2SH-P2WPKH output script
1768+
spks.insert(GetScriptForDestination(ScriptHash(script)));
17351769
}
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-
}
1770+
break;
1771+
}
1772+
// Multisig scripts are spendable if they are inside of a P2SH or P2WSH, and we have all of the private keys.
1773+
// Bare multisigs are never considered spendable
1774+
case TxoutType::MULTISIG:
1775+
{
1776+
std::vector<std::vector<unsigned char>> keys(solutions.begin() + 1, solutions.begin() + solutions.size() - 1);
1777+
if (!HaveKeys(keys, *this)) {
1778+
break;
17461779
}
1780+
// Insert P2SH-Multisig
1781+
spks.insert(GetScriptForDestination(ScriptHash(script)));
1782+
// P2WSH-Multisig output scripts are spendable if the P2WSH output script is also in mapScripts,
1783+
// and all keys are compressed
1784+
CScript ms_wsh = GetScriptForDestination(WitnessV0ScriptHash(script));
1785+
if (HaveCScript(CScriptID(ms_wsh)) && all_keys_compressed(keys)) {
1786+
spks.insert(ms_wsh);
1787+
spks.insert(GetScriptForDestination(ScriptHash(ms_wsh)));
1788+
}
1789+
break;
1790+
}
17471791
}
17481792
}
17491793

1794+
// Enum to track the execution context of a script, similar to the script interpreter's SigVersion.
1795+
// It is separate to distinguish between top level scriptPubKey execution and P2SH redeemScript execution
1796+
// which SigVersion does not distinguish. It also excludes Taproot and Tapscript as the legacy wallet
1797+
// never supported those.
1798+
enum class ScriptContext {
1799+
TOP,
1800+
P2SH,
1801+
P2WSH,
1802+
};
1803+
// Lambda helper function to determine whether a script is valid, mainly looking at key compression requirements.
1804+
std::function<bool(const CScript&, const ScriptContext)> is_valid_script = [&](const CScript& script, const ScriptContext ctx) -> bool {
1805+
std::vector<valtype> solutions;
1806+
TxoutType spk_type = Solver(script, solutions);
1807+
1808+
CKeyID keyID;
1809+
switch (spk_type) {
1810+
// Scripts with no nesting (arbitrary, unknown scripts) are always valid.
1811+
case TxoutType::NONSTANDARD:
1812+
case TxoutType::NULL_DATA:
1813+
case TxoutType::WITNESS_UNKNOWN:
1814+
case TxoutType::ANCHOR:
1815+
// Taproot output scripts are always valid. Legacy wallets did not support Taproot spending so no nested inspection is required.
1816+
case TxoutType::WITNESS_V1_TAPROOT:
1817+
return ctx == ScriptContext::TOP;
1818+
// P2PK in any nesting level is valid, but inside of P2WSH, the pubkey must also be compressed.
1819+
case TxoutType::PUBKEY:
1820+
if (ctx == ScriptContext::P2WSH && solutions[0].size() != CPubKey::COMPRESSED_SIZE) return false;
1821+
return true;
1822+
// P2WPKH is allowed as an output script or in P2SH, but not inside of P2WSH
1823+
case TxoutType::WITNESS_V0_KEYHASH:
1824+
return ctx != ScriptContext::P2WSH;
1825+
// P2PKH in any nesting level is valid, but inside of P2WSH, the pubkey must also be compressed.
1826+
// If the pubkey cannot be retrieved to check for compression, then the P2WSH-P2PKH is allowed.
1827+
case TxoutType::PUBKEYHASH:
1828+
if (ctx == ScriptContext::P2WSH) {
1829+
CPubKey pubkey;
1830+
if (GetPubKey(CKeyID(uint160(solutions[0])), pubkey) && !pubkey.IsCompressed()) {
1831+
return false;
1832+
}
1833+
}
1834+
return true;
1835+
// P2SH is allowed only as an output script.
1836+
// If the redeemScript is known, it must also be a valid script within P2SH.
1837+
// If the redeemScript is not known, the P2SH output script is valid.
1838+
case TxoutType::SCRIPTHASH:
1839+
{
1840+
if (ctx != ScriptContext::TOP) return false;
1841+
CScriptID script_id = CScriptID(uint160(solutions[0]));
1842+
CScript subscript;
1843+
if (GetCScript(script_id, subscript)) {
1844+
return is_valid_script(subscript, ScriptContext::P2SH);
1845+
}
1846+
return true;
1847+
}
1848+
// P2WSH is allowed as an output script or inside of P2SH, but not P2WSH.
1849+
// If the witnessScript is known, it must also be a valid script within P2WSH.
1850+
// If the witnessScript is not known, the P2WSH output script is valid.
1851+
case TxoutType::WITNESS_V0_SCRIPTHASH:
1852+
{
1853+
if (ctx == ScriptContext::P2WSH) return false;
1854+
// CScriptID is the hash160 of the script. For P2WSH, we already have the SHA256 of the script,
1855+
// so only RIPEMD160 of that is required to get the CScriptID for lookup.
1856+
CScriptID script_id{RIPEMD160(solutions[0])};
1857+
CScript subscript;
1858+
if (GetCScript(script_id, subscript)) {
1859+
return is_valid_script(subscript, ScriptContext::P2WSH);
1860+
}
1861+
return true;
1862+
}
1863+
// Multisig in any nesting level is valid, but inside of P2WSH, all pubkeys must be compressed.
1864+
case TxoutType::MULTISIG:
1865+
{
1866+
if (ctx == ScriptContext::P2WSH) {
1867+
std::vector<std::vector<unsigned char>> keys(solutions.begin() + 1, solutions.begin() + solutions.size() - 1);
1868+
if (!all_keys_compressed(keys)) {
1869+
return false;
1870+
}
1871+
}
1872+
return true;
1873+
}
1874+
}
1875+
assert(false);
1876+
};
1877+
// Iterate again for all the P2WSH scripts
1878+
for (const auto& [id, script] : mapScripts) {
1879+
std::vector<std::vector<unsigned char>> solutions;
1880+
TxoutType type = Solver(script, solutions);
1881+
if (type == TxoutType::WITNESS_V0_SCRIPTHASH) {
1882+
CScript witness_script;
1883+
int wit_ver = -1;
1884+
std::vector<unsigned char> wit_prog;
1885+
// For a P2WSH output script to be spendable, we must know and inspect its witnessScript.
1886+
if (GetCScript(id, witness_script) &&
1887+
!witness_script.IsPayToScriptHash() && // P2SH inside of P2WSH is not allowed
1888+
!witness_script.IsWitnessProgram(wit_ver, wit_prog) && // Witness programs are not allowed inside of P2WSH
1889+
// We only allow scripts that we would consider spendable as an output script.
1890+
// Note that while this would exclude P2WSH-multisigs, we are already handling those in the first loop.
1891+
spks.count(witness_script) > 0 &&
1892+
// Pubkeys must be compressed.
1893+
is_valid_script(witness_script, ScriptContext::P2WSH)) {
1894+
spks.insert(script);
1895+
spks.insert(GetScriptForDestination(ScriptHash(script)));
1896+
}
1897+
}
1898+
}
17501899
// All watchonly scripts are raw
17511900
for (const CScript& script : setWatchOnly) {
17521901
// As the legacy wallet allowed to import any script, we need to verify the validity here.
17531902
// LegacyScriptPubKeyMan::IsMine() return 'ISMINE_NO' for invalid or not watched scripts (IsMineResult::INVALID or IsMineResult::NO).
17541903
// e.g. a "sh(sh(pkh()))" which legacy wallets allowed to import!.
1755-
if (IsMine(script) != ISMINE_NO) spks.insert(script);
1904+
if (is_valid_script(script, ScriptContext::TOP)) spks.insert(script);
17561905
}
17571906

17581907
return spks;
@@ -1845,7 +1994,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
18451994
for (const CScript& spk : desc_spks) {
18461995
size_t erased = spks.erase(spk);
18471996
assert(erased == 1);
1848-
assert(IsMine(spk) == ISMINE_SPENDABLE);
18491997
}
18501998

18511999
out.desc_spkms.push_back(std::move(desc_spk_man));
@@ -1891,7 +2039,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
18912039
for (const CScript& spk : desc_spks) {
18922040
size_t erased = spks.erase(spk);
18932041
assert(erased == 1);
1894-
assert(IsMine(spk) == ISMINE_SPENDABLE);
18952042
}
18962043

18972044
out.desc_spkms.push_back(std::move(desc_spk_man));
@@ -1963,7 +2110,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
19632110
for (const CScript& desc_spk : desc_spks) {
19642111
auto del_it = spks.find(desc_spk);
19652112
assert(del_it != spks.end());
1966-
assert(IsMine(desc_spk) != ISMINE_NO);
19672113
it = spks.erase(del_it);
19682114
}
19692115
}
@@ -1998,8 +2144,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
19982144
// * The P2WSH script is in the wallet and it is being watched
19992145
std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1);
20002146
if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) {
2001-
// The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine
2002-
assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO);
20032147
continue;
20042148
}
20052149
assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO);

0 commit comments

Comments
 (0)