@@ -1717,42 +1717,191 @@ std::unordered_set<CScript, SaltedSipHasher> LegacyDataSPKM::GetScriptPubKeys()
1717
1717
spks.insert (GetScriptForDestination (PKHash (pub)));
1718
1718
}
1719
1719
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.
1723
1733
for (const auto & script_pair : mapScripts) {
1724
1734
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 ) {
1728
1757
spks.insert (GetScriptForDestination (ScriptHash (script)));
1729
1758
}
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)) {
1734
1766
spks.insert (script);
1767
+ // Also insert P2SH-P2WPKH output script
1768
+ spks.insert (GetScriptForDestination (ScriptHash (script)));
1735
1769
}
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 ;
1746
1779
}
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
+ }
1747
1791
}
1748
1792
}
1749
1793
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
+ }
1750
1899
// All watchonly scripts are raw
1751
1900
for (const CScript& script : setWatchOnly) {
1752
1901
// As the legacy wallet allowed to import any script, we need to verify the validity here.
1753
1902
// LegacyScriptPubKeyMan::IsMine() return 'ISMINE_NO' for invalid or not watched scripts (IsMineResult::INVALID or IsMineResult::NO).
1754
1903
// 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);
1756
1905
}
1757
1906
1758
1907
return spks;
@@ -1845,7 +1994,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
1845
1994
for (const CScript& spk : desc_spks) {
1846
1995
size_t erased = spks.erase (spk);
1847
1996
assert (erased == 1 );
1848
- assert (IsMine (spk) == ISMINE_SPENDABLE);
1849
1997
}
1850
1998
1851
1999
out.desc_spkms .push_back (std::move (desc_spk_man));
@@ -1891,7 +2039,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
1891
2039
for (const CScript& spk : desc_spks) {
1892
2040
size_t erased = spks.erase (spk);
1893
2041
assert (erased == 1 );
1894
- assert (IsMine (spk) == ISMINE_SPENDABLE);
1895
2042
}
1896
2043
1897
2044
out.desc_spkms .push_back (std::move (desc_spk_man));
@@ -1963,7 +2110,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
1963
2110
for (const CScript& desc_spk : desc_spks) {
1964
2111
auto del_it = spks.find (desc_spk);
1965
2112
assert (del_it != spks.end ());
1966
- assert (IsMine (desc_spk) != ISMINE_NO);
1967
2113
it = spks.erase (del_it);
1968
2114
}
1969
2115
}
@@ -1998,8 +2144,6 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
1998
2144
// * The P2WSH script is in the wallet and it is being watched
1999
2145
std::vector<std::vector<unsigned char >> keys (sols.begin () + 1 , sols.begin () + sols.size () - 1 );
2000
2146
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);
2003
2147
continue ;
2004
2148
}
2005
2149
assert (IsMine (sh_spk) == ISMINE_NO && IsMine (witprog) == ISMINE_NO && IsMine (sh_wsh_spk) == ISMINE_NO);
0 commit comments