Accept any sequence of PUSHDATAs in OP_RETURN outputs

Previously only one PUSHDATA was allowed, needlessly limiting
applications such as matching OP_RETURN contents with bloom filters that
operate on a per-PUSHDATA level. Now any combination that passes
IsPushOnly() is allowed, so long as the total size of the scriptPubKey
is less than 42 bytes. (unchanged modulo non-minimal PUSHDATA encodings)

Also, this fixes the odd bug where previously the PUSHDATA could be
replaced by any single opcode, even sigops consuming opcodes such as
CHECKMULTISIG. (20 sigops!)
This commit is contained in:
Peter Todd 2014-10-13 10:18:05 -04:00 committed by Jorge Timón
parent 5d8709c3b7
commit da894ab5da
6 changed files with 34 additions and 17 deletions

View file

@ -49,7 +49,9 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType)
return false; return false;
if (m < 1 || m > n) if (m < 1 || m > n)
return false; return false;
} } else if (whichType == TX_NULL_DATA &&
(!GetBoolArg("-datacarrier", true) || scriptPubKey.size() > nMaxDatacarrierBytes))
return false;
return whichType != TX_NONSTANDARD; return whichType != TX_NONSTANDARD;
} }

View file

@ -144,7 +144,7 @@ const char* GetOpName(opcodetype opcode)
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
// Note: // Note:
// The template matching params OP_SMALLDATA/etc are defined in opcodetype enum // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum
// as kind of implementation hack, they are *NOT* real opcodes. If found in real // as kind of implementation hack, they are *NOT* real opcodes. If found in real
// Script, just let the default: case deal with them. // Script, just let the default: case deal with them.

View file

@ -167,7 +167,6 @@ enum opcodetype
// template matching params // template matching params
OP_SMALLDATA = 0xf9,
OP_SMALLINTEGER = 0xfa, OP_SMALLINTEGER = 0xfa,
OP_PUBKEYS = 0xfb, OP_PUBKEYS = 0xfb,
OP_PUBKEYHASH = 0xfd, OP_PUBKEYHASH = 0xfd,

View file

@ -51,11 +51,6 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi
// Sender provides N pubkeys, receivers provides M signatures // Sender provides N pubkeys, receivers provides M signatures
mTemplates.insert(make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG)); mTemplates.insert(make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));
// Empty, provably prunable, data-carrying output
if (GetBoolArg("-datacarrier", true))
mTemplates.insert(make_pair(TX_NULL_DATA, CScript() << OP_RETURN << OP_SMALLDATA));
mTemplates.insert(make_pair(TX_NULL_DATA, CScript() << OP_RETURN));
} }
vSolutionsRet.clear(); vSolutionsRet.clear();
@ -70,6 +65,16 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi
return true; return true;
} }
// Provably prunable, data-carrying output
//
// So long as script passes the IsUnspendable() test and all but the first
// byte passes the IsPushOnly() test we don't care what exactly is in the
// script.
if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
typeRet = TX_NULL_DATA;
return true;
}
// Scan templates // Scan templates
const CScript& script1 = scriptPubKey; const CScript& script1 = scriptPubKey;
BOOST_FOREACH(const PAIRTYPE(txnouttype, CScript)& tplate, mTemplates) BOOST_FOREACH(const PAIRTYPE(txnouttype, CScript)& tplate, mTemplates)
@ -142,12 +147,6 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, vector<vector<unsi
else else
break; break;
} }
else if (opcode2 == OP_SMALLDATA)
{
// small pushdata, <= nMaxDatacarrierBytes
if (vch1.size() > nMaxDatacarrierBytes)
break;
}
else if (opcode1 != opcode2 || vch1 != vch2) else if (opcode1 != opcode2 || vch1 != vch2)
{ {
// Others must match exactly // Others must match exactly

View file

@ -25,7 +25,7 @@ public:
CScriptID(const uint160& in) : uint160(in) {} CScriptID(const uint160& in) : uint160(in) {}
}; };
static const unsigned int MAX_OP_RETURN_RELAY = 80; //! bytes static const unsigned int MAX_OP_RETURN_RELAY = 83; //! bytes (+1 for OP_RETURN, +2 for the pushdata opcodes)
extern unsigned nMaxDatacarrierBytes; extern unsigned nMaxDatacarrierBytes;
/** /**

View file

@ -351,12 +351,29 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
t.vout[0].scriptPubKey = CScript() << OP_1; t.vout[0].scriptPubKey = CScript() << OP_1;
BOOST_CHECK(!IsStandardTx(t, reason)); BOOST_CHECK(!IsStandardTx(t, reason));
// 80-byte TX_NULL_DATA (standard) // MAX_OP_RETURN_RELAY-byte TX_NULL_DATA (standard)
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY, t.vout[0].scriptPubKey.size());
BOOST_CHECK(IsStandardTx(t, reason)); BOOST_CHECK(IsStandardTx(t, reason));
// 81-byte TX_NULL_DATA (non-standard) // MAX_OP_RETURN_RELAY+1-byte TX_NULL_DATA (non-standard)
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800");
BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size());
BOOST_CHECK(!IsStandardTx(t, reason));
// Data payload can be encoded in any way...
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("");
BOOST_CHECK(IsStandardTx(t, reason));
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("00") << ParseHex("01");
BOOST_CHECK(IsStandardTx(t, reason));
// OP_RESERVED *is* considered to be a PUSHDATA type opcode by IsPushOnly()!
t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RESERVED << -1 << 0 << ParseHex("01") << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16;
BOOST_CHECK(IsStandardTx(t, reason));
t.vout[0].scriptPubKey = CScript() << OP_RETURN << 0 << ParseHex("01") << 2 << ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
BOOST_CHECK(IsStandardTx(t, reason));
// ...so long as it only contains PUSHDATA's
t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN;
BOOST_CHECK(!IsStandardTx(t, reason)); BOOST_CHECK(!IsStandardTx(t, reason));
// TX_NULL_DATA w/o PUSHDATA // TX_NULL_DATA w/o PUSHDATA