Skip to content

Commit 946db0f

Browse files
committed
Merge #1512: Detect witness stripping without re-running Script checks
0cb5c66 Merge bitcoin/bitcoin#33105: validation: detect witness stripping without re-running Script checks (merge-script) Pull request description: Backport of bitcoin/bitcoin#33105 ACKs for top commit: delta1: ACK 0cb5c66 Tree-SHA512: 6e982a18450edfe8c8f6ce0087af4d084f04dd39c0113144ab367802a2aee7e991899941c95e4f01e9d67248e7b87810b23d6fcb687c0582f6b69b72583ae829
2 parents 98bd718 + 0cb5c66 commit 946db0f

File tree

5 files changed

+194
-7
lines changed

5 files changed

+194
-7
lines changed

src/policy/policy.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,42 @@ bool IsIssuanceInMoneyRange(const CTransaction& tx)
326326
return true;
327327
}
328328

329+
bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& prevouts)
330+
{
331+
if (tx.IsCoinBase()) {
332+
return false;
333+
}
334+
335+
int version;
336+
std::vector<uint8_t> program;
337+
for (const auto& txin: tx.vin) {
338+
const auto& prev_spk{prevouts.AccessCoin(txin.prevout).out.scriptPubKey};
339+
340+
// Note this includes not-yet-defined witness programs.
341+
if (prev_spk.IsWitnessProgram(version, program)) {
342+
return true;
343+
}
344+
345+
// For P2SH extract the redeem script and check if it spends a non-Taproot witness program. Note
346+
// this is fine to call EvalScript (as done in AreInputsStandard/IsWitnessStandard) because this
347+
// function is only ever called after IsStandardTx, which checks the scriptsig is pushonly.
348+
if (prev_spk.IsPayToScriptHash()) {
349+
// If EvalScript fails or results in an empty stack, the transaction is invalid by consensus.
350+
std::vector <std::vector<uint8_t>> stack;
351+
if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker{}, SigVersion::BASE)
352+
|| stack.empty()) {
353+
continue;
354+
}
355+
const CScript redeem_script{stack.back().begin(), stack.back().end()};
356+
if (redeem_script.IsWitnessProgram(version, program)) {
357+
return true;
358+
}
359+
}
360+
}
361+
362+
return false;
363+
}
364+
329365
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop)
330366
{
331367
return (std::max(nWeight, nSigOpCost * bytes_per_sigop) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR;

src/policy/policy.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
128128
* Also enforce a maximum stack item size limit and no annexes for tapscript spends.
129129
*/
130130
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
131+
/**
132+
* Check whether this transaction spends any witness program but P2A, including not-yet-defined ones.
133+
* May return `false` early for consensus-invalid transactions.
134+
*/
135+
bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& prevouts);
131136

132137
/* ELEMENTS
133138
* Check if unblinded issuance/reissuance is in MoneyRange

src/test/transaction_tests.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,4 +979,142 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
979979
}
980980
}
981981

982+
/** Sanity check the return value of SpendsNonAnchorWitnessProg for various output types. */
983+
/**/
984+
BOOST_AUTO_TEST_CASE(spends_witness_prog)
985+
{
986+
CCoinsView coins_dummy;
987+
CCoinsViewCache coins(&coins_dummy);
988+
CKey key;
989+
key.MakeNewKey(true);
990+
const CPubKey pubkey{key.GetPubKey()};
991+
CMutableTransaction tx_create{}, tx_spend{};
992+
tx_create.vout.emplace_back(CConfidentialAsset(CAsset{}), CConfidentialValue(CAmount(0)), CScript{});
993+
uint256 txid{};
994+
tx_spend.vin.emplace_back(txid, 0);
995+
std::vector<std::vector<uint8_t>> sol_dummy;
996+
997+
// CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash,
998+
// WitnessV1Taproot, PayToAnchor, WitnessUnknown.
999+
static_assert(std::variant_size_v<CTxDestination> == 8);
1000+
1001+
// Go through all defined output types and sanity check SpendsNonAnchorWitnessProg.
1002+
1003+
// P2PKH
1004+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(PKHash{pubkey});
1005+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::PUBKEYHASH);
1006+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1007+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1008+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1009+
1010+
// P2SH
1011+
auto redeem_script{CScript{} << OP_1 << OP_CHECKSIG};
1012+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash{redeem_script});
1013+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
1014+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1015+
tx_spend.vin[0].scriptSig = CScript{} << OP_0 << ToByteVector(redeem_script);
1016+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1017+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1018+
tx_spend.vin[0].scriptSig.clear();
1019+
1020+
// native P2WSH
1021+
const auto witness_script{CScript{} << OP_12 << OP_HASH160 << OP_DUP << OP_EQUAL};
1022+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash{witness_script});
1023+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_V0_SCRIPTHASH);
1024+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1025+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1026+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1027+
1028+
// P2SH-wrapped P2WSH
1029+
redeem_script = tx_create.vout[0].scriptPubKey;
1030+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
1031+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
1032+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1033+
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
1034+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1035+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1036+
tx_spend.vin[0].scriptSig.clear();
1037+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1038+
1039+
// native P2WPKH
1040+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessV0KeyHash{pubkey});
1041+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_V0_KEYHASH);
1042+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1043+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1044+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1045+
1046+
// P2SH-wrapped P2WPKH
1047+
redeem_script = tx_create.vout[0].scriptPubKey;
1048+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
1049+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
1050+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1051+
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
1052+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1053+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1054+
tx_spend.vin[0].scriptSig.clear();
1055+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1056+
1057+
// P2TR
1058+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey{pubkey}});
1059+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_V1_TAPROOT);
1060+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1061+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1062+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1063+
1064+
// P2SH-wrapped P2TR (undefined, non-standard)
1065+
redeem_script = tx_create.vout[0].scriptPubKey;
1066+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
1067+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
1068+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1069+
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
1070+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1071+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1072+
tx_spend.vin[0].scriptSig.clear();
1073+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1074+
1075+
// Undefined version 1 witness program
1076+
WitnessUnknown wu = {1, 2, {}, CPubKey()};
1077+
auto program1 = std::vector<unsigned char>{0x42, 0x42};
1078+
std::copy(program1.begin(), program1.end(), wu.program);
1079+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(wu);
1080+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_UNKNOWN);
1081+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1082+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1083+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1084+
1085+
// P2SH-wrapped undefined version 1 witness program
1086+
redeem_script = tx_create.vout[0].scriptPubKey;
1087+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
1088+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
1089+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1090+
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
1091+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1092+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1093+
tx_spend.vin[0].scriptSig.clear();
1094+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1095+
1096+
// Various undefined version >1 32-byte witness programs.
1097+
const auto program{ToByteVector(XOnlyPubKey{pubkey})};
1098+
for (int i{2}; i <= 16; ++i) {
1099+
wu = WitnessUnknown{static_cast<unsigned int>(i), static_cast<unsigned int>(program.size()), {}, CPubKey()};
1100+
std::copy(program.begin(), program.end(), wu.program);
1101+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(wu);
1102+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::WITNESS_UNKNOWN);
1103+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1104+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1105+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1106+
1107+
// It's also detected within P2SH.
1108+
redeem_script = tx_create.vout[0].scriptPubKey;
1109+
tx_create.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(redeem_script));
1110+
BOOST_CHECK_EQUAL(Solver(tx_create.vout[0].scriptPubKey, sol_dummy), TxoutType::SCRIPTHASH);
1111+
tx_spend.vin[0].prevout.hash = tx_create.GetHash();
1112+
tx_spend.vin[0].scriptSig = CScript{} << ToByteVector(redeem_script);
1113+
AddCoins(coins, CTransaction{tx_create}, 0, false);
1114+
BOOST_CHECK(::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1115+
tx_spend.vin[0].scriptSig.clear();
1116+
BOOST_CHECK(!::SpendsNonAnchorWitnessProg(CTransaction{tx_spend}, coins));
1117+
}
1118+
}
1119+
9821120
BOOST_AUTO_TEST_SUITE_END()

src/validation.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,13 +1080,8 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)
10801080
// Check input scripts and signatures.
10811081
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
10821082
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata)) {
1083-
// SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
1084-
// need to turn both off, and compare against just turning off CLEANSTACK
1085-
// to see if the failure is specifically due to witness validation.
1086-
TxValidationState state_dummy; // Want reported failures to be from first CheckInputScripts
1087-
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata) &&
1088-
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata)) {
1089-
// Only the witness is missing, so the transaction itself may be fine.
1083+
// Detect a failure due to a missing witness so that p2p code can handle rejection caching appropriately.
1084+
if (!tx.HasWitness() && SpendsNonAnchorWitnessProg(tx, m_view)) {
10901085
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
10911086
state.GetRejectReason(), state.GetDebugMessage());
10921087
}

test/functional/p2p_segwit.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,12 @@ def test_p2sh_witness(self):
721721
expected_msgs=(spend_tx.hash, 'was not accepted: non-mandatory-script-verify-flag (Witness program was passed an empty witness)')):
722722
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
723723

724+
# The transaction was detected as witness stripped above and not added to the reject
725+
# filter. Trying again will check it again and result in the same error.
726+
with self.nodes[0].assert_debug_log(
727+
expected_msgs=[spend_tx.getwtxid() , 'was not accepted: non-mandatory-script-verify-flag (Witness program was passed an empty witness)']):
728+
test_transaction_acceptance(self.nodes[0], self.test_node, spend_tx, with_witness=False, accepted=False)
729+
724730
# Try to put the witness script in the scriptSig, should also fail.
725731
spend_tx.vin[0].scriptSig = CScript([p2wsh_pubkey, b'a'])
726732
spend_tx.rehash()
@@ -1317,6 +1323,13 @@ def test_tx_relay_after_segwit_activation(self):
13171323
test_transaction_acceptance(self.nodes[0], self.test_node, tx2, with_witness=True, accepted=True)
13181324
test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=True, accepted=False)
13191325

1326+
# Now do the opposite: strip the witness entirely. This will be detected as witness stripping and
1327+
# the (w)txid won't be added to the reject filter: we can try again and get the same error.
1328+
tx3.wit.vtxinwit[0].scriptWitness.stack = []
1329+
reason = "was not accepted: non-mandatory-script-verify-flag (Witness program was passed an empty witness)"
1330+
test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=False, accepted=False, reason=reason)
1331+
test_transaction_acceptance(self.nodes[0], self.test_node, tx3, with_witness=False, accepted=False, reason=reason)
1332+
13201333
# Get rid of the extra witness, and verify acceptance.
13211334
tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_script]
13221335
# Also check that old_node gets a tx announcement, even though this is

0 commit comments

Comments
 (0)