diff --git a/src/core_read.cpp b/src/core_read.cpp index d21d2220d..1614fcfe1 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -1,214 +1,249 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "core_io.h" #include "primitives/block.h" #include "primitives/transaction.h" #include "script/script.h" #include "serialize.h" #include "streams.h" #include "util.h" #include "utilstrencodings.h" #include "version.h" #include #include #include #include #include CScript ParseScript(const std::string &s) { CScript result; static std::map mapOpNames; if (mapOpNames.empty()) { for (int op = 0; op < FIRST_UNDEFINED_OP_VALUE; op++) { if (op < OP_PUSHDATA1) { continue; } const char *name = GetOpName((opcodetype)op); if (strcmp(name, "OP_UNKNOWN") == 0) { continue; } std::string strName(name); mapOpNames[strName] = (opcodetype)op; // Convenience: OP_ADD and just ADD are both recognized: boost::algorithm::replace_first(strName, "OP_", ""); mapOpNames[strName] = (opcodetype)op; } } std::vector words; boost::algorithm::split(words, s, boost::algorithm::is_any_of(" \t\n"), boost::algorithm::token_compress_on); 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()) { // Empty string, ignore. (boost::split given '' will return one // word) continue; } // Update script size. script_size = result.size(); // Make sure we keep track of the size of push operations. 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()), boost::algorithm::is_digit()))) { // Number int64_t n = atoi64(w); result << n; 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()))) { // Should only arrive here for improperly formatted hex values throw std::runtime_error("Hex numbers expected to be formatted " "in full-byte chunks (ex: 0x00 " "instead of 0x0)"); } // 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; } 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 // parsing, spaces/tabs/newlines in single-quoted strings won't // work. std::vector value(w.begin() + 1, w.end() - 1); result << value; goto next; } if (mapOpNames.count(w)) { // 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."); } } return result; } bool DecodeHexTx(CMutableTransaction &tx, const std::string &strHexTx) { if (!IsHex(strHexTx)) { return false; } std::vector txData(ParseHex(strHexTx)); CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); try { ssData >> tx; if (!ssData.empty()) { return false; } } catch (const std::exception &) { return false; } return true; } bool DecodeHexBlk(CBlock &block, const std::string &strHexBlk) { if (!IsHex(strHexBlk)) { return false; } std::vector blockData(ParseHex(strHexBlk)); CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION); try { ssBlock >> block; } catch (const std::exception &) { return false; } return true; } uint256 ParseHashUV(const UniValue &v, const std::string &strName) { std::string strHex; if (v.isStr()) { strHex = v.getValStr(); } // Note: ParseHashStr("") throws a runtime_error return ParseHashStr(strHex, strName); } uint256 ParseHashStr(const std::string &strHex, const std::string &strName) { if (!IsHex(strHex)) { // Note: IsHex("") is false throw std::runtime_error( strName + " must be hexadecimal string (not '" + strHex + "')"); } uint256 result; result.SetHex(strHex); return result; } std::vector ParseHexUV(const UniValue &v, const std::string &strName) { std::string strHex; if (v.isStr()) { strHex = v.getValStr(); } if (!IsHex(strHex)) { throw std::runtime_error( strName + " must be hexadecimal string (not '" + strHex + "')"); } return ParseHex(strHex); } diff --git a/src/test/core_io_tests.cpp b/src/test/core_io_tests.cpp index ed08cdab2..f9f91016a 100644 --- a/src/test/core_io_tests.cpp +++ b/src/test/core_io_tests.cpp @@ -1,47 +1,148 @@ // Copyright (c) 2018 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "core_io.h" #include "test/test_bitcoin.h" #include #include BOOST_FIXTURE_TEST_SUITE(core_io_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(parse_hex_test) { std::string s = "0x"; BOOST_CHECK_THROW(ParseScript(s), std::runtime_error); for (int numZeroes = 1; numZeroes <= 32; numZeroes++) { s += "0"; if (numZeroes % 2 == 0) { BOOST_CHECK_NO_THROW(ParseScript(s)); } else { BOOST_CHECK_THROW(ParseScript(s), std::runtime_error); } } } +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")); BOOST_CHECK_NO_THROW(ParseScript("0x01 1")); BOOST_CHECK_NO_THROW(ParseScript("0x01 ''")); BOOST_CHECK_NO_THROW(ParseScript("0x02 0x0101")); BOOST_CHECK_NO_THROW(ParseScript("0x02 42")); BOOST_CHECK_NO_THROW(ParseScript("0x02 'a'")); BOOST_CHECK_THROW(ParseScript("0x01 0x0101"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x01 42"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x02 0x01"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x02 XOR"), std::runtime_error); BOOST_CHECK_THROW(ParseScript("0x02 1"), std::runtime_error); 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()