diff options
Diffstat (limited to 'src/wallet/scriptpubkeyman.cpp')
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 325 |
1 files changed, 319 insertions, 6 deletions
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 8d40846332..41654579c6 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -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); @@ -1658,6 +1659,318 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const return set_address; } +const std::unordered_set<CScript, SaltedSipHasher> LegacyScriptPubKeyMan::GetScriptPubKeys() const +{ + LOCK(cs_KeyStore); + std::unordered_set<CScript, SaltedSipHasher> spks; + + // All keys have at least P2PK and P2PKH + for (const auto& key_pair : mapKeys) { + const CPubKey& pub = key_pair.second.GetPubKey(); + spks.insert(GetScriptForRawPubKey(pub)); + spks.insert(GetScriptForDestination(PKHash(pub))); + } + for (const auto& key_pair : mapCryptedKeys) { + const CPubKey& pub = key_pair.second.first; + spks.insert(GetScriptForRawPubKey(pub)); + spks.insert(GetScriptForDestination(PKHash(pub))); + } + + // For every script in mapScript, only the ISMINE_SPENDABLE ones are being tracked. + // The watchonly ones will be in setWatchOnly which we deal with later + // For all keys, if they have segwit scripts, those scripts will end up in mapScripts + for (const auto& script_pair : mapScripts) { + const CScript& script = script_pair.second; + if (IsMine(script) == ISMINE_SPENDABLE) { + // Add ScriptHash for scripts that are not already P2SH + if (!script.IsPayToScriptHash()) { + spks.insert(GetScriptForDestination(ScriptHash(script))); + } + // For segwit scripts, we only consider them spendable if we have the segwit spk + int wit_ver = -1; + std::vector<unsigned char> witprog; + if (script.IsWitnessProgram(wit_ver, witprog) && wit_ver == 0) { + spks.insert(script); + } + } else { + // Multisigs are special. They don't show up as ISMINE_SPENDABLE unless they are in a P2SH + // So check the P2SH of a multisig to see if we should insert it + std::vector<std::vector<unsigned char>> sols; + TxoutType type = Solver(script, sols); + if (type == TxoutType::MULTISIG) { + CScript ms_spk = GetScriptForDestination(ScriptHash(script)); + if (IsMine(ms_spk) != ISMINE_NO) { + spks.insert(ms_spk); + } + } + } + } + + // All watchonly scripts are raw + spks.insert(setWatchOnly.begin(), setWatchOnly.end()); + + 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; +} + +bool LegacyScriptPubKeyMan::DeleteRecords() +{ + LOCK(cs_KeyStore); + WalletBatch batch(m_storage.GetDatabase()); + return batch.EraseRecords(DBKeys::LEGACY_TYPES); +} + 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 @@ -1788,7 +2101,7 @@ std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const AssertLockHeld(cs_desc_man); if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked()) { KeyMap keys; - for (auto key_pair : m_map_crypted_keys) { + for (const auto& key_pair : m_map_crypted_keys) { const CPubKey& pubkey = key_pair.second.first; const std::vector<unsigned char>& crypted_secret = key_pair.second.second; CKey key; @@ -2327,14 +2640,14 @@ const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const return m_wallet_descriptor; } -const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const +const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys() const { LOCK(cs_desc_man); - std::vector<CScript> script_pub_keys; + std::unordered_set<CScript, SaltedSipHasher> script_pub_keys; script_pub_keys.reserve(m_map_script_pub_keys.size()); for (auto const& script_pub_key: m_map_script_pub_keys) { - script_pub_keys.push_back(script_pub_key.first); + script_pub_keys.insert(script_pub_key.first); } return script_pub_keys; } |