diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -334,18 +334,19 @@ return set_error(serror, SCRIPT_ERR_OP_COUNT); } - if (opcode == OP_SPLIT || opcode == OP_NUM2BIN || - opcode == OP_BIN2NUM || opcode == OP_INVERT || - opcode == OP_2MUL || opcode == OP_2DIV || opcode == OP_MUL || - opcode == OP_LSHIFT || opcode == OP_RSHIFT) { + if (opcode == OP_NUM2BIN || opcode == OP_BIN2NUM || + 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); } // if not monolith protocol upgrade (May 2018) then still disabled if (!fEnabledOpCodesMonolith && - (opcode == OP_CAT || opcode == OP_AND || opcode == OP_XOR || - opcode == OP_OR || opcode == OP_DIV || opcode == OP_MOD)) { + (opcode == OP_CAT || opcode == OP_SPLIT || 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); } @@ -1268,6 +1269,46 @@ stack.pop_back(); } break; + case OP_SPLIT: { + // (in position -- x1 x2) + if (stack.size() < 2) { + return set_error( + serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + } + valtype vch = stacktop(-2); + int64_t nPosition = + CScriptNum(stacktop(-1), fRequireMinimal).getint(); + + // if nPosition is less than 0 or is larger than the + // input then throw error + if (nPosition < 0 || + static_cast(nPosition) > vch.size()) { + return set_error(serror, + SCRIPT_ERR_INVALID_SPLIT_RANGE); + } + + stack.pop_back(); + stack.pop_back(); + + // initialize outputs + if (nPosition == 0) { + stack.push_back(valtype()); + stack.push_back(vch); + } else if (static_cast(nPosition) == + vch.size()) { + stack.push_back(vch); + stack.push_back(valtype()); + } else { + valtype vchOut1, vchOut2; + vchOut1.insert(vchOut1.end(), vch.begin(), + vch.begin() + nPosition); + vchOut2.insert(vchOut2.end(), + vch.begin() + nPosition, vch.end()); + stack.emplace_back(move(vchOut1)); + stack.emplace_back(move(vchOut2)); + } + } 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 @@ -36,6 +36,7 @@ SCRIPT_ERR_INVALID_BITWISE_OPERATION, SCRIPT_ERR_DIV_BY_ZERO, SCRIPT_ERR_MOD_BY_ZERO, + SCRIPT_ERR_INVALID_SPLIT_RANGE, /* 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 @@ -52,6 +52,8 @@ return "Invalid division operation"; case SCRIPT_ERR_MOD_BY_ZERO: return "Invalid modulo operation"; + case SCRIPT_ERR_INVALID_SPLIT_RANGE: + return "Invalid OP_SPLIT range"; 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 @@ -414,6 +414,88 @@ stack_t{{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14}}); } + +/// OP_SPLIT + +void test_split(uint32_t flags) { + CScript script; + script << OP_SPLIT; // inputs: x n; outputs: x1 x2 + + // two inputs required + test(script, stack_t(), flags, SCRIPT_ERR_INVALID_STACK_OPERATION); + test(script, stack_t{{0x01}}, flags, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // length of 2nd input greater than CScriptNum::nDefaultMaxNumSize + { + item maxlength_num_item(CScriptNum::nDefaultMaxNumSize, 0x01); + item illegal_item = maxlength_num_item; + illegal_item.push_back(0x00); + test(script, stack_t{{0x01}, illegal_item}, flags, + SCRIPT_ERR_UNKNOWN_ERROR); + } + + // if n == 0, then x1 is the empty array and x2 == x; + // execution of OP_SPLIT on empty array results in two empty arrays. + test(script, stack_t{{}, {}}, flags, stack_t{{}, {}}); + test(script, stack_t{{0x01}, {}}, flags, + stack_t{{}, {0x01}}); // x 0 OP_SPLIT -> OP_0 x + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {}}, flags, + stack_t{{}, {0x01, 0x02, 0x03, 0x04}}); + + // if n == len(x) then x1 == x and x2 is the empty array + test(script, stack_t{{0x01}, {0x01}}, flags, stack_t{{0x01}, {}}); + test(script, stack_t{{0x01, 0x02, 0x03}, {0x03}}, flags, + stack_t{{0x01, 0x02, 0x03}, {}}); // x len(x) OP_SPLIT -> x OP_0 + + // if n > len(x), then the operator must fail; x (len(x) + 1) OP_SPLIT -> + // FAIL + test(script, stack_t{{}, {0x01}}, flags, SCRIPT_ERR_INVALID_SPLIT_RANGE); + test(script, stack_t{{0x01}, {0x02}}, flags, + SCRIPT_ERR_INVALID_SPLIT_RANGE); + test(script, stack_t{{0x01, 0x02, 0x03}, {0x04}}, flags, + SCRIPT_ERR_INVALID_SPLIT_RANGE); + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {0x05}}, flags, + SCRIPT_ERR_INVALID_SPLIT_RANGE); + + // if n < 0 the operator must fail. + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {0x81}}, flags, + SCRIPT_ERR_INVALID_SPLIT_RANGE); + + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {0x01}}, flags, + stack_t{{0x01}, {0x02, 0x03, 0x04}}); + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {0x02}}, flags, + stack_t{{0x01, 0x02}, {0x03, 0x04}}); + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {0x03}}, flags, + stack_t{{0x01, 0x02, 0x03}, {0x04}}); + test(script, stack_t{{0x01, 0x02, 0x03, 0x04}, {0x04}}, flags, + stack_t{{0x01, 0x02, 0x03, 0x04}, {}}); + + // split of a max-len item + { + item maxlength_item(MAX_SCRIPT_ELEMENT_SIZE, 0x00); + test(script, stack_t{maxlength_item, {}}, flags, + stack_t{{}, maxlength_item}); + } +} + +/// OP_CAT + OP_SPLIT + +void test_cat_split(const item &x, uint32_t flags) { + CScript script; + script << OP_SPLIT << OP_CAT; + // x n OP_SPLIT OP_CAT -> x - for all x and for all 0 <= n <= len(x) + test(script, stack_t{x, {}}, flags, stack_t{x}); + for (uint8_t i = 1; i <= x.size(); ++i) { + test(script, stack_t{x, {i}}, flags, stack_t{x}); + } +} + +void test_cat_split(uint32_t flags) { + test_cat_split({}, flags); + test_cat_split({0x01}, flags); + test_cat_split({0x01, 0x02}, flags); + test_cat_split({0x01, 0x02, 0x03}, flags); +} } /// Entry points @@ -468,4 +550,22 @@ test_cat(STANDARD_LOCKTIME_VERIFY_FLAGS | SCRIPT_ENABLE_OPCODES_MONOLITH); } +BOOST_AUTO_TEST_CASE(op_split) { + test_split(SCRIPT_ENABLE_OPCODES_MONOLITH); + test_split(STANDARD_SCRIPT_VERIFY_FLAGS | SCRIPT_ENABLE_OPCODES_MONOLITH); + test_split(STANDARD_NOT_MANDATORY_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_split(STANDARD_LOCKTIME_VERIFY_FLAGS | SCRIPT_ENABLE_OPCODES_MONOLITH); +} + +BOOST_AUTO_TEST_CASE(cat_split) { + test_cat_split(SCRIPT_ENABLE_OPCODES_MONOLITH); + test_cat_split(STANDARD_SCRIPT_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_cat_split(STANDARD_NOT_MANDATORY_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); + test_cat_split(STANDARD_LOCKTIME_VERIFY_FLAGS | + SCRIPT_ENABLE_OPCODES_MONOLITH); +} + BOOST_AUTO_TEST_SUITE_END()