diff --git a/bip-0110.mediawiki b/bip-0110.mediawiki
index 6147361987..eae9b388b9 100644
--- a/bip-0110.mediawiki
+++ b/bip-0110.mediawiki
@@ -8,6 +8,7 @@
Assigned: 2025-12-03
License: BSD-3-Clause
Discussion: https://groups.google.com/g/bitcoindev/c/nOZim6FbuF8
+ Version: 2.0.0
==Abstract==
@@ -29,6 +30,7 @@ Blocks during a temporary, one-year deployment are checked with these additional
# Taproot control blocks larger than 257 bytes (a merkle tree with 128 script leaves) are invalid.
# Tapscripts including OP_SUCCESS* opcodes anywhere (even unexecuted) are invalid.
# Tapscripts executing the OP_IF or OP_NOTIF instruction (regardless of result) are invalid.
+# A spend of a Pay-to-Anchor (P2A) output is invalid if the witness stack for that input is non-empty.
===UTXO grandfathering===
@@ -112,6 +114,7 @@ The following witness stack elements are '''not''' script argument witness items
* Others are not part of a script-path spend at all, so the script interpreter is not invoked:
** Taproot key path signatures (limited to exactly 64 or 65 bytes by BIP 341 signature validation)
** Witness stacks in spends of undefined witness versions (invalidated by rule 3)
+** Witness stacks in spends of P2A outputs (invalidated by rule 8)
All '''other''' witness stack elements are script argument witness items, and are thus limited to 256 bytes by rule 2.
@@ -135,6 +138,13 @@ This has no use case, but would require nodes to track these UTXOs in case of po
By making spending invalid, it is possible for nodes to store them instead in slow memory not needed until this softfork expires.
(With proper planning, it also makes it possible for a future softfork making use of these witness versions to allow users to receive with an upgraded wallet even prior to activation of the upgrade.)
+'''Why require P2A witness stacks to be empty?'''
+
+P2A is a keyless output type and requires no witness stack elements for spending.
+Any witness stack elements have no defined meaning and may be used as arbitrary data buckets.
+[[bip-0433.mediawiki|BIP 433]] makes P2A spends with non-empty witness stacks nonstandard while leaving their consensus meaning unchanged.
+Rule 8 temporarily promotes this policy restriction to consensus: subject to UTXO grandfathering, a P2A spend with a non-empty witness stack is invalid while this deployment is active.
+
'''Why make the Taproot annex invalid?'''
The annex is currently undefined data with unlimited size.
@@ -308,6 +318,8 @@ Pains have been taken to avoid breaking monetary use cases, and it is unlikely t
There are a couple of very unlikely scenarios in which funds could theoretically be frozen or lost. See the Tradeoffs section for more details.
+Rule 8 affects spends of P2A outputs with non-empty witness stacks. Such spends are already nonstandard under BIP 433; this proposal makes them consensus-invalid for non-grandfathered P2A outputs while the deployment is active.
+
If this proposal activates, the Miniscript compiler will need to be modified not to produce scripts that violate the new rules, at least while the new rules are active.
Users storing arbitrary data in the Bitcoin blockchain should start looking for other places to store their data, such as Nostr, IPFS, BitTorrent, cloud storage, etc, as the Bitcoin network will not support this use case going forward.
@@ -322,11 +334,15 @@ https://github.com/bitcoinknots/bitcoin/compare/29.x-knots...dathonohm:bitcoin:u
Test coverage specific to these rules is provided by the reference implementation's functional test suite:
-* [https://github.com/dathonohm/bitcoin/blob/uasf-modified-bip9/test/functional/feature_rdts.py feature_rdts.py] exercises all seven consensus rules, including their boundaries and the script-type exemptions to rule 2 (BIP16 redeemScripts, witness scripts, and Tapleaf scripts).
+* [https://github.com/dathonohm/bitcoin/blob/uasf-modified-bip9/test/functional/feature_rdts.py feature_rdts.py] exercises rules 1 through 7, including their boundaries and the script-type exemptions to rule 2 (BIP16 redeemScripts, witness scripts, and Tapleaf scripts).
* [https://github.com/dathonohm/bitcoin/blob/uasf-modified-bip9/test/functional/feature_reduced_data_utxo_height.py feature_reduced_data_utxo_height.py] covers the grandfathering of UTXOs created before the activation height.
+* [https://github.com/bitcoinknots/bitcoin/commit/38996fb57a695f45af44e85bba265508fc598088 Bitcoin Knots commit 38996fb] implements rule 8 on the 29.x-knots branch.
+* [https://github.com/bitcoinknots/bitcoin/commit/295508d530046e8f7e291680d7943fa4f7d18d69 Bitcoin Knots commit 295508d] adds functional coverage for empty and non-empty P2A witness stacks on that branch.
In addition, a set of transaction-level test vectors generated and verified against the reference implementation is provided in [[bip-0110/test-vectors.json|test-vectors.json]]. Each vector gives the spent output(s), the spending (or output-creating) transaction, and whether a block containing it is valid while these rules are active; the rule and spent_utxo fields identify which numbered rule is exercised and whether the spent UTXO is grandfathered. These vectors were produced by [[bip-0110/test-vectors.py|test-vectors.py]], which submits each case to a regtest node enforcing the rules and emits the verified result; see that file for reproduction instructions.
+The rule 8 vectors distinguish an empty witness stack from a stack containing one empty element, and verify that the rule is subject to UTXO grandfathering.
+
==Deployment==
This deployment uses a modified version of BIP9 with the following parameters:
@@ -367,6 +383,8 @@ Original draft and advice: Luke-Jr
==Changelog==
+* '''2.0.0''' (2026-07-26):
+** Document the P2A empty-witness consensus rule implemented before BIP 110 reached Complete and add corresponding test vectors
* '''1.0.0''' (2026-06-25):
** Advance to Complete
* '''0.0.4''' (2026-02-04):
diff --git a/bip-0110/test-vectors.json b/bip-0110/test-vectors.json
index 80124af79f..22dc7abccc 100644
--- a/bip-0110/test-vectors.json
+++ b/bip-0110/test-vectors.json
@@ -281,6 +281,66 @@
"expected": "valid",
"reject_reason": null
},
+ {
+ "rule": 8,
+ "name": "p2a_empty_witness_valid",
+ "description": "Spend a P2A output with an empty witness stack",
+ "reduced_data_active": true,
+ "spent_utxo": "post-activation",
+ "spent_outputs": [
+ {
+ "prevout": {
+ "txid": "110000000000000000000000000000000000000000000000000000000000000f",
+ "vout": 0
+ },
+ "scriptPubKey": "51024e73",
+ "amount": 100000
+ }
+ ],
+ "tx": "02000000010f0000000000000000000000000000000000000000000000000000000000001100000000000000000001b882010000000000015100000000",
+ "expected": "valid",
+ "reject_reason": null
+ },
+ {
+ "rule": 8,
+ "name": "p2a_empty_item_invalid",
+ "description": "Spend a P2A output with one empty witness item (non-empty stack)",
+ "reduced_data_active": true,
+ "spent_utxo": "post-activation",
+ "spent_outputs": [
+ {
+ "prevout": {
+ "txid": "1100000000000000000000000000000000000000000000000000000000000010",
+ "vout": 0
+ },
+ "scriptPubKey": "51024e73",
+ "amount": 100000
+ }
+ ],
+ "tx": "02000000000101100000000000000000000000000000000000000000000000000000000000001100000000000000000001b8820100000000000151010000000000",
+ "expected": "invalid",
+ "reject_reason": "script-verify-flag-failed (Witness version reserved for soft-fork upgrades)"
+ },
+ {
+ "rule": 8,
+ "name": "p2a_one_byte_item_invalid",
+ "description": "Spend a P2A output with one one-byte witness item",
+ "reduced_data_active": true,
+ "spent_utxo": "post-activation",
+ "spent_outputs": [
+ {
+ "prevout": {
+ "txid": "1100000000000000000000000000000000000000000000000000000000000011",
+ "vout": 0
+ },
+ "scriptPubKey": "51024e73",
+ "amount": 100000
+ }
+ ],
+ "tx": "02000000000101110000000000000000000000000000000000000000000000000000000000001100000000000000000001b882010000000000015101010100000000",
+ "expected": "invalid",
+ "reject_reason": "script-verify-flag-failed (Witness version reserved for soft-fork upgrades)"
+ },
{
"rule": 2,
"name": "grandfathered_witness_item_257_valid",
@@ -290,14 +350,34 @@
"spent_outputs": [
{
"prevout": {
- "txid": "110000000000000000000000000000000000000000000000000000000000000f",
+ "txid": "1100000000000000000000000000000000000000000000000000000000000012",
"vout": 0
},
"scriptPubKey": "002033198a9bfef674ebddb9ffaa52928017b8472791e54c609cb95f278ac6b1e349",
"amount": 100000
}
],
- "tx": "020000000001010f0000000000000000000000000000000000000000000000000000000000001100000000000000000001b882010000000000015102fd0101424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424202755100000000",
+ "tx": "02000000000101120000000000000000000000000000000000000000000000000000000000001100000000000000000001b882010000000000015102fd0101424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424202755100000000",
+ "expected": "valid",
+ "reject_reason": null
+ },
+ {
+ "rule": 8,
+ "name": "grandfathered_p2a_nonempty_witness_valid",
+ "description": "Spend a PRE-activation P2A output with a non-empty witness stack (grandfathered, exempt)",
+ "reduced_data_active": true,
+ "spent_utxo": "pre-activation",
+ "spent_outputs": [
+ {
+ "prevout": {
+ "txid": "1100000000000000000000000000000000000000000000000000000000000013",
+ "vout": 0
+ },
+ "scriptPubKey": "51024e73",
+ "amount": 100000
+ }
+ ],
+ "tx": "02000000000101130000000000000000000000000000000000000000000000000000000000001100000000000000000001b882010000000000015101010100000000",
"expected": "valid",
"reject_reason": null
},
@@ -310,14 +390,14 @@
"spent_outputs": [
{
"prevout": {
- "txid": "1100000000000000000000000000000000000000000000000000000000000010",
+ "txid": "1100000000000000000000000000000000000000000000000000000000000014",
"vout": 0
},
"scriptPubKey": "00204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260",
"amount": 100000
}
],
- "tx": "02000000000101100000000000000000000000000000000000000000000000000000000000001100000000000000000001b88201000000000023510000000000000000000000000000000000000000000000000000000000000000000001015100000000",
+ "tx": "02000000000101140000000000000000000000000000000000000000000000000000000000001100000000000000000001b88201000000000023510000000000000000000000000000000000000000000000000000000000000000000001015100000000",
"expected": "invalid",
"reject_reason": "bad-txns-vout-script-toolarge"
}
diff --git a/bip-0110/test-vectors.py b/bip-0110/test-vectors.py
index 1644bdf2d2..7636878d18 100755
--- a/bip-0110/test-vectors.py
+++ b/bip-0110/test-vectors.py
@@ -18,8 +18,9 @@
Rule 5: Taproot control block size (<=257)
Rule 6: OP_SUCCESS* in tapscript
Rule 7: OP_IF / OP_NOTIF in tapscript
+ Rule 8: P2A witness stack must be empty
Grandfathering: inputs spending pre-activation UTXOs are exempt from the
- per-input script rules (2-7); the per-output rule (1) still
+ per-input script rules (2-8); the per-output rule (1) still
applies to outputs created while active.
This is a Bitcoin Core / Knots functional test; it needs the test framework and
@@ -255,17 +256,20 @@ def run_test(self):
# P2WSH(OP_DROP OP_TRUE) lets us inject an arbitrary witness data element (rule 2)
wsh_drop = CScript([OP_DROP, OP_TRUE])
wsh_drop_spk = script_to_p2wsh_script(wsh_drop)
+ p2a_spk = CScript([OP_1, bytes.fromhex("4e73")])
# Fund "old" UTXOs BEFORE activation so they are grandfathered (exempt
# from the per-input script rules). Done first, at a low height.
self.log.info("Funding pre-activation (grandfathered) UTXOs...")
old_drop_o, old_drop_a, old_drop_h = self.fund(wsh_drop_spk)
old_true_o, old_true_a, old_true_h = self.fund(wsh_true_spk)
+ old_p2a_o, old_p2a_a, old_p2a_h = self.fund(p2a_spk)
self.log.info("Activating REDUCED_DATA...")
self.activate()
assert old_drop_h < self.activation_height
assert old_true_h < self.activation_height
+ assert old_p2a_h < self.activation_height
self.generate(self.wallet, 20) # spendable coins for funding "new" UTXOs
self.log.info("=== Rule 1: output scriptPubKey size (per-output) ===")
@@ -365,6 +369,19 @@ def run_test(self):
self.check(7, "witness_v0_op_if_valid", "OP_IF in a witness v0 script is valid (rule is tapscript-only)",
[(wsh_if_spk, a)], self.spend(o, a, [bytes(wsh_if)]), True, "post-activation")
+ self.log.info("=== Rule 8: P2A witness stack must be empty ===")
+ o, a = self.fund(p2a_spk)[0:2]
+ self.check(8, "p2a_empty_witness_valid", "Spend a P2A output with an empty witness stack",
+ [(p2a_spk, a)], self.spend(o, a, []), True, "post-activation")
+ o, a = self.fund(p2a_spk)[0:2]
+ self.check(8, "p2a_empty_item_invalid",
+ "Spend a P2A output with one empty witness item (non-empty stack)",
+ [(p2a_spk, a)], self.spend(o, a, [b'']), False, "post-activation")
+ o, a = self.fund(p2a_spk)[0:2]
+ self.check(8, "p2a_one_byte_item_invalid",
+ "Spend a P2A output with one one-byte witness item",
+ [(p2a_spk, a)], self.spend(o, a, [b'\x01']), False, "post-activation")
+
self.log.info("=== Grandfathering: pre-activation UTXOs ===")
# An input spending a pre-activation UTXO is exempt from the per-input
# script rules: a 257-byte witness item is accepted here (cf. rule 2).
@@ -373,6 +390,11 @@ def run_test(self):
[(wsh_drop_spk, old_drop_a)],
self.spend(old_drop_o, old_drop_a, [b'\x42' * 257, bytes(wsh_drop)]),
True, "pre-activation")
+ self.check(8, "grandfathered_p2a_nonempty_witness_valid",
+ "Spend a PRE-activation P2A output with a non-empty witness stack (grandfathered, exempt)",
+ [(p2a_spk, old_p2a_a)],
+ self.spend(old_p2a_o, old_p2a_a, [b'\x01']),
+ True, "pre-activation")
# But the per-output rule 1 still applies even when spending an old UTXO:
# creating an oversized output is rejected regardless of input age.
self.check(1, "grandfathered_input_oversized_output_invalid",