mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-18 15:44:44 -03:00
193 lines
6.4 KiB
C++
193 lines
6.4 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2018 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 <script/ismine.h>
|
|
|
|
#include <key.h>
|
|
#include <keystore.h>
|
|
#include <script/script.h>
|
|
#include <script/sign.h>
|
|
|
|
|
|
typedef std::vector<unsigned char> 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<valtype>& pubkeys, const CKeyStore& keystore)
|
|
{
|
|
for (const valtype& pubkey : pubkeys) {
|
|
CKeyID keyID = CPubKey(pubkey).GetID();
|
|
if (!keystore.HaveKey(keyID)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
|
|
{
|
|
IsMineResult ret = IsMineResult::NO;
|
|
|
|
std::vector<valtype> vSolutions;
|
|
txnouttype whichType = Solver(scriptPubKey, vSolutions);
|
|
|
|
CKeyID keyID;
|
|
switch (whichType)
|
|
{
|
|
case TX_NONSTANDARD:
|
|
case TX_NULL_DATA:
|
|
case TX_WITNESS_UNKNOWN:
|
|
break;
|
|
case TX_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 TX_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(CKeyID(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
|
|
break;
|
|
}
|
|
case TX_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 TX_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, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
|
|
}
|
|
break;
|
|
}
|
|
case TX_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;
|
|
}
|
|
uint160 hash;
|
|
CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin());
|
|
CScriptID scriptID = CScriptID(hash);
|
|
CScript subscript;
|
|
if (keystore.GetCScript(scriptID, subscript)) {
|
|
ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TX_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<valtype> 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;
|
|
}
|
|
}
|
|
|
|
if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
|
|
ret = std::max(ret, IsMineResult::WATCH_ONLY);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey)
|
|
{
|
|
switch (IsMineInner(keystore, scriptPubKey, 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);
|
|
}
|
|
|
|
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest)
|
|
{
|
|
CScript script = GetScriptForDestination(dest);
|
|
return IsMine(keystore, script);
|
|
}
|