mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-08 20:28:55 +02:00
Implement LegacyScriptPubKeyMan::MigrateToDescriptor
This commit is contained in:
parent
ea1ab390e4
commit
35f428fae6
@ -999,9 +999,10 @@ bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& inf
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
auto it = mapKeyMetadata.find(keyID);
|
||||
if (it != mapKeyMetadata.end()) {
|
||||
meta = it->second;
|
||||
if (it == mapKeyMetadata.end()) {
|
||||
return false;
|
||||
}
|
||||
meta = it->second;
|
||||
}
|
||||
if (meta.has_key_origin) {
|
||||
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
|
||||
return spks;
|
||||
}
|
||||
|
||||
std::optional<MigrationData> LegacyScriptPubKeyMan::MigrateToDescriptor()
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
if (m_storage.IsLocked()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MigrationData out;
|
||||
|
||||
std::unordered_set<CScript, SaltedSipHasher> spks{GetScriptPubKeys()};
|
||||
|
||||
// Get all key ids
|
||||
std::set<CKeyID> keyids;
|
||||
for (const auto& key_pair : mapKeys) {
|
||||
keyids.insert(key_pair.first);
|
||||
}
|
||||
for (const auto& key_pair : mapCryptedKeys) {
|
||||
keyids.insert(key_pair.first);
|
||||
}
|
||||
|
||||
// Get key metadata and figure out which keys don't have a seed
|
||||
// Note that we do not ignore the seeds themselves because they are considered IsMine!
|
||||
for (auto keyid_it = keyids.begin(); keyid_it != keyids.end();) {
|
||||
const CKeyID& keyid = *keyid_it;
|
||||
const auto& it = mapKeyMetadata.find(keyid);
|
||||
if (it != mapKeyMetadata.end()) {
|
||||
const CKeyMetadata& meta = it->second;
|
||||
if (meta.hdKeypath == "s" || meta.hdKeypath == "m") {
|
||||
keyid_it++;
|
||||
continue;
|
||||
}
|
||||
if (m_hd_chain.seed_id == meta.hd_seed_id || m_inactive_hd_chains.count(meta.hd_seed_id) > 0) {
|
||||
keyid_it = keyids.erase(keyid_it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
keyid_it++;
|
||||
}
|
||||
|
||||
// keyids is now all non-HD keys. Each key will have its own combo descriptor
|
||||
for (const CKeyID& keyid : keyids) {
|
||||
CKey key;
|
||||
if (!GetKey(keyid, key)) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Get birthdate from key meta
|
||||
uint64_t creation_time = 0;
|
||||
const auto& it = mapKeyMetadata.find(keyid);
|
||||
if (it != mapKeyMetadata.end()) {
|
||||
creation_time = it->second.nCreateTime;
|
||||
}
|
||||
|
||||
// Get the key origin
|
||||
// Maybe this doesn't matter because floating keys here shouldn't have origins
|
||||
KeyOriginInfo info;
|
||||
bool has_info = GetKeyOrigin(keyid, info);
|
||||
std::string origin_str = has_info ? "[" + HexStr(info.fingerprint) + FormatHDKeypath(info.path) + "]" : "";
|
||||
|
||||
// Construct the combo descriptor
|
||||
std::string desc_str = "combo(" + origin_str + HexStr(key.GetPubKey()) + ")";
|
||||
FlatSigningProvider keys;
|
||||
std::string error;
|
||||
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
|
||||
WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
|
||||
|
||||
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
|
||||
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
|
||||
desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
|
||||
desc_spk_man->TopUp();
|
||||
auto desc_spks = desc_spk_man->GetScriptPubKeys();
|
||||
|
||||
// Remove the scriptPubKeys from our current set
|
||||
for (const CScript& spk : desc_spks) {
|
||||
size_t erased = spks.erase(spk);
|
||||
assert(erased == 1);
|
||||
assert(IsMine(spk) == ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
out.desc_spkms.push_back(std::move(desc_spk_man));
|
||||
}
|
||||
|
||||
// Handle HD keys by using the CHDChains
|
||||
std::vector<CHDChain> chains;
|
||||
chains.push_back(m_hd_chain);
|
||||
for (const auto& chain_pair : m_inactive_hd_chains) {
|
||||
chains.push_back(chain_pair.second);
|
||||
}
|
||||
for (const CHDChain& chain : chains) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// Skip if doing internal chain and split chain is not supported
|
||||
if (chain.seed_id.IsNull() || (i == 1 && !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))) {
|
||||
continue;
|
||||
}
|
||||
// Get the master xprv
|
||||
CKey seed_key;
|
||||
if (!GetKey(chain.seed_id, seed_key)) {
|
||||
assert(false);
|
||||
}
|
||||
CExtKey master_key;
|
||||
master_key.SetSeed(seed_key);
|
||||
|
||||
// Make the combo descriptor
|
||||
std::string xpub = EncodeExtPubKey(master_key.Neuter());
|
||||
std::string desc_str = "combo(" + xpub + "/0'/" + ToString(i) + "'/*')";
|
||||
FlatSigningProvider keys;
|
||||
std::string error;
|
||||
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
|
||||
uint32_t chain_counter = std::max((i == 1 ? chain.nInternalChainCounter : chain.nExternalChainCounter), (uint32_t)0);
|
||||
WalletDescriptor w_desc(std::move(desc), 0, 0, chain_counter, 0);
|
||||
|
||||
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
|
||||
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
|
||||
desc_spk_man->AddDescriptorKey(master_key.key, master_key.key.GetPubKey());
|
||||
desc_spk_man->TopUp();
|
||||
auto desc_spks = desc_spk_man->GetScriptPubKeys();
|
||||
|
||||
// Remove the scriptPubKeys from our current set
|
||||
for (const CScript& spk : desc_spks) {
|
||||
size_t erased = spks.erase(spk);
|
||||
assert(erased == 1);
|
||||
assert(IsMine(spk) == ISMINE_SPENDABLE);
|
||||
}
|
||||
|
||||
out.desc_spkms.push_back(std::move(desc_spk_man));
|
||||
}
|
||||
}
|
||||
// Add the current master seed to the migration data
|
||||
if (!m_hd_chain.seed_id.IsNull()) {
|
||||
CKey seed_key;
|
||||
if (!GetKey(m_hd_chain.seed_id, seed_key)) {
|
||||
assert(false);
|
||||
}
|
||||
out.master_key.SetSeed(seed_key);
|
||||
}
|
||||
|
||||
// Handle the rest of the scriptPubKeys which must be imports and may not have all info
|
||||
for (auto it = spks.begin(); it != spks.end();) {
|
||||
const CScript& spk = *it;
|
||||
|
||||
// Get birthdate from script meta
|
||||
uint64_t creation_time = 0;
|
||||
const auto& mit = m_script_metadata.find(CScriptID(spk));
|
||||
if (mit != m_script_metadata.end()) {
|
||||
creation_time = mit->second.nCreateTime;
|
||||
}
|
||||
|
||||
// InferDescriptor as that will get us all the solving info if it is there
|
||||
std::unique_ptr<Descriptor> desc = InferDescriptor(spk, *GetSolvingProvider(spk));
|
||||
// Get the private keys for this descriptor
|
||||
std::vector<CScript> scripts;
|
||||
FlatSigningProvider keys;
|
||||
if (!desc->Expand(0, DUMMY_SIGNING_PROVIDER, scripts, keys)) {
|
||||
assert(false);
|
||||
}
|
||||
std::set<CKeyID> privkeyids;
|
||||
for (const auto& key_orig_pair : keys.origins) {
|
||||
privkeyids.insert(key_orig_pair.first);
|
||||
}
|
||||
|
||||
std::vector<CScript> desc_spks;
|
||||
|
||||
// Make the descriptor string with private keys
|
||||
std::string desc_str;
|
||||
bool watchonly = !desc->ToPrivateString(*this, desc_str);
|
||||
if (watchonly && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
out.watch_descs.push_back({desc->ToString(), creation_time});
|
||||
|
||||
// Get the scriptPubKeys without writing this to the wallet
|
||||
FlatSigningProvider provider;
|
||||
desc->Expand(0, provider, desc_spks, provider);
|
||||
} else {
|
||||
// Make the DescriptorScriptPubKeyMan and get the scriptPubKeys
|
||||
WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
|
||||
auto desc_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(m_storage, w_desc));
|
||||
for (const auto& keyid : privkeyids) {
|
||||
CKey key;
|
||||
if (!GetKey(keyid, key)) {
|
||||
continue;
|
||||
}
|
||||
desc_spk_man->AddDescriptorKey(key, key.GetPubKey());
|
||||
}
|
||||
desc_spk_man->TopUp();
|
||||
auto desc_spks_set = desc_spk_man->GetScriptPubKeys();
|
||||
desc_spks.insert(desc_spks.end(), desc_spks_set.begin(), desc_spks_set.end());
|
||||
|
||||
out.desc_spkms.push_back(std::move(desc_spk_man));
|
||||
}
|
||||
|
||||
// Remove the scriptPubKeys from our current set
|
||||
for (const CScript& desc_spk : desc_spks) {
|
||||
auto del_it = spks.find(desc_spk);
|
||||
assert(del_it != spks.end());
|
||||
assert(IsMine(desc_spk) != ISMINE_NO);
|
||||
it = spks.erase(del_it);
|
||||
}
|
||||
}
|
||||
|
||||
// Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH
|
||||
// So we have to check if any of our scripts are a multisig and if so, add the P2SH
|
||||
for (const auto& script_pair : mapScripts) {
|
||||
const CScript script = script_pair.second;
|
||||
|
||||
// Get birthdate from script meta
|
||||
uint64_t creation_time = 0;
|
||||
const auto& it = m_script_metadata.find(CScriptID(script));
|
||||
if (it != m_script_metadata.end()) {
|
||||
creation_time = it->second.nCreateTime;
|
||||
}
|
||||
|
||||
std::vector<std::vector<unsigned char>> sols;
|
||||
TxoutType type = Solver(script, sols);
|
||||
if (type == TxoutType::MULTISIG) {
|
||||
CScript sh_spk = GetScriptForDestination(ScriptHash(script));
|
||||
CTxDestination witdest = WitnessV0ScriptHash(script);
|
||||
CScript witprog = GetScriptForDestination(witdest);
|
||||
CScript sh_wsh_spk = GetScriptForDestination(ScriptHash(witprog));
|
||||
|
||||
// We only want the multisigs that we have not already seen, i.e. they are not watchonly and not spendable
|
||||
// For P2SH, a multisig is not ISMINE_NO when:
|
||||
// * All keys are in the wallet
|
||||
// * The multisig itself is watch only
|
||||
// * The P2SH is watch only
|
||||
// For P2SH-P2WSH, if the script is in the wallet, then it will have the same conditions as P2SH.
|
||||
// For P2WSH, a multisig is not ISMINE_NO when, other than the P2SH conditions:
|
||||
// * The P2WSH script is in the wallet and it is being watched
|
||||
std::vector<std::vector<unsigned char>> keys(sols.begin() + 1, sols.begin() + sols.size() - 1);
|
||||
if (HaveWatchOnly(sh_spk) || HaveWatchOnly(script) || HaveKeys(keys, *this) || (HaveCScript(CScriptID(witprog)) && HaveWatchOnly(witprog))) {
|
||||
// The above emulates IsMine for these 3 scriptPubKeys, so double check that by running IsMine
|
||||
assert(IsMine(sh_spk) != ISMINE_NO || IsMine(witprog) != ISMINE_NO || IsMine(sh_wsh_spk) != ISMINE_NO);
|
||||
continue;
|
||||
}
|
||||
assert(IsMine(sh_spk) == ISMINE_NO && IsMine(witprog) == ISMINE_NO && IsMine(sh_wsh_spk) == ISMINE_NO);
|
||||
|
||||
std::unique_ptr<Descriptor> sh_desc = InferDescriptor(sh_spk, *GetSolvingProvider(sh_spk));
|
||||
out.solvable_descs.push_back({sh_desc->ToString(), creation_time});
|
||||
|
||||
const auto desc = InferDescriptor(witprog, *this);
|
||||
if (desc->IsSolvable()) {
|
||||
std::unique_ptr<Descriptor> wsh_desc = InferDescriptor(witprog, *GetSolvingProvider(witprog));
|
||||
out.solvable_descs.push_back({wsh_desc->ToString(), creation_time});
|
||||
std::unique_ptr<Descriptor> sh_wsh_desc = InferDescriptor(sh_wsh_spk, *GetSolvingProvider(sh_wsh_spk));
|
||||
out.solvable_descs.push_back({sh_wsh_desc->ToString(), creation_time});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that we have accounted for all scriptPubKeys
|
||||
assert(spks.size() == 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type)
|
||||
{
|
||||
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
|
||||
|
@ -265,6 +265,8 @@ static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
|
||||
OutputType::BECH32,
|
||||
};
|
||||
|
||||
class DescriptorScriptPubKeyMan;
|
||||
|
||||
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
|
||||
{
|
||||
private:
|
||||
@ -511,6 +513,10 @@ public:
|
||||
|
||||
std::set<CKeyID> GetKeys() const override;
|
||||
const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
|
||||
|
||||
/** Get the DescriptorScriptPubKeyMans (with private keys) that have the same scriptPubKeys as this LegacyScriptPubKeyMan.
|
||||
* Does not modify this ScriptPubKeyMan. */
|
||||
std::optional<MigrationData> MigrateToDescriptor();
|
||||
};
|
||||
|
||||
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */
|
||||
|
@ -104,6 +104,20 @@ public:
|
||||
WalletDescriptor() {}
|
||||
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) {}
|
||||
};
|
||||
|
||||
class CWallet;
|
||||
class DescriptorScriptPubKeyMan;
|
||||
|
||||
/** struct containing information needed for migrating legacy wallets to descriptor wallets */
|
||||
struct MigrationData
|
||||
{
|
||||
CExtKey master_key;
|
||||
std::vector<std::pair<std::string, int64_t>> watch_descs;
|
||||
std::vector<std::pair<std::string, int64_t>> solvable_descs;
|
||||
std::vector<std::unique_ptr<DescriptorScriptPubKeyMan>> desc_spkms;
|
||||
std::shared_ptr<CWallet> watchonly_wallet{nullptr};
|
||||
std::shared_ptr<CWallet> solvable_wallet{nullptr};
|
||||
};
|
||||
} // namespace wallet
|
||||
|
||||
#endif // BITCOIN_WALLET_WALLETUTIL_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user