diff --git a/src/core_read.cpp b/src/core_read.cpp --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -50,6 +50,8 @@ size_t push_size = 0, next_push_size = 0; size_t script_size = 0; + // Deal with PUSHDATA1 operation with some more hacks. + size_t push_data_size = 0; for (const auto &w : words) { if (w.empty()) { @@ -65,6 +67,7 @@ push_size = next_push_size; next_push_size = 0; + // Decimal numbers if (all(w, boost::algorithm::is_digit()) || (boost::algorithm::starts_with(w, "-") && all(std::string(w.begin() + 1, w.end()), @@ -75,6 +78,7 @@ goto next; } + // Hex Data if (boost::algorithm::starts_with(w, "0x") && (w.begin() + 2 != w.end())) { if (!IsHex(std::string(w.begin() + 2, w.end()))) { @@ -87,11 +91,6 @@ // Raw hex data, inserted NOT pushed onto stack: std::vector raw = ParseHex(std::string(w.begin() + 2, w.end())); - // If we have what looks like an immediate push, figure out its - // size. - if (!push_size && raw.size() == 1 && raw[0] < OP_PUSHDATA1) { - next_push_size = raw[0]; - } result.insert(result.end(), raw.begin(), raw.end()); goto next; @@ -111,30 +110,66 @@ // opcode, e.g. OP_ADD or ADD: opcodetype op = mapOpNames[w]; + result << op; + goto next; + } + + throw std::runtime_error("Error parsing script: " + s); + + next: + size_t size_change = result.size() - script_size; + + // If push_size is set, ensure have added the right amount of stuff. + if (push_size != 0 && size_change != push_size) { + throw std::runtime_error("Wrong number of bytes being pushed."); + } + + // If push_size is set, and we have push_data_size set, then we have a + // PUSHDATAX opcode. We need to read it's push size as a LE value for + // the next iteration of this loop. + if (push_size != 0 && push_data_size != 0) { + auto offset = &result[script_size]; + + // Push data size is not a CScriptNum (Because it is + // 2's-complement instead of 1's complement). We need to use + // ReadLE(N) instead of converting to a CScriptNum. + if (push_data_size == 1) { + next_push_size = *offset; + } else if (push_data_size == 2) { + next_push_size = ReadLE16(offset); + } else if (push_data_size == 4) { + next_push_size = ReadLE32(offset); + } + + push_data_size = 0; + } + + // If push_size is unset, but size_change is 1, that means we have an + // opcode in the form of `0x00` or . We will check to see + // if it is a push operation and set state accordingly + if (push_size == 0 && size_change == 1) { + opcodetype op = opcodetype(*result.rbegin()); + + // If we have what looks like an immediate push, figure out its + // size. + if (op < OP_PUSHDATA1) { + next_push_size = op; + continue; + } + switch (op) { case OP_PUSHDATA1: - next_push_size = 1; + push_data_size = next_push_size = 1; break; case OP_PUSHDATA2: - next_push_size = 2; + push_data_size = next_push_size = 2; break; case OP_PUSHDATA4: - next_push_size = 4; + push_data_size = next_push_size = 4; break; default: break; } - - result << op; - goto next; - } - - throw std::runtime_error("Error parsing script: " + s); - - next: - size_t size_change = result.size() - script_size; - if (push_size && size_change != push_size) { - throw std::runtime_error("Wrong number of bytes being pushed."); } } 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 @@ -25,6 +25,57 @@ } } +static void PrintLE(std::ostringstream &testString, size_t bytes, + size_t pushLength) { + testString << "0x"; + while (bytes != 0) { + testString << std::setfill('0') << std::setw(2) << std::hex + << pushLength % 256; + pushLength /= 256; + bytes--; + } +} + +static std::string TestPushOpcode(size_t pushWidth, size_t pushLength, + size_t actualLength) { + std::ostringstream testString; + + switch (pushWidth) { + case 1: + testString << "PUSHDATA1 "; + break; + case 2: + testString << "PUSHDATA2 "; + break; + case 4: + testString << "PUSHDATA4 "; + break; + default: + assert(false); + } + PrintLE(testString, pushWidth, pushLength); + testString << " 0x"; + + for (size_t i = 0; i < actualLength; i++) { + testString << "01"; + } + + return testString.str(); +} + +BOOST_AUTO_TEST_CASE(printle_tests) { + // Ensure the test generator is doing what we think it is. + std::ostringstream testString; + PrintLE(testString, 04, 0x8001); + BOOST_CHECK_EQUAL(testString.str(), "0x01800000"); +} + +BOOST_AUTO_TEST_CASE(testpushopcode_tests) { + BOOST_CHECK_EQUAL(TestPushOpcode(1, 2, 2), "PUSHDATA1 0x02 0x0101"); + BOOST_CHECK_EQUAL(TestPushOpcode(2, 2, 2), "PUSHDATA2 0x0200 0x0101"); + BOOST_CHECK_EQUAL(TestPushOpcode(4, 2, 2), "PUSHDATA4 0x02000000 0x0101"); +} + BOOST_AUTO_TEST_CASE(parse_push_test) { BOOST_CHECK_NO_THROW(ParseScript("0x01 0x01")); BOOST_CHECK_NO_THROW(ParseScript("0x01 XOR")); @@ -42,6 +93,56 @@ BOOST_CHECK_THROW(ParseScript("0x02 ''"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x02 0x010101"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x02 'ab'"), 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. + BOOST_CHECK_NO_THROW(ParseScript("PUSHDATA4 0x02000000 0x0101")); + BOOST_CHECK_THROW(ParseScript("PUSHDATA4 0x03000000 0x0101"), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript("PUSHDATA4 0x02000000 0x010101"), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript("PUSHDATA4 0x020000 0x0101"), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript("PUSHDATA4 0x0200000000 0x0101"), + std::runtime_error); + + BOOST_CHECK_NO_THROW(ParseScript("PUSHDATA2 0x0200 0x0101")); + BOOST_CHECK_THROW(ParseScript("PUSHDATA2 0x0300 0x0101"), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript("PUSHDATA2 0x030000 0x0101"), + std::runtime_error); + BOOST_CHECK_NO_THROW(ParseScript("PUSHDATA1 0x02 0x0101")); + BOOST_CHECK_THROW(ParseScript("PUSHDATA1 0x02 0x010101"), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript("PUSHDATA1 0x0200 0x010101"), + std::runtime_error); + + // Ensure pushdata handling is not using 1's complement + BOOST_CHECK_NO_THROW(ParseScript(TestPushOpcode(1, 0xC8, 0xC8))); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(1, 0xC8, 0xC9)), + std::runtime_error); + + BOOST_CHECK_NO_THROW(ParseScript(TestPushOpcode(2, 0x8000, 0x8000))); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(2, 0x8000, 0x8001)), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(2, 0x8001, 0x8000)), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(2, 0x80, 0x81)), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(2, 0x80, 0x7F)), + std::runtime_error); + + // Can't build something too long. + BOOST_CHECK_NO_THROW(ParseScript(TestPushOpcode(4, 0x8000, 0x8000))); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(4, 0x8000, 0x8001)), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(4, 0x8001, 0x8000)), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(4, 0x80, 0x81)), + std::runtime_error); + BOOST_CHECK_THROW(ParseScript(TestPushOpcode(4, 0x80, 0x7F)), + std::runtime_error); } BOOST_AUTO_TEST_SUITE_END()