Skip to content

Commit 35f428f

Browse files
committed
Implement LegacyScriptPubKeyMan::MigrateToDescriptor
1 parent ea1ab39 commit 35f428f

File tree

3 files changed

+275
-2
lines changed

3 files changed

+275
-2
lines changed

src/wallet/scriptpubkeyman.cpp

Lines changed: 255 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -999,9 +999,10 @@ bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& inf
999999
{
10001000
LOCK(cs_KeyStore);
10011001
auto it = mapKeyMetadata.find(keyID);
1002-
if (it != mapKeyMetadata.end()) {
1003-
meta = it->second;
1002+
if (it == mapKeyMetadata.end()) {
1003+
return false;
10041004
}
1005+
meta = it->second;
10051006
}
10061007
if (meta.has_key_origin) {
10071008
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
17111712
return spks;
17121713
}
17131714

1715+
std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
1716+
{
1717+
LOCK(cs_KeyStore);
1718+
if (m_storage.IsLocked()) {
1719+
return std::nullopt;
1720+
}
1721+
1722+
MigrationData out;
1723+
1724+
std::unordered_set<CScript, SaltedSipHasher> spks{GetScriptPubKeys()};
1725+
1726+
// Get all key ids
1727+
std::set<CKeyID> keyids;
1728+
for (const auto& key_pair : mapKeys) {
1729+
keyids.insert(key_pair.first);
1730+
}
1731+
for (const auto& key_pair : mapCryptedKeys) {
1732+
keyids.insert(key_pair.first);
1733+
}
1734+
1735+
// Get key metadata and figure out which keys don't have a seed
1736+
// Note that we do not ignore the seeds themselves because they are considered IsMine!
1737+
for (auto keyid_it = keyids.begin(); keyid_it != keyids.end();) {
1738+
const CKeyID& keyid = *keyid_it;
1739+
const auto& it = mapKeyMetadata.find(keyid);
1740+
if (it != mapKeyMetadata.end()) {
1741+
const CKeyMetadata& meta = it->second;
1742+
if (meta.hdKeypath == "s" || meta.hdKeypath == "m") {
1743+
keyid_it++;
1744+
continue;
1745+
}
1746+
if (m_hd_chain.seed_id == meta.hd_seed_id || m_inactive_hd_chains.count(meta.hd_seed_id) > 0) {
1747+
keyid_it = keyids.erase(keyid_it);
1748+
continue;
1749+
}
1750+
}
1751+
keyid_it++;
1752+
}
1753+
1754+
// keyids is now all non-HD keys. Each key will have its own combo descriptor
1755+
for (const CKeyID& keyid : keyids) {
1756+
CKey key;
1757+
if (!GetKey(keyid, key)) {
1758+
assert(false);
1759+
}
1760+
1761+
// Get birthdate from key meta
1762+
uint64_t creation_time = 0;
1763+
const auto& it = mapKeyMetadata.find(keyid);
1764+
if (it != mapKeyMetadata.end()) {
1765+
creation_time = it->second.nCreateTime;
1766+
}
1767+
1768+
// Get the key origin
1769+
// Maybe this doesn't matter because floating keys here shouldn't have origins
1770+
KeyOriginInfo info;
1771+
bool has_info = GetKeyOrigin(keyid, info);
1772+
std::string origin_str = has_info ? "[" + HexStr(info.fingerprint) + FormatHDKeypath(info.path) + "]" : "";
1773+
1774+
// Construct the combo descriptor
1775+
std::string desc_str = "combo(" + origin_str + HexStr(key.GetPubKey()) + ")";
1776+
FlatSigningProvider keys;
1777+
std::string error;
1778+
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
1779+
WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
1780+
1781+
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
1782+
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
1783+
desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
1784+
desc_spk_man->TopUp();
1785+
auto desc_spks = desc_spk_man->GetScriptPubKeys();
1786+
1787+
// Remove the scriptPubKeys from our current set
1788+
for (const CScript& spk : desc_spks) {
1789+
size_t erased = spks.erase(spk);
1790+
assert(erased == 1);
1791+
assert(IsMine(spk) == ISMINE_SPENDABLE);
1792+
}
1793+
1794+
out.desc_spkms.push_back(std::move(desc_spk_man));
1795+
}
1796+
1797+
// Handle HD keys by using the CHDChains
1798+
std::vector<CHDChain> chains;
1799+
chains.push_back(m_hd_chain);
1800+
for (const auto& chain_pair : m_inactive_hd_chains) {
1801+
chains.push_back(chain_pair.second);
1802+
}
1803+
for (const CHDChain& chain : chains) {
1804+
for (int i = 0; i < 2; ++i) {
1805+
// Skip if doing internal chain and split chain is not supported
1806+
if (chain.seed_id.IsNull() || (i == 1 && !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))) {
1807+
continue;
1808+
}
1809+
// Get the master xprv
1810+
CKey seed_key;
1811+
if (!GetKey(chain.seed_id, seed_key)) {
1812+
assert(false);
1813+
}
1814+
CExtKey master_key;
1815+
master_key.SetSeed(seed_key);
1816+
1817+
// Make the combo descriptor
1818+
std::string xpub = EncodeExtPubKey(master_key.Neuter());
1819+
std::string desc_str = "combo(" + xpub + "/0'/" + ToString(i) + "'/*')";
1820+
FlatSigningProvider keys;
1821+
std::string error;
1822+
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
1823+
uint32_t chain_counter = std::max((i == 1 ? chain.nInternalChainCounter : chain.nExternalChainCounter), (uint32_t)0);
1824+
WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0);
1825+
1826+
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
1827+
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
1828+
desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey());
1829+
desc_spk_man->TopUp();
1830+
auto desc_spks = desc_spk_man->GetScriptPubKeys();
1831+
1832+
// Remove the scriptPubKeys from our current set
1833+
for (const CScript& spk : desc_spks) {
1834+
size_t erased = spks.erase(spk);
1835+
assert(erased == 1);
1836+
assert(IsMine(spk) == ISMINE_SPENDABLE);
1837+
}
1838+
1839+
out.desc_spkms.push_back(std::move(desc_spk_man));
1840+
}
1841+
}
1842+
// Add the current master seed to the migration data
1843+
if (!m_hd_chain.seed_id.IsNull()) {
1844+
CKey seed_key;
1845+
if (!GetKey(m_hd_chain.seed_id, seed_key)) {
1846+
assert(false);
1847+
}
1848+
out.master_key.SetSeed(seed_key);
1849+
}
1850+
1851+
// Handle the rest of the scriptPubKeys which must be imports and may not have all info
1852+
for (auto it = spks.begin(); it != spks.end();) {
1853+
const CScript& spk = *it;
1854+
1855+
// Get birthdate from script meta
1856+
uint64_t creation_time = 0;
1857+
const auto& mit = m_script_metadata.find(CScriptID(spk));
1858+
if (mit != m_script_metadata.end()) {
1859+
creation_time = mit->second.nCreateTime;
1860+
}
1861+
1862+
// InferDescriptor as that will get us all the solving info if it is there
1863+
std::unique_ptr<Descriptor> desc = InferDescriptor(spk, *GetSolvingProvider(spk));
1864+
// Get the private keys for this descriptor
1865+
std::vector<CScript> scripts;
1866+
FlatSigningProvider keys;
1867+
if (!desc->Expand(0, DUMMY_SIGNING_PROVIDER, scripts, keys)) {
1868+
assert(false);
1869+
}
1870+
std::set<CKeyID> privkeyids;
1871+
for (const auto& key_orig_pair : keys.origins) {
1872+
privkeyids.insert(key_orig_pair.first);
1873+
}
1874+
1875+
std::vector<CScript> desc_spks;
1876+
1877+
// Make the descriptor string with private keys
1878+
std::string desc_str;
1879+
bool watchonly = !desc->ToPrivateString(*this, desc_str);
1880+
if (watchonly && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1881+
out.watch_descs.push_back({desc->ToString(), creation_time});
1882+
1883+
// Get the scriptPubKeys without writing this to the wallet
1884+
FlatSigningProvider provider;
1885+
desc->Expand(0, provider, desc_spks, provider);
1886+
} else {
1887+
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
1888+
WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
1889+
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
1890+
for (const auto& keyid : privkeyids) {
1891+
CKey key;
1892+
if (!GetKey(keyid, key)) {
1893+
continue;
1894+
}
1895+
desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
1896+
}
1897+
desc_spk_man->TopUp();
1898+
auto desc_spks_set = desc_spk_man->GetScriptPubKeys();
1899+
desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end());
1900+
1901+
out.desc_spkms.push_back(std::move(desc_spk_man));
1902+
}
1903+
1904+
// Remove the scriptPubKeys from our current set
1905+
for (const CScript& desc_spk : desc_spks) {
1906+
auto del_it = spks.find(desc_spk);
1907+
assert(del_it != spks.end());
1908+
assert(IsMine(desc_spk) != ISMINE_NO);
1909+
it = spks.erase(del_it);
1910+
}
1911+
}
1912+
1913+
// Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH
1914+
// So we have to check if any of our scripts are a multisig and if so, add the P2SH
1915+
for (const auto& script_pair : mapScripts) {
1916+
const CScript script = script_pair.second;
1917+
1918+
// Get birthdate from script meta
1919+
uint64_t creation_time = 0;
1920+
const auto& it = m_script_metadata.find(CScriptID(script));
1921+
if (it != m_script_metadata.end()) {
1922+
creation_time = it->second.nCreateTime;
1923+
}
1924+
1925+
std::vector<std::vector<unsigned char>> sols;
1926+
TxoutType type = Solver(script, sols);
1927+
if (type == TxoutType::MULTISIG) {
1928+
CScript sh_spk = GetScriptForDestination(ScriptHash(script));
1929+
CTxDestination witdest = WitnessV0ScriptHash(script);
1930+
CScript witprog = GetScriptForDestination(witdest);
1931+
CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog));
1932+
1933+
// We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable
1934+
// For P2SH, a multisig is not ISMINE_NO when:
1935+
// * All keys are in the wallet
1936+
// * The multisig itself is watch only
1937+
// * The P2SH is watch only
1938+
// For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH.
1939+
// For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions:
1940+
// * The P2WSH script is in the wallet and it is being watched
1941+
std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1);
1942+
if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) {
1943+
// The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine
1944+
assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO);
1945+
continue;
1946+
}
1947+
assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO);
1948+
1949+
std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk));
1950+
out.solvable_descs.push_back({sh_desc->ToString(), creation_time});
1951+
1952+
const auto desc = InferDescriptor(witprog, *this);
1953+
if (desc->IsSolvable()) {
1954+
std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog));
1955+
out.solvable_descs.push_back({wsh_desc->ToString(), creation_time});
1956+
std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk));
1957+
out.solvable_descs.push_back({sh_wsh_desc->ToString(), creation_time});
1958+
}
1959+
}
1960+
}
1961+
1962+
// Make sure that we have accounted for all scriptPubKeys
1963+
assert(spks.size() == 0);
1964+
return out;
1965+
}
1966+
17141967
util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type)
17151968
{
17161969
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later

src/wallet/scriptpubkeyman.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
265265
OutputType::BECH32,
266266
};
267267

268+
class DescriptorScriptPubKeyMan;
269+
268270
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
269271
{
270272
private:
@@ -511,6 +513,10 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
511513

512514
std::set<CKeyID> GetKeys() const override;
513515
const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
516+
517+
/** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
518+
* Does not modify this ScriptPubKeyMan. */
519+
std::optional<MigrationData> MigrateToDescriptor();
514520
};
515521

516522
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */

src/wallet/walletutil.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,20 @@ class WalletDescriptor
104104
WalletDescriptor() {}
105105
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) {}
106106
};
107+
108+
class CWallet;
109+
class DescriptorScriptPubKeyMan;
110+
111+
/** struct containing information needed for migrating legacy wallets to descriptor wallets */
112+
struct MigrationData
113+
{
114+
CExtKey master_key;
115+
std::vector<std::pair<std::string, int64_t>> watch_descs;
116+
std::vector<std::pair<std::string, int64_t>> solvable_descs;
117+
std::vector<std::unique_ptr<DescriptorScriptPubKeyMan>> desc_spkms;
118+
std::shared_ptr<CWallet> watchonly_wallet{nullptr};
119+
std::shared_ptr<CWallet> solvable_wallet{nullptr};
120+
};
107121
} // namespace wallet
108122

109123
#endif // BITCOIN_WALLET_WALLETUTIL_H

0 commit comments

Comments
 (0)