Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2019-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <wallet/scriptpubkeyman.h>7#include <coins.h>8#include <hash.h>9#include <key_io.h>10#include <node/types.h>11#include <outputtype.h>12#include <script/descriptor.h>13#include <script/script.h>14#include <script/sign.h>15#include <script/solver.h>16#include <util/bip32.h>17#include <util/check.h>18#include <util/log.h>19#include <util/strencodings.h>20#include <util/string.h>21#include <util/time.h>22#include <util/translation.h>24#include <optional>26using common::PSBTError;27using util::ToString;29namespace wallet {31typedef std::vector<unsigned char> valtype;33// Legacy wallet IsMine(). Used only in migration34// DO NOT USE ANYTHING IN THIS NAMESPACE OUTSIDE OF MIGRATION35namespace {37/**38* This is an enum that tracks the execution context of a script, similar to39* SigVersion in script/interpreter. It is separate however because we want to40* distinguish between top-level scriptPubKey execution and P2SH redeemScript41* execution (a distinction that has no impact on consensus rules).42*/43enum class IsMineSigVersion44{45TOP = 0, //!< scriptPubKey execution46P2SH = 1, //!< P2SH redeemScript47WITNESS_V0 = 2, //!< P2WSH witness script execution48};50/**51* This is an internal representation of isminetype + invalidity.52* Its order is significant, as we return the max of all explored53* possibilities.54*/55enum class IsMineResult56{57NO = 0, //!< Not ours58WATCH_ONLY = 1, //!< Included in watch-only balance59SPENDABLE = 2, //!< Included in all balances60INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness)61};63bool PermitsUncompressed(IsMineSigVersion sigversion)64{65return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH;66}68bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyDataSPKM& keystore)69{70for (const valtype& pubkey : pubkeys) {71CKeyID keyID = CPubKey(pubkey).GetID();72if (!keystore.HaveKey(keyID)) return false;73}74return true;75}77//! Recursively solve script and return spendable/watchonly/invalid status.78//!79//! @param keystore legacy key and script store80//! @param scriptPubKey script to solve81//! @param sigversion script type (top-level / redeemscript / witnessscript)82//! @param recurse_scripthash whether to recurse into nested p2sh and p2wsh83//! scripts or simply treat any script that has been84//! stored in the keystore as spendable85// NOLINTNEXTLINE(misc-no-recursion)86IsMineResult LegacyWalletIsMineInnerDONOTUSE(const LegacyDataSPKM& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion, bool recurse_scripthash=true)87{88IsMineResult ret = IsMineResult::NO;90std::vector<valtype> vSolutions;91TxoutType whichType = Solver(scriptPubKey, vSolutions);93CKeyID keyID;94switch (whichType) {95case TxoutType::NONSTANDARD:96case TxoutType::NULL_DATA:97case TxoutType::WITNESS_UNKNOWN:98case TxoutType::WITNESS_V1_TAPROOT:99case TxoutType::ANCHOR:100break;101case TxoutType::PUBKEY:102keyID = CPubKey(vSolutions[0]).GetID();103if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) {104return IsMineResult::INVALID;105}106if (keystore.HaveKey(keyID)) {107ret = std::max(ret, IsMineResult::SPENDABLE);108}109break;110case TxoutType::WITNESS_V0_KEYHASH:111{112if (sigversion == IsMineSigVersion::WITNESS_V0) {113// P2WPKH inside P2WSH is invalid.114return IsMineResult::INVALID;115}116if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {117// We do not support bare witness outputs unless the P2SH version of it would be118// acceptable as well. This protects against matching before segwit activates.119// This also applies to the P2WSH case.120break;121}122ret = std::max(ret, LegacyWalletIsMineInnerDONOTUSE(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));123break;124}125case TxoutType::PUBKEYHASH:126keyID = CKeyID(uint160(vSolutions[0]));127if (!PermitsUncompressed(sigversion)) {128CPubKey pubkey;129if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) {130return IsMineResult::INVALID;131}132}133if (keystore.HaveKey(keyID)) {134ret = std::max(ret, IsMineResult::SPENDABLE);135}136break;137case TxoutType::SCRIPTHASH:138{139if (sigversion != IsMineSigVersion::TOP) {140// P2SH inside P2WSH or P2SH is invalid.141return IsMineResult::INVALID;142}143CScriptID scriptID = CScriptID(uint160(vSolutions[0]));144CScript subscript;145if (keystore.GetCScript(scriptID, subscript)) {146ret = std::max(ret, recurse_scripthash ? LegacyWalletIsMineInnerDONOTUSE(keystore, subscript, IsMineSigVersion::P2SH) : IsMineResult::SPENDABLE);147}148break;149}150case TxoutType::WITNESS_V0_SCRIPTHASH:151{152if (sigversion == IsMineSigVersion::WITNESS_V0) {153// P2WSH inside P2WSH is invalid.154return IsMineResult::INVALID;155}156if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {157break;158}159CScriptID scriptID{RIPEMD160(vSolutions[0])};160CScript subscript;161if (keystore.GetCScript(scriptID, subscript)) {162ret = std::max(ret, recurse_scripthash ? LegacyWalletIsMineInnerDONOTUSE(keystore, subscript, IsMineSigVersion::WITNESS_V0) : IsMineResult::SPENDABLE);163}164break;165}167case TxoutType::MULTISIG:168{169// Never treat bare multisig outputs as ours (they can still be made watchonly-though)170if (sigversion == IsMineSigVersion::TOP) {171break;172}174// Only consider transactions "mine" if we own ALL the175// keys involved. Multi-signature transactions that are176// partially owned (somebody else has a key that can spend177// them) enable spend-out-from-under-you attacks, especially178// in shared-wallet situations.179std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);180if (!PermitsUncompressed(sigversion)) {181for (size_t i = 0; i < keys.size(); i++) {182if (keys[i].size() != 33) {183return IsMineResult::INVALID;184}185}186}187if (HaveKeys(keys, keystore)) {188ret = std::max(ret, IsMineResult::SPENDABLE);189}190break;191}192} // no default case, so the compiler can warn about missing cases194if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {195ret = std::max(ret, IsMineResult::WATCH_ONLY);196}197return ret;198}200} // namespace202bool LegacyDataSPKM::IsMine(const CScript& script) const203{204switch (LegacyWalletIsMineInnerDONOTUSE(*this, script, IsMineSigVersion::TOP)) {205case IsMineResult::INVALID:206case IsMineResult::NO:207return false;208case IsMineResult::WATCH_ONLY:209case IsMineResult::SPENDABLE:210return true;211}212assert(false);213}215bool LegacyDataSPKM::CheckDecryptionKey(const CKeyingMaterial& master_key)216{217{218LOCK(cs_KeyStore);219assert(mapKeys.empty());221bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys222bool keyFail = false;223CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();224WalletBatch batch(m_storage.GetDatabase());225for (; mi != mapCryptedKeys.end(); ++mi)226{227const CPubKey &vchPubKey = (*mi).second.first;228const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;229CKey key;230if (!DecryptKey(master_key, vchCryptedSecret, vchPubKey, key))231{232keyFail = true;233break;234}235keyPass = true;236if (fDecryptionThoroughlyChecked)237break;238else {239// Rewrite these encrypted keys with checksums240batch.WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);241}242}243if (keyPass && keyFail)244{245LogWarning("The wallet is probably corrupted: Some keys decrypt but not all.");246throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt.");247}248if (keyFail || !keyPass)249return false;250fDecryptionThoroughlyChecked = true;