Files
thehub/testing/bitcoin-protocol/script_P2SH_tests.cpp
tomFlowee cd317bc3c0 Cleanup p2sh32
The activation is past and there is no point in having a flag passed
through a dozen methods in order to detect when to enable it.
Because it is always enabled.
2026-05-07 16:15:26 +02:00

345 lines
13 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2012-2015 The Bitcoin Core developers
* Copyright (C) 2018 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 <common/MutableTransactionSignatureChecker.h>
#include "script_P2SH_tests.h"
#include "keystore.h"
#include "main.h"
#include "policy/policy.h"
#include <hash.h>
#include <script/interpreter.h>
#include <utxo/UnspentOutputDatabase.h>
// Helpers:
static std::vector<unsigned char>
Serialize(const CScript& s)
{
std::vector<unsigned char> sSerialized(s.begin(), s.end());
return sSerialized;
}
static bool
VerifyWithFlags(const CScript& scriptSig, const CScript& scriptPubKey, uint32_t flags, ScriptError& err)
{
// Create dummy to/from transactions:
CMutableTransaction txFrom;
txFrom.vout.resize(1);
txFrom.vout[0].scriptPubKey = scriptPubKey;
CMutableTransaction txTo;
txTo.vin.resize(1);
txTo.vout.resize(1);
txTo.vin[0].prevout.n = 0;
txTo.vin[0].prevout.hash = txFrom.GetHash();
txTo.vin[0].scriptSig = scriptSig;
txTo.vout[0].nValue = 1;
Script::State state(flags);
bool ok = Script::verify(scriptSig, scriptPubKey, MutableTransactionSignatureChecker(&txTo, 0, txFrom.vout[0].nValue), state);
err = state.error;
return ok;
}
static bool
Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, ScriptError& err)
{
return VerifyWithFlags(scriptSig, scriptPubKey, fStrict ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, err);
}
void TestPaymentToScriptHash::norecurse()
{
ScriptError err;
// Make sure only the outer pay-to-script-hash does the
// extra-validation thing:
CScript invalidAsScript;
invalidAsScript << INVALIDOPCODE << INVALIDOPCODE;
CScript p2sh = GetScriptForDestination(CScriptID(invalidAsScript));
CScript scriptSig;
scriptSig << Serialize(invalidAsScript);
// Should not verify, because it will try to execute OP_INVALIDOPCODE
QVERIFY(!Verify(scriptSig, p2sh, true, err));
QCOMPARE(err, SCRIPT_ERR_BAD_OPCODE);
// Try to recur, and verification should succeed because
// the inner HASH160 <> EQUAL should only check the hash:
CScript p2sh2 = GetScriptForDestination(CScriptID(p2sh));
CScript scriptSig2;
scriptSig2 << Serialize(invalidAsScript) << Serialize(p2sh);
QVERIFY(Verify(scriptSig2, p2sh2, true, err));
QCOMPARE(err, SCRIPT_ERR_OK);
}
void TestPaymentToScriptHash::set()
{
LOCK(cs_main);
// Test the CScript::Set* methods
CBasicKeyStore keystore;
PrivateKey key[4];
std::vector<PublicKey> keys;
for (int i = 0; i < 4; i++)
{
key[i].makeNewKey(true);
keystore.AddKey(key[i]);
keys.push_back(key[i].getPubKey());
}
CScript inner[4];
inner[0] = GetScriptForDestination(key[0].getPubKey().getKeyId());
inner[1] = GetScriptForMultisig(2, std::vector<PublicKey>(keys.begin(), keys.begin()+2));
inner[2] = GetScriptForMultisig(1, std::vector<PublicKey>(keys.begin(), keys.begin()+2));
inner[3] = GetScriptForMultisig(2, std::vector<PublicKey>(keys.begin(), keys.begin()+3));
CScript outer[4];
for (int i = 0; i < 4; i++)
{
outer[i] = GetScriptForDestination(CScriptID(inner[i]));
keystore.AddCScript(inner[i]);
}
CMutableTransaction txFrom; // Funding transaction:
std::string reason;
txFrom.vout.resize(4);
for (int i = 0; i < 4; i++)
{
txFrom.vout[i].scriptPubKey = outer[i];
txFrom.vout[i].nValue = CENT;
}
QVERIFY(IsStandardTx(txFrom, reason));
CMutableTransaction txTo[4]; // Spending transactions
for (int i = 0; i < 4; i++)
{
txTo[i].vin.resize(1);
txTo[i].vout.resize(1);
txTo[i].vin[0].prevout.n = i;
txTo[i].vin[0].prevout.hash = txFrom.GetHash();
txTo[i].vout[0].nValue = 1*CENT;
txTo[i].vout[0].scriptPubKey = inner[i];
}
for (int i = 0; i < 4; i++)
{
QVERIFY(IsStandardTx(txTo[i], reason));
}
}
void TestPaymentToScriptHash::is()
{
// Test CScript::IsPayToScriptHash()
uint160 dummy;
CScript p2sh;
p2sh << OP_HASH160 << ToByteVector(dummy) << OP_EQUAL;
QVERIFY(p2sh.IsPayToScriptHash());
// Not considered pay-to-script-hash if using one of the OP_PUSHDATA opcodes:
static const unsigned char direct[] = { OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL };
QVERIFY(CScript(direct, direct+sizeof(direct)).IsPayToScriptHash());
static const unsigned char pushdata1[] = { OP_HASH160, OP_PUSHDATA1, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL };
QVERIFY(!CScript(pushdata1, pushdata1+sizeof(pushdata1)).IsPayToScriptHash());
static const unsigned char pushdata2[] = { OP_HASH160, OP_PUSHDATA2, 20,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL };
QVERIFY(!CScript(pushdata2, pushdata2+sizeof(pushdata2)).IsPayToScriptHash());
static const unsigned char pushdata4[] = { OP_HASH160, OP_PUSHDATA4, 20,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL };
QVERIFY(!CScript(pushdata4, pushdata4+sizeof(pushdata4)).IsPayToScriptHash());
CScript not_p2sh;
QVERIFY(!not_p2sh.IsPayToScriptHash());
not_p2sh.clear(); not_p2sh << OP_HASH160 << ToByteVector(dummy) << ToByteVector(dummy) << OP_EQUAL;
QVERIFY(!not_p2sh.IsPayToScriptHash());
not_p2sh.clear(); not_p2sh << OP_NOP << ToByteVector(dummy) << OP_EQUAL;
QVERIFY(!not_p2sh.IsPayToScriptHash());
not_p2sh.clear(); not_p2sh << OP_HASH160 << ToByteVector(dummy) << OP_CHECKSIG;
QVERIFY(!not_p2sh.IsPayToScriptHash());
}
void TestPaymentToScriptHash::p2sh32()
{
const CScript trueRedeemScript = CScript() << OP_TRUE;
const CScript falseRedeemScript = CScript() << OP_FALSE;
const uint256 trueHash = Hash(trueRedeemScript.begin(), trueRedeemScript.end());
const uint256 falseHash = Hash(falseRedeemScript.begin(), falseRedeemScript.end());
CScript trueP2sh32;
trueP2sh32 << OP_HASH256 << ToByteVector(trueHash) << OP_EQUAL;
CScript falseP2sh32;
falseP2sh32 << OP_HASH256 << ToByteVector(falseHash) << OP_EQUAL;
QVERIFY(trueP2sh32.IsPayToScriptHash());
std::vector<unsigned char> zeros32(32, 0);
std::vector<unsigned char> direct32{OP_HASH256, 32};
direct32.insert(direct32.end(), zeros32.begin(), zeros32.end());
direct32.push_back(OP_EQUAL);
QVERIFY(CScript(direct32.begin(), direct32.end()).IsPayToScriptHash());
std::vector<unsigned char> pushdata1_32{OP_HASH256, OP_PUSHDATA1, 32};
pushdata1_32.insert(pushdata1_32.end(), zeros32.begin(), zeros32.end());
pushdata1_32.push_back(OP_EQUAL);
QVERIFY(!CScript(pushdata1_32.begin(), pushdata1_32.end()).IsPayToScriptHash());
std::vector<unsigned char> pushdata2_32{OP_HASH256, OP_PUSHDATA2, 32, 0};
pushdata2_32.insert(pushdata2_32.end(), zeros32.begin(), zeros32.end());
pushdata2_32.push_back(OP_EQUAL);
QVERIFY(!CScript(pushdata2_32.begin(), pushdata2_32.end()).IsPayToScriptHash());
std::vector<unsigned char> pushdata4_32{OP_HASH256, OP_PUSHDATA4, 32, 0, 0, 0};
pushdata4_32.insert(pushdata4_32.end(), zeros32.begin(), zeros32.end());
pushdata4_32.push_back(OP_EQUAL);
QVERIFY(!CScript(pushdata4_32.begin(), pushdata4_32.end()).IsPayToScriptHash());
std::vector<unsigned char> hashBytes;
QVERIFY(trueP2sh32.IsPayToScriptHash(&hashBytes));
QCOMPARE(hashBytes.size(), size_t(32));
QVERIFY(std::equal(hashBytes.begin(), hashBytes.end(), trueHash.begin()));
Script::TxnOutType typeRet = Script::TX_NONSTANDARD;
std::vector<std::vector<unsigned char> > solutions;
QVERIFY(Script::solver(trueP2sh32, typeRet, solutions));
QCOMPARE(typeRet, Script::TX_SCRIPTHASH);
QCOMPARE(solutions.size(), size_t(1));
QCOMPARE(solutions.front().size(), size_t(32));
int dataUsed = 0;
QVERIFY(IsStandard(trueP2sh32, typeRet, dataUsed));
CScript falseScriptSig;
falseScriptSig << Serialize(falseRedeemScript);
QVERIFY(Policy::isInputStandard(falseP2sh32, falseScriptSig));
ScriptError err;
QVERIFY(VerifyWithFlags(falseScriptSig, falseP2sh32, SCRIPT_VERIFY_P2SH, err));
QCOMPARE(err, SCRIPT_ERR_OK);
QVERIFY(!VerifyWithFlags(falseScriptSig, falseP2sh32, SCRIPT_VERIFY_P2SH | SCRIPT_ENABLE_P2SH_32, err));
QCOMPARE(err, SCRIPT_ERR_EVAL_FALSE);
}
void TestPaymentToScriptHash::switchover()
{
// Test switch over code
CScript notValid;
ScriptError err;
notValid << OP_11 << OP_12 << OP_EQUALVERIFY;
CScript scriptSig;
scriptSig << Serialize(notValid);
CScript fund = GetScriptForDestination(CScriptID(notValid));
// Validation should succeed under old rules (hash is correct):
QVERIFY(Verify(scriptSig, fund, false, err));
QCOMPARE(err, SCRIPT_ERR_OK);
// Fail under new:
QVERIFY(!Verify(scriptSig, fund, true, err));
QCOMPARE(err, SCRIPT_ERR_EQUALVERIFY);
}
void TestPaymentToScriptHash::AreInputsStandard()
{
LOCK(cs_main);
CBasicKeyStore keystore;
PrivateKey key[6];
std::vector<PublicKey> keys;
for (int i = 0; i < 6; i++)
{
key[i].makeNewKey(true);
keystore.AddKey(key[i]);
}
for (int i = 0; i < 3; i++)
keys.push_back(key[i].getPubKey());
CMutableTransaction txFrom;
txFrom.vout.resize(7);
// First three are standard:
CScript pay1 = GetScriptForDestination(key[0].getPubKey().getKeyId());
keystore.AddCScript(pay1);
CScript pay1of3 = GetScriptForMultisig(1, keys);
txFrom.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(pay1)); // P2SH (OP_CHECKSIG)
txFrom.vout[0].nValue = 1000;
txFrom.vout[1].scriptPubKey = pay1; // ordinary OP_CHECKSIG
txFrom.vout[1].nValue = 2000;
txFrom.vout[2].scriptPubKey = pay1of3; // ordinary OP_CHECKMULTISIG
txFrom.vout[2].nValue = 3000;
// vout[3] is complicated 1-of-3 AND 2-of-3
// ... that is OK if wrapped in P2SH:
CScript oneAndTwo;
oneAndTwo << OP_1 << ToByteVector(key[0].getPubKey()) << ToByteVector(key[1].getPubKey()) << ToByteVector(key[2].getPubKey());
oneAndTwo << OP_3 << OP_CHECKMULTISIGVERIFY;
oneAndTwo << OP_2 << ToByteVector(key[3].getPubKey()) << ToByteVector(key[4].getPubKey()) << ToByteVector(key[5].getPubKey());
oneAndTwo << OP_3 << OP_CHECKMULTISIG;
keystore.AddCScript(oneAndTwo);
txFrom.vout[3].scriptPubKey = GetScriptForDestination(CScriptID(oneAndTwo));
txFrom.vout[3].nValue = 4000;
// vout[4] is max sigchecks: Non-standard because its too long
CScript fifteenSigops; fifteenSigops << OP_1;
for (unsigned i = 0; i < Policy::MAX_SIGCHEKCS_PER_TX; i++)
fifteenSigops << ToByteVector(key[i%3].getPubKey());
fifteenSigops << OP_15 << OP_CHECKMULTISIG;
keystore.AddCScript(fifteenSigops);
txFrom.vout[4].scriptPubKey = GetScriptForDestination(CScriptID(fifteenSigops));
txFrom.vout[4].nValue = 5000;
// vout[5/6] are non-standard because they exceed MAX_P2SH_SIGOPS
CScript sixteenSigops; sixteenSigops << OP_16 << OP_CHECKMULTISIG;
keystore.AddCScript(sixteenSigops);
txFrom.vout[5].scriptPubKey = GetScriptForDestination(CScriptID(fifteenSigops));
txFrom.vout[5].nValue = 5000;
CScript twentySigops; twentySigops << OP_CHECKMULTISIG;
keystore.AddCScript(twentySigops);
txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops));
txFrom.vout[6].nValue = 6000;
CMutableTransaction txTo;
txTo.vout.resize(1);
txTo.vout[0].scriptPubKey = GetScriptForDestination(key[1].getPubKey().getKeyId());
txTo.vin.resize(5);
for (int i = 0; i < 5; i++)
{
txTo.vin[i].prevout.n = i;
txTo.vin[i].prevout.hash = txFrom.GetHash();
}
// We're not testing validating signatures, so just create
// dummy signatures that DO include the correct P2SH scripts:
txTo.vin[0].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
txTo.vin[1].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
txTo.vin[2].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end());
for (size_t i = 0; i < txTo.vin.size(); ++i) {
const auto in = txTo.vin.at(i);
const auto prevOut = txFrom.vout.at(i);
const bool ok = Policy::isInputStandard(prevOut.scriptPubKey, in.scriptSig);
QCOMPARE(ok, i < 4);
}
}