diff options
Diffstat (limited to 'src/wallet/scriptpubkeyman.cpp')
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 430 |
1 files changed, 385 insertions, 45 deletions
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 8633e7c62c..41654579c6 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -21,26 +21,22 @@ namespace wallet { //! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details. const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; -bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) +util::Result<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type) { if (LEGACY_OUTPUT_TYPES.count(type) == 0) { - error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types"); - return false; + return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")}; } assert(type != OutputType::BECH32M); LOCK(cs_KeyStore); - error.clear(); // Generate a new key that is added to wallet CPubKey new_key; if (!GetKeyFromPool(new_key, type)) { - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } LearnRelatedScripts(new_key, type); - dest = GetDestinationForKey(new_key, type); - return true; + return GetDestinationForKey(new_key, type); } typedef std::vector<unsigned char> valtype; @@ -296,26 +292,22 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat return true; } -bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) +util::Result<CTxDestination> LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { if (LEGACY_OUTPUT_TYPES.count(type) == 0) { - error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types"); - return false; + return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")}; } assert(type != OutputType::BECH32M); LOCK(cs_KeyStore); if (!CanGetAddresses(internal)) { - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } if (!ReserveKeyFromKeyPool(index, keypool, internal)) { - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } - address = GetDestinationForKey(keypool.vchPubKey, type); - return true; + return GetDestinationForKey(keypool.vchPubKey, type); } bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal) @@ -1007,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); @@ -1088,6 +1081,13 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, CHDChain& hd_c return pubkey; } +//! Try to derive an extended key, throw if it fails. +static void DeriveExtKey(CExtKey& key_in, unsigned int index, CExtKey& key_out) { + if (!key_in.Derive(key_out, index)) { + throw std::runtime_error("Could not derive extended key"); + } +} + void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k @@ -1105,11 +1105,11 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& // derive m/0' // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) - masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); + DeriveExtKey(masterKey, BIP32_HARDENED_KEY_LIMIT, accountKey); // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) assert(internal ? m_storage.CanSupportFeature(FEATURE_HD_SPLIT) : true); - accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0)); + DeriveExtKey(accountKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0), chainChildKey); // derive child key at next index, skip keys already known to the wallet do { @@ -1117,7 +1117,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 if (internal) { - chainChildKey.Derive(childKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + DeriveExtKey(chainChildKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT, childKey); metadata.hdKeypath = "m/0'/1'/" + ToString(hd_chain.nInternalChainCounter) + "'"; metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT); @@ -1125,7 +1125,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& hd_chain.nInternalChainCounter++; } else { - chainChildKey.Derive(childKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); + DeriveExtKey(chainChildKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT, childKey); metadata.hdKeypath = "m/0'/0'/" + ToString(hd_chain.nExternalChainCounter) + "'"; metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); @@ -1457,7 +1457,8 @@ void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType t CTxDestination witdest = WitnessV0KeyHash(key.GetID()); CScript witprog = GetScriptForDestination(witdest); // Make sure the resulting program is solvable. - assert(IsSolvable(*this, witprog)); + const auto desc = InferDescriptor(witprog, *this); + assert(desc && desc->IsSolvable()); AddCScript(witprog); } } @@ -1658,12 +1659,323 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const return set_address; } -bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) +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 if (!CanGetAddresses()) { - error = _("No addresses available"); - return false; + return util::Error{_("No addresses available")}; } { LOCK(cs_desc_man); @@ -1681,15 +1993,14 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest std::vector<CScript> scripts_temp; if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) { // We can't generate anymore keys - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) { // We can't generate anymore keys - error = _("Error: Keypool ran out, please call keypoolrefill first"); - return false; + return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; } + CTxDestination dest; std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType(); if (out_script_type && out_script_type == type) { ExtractDestination(scripts_temp[0], dest); @@ -1698,7 +2009,7 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest } m_wallet_descriptor.next_index++; WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor); - return true; + return dest; } } @@ -1766,12 +2077,12 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle return true; } -bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error) +util::Result<CTxDestination> DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { LOCK(cs_desc_man); - bool result = GetNewDestination(type, address, error); + auto op_dest = GetNewDestination(type); index = m_wallet_descriptor.next_index - 1; - return result; + return op_dest; } void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) @@ -1790,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; @@ -1967,6 +2278,11 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ desc_prefix = "tr(" + xpub + "/86'"; break; } + case OutputType::UNKNOWN: { + // We should never have a DescriptorScriptPubKeyMan for an UNKNOWN OutputType, + // so if we get to this point something is wrong + assert(false); + } } // no default case, so the compiler can warn about missing cases assert(!desc_prefix.empty()); @@ -2076,10 +2392,21 @@ std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvid std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvider(int32_t index, bool include_private) const { AssertLockHeld(cs_desc_man); - // Get the scripts, keys, and key origins for this script + std::unique_ptr<FlatSigningProvider> out_keys = std::make_unique<FlatSigningProvider>(); - std::vector<CScript> scripts_temp; - if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr; + + // Fetch SigningProvider from cache to avoid re-deriving + auto it = m_map_signing_providers.find(index); + if (it != m_map_signing_providers.end()) { + out_keys->Merge(FlatSigningProvider{it->second}); + } else { + // Get the scripts, keys, and key origins for this script + std::vector<CScript> scripts_temp; + if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr; + + // Cache SigningProvider so we don't need to re-derive if we need this SigningProvider again + m_map_signing_providers[index] = *out_keys; + } if (HavePrivateKeys() && include_private) { FlatSigningProvider master_provider; @@ -2108,7 +2435,7 @@ bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const s if (!coin_keys) { continue; } - *keys = Merge(*keys, *coin_keys); + keys->Merge(std::move(*coin_keys)); } return ::SignTransaction(tx, keys.get(), coins, sighash, input_errors); @@ -2169,7 +2496,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& std::unique_ptr<FlatSigningProvider> keys = std::make_unique<FlatSigningProvider>(); std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, sign); if (script_keys) { - *keys = Merge(*keys, *script_keys); + keys->Merge(std::move(*script_keys)); } else { // Maybe there are pubkeys listed that we can sign for script_keys = std::make_unique<FlatSigningProvider>(); @@ -2177,7 +2504,20 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& const CPubKey& pubkey = pk_pair.first; std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey); if (pk_keys) { - *keys = Merge(*keys, *pk_keys); + keys->Merge(std::move(*pk_keys)); + } + } + for (const auto& pk_pair : input.m_tap_bip32_paths) { + const XOnlyPubKey& pubkey = pk_pair.first; + for (unsigned char prefix : {0x02, 0x03}) { + unsigned char b[33] = {prefix}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey); + if (pk_keys) { + keys->Merge(std::move(*pk_keys)); + } } } } @@ -2300,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; } |