diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -87,6 +87,7 @@ test/multisig_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ + test/op_reversebytes_tests.cpp \ test/pmt_tests.cpp \ test/policyestimator_tests.cpp \ test/pow_tests.cpp \ diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1235,6 +1235,21 @@ stacktop(-1) = std::move(n2); } break; + case OP_REVERSEBYTES: { + if (!(flags & SCRIPT_ENABLE_OP_REVERSEBYTES)) { + return set_error(serror, ScriptError::BAD_OPCODE); + } + + // (in -- out) + if (stack.size() < 1) { + return set_error( + serror, ScriptError::INVALID_STACK_OPERATION); + } + + valtype &data = stacktop(-1); + std::reverse(data.begin(), data.end()); + } break; + // // Conversion operations // diff --git a/src/script/script.h b/src/script/script.h --- a/src/script/script.h +++ b/src/script/script.h @@ -182,6 +182,9 @@ OP_CHECKDATASIG = 0xba, OP_CHECKDATASIGVERIFY = 0xbb, + // additional byte string operations + OP_REVERSEBYTES = 0xbc, + // The first op_code value after all defined opcodes FIRST_UNDEFINED_OP_VALUE, diff --git a/src/script/script.cpp b/src/script/script.cpp --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -232,6 +232,8 @@ return "OP_CHECKDATASIG"; case OP_CHECKDATASIGVERIFY: return "OP_CHECKDATASIGVERIFY"; + case OP_REVERSEBYTES: + return "OP_REVERSEBYTES"; // expansion case OP_NOP1: diff --git a/src/script/script_flags.h b/src/script/script_flags.h --- a/src/script/script_flags.h +++ b/src/script/script_flags.h @@ -111,6 +111,9 @@ // Note: The Segwit Recovery feature is a (currently moot) exception to // VERIFY_INPUT_SIGCHECKS SCRIPT_VERIFY_INPUT_SIGCHECKS = (1U << 22), + + // Whether the new OP_REVERSEBYTES opcode can be used. + SCRIPT_ENABLE_OP_REVERSEBYTES = (1U << 23), // A utility flag to decide whether VerifyScript should output the correct // sigchecks value or to report zero. diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -127,6 +127,7 @@ multisig_tests.cpp net_tests.cpp netbase_tests.cpp + op_reversebytes_tests.cpp pmt_tests.cpp policyestimator_tests.cpp pow_tests.cpp 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 @@ -254,8 +254,8 @@ ["0", "IF CHECKDATASIG ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF CHECKDATASIGVERIFY ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xbc ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], -["0", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], +["0", "IF REVERSEBYTES ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], +["0", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], ["0", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xbf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xc0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], @@ -961,6 +961,40 @@ "P2SH,STRICTENC", "OK", "SPLIT, maximum length with empty string" ], +["REVERSEBYTES"], +["0", "REVERSEBYTES 0 EQUAL", "P2SH", "BAD_OPCODE", "REVERSEBYTES, not yet activated in executed branch"], +["0x02 0xbeef", "REVERSEBYTES 0x02 0xefbe EQUAL", "P2SH", "BAD_OPCODE", "REVERSEBYTES, not yet activated in executed branch"], +["", "REVERSEBYTES 0 EQUAL", "P2SH,REVERSEBYTES", "INVALID_STACK_OPERATION", "REVERSEBYTES, empty stack"], +["0", "REVERSEBYTES 0 EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, empty data"], +["0x01 0x99", "REVERSEBYTES 0x01 0x99 EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 1 byte"], +["0x02 0xbeef", "REVERSEBYTES 0x02 0xefbe EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 2 bytes"], +["0x03 0xdeada1", "REVERSEBYTES 0x03 0xa1adde EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 3 bytes"], +["0x04 0xdeadbeef", "REVERSEBYTES 0x04 0xefbeadde EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 4 bytes"], +["'Bitcoin:_A_peer-to-peer_electronic_cash_system'", "REVERSEBYTES 'metsys_hsac_cinortcele_reep-ot-reep_A_:nioctiB' EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, whitepaper title"], +[ + "'zngyivniryrgefgnvqwfwqplmramujzilzyrsdvinxfkfmuowdpuzycnzbupwwpzrfxsbyrhdlsyixyzysodseayvvrtbsfxtikrjwkbduulrjyjlwlaigomhyohsukawdwbrpuacdijzzgxhataguajvuopuktvtklwhsxqvzzfttpdgnxtnbpsiqecxurlczqmoxznlsuejvneiyejetcxlblzrydscnrbydnqytorstjtuzlbbtbyzfiniuehbisqnqhvexylhohjiyiknzgjowvobsrwcxyfowqcvakgdolwpltfcxtrhuysrrvtprzpsucgogsjapdkrbobpxccqgkdumskaleycwsbkabdkuukqiyizceduplmauszwjdzptvmthxocwrignxjogxsvrsjrrlecvdmazlpfkgmskiqqitrevuwiisvpxvkeypzaqjwwiozvmahmtvtjpbolwrymvzfstopzcexalirwbbcqgjvfjfuirrcnlgcfyqnafhh'", + "REVERSEBYTES 'hhfanqyfcglncrriufjfvjgqcbbwrilaxeczpotsfzvmyrwlobpjtvtmhamvzoiwwjqazpyekvxpvsiiwuvertiqqiksmgkfplzamdvcelrrjsrvsxgojxngirwcoxhtmvtpzdjwzsuamlpudecziyiqkuukdbakbswcyelaksmudkgqccxpbobrkdpajsgogcuspzrptvrrsyuhrtxcftlpwlodgkavcqwofyxcwrsbovwojgznkiyijhohlyxevhqnqsibheuinifzybtbblzutjtsrotyqndybrncsdyrzlblxctejeyienvjeuslnzxomqzclruxceqispbntxngdpttfzzvqxshwlktvtkupouvjaugatahxgzzjidcauprbwdwakushoyhmogialwljyjrluudbkwjrkitxfsbtrvvyaesdosyzyxiysldhrybsxfrzpwwpubzncyzupdwoumfkfxnivdsryzlizjumarmlpqwfwqvngfegryrinviygnz' EQUAL", + "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 520 bytes" +], +["0x03 0x123456", "REVERSEBYTES 0x03 0x563412 OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 3 bytes equal"], +["0x06 0x020406080a0c", "DUP REVERSEBYTES REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, 3 bytes double reverse equal"], +["'Bitcoin:_A_peer-to-peer_electronic_cash_system'", "DUP REVERSEBYTES REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, whitepaper title double reverse equal"], +[ + "'zngyivniryrgefgnvqwfwqplmramujzilzyrsdvinxfkfmuowdpuzycnzbupwwpzrfxsbyrhdlsyixyzysodseayvvrtbsfxtikrjwkbduulrjyjlwlaigomhyohsukawdwbrpuacdijzzgxhataguajvuopuktvtklwhsxqvzzfttpdgnxtnbpsiqecxurlczqmoxznlsuejvneiyejetcxlblzrydscnrbydnqytorstjtuzlbbtbyzfiniuehbisqnqhvexylhohjiyiknzgjowvobsrwcxyfowqcvakgdolwpltfcxtrhuysrrvtprzpsucgogsjapdkrbobpxccqgkdumskaleycwsbkabdkuukqiyizceduplmauszwjdzptvmthxocwrignxjogxsvrsjrrlecvdmazlpfkgmskiqqitrevuwiisvpxvkeypzaqjwwiozvmahmtvtjpbolwrymvzfstopzcexalirwbbcqgjvfjfuirrcnlgcfyqnafhh'", + "DUP REVERSEBYTES REVERSEBYTES OP_EQUAL", + "P2SH,REVERSEBYTES", + "OK", + "REVERSEBYTES, 520 bytes double reverse equal" +], +["0x05 0x0102030201", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, palindrome 1"], +["0x08 0x7766554444556677", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, palindrome 2"], +["'madam'", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, palindrome 3"], +["'racecar'", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, palindrome 4"], +["'redrum_siris_murder'", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, palindrome 5"], +["'step_on_no_pets'", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "OK", "REVERSEBYTES, palindrome 6"], +["'Bitcoin:_A_peer-to-peer_electronic_cash_system'", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "EVAL_FALSE", "REVERSEBYTES, non-palindrome 1"], +["0x02 0x1234", "DUP REVERSEBYTES OP_EQUAL", "P2SH,REVERSEBYTES", "EVAL_FALSE", "REVERSEBYTES, non-palindrome 2"], + ["NUM2BIN"], ["", "NUM2BIN 0 EQUAL", "P2SH,STRICTENC", "INVALID_STACK_OPERATION", "NUM2BIN, empty stack"], ["0", "NUM2BIN 0 EQUAL", "P2SH,STRICTENC", "INVALID_STACK_OPERATION", "NUM2BIN, one parameter"], @@ -1241,8 +1275,8 @@ "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS", "Discouraged NOP10 in redeemScript"], ["0x50","1", "P2SH,STRICTENC", "BAD_OPCODE", "opcode 0x50 is reserved"], -["1", "IF 0xbc ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], -["1", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], +["1", "IF REVERSEBYTES ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcode OP_REVERSEBYTES invalid if executed normally"], +["1", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], ["1", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xbf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xc0 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], @@ -1362,7 +1396,7 @@ ["1","RESERVED", "P2SH,STRICTENC", "BAD_OPCODE", "OP_RESERVED is reserved"], ["1","RESERVED1", "P2SH,STRICTENC", "BAD_OPCODE", "OP_RESERVED1 is reserved"], ["1","RESERVED2", "P2SH,STRICTENC", "BAD_OPCODE", "OP_RESERVED2 is reserved"], -["1","0xbc", "P2SH,STRICTENC", "BAD_OPCODE", "0xbc == FIRST_UNDEFINED_OP_VALUE"], +["1","0xbd", "P2SH,STRICTENC", "BAD_OPCODE", "0xbd == FIRST_UNDEFINED_OP_VALUE"], ["2147483648", "1ADD 1", "P2SH,STRICTENC", "UNKNOWN_ERROR", "We cannot do math on 5-byte integers"], ["2147483648", "NEGATE 1", "P2SH,STRICTENC", "UNKNOWN_ERROR", "We cannot do math on 5-byte integers"], diff --git a/src/test/op_reversebytes_tests.cpp b/src/test/op_reversebytes_tests.cpp new file mode 100644 --- /dev/null +++ b/src/test/op_reversebytes_tests.cpp @@ -0,0 +1,156 @@ +// Copyright (c) 2020 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <script/interpreter.h> +#include <script/script.h> + +#include <test/lcg.h> +#include <test/test_bitcoin.h> + +#include <boost/test/unit_test.hpp> + +typedef std::vector<uint8_t> valtype; +typedef std::vector<valtype> stacktype; + +BOOST_FIXTURE_TEST_SUITE(op_reversebytes_tests, BasicTestingSetup) + +struct ReverseTestCase { + const valtype item; + const valtype reversed_item; +}; + +static void CheckErrorWithFlags(const uint32_t flags, + const stacktype &original_stack, + const CScript &script, + const ScriptError expected) { + BaseSignatureChecker sigchecker; + ScriptError err = ScriptError::OK; + stacktype stack{original_stack}; + bool r = EvalScript(stack, script, flags, sigchecker, &err); + BOOST_CHECK(!r); + BOOST_CHECK(err == expected); +} + +static void CheckPassWithFlags(const uint32_t flags, + const stacktype &original_stack, + const CScript &script, + const stacktype &expected) { + BaseSignatureChecker sigchecker; + ScriptError err = ScriptError::OK; + stacktype stack{original_stack}; + bool r = EvalScript(stack, script, flags, sigchecker, &err); + BOOST_CHECK(r); + BOOST_CHECK(err == ScriptError::OK); + BOOST_CHECK(stack == expected); +} + +/** + * Verifies that the given error occurs with OP_REVERSEBYTES enabled + * and that BAD_OPCODE occurs if disabled. + */ +static void CheckErrorIfEnabled(const uint32_t flags, + const stacktype &original_stack, + const CScript &script, + const ScriptError expected) { + CheckErrorWithFlags(flags | SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, + script, expected); + CheckErrorWithFlags(flags & ~SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, + script, ScriptError::BAD_OPCODE); +} + +/** + * Verifies that the given stack results with OP_REVERSEBYTES enabled + * and that BAD_OPCODE occurs if disabled. + */ +static void CheckPassIfEnabled(const uint32_t flags, + const stacktype &original_stack, + const CScript &script, + const stacktype &expected) { + CheckPassWithFlags(flags | SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, + script, expected); + CheckErrorWithFlags(flags & ~SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, + script, ScriptError::BAD_OPCODE); +} + +/** + * Verifies a given reverse test case. + * Checks both if <item> OP_REVERSEBYTES results in <reversed_item> and + * whether double-reversing <item> is a no-op. + */ +static void CheckPassReverse(const uint32_t flags, + const ReverseTestCase &reverse_case) { + CheckPassIfEnabled(flags, {reverse_case.item}, CScript() << OP_REVERSEBYTES, + {reverse_case.reversed_item}); + CheckPassIfEnabled(flags, {reverse_case.item}, + CScript() << OP_DUP << OP_REVERSEBYTES << OP_REVERSEBYTES + << OP_EQUALVERIFY, + {}); +} + +BOOST_AUTO_TEST_CASE(op_reversebytes_tests) { + MMIXLinearCongruentialGenerator lcg; + // Manual tests. + std::vector<ReverseTestCase> test_cases({ + {{}, {}}, + {{99}, {99}}, + {{0xde, 0xad}, {0xad, 0xde}}, + {{0xde, 0xad, 0xa1}, {0xa1, 0xad, 0xde}}, + {{0xde, 0xad, 0xbe, 0xef}, {0xef, 0xbe, 0xad, 0xde}}, + {{0x12, 0x34, 0x56}, {0x56, 0x34, 0x12}}, + }); + + // Palindrome tests, they are their own reverse. + std::vector<valtype> palindromes; + palindromes.reserve(MAX_SCRIPT_ELEMENT_SIZE); + + // Generated tests: + // - for iota(n) mod 256, n = 0,..,520. + // - for random bitstrings, n = 0,..,520. + // - for palindromes 0,..,n,..,0. + for (size_t datasize = 0; datasize <= MAX_SCRIPT_ELEMENT_SIZE; ++datasize) { + valtype iota_data, random_data, palindrome; + iota_data.reserve(datasize); + random_data.reserve(datasize); + palindrome.reserve(datasize); + for (size_t item = 0; item < datasize; ++item) { + iota_data.emplace_back(item % 256); + random_data.emplace_back(lcg.next() % 256); + palindrome.emplace_back( + (item < (datasize + 1) / 2 ? item : datasize - item - 1) % 256); + } + test_cases.push_back( + {iota_data, {iota_data.rbegin(), iota_data.rend()}}); + test_cases.push_back( + {random_data, {random_data.rbegin(), random_data.rend()}}); + palindromes.push_back(palindrome); + } + + for (int i = 0; i < 4096; i++) { + // Generate random flags. + uint32_t flags = lcg.next(); + + // Empty stack. + CheckErrorIfEnabled(flags, {}, CScript() << OP_REVERSEBYTES, + ScriptError::INVALID_STACK_OPERATION); + + for (const ReverseTestCase &test_case : test_cases) { + CheckPassReverse(flags, test_case); + } + + for (const valtype &palindrome : palindromes) { + // Verify palindrome. + CheckPassIfEnabled( + flags, {palindrome}, + CScript() << OP_DUP << OP_REVERSEBYTES << OP_EQUALVERIFY, {}); + } + + // Verify non-palindrome fails. + CheckErrorIfEnabled(flags, {{0x01, 0x02, 0x03, 0x02, 0x02}}, + CScript() + << OP_DUP << OP_REVERSEBYTES << OP_EQUALVERIFY, + ScriptError::EQUALVERIFY); + } +} + +BOOST_AUTO_TEST_SUITE_END() 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 @@ -185,7 +185,7 @@ // anything about what happens when they are flipped. Keep them as-is. extra_flags &= ~(SCRIPT_ENABLE_SIGHASH_FORKID | SCRIPT_ENABLE_REPLAY_PROTECTION | - SCRIPT_ENABLE_SCHNORR_MULTISIG); + SCRIPT_ENABLE_SCHNORR_MULTISIG | SCRIPT_ENABLE_OP_REVERSEBYTES); uint32_t combined_flags = expect ? (flags & ~extra_flags) : (flags | extra_flags); // Weed out invalid flag combinations. diff --git a/src/test/scriptflags.cpp b/src/test/scriptflags.cpp --- a/src/test/scriptflags.cpp +++ b/src/test/scriptflags.cpp @@ -34,6 +34,7 @@ {"DISALLOW_SEGWIT_RECOVERY", SCRIPT_DISALLOW_SEGWIT_RECOVERY}, {"SCHNORR_MULTISIG", SCRIPT_ENABLE_SCHNORR_MULTISIG}, {"INPUT_SIGCHECKS", SCRIPT_VERIFY_INPUT_SIGCHECKS}, + {"REVERSEBYTES", SCRIPT_ENABLE_OP_REVERSEBYTES}, }; uint32_t ParseScriptFlags(std::string strFlags) { diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -242,6 +242,9 @@ OP_CHECKDATASIG = CScriptOp(0xba) OP_CHECKDATASIGVERIFY = CScriptOp(0xbb) +# additional byte string operations +OP_REVERSEBYTES = CScriptOp(0xbc) + # multi-byte opcodes OP_PREFIX_BEGIN = CScriptOp(0xf0) OP_PREFIX_END = CScriptOp(0xf7)