Files
thehub/testing/bitcoin-protocol/scriptnum_tests.cpp
tomFlowee 0ae11a2bfc Fix test after moving bigint activation
Since we activated op-mul at the 64-bit upgrade date, this test
now follows this idea.
2026-05-17 13:10:22 +02:00

434 lines
17 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2012-2015 The Bitcoin Core developers
* Copyright (C) 2026 Tom Zander <tom@flowee.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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<unsigned char> valtype;
typedef std::vector<valtype> 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<int64_t>::max() - num2))) ||
((num2 < 0) && (num1 < (std::numeric_limits<int64_t>::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<int64_t>::min() + num2) ||
(num2 < 0 && num1 > std::numeric_limits<int64_t>::max() + num2));
if (!invalid) {
QVERIFY(verify(bignum1 - bignum2, scriptnum1 - scriptnum2));
QVERIFY(verify(bignum1 - bignum2, scriptnum1 - num2));
}
invalid = ((num1 > 0 && num2 < std::numeric_limits<int64_t>::min() + num1) ||
(num1 < 0 && num2 > std::numeric_limits<int64_t>::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<int64_t>::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<qint64>("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<unsigned char> 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());
}