diff --git a/src/core_read.cpp b/src/core_read.cpp --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -27,8 +27,8 @@ if (mapOpNames.empty()) { for (int op = 0; op < FIRST_UNDEFINED_OP_VALUE; op++) { - // ignore all "PUSHDATA" ops, but dont ignore OP_RESERVED - if (op < OP_NOP && op != OP_RESERVED) { + // ignore all "PUSHDATA" ops before PUSHDATA1 + if (op < OP_PUSHDATA1) { continue; } @@ -50,6 +50,8 @@ boost::algorithm::token_compress_on); size_t push_size = 0, next_push_size = 0; + // Deal with PUSHDATA1 operation with some more hacks. + bool have_push_data = false; for (const auto &w : words) { if (w.empty()) { @@ -62,16 +64,7 @@ push_size = next_push_size; next_push_size = 0; - if (all(w, boost::algorithm::is_digit()) || - (boost::algorithm::starts_with(w, "-") && - all(std::string(w.begin() + 1, w.end()), - boost::algorithm::is_digit()))) { - // Number - int64_t n = atoi64(w); - result << n; - continue; - } - + // Hex Data if (boost::algorithm::starts_with(w, "0x") && (w.begin() + 2 != w.end())) { if (!IsHex(std::string(w.begin() + 2, w.end()))) { @@ -84,20 +77,50 @@ // Raw hex data, inserted NOT pushed onto stack: std::vector raw = ParseHex(std::string(w.begin() + 2, w.end())); + if (push_size && raw.size() != push_size) { throw std::runtime_error("Hex number doesn't match the " "number of bytes being pushed"); } // If we have what looks like an immediate push, figure out its // size. - if (!push_size && raw.size() == 1 && raw[0] < OP_PUSHDATA1) { + if (push_size == 0 && raw.size() == 1 && raw[0] < OP_PUSHDATA1) { next_push_size = raw[0]; } + // TODO: Disallow `push_size == 0 && raw.size() > 1` here, tests + // should + // not have multibyte raw opcode data. However some of them + // currently do. + + if (have_push_data) { + next_push_size = CScriptNum(raw, false).getint(); + // std::cout << "test: " << next_push_size << std::endl; + have_push_data = false; + } + result.insert(result.end(), raw.begin(), raw.end()); continue; } + // Quoted data and decimal numbers should not + // follow any explicit `push` operations. + if (0 != push_size) { + throw std::runtime_error("Expected explicit hex data to push, " + "found none"); + } + + // Decimal numbers + if (all(w, boost::algorithm::is_digit()) || + (boost::algorithm::starts_with(w, "-") && + all(std::string(w.begin() + 1, w.end()), + boost::algorithm::is_digit()))) { + // Number + int64_t n = atoi64(w); + result << n; + continue; + } + if (w.size() >= 2 && boost::algorithm::starts_with(w, "'") && boost::algorithm::ends_with(w, "'")) { // Single-quoted string, pushed as data. NOTE: this is poor-man's @@ -115,12 +138,15 @@ switch (op) { case OP_PUSHDATA1: next_push_size = 1; + have_push_data = true; break; case OP_PUSHDATA2: next_push_size = 2; + have_push_data = true; break; case OP_PUSHDATA4: next_push_size = 4; + have_push_data = true; break; default: break; diff --git a/src/test/core_io_tests.cpp b/src/test/core_io_tests.cpp --- a/src/test/core_io_tests.cpp +++ b/src/test/core_io_tests.cpp @@ -31,6 +31,24 @@ BOOST_CHECK_NO_THROW(ParseScript("0x02 0x0101")); BOOST_CHECK_THROW(ParseScript("0x02 0x01"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x02 0x010101"), std::runtime_error); + + // Note sizes are LE encoded. Also, some of these values are not + // minimally encoded intentionally -- nor are they being required to be + // minimally encoded. The full interpreter requires minimally encoded + // values. Tests using the parsed script will fail if the tests do not + // use minimally encoded sizes. + BOOST_CHECK_NO_THROW(ParseScript("PUSHDATA4 0x02000000 0x0101")); + BOOST_CHECK_THROW(ParseScript("PUSHDATA4 0x03000000 0x0101"), + std::runtime_error); + BOOST_CHECK_NO_THROW(ParseScript("PUSHDATA2 0x0200 0x0101")); + BOOST_CHECK_THROW(ParseScript("PUSHDATA2 0x0300 0x0101"), + std::runtime_error); + BOOST_CHECK_NO_THROW(ParseScript("PUSHDATA1 0x02 0x0101")); + BOOST_CHECK_THROW(ParseScript("PUSHDATA1 0x02 0x010101"), + std::runtime_error); + + // Implicit push not allowed after explicit push + BOOST_CHECK_THROW(ParseScript("0x04 'help'"), std::runtime_error); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -520,8 +520,8 @@ "NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY 0 0 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 20 CHECKMULTISIGVERIFY", "P2SH,STRICTENC", "OK"], -["0 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "OK", "Very basic P2SH"], -["0x4c 0 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "OK"], +["0 0x01 0x51", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "OK", "Very basic P2SH"], +["0x4c 0 0x01 0x51", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "OK"], ["0x40 0x42424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242", "0x4d 0x4000 0x42424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242 EQUAL", @@ -718,9 +718,9 @@ ["DEPTH", "", "P2SH,STRICTENC", "EVAL_FALSE"], -["0x4c01","0x01 NOP", "P2SH,STRICTENC","BAD_OPCODE", "PUSHDATA1 with not enough bytes"], -["0x4d0200ff","0x01 NOP", "P2SH,STRICTENC","BAD_OPCODE", "PUSHDATA2 with not enough bytes"], -["0x4e03000000ffff","0x01 NOP", "P2SH,STRICTENC","BAD_OPCODE", "PUSHDATA4 with not enough bytes"], +["0x4c01","0x01 0x61", "P2SH,STRICTENC","BAD_OPCODE", "PUSHDATA1 with not enough bytes"], +["0x4d0200ff","0x01 0x61", "P2SH,STRICTENC","BAD_OPCODE", "PUSHDATA2 with not enough bytes"], +["0x4e03000000ffff","0x01 0x61", "P2SH,STRICTENC","BAD_OPCODE", "PUSHDATA4 with not enough bytes"], ["1", "IF 0x50 ENDIF 1", "P2SH,STRICTENC","BAD_OPCODE", "0x50 is reserved"], ["0x52", "0x5f ADD 0x60 EQUAL", "P2SH,STRICTENC","EVAL_FALSE", "0x51 through 0x60 push 1 through 16 onto stack"], @@ -1299,11 +1299,11 @@ ["0 'sig' 1 0", "CHECKMULTISIG 1", "P2SH,STRICTENC", "SIG_COUNT", "nSigs > nPubKeys"], -["NOP 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "SIG_PUSHONLY", "Tests for Script.IsPushOnly()"], -["NOP1 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "SIG_PUSHONLY"], +["NOP 0x01 0x51", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "SIG_PUSHONLY", "Tests for Script.IsPushOnly()"], +["NOP1 0x01 0x51", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL", "P2SH,STRICTENC", "SIG_PUSHONLY"], ["0 0x01 0x50", "HASH160 0x14 0xece424a6bb6ddf4db592c0faed60685047a361b1 EQUAL", "P2SH,STRICTENC", "BAD_OPCODE", "OP_RESERVED in P2SH should fail"], -["0 0x01 VER", "HASH160 0x14 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", "P2SH,STRICTENC", "BAD_OPCODE", "OP_VER in P2SH should fail"], +["0 0x01 0x62", "HASH160 0x14 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", "P2SH,STRICTENC", "BAD_OPCODE", "OP_VER in P2SH should fail"], ["0x00", "'00' EQUAL", "P2SH,STRICTENC", "EVAL_FALSE", "Basic OP_0 execution"], 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 @@ -1138,14 +1138,21 @@ } std::string scriptSigString = test[pos++].get_str(); - CScript scriptSig = ParseScript(scriptSigString); std::string scriptPubKeyString = test[pos++].get_str(); - CScript scriptPubKey = ParseScript(scriptPubKeyString); - unsigned int scriptflags = ParseScriptFlags(test[pos++].get_str()); - int scriptError = ParseScriptError(test[pos++].get_str()); - - DoTest(scriptPubKey, scriptSig, scriptflags, strTest, scriptError, - nValue); + try { + CScript scriptSig = ParseScript(scriptSigString); + CScript scriptPubKey = ParseScript(scriptPubKeyString); + unsigned int scriptflags = ParseScriptFlags(test[pos++].get_str()); + int scriptError = ParseScriptError(test[pos++].get_str()); + + DoTest(scriptPubKey, scriptSig, scriptflags, strTest, scriptError, + nValue); + } catch (...) { + BOOST_TEST_MESSAGE("Script test failed. scriptSig: " + << scriptSigString + << " scriptPubKey: " << scriptPubKeyString); + throw; + } } }