diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -295,7 +295,6 @@ switch (opcode) { case OP_CAT: case OP_SPLIT: - case OP_BIN2NUM: case OP_NUM2BIN: case OP_INVERT: case OP_2MUL: @@ -311,6 +310,7 @@ case OP_AND: case OP_OR: case OP_XOR: + case OP_BIN2NUM: // Opcodes that have been reenabled. if ((flags & SCRIPT_ENABLE_MONOLITH_OPCODES) == 0) { return true; @@ -858,6 +858,7 @@ } valtype &vch1 = stacktop(-2); valtype &vch2 = stacktop(-1); + bool fEqual = (vch1 == vch2); // OP_NOTEQUAL is disabled because it would be too // easy to say something like n != 1 and have some @@ -1246,6 +1247,26 @@ } } break; + // + // Conversion operations + // + case OP_BIN2NUM: { + // (in -- out) + if (stack.size() < 1) { + return set_error( + serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + } + + valtype &n = stacktop(-1); + CScriptNum::MinimallyEncode(n); + + // The resulting number must be a valid number. + if (!CScriptNum::IsMinimallyEncoded(n)) { + return set_error(serror, + SCRIPT_ERR_INVALID_NUMBER_RANGE); + } + } 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 @@ -22,6 +22,7 @@ /* Operands checks */ SCRIPT_ERR_INVALID_OPERAND_SIZE, + SCRIPT_ERR_INVALID_NUMBER_RANGE, /* Failed verify operations */ SCRIPT_ERR_VERIFY, 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 @@ -36,6 +36,9 @@ return "Pubkey count negative or limit exceeded"; case SCRIPT_ERR_INVALID_OPERAND_SIZE: return "Invalid operand size"; + case SCRIPT_ERR_INVALID_NUMBER_RANGE: + return "Given operand is not a number within the valid range " + "[-2^31...2^31]"; case SCRIPT_ERR_BAD_OPCODE: return "Opcode missing or not understood"; case SCRIPT_ERR_DISABLED_OPCODE: 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 @@ -828,7 +828,33 @@ ["'abc' 2 0", "IF NUM2BIN ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "NUM2BIN disabled"], ["'abc' 2 0", "IF NUM2BIN ELSE 1 ENDIF", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "NUM2BIN disabled"], ["'abc' 2 0", "IF BIN2NUM ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "BIN2NUM disabled"], -["'abc' 2 0", "IF BIN2NUM ELSE 1 ENDIF", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "BIN2NUM disabled"], +["'abc' 2 0", "IF BIN2NUM ELSE 1 ENDIF", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM enabled"], + +["BIN2NUM"], +["", "BIN2NUM 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "BIN2NUM, empty stack"], +["0", "BIN2NUM 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, canonical arguement"], +["1", "BIN2NUM 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, canonical arguement"], +["-42", "BIN2NUM -42 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, canonical arguement"], +["0x01 0x00", "BIN2NUM 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, non-canonical arguement"], +["0x04 0xffffff7f", "BIN2NUM 2147483647 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, maximum size arguement"], +["0x04 0xffffffff", "BIN2NUM -2147483647 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, maximum size arguement"], +["0x05 0xffffffff00", "BIN2NUM 2147483647 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_NUMBER_RANGE", "BIN2NUM, oversized arguement"], +["0x05 0xffffff7f80", "BIN2NUM -2147483647 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, non-canonical maximum size arguement"], +["0x05 0x0100000000", "BIN2NUM 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["0x05 0xFE00000000", "BIN2NUM 254 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["0x05 0x0500000080", "BIN2NUM 0x01 0x85 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["0x03 0x800000", "BIN2NUM 128 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Pad where MSB of number is set"], +["0x03 0x800080", "BIN2NUM -128 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Pad where MSB of number is set"], +["0x02 0x8000", "BIN2NUM 128 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Pad where MSB of number is set"], +["0x02 0x8080", "BIN2NUM -128 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Pad where MSB of number is set"], +["0x03 0x0f0000", "BIN2NUM 15 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Don't pad where MSB of number is not set"], +["0x03 0x0f0080", "BIN2NUM -15 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Don't pad where MSB of number is not set"], +["0x02 0x0f00", "BIN2NUM 15 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Don't pad where MSB of number is not set"], +["0x02 0x0f80", "BIN2NUM -15 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Don't pad where MSB of number is not set"], +["0x05 0x0100800000", "BIN2NUM 8388609 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Ensure significant zero bytes are retained"], +["0x05 0x0100800080", "BIN2NUM -8388609 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Ensure significant zero bytes are retained"], +["0x05 0x01000f0000", "BIN2NUM 983041 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Ensure significant zero bytes are retained"], +["0x05 0x01000f0080", "BIN2NUM -983041 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Ensure significant zero bytes are retained"], ["NOP", "SIZE 1", "P2SH,STRICTENC", "INVALID_STACK_OPERATION"], diff --git a/src/test/monolith_opcodes.cpp b/src/test/monolith_opcodes.cpp --- a/src/test/monolith_opcodes.cpp +++ b/src/test/monolith_opcodes.cpp @@ -352,4 +352,95 @@ CheckAllBitwiseOpErrors({b, {}}, SCRIPT_ERR_INVALID_OPERAND_SIZE); } +static void CheckBin2NumOp(const valtype &n, const valtype &expected) { + BaseSignatureChecker sigchecker; + + for (uint32_t flags : flagset) { + ScriptError err = SCRIPT_ERR_OK; + std::vector stack{n}; + bool r = EvalScript(stack, CScript() << OP_BIN2NUM, + flags | SCRIPT_ENABLE_MONOLITH_OPCODES, sigchecker, + &err); + BOOST_CHECK(r); + + std::vector expected_stack{expected}; + BOOST_CHECK(stack == expected_stack); + + // Make sure that if we do not pass the monolith flag, opcodes are still + // disabled. + err = SCRIPT_ERR_OK; + stack = {n}; + r = EvalScript(stack, CScript() << OP_BIN2NUM, flags, sigchecker, &err); + BOOST_CHECK(!r); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_DISABLED_OPCODE); + + // TODO: Check roundtrip with NUM2BIN when NUM2BIN is implemented. + } +} + +static void CheckBin2NumError(const std::vector &original_stack, + ScriptError expected_error) { + BaseSignatureChecker sigchecker; + + for (uint32_t flags : flagset) { + ScriptError err = SCRIPT_ERR_OK; + std::vector stack{original_stack}; + bool r = EvalScript(stack, CScript() << OP_BIN2NUM, + flags | SCRIPT_ENABLE_MONOLITH_OPCODES, sigchecker, + &err); + BOOST_CHECK(!r); + BOOST_CHECK_EQUAL(err, expected_error); + + // Make sure that if we do not pass the monolith flag, opcodes are still + // disabled. + err = SCRIPT_ERR_OK; + stack = {original_stack}; + r = EvalScript(stack, CScript() << OP_BIN2NUM, flags, sigchecker, &err); + BOOST_CHECK(!r); + BOOST_CHECK_EQUAL(err, SCRIPT_ERR_DISABLED_OPCODE); + } +} + +BOOST_AUTO_TEST_CASE(type_conversion_test) { + valtype empty; + CheckBin2NumOp(empty, empty); + + valtype paddedzero, paddednegzero; + for (size_t i = 0; i <= MAX_SCRIPT_ELEMENT_SIZE; i++) { + CheckBin2NumOp(paddedzero, empty); + paddedzero.push_back(0x00); + + paddednegzero.push_back(0x80); + CheckBin2NumOp(paddednegzero, empty); + paddednegzero[paddednegzero.size() - 1] = 0x00; + } + + // Merge leading byte when sign bit isn't used. + std::vector k{0x7f}, negk{0xff}; + std::vector kpadded = k, negkpadded = negk; + for (size_t i = 0; i < MAX_SCRIPT_ELEMENT_SIZE; i++) { + CheckBin2NumOp(kpadded, k); + kpadded.push_back(0x00); + + CheckBin2NumOp(negkpadded, negk); + negkpadded[negkpadded.size() - 1] &= 0x7f; + negkpadded.push_back(0x80); + } + + // Some known values. + CheckBin2NumOp({0xab, 0xcd, 0xef, 0x00}, {0xab, 0xcd, 0xef, 0x00}); + CheckBin2NumOp({0xab, 0xcd, 0x7f, 0x00}, {0xab, 0xcd, 0x7f}); + + // Reductions + CheckBin2NumOp({0xab, 0xcd, 0xef, 0x42, 0x80}, {0xab, 0xcd, 0xef, 0xc2}); + CheckBin2NumOp({0xab, 0xcd, 0x7f, 0x42, 0x00}, {0xab, 0xcd, 0x7f, 0x42}); + + // Empty stack is an error. + CheckBin2NumError({}, SCRIPT_ERR_INVALID_STACK_OPERATION); + CheckBin2NumError({{0xab, 0xcd, 0xef, 0xc2, 0x80}}, + SCRIPT_ERR_INVALID_NUMBER_RANGE); + CheckBin2NumError({{0x00, 0x00, 0x00, 0x80, 0x80}}, + SCRIPT_ERR_INVALID_NUMBER_RANGE); +} + 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 @@ -63,6 +63,7 @@ {SCRIPT_ERR_SIG_COUNT, "SIG_COUNT"}, {SCRIPT_ERR_PUBKEY_COUNT, "PUBKEY_COUNT"}, {SCRIPT_ERR_INVALID_OPERAND_SIZE, "OPERAND_SIZE"}, + {SCRIPT_ERR_INVALID_NUMBER_RANGE, "INVALID_NUMBER_RANGE"}, {SCRIPT_ERR_VERIFY, "VERIFY"}, {SCRIPT_ERR_EQUALVERIFY, "EQUALVERIFY"}, {SCRIPT_ERR_CHECKMULTISIGVERIFY, "CHECKMULTISIGVERIFY"},