diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -299,8 +299,6 @@ case OP_2MUL: case OP_2DIV: case OP_MUL: - case OP_DIV: - case OP_MOD: case OP_LSHIFT: case OP_RSHIFT: // Disabled opcodes. @@ -311,6 +309,8 @@ case OP_XOR: case OP_NUM2BIN: case OP_BIN2NUM: + case OP_DIV: + case OP_MOD: // Opcodes that have been reenabled. if ((flags & SCRIPT_ENABLE_MONOLITH_OPCODES) == 0) { return true; @@ -926,6 +926,8 @@ case OP_ADD: case OP_SUB: + case OP_DIV: + case OP_MOD: case OP_BOOLAND: case OP_BOOLOR: case OP_NUMEQUAL: @@ -954,6 +956,24 @@ bn = bn1 - bn2; break; + case OP_DIV: + // denominator must not be 0 + if (bn2 == 0) { + return set_error(serror, + SCRIPT_ERR_DIV_BY_ZERO); + } + bn = bn1 / bn2; + break; + + case OP_MOD: + // divisor must not be 0 + if (bn2 == 0) { + return set_error(serror, + SCRIPT_ERR_MOD_BY_ZERO); + } + bn = bn1 % bn2; + break; + case OP_BOOLAND: bn = (bn1 != bnZero && bn2 != bnZero); break; diff --git a/src/script/script.h b/src/script/script.h --- a/src/script/script.h +++ b/src/script/script.h @@ -266,6 +266,20 @@ return operator-(rhs.m_value); } + inline CScriptNum operator/(const int64_t &rhs) const { + return CScriptNum(m_value / rhs); + } + inline CScriptNum operator/(const CScriptNum &rhs) const { + return operator/(rhs.m_value); + } + + inline CScriptNum operator%(const int64_t &rhs) const { + return CScriptNum(m_value % rhs); + } + inline CScriptNum operator%(const CScriptNum &rhs) const { + return operator%(rhs.m_value); + } + inline CScriptNum &operator+=(const CScriptNum &rhs) { return operator+=(rhs.m_value); } 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 @@ -39,6 +39,10 @@ SCRIPT_ERR_INVALID_ALTSTACK_OPERATION, SCRIPT_ERR_UNBALANCED_CONDITIONAL, + /* Divisor errors */ + SCRIPT_ERR_DIV_BY_ZERO, + SCRIPT_ERR_MOD_BY_ZERO, + /* CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY */ SCRIPT_ERR_NEGATIVE_LOCKTIME, SCRIPT_ERR_UNSATISFIED_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 @@ -53,6 +53,10 @@ return "OP_RETURN was encountered"; case SCRIPT_ERR_UNBALANCED_CONDITIONAL: return "Invalid OP_IF construction"; + case SCRIPT_ERR_DIV_BY_ZERO: + return "Division by zero error"; + case SCRIPT_ERR_MOD_BY_ZERO: + return "Modulo by zero error"; case SCRIPT_ERR_NEGATIVE_LOCKTIME: return "Negative locktime"; case SCRIPT_ERR_UNSATISFIED_LOCKTIME: 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 @@ -890,9 +890,9 @@ ["2 2 0 IF MUL ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "MUL disabled"], ["2 2 0 IF MUL ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "MUL disabled"], ["2 2 0 IF DIV ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "DIV disabled"], -["2 2 0 IF DIV ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "DIV disabled"], +["2 2 0 IF DIV ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "DIV enabled"], ["2 2 0 IF MOD ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "MOD disabled"], -["2 2 0 IF MOD ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "MOD disabled"], +["2 2 0 IF MOD ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "MOD enabled"], ["2 2 0 IF LSHIFT ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "LSHIFT disabled"], ["2 2 0 IF LSHIFT ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "LSHIFT disabled"], ["2 2 0 IF RSHIFT ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "RSHIFT disabled"], @@ -930,6 +930,39 @@ ["0 1", "XOR 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OPERAND_SIZE", "XOR, different operand size"], ["0x01 0xab 0x01 0xcd", "XOR 0x01 0x66 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "XOR, more complex operands"], +["DIV"], +["1 1", "DIV 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["1 -1", "DIV -1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["-1 1", "DIV -1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["-1 -1", "DIV 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["2 2", "DIV 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["2 -2", "DIV -1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["-2 2", "DIV -1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["-2 -2", "DIV 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["0 1", "DIV 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["1 0", "DIV", "P2SH,STRICTENC,MONOLITH_OPCODES", "DIV_BY_ZERO", "DIV, divide by zero"], +["3 2", "DIV 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Round towards zero"], +["3 -2", "DIV -1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Round towards zero"], +["1 1", "DIV DEPTH 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Stack depth correct"], +["1", "DIV", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "Not enough operands"], +["0", "DIV", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "Not enough operands"], +["1 1", "DIV 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["2147483648 1", "DIV", "P2SH,STRICTENC,MONOLITH_OPCODES", "UNKNOWN_ERROR", "We cannot do math on 5-byte integers"], +["1 2147483648", "DIV", "P2SH,STRICTENC,MONOLITH_OPCODES", "UNKNOWN_ERROR", "We cannot do math on 5-byte integers"], + +["MOD"], +["1 1", "MOD 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["7 -3", "MOD 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["-7 3", "MOD -1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["10 3", "MOD 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["0 1", "MOD 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], +["1 0", "MOD", "P2SH,STRICTENC,MONOLITH_OPCODES", "MOD_BY_ZERO", "MOD, modulo by zero"], +["1 1", "MOD DEPTH 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Stack depth correct"], +["1", "MOD", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "Not enough operands"], +["0", "MOD", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "Not enough operands"], +["2147483648 1", "MOD", "P2SH,STRICTENC,MONOLITH_OPCODES", "UNKNOWN_ERROR", "We cannot do math on 5-byte integers"], +["1 2147483648", "MOD", "P2SH,STRICTENC,MONOLITH_OPCODES", "UNKNOWN_ERROR", "We cannot do math on 5-byte integers"], + ["EQUAL"], ["", "EQUAL NOT", "P2SH,STRICTENC", "INVALID_STACK_OPERATION", "EQUAL must error when there are no stack items"], ["0", "EQUAL NOT", "P2SH,STRICTENC", "INVALID_STACK_OPERATION", "EQUAL must error when there are not 2 stack items"], @@ -945,13 +978,13 @@ ["2 DUP MUL", "4 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], ["2 DUP MUL", "4 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "disabled"], ["2 DUP DIV", "1 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], -["2 DUP DIV", "1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "disabled"], +["2 DUP DIV", "1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], ["2 2MUL", "4 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], ["2 2MUL", "4 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "disabled"], ["2 2DIV", "1 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], ["2 2DIV", "1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "disabled"], ["7 3 MOD", "1 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], -["7 3 MOD", "1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "disabled"], +["7 3 MOD", "1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK"], ["2 2 LSHIFT", "8 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], ["2 2 LSHIFT", "8 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "disabled"], ["2 1 RSHIFT", "1 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"], 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 @@ -89,6 +89,12 @@ CheckTestResultForAllFlags({a, b}, CScript() << op, {expected}); } +static valtype NegativeValtype(const valtype &v) { + valtype r(v); + r[r.size() - 1] |= 0x80; + return r; +} + /** * Bitwise Opcodes */ @@ -486,4 +492,113 @@ SCRIPT_ERR_IMPOSSIBLE_ENCODING); } +/** + * Arithmetic Opcodes + */ +static void CheckDiv(const valtype &a, const valtype &b, + const valtype &baseResult) { + CheckOp(a, b, OP_DIV, baseResult); + CheckOp(a, NegativeValtype(b), OP_DIV, NegativeValtype(baseResult)); + CheckOp(NegativeValtype(a), b, OP_DIV, NegativeValtype(baseResult)); + CheckOp(NegativeValtype(a), NegativeValtype(b), OP_DIV, baseResult); +} + +static void CheckMod(const valtype &a, const valtype &b, + const valtype &baseResult) { + CheckOp(a, b, OP_MOD, baseResult); + CheckOp(a, NegativeValtype(b), OP_MOD, baseResult); + CheckOp(NegativeValtype(a), b, OP_MOD, NegativeValtype(baseResult)); + CheckOp(NegativeValtype(a), NegativeValtype(b), OP_MOD, + NegativeValtype(baseResult)); +} + +BOOST_AUTO_TEST_CASE(div_opcode_tests) { + CheckOpError(stacktype{}, OP_DIV, SCRIPT_ERR_INVALID_STACK_OPERATION); + CheckOpError(stacktype{{}}, OP_DIV, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // CheckOps not valid numbers + CheckOpError(stacktype{{0x01, 0x02, 0x03, 0x04, 0x05}, + {0x01, 0x02, 0x03, 0x04, 0x05}}, + OP_DIV, SCRIPT_ERR_UNKNOWN_ERROR); + CheckOpError(stacktype{{0x01, 0x02, 0x03, 0x04, 0x05}, {0x01}}, OP_DIV, + SCRIPT_ERR_UNKNOWN_ERROR); + CheckOpError(stacktype{{0x01, 0x05}, {0x01, 0x02, 0x03, 0x04, 0x05}}, + OP_DIV, SCRIPT_ERR_UNKNOWN_ERROR); + // b == 0 ; b is equal to any type of zero + CheckOpError(stacktype{{0x01, 0x05}, {}}, OP_DIV, SCRIPT_ERR_DIV_BY_ZERO); + CheckOpError(stacktype{{}, {}}, OP_DIV, SCRIPT_ERR_DIV_BY_ZERO); + + for (uint32_t flags : flagset) { + if (flags & SCRIPT_VERIFY_MINIMALDATA) { + CheckError(flags, stacktype{{}, {0x00}}, CScript() << OP_DIV, + SCRIPT_ERR_UNKNOWN_ERROR); + CheckError(flags, stacktype{{}, {0x00, 0x00}}, CScript() << OP_DIV, + SCRIPT_ERR_UNKNOWN_ERROR); + } else { + CheckError(flags, stacktype{{}, {0x00}}, CScript() << OP_DIV, + SCRIPT_ERR_DIV_BY_ZERO); + CheckError(flags, stacktype{{}, {0x00, 0x00}}, CScript() << OP_DIV, + SCRIPT_ERR_DIV_BY_ZERO); + } + } + + // 185377af/85f41b01 =-4 + // 185377af/00001b01 =E69D + CheckOp(valtype{0xaf, 0x77, 0x53, 0x18}, valtype{0x01, 0x1b, 0xf4, 0x85}, + OP_DIV, valtype{0x84}); + CheckOp(valtype{0xaf, 0x77, 0x53, 0x18}, valtype{0x01, 0x1b}, OP_DIV, + valtype{0x9D, 0xE6, 0x00}); + + // Identity: 15/1 = 15 (and negative operands) + CheckDiv(valtype{0x0f}, valtype{0x01}, valtype{0x0f}); + // 15/4 = 3 (and negative operands) + CheckDiv(valtype{0x0f}, valtype{0x04}, valtype{0x03}); + // 15000/4 = 3750 (and negative operands) + CheckDiv(valtype{0x98, 0x3a}, valtype{0x04}, valtype{0xa6, 0x0e}); + // 15000/4000 = 3 (and negative operands) + CheckDiv(valtype{0x98, 0x3a}, valtype{0xa0, 0x0f}, valtype{0x03}); + // 15000000/4000 = 3750 (and negative operands) + CheckDiv(valtype{0xc0, 0xe1, 0xe4, 0x00}, valtype{0xa0, 0x0f}, + valtype{0xa6, 0x0e}); + // 15000000/4 = 3750000 (and negative operands) + CheckDiv(valtype{0xc0, 0xe1, 0xe4, 0x00}, valtype{0x04}, + valtype{0x70, 0x38, 0x39}); +} + +BOOST_AUTO_TEST_CASE(mod_opcode_tests) { + CheckOpError(stacktype{}, OP_MOD, SCRIPT_ERR_INVALID_STACK_OPERATION); + CheckOpError(stacktype{{}}, OP_MOD, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // test not valid numbers + CheckOpError(stacktype{{0x01, 0x02, 0x03, 0x04, 0x05}, + {0x01, 0x02, 0x03, 0x04, 0x05}}, + OP_MOD, SCRIPT_ERR_UNKNOWN_ERROR); + CheckOpError(stacktype{{0x01, 0x02, 0x03, 0x04, 0x05}, {0x01}}, OP_MOD, + SCRIPT_ERR_UNKNOWN_ERROR); + CheckOpError(stacktype{{0x01, 0x05}, {0x01, 0x02, 0x03, 0x04, 0x05}}, + OP_MOD, SCRIPT_ERR_UNKNOWN_ERROR); + + // mod by 0 + CheckOpError(stacktype{{0x01, 0x05}, {}}, OP_MOD, SCRIPT_ERR_MOD_BY_ZERO); + + // 56488123 % 321 = 148 (and negative operands) + // 56488123 % 3 = 1 (and negative operands) + // 56488123 % 564881230 = 56488123 (and negative operands) + CheckMod(valtype{0xbb, 0xf0, 0x5d, 0x03}, valtype{0x41, 0x01}, + valtype{0x94, 0x00}); + CheckMod(valtype{0xbb, 0xf0, 0x5d, 0x03}, valtype{0x03}, valtype{0x01}); + CheckMod(valtype{0xbb, 0xf0, 0x5d, 0x03}, valtype{0x4e, 0x67, 0xab, 0x21}, + valtype{0xbb, 0xf0, 0x5d, 0x03}); + + // Identity: a % b % b = a % b + for (uint8_t a = OP_1; a <= OP_16; a++) { + for (uint8_t b = OP_1; b <= OP_16; b++) { + CheckTestResultForAllFlags(stacktype{{a}, {b}}, + CScript() << OP_MOD << b << OP_MOD << a + << b << OP_MOD << OP_EQUAL, + stacktype{{0x01}}); + } + } +} + 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 @@ -92,6 +92,8 @@ {SCRIPT_ERR_NONCOMPRESSED_PUBKEY, "NONCOMPRESSED_PUBKEY"}, {SCRIPT_ERR_ILLEGAL_FORKID, "ILLEGAL_FORKID"}, {SCRIPT_ERR_MUST_USE_FORKID, "MISSING_FORKID"}, + {SCRIPT_ERR_DIV_BY_ZERO, "DIV_BY_ZERO"}, + {SCRIPT_ERR_MOD_BY_ZERO, "MOD_BY_ZERO"}, }; const char *FormatScriptError(ScriptError_t err) {