Skip to content

Conversation

instagibbs
Copy link
Member

@instagibbs instagibbs commented Oct 26, 2022

Follow-on to to #26265

Implements the logic not effected by Great Consensus Cleanup 2025 effort: bitcoin/bips#1800

Copy link
Contributor

@hernanmarino hernanmarino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code review ACK, looks OK. I suggest to change the title of this PR though, it suggests that we are relaxing the restriction to ALLOW 64 byte transactions, and might confuse someone.

@instagibbs instagibbs changed the title Relax MIN_STANDARD_TX_NONWITNESS_SIZE to 64 non-witness bytes exactly Relax MIN_STANDARD_TX_NONWITNESS_SIZE to preclude 64 non-witness bytes only Oct 26, 2022
@DrahtBot
Copy link
Contributor

DrahtBot commented Oct 26, 2022

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/26398.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK TheCharlatan
Concept ACK hernanmarino, darosior
Approach NACK ajtowns
Stale ACK theStack, rajarshimaitra, glozow, maflcko

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32521 (policy: make pathological transactions packed with legacy sigops non-standard by darosior)
  • #32421 (test: refactor: overhaul (w)txid determination for CTransaction objects by theStack)
  • #31622 (psbt: add non-default sighash types to PSBTs and unify sighash type match checking by achow101)
  • #29675 (wallet: Be able to receive and spend inputs involving MuSig2 aggregate keys by achow101)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Copy link
Contributor

@rajarshimaitra rajarshimaitra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK a67341a

Code changes looks clean and inclining for this over #26265 . Even if it feels a odd check, if hiding the CVE isn't the purpose anymore, and 64 was the only culprit, no point punishing smaller use case..

@instagibbs
Copy link
Member Author

@rajarshimaitra fwiw, I agree, I like this test code materially more, which suggests it's the right direction, with less indirection happening

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@darosior
Copy link
Member

Concept ACK, prefer this over #26265

@instagibbs instagibbs force-pushed the relax_too_small_tx_equality branch 3 times, most recently from c361621 to 2ddd3a8 Compare October 28, 2022 14:17
@instagibbs instagibbs requested review from rajarshimaitra and hernanmarino and removed request for rajarshimaitra and hernanmarino October 31, 2022 17:41
Copy link
Contributor

@theStack theStack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept and code-review ACK 2ddd3a8

nit: could utilize more MiniWallet magic here, in order to avoid crafting the output of the parent tx and the input of the spending tx manually (works only as long as a segwit wallet mode is used, i.e. ADDRESS_OP_TRUE with segwitv1 outputs currently):

b/test/functional/mempool_accept.py
index c2d94ddad..09f8c3b23 100755
--- a/test/functional/mempool_accept.py
+++ b/test/functional/mempool_accept.py
@@ -36,7 +36,6 @@ from test_framework.script_util import (
     NONSTANDARD_OP_RETURN_SCRIPT,
     NONSTANDARD_TX_NONWITNESS_SIZE,
     script_to_p2sh_script,
-    script_to_p2wsh_script,
 )
 from test_framework.util import (
     assert_equal,
@@ -340,16 +339,13 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
             maxfeerate=0,
         )

-        # Prep for tiny-tx tests with wsh(OP_TRUE) output
-        seed_tx = self.wallet.send_to(from_node=node, scriptPubKey=script_to_p2wsh_script(CScript([OP_TRUE])), amount=COIN)
+        # Prep for tiny-tx tests with MiniWallet anyone-can-spend output
+        seed_utxo = self.wallet.send_self_transfer(from_node=node)["new_utxo"]
         self.generate(node, 1)

         self.log.info('A tiny transaction(in non-witness bytes) that is disallowed')
-        tx = CTransaction()
-        tx.vin.append(CTxIn(COutPoint(int(seed_tx[0], 16), seed_tx[1]), b"", SEQUENCE_FINAL))
-        tx.wit.vtxinwit = [CTxInWitness()]
-        tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
-        tx.vout.append(CTxOut(0, NONSTANDARD_OP_RETURN_SCRIPT))
+        tx = self.wallet.create_self_transfer(utxo_to_spend=seed_utxo)["tx"]
+        tx.vout[0].scriptPubKey = NONSTANDARD_OP_RETURN_SCRIPT
         # Note it's only non-witness size that matters!
         assert_equal(len(tx.serialize_without_witness()), NONSTANDARD_TX_NONWITNESS_SIZE)
         assert len(tx.serialize()) != NONSTANDARD_TX_NONWITNESS_SIZE
@@ -361,7 +357,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
         )

         self.log.info('Just-below size transaction(in non-witness bytes) that is allowed')
-        tx.vout[0] = CTxOut(COIN - 1000, CScript([OP_RETURN] + ([OP_0] * (INVALID_SPK_LEN - 2))))
+        tx.vout[0] = CTxOut(int(seed_utxo["value"] * COIN) - 1000, CScript([OP_RETURN] + ([OP_0] * (INVALID_SPK_LEN - 2))))
         assert_equal(len(tx.serialize_without_witness()), NONSTANDARD_TX_NONWITNESS_SIZE - 1)
         self.check_mempool_result(
             result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.00001000')}}],
@@ -370,7 +366,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
         )

         self.log.info('Just-above size transaction(in non-witness bytes) that is allowed')
-        tx.vout[0] = CTxOut(COIN - 1000, CScript([OP_RETURN] + ([OP_0] * (INVALID_SPK_LEN))))
+        tx.vout[0] = CTxOut(int(seed_utxo["value"] * COIN) - 1000, CScript([OP_RETURN] + ([OP_0] * (INVALID_SPK_LEN))))
         assert_equal(len(tx.serialize_without_witness()), NONSTANDARD_TX_NONWITNESS_SIZE + 1)
         self.check_mempool_result(
             result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.00001000')}}],

@maflcko
Copy link
Member

maflcko commented Nov 10, 2022

review ACK 2ddd3a8 🏊

Show signature

Signature:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

review ACK 2ddd3a839a08f09c11e9a24c63c2d3a1cba235b8 🏊
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
pUieVQv/RIMmNfx0FNL6Wk6XQNc/KquyZHRhr+3NSfMXIBwH02jyoc1lHbE6DJlB
unJMOHwggGGzJT8Ohzq83c3P2sU2sTP7KSGKywtge5S6+FBY9sZNvlSbY3Xg7NNW
tokt9W7VCUYDDvVZpKV+HKnOV+qL4ZHDBYcu33JUDXZnvgkD7nc36kN7Rf15unNT
j2tVQ21XV0E/Ok3y1W+C3I6S0tDao6JdpwcHT3+ei8fCp7tAAbzSFpGhutWd6Wpg
GRTwvsmr3ZM5UlDaYUHGh1oGsqQvHnWhKh6nNF/j1iKpTOEYiyRR40pPxifUF+wG
FHc+b9GuKYLnNXcaarmxOmtsHh1SFtjjEBotTw9mRYdSlkTFN6vboTppCtyqefhg
XxcmzBSnuKt0AA3l/7sZIBTWqxcf4N0kqOjzMIFiDaJJklqwn/vvUdxzYYXpoj6T
jP4EzBc5wFUgPLZCdLCQ56dtjZTeHjNOWktdAr+0uFcpvxvNOMQeLpDDNhhf/ukK
A+FIuasE
=cyTE
-----END PGP SIGNATURE-----

For historical context, see #26265 (review)

Was there a recent thread on the mailing list you could link to? I can't seem to find the one you created, as there are about 5 trillion messages about RBF.

About the practical impact of this change: My understanding is that 60-byte transactions are still not policy-allowed, because an empty scriptPubKey is nonstandard. The smallest transaction this would allow is a 61-byte transaction with one nulldata output. Also, the smallest transaction with a witness_unknown output is 64-byte and would thus remain invalid. (Obviously putting the smallest witness_unknown, or an INVALID_SPK_LEN nulldata output in a large enough transaction is already valid today)

@instagibbs
Copy link
Member Author

@MarcoFalke I agree with your statements

https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-October/020995.html

Also found this old one just now that I was a part of 2 years ago and don't recall: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-May/017883.html

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code review ACK 2ddd3a8

Could you please link the ML post in the OP and write a release note?

@instagibbs instagibbs force-pushed the relax_too_small_tx_equality branch from 2ddd3a8 to a75b0fd Compare November 10, 2022 19:29
@instagibbs
Copy link
Member Author

instagibbs commented Nov 10, 2022

@glozow pushed a brief release note along with edits from @glozow and @theStack

Restricting sizes below 64 vbytes interferes with specific
use-cases, and does not interfere with proposed consensus
changes such as the Great Consensus Cleanup of 2025. We
change software to protect against the case we care about:
64 bytes
@instagibbs instagibbs force-pushed the relax_too_small_tx_equality branch from a873be7 to 72ddf42 Compare March 27, 2025 19:42
@instagibbs instagibbs marked this pull request as ready for review March 27, 2025 19:51
@instagibbs
Copy link
Member Author

Re-opened due to the recent Great Consensus Cleanup work, though I still think there may be minor debate about the utility of the change.

TheBlueMatt pushed a commit to TheBlueMatt/rust-bitcoin that referenced this pull request Mar 28, 2025
Align with Bitcoin Core's policy by reducing the minimum non-witness
transaction size from 82 to 65 bytes. This change allows for more
minimal transaction cases (e.g., 1 input with 1 OP_RETURN output),
while still maintaining protection against CVE-2017-12842.

Matches bitcoin/bitcoin#26398
Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for rebasing! Needs release note.

@ajtowns
Copy link
Contributor

ajtowns commented Mar 31, 2025

I suggest we reconsider this. To add on to the earlier points in favor of this approach, I ran into "tx-size-small" when testing new policies with packages: a 1-in-1-out child spending the p2a of a parent can be less than 64 bytes.

That's easily fixed by having the 1-out be a 0sat OP_RETURN pushing three bytes of data... I remain Concept and Approach NACK on this, supporting 60-63 byte txs but not 64 byte txs just seems like it's asking for trouble. ie, just add the extra bytes to your output in your tests, this isn't worth optimising. And if it were worth optimising, we should actually optimise it.

OTOH, it might be plausible to just not worry about small-tx's at all, in which case the check could just be removed entirely, avoiding any discontinuities...

@instagibbs
Copy link
Member Author

@ajtowns Bit of a tomato/tomato situation imo, because I think the rules as-is invite more trouble than clearly marking the exact case we're worried about.

The strongest case against this PR, imo, is not some application-level danger about discontinuity but the fact that once you open it up it's difficult to close. But it's been many years and no one has made an argument why this would be dangerous at a network level.

OTOH, it might be plausible to just not worry about small-tx's at all, in which case the check could just be removed entirely, avoiding any discontinuities...

If we end up deciding to not ban 64b by consensus, we should seriously consider this.

@darosior
Copy link
Member

Concept ACK.

I don't buy that the discontinuity would cause any more confusion than making harmless transactions non-standard. The upgrade hook argument made sense back when this PR was closed, but i don't think it holds anymore now that no proposal includes invalidating <64 bytes transactions.

OTOH, it might be plausible to just not worry about small-tx's at all, in which case the check could just be removed entirely, avoiding any discontinuities...

This goes completely against your earlier argument of keeping our options open with regard to upgrade hooks! There is a current proposal to make those transactions invalid, so making them standard now seems unnecessarily risky.

If we end up deciding to not ban 64b by consensus, we should seriously consider this.

I'm sympathetic to this view but i'm not sure. We might decide it's not worth changing consensus over this, but it's not necessarily a reason to make it easier to exploit?

@ajtowns
Copy link
Contributor

ajtowns commented Mar 31, 2025

OTOH, it might be plausible to just not worry about small-tx's at all, in which case the check could just be removed entirely, avoiding any discontinuities...

This goes completely against your earlier argument of keeping our options open with regard to upgrade hooks! There is a current proposal to make those transactions invalid, so making them standard now seems unnecessarily risky.

If there is a need to worry about small transactions, then the conservative thing to do in consensus is forbid as little as possible (because things that have been forbidden are very difficult to re-enable), and the conservative thing to do in policy is to continue to forbid everything that's not obviously useful (because things that are enabled are very difficult to forbid). In both cases, it's the conservative option because it keeps more future possibilities open. Personally, I think ">65 is okay" is easier to deal with than "60 isn't really okay for different reasons, 61, 62, 63 are okay, 64 isn't, 65+ is".

But all of that is only relevant if there is a reason to worry about small transactions in the first place; if at some point it's established that there isn't a reason to worry about them, there's no reason to forbid anything it in either consensus or policy -- there's no need to worry about future options if it's established that the future options are definitely not needed. I don't believe that's been done yet, but it's starting to seem plausible to me. [EDIT: This isn't plausible.]

@Sjors
Copy link
Member

Sjors commented Apr 1, 2025

Suggested PR title: Policy: reallow short transactions except 64-byte

And extra sentence for the description:

Replace lower bound rule MIN_STANDARD_TX_NONWITNESS_SIZE{65} with just an exception NONSTANDARD_TX_NONWITNESS_SIZE{64}. This makes policy more aligned with the proposed consensus change in the Great Consensus Cleanup, but even if that is never activated, this is the most narrow rule which addresses CVE-2017-12842.

As for the actual change, I'm not sure either way. But to expand on @ajtowns's comment:

the conservative thing to do in policy is to continue to forbid everything that's not obviously useful

And @glozow's finding:

I ran into "tx-size-small" when testing new policies with packages: a 1-in-1-out child spending the p2a of a parent can be less than 64 bytes.

It seems there's a potential footgun in wallets that can generate < 64 transactions, and then for whatever reason one byte gets added and the transaction is stuck. From that perspective it's safer to have a policy that simplify requires > 64 bytes.

So from the perspective of using policy as a way to encourage safe designs, the current approach is better.

On the flip side, someone may feel that the current policy is too "pedantic" and start running relays using the commit from this PR. But that applies to every single policy != consensus rule, and is probably only an (economic) issue for very common transactions.

@DrahtBot
Copy link
Contributor

🐙 This pull request conflicts with the target branch and needs rebase.

@TheCharlatan
Copy link
Contributor

Concept ACK

Copy link
Contributor

@TheCharlatan TheCharlatan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 72ddf42

While I don't think the changes here are pressing, I'm for aligning policy more closely with the actual behaviour we'd like to prohibit and (eventually) enforce.

It seems there's a potential footgun in wallets that can generate < 64 transactions, and then for whatever reason one byte gets added and the transaction is stuck.

This seems a bit constructed. Why would a wallet correctly report a failed send for < 64, but fail to do so with 64 because of this rule change? Scenarios where transactions get stuck because of fees are far likelier in any case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.