diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -334,9 +334,9 @@ return set_error(serror, SCRIPT_ERR_OP_COUNT); } - if (opcode == OP_NUM2BIN || opcode == OP_INVERT || - opcode == OP_2MUL || opcode == OP_2DIV || opcode == OP_MUL || - opcode == OP_LSHIFT || opcode == OP_RSHIFT) { + if (opcode == OP_INVERT || opcode == OP_2MUL || opcode == OP_2DIV || + opcode == OP_MUL || opcode == OP_LSHIFT || + opcode == OP_RSHIFT) { // Disabled opcodes. return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); } @@ -344,8 +344,9 @@ // if not monolith protocol upgrade (May 2018) then still disabled if (!fEnabledOpCodesMonolith && (opcode == OP_CAT || opcode == OP_SPLIT || - opcode == OP_BIN2NUM || opcode == OP_AND || opcode == OP_XOR || - opcode == OP_OR || opcode == OP_DIV || opcode == OP_MOD)) { + opcode == OP_BIN2NUM || opcode == OP_NUM2BIN || + opcode == OP_AND || opcode == OP_XOR || opcode == OP_OR || + opcode == OP_DIV || opcode == OP_MOD)) { // Disabled opcodes. return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); } @@ -1330,6 +1331,52 @@ stack.push_back(num.getvch()); } break; + case OP_NUM2BIN: { + // (in size -- out) + if (stack.size() < 2) { + return set_error( + serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + } + CScriptNum num(stacktop(-2), fRequireMinimal); + int64_t size = + CScriptNum(stacktop(-1), fRequireMinimal).getint(); + if (size < 1 || + static_cast(size) > + CScriptNum::nDefaultMaxNumSize) { + return set_error( + serror, SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + } + // Produces a byte vector of num and check if input size + // is valid + valtype vchNum = num.getvch(); + if (size < vchNum.size()) { + return set_error( + serror, SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + } + // Initialize byte vector for result + valtype result; + result.reserve(size); + bool neg{false}; + // Avoid negative zero + if (!vchNum.empty()) { + neg = *vchNum.rbegin() & 0x80; + *vchNum.rbegin() &= ~0x80; + } + // Pad result to declared input size + size_t pad = size - vchNum.size(); + for (uint8_t i = 0; i < pad; ++i) { + result.push_back(0); + } + for (auto i = vchNum.rbegin(); i != vchNum.rend(); + ++i) { + result.push_back(*i); + } + if (neg) *result.begin() |= 0x80; + stack.pop_back(); + stack.pop_back(); + stack.push_back(result); + } break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/script_error.h b/src/script/script_error.h --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -38,6 +38,7 @@ SCRIPT_ERR_MOD_BY_ZERO, SCRIPT_ERR_INVALID_SPLIT_RANGE, SCRIPT_ERR_INVALID_BIN2NUM_OPERATION, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION, /* CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY */ SCRIPT_ERR_NEGATIVE_LOCKTIME, diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -56,6 +56,8 @@ return "Invalid OP_SPLIT range"; case SCRIPT_ERR_INVALID_BIN2NUM_OPERATION: return "Invalid OP_BIN2NUM operation (check operand values)"; + case SCRIPT_ERR_INVALID_NUM2BIN_OPERATION: + return "Invalid OP_NUM2BIN operation (check operand values)"; case SCRIPT_ERR_NEGATIVE_LOCKTIME: return "Negative locktime"; case SCRIPT_ERR_UNSATISFIED_LOCKTIME: diff --git a/src/test/op_code.cpp b/src/test/op_code.cpp --- a/src/test/op_code.cpp +++ b/src/test/op_code.cpp @@ -560,6 +560,157 @@ stack_t{CScriptNum(-106894).getvch()}); test(script, stack_t{mk_bin(0)}, flags, stack_t{CScriptNum(0).getvch()}); } + +/// OP_NUM2BIN tests + +/// make expected value - helper function +/// input: number in LE byte order, desired output byte length +/// output: BIN representation as expected to be generated by the +/// interpreter. +/// removes the sign, constructs a BE array of bytes with the positive +/// number, +/// the adds the sign. +/// +vector make_ev(vector vchInput, size_t size) { + if (vchInput.empty()) return vector(size, 0); + vector result; + assert(size >= vchInput.size()); + result.reserve(size); + bool neg = *vchInput.rbegin() & 0x80; + *vchInput.rbegin() &= ~0x80; + size_t pad = size - vchInput.size(); + for (uint8_t i = 0; i < pad; ++i) { + result.push_back(0); + } + for (auto i = vchInput.rbegin(); i != vchInput.rend(); ++i) { + result.push_back(*i); + } + if (neg) *result.begin() |= 0x80; + return result; +} + +void test_num2bin(const CScript &script, vector vchInput, + uint32_t flags) { + if (vchInput.empty()) return; + for (uint8_t i = 0; i < vchInput.size(); ++i) { + if (i == 0) + test(script, stack_t{vchInput, {}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + else + test(script, stack_t{vchInput, {i}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + } + for (uint8_t i = vchInput.size(); i <= CScriptNum::nDefaultMaxNumSize; + ++i) { + if (i == 0) + test(script, stack_t{vchInput, {}}, flags, + stack_t{make_ev(vchInput, i)}); + else + test(script, stack_t{vchInput, {i}}, flags, + stack_t{make_ev(vchInput, i)}); + } +} + +void test_num2bin(uint32_t flags) { + CScript script; + script << OP_NUM2BIN; + test(script, stack_t(), flags, SCRIPT_ERR_INVALID_STACK_OPERATION); + test(script, stack_t{{4}}, flags, SCRIPT_ERR_INVALID_STACK_OPERATION); + test(script, stack_t{{0x02}, {CScriptNum::nDefaultMaxNumSize + 1}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + test(script, stack_t{{0x85}, {CScriptNum::nDefaultMaxNumSize + 1}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + test(script, stack_t{{0x02}, {}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + test(script, stack_t{{0x85}, {0x85}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + test(script, stack_t{{0x85}, {}}, flags, + SCRIPT_ERR_INVALID_NUM2BIN_OPERATION); + test_num2bin(script, {}, flags); + test_num2bin(script, {0x7f}, flags); + test_num2bin(script, {0xff, 0x7f}, flags); // LE for 0x7FFF + test_num2bin(script, {0x02, 0x71}, flags); + test_num2bin(script, {0xff, 0xff, 0x7f}, flags); + test_num2bin(script, {0x03, 0x02, 0x71}, flags); + test_num2bin(script, {0xff, 0xff, 0xff, 0x7f}, flags); + test_num2bin(script, {0x04, 0x03, 0x02, 0x71}, flags); + test_num2bin(script, {0x81}, flags); + test_num2bin(script, {0xff, 0x80}, flags); + test_num2bin(script, {0xaf, 0x81}, flags); + test_num2bin(script, {0xed, 0x60, 0x83}, flags); + test_num2bin(script, {0xb6, 0xe3, 0x81}, flags); + test_num2bin(script, {0x81, 0x9a, 0x6e, 0x84}, flags); + test_num2bin(script, {0xe4, 0xc3, 0x92, 0x91}, flags); +} + +/// OP_BIN2NUM + OP_NUM2BIN tests + +void test_bin2num_num2bin(const CScript &script, size_t size, int64_t numBinary, + uint32_t flags) { + auto x = mk_bin(numBinary); + test(script, stack_t{x}, flags, + stack_t{make_ev(CScriptNum(numBinary).getvch(), size)}); +} + +void test_num2bin_bin2num(const CScript &script, int64_t numBinary, + uint32_t flags) { + test(script, stack_t{CScriptNum(numBinary).getvch()}, flags, + stack_t{CScriptNum(numBinary).getvch()}); +} + +void test_bin2num_num2bin(int size, uint32_t flags) { + CScript script; + script << OP_BIN2NUM << size << OP_NUM2BIN; + test_bin2num_num2bin(script, size, 0, flags); + test_bin2num_num2bin(script, size, 1, flags); + test_bin2num_num2bin(script, size, -1, flags); + if (size >= 2) { + test_bin2num_num2bin(script, size, 321, flags); + test_bin2num_num2bin(script, size, -321, flags); + if (size >= 3) { + test_bin2num_num2bin(script, size, 106894, flags); + test_bin2num_num2bin(script, size, -106894, flags); + if (size >= 4) { + test_bin2num_num2bin(script, size, INT_MAX >> 1, flags); + test_bin2num_num2bin(script, size, INT_MIN >> 1, flags); + } + } + } +} + +void test_num2bin_bin2num(int size, uint32_t flags) { + CScript script; + script << size << OP_NUM2BIN << OP_BIN2NUM; + test_num2bin_bin2num(script, 0, flags); + test_num2bin_bin2num(script, 1, flags); + test_num2bin_bin2num(script, -1, flags); + if (size >= 2) { + test_num2bin_bin2num(script, 321, flags); + test_num2bin_bin2num(script, -321, flags); + if (size >= 3) { + test_num2bin_bin2num(script, 106894, flags); + test_num2bin_bin2num(script, -106894, flags); + if (size >= 4) { + test_num2bin_bin2num(script, INT_MAX >> 1, flags); + test_num2bin_bin2num(script, INT_MIN >> 1, flags); + } + } + } +} + +void test_bin2num_num2bin(uint32_t flags) { + test_bin2num_num2bin(4, flags); // expect 4 byte output + test_bin2num_num2bin(3, flags); // expect 3 byte output + test_bin2num_num2bin(2, flags); // expect 2 byte output + test_bin2num_num2bin(1, flags); // expect 1 byte output +} + +void test_num2bin_bin2num(uint32_t flags) { + test_num2bin_bin2num(4, flags); // 4 byte num2bin output + test_num2bin_bin2num(3, flags); // 3 byte num2bin output + test_num2bin_bin2num(2, flags); // 2 byte num2bin output + test_num2bin_bin2num(1, flags); // 1 byte num2bin output +} } /// Entry points @@ -641,4 +792,32 @@ SCRIPT_ENABLE_OPCODES_MONOLITH); } +BOOST_AUTO_TEST_CASE(op_num2bin) { + test_num2bin(SCRIPT_ENABLE_OPCODES_MONOLITH); + test_num2bin(STANDARD_SCRIPT_VERIFY_FLAGS | SCRIPT_ENABLE_OPCODES_MONOLITH); + test_num2bin(STANDARD_NOT_MANDATORY_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_num2bin(STANDARD_LOCKTIME_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); +} + +BOOST_AUTO_TEST_CASE(bin2num_num2bin) { + test_bin2num_num2bin(SCRIPT_ENABLE_OPCODES_MONOLITH); + test_bin2num_num2bin(STANDARD_SCRIPT_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_bin2num_num2bin(STANDARD_NOT_MANDATORY_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_bin2num_num2bin(STANDARD_LOCKTIME_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); +} + +BOOST_AUTO_TEST_CASE(num2bin_bin2num) { + test_num2bin_bin2num(SCRIPT_ENABLE_OPCODES_MONOLITH); + test_num2bin_bin2num(STANDARD_SCRIPT_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_num2bin_bin2num(STANDARD_NOT_MANDATORY_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_num2bin_bin2num(STANDARD_LOCKTIME_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); +} BOOST_AUTO_TEST_SUITE_END()