aboutsummaryrefslogtreecommitdiff
path: root/src/script
diff options
context:
space:
mode:
Diffstat (limited to 'src/script')
-rw-r--r--src/script/interpreter.cpp43
-rw-r--r--src/script/interpreter.h6
-rw-r--r--src/script/ismine.cpp78
-rw-r--r--src/script/ismine.h7
-rw-r--r--src/script/script.cpp52
-rw-r--r--src/script/script.h102
-rw-r--r--src/script/script_error.cpp4
-rw-r--r--src/script/script_error.h4
-rw-r--r--src/script/standard.h2
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) {}
};