mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-27 19:39:02 +02:00
policy: introduce a helper to detect whether a transaction spends Segwit outputs
We will use this helper in later commits to detect witness stripping without having to execute every input Script three times in a row.
This commit is contained in:
@@ -337,6 +337,42 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& prevouts)
|
||||
{
|
||||
if (tx.IsCoinBase()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int version;
|
||||
std::vector<uint8_t> program;
|
||||
for (const auto& txin: tx.vin) {
|
||||
const auto& prev_spk{prevouts.AccessCoin(txin.prevout).out.scriptPubKey};
|
||||
|
||||
// Note this includes not-yet-defined witness programs.
|
||||
if (prev_spk.IsWitnessProgram(version, program) && !prev_spk.IsPayToAnchor(version, program)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For P2SH extract the redeem script and check if it spends a non-Taproot witness program. Note
|
||||
// this is fine to call EvalScript (as done in AreInputsStandard/IsWitnessStandard) because this
|
||||
// function is only ever called after IsStandardTx, which checks the scriptsig is pushonly.
|
||||
if (prev_spk.IsPayToScriptHash()) {
|
||||
// If EvalScript fails or results in an empty stack, the transaction is invalid by consensus.
|
||||
std::vector <std::vector<uint8_t>> stack;
|
||||
if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker{}, SigVersion::BASE)
|
||||
|| stack.empty()) {
|
||||
continue;
|
||||
}
|
||||
const CScript redeem_script{stack.back().begin(), stack.back().end()};
|
||||
if (redeem_script.IsWitnessProgram(version, program)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop)
|
||||
{
|
||||
return (std::max(nWeight, nSigOpCost * bytes_per_sigop) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;
|
||||
|
@@ -166,6 +166,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
||||
* Also enforce a maximum stack item size limit and no annexes for tapscript spends.
|
||||
*/
|
||||
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
|
||||
/**
|
||||
* Check whether this transaction spends any witness program but P2A, including not-yet-defined ones.
|
||||
* May return `false` early for consensus-invalid transactions.
|
||||
*/
|
||||
bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& prevouts);
|
||||
|
||||
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
|
||||
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);
|
||||
|
@@ -1149,4 +1149,159 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
|
||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
|
||||
}
|
||||
|
||||
/** Sanity check the return value of SpendsNonAnchorWitnessProg for various output types. */
|
||||
BOOST_AUTO_TEST_CASE(spends_witness_prog)
|
||||
{
|
||||
CCoinsView coins_dummy;
|
||||
CCoinsViewCache coins(&coins_dummy);
|
||||
CKey key;
|
||||
key.MakeNewKey(true);
|
||||
const CPubKey pubkey{key.GetPubKey()};
|
||||
CMutableTransaction tx_create{}, tx_spend{};
|
||||
tx_create.vout.emplace_back(0, CScript{});
|
||||
tx_spend.vin.emplace_back(Txid{}, 0);
|
||||
std::vector<std::vector<uint8_t>> sol_dummy;
|
||||
|
||||
// CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash,
|
||||
// WitnessV1Taproot, PayToAnchor, WitnessUnknown.
|
||||
static_assert(std::variant_size_v<CTxDestination> == 9);
|
||||
|
||||
// Go through all defined output types and sanity check SpendsNonAnchorWitnessProg.
|
||||
|
||||
// P2PK
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(PubKeyDestination{pubkey});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::PUBKEY);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2PKH
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(PKHash{pubkey});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::PUBKEYHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2SH
|
||||
auto redeem_script{CScript{} << OP_1 << OP_CHECKSIG};
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash{redeem_script});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << OP_0 << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
|
||||
// native P2WSH
|
||||
const auto witness_script{CScript{} << OP_12 << OP_HASH160 << OP_DUP << OP_EQUAL};
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash{witness_script});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_V0_SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2SH-wrapped P2WSH
|
||||
redeem_script = tx_create.vout[0].scriptPubKey;
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// native P2WPKH
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessV0KeyHash{pubkey});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_V0_KEYHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2SH-wrapped P2WPKH
|
||||
redeem_script = tx_create.vout[0].scriptPubKey;
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2TR
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey{pubkey}});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_V1_TAPROOT);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2SH-wrapped P2TR (undefined, non-standard)
|
||||
redeem_script = tx_create.vout[0].scriptPubKey;
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2A
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(PayToAnchor{});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::ANCHOR);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2SH-wrapped P2A (undefined, non-standard)
|
||||
redeem_script = tx_create.vout[0].scriptPubKey;
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
|
||||
// Undefined version 1 witness program
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessUnknown{1, {0x42, 0x42}});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_UNKNOWN);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// P2SH-wrapped undefined version 1 witness program
|
||||
redeem_script = tx_create.vout[0].scriptPubKey;
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// Various undefined version >1 32-byte witness programs.
|
||||
const auto program{ToByteVector(XOnlyPubKey{pubkey})};
|
||||
for (int i{2}; i <= 16; ++i) {
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessUnknown{i, program});
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_UNKNOWN);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
|
||||
// It's also detected within P2SH.
|
||||
redeem_script = tx_create.vout[0].scriptPubKey;
|
||||
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
|
||||
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
|
||||
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
|
||||
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
|
||||
AddCoins(coins, CTransaction{tx_create}, 0, false);
|
||||
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
tx_spend.vin[0].scriptSig.clear();
|
||||
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Reference in New Issue
Block a user