diff options
Diffstat (limited to 'src/script/ismine.cpp')
| -rw-r--r-- | src/script/ismine.cpp | 180 |
1 files changed, 132 insertions, 48 deletions
diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 0bf180341..e3a6278c7 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -1,110 +1,194 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2015 The Bitcoin Core developers +// 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 "ismine.h" +#include <script/ismine.h> -#include "key.h" -#include "keystore.h" -#include "script/script.h" -#include "script/standard.h" -#include "script/sign.h" +#include <key.h> +#include <keystore.h> +#include <script/script.h> +#include <script/sign.h> -#include <boost/foreach.hpp> -using namespace std; +typedef std::vector<unsigned char> valtype; -typedef vector<unsigned char> valtype; +namespace { -unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore) +/** + * 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 { - unsigned int nResult = 0; - BOOST_FOREACH(const valtype& pubkey, pubkeys) - { - CKeyID keyID = CPubKey(pubkey).GetID(); - if (keystore.HaveKey(keyID)) - ++nResult; - } - return nResult; + 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; } -isminetype IsMine(const CKeyStore &keystore, const CTxDestination& dest) +bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore) { - CScript script = GetScriptForDestination(dest); - return IsMine(keystore, script); + for (const valtype& pubkey : pubkeys) { + CKeyID keyID = CPubKey(pubkey).GetID(); + if (!keystore.HaveKey(keyID)) return false; + } + return true; } -isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey) +IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion) { - vector<valtype> vSolutions; + IsMineResult ret = IsMineResult::NO; + + std::vector<valtype> vSolutions; txnouttype whichType; - if (!Solver(scriptPubKey, whichType, vSolutions)) { - if (keystore.HaveWatchOnly(scriptPubKey)) - return ISMINE_WATCH_UNSOLVABLE; - return ISMINE_NO; - } + Solver(scriptPubKey, whichType, 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 (keystore.HaveKey(keyID)) - return ISMINE_SPENDABLE; + if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { + return IsMineResult::INVALID; + } + if (keystore.HaveKey(keyID)) { + ret = std::max(ret, IsMineResult::SPENDABLE); + } break; - case TX_PUBKEYHASH: 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 (keystore.HaveKey(keyID)) - return ISMINE_SPENDABLE; + 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)) { - isminetype ret = IsMine(keystore, subscript); - if (ret == ISMINE_SPENDABLE) - return ret; + 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)) { - isminetype ret = IsMine(keystore, subscript); - if (ret == ISMINE_SPENDABLE) - return ret; + 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. - vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1); - if (HaveKeys(keys, keystore) == keys.size()) - return ISMINE_SPENDABLE; + 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 (keystore.HaveWatchOnly(scriptPubKey)) { - // TODO: This could be optimized some by doing some work after the above solver - SignatureData sigs; - return ProduceSignature(DummySignatureCreator(&keystore), scriptPubKey, sigs) ? ISMINE_WATCH_SOLVABLE : ISMINE_WATCH_UNSOLVABLE; + if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) { + ret = std::max(ret, IsMineResult::WATCH_ONLY); } - return ISMINE_NO; + 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); } |