aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/scriptpubkeyman.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/scriptpubkeyman.cpp')
-rw-r--r--src/wallet/scriptpubkeyman.cpp325
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;
}