diff --git a/src/script/script.h b/src/script/script.h --- a/src/script/script.h +++ b/src/script/script.h @@ -673,6 +673,12 @@ bool IsPushOnly(const_iterator pc) const; bool IsPushOnly() const; + /** + * Like IsPushOnly, except only allows push opcodes (no OP_RESERVED) and + * the data must be minimally pushed. + */ + bool IsMinimalPushOnly() const; + /** * Returns whether the script is guaranteed to fail at execution, regardless * of the initial stack. This allows outputs to be pruned instantly when diff --git a/src/script/script.cpp b/src/script/script.cpp --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -483,3 +483,22 @@ bool CScript::IsPushOnly() const { return this->IsPushOnly(begin()); } + +bool CScript::IsMinimalPushOnly() const { + const_iterator pc = begin(); + while (pc < end()) { + opcodetype opcode; + std::vector vchPushValue; + if (!GetOp(pc, opcode, vchPushValue)) { + return false; + } + if (opcode <= OP_PUSHDATA4) { + if (!CheckMinimalPush(vchPushValue, opcode)) { + return false; + } + } else if (opcode > OP_16 || opcode == OP_RESERVED) { + return false; + } // else OP_1NEGATE or OP_1 to OP_16, always minimal. + } + return true; +} diff --git a/src/test/script_pushonly_tests.cpp b/src/test/script_pushonly_tests.cpp --- a/src/test/script_pushonly_tests.cpp +++ b/src/test/script_pushonly_tests.cpp @@ -24,61 +24,75 @@ { CScript script = script_from_raw({0x01}); BOOST_CHECK(!script.IsPushOnly()); + // Also test IsMinimalPushOnly + BOOST_CHECK(!script.IsMinimalPushOnly()); } // Additional tests { // PUSHDATA1 truncated before size CScript script = script_from_raw({0x4c}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } { // PUSHDATA1 truncated mid-data CScript script = script_from_raw({0x4c, 0x02, 0x00}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } { // PUSHDATA2 truncated mid-size CScript script = script_from_raw({0x4d, 0x01}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } { // PUSHDATA2 truncated mid-data CScript script = script_from_raw({0x4d, 0x02, 0x00, 0x00}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } { // PUSHDATA4 truncated mid-size CScript script = script_from_raw({0x4e, 0x01}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } { // PUSHDATA4 truncated mid-data CScript script = script_from_raw({0x4e, 0x02, 0x00, 0x00, 0x00, 0x00}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } { // PUSHDATA4 with massive size (4 GB), truncated CScript script = script_from_raw({0x4e, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00}); BOOST_CHECK(!script.IsPushOnly()); + BOOST_CHECK(!script.IsMinimalPushOnly()); } } BOOST_AUTO_TEST_CASE(singlebyteopcodes) { - // OP_0 and OP_1NEGATE and OP_1 through OP_16 are push-only. + // OP_0 and OP_1NEGATE and OP_1 through OP_16 are push-only and minimal. CScript onebytepushes = CScript() << OP_0 << OP_1NEGATE << OP_1 << OP_2 << OP_3 << OP_4 << OP_5 << OP_6 << OP_7 << OP_8 << OP_9 << OP_10 << OP_11 << OP_12 << OP_13 << OP_14 << OP_15 << OP_16; BOOST_CHECK(onebytepushes.IsPushOnly()); - // OP_0 and OP_1NEGATE through to OP_16 (incl OP_RESERVED) are "PushOnly". + BOOST_CHECK(onebytepushes.IsMinimalPushOnly()); + // OP_0 and OP_1NEGATE through to OP_16 (incl OP_RESERVED) are "PushOnly" + // but not actually push-only. CScript withreserved = onebytepushes << OP_RESERVED; BOOST_CHECK(withreserved.IsPushOnly()); + BOOST_CHECK(!withreserved.IsMinimalPushOnly()); // Do all single-byte opcodes, except OP_0. for (uint8_t op = 0xff; op > OP_PUSHDATA4; op--) { CScript script = CScript() << OP_5 << opcodetype(op) << OP_11; BOOST_CHECK_EQUAL(script.IsPushOnly(), op <= OP_16); + BOOST_CHECK_EQUAL(script.IsMinimalPushOnly(), + op <= OP_16 && op != OP_RESERVED); } } @@ -89,33 +103,44 @@ { CScript scriptPUSH = CScript() << OP_0; BOOST_CHECK(scriptPUSH.IsPushOnly()); + BOOST_CHECK(scriptPUSH.IsMinimalPushOnly()); CScript scriptPUSHDATA1 = script_from_raw({0x4c, 0x00}); BOOST_CHECK(scriptPUSHDATA1.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA1.IsMinimalPushOnly()); CScript scriptPUSHDATA2 = script_from_raw({0x4d, 0x00, 0x00}); BOOST_CHECK(scriptPUSHDATA2.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA2.IsMinimalPushOnly()); CScript scriptPUSHDATA4 = script_from_raw({0x4e, 0x00, 0x00, 0x00, 0x00}); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA4.IsMinimalPushOnly()); } // Do all possible 1-byte pushes in all four forms. for (int i = 0; i < 256; i++) { + // [0x01] to [0x10] and [0x81] all have special forms. + bool hasSpecialOpCode = ((i > 0 && i <= 16) || i == 0x81); + CScript scriptPUSH = script_from_raw({0x01, uint8_t(i)}); BOOST_CHECK(scriptPUSH.IsPushOnly()); + BOOST_CHECK_EQUAL(scriptPUSH.IsMinimalPushOnly(), !hasSpecialOpCode); CScript scriptPUSHDATA1 = script_from_raw({0x4c, 0x01, uint8_t(i)}); BOOST_CHECK(scriptPUSHDATA1.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA1.IsMinimalPushOnly()); CScript scriptPUSHDATA2 = script_from_raw({0x4d, 0x01, 0x00, uint8_t(i)}); BOOST_CHECK(scriptPUSHDATA2.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA2.IsMinimalPushOnly()); CScript scriptPUSHDATA4 = script_from_raw({0x4e, 0x01, 0x00, 0x00, 0x00, uint8_t(i)}); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA4.IsMinimalPushOnly()); } // Do various longer pushes. @@ -124,17 +149,21 @@ { CScript scriptPUSH = script_from_raw({0x02, 0x00, 0x00}); BOOST_CHECK(scriptPUSH.IsPushOnly()); + BOOST_CHECK(scriptPUSH.IsMinimalPushOnly()); CScript scriptPUSHDATA1 = script_from_raw({0x4c, 0x02, 0x00, 0x00}); BOOST_CHECK(scriptPUSHDATA1.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA1.IsMinimalPushOnly()); CScript scriptPUSHDATA2 = script_from_raw({0x4d, 0x02, 0x00, 0x00, 0x00}); BOOST_CHECK(scriptPUSHDATA2.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA2.IsMinimalPushOnly()); CScript scriptPUSHDATA4 = script_from_raw({0x4e, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00}); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA4.IsMinimalPushOnly()); } // 75 zero bytes @@ -142,10 +171,12 @@ CScript scriptPUSH = script_from_raw({75}); scriptPUSH.resize(scriptPUSH.size() + 75); BOOST_CHECK(scriptPUSH.IsPushOnly()); + BOOST_CHECK(scriptPUSH.IsMinimalPushOnly()); CScript scriptPUSHDATA1 = script_from_raw({0x4c, 75}); scriptPUSHDATA1.resize(scriptPUSHDATA1.size() + 75); BOOST_CHECK(scriptPUSHDATA1.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA1.IsMinimalPushOnly()); CScript scriptPUSHDATA2 = script_from_raw({ 0x4d, @@ -154,10 +185,12 @@ }); scriptPUSHDATA2.resize(scriptPUSHDATA2.size() + 75); BOOST_CHECK(scriptPUSHDATA2.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA2.IsMinimalPushOnly()); CScript scriptPUSHDATA4 = script_from_raw({0x4e, 75, 0x00, 0x00, 0x00}); scriptPUSHDATA4.resize(scriptPUSHDATA4.size() + 75); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA4.IsMinimalPushOnly()); } // 255 zero bytes @@ -165,6 +198,7 @@ CScript scriptPUSHDATA1 = script_from_raw({0x4c, 255}); scriptPUSHDATA1.resize(scriptPUSHDATA1.size() + 255); BOOST_CHECK(scriptPUSHDATA1.IsPushOnly()); + BOOST_CHECK(scriptPUSHDATA1.IsMinimalPushOnly()); CScript scriptPUSHDATA2 = script_from_raw({ 0x4d, @@ -173,11 +207,13 @@ }); scriptPUSHDATA2.resize(scriptPUSHDATA2.size() + 255); BOOST_CHECK(scriptPUSHDATA2.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA2.IsMinimalPushOnly()); CScript scriptPUSHDATA4 = script_from_raw({0x4e, 255, 0x00, 0x00, 0x00}); scriptPUSHDATA4.resize(scriptPUSHDATA4.size() + 255); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA4.IsMinimalPushOnly()); } // 0xffff zero bytes @@ -189,11 +225,13 @@ }); scriptPUSHDATA2.resize(scriptPUSHDATA2.size() + 0xffff); BOOST_CHECK(scriptPUSHDATA2.IsPushOnly()); + BOOST_CHECK(scriptPUSHDATA2.IsMinimalPushOnly()); CScript scriptPUSHDATA4 = script_from_raw({0x4e, 0xff, 0xff, 0x00, 0x00}); scriptPUSHDATA4.resize(scriptPUSHDATA4.size() + 0xffff); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(!scriptPUSHDATA4.IsMinimalPushOnly()); } // 0x10000 zero bytes @@ -202,6 +240,7 @@ script_from_raw({0x4e, 0x00, 0x00, 0x01, 0x00}); scriptPUSHDATA4.resize(scriptPUSHDATA4.size() + 0x10000); BOOST_CHECK(scriptPUSHDATA4.IsPushOnly()); + BOOST_CHECK(scriptPUSHDATA4.IsMinimalPushOnly()); } } diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -2749,6 +2749,8 @@ SCRIPT_VERIFY_MINIMALDATA, BaseSignatureChecker(), &err), "Number " << i << " push is not minimal data."); + BOOST_CHECK_MESSAGE(script.IsMinimalPushOnly(), + "Number " << i << " push is not minimally pushed."); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } @@ -2762,6 +2764,8 @@ SCRIPT_VERIFY_MINIMALDATA, BaseSignatureChecker(), &err), "Length " << i << " push is not minimal data."); + BOOST_CHECK_MESSAGE(script.IsMinimalPushOnly(), + "Number " << i << " push is not minimally pushed."); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } }