diff --git a/src/script/script.h b/src/script/script.h --- a/src/script/script.h +++ b/src/script/script.h @@ -194,6 +194,8 @@ bool IsMinimalArray(const std::vector &vch, const size_t nMaxNumSize = DEFAULT_MAX_NUM_BYTES); +std::vector MinimalizeBigEndianArray(const std::vector &data); + class scriptnum_error : public std::runtime_error { public: explicit scriptnum_error(const std::string &str) diff --git a/src/script/script.cpp b/src/script/script.cpp --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -8,6 +8,8 @@ #include "tinyformat.h" #include "utilstrencodings.h" +#include + const char *GetOpName(opcodetype opcode) { switch (opcode) { // push value @@ -289,6 +291,59 @@ return true; } +std::vector +MinimalizeBigEndianArray(const std::vector &data) { + std::vector res; + + // Can't encode more than this, go ahead and grab as much room as we could + // possibly need + res.reserve(data.size()); + + // Ensure we have a byte to work with. + if (data.size() == 0) { + return res; + } + + // Store the MSB + uint8_t neg = data[0] & 0x80; + bool havePushed = false; + for (size_t i = 0; i < data.size(); ++i) { + uint8_t x = data[i]; + + // Remove any MSB that might exist + if (i == 0) { + x &= 0x7f; + } + + // If we haven't pushed anything, and the current value is zero, keep + // ignoring bytes. + if (!havePushed && x == 0) { + continue; + } + + // Record that we have begun pushing, and store the current value. + havePushed = true; + res.push_back(x); + } + + // Give us at least one byte + if (res.size() == 0) { + return res; + } + + // Only add back the sign if a value has been pushed. This implies the + // result is non-zero. + if (havePushed) { + // If the MSB is currently occupied, we need one extra byte. + if ((res[0] & 0x80) != 0) { + res.insert(res.begin(), 0); + } + res[0] |= neg; + } + + return res; +} + unsigned int CScript::GetSigOpCount(bool fAccurate) const { unsigned int n = 0; const_iterator pc = begin(); 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 @@ -414,6 +414,47 @@ } } // namespace +BOOST_AUTO_TEST_CASE(minimize_big_endian_test) { + // Empty array case + BOOST_CHECK(MinimalizeBigEndianArray(std::vector()) == + std::vector()); + + // Zero arrays of various lengths + std::vector zeroArray({0x00}); + std::vector negZeroArray({0x80}); + for (int i = 0; i < 16; i++) { + if (i > 0) { + zeroArray.push_back(0x00); + negZeroArray.push_back(0x00); + } + + BOOST_CHECK(MinimalizeBigEndianArray(zeroArray) == + std::vector()); + + // -0 should always evaluate to 0x00 + BOOST_CHECK(MinimalizeBigEndianArray(negZeroArray) == + std::vector()); + } + + // Shouldn't minimalize this array to a negative number + std::vector notNegArray({{0x00, 0x80}}); + std::vector notNegArrayPadded({{0x00, 0x80}}); + for (int i = 0; i < 16; i++) { + notNegArray.push_back(i); + notNegArrayPadded.insert(notNegArrayPadded.begin(), 0x00); + BOOST_CHECK(MinimalizeBigEndianArray(notNegArray) == notNegArray); + BOOST_CHECK(MinimalizeBigEndianArray(notNegArrayPadded) == + std::vector({{0x00, 0x80}})); + } + + // Shouldn't minimalize these arrays at all + std::vector noMinArray; + for (int i = 1; i < 0x80; i++) { + noMinArray.push_back(i); + BOOST_CHECK(MinimalizeBigEndianArray(noMinArray) == noMinArray); + } +} + BOOST_AUTO_TEST_CASE(script_build) { const KeyData keys;