/* * This file is part of the Flowee project * Copyright (C) 2012-2015 The Bitcoin Core developers * Copyright (C) 2026 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "scriptnum_tests.h" #include "scriptnum10.h" #include "primitives/script.h" #include "primitives/ScriptBigNum_p.h" #include "script/interpreter.h" static const int64_t values[] = \ { 0, 1, CHAR_MIN, CHAR_MAX, UCHAR_MAX, SHRT_MIN, USHRT_MAX, INT_MIN, INT_MAX, UINT_MAX, LONG_MIN, LONG_MAX }; static const int64_t offsets[] = { 1, 0x79, 0x80, 0x81, 0xFF, 0x7FFF, 0x8000, 0xFFFF, 0x10000}; static bool verify(const CScriptNum10& bignum, const CScriptNum& scriptnum) { return bignum.getvch() == scriptnum.getvch() && bignum.getint() == scriptnum.getint(); } typedef std::vector valtype; typedef std::vector stacktype; static bool EvalScript(uint32_t flags, stacktype &stack, const CScript &script, ScriptError &error) { BaseSignatureChecker checker; Script::State state(flags); const bool result = Script::eval(stack, script, checker, state); error = state.error; return result; } static valtype ToScriptNumVch(const ScriptBigNum::BigInt &value) { return ScriptBigNum(value).getvch(); } static valtype ToBoolVch(bool value) { return CScriptNum(value ? 1 : 0).getvch(); } static void RunOperators(const int64_t& num1, const int64_t& num2) { const CScriptNum10 bignum1(num1); const CScriptNum10 bignum2(num2); const CScriptNum scriptnum1(num1); const CScriptNum scriptnum2(num2); CScriptNum10 bignum3(num1); CScriptNum10 bignum4(num1); CScriptNum scriptnum3(num1); CScriptNum scriptnum4(num1); // int64_t overflow is undefined. bool invalid = (((num2 > 0) && (num1 > (std::numeric_limits::max() - num2))) || ((num2 < 0) && (num1 < (std::numeric_limits::min() - num2)))); if (!invalid) { QVERIFY(verify(bignum1 + bignum2, scriptnum1 + scriptnum2)); QVERIFY(verify(bignum1 + bignum2, scriptnum1 + num2)); QVERIFY(verify(bignum1 + bignum2, scriptnum2 + num1)); } // CheckSubtract // int64_t overflow is undefined. invalid = ((num2 > 0 && num1 < std::numeric_limits::min() + num2) || (num2 < 0 && num1 > std::numeric_limits::max() + num2)); if (!invalid) { QVERIFY(verify(bignum1 - bignum2, scriptnum1 - scriptnum2)); QVERIFY(verify(bignum1 - bignum2, scriptnum1 - num2)); } invalid = ((num1 > 0 && num2 < std::numeric_limits::min() + num1) || (num1 < 0 && num2 > std::numeric_limits::max() + num1)); if (!invalid) { QVERIFY(verify(bignum2 - bignum1, scriptnum2 - scriptnum1)); QVERIFY(verify(bignum2 - bignum1, scriptnum2 - num1)); } // CheckNegate // -INT64_MIN is undefined if (num1 != std::numeric_limits::min()) QVERIFY(verify(-bignum1, -scriptnum1)); // CheckCompare QVERIFY((bignum1 == bignum1) == (scriptnum1 == scriptnum1)); QVERIFY((bignum1 != bignum1) == (scriptnum1 != scriptnum1)); QVERIFY((bignum1 < bignum1) == (scriptnum1 < scriptnum1)); QVERIFY((bignum1 > bignum1) == (scriptnum1 > scriptnum1)); QVERIFY((bignum1 >= bignum1) == (scriptnum1 >= scriptnum1)); QVERIFY((bignum1 <= bignum1) == (scriptnum1 <= scriptnum1)); QVERIFY((bignum1 == bignum1) == (scriptnum1 == num1)); QVERIFY((bignum1 != bignum1) == (scriptnum1 != num1)); QVERIFY((bignum1 < bignum1) == (scriptnum1 < num1)); QVERIFY((bignum1 > bignum1) == (scriptnum1 > num1)); QVERIFY((bignum1 >= bignum1) == (scriptnum1 >= num1)); QVERIFY((bignum1 <= bignum1) == (scriptnum1 <= num1)); QVERIFY((bignum1 == bignum2) == (scriptnum1 == scriptnum2)); QVERIFY((bignum1 != bignum2) == (scriptnum1 != scriptnum2)); QVERIFY((bignum1 < bignum2) == (scriptnum1 < scriptnum2)); QVERIFY((bignum1 > bignum2) == (scriptnum1 > scriptnum2)); QVERIFY((bignum1 >= bignum2) == (scriptnum1 >= scriptnum2)); QVERIFY((bignum1 <= bignum2) == (scriptnum1 <= scriptnum2)); QVERIFY((bignum1 == bignum2) == (scriptnum1 == num2)); QVERIFY((bignum1 != bignum2) == (scriptnum1 != num2)); QVERIFY((bignum1 < bignum2) == (scriptnum1 < num2)); QVERIFY((bignum1 > bignum2) == (scriptnum1 > num2)); QVERIFY((bignum1 >= bignum2) == (scriptnum1 >= num2)); QVERIFY((bignum1 <= bignum2) == (scriptnum1 <= num2)); } void TestScriptNum::creation_data() { QTest::addColumn("num"); for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) { for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j) { qint64 num = values[i]; QTest::newRow(QString::number(num).toLatin1().constData()) << num; num = values[i] + offsets[j]; QTest::newRow(QString::number(num).toLatin1().constData()) << num; num = values[i] - offsets[j]; QTest::newRow(QString::number(num).toLatin1().constData()) << num; } } } void TestScriptNum::creation() { QFETCH(qint64, num); // CheckCreateInt(num); CScriptNum10 bignum(num); CScriptNum scriptnum(num); QVERIFY(verify(bignum, scriptnum)); QVERIFY(verify(CScriptNum10(bignum.getint()), CScriptNum(scriptnum.getint()))); QVERIFY(verify(CScriptNum10(scriptnum.getint()), CScriptNum(bignum.getint()))); QVERIFY(verify(CScriptNum10(CScriptNum10(scriptnum.getint()).getint()), CScriptNum(CScriptNum(bignum.getint()).getint()))); const bool expectException = (scriptnum.getvch().size() > CScriptNum::nDefaultMaxNumSize); try { CScriptNum10 bignum(num); CScriptNum scriptnum(num); QVERIFY(verify(bignum, scriptnum)); std::vector vch = bignum.getvch(); CScriptNum10 bignum2(bignum.getvch(), false); vch = scriptnum.getvch(); CScriptNum scriptnum2(scriptnum.getvch(), false); QVERIFY(verify(bignum2, scriptnum2)); CScriptNum10 bignum3(scriptnum2.getvch(), false); CScriptNum scriptnum3(bignum2.getvch(), false); QVERIFY(verify(bignum3, scriptnum3)); if (expectException) QFAIL("Expected exception"); } catch (scriptnum10_error &e) { if (!expectException) QFAIL("unexpected exception scriptnum10_error"); } catch (...) { QFAIL("unexpected exception"); } } void TestScriptNum::operators() { for(size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) { for(size_t j = 0; j < sizeof(offsets) / sizeof(offsets[0]); ++j) { RunOperators(values[i], values[i]); RunOperators(values[i], -values[i]); RunOperators(values[i], values[j]); RunOperators(values[i], -values[j]); RunOperators(values[i] + values[j], values[j]); RunOperators(values[i] + values[j], -values[j]); RunOperators(values[i] - values[j], values[j]); RunOperators(values[i] - values[j], -values[j]); RunOperators(values[i] + values[j], values[i] + values[j]); RunOperators(values[i] + values[j], values[i] - values[j]); RunOperators(values[i] - values[j], values[i] + values[j]); RunOperators(values[i] - values[j], values[i] - values[j]); } } } void TestScriptNum::op_mul_basic() { const CScript script = CScript() << OP_MUL; ScriptError error = SCRIPT_ERR_OK; stacktype legacyStack{CScriptNum(6).getvch(), CScriptNum(7).getvch()}; QVERIFY(!EvalScript(0, legacyStack, script, error)); QCOMPARE(error, SCRIPT_ERR_DISABLED_OPCODE); stacktype may2025Stack{CScriptNum(6).getvch(), CScriptNum(7).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, may2025Stack, script, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(may2025Stack.size(), size_t(1)); QCOMPARE(may2025Stack.back(), CScriptNum(42).getvch()); } void TestScriptNum::bigint_arithmetic() { const ScriptBigNum::BigInt twoToEighty = ScriptBigNum::BigInt(1) << 80; const ScriptBigNum big(twoToEighty); ScriptError error = SCRIPT_ERR_OK; stacktype addStack{big.getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, addStack, CScript() << OP_1ADD, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(addStack.size(), size_t(1)); QCOMPARE(addStack.back(), ScriptBigNum(twoToEighty + 1).getvch()); stacktype mulStack{big.getvch(), CScriptNum(3).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, mulStack, CScript() << OP_MUL, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(mulStack.size(), size_t(1)); QCOMPARE(mulStack.back(), ScriptBigNum(twoToEighty * 3).getvch()); } void TestScriptNum::bigint_opcode_coverage() { using BigInt = ScriptBigNum::BigInt; const BigInt q = (BigInt(1) << 80) + 17; const BigInt a = q * 3; const BigInt b = 3; ScriptError error = SCRIPT_ERR_OK; struct UnaryCase { opcodetype opcode; BigInt input; BigInt expected; }; const UnaryCase unaryCases[] = { {OP_1ADD, q, q + 1}, {OP_1SUB, q, q - 1}, {OP_NEGATE, q, -q}, {OP_ABS, -q, q}, {OP_NOT, q, 0}, {OP_NOT, 0, 1}, {OP_0NOTEQUAL, q, 1}, {OP_0NOTEQUAL, 0, 0}, }; for (const auto &test : unaryCases) { stacktype stack{ToScriptNumVch(test.input)}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, stack, CScript() << test.opcode, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(stack.size(), size_t(1)); QCOMPARE(stack.back(), ToScriptNumVch(test.expected)); } struct BinaryCase { opcodetype opcode; BigInt lhs; BigInt rhs; valtype expected; }; const BinaryCase binaryCases[] = { {OP_ADD, a, b, ToScriptNumVch(a + b)}, {OP_SUB, a, b, ToScriptNumVch(a - b)}, {OP_MUL, q, b, ToScriptNumVch(q * b)}, {OP_DIV, a, b, ToScriptNumVch(q)}, {OP_MOD, a + 2, b, ToScriptNumVch(2)}, {OP_BOOLAND, a, 0, ToBoolVch(false)}, {OP_BOOLAND, a, b, ToBoolVch(true)}, {OP_BOOLOR, 0, b, ToBoolVch(true)}, {OP_NUMEQUAL, a, a, ToBoolVch(true)}, {OP_NUMEQUAL, a, b, ToBoolVch(false)}, {OP_NUMNOTEQUAL, a, b, ToBoolVch(true)}, {OP_LESSTHAN, b, a, ToBoolVch(true)}, {OP_GREATERTHAN, a, b, ToBoolVch(true)}, {OP_LESSTHANOREQUAL, a, a, ToBoolVch(true)}, {OP_GREATERTHANOREQUAL, a, a, ToBoolVch(true)}, {OP_MIN, a, b, ToScriptNumVch(b)}, {OP_MAX, a, b, ToScriptNumVch(a)}, }; for (const auto &test : binaryCases) { stacktype stack{ToScriptNumVch(test.lhs), ToScriptNumVch(test.rhs)}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, stack, CScript() << test.opcode, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(stack.size(), size_t(1)); QCOMPARE(stack.back(), test.expected); } stacktype equalVerifySuccess{ToScriptNumVch(a), ToScriptNumVch(a)}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, equalVerifySuccess, CScript() << OP_NUMEQUALVERIFY, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(equalVerifySuccess.size(), size_t(0)); stacktype equalVerifyFailure{ToScriptNumVch(a), ToScriptNumVch(b)}; QVERIFY(!EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, equalVerifyFailure, CScript() << OP_NUMEQUALVERIFY, error)); QCOMPARE(error, SCRIPT_ERR_NUMEQUALVERIFY); stacktype divByZero{ToScriptNumVch(a), ToScriptNumVch(0)}; QVERIFY(!EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, divByZero, CScript() << OP_DIV, error)); QCOMPARE(error, SCRIPT_ERR_DIV_BY_ZERO); stacktype modByZero{ToScriptNumVch(a), ToScriptNumVch(0)}; QVERIFY(!EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, modByZero, CScript() << OP_MOD, error)); QCOMPARE(error, SCRIPT_ERR_MOD_BY_ZERO); stacktype withinTrue{ToScriptNumVch(q), ToScriptNumVch(q - 1), ToScriptNumVch(q + 1)}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, withinTrue, CScript() << OP_WITHIN, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(withinTrue.size(), size_t(1)); QCOMPARE(withinTrue.back(), ToBoolVch(true)); stacktype withinFalse{ToScriptNumVch(q), ToScriptNumVch(q + 1), ToScriptNumVch(q + 2)}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, withinFalse, CScript() << OP_WITHIN, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(withinFalse.size(), size_t(1)); QCOMPARE(withinFalse.back(), ToBoolVch(false)); const valtype qMinimal = ToScriptNumVch(q); const size_t paddedSize = qMinimal.size() + 2; stacktype padded{qMinimal, CScriptNum(paddedSize).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, padded, CScript() << OP_NUM2BIN, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(padded.size(), size_t(1)); QCOMPARE(padded.back().size(), paddedSize); stacktype normalized{padded.back()}; QVERIFY(EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, normalized, CScript() << OP_BIN2NUM, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(normalized.size(), size_t(1)); QCOMPARE(normalized.back(), qMinimal); const size_t maxBigIntBits = ScriptBigNum::MAXIMUM_ELEMENT_SIZE_BIG_INT * 8 - 1; const BigInt maxBigInt = (BigInt(1) << maxBigIntBits) - 1; stacktype overflow{ToScriptNumVch(maxBigInt)}; QVERIFY(!EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, overflow, CScript() << OP_1ADD, error)); QCOMPARE(error, SCRIPT_ERR_INVALID_NUMBER_RANGE_BIG_INT); stacktype oversized{valtype(ScriptBigNum::MAXIMUM_ELEMENT_SIZE_BIG_INT + 1, 1)}; QVERIFY(!EvalScript(SCRIPT_ENABLE_64_BIT_INTEGERS, oversized, CScript() << OP_BIN2NUM, error)); QCOMPARE(error, SCRIPT_ERR_INVALID_NUMBER_RANGE_BIG_INT); } void TestScriptNum::may2025_vm_limit_accounting() { ScriptError error = SCRIPT_ERR_OK; CScript opCostScript; for (int i = 0; i < 329; ++i) opCostScript << OP_NOP; stacktype stack; QVERIFY(!EvalScript(SCRIPT_ENABLE_MAY2025, stack, opCostScript, error)); QCOMPARE(error, SCRIPT_ERR_OP_COST); CScript hashScript; for (int i = 0; i < 21; ++i) hashScript << OP_SHA256; stacktype hashStack{valtype(32, 0xaa)}; QVERIFY(!EvalScript(SCRIPT_ENABLE_MAY2025 | SCRIPT_VM_LIMITS_STANDARD, hashStack, hashScript, error)); QCOMPARE(error, SCRIPT_ERR_TOO_MANY_HASH_ITERS); } void TestScriptNum::may2026_bitwise_shift_opcodes() { ScriptError error = SCRIPT_ERR_OK; stacktype disabled{valtype{0x0f, 0xf0}}; QVERIFY(!EvalScript(0, disabled, CScript() << OP_INVERT, error)); QCOMPARE(error, SCRIPT_ERR_DISABLED_OPCODE); stacktype inverted{valtype{0x0f, 0xf0}}; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, inverted, CScript() << OP_INVERT, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(inverted.back(), valtype({0xf0, 0x0f})); stacktype leftNum{CScriptNum(12345).getvch(), CScriptNum(3).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, leftNum, CScript() << OP_LSHIFTNUM, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(leftNum.back(), CScriptNum(12345 * 8).getvch()); stacktype rightNum{CScriptNum(12345).getvch(), CScriptNum(3).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, rightNum, CScript() << OP_RSHIFTNUM, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(rightNum.back(), CScriptNum(12345 / 8).getvch()); stacktype leftBlob{valtype{0xad, 0x00, 0xff}, CScriptNum(2).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, leftBlob, CScript() << OP_LSHIFTBIN, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(leftBlob.back(), valtype({0xb4, 0x03, 0xfc})); stacktype rightBlob{valtype{0xad, 0x00, 0xff}, CScriptNum(2).getvch()}; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, rightBlob, CScript() << OP_RSHIFTBIN, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(rightBlob.back(), valtype({0x2b, 0x40, 0x3f})); } void TestScriptNum::may2026_control_flow_opcodes() { ScriptError error = SCRIPT_ERR_OK; stacktype disabled; QVERIFY(!EvalScript(0, disabled, CScript() << OP_BEGIN, error)); QCOMPARE(error, SCRIPT_ERR_BAD_OPCODE); CScript loopScript; loopScript << OP_0 << OP_BEGIN << OP_1ADD << OP_DUP << OP_3 << OP_NUMEQUAL << OP_UNTIL; stacktype loopStack; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, loopStack, loopScript, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(loopStack.size(), size_t(1)); QCOMPARE(loopStack.back(), CScriptNum(3).getvch()); const valtype functionCode{OP_2, OP_3, OP_ADD}; const valtype functionId{'f'}; CScript functionScript; functionScript << functionCode << functionId << OP_DEFINE << functionId << OP_INVOKE; stacktype functionStack; QVERIFY(EvalScript(SCRIPT_ENABLE_MAY2026, functionStack, functionScript, error)); QCOMPARE(error, SCRIPT_ERR_OK); QCOMPARE(functionStack.size(), size_t(1)); QCOMPARE(functionStack.back(), CScriptNum(5).getvch()); }