diff options
Diffstat (limited to 'src/script')
| -rw-r--r-- | src/script/interpreter.cpp | 43 | ||||
| -rw-r--r-- | src/script/interpreter.h | 6 | ||||
| -rw-r--r-- | src/script/ismine.cpp | 78 | ||||
| -rw-r--r-- | src/script/ismine.h | 7 | ||||
| -rw-r--r-- | src/script/script.cpp | 52 | ||||
| -rw-r--r-- | src/script/script.h | 102 | ||||
| -rw-r--r-- | src/script/script_error.cpp | 4 | ||||
| -rw-r--r-- | src/script/script_error.h | 4 | ||||
| -rw-r--r-- | src/script/standard.h | 2 |
9 files changed, 166 insertions, 132 deletions
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 338e07e24..13f41a7cb 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -250,6 +250,34 @@ bool static CheckMinimalPush(const valtype& data, opcodetype opcode) { return true; } +int FindAndDelete(CScript& script, const CScript& b) +{ + int nFound = 0; + if (b.empty()) + return nFound; + CScript result; + CScript::const_iterator pc = script.begin(), pc2 = script.begin(), end = script.end(); + opcodetype opcode; + do + { + result.insert(result.end(), pc2, pc); + while (static_cast<size_t>(end - pc) >= b.size() && std::equal(b.begin(), b.end(), pc)) + { + pc = pc + b.size(); + ++nFound; + } + pc2 = pc; + } + while (script.GetOp(pc, opcode)); + + if (nFound > 0) { + result.insert(result.end(), pc2, end); + script = std::move(result); + } + + return nFound; +} + bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror) { static const CScriptNum bnZero(0); @@ -308,6 +336,10 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& opcode == OP_RSHIFT) return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); // Disabled opcodes. + // With SCRIPT_VERIFY_CONST_SCRIPTCODE, OP_CODESEPARATOR in non-segwit script is rejected even in an unexecuted branch + if (opcode == OP_CODESEPARATOR && sigversion == SigVersion::BASE && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) + return set_error(serror, SCRIPT_ERR_OP_CODESEPARATOR); + if (fExec && 0 <= opcode && opcode <= OP_PUSHDATA4) { if (fRequireMinimal && !CheckMinimalPush(vchPushValue, opcode)) { return set_error(serror, SCRIPT_ERR_MINIMALDATA); @@ -871,6 +903,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& case OP_CODESEPARATOR: { + // If SCRIPT_VERIFY_CONST_SCRIPTCODE flag is set, use of OP_CODESEPARATOR is rejected in pre-segwit + // script, even in an unexecuted branch (this is checked above the opcode case statement). + // Hash starts after the code separator pbegincodehash = pc; } @@ -891,7 +926,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& // Drop the signature in pre-segwit scripts but not segwit scripts if (sigversion == SigVersion::BASE) { - scriptCode.FindAndDelete(CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript(vchSig)); + if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) + return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } if (!CheckSignatureEncoding(vchSig, flags, serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) { @@ -955,7 +992,9 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& { valtype& vchSig = stacktop(-isig-k); if (sigversion == SigVersion::BASE) { - scriptCode.FindAndDelete(CScript(vchSig)); + int found = FindAndDelete(scriptCode, CScript(vchSig)); + if (found > 0 && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE)) + return set_error(serror, SCRIPT_ERR_SIG_FINDANDDELETE); } } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 601a4a866..2800473a6 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -111,6 +111,10 @@ enum // Public keys in segregated witness scripts must be compressed // SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1U << 15), + + // Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts + // + SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16), }; bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror); @@ -189,4 +193,6 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags); +int FindAndDelete(CScript& script, const CScript& b); + #endif // BITCOIN_SCRIPT_INTERPRETER_H diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index b826bcfe2..fefa02fde 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -13,34 +13,36 @@ typedef std::vector<unsigned char> valtype; -static 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; -} +namespace { -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, SigVersion sigversion) +/** + * 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 { - bool isInvalid = false; - return IsMine(keystore, scriptPubKey, isInvalid, sigversion); -} + TOP = 0, //! scriptPubKey execution + P2SH = 1, //! P2SH redeemScript + WITNESS_V0 = 2 //! P2WSH witness script execution +}; -isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, SigVersion sigversion) +bool PermitsUncompressed(IsMineSigVersion sigversion) { - bool isInvalid = false; - return IsMine(keystore, dest, isInvalid, sigversion); + return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH; } -isminetype IsMine(const CKeyStore &keystore, const CTxDestination& dest, bool& isInvalid, SigVersion sigversion) +bool HaveKeys(const std::vector<valtype>& pubkeys, const CKeyStore& keystore) { - CScript script = GetScriptForDestination(dest); - return IsMine(keystore, script, isInvalid, sigversion); + 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, bool& isInvalid, SigVersion sigversion) +isminetype IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, IsMineSigVersion sigversion) { isInvalid = false; @@ -61,7 +63,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& break; case TX_PUBKEY: keyID = CPubKey(vSolutions[0]).GetID(); - if (sigversion != SigVersion::BASE && vSolutions[0].size() != 33) { + if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) { isInvalid = true; return ISMINE_NO; } @@ -70,20 +72,20 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& break; case TX_WITNESS_V0_KEYHASH: { - if (!keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + 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; } - isminetype ret = ::IsMine(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), isInvalid, SigVersion::WITNESS_V0); + isminetype ret = IsMineInner(keystore, GetScriptForDestination(CKeyID(uint160(vSolutions[0]))), isInvalid, IsMineSigVersion::WITNESS_V0); if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) return ret; break; } case TX_PUBKEYHASH: keyID = CKeyID(uint160(vSolutions[0])); - if (sigversion != SigVersion::BASE) { + if (!PermitsUncompressed(sigversion)) { CPubKey pubkey; if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) { isInvalid = true; @@ -98,7 +100,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& CScriptID scriptID = CScriptID(uint160(vSolutions[0])); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - isminetype ret = IsMine(keystore, subscript, isInvalid); + isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::P2SH); if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) return ret; } @@ -106,7 +108,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& } case TX_WITNESS_V0_SCRIPTHASH: { - if (!keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { + if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) { break; } uint160 hash; @@ -114,7 +116,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& CScriptID scriptID = CScriptID(hash); CScript subscript; if (keystore.GetCScript(scriptID, subscript)) { - isminetype ret = IsMine(keystore, subscript, isInvalid, SigVersion::WITNESS_V0); + isminetype ret = IsMineInner(keystore, subscript, isInvalid, IsMineSigVersion::WITNESS_V0); if (ret == ISMINE_SPENDABLE || ret == ISMINE_WATCH_SOLVABLE || (ret == ISMINE_NO && isInvalid)) return ret; } @@ -123,13 +125,16 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& 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 (sigversion != SigVersion::BASE) { + if (!PermitsUncompressed(sigversion)) { for (size_t i = 0; i < keys.size(); i++) { if (keys[i].size() != 33) { isInvalid = true; @@ -150,3 +155,22 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& } return ISMINE_NO; } + +} // namespace + +isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid) +{ + return IsMineInner(keystore, scriptPubKey, isInvalid, IsMineSigVersion::TOP); +} + +isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey) +{ + bool isInvalid = false; + return IsMine(keystore, scriptPubKey, isInvalid); +} + +isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest) +{ + CScript script = GetScriptForDestination(dest); + return IsMine(keystore, script); +} diff --git a/src/script/ismine.h b/src/script/ismine.h index f93a66e35..8573bdfbd 100644 --- a/src/script/ismine.h +++ b/src/script/ismine.h @@ -33,9 +33,8 @@ typedef uint8_t isminefilter; * different SIGVERSION may have different network rules. Currently the only use of isInvalid is indicate uncompressed * keys in SigVersion::WITNESS_V0 script, but could also be used in similar cases in the future */ -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid, SigVersion = SigVersion::BASE); -isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, SigVersion = SigVersion::BASE); -isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, bool& isInvalid, SigVersion = SigVersion::BASE); -isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest, SigVersion = SigVersion::BASE); +isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey, bool& isInvalid); +isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey); +isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest); #endif // BITCOIN_SCRIPT_ISMINE_H diff --git a/src/script/script.cpp b/src/script/script.cpp index 65e5405eb..7f25d915a 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -280,3 +280,55 @@ bool CScript::HasValidOps() const } return true; } + +bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet) +{ + opcodeRet = OP_INVALIDOPCODE; + if (pvchRet) + pvchRet->clear(); + if (pc >= end) + return false; + + // Read instruction + if (end - pc < 1) + return false; + unsigned int opcode = *pc++; + + // Immediate operand + if (opcode <= OP_PUSHDATA4) + { + unsigned int nSize = 0; + if (opcode < OP_PUSHDATA1) + { + nSize = opcode; + } + else if (opcode == OP_PUSHDATA1) + { + if (end - pc < 1) + return false; + nSize = *pc++; + } + else if (opcode == OP_PUSHDATA2) + { + if (end - pc < 2) + return false; + nSize = ReadLE16(&pc[0]); + pc += 2; + } + else if (opcode == OP_PUSHDATA4) + { + if (end - pc < 4) + return false; + nSize = ReadLE32(&pc[0]); + pc += 4; + } + if (end - pc < 0 || (unsigned int)(end - pc) < nSize) + return false; + if (pvchRet) + pvchRet->assign(pc, pc + nSize); + pc += nSize; + } + + opcodeRet = static_cast<opcodetype>(opcode); + return true; +} diff --git a/src/script/script.h b/src/script/script.h index 59ceff247..d8b7c0601 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -385,6 +385,8 @@ private: */ typedef prevector<28, unsigned char> CScriptBase; +bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet); + /** Serialized script, used inside transaction inputs and outputs */ class CScript : public CScriptBase { @@ -493,84 +495,16 @@ public: } - bool GetOp(iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>& vchRet) - { - // Wrapper so it can be called with either iterator or const_iterator - const_iterator pc2 = pc; - bool fRet = GetOp2(pc2, opcodeRet, &vchRet); - pc = begin() + (pc2 - begin()); - return fRet; - } - - bool GetOp(iterator& pc, opcodetype& opcodeRet) - { - const_iterator pc2 = pc; - bool fRet = GetOp2(pc2, opcodeRet, nullptr); - pc = begin() + (pc2 - begin()); - return fRet; - } - bool GetOp(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>& vchRet) const { - return GetOp2(pc, opcodeRet, &vchRet); + return GetScriptOp(pc, end(), opcodeRet, &vchRet); } bool GetOp(const_iterator& pc, opcodetype& opcodeRet) const { - return GetOp2(pc, opcodeRet, nullptr); + return GetScriptOp(pc, end(), opcodeRet, nullptr); } - bool GetOp2(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet) const - { - opcodeRet = OP_INVALIDOPCODE; - if (pvchRet) - pvchRet->clear(); - if (pc >= end()) - return false; - - // Read instruction - if (end() - pc < 1) - return false; - unsigned int opcode = *pc++; - - // Immediate operand - if (opcode <= OP_PUSHDATA4) - { - unsigned int nSize = 0; - if (opcode < OP_PUSHDATA1) - { - nSize = opcode; - } - else if (opcode == OP_PUSHDATA1) - { - if (end() - pc < 1) - return false; - nSize = *pc++; - } - else if (opcode == OP_PUSHDATA2) - { - if (end() - pc < 2) - return false; - nSize = ReadLE16(&pc[0]); - pc += 2; - } - else if (opcode == OP_PUSHDATA4) - { - if (end() - pc < 4) - return false; - nSize = ReadLE32(&pc[0]); - pc += 4; - } - if (end() - pc < 0 || (unsigned int)(end() - pc) < nSize) - return false; - if (pvchRet) - pvchRet->assign(pc, pc + nSize); - pc += nSize; - } - - opcodeRet = static_cast<opcodetype>(opcode); - return true; - } /** Encode/decode small integers: */ static int DecodeOP_N(opcodetype opcode) @@ -588,34 +522,6 @@ public: return (opcodetype)(OP_1+n-1); } - int FindAndDelete(const CScript& b) - { - int nFound = 0; - if (b.empty()) - return nFound; - CScript result; - iterator pc = begin(), pc2 = begin(); - opcodetype opcode; - do - { - result.insert(result.end(), pc2, pc); - while (static_cast<size_t>(end() - pc) >= b.size() && std::equal(b.begin(), b.end(), pc)) - { - pc = pc + b.size(); - ++nFound; - } - pc2 = pc; - } - while (GetOp(pc, opcode)); - - if (nFound > 0) { - result.insert(result.end(), pc2, end()); - *this = result; - } - - return nFound; - } - /** * Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs * as 20 sigops. With pay-to-script-hash, that changed: diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index dbceb1f74..ceda74058 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -89,6 +89,10 @@ const char* ScriptErrorString(const ScriptError serror) return "Witness provided for non-witness script"; case SCRIPT_ERR_WITNESS_PUBKEYTYPE: return "Using non-compressed keys in segwit"; + case SCRIPT_ERR_OP_CODESEPARATOR: + return "Using OP_CODESEPARATOR in non-witness script"; + case SCRIPT_ERR_SIG_FINDANDDELETE: + return "Signature is found in scriptCode"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index 3200e9470..6982a087f 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -64,6 +64,10 @@ typedef enum ScriptError_t SCRIPT_ERR_WITNESS_UNEXPECTED, SCRIPT_ERR_WITNESS_PUBKEYTYPE, + /* Constant scriptCode */ + SCRIPT_ERR_OP_CODESEPARATOR, + SCRIPT_ERR_SIG_FINDANDDELETE, + SCRIPT_ERR_ERROR_COUNT } ScriptError; diff --git a/src/script/standard.h b/src/script/standard.h index 3b2838a5b..4922b7236 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -23,7 +23,7 @@ class CScriptID : public uint160 { public: CScriptID() : uint160() {} - CScriptID(const CScript& in); + explicit CScriptID(const CScript& in); CScriptID(const uint160& in) : uint160(in) {} }; |