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_NUM2BIN: case OP_INVERT: case OP_2MUL: case OP_2DIV: @@ -310,6 +309,7 @@ case OP_AND: case OP_OR: case OP_XOR: + case OP_NUM2BIN: case OP_BIN2NUM: // Opcodes that have been reenabled. if ((flags & SCRIPT_ENABLE_MONOLITH_OPCODES) == 0) { @@ -1250,6 +1250,51 @@ // // Conversion operations // + case OP_NUM2BIN: { + // (in size -- out) + if (stack.size() < 2) { + return set_error( + serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + } + + uint64_t size = + CScriptNum(stacktop(-1), fRequireMinimal).getint(); + if (size > MAX_SCRIPT_ELEMENT_SIZE) { + return set_error(serror, SCRIPT_ERR_PUSH_SIZE); + } + + popstack(stack); + valtype &rawnum = stacktop(-1); + + // Try to see if we can fit that number in the number of + // byte requested. + CScriptNum::MinimallyEncode(rawnum); + if (rawnum.size() > size) { + // We definitively cannot. + return set_error(serror, + SCRIPT_ERR_IMPOSSIBLE_ENCODING); + } + + // We already have an element of the right size, we + // don't need to do anything. + if (rawnum.size() == size) { + break; + } + + uint8_t signbit = 0x00; + if (rawnum.size() > 0) { + signbit = rawnum.back() & 0x80; + rawnum[rawnum.size() - 1] &= 0x7f; + } + + rawnum.reserve(size); + while (rawnum.size() < size - 1) { + rawnum.push_back(0x00); + } + + rawnum.push_back(signbit); + } break; + case OP_BIN2NUM: { // (in -- out) if (stack.size() < 1) { 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 @@ -23,6 +23,7 @@ /* Operands checks */ SCRIPT_ERR_INVALID_OPERAND_SIZE, SCRIPT_ERR_INVALID_NUMBER_RANGE, + SCRIPT_ERR_IMPOSSIBLE_ENCODING, /* 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 @@ -39,6 +39,8 @@ case SCRIPT_ERR_INVALID_NUMBER_RANGE: return "Given operand is not a number within the valid range " "[-2^31...2^31]"; + case SCRIPT_ERR_IMPOSSIBLE_ENCODING: + return "The requested encoding is impossible to satisfy"; 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 @@ -826,18 +826,35 @@ ["'abc' 1 1 0", "IF SPLIT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "SPLIT disabled"], ["'abc' 1 1 0", "IF SPLIT ELSE 1 ENDIF", "P2SH,STRICTENC,MONOLITH_OPCODES", "DISABLED_OPCODE", "SPLIT disabled"], ["'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 NUM2BIN ELSE 1 ENDIF", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN enabled"], ["'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", "OK", "BIN2NUM enabled"], +["NUM2BIN"], +["", "NUM2BIN 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "NUM2BIN, empty stack"], +["0", "NUM2BIN 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "INVALID_STACK_OPERATION", "NUM2BIN, one parameter"], +["0 0", "NUM2BIN 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, canonical argument "], +["0 1", "NUM2BIN 0x01 0x00 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, zero extend"], +["0 7", "NUM2BIN 0x01 0x00000000000000 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, zero extend"], +["1 1", "NUM2BIN 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, canonical argument "], +["-42 1", "NUM2BIN -42 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, canonical argument "], +["-42 2", "NUM2BIN 0x02 0x2a80 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, canonical argument "], +["-42 10", "NUM2BIN 0x02 0x2a000000000000000080 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "NUM2BIN, large materialization"], +["-42 520", "NUM2BIN", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Pushing 520 bytes is ok"], +["-42 521", "NUM2BIN", "P2SH,STRICTENC,MONOLITH_OPCODES", "PUSH_SIZE", "Pushing 521 bytes is not"], +["-42 -3", "NUM2BIN", "P2SH,STRICTENC,MONOLITH_OPCODES", "PUSH_SIZE", "Negative size"], +["0x05 0xabcdef4280 4", "NUM2BIN 0x04 0xabcdefc2", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Item size reduction"], +["0x01 0x80 0", "NUM2BIN 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Negative zero"], +["0x01 0x80 3", "NUM2BIN 0x03 0x000000 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "Negative zero, larger output"], + ["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"], +["0", "BIN2NUM 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, canonical argument "], +["1", "BIN2NUM 1 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, canonical argument "], +["-42", "BIN2NUM -42 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, canonical argument "], +["0x01 0x00", "BIN2NUM 0 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, non-canonical argument "], +["0x04 0xffffff7f", "BIN2NUM 2147483647 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, maximum size argument "], +["0x04 0xffffffff", "BIN2NUM -2147483647 EQUAL", "P2SH,STRICTENC,MONOLITH_OPCODES", "OK", "BIN2NUM, maximum size argument "], ["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"], 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 @@ -155,6 +155,9 @@ valtype allzeros(MAX_SCRIPT_ELEMENT_SIZE, 0); valtype allones(MAX_SCRIPT_ELEMENT_SIZE, 0xff); + BOOST_CHECK_EQUAL(allzeros.size(), MAX_SCRIPT_ELEMENT_SIZE); + BOOST_CHECK_EQUAL(allones.size(), MAX_SCRIPT_ELEMENT_SIZE); + TestBitwiseOpcodes(allzeros, allzeros, allzeros, allzeros); TestBitwiseOpcodes(allzeros, allones, allzeros, allones); TestBitwiseOpcodes(allones, allones, allones, allones); @@ -371,10 +374,39 @@ /** * Type conversion opcodes. */ -static void CheckBin2NumOp(const valtype &n, const valtype &expected) { - CheckTestResultForAllFlags({n}, CScript() << OP_BIN2NUM, {expected}); +static void CheckTypeConversionOp(const valtype &bin, const valtype &num) { + // Check BIN2NUM. + CheckTestResultForAllFlags({bin}, CScript() << OP_BIN2NUM, {num}); + + // Check NUM2BIN. Negative 0 is rebuilt as regular zero, so we need a tweak. + valtype rebuilt_bin{bin}; + if (num.size() == 0 && bin.size() > 0) { + rebuilt_bin[rebuilt_bin.size() - 1] &= 0x7f; + } - // TODO: Check roundtrip with NUM2BIN when NUM2BIN is implemented. + CheckTestResultForAllFlags({num}, CScript() << bin.size() << OP_NUM2BIN, + {rebuilt_bin}); + + // Check roundtrip with NUM2BIN. + CheckTestResultForAllFlags( + {bin}, CScript() << OP_BIN2NUM << bin.size() << OP_NUM2BIN, + {rebuilt_bin}); + + // Grow and shrink back down using NUM2BIN. + CheckTestResultForAllFlags({bin}, + CScript() + << MAX_SCRIPT_ELEMENT_SIZE << OP_NUM2BIN + << bin.size() << OP_NUM2BIN, + {rebuilt_bin}); + CheckTestResultForAllFlags({num}, + CScript() + << MAX_SCRIPT_ELEMENT_SIZE << OP_NUM2BIN + << bin.size() << OP_NUM2BIN, + {rebuilt_bin}); + + // BIN2NUM is indempotent. + CheckTestResultForAllFlags({bin}, CScript() << OP_BIN2NUM << OP_BIN2NUM, + {num}); } static void CheckBin2NumError(const std::vector &original_stack, @@ -383,17 +415,23 @@ expected_error); } +static void CheckNum2BinError(const std::vector &original_stack, + ScriptError expected_error) { + CheckErrorForAllFlags(original_stack, CScript() << OP_NUM2BIN, + expected_error); +} + BOOST_AUTO_TEST_CASE(type_conversion_test) { valtype empty; - CheckBin2NumOp(empty, empty); + CheckTypeConversionOp(empty, empty); valtype paddedzero, paddednegzero; - for (size_t i = 0; i <= MAX_SCRIPT_ELEMENT_SIZE; i++) { - CheckBin2NumOp(paddedzero, empty); + for (size_t i = 0; i < MAX_SCRIPT_ELEMENT_SIZE; i++) { + CheckTypeConversionOp(paddedzero, empty); paddedzero.push_back(0x00); paddednegzero.push_back(0x80); - CheckBin2NumOp(paddednegzero, empty); + CheckTypeConversionOp(paddednegzero, empty); paddednegzero[paddednegzero.size() - 1] = 0x00; } @@ -401,28 +439,48 @@ 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); + CheckTypeConversionOp(kpadded, k); kpadded.push_back(0x00); - CheckBin2NumOp(negkpadded, negk); + CheckTypeConversionOp(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}); + CheckTypeConversionOp({0xab, 0xcd, 0xef, 0x00}, {0xab, 0xcd, 0xef, 0x00}); + CheckTypeConversionOp({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}); + CheckTypeConversionOp({0xab, 0xcd, 0xef, 0x42, 0x80}, + {0xab, 0xcd, 0xef, 0xc2}); + CheckTypeConversionOp({0xab, 0xcd, 0x7f, 0x42, 0x00}, + {0xab, 0xcd, 0x7f, 0x42}); // Empty stack is an error. CheckBin2NumError({}, SCRIPT_ERR_INVALID_STACK_OPERATION); + CheckNum2BinError({}, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // NUM2BIN require 2 elements on the stack. + CheckNum2BinError({{0x00}}, SCRIPT_ERR_INVALID_STACK_OPERATION); + + // Values that do not fit in 4 bytes are considered out of range for + // BIN2NUM. CheckBin2NumError({{0xab, 0xcd, 0xef, 0xc2, 0x80}}, SCRIPT_ERR_INVALID_NUMBER_RANGE); CheckBin2NumError({{0x00, 0x00, 0x00, 0x80, 0x80}}, SCRIPT_ERR_INVALID_NUMBER_RANGE); + + // NUM2BIN must not generate oversized push. + valtype largezero(MAX_SCRIPT_ELEMENT_SIZE, 0); + BOOST_CHECK_EQUAL(largezero.size(), MAX_SCRIPT_ELEMENT_SIZE); + CheckTypeConversionOp(largezero, {}); + + CheckNum2BinError({{}, {0x09, 0x02}}, SCRIPT_ERR_PUSH_SIZE); + + // Check that the requested encoding is possible. + CheckNum2BinError({{0xab, 0xcd, 0xef, 0x80}, {0x03}}, + SCRIPT_ERR_IMPOSSIBLE_ENCODING); } BOOST_AUTO_TEST_SUITE_END()