From 4b9032209a9f793dff7cbb3a8a7cc33fe1813dd7 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 25 Aug 2021 20:48:20 +0000 Subject: [PATCH] Implement arithmetic opcodes --- src/script/interpreter.cpp | 171 ++++++++++++++++++++++++++++++++++++ src/script/script.cpp | 13 +++ src/script/script.h | 18 +++- src/script/script_error.cpp | 4 + src/script/script_error.h | 4 +- 5 files changed, 208 insertions(+), 2 deletions(-) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 054b1b0298..9eab9ef70b 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -15,6 +15,10 @@ typedef std::vector valtype; +// These asserts are consensus critical for elements tapscript arithmetic opcodes +static_assert(static_cast(std::numeric_limits::max()) == UINT64_C(0x7FFFFFFFFFFFFFFF)); +static_assert(static_cast(std::numeric_limits::min()) == UINT64_C(0x8000000000000000)); + namespace { inline bool set_success(ScriptError* ret) @@ -61,6 +65,19 @@ static inline void popstack(std::vector& stack) stack.pop_back(); } +static inline int64_t cast_signed64(uint64_t v) +{ + uint64_t int64_min = static_cast(std::numeric_limits::min()); + if (v >= int64_min) + return static_cast(v - int64_min) + std::numeric_limits::min(); + return static_cast(v); +} + +static inline int64_t read_le8_signed(const unsigned char* ptr) +{ + return cast_signed64(ReadLE64(ptr)); +} + static inline void push4_le(std::vector& stack, uint32_t v) { uint32_t v_le = htole32(v); @@ -1948,6 +1965,160 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_ADD64: + case OP_SUB64: + case OP_MUL64: + case OP_DIV64: + case OP_LESSTHAN64: + case OP_LESSTHANOREQUAL64: + case OP_GREATERTHAN64: + case OP_GREATERTHANOREQUAL64: + { + // Opcodes only available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 2) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& vcha = stacktop(-2); + valtype& vchb = stacktop(-1); + if (vchb.size() != 8 || vcha.size() != 8) + return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES); + + int64_t b = read_le8_signed(vchb.data()); + int64_t a = read_le8_signed(vcha.data()); + + switch(opcode) + { + case OP_ADD64: + if ((a > 0 && b > std::numeric_limits::max() - a) || + (a < 0 && b < std::numeric_limits::min() - a)) + stack.push_back(vchFalse); + else { + popstack(stack); + popstack(stack); + push8_le(stack, a + b); + stack.push_back(vchTrue); + } + break; + case OP_SUB64: + if ((b > 0 && a < std::numeric_limits::min() + b) || + (b < 0 && a > std::numeric_limits::max() + b)) + stack.push_back(vchFalse); + else { + popstack(stack); + popstack(stack); + push8_le(stack, a - b); + stack.push_back(vchTrue); + } + break; + case OP_MUL64: + if ((a > 0 && b > 0 && a > std::numeric_limits::max() / b) || + (a > 0 && b < 0 && b < std::numeric_limits::min() / a) || + (a < 0 && b > 0 && a < std::numeric_limits::min() / b) || + (a < 0 && b < 0 && b < std::numeric_limits::max() / a)) + stack.push_back(vchFalse); + else { + popstack(stack); + popstack(stack); + push8_le(stack, a * b); + stack.push_back(vchTrue); + } + break; + case OP_DIV64: + { + if (b == 0 || (b == -1 && a == std::numeric_limits::min())) { stack.push_back(vchFalse); break; } + int64_t r = a % b; + int64_t q = a / b; + if (r < 0 && b > 0) { r += b; q-=1;} // ensures that 0<=r<|b| + else if (r < 0 && b < 0) { r -= b; q+=1;} // ensures that 0<=r<|b| + popstack(stack); + popstack(stack); + push8_le(stack, r); + push8_le(stack, q); + stack.push_back(vchTrue); + } + break; + break; + case OP_LESSTHAN64: popstack(stack); popstack(stack); stack.push_back( (a < b) ? vchTrue : vchFalse ); break; + case OP_LESSTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a <= b) ? vchTrue : vchFalse ); break; + case OP_GREATERTHAN64: popstack(stack); popstack(stack); stack.push_back( (a > b) ? vchTrue : vchFalse ); break; + case OP_GREATERTHANOREQUAL64: popstack(stack); popstack(stack); stack.push_back( (a >= b) ? vchTrue : vchFalse ); break; + default: assert(!"invalid opcode"); break; + } + } + break; + case OP_NEG64: + { + // Opcodes only available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& vcha = stacktop(-1); + if (vcha.size() != 8) + return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES); + + int64_t a = read_le8_signed(vcha.data()); + if (a == std::numeric_limits::min()) { stack.push_back(vchFalse); break; } + + popstack(stack); + push8_le(stack, -a); + stack.push_back(vchTrue); + } + break; + + case OP_SCRIPTNUMTOLE64: + { + // Opcodes only available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + int64_t num = CScriptNum(stacktop(-1), fRequireMinimal).getint(); + popstack(stack); + push8_le(stack, num); + } + break; + case OP_LE64TOSCRIPTNUM: + { + // Opcodes only available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& vchnum = stacktop(-1); + if (vchnum.size() != 8) + return set_error(serror, SCRIPT_ERR_EXPECTED_8BYTES); + valtype vchscript_num = CScriptNum(read_le8_signed(vchnum.data())).getvch(); + if (vchscript_num.size() > CScriptNum::nDefaultMaxNumSize) { + return set_error(serror, SCRIPT_ERR_ARITHMETIC64); + } else { + popstack(stack); + stack.push_back(std::move(vchscript_num)); + } + } + break; + case OP_LE32TOLE64: + { + // Opcodes only available post tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + + if (stack.size() < 1) + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + + valtype& vchnum = stacktop(-1); + if (vchnum.size() != 4) + return set_error(serror, SCRIPT_ERR_ARITHMETIC64); + uint32_t num = ReadLE32(vchnum.data()); + popstack(stack); + push8_le(stack, static_cast(num)); + } + break; + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/script/script.cpp b/src/script/script.cpp index 3083277228..7df5d6776b 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -168,6 +168,19 @@ std::string GetOpName(opcodetype opcode) case OP_INSPECTNUMOUTPUTS : return "OP_INSPECTNUMOUTPUTS"; case OP_TXWEIGHT : return "OP_TXWEIGHT"; + // 64 bit LE arithmetic opcodes + case OP_ADD64 : return "OP_ADD64"; + case OP_SUB64 : return "OP_SUB64"; + case OP_MUL64 : return "OP_MUL64"; + case OP_DIV64 : return "OP_DIV64"; + case OP_NEG64 : return "OP_NEG64"; + case OP_LESSTHAN64 : return "OP_LESSTHAN64"; + case OP_LESSTHANOREQUAL64 : return "OP_LESSTHANOREQUAL64"; + case OP_GREATERTHAN64 : return "OP_GREATERTHAN64"; + case OP_GREATERTHANOREQUAL64 : return "OP_GREATERTHANOREQUAL64"; + case OP_SCRIPTNUMTOLE64 : return "OP_SCRIPTNUMTOLE64"; + case OP_LE64TOSCRIPTNUM : return "OP_LE64TOSCRIPTNUM"; + case OP_LE32TOLE64 : return "OP_LE32TOLE64"; case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE"; default: diff --git a/src/script/script.h b/src/script/script.h index ed23ca8d53..cb6159cc2a 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -243,11 +243,27 @@ enum opcodetype OP_INSPECTNUMOUTPUTS = 0xd5, OP_TXWEIGHT = 0xd6, + // Arithmetic opcodes + OP_ADD64 = 0xd7, + OP_SUB64 = 0xd8, + OP_MUL64 = 0xd9, + OP_DIV64 = 0xda, + OP_NEG64 = 0xdb, + OP_LESSTHAN64 = 0xdc, + OP_LESSTHANOREQUAL64 = 0xdd, + OP_GREATERTHAN64 = 0xde, + OP_GREATERTHANOREQUAL64 = 0xdf, + + // Conversion opcodes + OP_SCRIPTNUMTOLE64 = 0xe0, + OP_LE64TOSCRIPTNUM = 0xe1, + OP_LE32TOLE64 = 0xe2, + OP_INVALIDOPCODE = 0xff, }; // Maximum value that an opcode can be -static const unsigned int MAX_OPCODE = OP_TXWEIGHT; // 0xd7 +static const unsigned int MAX_OPCODE = OP_LE32TOLE64; // 0xe6 std::string GetOpName(opcodetype opcode); diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index 9e66d8d3b0..89981c480a 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -123,6 +123,10 @@ std::string ScriptErrorString(const ScriptError serror) return "Introspection opcode used without correct evaluation context"; case SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS: return "Introspection index out of bounds"; + case SCRIPT_ERR_EXPECTED_8BYTES: + return "Arithmetic opcodes expect 8 bytes operands"; + case SCRIPT_ERR_ARITHMETIC64: + return "Arithmetic opcode error"; case SCRIPT_ERR_UNKNOWN_ERROR: case SCRIPT_ERR_ERROR_COUNT: default: break; diff --git a/src/script/script_error.h b/src/script/script_error.h index a1c3a94441..f702fbff1b 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -94,9 +94,11 @@ typedef enum ScriptError_t SCRIPT_ERR_SHA2_CONTEXT_WRITE, SCRIPT_ERR_INTROSPECT_CONTEXT_UNAVAILABLE, SCRIPT_ERR_INTROSPECT_INDEX_OUT_OF_BOUNDS, + SCRIPT_ERR_EXPECTED_8BYTES, + SCRIPT_ERR_ARITHMETIC64 } ScriptError; -#define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT +#define SCRIPT_ERR_LAST SCRIPT_ERR_ARITHMETIC64 std::string ScriptErrorString(const ScriptError error);