From e88b01e4b6112161006f43dc61d32051efb84eec Mon Sep 17 00:00:00 2001 From: Ava Chow Date: Fri, 5 Jan 2024 18:42:23 -0500 Subject: [PATCH] wallet: Delete LegacySPKM Deletes LegacyScriptPubKeyMan and related tests --- src/Makefile.test.include | 2 - src/interfaces/wallet.h | 3 - src/qt/overviewpage.cpp | 26 +- src/qt/sendcoinsdialog.cpp | 3 - src/wallet/feebumper.cpp | 2 +- src/wallet/interfaces.cpp | 8 +- src/wallet/rpc/addresses.cpp | 8 - src/wallet/rpc/coins.cpp | 9 - src/wallet/rpc/wallet.cpp | 8 - src/wallet/scriptpubkeyman.cpp | 1638 ++------------------- src/wallet/scriptpubkeyman.h | 195 --- src/wallet/spend.cpp | 2 - src/wallet/test/ismine_tests.cpp | 724 --------- src/wallet/test/scriptpubkeyman_tests.cpp | 44 - src/wallet/test/walletdb_tests.cpp | 26 - src/wallet/wallet.cpp | 216 +-- src/wallet/wallet.h | 9 - src/wallet/walletdb.cpp | 17 - src/wallet/wallettool.cpp | 3 +- 19 files changed, 125 insertions(+), 2818 deletions(-) delete mode 100644 src/wallet/test/ismine_tests.cpp delete mode 100644 src/wallet/test/scriptpubkeyman_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 7486103f303ecf..ffef294c986216 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -186,9 +186,7 @@ BITCOIN_TESTS += \ wallet/test/wallet_transaction_tests.cpp \ wallet/test/coinselector_tests.cpp \ wallet/test/init_tests.cpp \ - wallet/test/ismine_tests.cpp \ wallet/test/rpc_util_tests.cpp \ - wallet/test/scriptpubkeyman_tests.cpp \ wallet/test/walletload_tests.cpp \ wallet/test/group_outputs_tests.cpp diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index a01588f360c57b..87b58bc660e05a 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -278,9 +278,6 @@ class Wallet // Remove wallet. virtual void remove() = 0; - //! Return whether is a legacy wallet - virtual bool isLegacy() = 0; - //! Register handler for unload message. using UnloadFn = std::function; virtual std::unique_ptr handleUnload(UnloadFn fn) = 0; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 7d584920822c89..f6ee169abe0780 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -195,28 +195,10 @@ OverviewPage::~OverviewPage() void OverviewPage::setBalance(const interfaces::WalletBalances& balances) { BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit(); - if (walletModel->wallet().isLegacy()) { - if (walletModel->wallet().privateKeysDisabled()) { - ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - } else { - ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchAvailable->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchPending->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelWatchTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - } - } else { - ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); - } + ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); + ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); + ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy)); // only show immature (newly mined) balance if it's non-zero, so as not to complicate things // for the non-mining users bool showImmature = balances.immature_balance != 0; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 0d8c0f7a636b46..63a3cd7b31e7c4 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -705,9 +705,6 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances) CAmount balance = balances.balance; if (model->wallet().hasExternalSigner()) { ui->labelBalanceName->setText(tr("External balance:")); - } else if (model->wallet().isLegacy() && model->wallet().privateKeysDisabled()) { - balance = balances.watch_only_balance; - ui->labelBalanceName->setText(tr("Watch-only balance:")); } ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); } diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 6a8453965be710..5469d77b907ebc 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -52,7 +52,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet if (require_mine) { // check that original tx consists entirely of our inputs // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) - isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + isminefilter filter = ISMINE_SPENDABLE; if (!AllInputsMine(wallet, *wtx.tx, filter)) { errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); return feebumper::Result::WALLET_ERROR; diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 41cd2a5ec7d22f..710312a126b3c0 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -175,11 +175,7 @@ class WalletImpl : public Wallet } bool haveWatchOnly() override { - auto spk_man = m_wallet->GetLegacyScriptPubKeyMan(); - if (spk_man) { - return spk_man->HaveWatchOnly(); - } - return false; + return m_wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS); }; bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional& purpose) override { @@ -514,7 +510,6 @@ class WalletImpl : public Wallet bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); } bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } bool taprootEnabled() override { - if (m_wallet->IsLegacy()) return false; auto spk_man = m_wallet->GetScriptPubKeyMan(OutputType::BECH32M, /*internal=*/false); return spk_man != nullptr; } @@ -524,7 +519,6 @@ class WalletImpl : public Wallet { RemoveWallet(m_context, m_wallet, /*load_on_start=*/false); } - bool isLegacy() override { return m_wallet->IsLegacy(); } std::unique_ptr handleUnload(UnloadFn fn) override { return MakeSignalHandler(m_wallet->NotifyUnload.connect(fn)); diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index 5664a4c0cd20e6..5050e72625c50c 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -54,8 +54,6 @@ RPCHelpMan getnewaddress() std::optional parsed = ParseOutputType(request.params[1].get_str()); if (!parsed) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); - } else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); } output_type = parsed.value(); } @@ -101,8 +99,6 @@ RPCHelpMan getrawchangeaddress() std::optional parsed = ParseOutputType(request.params[0].get_str()); if (!parsed) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); - } else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); } output_type = parsed.value(); } @@ -233,10 +229,6 @@ RPCHelpMan keypoolrefill() std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return UniValue::VNULL; - if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } - LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 2cf94a57223044..20751651a9eddf 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -479,15 +479,6 @@ RPCHelpMan getbalances() } balances.pushKV("mine", std::move(balances_mine)); } - auto spk_man = wallet.GetLegacyScriptPubKeyMan(); - if (spk_man && spk_man->HaveWatchOnly()) { - UniValue balances_watchonly{UniValue::VOBJ}; - balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted)); - balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending)); - balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature)); - balances.pushKV("watchonly", std::move(balances_watchonly)); - } - AppendLastProcessedBlock(balances, wallet); return balances; }, diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 8664a22c1bd2d0..4ae9d815632c73 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -107,14 +107,6 @@ static RPCHelpMan getwalletinfo() } obj.pushKV("keypoolsize", (int64_t)kpExternalSize); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (spk_man) { - CKeyID seed_id = spk_man->GetHDChain().seed_id; - if (!seed_id.IsNull()) { - obj.pushKV("hdseedid", seed_id.GetHex()); - } - } - if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); } diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 6dea37f0915b21..0d8ff2dbabd3c5 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -21,65 +21,9 @@ #include 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; - -util::Result LegacyScriptPubKeyMan::GetNewDestination(const OutputType type) -{ - if (LEGACY_OUTPUT_TYPES.count(type) == 0) { - return util::Error{_("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types")}; - } - assert(type != OutputType::BECH32M); - - // Fill-up keypool if needed - TopUp(); - - LOCK(cs_KeyStore); - - // Generate a new key that is added to wallet - CPubKey new_key; - if (!GetKeyFromPool(new_key, type)) { - return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; - } - LearnRelatedScripts(new_key, type); - return GetDestinationForKey(new_key, type); -} typedef std::vector valtype; -namespace { - -/** - * This is an enum that tracks the execution context of a script, similar to - * SigVersion in script/interpreter. It is separate however because we want to - * distinguish between top-level scriptPubKey execution and P2SH redeemScript - * execution (a distinction that has no impact on consensus rules). - */ -enum class IsMineSigVersion -{ - TOP = 0, //!< scriptPubKey execution - P2SH = 1, //!< P2SH redeemScript - WITNESS_V0 = 2, //!< P2WSH witness script execution -}; - -/** - * This is an internal representation of isminetype + invalidity. - * Its order is significant, as we return the max of all explored - * possibilities. - */ -enum class IsMineResult -{ - NO = 0, //!< Not ours - WATCH_ONLY = 1, //!< Included in watch-only balance - SPENDABLE = 2, //!< Included in all balances - INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness) -}; - -bool PermitsUncompressed(IsMineSigVersion sigversion) -{ - return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; -} - bool HaveKeys(const std::vector& pubkeys, const LegacyDataSPKM& keystore) { for (const valtype& pubkey : pubkeys) { @@ -89,144 +33,6 @@ bool HaveKeys(const std::vector& pubkeys, const LegacyDataSPKM& keystor return true; } -//! Recursively solve script and return spendable/watchonly/invalid status. -//! -//! @param keystore legacy key and script store -//! @param scriptPubKey script to solve -//! @param sigversion script type (top-level / redeemscript / witnessscript) -//! @param recurse_scripthash whether to recurse into nested p2sh and p2wsh -//! scripts or simply treat any script that has been -//! stored in the keystore as spendable -// NOLINTNEXTLINE(misc-no-recursion) -IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion, bool recurse_scripthash=true) -{ - IsMineResult ret = IsMineResult::NO; - - std::vector vSolutions; - TxoutType whichType = Solver(scriptPubKey, vSolutions); - - CKeyID keyID; - switch (whichType) { - case TxoutType::NONSTANDARD: - case TxoutType::NULL_DATA: - case TxoutType::WITNESS_UNKNOWN: - case TxoutType::WITNESS_V1_TAPROOT: - break; - case TxoutType::PUBKEY: - keyID = CPubKey(vSolutions[0]).GetID(); - if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { - return IsMineResult::INVALID; - } - if (keystore.HaveKey(keyID)) { - ret = std::max(ret, IsMineResult::SPENDABLE); - } - break; - case TxoutType::WITNESS_V0_KEYHASH: - { - if (sigversion == IsMineSigVersion::WITNESS_V0) { - // P2WPKH inside P2WSH is invalid. - return IsMineResult::INVALID; - } - if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { - // We do not support bare witness outputs unless the P2SH version of it would be - // acceptable as well. This protects against matching before segwit activates. - // This also applies to the P2WSH case. - break; - } - ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0)); - break; - } - case TxoutType::PUBKEYHASH: - keyID = CKeyID(uint160(vSolutions[0])); - if (!PermitsUncompressed(sigversion)) { - CPubKey pubkey; - if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { - return IsMineResult::INVALID; - } - } - if (keystore.HaveKey(keyID)) { - ret = std::max(ret, IsMineResult::SPENDABLE); - } - break; - case TxoutType::SCRIPTHASH: - { - if (sigversion != IsMineSigVersion::TOP) { - // P2SH inside P2WSH or P2SH is invalid. - return IsMineResult::INVALID; - } - CScriptID scriptID = CScriptID(uint160(vSolutions[0])); - CScript subscript; - if (keystore.GetCScript(scriptID, subscript)) { - ret = std::max(ret, recurse_scripthash ? IsMineInner(keystore, subscript, IsMineSigVersion::P2SH) : IsMineResult::SPENDABLE); - } - break; - } - case TxoutType::WITNESS_V0_SCRIPTHASH: - { - if (sigversion == IsMineSigVersion::WITNESS_V0) { - // P2WSH inside P2WSH is invalid. - return IsMineResult::INVALID; - } - if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { - break; - } - CScriptID scriptID{RIPEMD160(vSolutions[0])}; - CScript subscript; - if (keystore.GetCScript(scriptID, subscript)) { - ret = std::max(ret, recurse_scripthash ? IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0) : IsMineResult::SPENDABLE); - } - break; - } - - case TxoutType::MULTISIG: - { - // Never treat bare multisig outputs as ours (they can still be made watchonly-though) - if (sigversion == IsMineSigVersion::TOP) { - break; - } - - // Only consider transactions "mine" if we own ALL the - // keys involved. Multi-signature transactions that are - // partially owned (somebody else has a key that can spend - // them) enable spend-out-from-under-you attacks, especially - // in shared-wallet situations. - std::vector keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); - if (!PermitsUncompressed(sigversion)) { - for (size_t i = 0; i < keys.size(); i++) { - if (keys[i].size() != 33) { - return IsMineResult::INVALID; - } - } - } - if (HaveKeys(keys, keystore)) { - ret = std::max(ret, IsMineResult::SPENDABLE); - } - break; - } - } // no default case, so the compiler can warn about missing cases - - if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { - ret = std::max(ret, IsMineResult::WATCH_ONLY); - } - return ret; -} - -} // namespace - -isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const -{ - switch (IsMineInner(*this, script, IsMineSigVersion::TOP)) { - case IsMineResult::INVALID: - case IsMineResult::NO: - return ISMINE_NO; - case IsMineResult::WATCH_ONLY: - return ISMINE_WATCH_ONLY; - case IsMineResult::SPENDABLE: - return ISMINE_SPENDABLE; - } - assert(false); -} - bool LegacyDataSPKM::CheckDecryptionKey(const CKeyingMaterial& master_key) { { @@ -267,914 +73,198 @@ bool LegacyDataSPKM::CheckDecryptionKey(const CKeyingMaterial& master_key) return true; } -bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) +std::unique_ptr LegacyDataSPKM::GetSolvingProvider(const CScript& script) const { - LOCK(cs_KeyStore); - encrypted_batch = batch; - if (!mapCryptedKeys.empty()) { - encrypted_batch = nullptr; - return false; - } - - KeyMap keys_to_encrypt; - keys_to_encrypt.swap(mapKeys); // Clear mapKeys so AddCryptedKeyInner will succeed. - for (const KeyMap::value_type& mKey : keys_to_encrypt) - { - const CKey &key = mKey.second; - CPubKey vchPubKey = key.GetPubKey(); - CKeyingMaterial vchSecret{UCharCast(key.begin()), UCharCast(key.end())}; - std::vector vchCryptedSecret; - if (!EncryptSecret(master_key, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) { - encrypted_batch = nullptr; - return false; - } - if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) { - encrypted_batch = nullptr; - return false; - } - } - encrypted_batch = nullptr; - return true; + return std::make_unique(*this); } -util::Result LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) +bool LegacyDataSPKM::LoadKey(const CKey& key, const CPubKey &pubkey) { - if (LEGACY_OUTPUT_TYPES.count(type) == 0) { - 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)) { - return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; - } - - // Fill-up keypool if needed - TopUp(); - - if (!ReserveKeyFromKeyPool(index, keypool, internal)) { - return util::Error{_("Error: Keypool ran out, please call keypoolrefill first")}; - } - return GetDestinationForKey(keypool.vchPubKey, type); + return AddKeyPubKeyInner(key, pubkey); } -bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal) +bool LegacyDataSPKM::LoadCScript(const CScript& redeemScript) { - LOCK(cs_KeyStore); - - auto it = m_inactive_hd_chains.find(seed_id); - if (it == m_inactive_hd_chains.end()) { - return false; - } - - CHDChain& chain = it->second; - - if (internal) { - chain.m_next_internal_index = std::max(chain.m_next_internal_index, index + 1); - } else { - chain.m_next_external_index = std::max(chain.m_next_external_index, index + 1); + /* A sanity check was added in pull #3843 to avoid adding redeemScripts + * that never can be redeemed. However, old wallets may still contain + * these. Do not add them to the wallet and warn. */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) + { + std::string strAddr = EncodeDestination(ScriptHash(redeemScript)); + WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); + return true; } - WalletBatch batch(m_storage.GetDatabase()); - TopUpChain(batch, chain, 0); - - return true; + return FillableSigningProvider::AddCScript(redeemScript); } -std::vector LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script) +void LegacyDataSPKM::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) { LOCK(cs_KeyStore); - std::vector result; - // extract addresses and check if they match with an unused keypool key - for (const auto& keyid : GetAffectedKeys(script, *this)) { - std::map::const_iterator mi = m_pool_key_to_index.find(keyid); - if (mi != m_pool_key_to_index.end()) { - WalletLogPrintf("%s: Detected a used keypool key, mark all keypool keys up to this key as used\n", __func__); - for (const auto& keypool : MarkReserveKeysAsUsed(mi->second)) { - // derive all possible destinations as any of them could have been used - for (const auto& type : LEGACY_OUTPUT_TYPES) { - const auto& dest = GetDestinationForKey(keypool.vchPubKey, type); - result.push_back({dest, keypool.fInternal}); - } - } - - if (!TopUp()) { - WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); - } - } - - // Find the key's metadata and check if it's seed id (if it has one) is inactive, i.e. it is not the current m_hd_chain seed id. - // If so, TopUp the inactive hd chain - auto it = mapKeyMetadata.find(keyid); - if (it != mapKeyMetadata.end()){ - CKeyMetadata meta = it->second; - if (!meta.hd_seed_id.IsNull() && meta.hd_seed_id != m_hd_chain.seed_id) { - std::vector path; - if (meta.has_key_origin) { - path = meta.key_origin.path; - } else if (!ParseHDKeypath(meta.hdKeypath, path)) { - WalletLogPrintf("%s: Adding inactive seed keys failed, invalid hdKeypath: %s\n", - __func__, - meta.hdKeypath); - } - if (path.size() != 3) { - WalletLogPrintf("%s: Adding inactive seed keys failed, invalid path size: %d, has_key_origin: %s\n", - __func__, - path.size(), - meta.has_key_origin); - } else { - bool internal = (path[1] & ~BIP32_HARDENED_KEY_LIMIT) != 0; - int64_t index = path[2] & ~BIP32_HARDENED_KEY_LIMIT; - - if (!TopUpInactiveHDChain(meta.hd_seed_id, index, internal)) { - WalletLogPrintf("%s: Adding inactive seed keys failed\n", __func__); - } - } - } - } - } - - return result; + mapKeyMetadata[keyID] = meta; } -void LegacyScriptPubKeyMan::UpgradeKeyMetadata() +void LegacyDataSPKM::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) { LOCK(cs_KeyStore); - if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { - return; - } - - std::unique_ptr batch = std::make_unique(m_storage.GetDatabase()); - for (auto& meta_pair : mapKeyMetadata) { - CKeyMetadata& meta = meta_pair.second; - if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin - CKey key; - GetKey(meta.hd_seed_id, key); - CExtKey masterKey; - masterKey.SetSeed(key); - // Add to map - CKeyID master_id = masterKey.key.GetPubKey().GetID(); - std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint); - if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) { - throw std::runtime_error("Invalid stored hdKeypath"); - } - meta.has_key_origin = true; - if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) { - meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN; - } - - // Write meta to wallet - CPubKey pubkey; - if (GetPubKey(meta_pair.first, pubkey)) { - batch->WriteKeyMetadata(meta, pubkey, true); - } - } - } -} - -bool LegacyScriptPubKeyMan::SetupGeneration(bool force) -{ - if ((CanGenerateKeys() && !force) || m_storage.IsLocked()) { - return false; - } - - SetHDSeed(GenerateNewSeed()); - if (!NewKeyPool()) { - return false; - } - return true; + m_script_metadata[script_id] = meta; } -bool LegacyScriptPubKeyMan::IsHDEnabled() const +bool LegacyDataSPKM::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) { - return !m_hd_chain.seed_id.IsNull(); + // This function should only be called during loading of a legacy to be migrated. + // As such, the wallet should not be encrypted if this is called. + LOCK(cs_KeyStore); + assert(!m_storage.HasEncryptionKeys()); + return FillableSigningProvider::AddKeyPubKey(key, pubkey); } -bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) const +bool LegacyDataSPKM::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid) { - LOCK(cs_KeyStore); - // Check if the keypool has keys - bool keypool_has_keys; - if (internal && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) { - keypool_has_keys = setInternalKeyPool.size() > 0; - } else { - keypool_has_keys = KeypoolCountExternalKeys() > 0; - } - // If the keypool doesn't have keys, check if we can generate them - if (!keypool_has_keys) { - return CanGenerateKeys(); + // Set fDecryptionThoroughlyChecked to false when the checksum is invalid + if (!checksum_valid) { + fDecryptionThoroughlyChecked = false; } - return keypool_has_keys; + + return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); } -bool LegacyScriptPubKeyMan::Upgrade(int prev_version, int new_version, bilingual_str& error) +bool LegacyDataSPKM::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { LOCK(cs_KeyStore); + assert(mapKeys.empty()); - if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - // Nothing to do here if private keys are not enabled - return true; - } - - bool hd_upgrade = false; - bool split_upgrade = false; - if (IsFeatureSupported(new_version, FEATURE_HD) && !IsHDEnabled()) { - WalletLogPrintf("Upgrading wallet to HD\n"); - m_storage.SetMinVersion(FEATURE_HD); - - // generate a new master key - CPubKey masterPubKey = GenerateNewSeed(); - SetHDSeed(masterPubKey); - hd_upgrade = true; - } - // Upgrade to HD chain split if necessary - if (!IsFeatureSupported(prev_version, FEATURE_HD_SPLIT) && IsFeatureSupported(new_version, FEATURE_HD_SPLIT)) { - WalletLogPrintf("Upgrading wallet to use HD chain split\n"); - m_storage.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); - split_upgrade = FEATURE_HD_SPLIT > prev_version; - // Upgrade the HDChain - if (m_hd_chain.nVersion < CHDChain::VERSION_HD_CHAIN_SPLIT) { - m_hd_chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT; - if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(m_hd_chain)) { - throw std::runtime_error(std::string(__func__) + ": writing chain failed"); - } - } - } - // Mark all keys currently in the keypool as pre-split - if (split_upgrade) { - MarkPreSplitKeys(); - } - // Regenerate the keypool if upgraded to HD - if (hd_upgrade) { - if (!NewKeyPool()) { - error = _("Unable to generate keys"); - return false; - } - } + mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); + ImplicitlyLearnRelatedKeyScripts(vchPubKey); return true; } -bool LegacyScriptPubKeyMan::HavePrivateKeys() const +bool LegacyDataSPKM::HaveWatchOnly(const CScript &dest) const { LOCK(cs_KeyStore); - return !mapKeys.empty() || !mapCryptedKeys.empty(); + return setWatchOnly.count(dest) > 0; } -void LegacyScriptPubKeyMan::RewriteDB() +bool LegacyDataSPKM::HaveWatchOnly() const { LOCK(cs_KeyStore); - setInternalKeyPool.clear(); - setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); - // Note: can't top-up keypool here, because wallet is locked. - // User will be prompted to unlock wallet the next operation - // that requires a new key. + return (!setWatchOnly.empty()); } -static int64_t GetOldestKeyTimeInPool(const std::set& setKeyPool, WalletBatch& batch) { - if (setKeyPool.empty()) { - return GetTime(); - } +bool LegacyDataSPKM::LoadWatchOnly(const CScript &dest) +{ + return AddWatchOnlyInMem(dest); +} - CKeyPool keypool; - int64_t nIndex = *(setKeyPool.begin()); - if (!batch.ReadPool(nIndex, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); - } - assert(keypool.vchPubKey.IsValid()); - return keypool.nTime; +static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) +{ + std::vector> solutions; + return Solver(dest, solutions) == TxoutType::PUBKEY && + (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); } -std::optional LegacyScriptPubKeyMan::GetOldestKeyPoolTime() const +bool LegacyDataSPKM::AddWatchOnlyInMem(const CScript &dest) { LOCK(cs_KeyStore); - - WalletBatch batch(m_storage.GetDatabase()); - - // load oldest key from keypool, get time and return - int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch); - if (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) { - oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey); - if (!set_pre_split_keypool.empty()) { - oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey); - } + setWatchOnly.insert(dest); + CPubKey pubKey; + if (ExtractPubKey(dest, pubKey)) { + mapWatchKeys[pubKey.GetID()] = pubKey; + ImplicitlyLearnRelatedKeyScripts(pubKey); } - - return oldestKey; + return true; } -size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys() const +void LegacyDataSPKM::LoadHDChain(const CHDChain& chain) { LOCK(cs_KeyStore); - return setExternalKeyPool.size() + set_pre_split_keypool.size(); + m_hd_chain = chain; } -unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const +void LegacyDataSPKM::AddInactiveHDChain(const CHDChain& chain) { LOCK(cs_KeyStore); - return setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(); + assert(!chain.seed_id.IsNull()); + m_inactive_hd_chains[chain.seed_id] = chain; } -int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const +bool LegacyDataSPKM::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); - return nTimeFirstKey; + if (!m_storage.HasEncryptionKeys()) { + return FillableSigningProvider::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; } -std::unique_ptr LegacyDataSPKM::GetSolvingProvider(const CScript& script) const +bool LegacyDataSPKM::GetKey(const CKeyID &address, CKey& keyOut) const { - return std::make_unique(*this); -} + LOCK(cs_KeyStore); + if (!m_storage.HasEncryptionKeys()) { + return FillableSigningProvider::GetKey(address, keyOut); + } -bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sigdata) -{ - IsMineResult ismine = IsMineInner(*this, script, IsMineSigVersion::TOP, /* recurse_scripthash= */ false); - if (ismine == IsMineResult::SPENDABLE || ismine == IsMineResult::WATCH_ONLY) { - // If ismine, it means we recognize keys or script ids in the script, or - // are watching the script itself, and we can at least provide metadata - // or solving information, even if not able to sign fully. - return true; - } else { - // If, given the stuff in sigdata, we could make a valid signature, then we can provide for this script - ProduceSignature(*this, DUMMY_SIGNATURE_CREATOR, script, sigdata); - if (!sigdata.signatures.empty()) { - // If we could make signatures, make sure we have a private key to actually make a signature - bool has_privkeys = false; - for (const auto& key_sig_pair : sigdata.signatures) { - has_privkeys |= HaveKey(key_sig_pair.first); - } - return has_privkeys; - } - return false; + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + const CPubKey &vchPubKey = (*mi).second.first; + const std::vector &vchCryptedSecret = (*mi).second.second; + return m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { + return DecryptKey(encryption_key, vchCryptedSecret, vchPubKey, keyOut); + }); } + return false; } -bool LegacyScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const +bool LegacyDataSPKM::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const { - return ::SignTransaction(tx, this, coins, sighash, input_errors); + CKeyMetadata meta; + { + LOCK(cs_KeyStore); + auto it = mapKeyMetadata.find(keyID); + 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); + info.path = meta.key_origin.path; + } else { // Single pubkeys get the master fingerprint of themselves + std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint); + } + return true; } -SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const +bool LegacyDataSPKM::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const { - CKey key; - if (!GetKey(ToKeyID(pkhash), key)) { - return SigningResult::PRIVATE_KEY_NOT_AVAILABLE; - } - - if (MessageSign(key, message, str_sig)) { - return SigningResult::OK; + LOCK(cs_KeyStore); + WatchKeyMap::const_iterator it = mapWatchKeys.find(address); + if (it != mapWatchKeys.end()) { + pubkey_out = it->second; + return true; } - return SigningResult::SIGNING_FAILED; -} - -TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const -{ - if (n_signed) { - *n_signed = 0; - } - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - const CTxIn& txin = psbtx.tx->vin[i]; - PSBTInput& input = psbtx.inputs.at(i); - - if (PSBTInputSigned(input)) { - continue; - } - - // Get the Sighash type - if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) { - return TransactionError::SIGHASH_MISMATCH; - } - - // Check non_witness_utxo has specified prevout - if (input.non_witness_utxo) { - if (txin.prevout.n >= input.non_witness_utxo->vout.size()) { - return TransactionError::MISSING_INPUTS; - } - } else if (input.witness_utxo.IsNull()) { - // There's no UTXO so we can just skip this now - continue; - } - SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); - - bool signed_one = PSBTInputSigned(input); - if (n_signed && (signed_one || !sign)) { - // If sign is false, we assume that we _could_ sign if we get here. This - // will never have false negatives; it is hard to tell under what i - // circumstances it could have false positives. - (*n_signed)++; - } - } - - // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change - for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i); - } - - return TransactionError::OK; -} - -std::unique_ptr LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const -{ - LOCK(cs_KeyStore); - - CKeyID key_id = GetKeyForDestination(*this, dest); - if (!key_id.IsNull()) { - auto it = mapKeyMetadata.find(key_id); - if (it != mapKeyMetadata.end()) { - return std::make_unique(it->second); - } - } - - CScript scriptPubKey = GetScriptForDestination(dest); - auto it = m_script_metadata.find(CScriptID(scriptPubKey)); - if (it != m_script_metadata.end()) { - return std::make_unique(it->second); - } - - return nullptr; -} - -uint256 LegacyScriptPubKeyMan::GetID() const -{ - return uint256::ONE; -} - -/** - * Update wallet first key creation time. This should be called whenever keys - * are added to the wallet, with the oldest key creation time. - */ -void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime) -{ - AssertLockHeld(cs_KeyStore); - if (nCreateTime <= 1) { - // Cannot determine birthday information, so set the wallet birthday to - // the beginning of time. - nTimeFirstKey = 1; - } else if (nTimeFirstKey == UNKNOWN_TIME || nCreateTime < nTimeFirstKey) { - nTimeFirstKey = nCreateTime; - } - - NotifyFirstKeyTimeChanged(this, nTimeFirstKey); -} - -bool LegacyDataSPKM::LoadKey(const CKey& key, const CPubKey &pubkey) -{ - return AddKeyPubKeyInner(key, pubkey); -} - -bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) -{ - LOCK(cs_KeyStore); - WalletBatch batch(m_storage.GetDatabase()); - return LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey); -} - -bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) -{ - AssertLockHeld(cs_KeyStore); - - // Make sure we aren't adding private keys to private key disabled wallets - assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - - // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey - // which is overridden below. To avoid flushes, the database handle is - // tunneled through to it. - bool needsDB = !encrypted_batch; - if (needsDB) { - encrypted_batch = &batch; - } - if (!AddKeyPubKeyInner(secret, pubkey)) { - if (needsDB) encrypted_batch = nullptr; - return false; - } - if (needsDB) encrypted_batch = nullptr; - - // check if we need to remove from watch-only - CScript script; - script = GetScriptForDestination(PKHash(pubkey)); - if (HaveWatchOnly(script)) { - RemoveWatchOnly(script); - } - script = GetScriptForRawPubKey(pubkey); - if (HaveWatchOnly(script)) { - RemoveWatchOnly(script); - } - - m_storage.UnsetBlankWalletFlag(batch); - if (!m_storage.HasEncryptionKeys()) { - return batch.WriteKey(pubkey, - secret.GetPrivKey(), - mapKeyMetadata[pubkey.GetID()]); - } - return true; -} - -bool LegacyDataSPKM::LoadCScript(const CScript& redeemScript) -{ - /* A sanity check was added in pull #3843 to avoid adding redeemScripts - * that never can be redeemed. However, old wallets may still contain - * these. Do not add them to the wallet and warn. */ - if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) - { - std::string strAddr = EncodeDestination(ScriptHash(redeemScript)); - WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); - return true; - } - - return FillableSigningProvider::AddCScript(redeemScript); -} - -void LegacyDataSPKM::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) -{ - LOCK(cs_KeyStore); - mapKeyMetadata[keyID] = meta; -} - -void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) -{ - LOCK(cs_KeyStore); - LegacyDataSPKM::LoadKeyMetadata(keyID, meta); - UpdateTimeFirstKey(meta.nCreateTime); -} - -void LegacyDataSPKM::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) -{ - LOCK(cs_KeyStore); - m_script_metadata[script_id] = meta; -} - -void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) -{ - LOCK(cs_KeyStore); - LegacyDataSPKM::LoadScriptMetadata(script_id, meta); - UpdateTimeFirstKey(meta.nCreateTime); -} - -bool LegacyDataSPKM::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) -{ - // This function should only be called during loading of a legacy to be migrated. - // As such, the wallet should not be encrypted if this is called. - LOCK(cs_KeyStore); - assert(!m_storage.HasEncryptionKeys()); - return FillableSigningProvider::AddKeyPubKey(key, pubkey); -} - -bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) -{ - LOCK(cs_KeyStore); - if (!m_storage.HasEncryptionKeys()) { - return FillableSigningProvider::AddKeyPubKey(key, pubkey); - } - - if (m_storage.IsLocked()) { - return false; - } - - std::vector vchCryptedSecret; - CKeyingMaterial vchSecret{UCharCast(key.begin()), UCharCast(key.end())}; - if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { - return EncryptSecret(encryption_key, vchSecret, pubkey.GetHash(), vchCryptedSecret); - })) { - return false; - } - - if (!AddCryptedKey(pubkey, vchCryptedSecret)) { - return false; - } - return true; -} - -bool LegacyDataSPKM::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, bool checksum_valid) -{ - // Set fDecryptionThoroughlyChecked to false when the checksum is invalid - if (!checksum_valid) { - fDecryptionThoroughlyChecked = false; - } - - return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); -} - -bool LegacyDataSPKM::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) -{ - LOCK(cs_KeyStore); - assert(mapKeys.empty()); - - mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); - ImplicitlyLearnRelatedKeyScripts(vchPubKey); - return true; -} - -bool LegacyScriptPubKeyMan::AddCryptedKey(const CPubKey &vchPubKey, - const std::vector &vchCryptedSecret) -{ - if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) - return false; - { - LOCK(cs_KeyStore); - if (encrypted_batch) - return encrypted_batch->WriteCryptedKey(vchPubKey, - vchCryptedSecret, - mapKeyMetadata[vchPubKey.GetID()]); - else - return WalletBatch(m_storage.GetDatabase()).WriteCryptedKey(vchPubKey, - vchCryptedSecret, - mapKeyMetadata[vchPubKey.GetID()]); - } -} - -bool LegacyDataSPKM::HaveWatchOnly(const CScript &dest) const -{ - LOCK(cs_KeyStore); - return setWatchOnly.count(dest) > 0; -} - -bool LegacyDataSPKM::HaveWatchOnly() const -{ - LOCK(cs_KeyStore); - return (!setWatchOnly.empty()); -} - -static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut) -{ - std::vector> solutions; - return Solver(dest, solutions) == TxoutType::PUBKEY && - (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); -} - -bool LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest) -{ - { - LOCK(cs_KeyStore); - setWatchOnly.erase(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys.erase(pubKey.GetID()); - } - // Related CScripts are not removed; having superfluous scripts around is - // harmless (see comment in ImplicitlyLearnRelatedKeyScripts). - } - - if (!HaveWatchOnly()) - NotifyWatchonlyChanged(false); - if (!WalletBatch(m_storage.GetDatabase()).EraseWatchOnly(dest)) - return false; - - return true; -} - -bool LegacyDataSPKM::LoadWatchOnly(const CScript &dest) -{ - return AddWatchOnlyInMem(dest); -} - -bool LegacyDataSPKM::AddWatchOnlyInMem(const CScript &dest) -{ - LOCK(cs_KeyStore); - setWatchOnly.insert(dest); - CPubKey pubKey; - if (ExtractPubKey(dest, pubKey)) { - mapWatchKeys[pubKey.GetID()] = pubKey; - ImplicitlyLearnRelatedKeyScripts(pubKey); - } - return true; -} - -bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) -{ - if (!AddWatchOnlyInMem(dest)) - return false; - const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; - UpdateTimeFirstKey(meta.nCreateTime); - NotifyWatchonlyChanged(true); - if (batch.WriteWatchOnly(dest, meta)) { - m_storage.UnsetBlankWalletFlag(batch); - return true; - } - return false; -} - -bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) -{ - m_script_metadata[CScriptID(dest)].nCreateTime = create_time; - return AddWatchOnlyWithDB(batch, dest); -} - -bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest) -{ - WalletBatch batch(m_storage.GetDatabase()); - return AddWatchOnlyWithDB(batch, dest); -} - -bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTime) -{ - m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; - return AddWatchOnly(dest); -} - -void LegacyDataSPKM::LoadHDChain(const CHDChain& chain) -{ - LOCK(cs_KeyStore); - m_hd_chain = chain; -} - -void LegacyScriptPubKeyMan::AddHDChain(const CHDChain& chain) -{ - LOCK(cs_KeyStore); - // Store the new chain - if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) { - throw std::runtime_error(std::string(__func__) + ": writing chain failed"); - } - // When there's an old chain, add it as an inactive chain as we are now rotating hd chains - if (!m_hd_chain.seed_id.IsNull()) { - AddInactiveHDChain(m_hd_chain); - } - - m_hd_chain = chain; -} - -void LegacyDataSPKM::AddInactiveHDChain(const CHDChain& chain) -{ - LOCK(cs_KeyStore); - assert(!chain.seed_id.IsNull()); - m_inactive_hd_chains[chain.seed_id] = chain; -} - -bool LegacyDataSPKM::HaveKey(const CKeyID &address) const -{ - LOCK(cs_KeyStore); - if (!m_storage.HasEncryptionKeys()) { - return FillableSigningProvider::HaveKey(address); - } - return mapCryptedKeys.count(address) > 0; -} - -bool LegacyDataSPKM::GetKey(const CKeyID &address, CKey& keyOut) const -{ - LOCK(cs_KeyStore); - if (!m_storage.HasEncryptionKeys()) { - return FillableSigningProvider::GetKey(address, keyOut); - } - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - const CPubKey &vchPubKey = (*mi).second.first; - const std::vector &vchCryptedSecret = (*mi).second.second; - return m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { - return DecryptKey(encryption_key, vchCryptedSecret, vchPubKey, keyOut); - }); - } - return false; -} - -bool LegacyDataSPKM::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const -{ - CKeyMetadata meta; - { - LOCK(cs_KeyStore); - auto it = mapKeyMetadata.find(keyID); - 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); - info.path = meta.key_origin.path; - } else { // Single pubkeys get the master fingerprint of themselves - std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint); - } - return true; -} - -bool LegacyDataSPKM::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const -{ - LOCK(cs_KeyStore); - WatchKeyMap::const_iterator it = mapWatchKeys.find(address); - if (it != mapWatchKeys.end()) { - pubkey_out = it->second; - return true; - } - return false; + return false; } bool LegacyDataSPKM::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const { LOCK(cs_KeyStore); - if (!m_storage.HasEncryptionKeys()) { - if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { - return GetWatchPubKey(address, vchPubKeyOut); - } - return true; - } - - CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); - if (mi != mapCryptedKeys.end()) - { - vchPubKeyOut = (*mi).second.first; - return true; - } - // Check for watch-only pubkeys - return GetWatchPubKey(address, vchPubKeyOut); -} - -CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, CHDChain& hd_chain, bool internal) -{ - assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); - AssertLockHeld(cs_KeyStore); - bool fCompressed = m_storage.CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets - - CKey secret; - - // Create new metadata - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - - // use HD key derivation if HD was enabled during wallet creation and a seed is present - if (IsHDEnabled()) { - DeriveNewChildKey(batch, metadata, secret, hd_chain, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); - } else { - secret.MakeNewKey(fCompressed); - } - - // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) { - m_storage.SetMinVersion(FEATURE_COMPRPUBKEY); - } - - CPubKey pubkey = secret.GetPubKey(); - assert(secret.VerifyPubKey(pubkey)); - - mapKeyMetadata[pubkey.GetID()] = metadata; - UpdateTimeFirstKey(nCreationTime); - - if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) { - throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + if (!m_storage.HasEncryptionKeys()) { + if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { + return GetWatchPubKey(address, vchPubKeyOut); + } + return true; } - 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"); + CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); + if (mi != mapCryptedKeys.end()) + { + vchPubKeyOut = (*mi).second.first; + return true; } -} - -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 - CKey seed; //seed (256bit) - CExtKey masterKey; //hd master key - CExtKey accountKey; //key at m/0' - CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal) - CExtKey childKey; //key at m/0'/0'/' - - // try to get the seed - if (!GetKey(hd_chain.seed_id, seed)) - throw std::runtime_error(std::string(__func__) + ": seed not found"); - - masterKey.SetSeed(seed); - - // derive m/0' - // use hardened derivation (child keys >= 0x80000000 are hardened after bip32) - 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); - DeriveExtKey(accountKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0), chainChildKey); - - // derive child key at next index, skip keys already known to the wallet - do { - // always derive hardened keys - // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range - // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 - if (internal) { - 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); - metadata.key_origin.path.push_back(hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); - hd_chain.nInternalChainCounter++; - } - else { - 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); - metadata.key_origin.path.push_back(hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); - hd_chain.nExternalChainCounter++; - } - } while (HaveKey(childKey.key.GetPubKey().GetID())); - secret = childKey.key; - metadata.hd_seed_id = hd_chain.seed_id; - CKeyID master_id = masterKey.key.GetPubKey().GetID(); - std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint); - metadata.has_key_origin = true; - // update the chain model in the database - if (hd_chain.seed_id == m_hd_chain.seed_id && !batch.WriteHDChain(hd_chain)) - throw std::runtime_error(std::string(__func__) + ": writing HD chain model failed"); + // Check for watch-only pubkeys + return GetWatchPubKey(address, vchPubKeyOut); } void LegacyDataSPKM::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) @@ -1198,506 +288,6 @@ void LegacyDataSPKM::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); } -bool LegacyScriptPubKeyMan::CanGenerateKeys() const -{ - // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD) - LOCK(cs_KeyStore); - return IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD); -} - -CPubKey LegacyScriptPubKeyMan::GenerateNewSeed() -{ - assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - CKey key = GenerateRandomKey(); - return DeriveNewSeed(key); -} - -CPubKey LegacyScriptPubKeyMan::DeriveNewSeed(const CKey& key) -{ - int64_t nCreationTime = GetTime(); - CKeyMetadata metadata(nCreationTime); - - // calculate the seed - CPubKey seed = key.GetPubKey(); - assert(key.VerifyPubKey(seed)); - - // set the hd keypath to "s" -> Seed, refers the seed to itself - metadata.hdKeypath = "s"; - metadata.has_key_origin = false; - metadata.hd_seed_id = seed.GetID(); - - { - LOCK(cs_KeyStore); - - // mem store the metadata - mapKeyMetadata[seed.GetID()] = metadata; - - // write the key&metadata to the database - if (!AddKeyPubKey(key, seed)) - throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); - } - - return seed; -} - -void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed) -{ - LOCK(cs_KeyStore); - // store the keyid (hash160) together with - // the child index counter in the database - // as a hdchain object - CHDChain newHdChain; - newHdChain.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; - newHdChain.seed_id = seed.GetID(); - AddHDChain(newHdChain); - NotifyCanGetAddressesChanged(); - WalletBatch batch(m_storage.GetDatabase()); - m_storage.UnsetBlankWalletFlag(batch); -} - -/** - * Mark old keypool keys as used, - * and generate all new keys - */ -bool LegacyScriptPubKeyMan::NewKeyPool() -{ - if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - return false; - } - { - LOCK(cs_KeyStore); - WalletBatch batch(m_storage.GetDatabase()); - - for (const int64_t nIndex : setInternalKeyPool) { - batch.ErasePool(nIndex); - } - setInternalKeyPool.clear(); - - for (const int64_t nIndex : setExternalKeyPool) { - batch.ErasePool(nIndex); - } - setExternalKeyPool.clear(); - - for (const int64_t nIndex : set_pre_split_keypool) { - batch.ErasePool(nIndex); - } - set_pre_split_keypool.clear(); - - m_pool_key_to_index.clear(); - - if (!TopUp()) { - return false; - } - WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n"); - } - return true; -} - -bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize) -{ - if (!CanGenerateKeys()) { - return false; - } - - WalletBatch batch(m_storage.GetDatabase()); - if (!batch.TxnBegin()) return false; - if (!TopUpChain(batch, m_hd_chain, kpSize)) { - return false; - } - for (auto& [chain_id, chain] : m_inactive_hd_chains) { - if (!TopUpChain(batch, chain, kpSize)) { - return false; - } - } - if (!batch.TxnCommit()) throw std::runtime_error(strprintf("Error during keypool top up. Cannot commit changes for wallet %s", m_storage.GetDisplayName())); - NotifyCanGetAddressesChanged(); - // Note: Unlike with DescriptorSPKM, LegacySPKM does not need to call - // m_storage.TopUpCallback() as we do not know what new scripts the LegacySPKM is - // watching for. CWallet's scriptPubKey cache is not used for LegacySPKMs. - return true; -} - -bool LegacyScriptPubKeyMan::TopUpChain(WalletBatch& batch, CHDChain& chain, unsigned int kpSize) -{ - LOCK(cs_KeyStore); - - if (m_storage.IsLocked()) return false; - - // Top up key pool - unsigned int nTargetSize; - if (kpSize > 0) { - nTargetSize = kpSize; - } else { - nTargetSize = m_keypool_size; - } - int64_t target = std::max((int64_t) nTargetSize, int64_t{1}); - - // count amount of available keys (internal, external) - // make sure the keypool of external and internal keys fits the user selected target (-keypool) - int64_t missingExternal; - int64_t missingInternal; - if (chain == m_hd_chain) { - missingExternal = std::max(target - (int64_t)setExternalKeyPool.size(), int64_t{0}); - missingInternal = std::max(target - (int64_t)setInternalKeyPool.size(), int64_t{0}); - } else { - missingExternal = std::max(target - (chain.nExternalChainCounter - chain.m_next_external_index), int64_t{0}); - missingInternal = std::max(target - (chain.nInternalChainCounter - chain.m_next_internal_index), int64_t{0}); - } - - if (!IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) { - // don't create extra internal keys - missingInternal = 0; - } - bool internal = false; - for (int64_t i = missingInternal + missingExternal; i--;) { - if (i < missingInternal) { - internal = true; - } - - CPubKey pubkey(GenerateNewKey(batch, chain, internal)); - if (chain == m_hd_chain) { - AddKeypoolPubkeyWithDB(pubkey, internal, batch); - } - } - if (missingInternal + missingExternal > 0) { - if (chain == m_hd_chain) { - WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size()); - } else { - WalletLogPrintf("inactive seed with id %s added %d external keys, %d internal keys\n", HexStr(chain.seed_id), missingExternal, missingInternal); - } - } - return true; -} - -void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch) -{ - LOCK(cs_KeyStore); - assert(m_max_keypool_index < std::numeric_limits::max()); // How in the hell did you use so many keys? - int64_t index = ++m_max_keypool_index; - if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { - throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed"); - } - if (internal) { - setInternalKeyPool.insert(index); - } else { - setExternalKeyPool.insert(index); - } - m_pool_key_to_index[pubkey.GetID()] = index; -} - -void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type) -{ - assert(type != OutputType::BECH32M); - // Remove from key pool - WalletBatch batch(m_storage.GetDatabase()); - batch.ErasePool(nIndex); - CPubKey pubkey; - bool have_pk = GetPubKey(m_index_to_reserved_key.at(nIndex), pubkey); - assert(have_pk); - LearnRelatedScripts(pubkey, type); - m_index_to_reserved_key.erase(nIndex); - WalletLogPrintf("keypool keep %d\n", nIndex); -} - -void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, const CTxDestination&) -{ - // Return to key pool - { - LOCK(cs_KeyStore); - if (fInternal) { - setInternalKeyPool.insert(nIndex); - } else if (!set_pre_split_keypool.empty()) { - set_pre_split_keypool.insert(nIndex); - } else { - setExternalKeyPool.insert(nIndex); - } - CKeyID& pubkey_id = m_index_to_reserved_key.at(nIndex); - m_pool_key_to_index[pubkey_id] = nIndex; - m_index_to_reserved_key.erase(nIndex); - NotifyCanGetAddressesChanged(); - } - WalletLogPrintf("keypool return %d\n", nIndex); -} - -bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type) -{ - assert(type != OutputType::BECH32M); - if (!CanGetAddresses(/*internal=*/ false)) { - return false; - } - - CKeyPool keypool; - { - LOCK(cs_KeyStore); - int64_t nIndex; - if (!ReserveKeyFromKeyPool(nIndex, keypool, /*fRequestedInternal=*/ false) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - if (m_storage.IsLocked()) return false; - WalletBatch batch(m_storage.GetDatabase()); - result = GenerateNewKey(batch, m_hd_chain, /*internal=*/ false); - return true; - } - KeepDestination(nIndex, type); - result = keypool.vchPubKey; - } - return true; -} - -bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) -{ - nIndex = -1; - keypool.vchPubKey = CPubKey(); - { - LOCK(cs_KeyStore); - - bool fReturningInternal = fRequestedInternal; - fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); - bool use_split_keypool = set_pre_split_keypool.empty(); - std::set& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool; - - // Get the oldest key - if (setKeyPool.empty()) { - return false; - } - - WalletBatch batch(m_storage.GetDatabase()); - - auto it = setKeyPool.begin(); - nIndex = *it; - setKeyPool.erase(it); - if (!batch.ReadPool(nIndex, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read failed"); - } - CPubKey pk; - if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) { - throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); - } - // If the key was pre-split keypool, we don't care about what type it is - if (use_split_keypool && keypool.fInternal != fReturningInternal) { - throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); - } - if (!keypool.vchPubKey.IsValid()) { - throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); - } - - assert(m_index_to_reserved_key.count(nIndex) == 0); - m_index_to_reserved_key[nIndex] = keypool.vchPubKey.GetID(); - m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - WalletLogPrintf("keypool reserve %d\n", nIndex); - } - NotifyCanGetAddressesChanged(); - return true; -} - -void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type) -{ - assert(type != OutputType::BECH32M); - if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) { - CTxDestination witdest = WitnessV0KeyHash(key.GetID()); - CScript witprog = GetScriptForDestination(witdest); - // Make sure the resulting program is solvable. - const auto desc = InferDescriptor(witprog, *this); - assert(desc && desc->IsSolvable()); - AddCScript(witprog); - } -} - -void LegacyScriptPubKeyMan::LearnAllRelatedScripts(const CPubKey& key) -{ - // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types. - LearnRelatedScripts(key, OutputType::P2SH_SEGWIT); -} - -std::vector LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id) -{ - AssertLockHeld(cs_KeyStore); - bool internal = setInternalKeyPool.count(keypool_id); - if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id)); - std::set *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool); - auto it = setKeyPool->begin(); - - std::vector result; - WalletBatch batch(m_storage.GetDatabase()); - while (it != std::end(*setKeyPool)) { - const int64_t& index = *(it); - if (index > keypool_id) break; // set*KeyPool is ordered - - CKeyPool keypool; - if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary - m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); - } - LearnAllRelatedScripts(keypool.vchPubKey); - batch.ErasePool(index); - WalletLogPrintf("keypool index %d removed\n", index); - it = setKeyPool->erase(it); - result.push_back(std::move(keypool)); - } - - return result; -} - -std::vector GetAffectedKeys(const CScript& spk, const SigningProvider& provider) -{ - std::vector dummy; - FlatSigningProvider out; - InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out); - std::vector ret; - ret.reserve(out.pubkeys.size()); - for (const auto& entry : out.pubkeys) { - ret.push_back(entry.first); - } - return ret; -} - -void LegacyScriptPubKeyMan::MarkPreSplitKeys() -{ - WalletBatch batch(m_storage.GetDatabase()); - for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) { - int64_t index = *it; - CKeyPool keypool; - if (!batch.ReadPool(index, keypool)) { - throw std::runtime_error(std::string(__func__) + ": read keypool entry failed"); - } - keypool.m_pre_split = true; - if (!batch.WritePool(index, keypool)) { - throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed"); - } - set_pre_split_keypool.insert(index); - it = setExternalKeyPool.erase(it); - } -} - -bool LegacyScriptPubKeyMan::AddCScript(const CScript& redeemScript) -{ - WalletBatch batch(m_storage.GetDatabase()); - return AddCScriptWithDB(batch, redeemScript); -} - -bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript) -{ - if (!FillableSigningProvider::AddCScript(redeemScript)) - return false; - if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { - m_storage.UnsetBlankWalletFlag(batch); - return true; - } - return false; -} - -bool LegacyScriptPubKeyMan::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info) -{ - LOCK(cs_KeyStore); - std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint); - mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path; - mapKeyMetadata[pubkey.GetID()].has_key_origin = true; - mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path, /*apostrophe=*/true); - return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); -} - -bool LegacyScriptPubKeyMan::ImportScripts(const std::set scripts, int64_t timestamp) -{ - WalletBatch batch(m_storage.GetDatabase()); - for (const auto& entry : scripts) { - CScriptID id(entry); - if (HaveCScript(id)) { - WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); - continue; - } - if (!AddCScriptWithDB(batch, entry)) { - return false; - } - - if (timestamp > 0) { - m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; - } - } - if (timestamp > 0) { - UpdateTimeFirstKey(timestamp); - } - - return true; -} - -bool LegacyScriptPubKeyMan::ImportPrivKeys(const std::map& privkey_map, const int64_t timestamp) -{ - WalletBatch batch(m_storage.GetDatabase()); - for (const auto& entry : privkey_map) { - const CKey& key = entry.second; - CPubKey pubkey = key.GetPubKey(); - const CKeyID& id = entry.first; - assert(key.VerifyPubKey(pubkey)); - // Skip if we already have the key - if (HaveKey(id)) { - WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); - continue; - } - mapKeyMetadata[id].nCreateTime = timestamp; - // If the private key is not present in the wallet, insert it. - if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { - return false; - } - UpdateTimeFirstKey(timestamp); - } - return true; -} - -bool LegacyScriptPubKeyMan::ImportPubKeys(const std::vector& ordered_pubkeys, const std::map& pubkey_map, const std::map>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) -{ - WalletBatch batch(m_storage.GetDatabase()); - for (const auto& entry : key_origins) { - AddKeyOriginWithDB(batch, entry.second.first, entry.second.second); - } - for (const CKeyID& id : ordered_pubkeys) { - auto entry = pubkey_map.find(id); - if (entry == pubkey_map.end()) { - continue; - } - const CPubKey& pubkey = entry->second; - CPubKey temp; - if (GetPubKey(id, temp)) { - // Already have pubkey, skipping - WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); - continue; - } - if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { - return false; - } - mapKeyMetadata[id].nCreateTime = timestamp; - - // Add to keypool only works with pubkeys - if (add_keypool) { - AddKeypoolPubkeyWithDB(pubkey, internal, batch); - NotifyCanGetAddressesChanged(); - } - } - return true; -} - -bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set& script_pub_keys, const bool have_solving_data, const int64_t timestamp) -{ - WalletBatch batch(m_storage.GetDatabase()); - for (const CScript& script : script_pub_keys) { - if (!have_solving_data || !IsMine(script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated - if (!AddWatchOnlyWithDB(batch, script, timestamp)) { - return false; - } - } - } - return true; -} - -std::set LegacyScriptPubKeyMan::GetKeys() const -{ - LOCK(cs_KeyStore); - if (!m_storage.HasEncryptionKeys()) { - return FillableSigningProvider::GetKeys(); - } - std::set set_address; - for (const auto& mi : mapCryptedKeys) { - set_address.insert(mi.first); - } - return set_address; -} - std::unordered_set LegacyDataSPKM::GetScriptPubKeys() const { LOCK(cs_KeyStore); diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 8e697a40128d02..522156ebde2c8b 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -366,201 +366,6 @@ class LegacyDataSPKM : public ScriptPubKeyMan, public FillableSigningProvider bool DeleteRecords(); }; -// Implements the full legacy wallet behavior -class LegacyScriptPubKeyMan : public LegacyDataSPKM -{ -private: - WalletBatch *encrypted_batch GUARDED_BY(cs_KeyStore) = nullptr; - - // By default, do not scan any block until keys/scripts are generated/imported - int64_t nTimeFirstKey GUARDED_BY(cs_KeyStore) = UNKNOWN_TIME; - - //! Number of pre-generated keys/scripts (part of the look-ahead process, used to detect payments) - int64_t m_keypool_size GUARDED_BY(cs_KeyStore){DEFAULT_KEYPOOL_SIZE}; - - bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) override; - - /** - * Private version of AddWatchOnly method which does not accept a - * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if - * the watch key did not previously have a timestamp associated with it. - * Because this is an inherited virtual method, it is accessible despite - * being marked private, but it is marked private anyway to encourage use - * of the other AddWatchOnly which accepts a timestamp and sets - * nTimeFirstKey more intelligently for more efficient rescans. - */ - bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - //! Adds a watch-only address to the store, and saves it to disk. - bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - //! Adds a key to the store, and saves it to disk. - bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch); - - //! Adds a script to the store and saves it to disk - bool AddCScriptWithDB(WalletBatch& batch, const CScript& script); - - /** Add a KeyOriginInfo to the wallet */ - bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); - - /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - // Tracks keypool indexes to CKeyIDs of keys that have been taken out of the keypool but may be returned to it - std::map m_index_to_reserved_key; - - //! Fetches a key from the keypool - bool GetKeyFromPool(CPubKey &key, const OutputType type); - - /** - * Reserves a key from the keypool and sets nIndex to its index - * - * @param[out] nIndex the index of the key in keypool - * @param[out] keypool the keypool the key was drawn from, which could be the - * the pre-split pool if present, or the internal or external pool - * @param fRequestedInternal true if the caller would like the key drawn - * from the internal keypool, false if external is preferred - * - * @return true if succeeded, false if failed due to empty keypool - * @throws std::runtime_error if keypool read failed, key was invalid, - * was not found in the wallet, or was misclassified in the internal - * or external keypool - */ - bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); - - /** - * Like TopUp() but adds keys for inactive HD chains. - * Ensures that there are at least -keypool number of keys derived after the given index. - * - * @param seed_id the CKeyID for the HD seed. - * @param index the index to start generating keys from - * @param internal whether the internal chain should be used. true for internal chain, false for external chain. - * - * @return true if seed was found and keys were derived. false if unable to derive seeds - */ - bool TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal); - - bool TopUpChain(WalletBatch& batch, CHDChain& chain, unsigned int size); -public: - LegacyScriptPubKeyMan(WalletStorage& storage, int64_t keypool_size) : LegacyDataSPKM(storage), m_keypool_size(keypool_size) {} - - util::Result GetNewDestination(const OutputType type) override; - isminetype IsMine(const CScript& script) const override; - - bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - - util::Result GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override; - void KeepDestination(int64_t index, const OutputType& type) override; - void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override; - - bool TopUp(unsigned int size = 0) override; - - std::vector MarkUnusedAddresses(const CScript& script) override; - - //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo - void UpgradeKeyMetadata(); - - bool IsHDEnabled() const override; - - bool SetupGeneration(bool force = false) override; - - bool Upgrade(int prev_version, int new_version, bilingual_str& error) override; - - bool HavePrivateKeys() const override; - - void RewriteDB() override; - - std::optional GetOldestKeyPoolTime() const override; - size_t KeypoolCountExternalKeys() const; - unsigned int GetKeyPoolSize() const override; - - int64_t GetTimeFirstKey() const override; - - std::unique_ptr GetMetadata(const CTxDestination& dest) const override; - - bool CanGetAddresses(bool internal = false) const override; - - bool CanProvide(const CScript& script, SignatureData& sigdata) override; - - bool SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const override; - SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; - - uint256 GetID() const override; - - //! Adds a key to the store, and saves it to disk. - bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; - //! Adds an encrypted key to the store, and saves it to disk. - bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); - void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - //! Load metadata (used by LoadWallet) - void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) override; - void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) override; - //! Generate a new key - CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - /* Set the HD chain model (chain child index counters) and writes it to the database */ - void AddHDChain(const CHDChain& chain); - - //! Remove a watch only script from the keystore - bool RemoveWatchOnly(const CScript &dest); - bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - /* SigningProvider overrides */ - bool AddCScript(const CScript& redeemScript) override; - - bool NewKeyPool(); - void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - bool ImportScripts(const std::set scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - bool ImportPrivKeys(const std::map& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - bool ImportPubKeys(const std::vector& ordered_pubkeys, const std::map& pubkey_map, const std::map>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - bool ImportScriptPubKeys(const std::set& script_pub_keys, const bool have_solving_data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - - /* Returns true if the wallet can generate new keys */ - bool CanGenerateKeys() const; - - /* Generates a new HD seed (will not be activated) */ - CPubKey GenerateNewSeed(); - - /* Derives a new HD seed (will not be activated) */ - CPubKey DeriveNewSeed(const CKey& key); - - /* Set the current HD seed (will reset the chain child index counters) - Sets the seed's version based on the current wallet version (so the - caller must ensure the current wallet version is correct before calling - this function). */ - void SetHDSeed(const CPubKey& key); - - /** - * Explicitly make the wallet learn the related scripts for outputs to the - * given key. This is purely to make the wallet file compatible with older - * software, as FillableSigningProvider automatically does this implicitly for all - * keys now. - */ - void LearnRelatedScripts(const CPubKey& key, OutputType); - - /** - * Same as LearnRelatedScripts, but when the OutputType is not known (and could - * be anything). - */ - void LearnAllRelatedScripts(const CPubKey& key); - - /** - * Marks all keys in the keypool up to and including the provided key as used. - * - * @param keypool_id determines the last key to mark as used - * - * @return All affected keys - */ - std::vector MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); - const std::map& GetAllReserveKeys() const { return m_pool_key_to_index; } - - std::set GetKeys() const override; -}; - /** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */ class LegacySigningProvider : public SigningProvider { diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 23224714023f7e..e0751c3f395851 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -499,8 +499,6 @@ std::map> ListCoins(const CWallet& wallet) std::map> result; CCoinControl coin_control; - // Include watch-only for LegacyScriptPubKeyMan wallets without private keys - coin_control.fAllowWatchOnly = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); CoinFilterParams coins_params; coins_params.only_spendable = false; coins_params.skip_locked = false; diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp deleted file mode 100644 index dfad0e2126973d..00000000000000 --- a/src/wallet/test/ismine_tests.cpp +++ /dev/null @@ -1,724 +0,0 @@ -// Copyright (c) 2017-2022 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include -#include -#include -#include