mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-15 16:38:23 +01: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;
|
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)
|
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;
|
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.
|
* Also enforce a maximum stack item size limit and no annexes for tapscript spends.
|
||||||
*/
|
*/
|
||||||
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
|
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). */
|
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
|
||||||
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);
|
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));
|
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()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|||||||
Reference in New Issue
Block a user