Skip to content

Commit 9a7339f

Browse files
committed
Make bad-witness-nonstandard rejection more specific, and support overriding some
1 parent d926fd0 commit 9a7339f

File tree

7 files changed

+43
-20
lines changed

7 files changed

+43
-20
lines changed

src/policy/policy.cpp

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs,
244244
return true;
245245
}
246246

247-
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
247+
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
248248
{
249249
if (tx.IsCoinBase())
250250
return true; // Coinbases are skipped
@@ -273,9 +273,15 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
273273
// into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway.
274274
// If the check fails at this stage, we know that this txid must be a bad one.
275275
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE))
276+
{
277+
out_reason = reason_prefix + "scriptsig-failure";
276278
return false;
279+
}
277280
if (stack.empty())
281+
{
282+
out_reason = reason_prefix + "scriptcheck-missing";
278283
return false;
284+
}
279285
prevScript = CScript(stack.back().begin(), stack.back().end());
280286
p2sh = true;
281287
}
@@ -285,18 +291,21 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
285291

286292
// Non-witness program must not be associated with any witness
287293
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
294+
{
295+
out_reason = reason_prefix + "nonwitness-input";
288296
return false;
297+
}
289298

290299
// Check P2WSH standard limits
291300
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
292301
if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE)
293-
return false;
302+
MaybeReject("script-size");
294303
size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1;
295304
if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS)
296-
return false;
305+
MaybeReject("stackitem-count");
297306
for (unsigned int j = 0; j < sizeWitnessStack; j++) {
298307
if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE)
299-
return false;
308+
MaybeReject("stackitem-size");
300309
}
301310
}
302311

@@ -308,24 +317,36 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
308317
Span stack{tx.vin[i].scriptWitness.stack};
309318
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
310319
// Annexes are nonstandard as long as no semantics are defined for them.
311-
return false;
320+
MaybeReject("taproot-annex");
321+
// If reject reason is ignored, continue as if the annex wasn't there.
322+
SpanPopBack(stack);
312323
}
313324
if (stack.size() >= 2) {
314325
// Script path spend (2 or more stack elements after removing optional annex)
315326
const auto& control_block = SpanPopBack(stack);
316327
SpanPopBack(stack); // Ignore script
317-
if (control_block.empty()) return false; // Empty control block is invalid
328+
if (control_block.empty()) {
329+
// Empty control block is invalid
330+
out_reason = reason_prefix + "taproot-control-missing";
331+
return false;
332+
}
318333
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
319334
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
335+
if (!ignore_rejects.count(reason_prefix + "taproot-stackitem-size")) {
320336
for (const auto& item : stack) {
321-
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) return false;
337+
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) {
338+
out_reason = reason_prefix + "taproot-stackitem-size";
339+
return false;
340+
}
341+
}
322342
}
323343
}
324344
} else if (stack.size() == 1) {
325345
// Key path spend (1 stack element after removing optional annex)
326346
// (no policy rules apply)
327347
} else {
328348
// 0 stack elements; this is already invalid by consensus rules
349+
out_reason = reason_prefix + "taproot-witness-missing";
329350
return false;
330351
}
331352
}

src/policy/policy.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ inline bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& map
161161
*
162162
* Also enforce a maximum stack item size limit and no annexes for tapscript spends.
163163
*/
164-
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
164+
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
165165

166166
/** Compute the virtual transaction size (weight reinterpreted as bytes). */
167167
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);

src/test/fuzz/coins_view.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
284284
(void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
285285
},
286286
[&] {
287-
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
287+
std::string reason;
288+
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache, "bad-witness-", reason);
288289
});
289290
}
290291
}

src/test/fuzz/transaction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
8888
CCoinsView coins_view;
8989
const CCoinsViewCache coins_view_cache(&coins_view);
9090
(void)AreInputsStandard(tx, coins_view_cache);
91-
(void)IsWitnessStandard(tx, coins_view_cache);
91+
std::string reject_reason;
92+
(void)IsWitnessStandard(tx, coins_view_cache, "fuzz", reject_reason);
9293

9394
if (tx.GetTotalSize() < 250'000) { // Avoid high memory usage (with msan) due to json encoding
9495
{

src/test/txpackage_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
426426
auto it_parent = result_quit_early.m_tx_results.find(tx_parent_invalid->GetWitnessHash());
427427
auto it_child = result_quit_early.m_tx_results.find(tx_child->GetWitnessHash());
428428
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_WITNESS_MUTATED);
429-
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonstandard");
429+
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonwitness-input");
430430
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS);
431431
BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent");
432432
}

src/validation.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,8 +924,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
924924
}
925925

926926
// Check for non-standard witnesses.
927-
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) {
928-
MaybeReject(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
927+
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view, "bad-witness-", reason, ignore_rejects)) {
928+
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, reason);
929929
}
930930

931931
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);

test/functional/p2p_segwit.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,7 @@ def test_non_standard_witness_blinding(self):
17501750
tx2.rehash()
17511751
# This will be rejected due to a policy check:
17521752
# No witness is allowed, since it is not a witness program but a p2sh program
1753-
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, True, False, 'bad-witness-nonstandard')
1753+
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, True, False, 'bad-witness-nonwitness-input')
17541754

17551755
# If we send without witness, it should be accepted.
17561756
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, False, True)
@@ -1819,13 +1819,13 @@ def test_non_standard_witness(self):
18191819
# Testing native P2WSH
18201820
# Witness stack size, excluding witnessScript, over 100 is non-standard
18211821
p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]]
1822-
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[0], True, False, 'bad-witness-nonstandard')
1822+
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[0], True, False, 'bad-witness-stackitem-count')
18231823
# Non-standard nodes should accept
18241824
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[0], True, True)
18251825

18261826
# Stack element size over 80 bytes is non-standard
18271827
p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]]
1828-
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[1], True, False, 'bad-witness-nonstandard')
1828+
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[1], True, False, 'bad-witness-stackitem-size')
18291829
# Non-standard nodes should accept
18301830
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[1], True, True)
18311831
# Standard nodes should accept if element size is not over 80 bytes
@@ -1839,24 +1839,24 @@ def test_non_standard_witness(self):
18391839

18401840
# witnessScript size at 3601 bytes is non-standard
18411841
p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]]
1842-
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[3], True, False, 'bad-witness-nonstandard')
1842+
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[3], True, False, 'bad-witness-script-size')
18431843
# Non-standard nodes should accept
18441844
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[3], True, True)
18451845

18461846
# Repeating the same tests with P2SH-P2WSH
18471847
p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]]
1848-
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[0], True, False, 'bad-witness-nonstandard')
1848+
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[0], True, False, 'bad-witness-stackitem-count')
18491849
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[0], True, True)
18501850
p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]]
1851-
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, False, 'bad-witness-nonstandard')
1851+
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, False, 'bad-witness-stackitem-size')
18521852
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[1], True, True)
18531853
p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]]
18541854
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, True)
18551855
p2sh_txs[2].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, scripts[2]]
18561856
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[2], True, True)
18571857
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[2], True, True)
18581858
p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]]
1859-
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[3], True, False, 'bad-witness-nonstandard')
1859+
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[3], True, False, 'bad-witness-script-size')
18601860
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[3], True, True)
18611861

18621862
self.generate(self.nodes[0], 1) # Mine and clean up the mempool of non-standard node

0 commit comments

Comments
 (0)