* [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions
@ 2026-06-01 17:46 jeremy
2026-06-01 18:49 ` 'Antoine Poinsot' via Bitcoin Development Mailing List
2026-06-09 18:30 ` Matt Corallo
0 siblings, 2 replies; 11+ messages in thread
From: jeremy @ 2026-06-01 17:46 UTC (permalink / raw)
To: Bitcoin Development Mailing List
[-- Attachment #1.1: Type: text/plain, Size: 28431 bytes --]
Esteemed Colleagues,
As a result of some of my research on 64-byte transactions, I'd like to
discuss an alternative soft fork proposal that preserves the ability to
encode 64-byte transactions while offering protection to SPV users (who
must make a small patch to validate the path property).
The rule, stated simply, is:
A block is invalid if any Merkle Tree 64-byte preimage has the exact byte
structure of a minimal one-input, one-output, witness stripped transaction.
[With the miracle of GPT,] I've drafted a relatively complete BIP for
discussion.
Happy International Children's Day,
Jeremy
p.s. I will later propose potentially a couple other mitigations
separately, for discussion as well.
------------------------------
BIP: TBD
Layer: Consensus (soft fork)
Title: Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte
Transactions
Author: TBD
Status: Draft
Type: Standards Track
Created: 2026-06-01
License: BSD-2-Clause
*Abstract*
This document specifies a consensus rule that invalidates a block if any
transaction Merkle tree internal node preimage encodes a minimal 64-byte
transaction.
For each internal Merkle node, Bitcoin computes:
parent = SHA256d(left || right)
where left and right are 32-byte hashes. The 64-byte string left || right
is the internal node preimage.
After activation, a block is invalid if any such 64-byte preimage has the
exact byte structure of a minimal one-input, one-output, non-witness
transaction.
This prevents a 64-byte transaction serialization from being malleated into
an internal Merkle node preimage in SPV transaction inclusion proofs. It
does not make 64-byte transactions invalid in general.
*Motivation*
Bitcoin transaction identifiers and transaction Merkle internal nodes are
both computed with double SHA256:
txid = SHA256d(serialized_transaction)
parent = SHA256d(left_child_hash || right_child_hash)
If a valid transaction serialization is exactly 64 bytes, the same byte
string can also be interpreted as the concatenation of two 32-byte Merkle
child hashes:
serialized_transaction = left_child_hash || right_child_hash
This creates an ambiguity between a transaction leaf preimage and an
internal node preimage.
An SPV verifier that accepts a Merkle proof without authenticating the full
tree shape can be made to accept a proof terminating at an internal node
rather than at an actual transaction leaf.
This proposal removes that ambiguity by forbidding Merkle internal node
preimages that have the only practical 64-byte transaction encoding shape.
*SegWit and transaction identifiers*
Since SegWit activation, Bitcoin transactions have two related identifiers:
txid = SHA256d(legacy serialization)
wtxid = SHA256d(witness serialization)
The distinction is important for understanding this proposal.
A SegWit transaction is serialized on the wire as:
nVersion
marker
flag
vin
vout
witness
nLockTime
where:
marker = 0x00
flag = 0x01
The marker and flag bytes indicate that witness data is present.
However, the transaction identifier (txid) is not computed from this
witness serialization. Instead, the txid is computed from the legacy
serialization:
nVersion
vin
vout
nLockTime
with the marker, flag, and witness fields omitted.
Therefore:
txid = SHA256d(non-witness serialization)
while:
wtxid = SHA256d(full witness serialization)
The transaction Merkle root committed in the block header is built from
transaction identifiers (txids), not witness transaction identifiers (wtxids
).
Consequently:
Merkle root = Merkle(txid_0, txid_1, ..., txid_n)
and not:
Merkle(wtxid_0, wtxid_1, ..., wtxid_n)
This means that the marker byte (0x00), flag byte (0x01), and witness data
never appear in the transaction Merkle tree committed by the block header.
SegWit does define a separate witness Merkle tree whose root is committed
through the coinbase witness commitment, but that witness Merkle tree is
distinct from the transaction Merkle tree discussed in this proposal.
As a result, the ambiguity addressed by this proposal concerns only
transaction identifiers (txids) and the transaction Merkle root. The SegWit
marker and flag bytes are irrelevant to the transaction Merkle root because
they are excluded from txid serialization.
*Minimal 64-byte transaction shape*
This proposal is concerned with the serialization used to compute a
transaction's txid.
For legacy transactions, and for SegWit transactions when computing the txid,
the serialization format is:
4 bytes nVersion
1 byte vin count = 0x01
36 bytes prevout
1 byte scriptSig length = x
x bytes scriptSig
4 bytes nSequence
1 byte vout count = 0x01
8 bytes nValue
1 byte scriptPubKey length = y
y bytes scriptPubKey
4 bytes nLockTime
Notably, this serialization does not include:
marker
flag
witness stack
because those fields are excluded from txid computation.
The fixed overhead is:
4 + 1 + 36 + 1 + 4 + 1 + 8 + 1 + 4 = 60 bytes
Therefore, for total serialized size 64:
x + y = 4
There are exactly five possible script-length splits:
scriptSig length scriptPubKey length
0 4
1 3
2 2
3 1
4 0
This proposal defines a forbidden Merkle internal node preimage as a
64-byte byte string satisfying one of those five layouts and whose single
output value is in the consensus money range.
*Specification*
After activation, a block is invalid if any transaction Merkle internal
node preimage encodes a minimal 64-byte transaction.
For every internal Merkle parent computation in the transaction Merkle tree:
parent = SHA256d(left || right)
where left and right are 32-byte child hashes, define:
P = left || right
The block is invalid if P satisfies all of the following:
1.
P[4] == 0x01.
2.
P[41] is one of 0, 1, 2, 3, 4.
3.
Let x = P[41].
4.
Let sequence_pos = 42 + x.
5.
Let vout_count_pos = sequence_pos + 4.
6.
Let value_pos = vout_count_pos + 1.
7.
Let scriptpubkey_len_pos = value_pos + 8.
8.
P[vout_count_pos] == 0x01.
9.
P[scriptpubkey_len_pos] == 4 - x.
10.
Let locktime_pos = scriptpubkey_len_pos + 1 + (4 - x).
11.
locktime_pos + 4 == 64.
12.
The 8-byte little-endian integer at P[value_pos..value_pos+7] is in
MoneyRange.
Equivalently, the forbidden preimage is a 64-byte serialization of a
one-input, one-output, non-witness transaction with single-byte CompactSize
counts and script lengths, where the two script lengths sum to 4 and the
output value is in range.
For clarity, "non-witness transaction" here refers to the serialization
used for txid computation. Even for SegWit transactions, the transaction
Merkle tree uses txids, so the marker byte, flag byte, and witness data are
excluded.
This rule applies to every transaction Merkle internal node used to compute
the block header's transaction Merkle root.
*Odd-entry duplication*
If a Merkle level has an odd number of entries, Bitcoin duplicates the
final hash:
parent = SHA256d(last || last)
The preimage:
last || last
MUST be checked by the same rule.
*SPV verification rule*
An SPV verifier relying on this soft fork MUST reject a Merkle proof if any
branch preimage in the proof encodes a minimal 64-byte transaction under
the predicate above.
For each branch step, the verifier knows:
1.
The current hash.
2.
The sibling hash.
3.
The branch direction.
It reconstructs:
P = left_child_hash || right_child_hash
The verifier MUST check:
IsForbiddenMerkleInternalNodePreimage(P) == false
for every branch preimage in the proof.
If any branch preimage passes the forbidden-preimage predicate, the proof
MUST be rejected.
The verifier still performs the ordinary Merkle path computation and block
header proof-of-work validation.
*Rationale*
The known 64-byte transaction SPV malleability issue requires a byte string
that is both:
a valid 64-byte transaction serialization
and:
a transaction Merkle internal node preimage
This proposal forbids that overlap at the Merkle internal node boundary.
The rule is narrower than invalidating all 64-byte transactions. A 64-byte
transaction remains valid unless its exact serialization appears as a
transaction Merkle internal node preimage in the same block's transaction
Merkle tree.
The rule also avoids adding a general transaction validity rule that exists
only to protect Merkle proof semantics.
*Why SegWit does not eliminate the ambiguity*
It is sometimes assumed that SegWit automatically removes this ambiguity
because SegWit transactions contain the marker and flag bytes:
00 01
However, the ambiguity exists at the txid layer, not at the
witness-serialization layer.
The transaction Merkle root in the block header is computed from txids, and
txids are computed from the serialization that excludes:
marker
flag
witness
Therefore the relevant byte string remains:
nVersion
vin
vout
nLockTime
exactly as before SegWit.
The witness serialization affects the wtxid, but the block header's
transaction Merkle root does not commit to wtxids.
As a result, the existence of the SegWit marker and flag bytes does not
prevent a txid preimage from having the same byte structure as a Merkle
internal node preimage.
The ambiguity addressed by this proposal therefore remains relevant in the
SegWit era.
*Contrast with a 64-byte transaction invalidity rule*
A direct alternative is:
A transaction is invalid if its serialized size is exactly 64 bytes.
That rule has several advantages:
1.
It is simple to specify.
2.
It is simple for SPV verifiers to implement.
3.
It removes the original ambiguity by eliminating all valid 64-byte
transaction leaves.
However, it is not correct to describe that rule as automatically fixing
all light clients.
A 64-byte transaction invalidity rule protects an SPV verifier only if the
verifier enforces the new rule when interpreting the claimed transaction.
Existing or application-specific SPV verifiers that merely receive a byte
string and a Merkle branch may remain vulnerable if they do not parse the
claimed transaction and reject exactly-64-byte transaction serializations.
More generally, a consensus rule invalidating 64-byte transactions does not
prevent arbitrary internal node preimages from existing. It only prevents
those preimages from being valid Bitcoin transactions under upgraded
consensus rules. A bridge, wallet, or deposit system that accepts SPV-style
proofs but performs incomplete transaction parsing may still be induced to
treat an internal node preimage as an application-level event.
For example, suppose an application-level SPV verifier treats a proved byte
string as a "deposit" if some field inside the alleged transaction matches
a registered deposit address, deposit script, or deposit commitment, but
does not fully enforce the upgraded transaction-validity rule. An attacker
may be able to grind child hashes so that:
left_child_hash || right_child_hash
has bytes that the application interprets as a deposit transaction or
deposit commitment. In some systems, the attacker may also be able to
choose or register deposit data that matches bytes already present in the
left-hand side of an internal node preimage.
This is not a failure of upgraded full-node consensus. It is a failure of
the assumption that changing full-node transaction validity automatically
upgrades every SPV verifier and every bridge, wallet, or application that
consumes SPV-style proofs.
Therefore, both approaches require light-client changes:
64-byte transaction invalidity:
Light clients must reject claimed 64-byte transaction serializations.
Merkle-internal-node preimage invalidity:
Light clients must reject proofs containing forbidden internal branch
preimages.
The 64-byte transaction invalidity rule is simpler for light clients that
correctly implement it, but it is broader at the transaction layer. This
proposal places the rule at the Merkle ambiguity boundary and preserves
64-byte transactions generally.
In summary:
64-byte transaction invalidity:
- Simpler SPV rule when implemented correctly.
- Broader transaction validity change.
- Invalidates all 64-byte transactions.
- Does not automatically fix SPV applications that fail to enforce the
new rule.
Merkle-internal-node preimage invalidity:
- Preserves 64-byte transactions generally.
- Places the rule at the Merkle ambiguity boundary.
- Requires SPV verifiers to parse all branch preimages.
- Directly forbids the ambiguous internal-node preimage condition.
*Minimal C++ implementation sketch*
This implementation checks only the minimal forbidden 64-byte shape. It
does not invoke the full transaction deserializer.
The function returns true if the 64-byte preimage is forbidden.
static constexpr int64_t COIN = 100000000;
static constexpr int64_t MAX_MONEY = 21000000 * COIN;
static inline bool MoneyRange(int64_t nValue)
{
return nValue >= 0 && nValue <= MAX_MONEY;
}
static inline uint64_t ReadLE64(const unsigned char* p)
{
return uint64_t{p[0]}
| (uint64_t{p[1]} << 8)
| (uint64_t{p[2]} << 16)
| (uint64_t{p[3]} << 24)
| (uint64_t{p[4]} << 32)
| (uint64_t{p[5]} << 40)
| (uint64_t{p[6]} << 48)
| (uint64_t{p[7]} << 56);
}
static bool IsForbiddenMerkleInternalNodePreimage64(const unsigned char
p[64])
{
// Minimal 64-byte legacy transaction shape:
//
// 4 bytes nVersion
// 1 byte vin count = 0x01
// 36 bytes prevout
// 1 byte scriptSig length = x
// x bytes scriptSig
// 4 bytes nSequence
// 1 byte vout count = 0x01
// 8 bytes nValue
// 1 byte scriptPubKey length = y
// y bytes scriptPubKey
// 4 bytes nLockTime
//
// Since the fixed overhead is 60 bytes, x + y must equal 4.
if (p[4] != 0x01) {
return false;
}
const unsigned int x = p[41];
switch (x) {
case 0:
if (p[46] != 0x01) return false;
if (p[55] != 0x04) return false;
break;
case 1:
if (p[47] != 0x01) return false;
if (p[56] != 0x03) return false;
break;
case 2:
if (p[48] != 0x01) return false;
if (p[57] != 0x02) return false;
break;
case 3:
if (p[49] != 0x01) return false;
if (p[58] != 0x01) return false;
break;
case 4:
if (p[50] != 0x01) return false;
if (p[59] != 0x00) return false;
break;
default:
return false;
}
const size_t value_pos = 47 + x;
const uint64_t raw_value = ReadLE64(p + value_pos);
if (raw_value >
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
return false;
}
const int64_t nValue = static_cast<int64_t>(raw_value);
if (!MoneyRange(nValue)) {
return false;
}
return true;
}
static bool IsForbiddenMerkleInternalNode(
const uint256& left,
const uint256& right)
{
unsigned char p[64];
std::memcpy(p, left.begin(), 32);
std::memcpy(p + 32, right.begin(), 32);
return IsForbiddenMerkleInternalNodePreimage64(p);
}
A Merkle parent computation then checks the preimage before hashing:
static uint256 ComputeMerkleParentChecked(
const uint256& left,
const uint256& right,
bool& invalid)
{
if (IsForbiddenMerkleInternalNode(left, right)) {
invalid = true;
return uint256{};
}
unsigned char p[64];
std::memcpy(p, left.begin(), 32);
std::memcpy(p + 32, right.begin(), 32);
return Hash(Span<const unsigned char>(p, 64));
}
This is the intended minimal rule. It checks the five possible 64-byte
one-input, one-output transaction layouts directly.
*Miner considerations*
Accidental violations by honest miners are expected to be rare.
Adversarial violations are possible. An attacker may grind transaction
identifiers so that two transactions, if placed as siblings in the
transaction Merkle tree, form:
txid_A || txid_B
which encodes a forbidden minimal 64-byte transaction.
An attacker may attempt to influence sibling placement by fee rate, package
construction, direct miner submission, or transaction ordering effects.
Therefore miners MUST check candidate block templates before mining. Miners
MUST NOT rely on accidental violation probability.
*Merkle construction failure recovery*
If a candidate block template violates this rule, the miner usually does
not need to discard the entire template. The violation is local to one or
more internal Merkle node preimages:
left_child_hash || right_child_hash
A miner can usually repair the candidate block by changing transaction
order so that the offending pair of child hashes no longer appears as
siblings at the violating Merkle tree level.
*Recommended recovery procedure*
When Merkle root construction fails because an internal node preimage is
forbidden, mining software SHOULD use the following procedure:
1.
Record each offending internal node preimage.
2.
Identify the transaction subtree contributing to each offending child
hash.
3.
Attempt to repair the block by shuffling transaction order while
preserving consensus transaction-order constraints.
4.
Recompute the Merkle root and re-run the internal-node preimage check.
5.
If the shuffled template passes, mine the repaired template.
6.
If shuffling fails repeatedly, remove one or more transactions
contributing to the offending subtree and rebuild the template.
*Preserving transaction-order constraints*
A shuffle MUST NOT violate transaction dependency ordering.
If transaction B spends an output created by transaction A in the same
block, then A MUST appear before B.
The coinbase transaction MUST remain the first transaction in the block.
Mining software SHOULD shuffle only transactions whose relative order is
not constrained by in-block dependencies, or use a randomized topological
ordering of the block's transaction dependency graph.
*Simple shuffle algorithm*
A simple repair algorithm is:
1. Keep the coinbase fixed at index 0.
2. Build a dependency graph for all non-coinbase transactions.
3. Generate a randomized topological ordering of the graph.
4. Construct the Merkle tree using that ordering.
5. Reject the ordering if any internal node preimage is forbidden.
6. Retry with a new randomized topological ordering.
This changes Merkle sibling relationships without violating in-block
transaction dependencies.
*Repeated failure*
If randomized repair fails repeatedly, mining software SHOULD remove
transactions contributing to the repeated offending subtree.
A reasonable policy is:
If Merkle construction fails after 2 independent shuffle attempts,
remove at least one transaction from each repeatedly offending pair or
subtree.
For a bottom-level violation, the offending subtree usually corresponds to
two sibling transaction identifiers:
txid_A || txid_B
In that case, the miner may remove either tx_A or tx_B.
For a higher-level violation, each child hash commits to a subtree
containing multiple transactions. In that case, the miner may:
1. Try another dependency-preserving shuffle.
2. If the same higher-level violation recurs, remove one transaction from
one child subtree.
3. Prefer removing the lowest-feerate removable transaction that does not
force removal of higher-feerate descendants.
This policy does not need to identify a malicious transaction. It only
needs to produce a valid block template with minimal fee loss.
*Fee impact*
The expected fee impact for honest block templates should be negligible
because accidental violations are rare.
If an adversary intentionally creates transactions that cause violations
when paired, shuffling will usually defeat the attempt without fee loss. If
shuffling does not repair the template, removing one or more offending
transactions bounds the miner's exposure.
The adversary's practical effect is limited to potentially causing some
transactions to be omitted from a candidate block template. The rule
prevents upgraded miners from mining invalid blocks, provided miners check
the Merkle construction before mining.
*Relation to unupgraded miners*
Because accidental violations are rare, unupgraded miners are unlikely to
encounter the rule during ordinary operation.
However, an adversary can construct transaction pairs intended to trigger
the rule under specific sibling placement.
Unupgraded miners that do not enforce this rule may mine a block that
upgraded nodes reject after activation. Low accidental probability improves
deployment safety but is not a substitute for miner enforcement.
*Probability analysis*
This section estimates accidental violation probability under simplified
randomness assumptions.
*Random left || right*
Assume the 64-byte internal node preimage is uniformly random.
For the preimage to encode a minimal one-input, one-output 64-byte
transaction, it must satisfy:
vin_count = 0x01
scriptSig_len = x, where x ∈ {0,1,2,3,4}
vout_count = 0x01 at the position determined by x
scriptPubKey_len = 4 - x
nValue ∈ [0, MAX_MONEY]
Ignoring nValue, the structural probability is approximately:
5 / 256^3
because there are five valid (scriptSig_len, scriptPubKey_len) splits, and
three one-byte constraints:
vin_count
vout_count
scriptPubKey_len
Numerically:
5 / 256^3 ≈ 2.980232238769531e-7
or approximately:
1 in 3,355,443
Including the output value money range:
MAX_MONEY = 21,000,000 * 100,000,000
= 2,100,000,000,000,000
For a uniformly random unsigned 64-bit output value, the probability of
being in range is approximately:
(MAX_MONEY + 1) / 2^64
≈ 1.1384122811097797e-4
Therefore the approximate probability that a random 64-byte preimage is
structurally valid and has an in-range output value is:
(5 / 256^3) * ((MAX_MONEY + 1) / 2^64)
≈ 3.392733219831406e-11
or approximately:
1 in 29,475,000,000
*Random left || left*
For an odd-entry duplicated Merkle node, the preimage has the form:
left || left
where the first 32 bytes equal the last 32 bytes.
Let the 32-byte half be:
A[0..31]
Then:
P[0..31] = A[0..31]
P[32..63] = A[0..31]
For the same one-input, one-output 64-byte transaction shape:
P[4] = 0x01
P[41] = scriptSig_len = x
P[vout_count_pos] = 0x01
P[scriptpubkey_len_pos] = 4 - x
Because positions after byte 31 alias positions in the first half:
P[i] = A[i mod 32]
The relevant positions are:
vin_count_pos = 4
script_len_pos = 41 ≡ 9 mod 32
vout_count_pos = 46 + x ≡ 14 + x mod 32
scriptpubkey_len_pos = 55 + x ≡ 23 + x mod 32
The constraints are:
A[4] = 0x01
A[9] = x
A[14 + x] = 0x01
A[23 + x] = 4 - x
For each fixed x, these are four independent one-byte constraints under the
random-half model.
Thus the structural probability is approximately:
5 / 256^4
≈ 1.1641532182693481e-9
or approximately:
1 in 858,993,459
The output value begins at:
value_pos = 47 + x
which aliases to an 8-byte window in the random 32-byte half:
A[15 + x .. 22 + x]
Using the same simplified independence approximation, the probability of
being in MoneyRange is approximately:
(MAX_MONEY + 1) / 2^64
≈ 1.1384122811097797e-4
So the approximate probability that a random left || left preimage is
structurally valid and has an in-range output value is:
(5 / 256^4) * ((MAX_MONEY + 1) / 2^64)
≈ 1.3252864140005492e-13
or approximately:
1 in 7,545,600,000,000
*Block-level accidental probability*
A block with n transactions has approximately n - 1 internal Merkle nodes,
plus duplicated-node cases depending on tree shape.
Using the rough random left || right estimate:
p ≈ 3.39e-11
A block with 10,000 transactions has approximate accidental violation
probability:
1 - (1 - p)^9999 ≈ 3.39e-7
or roughly:
1 in 2,950,000 blocks
This is a simplified estimate. Actual txids are not perfect independent
random samples in all cases, duplicated nodes have lower estimated
probability, and additional implementation details may reduce or alter the
rate.
The deployment-relevant conclusion is:
Honest accidental violations should be rare.
Adversarial violations are possible.
Miners must enforce the rule.
*Backward compatibility*
This is a soft fork. Blocks violating the new rule were previously valid
and become invalid after activation.
Unupgraded full nodes may accept violating blocks after activation.
Activation therefore requires ordinary soft-fork deployment procedures.
Unupgraded SPV clients remain vulnerable to the legacy proof ambiguity. SPV
clients must update their Merkle proof validation logic to obtain the
benefit of this rule.
*Test vectors*
Test vectors should include:
1.
A block whose transaction Merkle internal node preimages do not encode
minimal 64-byte transactions. The block is valid.
2.
A block containing a 64-byte transaction whose serialization does not
appear as an internal node preimage. The block is valid.
3.
A block where an internal node preimage encodes a minimal 64-byte
transaction. The block is invalid.
4.
A block where an odd-entry duplicated preimage h || h encodes a minimal
64-byte transaction. The block is invalid.
5.
An SPV proof where one branch preimage encodes a minimal 64-byte
transaction. The proof is rejected.
6.
An SPV proof for a 64-byte transaction where no branch preimage encodes
a minimal 64-byte transaction. The proof is accepted if otherwise valid.
*Open questions*
1.
Should the rule include only the explicit minimal 64-byte legacy
transaction shape above, or should it call the full consensus transaction
deserializer?
2.
Should future transaction serialization changes be required to preserve
this exact forbidden-preimage invariant?
3.
Should pre-activation relay policy discourage transaction pairs that can
form forbidden sibling preimages?
4.
Should mining software standardize a recovery procedure for failed
Merkle construction, or should this remain implementation-specific?
5.
Should SPV proof formats include an explicit version bit indicating
branch-preimage checking support?
--
You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/f97afcc5-54ba-4284-8e9b-e8c35c7101f6n%40googlegroups.com.
[-- Attachment #1.2: Type: text/html, Size: 225442 bytes --]
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-01 17:46 [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions jeremy @ 2026-06-01 18:49 ` 'Antoine Poinsot' via Bitcoin Development Mailing List 2026-06-01 20:17 ` jeremy 2026-06-09 18:30 ` Matt Corallo 1 sibling, 1 reply; 11+ messages in thread From: 'Antoine Poinsot' via Bitcoin Development Mailing List @ 2026-06-01 18:49 UTC (permalink / raw) To: jeremy; +Cc: Bitcoin Development Mailing List [-- Attachment #1: Type: text/plain, Size: 30815 bytes --] Jeremy, Thanks for sharing the proposal you have been teasing. If requiring a change from SPV verifiers is acceptable, then there are already mitigations available today that don't need a consensus change. In fact SPV verifiers could already reject Merkle proofs where any node deserializes to a Bitcoin transaction. Introducing a consensus rule to address this issue is interesting if it allows to patch the issue "from under" Merkle proofs users. Because despite mitigations being available today, we keep seeing new SPV verifiers deployed without such mitigations in place by implementers who are not aware of this vulnerability. I don't think we should blame implementers here: this is really a quirk that we should fix at the consensus level if we have the opportunity to. When comparing your proposal to simply invalidating 64-byte transactions, you claim that the latter would also require SPV verifiers to patch their software: > More generally, a consensus rule invalidating 64-byte transactions does not prevent arbitrary internal node preimages from existing. It only prevents those preimages from being valid Bitcoin transactions under upgraded consensus rules. A bridge, wallet, or deposit system that accepts SPV-style proofs but performs incomplete transaction parsing may still be induced to treat an internal node preimage as an application-level event. This is incorrect for any bridge, wallet, or deposit system that does not receive funds to a script that either burns the funds or that anyone can spend. Sure, an SPV verifier for a bridge or a wallet that wants to receive funds into the void would have to be patched to make sure it rejects proofs for 64-byte transactions.. But i don't think this is a meaningful distinction, as any system that actually matters and tries to secure value would not have to be patched. This is why we chose this approach for BIP 54 (see the Rationale section). Antoine On Monday, June 1st, 2026 at 2:02 PM, jeremy <jeremy.l.rubin@gmail.com> wrote: > Esteemed Colleagues, > > As a result of some of my research on 64-byte transactions, I'd like to discuss an alternative soft fork proposal that preserves the ability to encode 64-byte transactions while offering protection to SPV users (who must make a small patch to validate the path property). > > The rule, stated simply, is: > > A block is invalid if any Merkle Tree 64-byte preimage has the exact byte structure of a minimal one-input, one-output, witness stripped transaction. > > [With the miracle of GPT,] I've drafted a relatively complete BIP for discussion. > > Happy International Children's Day, > > Jeremy > > p.s. I will later propose potentially a couple other mitigations separately, for discussion as well. > > --------------------------------------------------------------- > > BIP: TBD > Layer: Consensus (soft fork) > Title: Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions > Author: TBD > Status: Draft > Type: Standards Track > Created: 2026-06-01 > License: BSD-2-Clause > > Abstract > > This document specifies a consensus rule that invalidates a block if any transaction Merkle tree internal node preimage encodes a minimal 64-byte transaction. > > For each internal Merkle node, Bitcoin computes: > > parent = SHA256d(left || right) > > where left and right are 32-byte hashes. The 64-byte string left || right is the internal node preimage. > > After activation, a block is invalid if any such 64-byte preimage has the exact byte structure of a minimal one-input, one-output, non-witness transaction. > > This prevents a 64-byte transaction serialization from being malleated into an internal Merkle node preimage in SPV transaction inclusion proofs. It does not make 64-byte transactions invalid in general. > > Motivation > > Bitcoin transaction identifiers and transaction Merkle internal nodes are both computed with double SHA256: > > txid = SHA256d(serialized_transaction) > > parent = SHA256d(left_child_hash || right_child_hash) > > If a valid transaction serialization is exactly 64 bytes, the same byte string can also be interpreted as the concatenation of two 32-byte Merkle child hashes: > > serialized_transaction = left_child_hash || right_child_hash > > This creates an ambiguity between a transaction leaf preimage and an internal node preimage. > > An SPV verifier that accepts a Merkle proof without authenticating the full tree shape can be made to accept a proof terminating at an internal node rather than at an actual transaction leaf. > > This proposal removes that ambiguity by forbidding Merkle internal node preimages that have the only practical 64-byte transaction encoding shape. > > SegWit and transaction identifiers > > Since SegWit activation, Bitcoin transactions have two related identifiers: > > txid = SHA256d(legacy serialization) > > wtxid = SHA256d(witness serialization) > > The distinction is important for understanding this proposal. > > A SegWit transaction is serialized on the wire as: > > nVersion > > marker > > flag > > vin > > vout > > witness > > nLockTime > > where: > > marker = 0x00 > > flag = 0x01 > > The marker and flag bytes indicate that witness data is present. > > However, the transaction identifier (txid) is not computed from this witness serialization. Instead, the txid is computed from the legacy serialization: > > nVersion > > vin > > vout > > nLockTime > > with the marker, flag, and witness fields omitted. > > Therefore: > > txid = SHA256d(non-witness serialization) > > while: > > wtxid = SHA256d(full witness serialization) > > The transaction Merkle root committed in the block header is built from transaction identifiers (txids), not witness transaction identifiers (wtxids). > > Consequently: > > Merkle root = Merkle(txid_0, txid_1, ..., txid_n) > > and not: > > Merkle(wtxid_0, wtxid_1, ..., wtxid_n) > > This means that the marker byte (0x00), flag byte (0x01), and witness data never appear in the transaction Merkle tree committed by the block header. > > SegWit does define a separate witness Merkle tree whose root is committed through the coinbase witness commitment, but that witness Merkle tree is distinct from the transaction Merkle tree discussed in this proposal. > > As a result, the ambiguity addressed by this proposal concerns only transaction identifiers (txids) and the transaction Merkle root. The SegWit marker and flag bytes are irrelevant to the transaction Merkle root because they are excluded from txid serialization. > > Minimal 64-byte transaction shape > > This proposal is concerned with the serialization used to compute a transaction's txid. > > For legacy transactions, and for SegWit transactions when computing the txid, the serialization format is: > > 4 bytes nVersion > > 1 byte vin count = 0x01 > > 36 bytes prevout > > 1 byte scriptSig length = x > > x bytes scriptSig > > 4 bytes nSequence > > 1 byte vout count = 0x01 > > 8 bytes nValue > > 1 byte scriptPubKey length = y > > y bytes scriptPubKey > > 4 bytes nLockTime > > Notably, this serialization does not include: > > marker > > flag > > witness stack > > because those fields are excluded from txid computation. > > The fixed overhead is: > > 4 + 1 + 36 + 1 + 4 + 1 + 8 + 1 + 4 = 60 bytes > > Therefore, for total serialized size 64: > > x + y = 4 > > There are exactly five possible script-length splits: > > scriptSig length scriptPubKey length > > 0 4 > > 1 3 > > 2 2 > > 3 1 > > 4 0 > > This proposal defines a forbidden Merkle internal node preimage as a 64-byte byte string satisfying one of those five layouts and whose single output value is in the consensus money range. > > Specification > > After activation, a block is invalid if any transaction Merkle internal node preimage encodes a minimal 64-byte transaction. > > For every internal Merkle parent computation in the transaction Merkle tree: > > parent = SHA256d(left || right) > > where left and right are 32-byte child hashes, define: > > P = left || right > > The block is invalid if P satisfies all of the following: > > - > > P[4] == 0x01. > > - > > P[41] is one of 0, 1, 2, 3, 4. > > - > > Let x = P[41]. > > - > > Let sequence_pos = 42 + x. > > - > > Let vout_count_pos = sequence_pos + 4. > > - > > Let value_pos = vout_count_pos + 1. > > - > > Let scriptpubkey_len_pos = value_pos + 8. > > - > > P[vout_count_pos] == 0x01. > > - > > P[scriptpubkey_len_pos] == 4 - x. > > - > > Let locktime_pos = scriptpubkey_len_pos + 1 + (4 - x). > > - > > locktime_pos + 4 == 64. > > - > > The 8-byte little-endian integer at P[value_pos..value_pos+7] is in MoneyRange. > > Equivalently, the forbidden preimage is a 64-byte serialization of a one-input, one-output, non-witness transaction with single-byte CompactSize counts and script lengths, where the two script lengths sum to 4 and the output value is in range. > > For clarity, "non-witness transaction" here refers to the serialization used for txid computation. Even for SegWit transactions, the transaction Merkle tree uses txids, so the marker byte, flag byte, and witness data are excluded. > > This rule applies to every transaction Merkle internal node used to compute the block header's transaction Merkle root. > > Odd-entry duplication > > If a Merkle level has an odd number of entries, Bitcoin duplicates the final hash: > > parent = SHA256d(last || last) > > The preimage: > > last || last > > MUST be checked by the same rule. > > SPV verification rule > > An SPV verifier relying on this soft fork MUST reject a Merkle proof if any branch preimage in the proof encodes a minimal 64-byte transaction under the predicate above. > > For each branch step, the verifier knows: > > - > > The current hash. > > - > > The sibling hash. > > - > > The branch direction. > > It reconstructs: > > P = left_child_hash || right_child_hash > > The verifier MUST check: > > IsForbiddenMerkleInternalNodePreimage(P) == false > > for every branch preimage in the proof. > > If any branch preimage passes the forbidden-preimage predicate, the proof MUST be rejected. > > The verifier still performs the ordinary Merkle path computation and block header proof-of-work validation. > > Rationale > > The known 64-byte transaction SPV malleability issue requires a byte string that is both: > > a valid 64-byte transaction serialization > > and: > > a transaction Merkle internal node preimage > > This proposal forbids that overlap at the Merkle internal node boundary. > > The rule is narrower than invalidating all 64-byte transactions. A 64-byte transaction remains valid unless its exact serialization appears as a transaction Merkle internal node preimage in the same block's transaction Merkle tree. > > The rule also avoids adding a general transaction validity rule that exists only to protect Merkle proof semantics. > > Why SegWit does not eliminate the ambiguity > > It is sometimes assumed that SegWit automatically removes this ambiguity because SegWit transactions contain the marker and flag bytes: > > 00 01 > > However, the ambiguity exists at the txid layer, not at the witness-serialization layer. > > The transaction Merkle root in the block header is computed from txids, and txids are computed from the serialization that excludes: > > marker > > flag > > witness > > Therefore the relevant byte string remains: > > nVersion > > vin > > vout > > nLockTime > > exactly as before SegWit. > > The witness serialization affects the wtxid, but the block header's transaction Merkle root does not commit to wtxids. > > As a result, the existence of the SegWit marker and flag bytes does not prevent a txid preimage from having the same byte structure as a Merkle internal node preimage. > > The ambiguity addressed by this proposal therefore remains relevant in the SegWit era. > > Contrast with a 64-byte transaction invalidity rule > > A direct alternative is: > > A transaction is invalid if its serialized size is exactly 64 bytes. > > That rule has several advantages: > > - > > It is simple to specify. > > - > > It is simple for SPV verifiers to implement. > > - > > It removes the original ambiguity by eliminating all valid 64-byte transaction leaves. > > However, it is not correct to describe that rule as automatically fixing all light clients. > > A 64-byte transaction invalidity rule protects an SPV verifier only if the verifier enforces the new rule when interpreting the claimed transaction. Existing or application-specific SPV verifiers that merely receive a byte string and a Merkle branch may remain vulnerable if they do not parse the claimed transaction and reject exactly-64-byte transaction serializations. > > More generally, a consensus rule invalidating 64-byte transactions does not prevent arbitrary internal node preimages from existing. It only prevents those preimages from being valid Bitcoin transactions under upgraded consensus rules. A bridge, wallet, or deposit system that accepts SPV-style proofs but performs incomplete transaction parsing may still be induced to treat an internal node preimage as an application-level event. > > For example, suppose an application-level SPV verifier treats a proved byte string as a "deposit" if some field inside the alleged transaction matches a registered deposit address, deposit script, or deposit commitment, but does not fully enforce the upgraded transaction-validity rule. An attacker may be able to grind child hashes so that: > > left_child_hash || right_child_hash > > has bytes that the application interprets as a deposit transaction or deposit commitment. In some systems, the attacker may also be able to choose or register deposit data that matches bytes already present in the left-hand side of an internal node preimage. > > This is not a failure of upgraded full-node consensus. It is a failure of the assumption that changing full-node transaction validity automatically upgrades every SPV verifier and every bridge, wallet, or application that consumes SPV-style proofs. > > Therefore, both approaches require light-client changes: > > 64-byte transaction invalidity: > > Light clients must reject claimed 64-byte transaction serializations. > > Merkle-internal-node preimage invalidity: > > Light clients must reject proofs containing forbidden internal branch preimages. > > The 64-byte transaction invalidity rule is simpler for light clients that correctly implement it, but it is broader at the transaction layer. This proposal places the rule at the Merkle ambiguity boundary and preserves 64-byte transactions generally. > > In summary: > > 64-byte transaction invalidity: > > - Simpler SPV rule when implemented correctly. > > - Broader transaction validity change. > > - Invalidates all 64-byte transactions. > > - Does not automatically fix SPV applications that fail to enforce the new rule. > > Merkle-internal-node preimage invalidity: > > - Preserves 64-byte transactions generally. > > - Places the rule at the Merkle ambiguity boundary. > > - Requires SPV verifiers to parse all branch preimages. > > - Directly forbids the ambiguous internal-node preimage condition. > > Minimal C++ implementation sketch > > This implementation checks only the minimal forbidden 64-byte shape. It does not invoke the full transaction deserializer. > > The function returns true if the 64-byte preimage is forbidden. > > static constexpr int64_t COIN = 100000000; > > static constexpr int64_t MAX_MONEY = 21000000 * COIN; > > static inline bool MoneyRange(int64_t nValue) > > { > > return nValue >= 0 && nValue <= MAX_MONEY; > > } > > static inline uint64_t ReadLE64(const unsigned char* p) > > { > > return uint64_t{p[0]} > > | (uint64_t{p[1]} << 8) > > | (uint64_t{p[2]} << 16) > > | (uint64_t{p[3]} << 24) > > | (uint64_t{p[4]} << 32) > > | (uint64_t{p[5]} << 40) > > | (uint64_t{p[6]} << 48) > > | (uint64_t{p[7]} << 56); > > } > > static bool IsForbiddenMerkleInternalNodePreimage64(const unsigned char p[64]) > > { > > // Minimal 64-byte legacy transaction shape: > > // > > // 4 bytes nVersion > > // 1 byte vin count = 0x01 > > // 36 bytes prevout > > // 1 byte scriptSig length = x > > // x bytes scriptSig > > // 4 bytes nSequence > > // 1 byte vout count = 0x01 > > // 8 bytes nValue > > // 1 byte scriptPubKey length = y > > // y bytes scriptPubKey > > // 4 bytes nLockTime > > // > > // Since the fixed overhead is 60 bytes, x + y must equal 4. > > if (p[4] != 0x01) { > > return false; > > } > > const unsigned int x = p[41]; > > switch (x) { > > case 0: > > if (p[46] != 0x01) return false; > > if (p[55] != 0x04) return false; > > break; > > case 1: > > if (p[47] != 0x01) return false; > > if (p[56] != 0x03) return false; > > break; > > case 2: > > if (p[48] != 0x01) return false; > > if (p[57] != 0x02) return false; > > break; > > case 3: > > if (p[49] != 0x01) return false; > > if (p[58] != 0x01) return false; > > break; > > case 4: > > if (p[50] != 0x01) return false; > > if (p[59] != 0x00) return false; > > break; > > default: > > return false; > > } > > const size_t value_pos = 47 + x; > > const uint64_t raw_value = ReadLE64(p + value_pos); > > if (raw_value > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { > > return false; > > } > > const int64_t nValue = static_cast<int64_t>(raw_value); > > if (!MoneyRange(nValue)) { > > return false; > > } > > return true; > > } > > static bool IsForbiddenMerkleInternalNode( > > const uint256& left, > > const uint256& right) > > { > > unsigned char p[64]; > > std::memcpy(p, left.begin(), 32); > > std::memcpy(p + 32, right.begin(), 32); > > return IsForbiddenMerkleInternalNodePreimage64(p); > > } > > A Merkle parent computation then checks the preimage before hashing: > > static uint256 ComputeMerkleParentChecked( > > const uint256& left, > > const uint256& right, > > bool& invalid) > > { > > if (IsForbiddenMerkleInternalNode(left, right)) { > > invalid = true; > > return uint256{}; > > } > > unsigned char p[64]; > > std::memcpy(p, left.begin(), 32); > > std::memcpy(p + 32, right.begin(), 32); > > return Hash(Span<const unsigned char>(p, 64)); > > } > > This is the intended minimal rule. It checks the five possible 64-byte one-input, one-output transaction layouts directly. > > Miner considerations > > Accidental violations by honest miners are expected to be rare. > > Adversarial violations are possible. An attacker may grind transaction identifiers so that two transactions, if placed as siblings in the transaction Merkle tree, form: > > txid_A || txid_B > > which encodes a forbidden minimal 64-byte transaction. > > An attacker may attempt to influence sibling placement by fee rate, package construction, direct miner submission, or transaction ordering effects. > > Therefore miners MUST check candidate block templates before mining. Miners MUST NOT rely on accidental violation probability. > > Merkle construction failure recovery > > If a candidate block template violates this rule, the miner usually does not need to discard the entire template. The violation is local to one or more internal Merkle node preimages: > > left_child_hash || right_child_hash > > A miner can usually repair the candidate block by changing transaction order so that the offending pair of child hashes no longer appears as siblings at the violating Merkle tree level. > > Recommended recovery procedure > > When Merkle root construction fails because an internal node preimage is forbidden, mining software SHOULD use the following procedure: > > - > > Record each offending internal node preimage. > > - > > Identify the transaction subtree contributing to each offending child hash. > > - > > Attempt to repair the block by shuffling transaction order while preserving consensus transaction-order constraints. > > - > > Recompute the Merkle root and re-run the internal-node preimage check. > > - > > If the shuffled template passes, mine the repaired template. > > - > > If shuffling fails repeatedly, remove one or more transactions contributing to the offending subtree and rebuild the template. > > Preserving transaction-order constraints > > A shuffle MUST NOT violate transaction dependency ordering. > > If transaction B spends an output created by transaction A in the same block, then A MUST appear before B. > > The coinbase transaction MUST remain the first transaction in the block. > > Mining software SHOULD shuffle only transactions whose relative order is not constrained by in-block dependencies, or use a randomized topological ordering of the block's transaction dependency graph. > > Simple shuffle algorithm > > A simple repair algorithm is: > > 1. Keep the coinbase fixed at index 0. > > 2. Build a dependency graph for all non-coinbase transactions. > > 3. Generate a randomized topological ordering of the graph. > > 4. Construct the Merkle tree using that ordering. > > 5. Reject the ordering if any internal node preimage is forbidden. > > 6. Retry with a new randomized topological ordering. > > This changes Merkle sibling relationships without violating in-block transaction dependencies. > > Repeated failure > > If randomized repair fails repeatedly, mining software SHOULD remove transactions contributing to the repeated offending subtree. > > A reasonable policy is: > > If Merkle construction fails after 2 independent shuffle attempts, > > remove at least one transaction from each repeatedly offending pair or subtree. > > For a bottom-level violation, the offending subtree usually corresponds to two sibling transaction identifiers: > > txid_A || txid_B > > In that case, the miner may remove either tx_A or tx_B. > > For a higher-level violation, each child hash commits to a subtree containing multiple transactions. In that case, the miner may: > > 1. Try another dependency-preserving shuffle. > > 2. If the same higher-level violation recurs, remove one transaction from one child subtree. > > 3. Prefer removing the lowest-feerate removable transaction that does not force removal of higher-feerate descendants. > > This policy does not need to identify a malicious transaction. It only needs to produce a valid block template with minimal fee loss. > > Fee impact > > The expected fee impact for honest block templates should be negligible because accidental violations are rare. > > If an adversary intentionally creates transactions that cause violations when paired, shuffling will usually defeat the attempt without fee loss. If shuffling does not repair the template, removing one or more offending transactions bounds the miner's exposure. > > The adversary's practical effect is limited to potentially causing some transactions to be omitted from a candidate block template. The rule prevents upgraded miners from mining invalid blocks, provided miners check the Merkle construction before mining. > > Relation to unupgraded miners > > Because accidental violations are rare, unupgraded miners are unlikely to encounter the rule during ordinary operation. > > However, an adversary can construct transaction pairs intended to trigger the rule under specific sibling placement. > > Unupgraded miners that do not enforce this rule may mine a block that upgraded nodes reject after activation. Low accidental probability improves deployment safety but is not a substitute for miner enforcement. > > Probability analysis > > This section estimates accidental violation probability under simplified randomness assumptions. > > Random left || right > > Assume the 64-byte internal node preimage is uniformly random. > > For the preimage to encode a minimal one-input, one-output 64-byte transaction, it must satisfy: > > vin_count = 0x01 > > scriptSig_len = x, where x ∈ {0,1,2,3,4} > > vout_count = 0x01 at the position determined by x > > scriptPubKey_len = 4 - x > > nValue ∈ [0, MAX_MONEY] > > Ignoring nValue, the structural probability is approximately: > > 5 / 256^3 > > because there are five valid (scriptSig_len, scriptPubKey_len) splits, and three one-byte constraints: > > vin_count > > vout_count > > scriptPubKey_len > > Numerically: > > 5 / 256^3 ≈ 2.980232238769531e-7 > > or approximately: > > 1 in 3,355,443 > > Including the output value money range: > > MAX_MONEY = 21,000,000 * 100,000,000 > > = 2,100,000,000,000,000 > > For a uniformly random unsigned 64-bit output value, the probability of being in range is approximately: > > (MAX_MONEY + 1) / 2^64 > > ≈ 1.1384122811097797e-4 > > Therefore the approximate probability that a random 64-byte preimage is structurally valid and has an in-range output value is: > > (5 / 256^3) * ((MAX_MONEY + 1) / 2^64) > > ≈ 3.392733219831406e-11 > > or approximately: > > 1 in 29,475,000,000 > > Random left || left > > For an odd-entry duplicated Merkle node, the preimage has the form: > > left || left > > where the first 32 bytes equal the last 32 bytes. > > Let the 32-byte half be: > > A[0..31] > > Then: > > P[0..31] = A[0..31] > > P[32..63] = A[0..31] > > For the same one-input, one-output 64-byte transaction shape: > > P[4] = 0x01 > > P[41] = scriptSig_len = x > > P[vout_count_pos] = 0x01 > > P[scriptpubkey_len_pos] = 4 - x > > Because positions after byte 31 alias positions in the first half: > > P[i] = A[i mod 32] > > The relevant positions are: > > vin_count_pos = 4 > > script_len_pos = 41 ≡ 9 mod 32 > > vout_count_pos = 46 + x ≡ 14 + x mod 32 > > scriptpubkey_len_pos = 55 + x ≡ 23 + x mod 32 > > The constraints are: > > A[4] = 0x01 > > A[9] = x > > A[14 + x] = 0x01 > > A[23 + x] = 4 - x > > For each fixed x, these are four independent one-byte constraints under the random-half model. > > Thus the structural probability is approximately: > > 5 / 256^4 > > ≈ 1.1641532182693481e-9 > > or approximately: > > 1 in 858,993,459 > > The output value begins at: > > value_pos = 47 + x > > which aliases to an 8-byte window in the random 32-byte half: > > A[15 + x .. 22 + x] > > Using the same simplified independence approximation, the probability of being in MoneyRange is approximately: > > (MAX_MONEY + 1) / 2^64 > > ≈ 1.1384122811097797e-4 > > So the approximate probability that a random left || left preimage is structurally valid and has an in-range output value is: > > (5 / 256^4) * ((MAX_MONEY + 1) / 2^64) > > ≈ 1.3252864140005492e-13 > > or approximately: > > 1 in 7,545,600,000,000 > > Block-level accidental probability > > A block with n transactions has approximately n - 1 internal Merkle nodes, plus duplicated-node cases depending on tree shape. > > Using the rough random left || right estimate: > > p ≈ 3.39e-11 > > A block with 10,000 transactions has approximate accidental violation probability: > > 1 - (1 - p)^9999 ≈ 3.39e-7 > > or roughly: > > 1 in 2,950,000 blocks > > This is a simplified estimate. Actual txids are not perfect independent random samples in all cases, duplicated nodes have lower estimated probability, and additional implementation details may reduce or alter the rate. > > The deployment-relevant conclusion is: > > Honest accidental violations should be rare. > > Adversarial violations are possible. > > Miners must enforce the rule. > > Backward compatibility > > This is a soft fork. Blocks violating the new rule were previously valid and become invalid after activation. > > Unupgraded full nodes may accept violating blocks after activation. Activation therefore requires ordinary soft-fork deployment procedures. > > Unupgraded SPV clients remain vulnerable to the legacy proof ambiguity. SPV clients must update their Merkle proof validation logic to obtain the benefit of this rule. > > Test vectors > > Test vectors should include: > > - > > A block whose transaction Merkle internal node preimages do not encode minimal 64-byte transactions. The block is valid. > > - > > A block containing a 64-byte transaction whose serialization does not appear as an internal node preimage. The block is valid. > > - > > A block where an internal node preimage encodes a minimal 64-byte transaction. The block is invalid. > > - > > A block where an odd-entry duplicated preimage h || h encodes a minimal 64-byte transaction. The block is invalid. > > - > > An SPV proof where one branch preimage encodes a minimal 64-byte transaction. The proof is rejected. > > - > > An SPV proof for a 64-byte transaction where no branch preimage encodes a minimal 64-byte transaction. The proof is accepted if otherwise valid. > > Open questions > > - > > Should the rule include only the explicit minimal 64-byte legacy transaction shape above, or should it call the full consensus transaction deserializer? > > - > > Should future transaction serialization changes be required to preserve this exact forbidden-preimage invariant? > > - > > Should pre-activation relay policy discourage transaction pairs that can form forbidden sibling preimages? > > - > > Should mining software standardize a recovery procedure for failed Merkle construction, or should this remain implementation-specific? > > - > > Should SPV proof formats include an explicit version bit indicating branch-preimage checking support? > > -- > You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. > To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. > To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/f97afcc5-54ba-4284-8e9b-e8c35c7101f6n%40googlegroups.com. -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/MuaBBWyHNVBd2i0ppWeXGlhAx8C_UqdUZctkuQ7stQeo8CudqJSWfeJHsmJLYLgXz_XZgRw5i1nTJ4wAZCsdCyQccR1Xh4IzA4QjBMVUJpU%3D%40protonmail.com. [-- Attachment #2: Type: text/html, Size: 229703 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-01 18:49 ` 'Antoine Poinsot' via Bitcoin Development Mailing List @ 2026-06-01 20:17 ` jeremy 2026-06-02 12:36 ` Greg Sanders 2026-06-05 21:34 ` 'Antoine Poinsot' via Bitcoin Development Mailing List 0 siblings, 2 replies; 11+ messages in thread From: jeremy @ 2026-06-01 20:17 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 1515 bytes --] Antoine, Rejecting nodes with any valid tx in path, without this rule, is problematic, because it _can_ be possible for an attacking miner to engineer that scenario by grinding one TXID leaf to mask a subtree, which could have major consequences. Third party malleability vulnerability to deposit / withdrawal masking is a serious bug. Worth thinking that through very carefully before recommending these mitigations. Do you have an end-to-end working example of such a mitigation that doesn't have these issues? > This is incorrect for any bridge, wallet, or deposit system that does not receive funds to a script that either burns the funds or that anyone can spend. The problem is that from the perspective of a wide variety of layer 2 protocols, you actually do want to be able to simply close out a UTXO and prove a UTXO is spent. In the current L2 protocol design space, value doesn't always flow directly along the output, the UTXO may be being used as a connector input, and the spend of that output may be making a different output available after a timeout and excluding an alternative spend. Best, Jeremy -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/45558bbd-762c-45a4-a4a1-6105d7462a8en%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 2019 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-01 20:17 ` jeremy @ 2026-06-02 12:36 ` Greg Sanders 2026-06-02 18:15 ` jeremy 2026-06-05 21:34 ` 'Antoine Poinsot' via Bitcoin Development Mailing List 1 sibling, 1 reply; 11+ messages in thread From: Greg Sanders @ 2026-06-02 12:36 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 1883 bytes --] Hi Jeremy, Why does the SPV verifier need to do additional checks? SPV implies it's simply trusting the heaviest chain. Clearly they could validate it, but I'm not seeing necessity in the security model. Greg On Monday, June 1, 2026 at 4:22:28 PM UTC-4 jeremy wrote: > Antoine, > > Rejecting nodes with any valid tx in path, without this rule, is > problematic, because it _can_ be possible for an attacking miner to > engineer that scenario by grinding one TXID leaf to mask a subtree, which > could have major consequences. Third party malleability vulnerability to > deposit / withdrawal masking is a serious bug. Worth thinking that through > very carefully before recommending these mitigations. Do you have an > end-to-end working example of such a mitigation that doesn't have these > issues? > > > This is incorrect for any bridge, wallet, or deposit system that does > not receive funds to a script that either burns the funds or that anyone > can spend. > > The problem is that from the perspective of a wide variety of layer 2 > protocols, you actually do want to be able to simply close out a UTXO and > prove a UTXO is spent. > > In the current L2 protocol design space, value doesn't always flow > directly along the output, the UTXO may be being used as a connector > input, and the spend of that output may be making a different output > available after a timeout and excluding an alternative spend. > > Best, > > Jeremy > -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/923b3d65-3472-4d7d-9b13-da44753fe5f8n%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 2556 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-02 12:36 ` Greg Sanders @ 2026-06-02 18:15 ` jeremy 2026-06-03 1:05 ` Antoine Riard 0 siblings, 1 reply; 11+ messages in thread From: jeremy @ 2026-06-02 18:15 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 2698 bytes --] They aren't really doing "additional checks" in the way you're framing it. There's just a new rule applied to existing proofs, no additional data compared to before. That new rule is *something* like given proof : ([(Hash, LEFT | RIGHT) | ODDNODE ], Transaction) then as you hash you just forbid that any LEFT | RIGHT or LEFT | LEFT is allowed to be parseable as a transaction. This forces that if a 64-byte transaction is present, it must be a leaf. This prevents the other 64 byte transaction issue because you can't embed a txid sub-tree inside the 64-byte transaction to lie to an SPV user. My estimate is that this case happens naturally like 1 in 2 million blocks. On Tuesday, June 2, 2026 at 8:40:05 AM UTC-4 Greg Sanders wrote: > Hi Jeremy, > > Why does the SPV verifier need to do additional checks? SPV implies it's > simply trusting the heaviest chain. Clearly they could validate it, but I'm > not seeing necessity in the security model. > > Greg > > On Monday, June 1, 2026 at 4:22:28 PM UTC-4 jeremy wrote: > >> Antoine, >> >> Rejecting nodes with any valid tx in path, without this rule, is >> problematic, because it _can_ be possible for an attacking miner to >> engineer that scenario by grinding one TXID leaf to mask a subtree, which >> could have major consequences. Third party malleability vulnerability to >> deposit / withdrawal masking is a serious bug. Worth thinking that through >> very carefully before recommending these mitigations. Do you have an >> end-to-end working example of such a mitigation that doesn't have these >> issues? >> >> > This is incorrect for any bridge, wallet, or deposit system that does >> not receive funds to a script that either burns the funds or that anyone >> can spend. >> >> The problem is that from the perspective of a wide variety of layer 2 >> protocols, you actually do want to be able to simply close out a UTXO and >> prove a UTXO is spent. >> >> In the current L2 protocol design space, value doesn't always flow >> directly along the output, the UTXO may be being used as a connector >> input, and the spend of that output may be making a different output >> available after a timeout and excluding an alternative spend. >> >> Best, >> >> Jeremy >> > -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/e0fa7417-ffef-492f-93d6-6c7ae6dbad6en%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 3625 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-02 18:15 ` jeremy @ 2026-06-03 1:05 ` Antoine Riard 2026-06-03 15:07 ` jeremy 0 siblings, 1 reply; 11+ messages in thread From: Antoine Riard @ 2026-06-03 1:05 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 3573 bytes --] Hi Jeremy, If I'm getting correctly your idea, this is not a removal of the "seam" in the validity space of transaction, more a reduction of the "seam" to a specific structure. That means a second-layer client would still have to implement this check to rule out tx input that might infringe it. Not sure, if it's a real design win compared to the current proposal... By the way, as a side note if we can be spared with chat-gpt generate bips, thanks you. I did (naively) believe bitcoin development philosophy was about on delivering on a trust-minmized security model. Not in fact some novel trust-sama philosopy. Best, Antoine OTS hash: e0faa568d4eef3b250b193f01c7134129abe225f7b10f345825610738c5ea1a2 Le Tuesday, June 2, 2026 à 7:20:05 PM UTC+1, jeremy a écrit : > > They aren't really doing "additional checks" in the way you're framing it. > There's just a new rule applied to existing proofs, no additional data > compared to before. That new rule is *something* like given proof : > ([(Hash, LEFT | RIGHT) | ODDNODE ], Transaction) > > then as you hash you just forbid that any LEFT | RIGHT or LEFT | LEFT is > allowed to be parseable as a transaction. > > This forces that if a 64-byte transaction is present, it must be a leaf. > > This prevents the other 64 byte transaction issue because you can't embed > a txid sub-tree inside the 64-byte transaction to lie to an SPV user. > > My estimate is that this case happens naturally like 1 in 2 million > blocks. > On Tuesday, June 2, 2026 at 8:40:05 AM UTC-4 Greg Sanders wrote: > >> Hi Jeremy, >> >> Why does the SPV verifier need to do additional checks? SPV implies it's >> simply trusting the heaviest chain. Clearly they could validate it, but I'm >> not seeing necessity in the security model. >> >> Greg >> >> On Monday, June 1, 2026 at 4:22:28 PM UTC-4 jeremy wrote: >> >>> Antoine, >>> >>> Rejecting nodes with any valid tx in path, without this rule, is >>> problematic, because it _can_ be possible for an attacking miner to >>> engineer that scenario by grinding one TXID leaf to mask a subtree, which >>> could have major consequences. Third party malleability vulnerability to >>> deposit / withdrawal masking is a serious bug. Worth thinking that through >>> very carefully before recommending these mitigations. Do you have an >>> end-to-end working example of such a mitigation that doesn't have these >>> issues? >>> >>> > This is incorrect for any bridge, wallet, or deposit system that does >>> not receive funds to a script that either burns the funds or that anyone >>> can spend. >>> >>> The problem is that from the perspective of a wide variety of layer 2 >>> protocols, you actually do want to be able to simply close out a UTXO and >>> prove a UTXO is spent. >>> >>> In the current L2 protocol design space, value doesn't always flow >>> directly along the output, the UTXO may be being used as a connector >>> input, and the spend of that output may be making a different output >>> available after a timeout and excluding an alternative spend. >>> >>> Best, >>> >>> Jeremy >>> >> -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/8c49d0d1-7f87-4e4c-9636-1e631562bc8dn%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 4698 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-03 1:05 ` Antoine Riard @ 2026-06-03 15:07 ` jeremy 0 siblings, 0 replies; 11+ messages in thread From: jeremy @ 2026-06-03 15:07 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 4617 bytes --] Correct -- it doesn't alter the validity space of transactions, just the validity space of merkle roots, which are the cause of the problem to begin with. A second layer client would implement this rule as well, and be "protected" from prefix attacks by the consensus enforced rule. ------------------------- I largely agree with your point on the use of AI... I can at least reassure you that I used AI here as a *drafting tool* and not to come up with the idea or analysis. I noted the use of AI precisely to properly discount the value of the BIP at this point, which is surely superior to trying to pass it off as acceptable for full evaluation. As a drafting tool it got several facets of the proposal incorrect, and so I edited it substantively before including... There isn't really a trust-sama in this case... Regards, Jeremy On Tuesday, June 2, 2026 at 9:16:16 PM UTC-4 Antoine Riard wrote: > Hi Jeremy, > > If I'm getting correctly your idea, this is not a removal of the "seam" in > the > validity space of transaction, more a reduction of the "seam" to a specific > structure. That means a second-layer client would still have to implement > this > check to rule out tx input that might infringe it. Not sure, if it's a real > design win compared to the current proposal... > > By the way, as a side note if we can be spared with chat-gpt generate > bips, thanks > you. I did (naively) believe bitcoin development philosophy was about on > delivering > on a trust-minmized security model. Not in fact some novel trust-sama > philosopy. > > Best, > Antoine > OTS hash: e0faa568d4eef3b250b193f01c7134129abe225f7b10f345825610738c5ea1a2 > > Le Tuesday, June 2, 2026 à 7:20:05 PM UTC+1, jeremy a écrit : > >> >> They aren't really doing "additional checks" in the way you're framing >> it. There's just a new rule applied to existing proofs, no additional data >> compared to before. That new rule is *something* like given proof : >> ([(Hash, LEFT | RIGHT) | ODDNODE ], Transaction) >> >> then as you hash you just forbid that any LEFT | RIGHT or LEFT | LEFT is >> allowed to be parseable as a transaction. >> >> This forces that if a 64-byte transaction is present, it must be a leaf. >> >> This prevents the other 64 byte transaction issue because you can't embed >> a txid sub-tree inside the 64-byte transaction to lie to an SPV user. >> >> My estimate is that this case happens naturally like 1 in 2 million >> blocks. >> On Tuesday, June 2, 2026 at 8:40:05 AM UTC-4 Greg Sanders wrote: >> >>> Hi Jeremy, >>> >>> Why does the SPV verifier need to do additional checks? SPV implies it's >>> simply trusting the heaviest chain. Clearly they could validate it, but I'm >>> not seeing necessity in the security model. >>> >>> Greg >>> >>> On Monday, June 1, 2026 at 4:22:28 PM UTC-4 jeremy wrote: >>> >>>> Antoine, >>>> >>>> Rejecting nodes with any valid tx in path, without this rule, is >>>> problematic, because it _can_ be possible for an attacking miner to >>>> engineer that scenario by grinding one TXID leaf to mask a subtree, which >>>> could have major consequences. Third party malleability vulnerability to >>>> deposit / withdrawal masking is a serious bug. Worth thinking that through >>>> very carefully before recommending these mitigations. Do you have an >>>> end-to-end working example of such a mitigation that doesn't have these >>>> issues? >>>> >>>> > This is incorrect for any bridge, wallet, or deposit system that >>>> does not receive funds to a script that either burns the funds or that >>>> anyone can spend. >>>> >>>> The problem is that from the perspective of a wide variety of layer 2 >>>> protocols, you actually do want to be able to simply close out a UTXO and >>>> prove a UTXO is spent. >>>> >>>> In the current L2 protocol design space, value doesn't always flow >>>> directly along the output, the UTXO may be being used as a connector >>>> input, and the spend of that output may be making a different output >>>> available after a timeout and excluding an alternative spend. >>>> >>>> Best, >>>> >>>> Jeremy >>>> >>> -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/619e6297-ffe9-4288-801a-53e115dc720en%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 6005 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-01 20:17 ` jeremy 2026-06-02 12:36 ` Greg Sanders @ 2026-06-05 21:34 ` 'Antoine Poinsot' via Bitcoin Development Mailing List 2026-06-09 16:28 ` jeremy 1 sibling, 1 reply; 11+ messages in thread From: 'Antoine Poinsot' via Bitcoin Development Mailing List @ 2026-06-05 21:34 UTC (permalink / raw) To: jeremy; +Cc: Bitcoin Development Mailing List [-- Attachment #1: Type: text/plain, Size: 3644 bytes --] Jeremy, Good point. I think this could be more straightforwardly restated like so. "Invalidating 64-byte transactions only fixes malleability in one direction: confusing a leaf for a node. However malleability in the other direction may also be exploited by grinding an inner node to trick an SPV verifier that accepts proofs for 64-byte transactions." So you are correct that BIP 54 should only claim that invalidating 64-byte transactions addresses the issue with no change to SPV verifiers for those SPV verifiers that expect proofs for transactions that cannot be 64-byte (i.e. checking for deposits to any interesting scripts, or more generally any transaction through which value actually flows). In fact this is also true for any SPV verifier that expects proofs for transactions which could be 64-byte, as long as it is computationally infeasible to grind those transactions. Interestingly, this is true of the connector output example you gave! If 64-byte transactions were invalid and a miner wanted to attack a protocol by faking an SPV proof that a specific connector output was spent, it would need to grind over 256 bits for any inner node to match that prevout. So really the attacking miner would only ever be able to fake a proof for a 64-byte transaction that anybody else would be able to create anyways. So i think the point stands that preventing malleability only in one direction (by invalidating 64-byte transactions) is sufficient, and does not require SPV verifiers to do anything. Do you have a counter-example? Antoine On Monday, June 1st, 2026 at 4:22 PM, jeremy <jeremy.l.rubin@gmail.com> wrote: > Antoine, > > Rejecting nodes with any valid tx in path, without this rule, is problematic, because it _can_ be possible for an attacking miner to engineer that scenario by grinding one TXID leaf to mask a subtree, which could have major consequences. Third party malleability vulnerability to deposit / withdrawal masking is a serious bug. Worth thinking that through very carefully before recommending these mitigations. Do you have an end-to-end working example of such a mitigation that doesn't have these issues? > >> This is incorrect for any bridge, wallet, or deposit system that does not receive funds to a script that either burns the funds or that anyone can spend. > > The problem is that from the perspective of a wide variety of layer 2 protocols, you actually do want to be able to simply close out a UTXO and prove a UTXO is spent. > > In the current L2 protocol design space, value doesn't always flow directly along the output, the UTXO may be being used as a connector input, and the spend of that output may be making a different output available after a timeout and excluding an alternative spend. > > Best, > > Jeremy > > -- > You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. > To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. > To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/45558bbd-762c-45a4-a4a1-6105d7462a8en%40googlegroups.com. -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/zsUBtB2e_nQ-rup8cdIacq139Y1FznGRLQfq8XQ2lM-6SZNk3Kfucj2pxvX0YQ0QW1G2liAhenj8xYBFGqvzGLvtwZYFE5r1Xo2Y91O_Mz8%3D%40protonmail.com. [-- Attachment #2: Type: text/html, Size: 6616 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-05 21:34 ` 'Antoine Poinsot' via Bitcoin Development Mailing List @ 2026-06-09 16:28 ` jeremy 2026-06-09 16:37 ` jeremy 0 siblings, 1 reply; 11+ messages in thread From: jeremy @ 2026-06-09 16:28 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 5583 bytes --] Well, it's definitely possible to grind s.t. you have a higher up node that _looks_ like a transaction. It would be grinding an interior node that has the desired shape on the left (5 bytes) and on the right (depends what you're grinding for, but it's mostly the length fields and script that need to be specific). You can then in theory have a proof system that is tricked into thinking a UTXO exists that does not actually exist, and it might think it's spendable in a given block. E.g., if the script is <H> CLTV, this would be provably not spent until block H. And the SPV proof would be admitted. then you'd have proof of tx + proof based on script of not until H time. What *could* something like this be used for? Let's say some sort of "proof of mutex of miner claim" -- many things can be offered to a miner conditioned on them using a particular height locked UTXO to claim it. Since they can only spend the utxo one way, they can only pick one of the offers and the others can expire. E.g.: Faked Output X: H w/ <H> CLTV TR{Nums, {<H+1> CLTV <PK> CHECKSIG, <H> CLTV <MuSig Federation> CHECKSIG}} + Presigned via MuSig spending X using SIGHASH_NONE to allow late binding of the miner's preferred destination (or not SIGHASH_NONE and an OP_TRUE) ; or if GETINPUT opcode existed: TR{Nums, {<H+1> CLTV <PK> CHECKSIG, <H> CLTV 1 GETINPUT <X> EQUAL}} ---------- As you note, it won't be spending a known output, because of the hash boundaries. But I can still think of protocol applications where creating a false connector could be harmful. ------------ The rule I propose mitigates this issue because it would be disallowed to have any deserializable txn as an interior node. On Friday, June 5, 2026 at 5:39:49 PM UTC-4 Antoine Poinsot wrote: > Jeremy, > > Good point. I think this could be more straightforwardly restated like so. "Invalidating > 64-byte transactions only fixes malleability in one direction: confusing a > leaf for a node. However malleability in the other direction may also be > exploited by grinding an inner node to trick an SPV verifier that accepts > proofs for 64-byte transactions." > > So you are correct that BIP 54 should only claim that invalidating 64-byte > transactions addresses the issue with no change to SPV verifiers for those > SPV verifiers that expect proofs for transactions that cannot be 64-byte > (i.e. checking for deposits to any interesting scripts, or more generally > any transaction through which value actually flows). > > In fact this is also true for any SPV verifier that expects proofs for > transactions which could be 64-byte, as long as it is computationally > infeasible to grind those transactions. Interestingly, this is true of the > connector output example you gave! If 64-byte transactions were invalid and > a miner wanted to attack a protocol by faking an SPV proof that a specific > connector output was spent, it would need to grind over 256 bits for any > inner node to match that prevout. So really the attacking miner would only > ever be able to fake a proof for a 64-byte transaction that anybody else > would be able to create anyways. > > So i think the point stands that preventing malleability only in one > direction (by invalidating 64-byte transactions) is sufficient, and does > not require SPV verifiers to do anything. Do you have a counter-example? > > Antoine > On Monday, June 1st, 2026 at 4:22 PM, jeremy <jeremy....@gmail.com> wrote: > > Antoine, > > Rejecting nodes with any valid tx in path, without this rule, is > problematic, because it _can_ be possible for an attacking miner to > engineer that scenario by grinding one TXID leaf to mask a subtree, which > could have major consequences. Third party malleability vulnerability to > deposit / withdrawal masking is a serious bug. Worth thinking that through > very carefully before recommending these mitigations. Do you have an > end-to-end working example of such a mitigation that doesn't have these > issues? > > > This is incorrect for any bridge, wallet, or deposit system that does > not receive funds to a script that either burns the funds or that anyone > can spend. > > The problem is that from the perspective of a wide variety of layer 2 > protocols, you actually do want to be able to simply close out a UTXO and > prove a UTXO is spent. > > In the current L2 protocol design space, value doesn't always flow > directly along the output, the UTXO may be being used as a connector input, > and the spend of that output may be making a different output available > after a timeout and excluding an alternative spend. > > Best, > > Jeremy > > -- > > You received this message because you are subscribed to the Google Groups > "Bitcoin Development Mailing List" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to bitcoindev+...@googlegroups.com. > > To view this discussion visit > https://groups.google.com/d/msgid/bitcoindev/45558bbd-762c-45a4-a4a1-6105d7462a8en%40googlegroups.com > . > > > -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/75d0e9bd-3bdb-444b-9087-f8a082183253n%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 9182 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-09 16:28 ` jeremy @ 2026-06-09 16:37 ` jeremy 0 siblings, 0 replies; 11+ messages in thread From: jeremy @ 2026-06-09 16:37 UTC (permalink / raw) To: Bitcoin Development Mailing List [-- Attachment #1.1: Type: text/plain, Size: 6083 bytes --] I also want to make an opinionated point here, separate from the technical message above: These are esoteric use cases. This is precisely why it's important to get right. The edge of understanding is where future protocols end up with exploitable vulnerabilities. On Tuesday, June 9, 2026 at 12:30:31 PM UTC-4 jeremy wrote: > Well, it's definitely possible to grind s.t. you have a higher up node > that _looks_ like a transaction. It would be grinding an interior node > that has the desired shape on the left (5 bytes) and on the right (depends > what you're grinding for, but it's mostly the length fields and script that > need to be specific). > > You can then in theory have a proof system that is tricked into thinking a > UTXO exists that does not actually exist, and it might think it's spendable > in a given block. > > E.g., if the script is <H> CLTV, this would be provably not spent until > block H. > > And the SPV proof would be admitted. > > > then you'd have proof of tx + proof based on script of not until H time. > > > What *could* something like this be used for? > > > Let's say some sort of "proof of mutex of miner claim" -- many things can > be offered to a miner conditioned on them using a particular height locked > UTXO to claim it. > > Since they can only spend the utxo one way, they can only pick one of the > offers and the others can expire. E.g.: > > Faked Output X: H w/ <H> CLTV > > TR{Nums, {<H+1> CLTV <PK> CHECKSIG, <H> CLTV <MuSig Federation> CHECKSIG}} > + Presigned via MuSig spending X using SIGHASH_NONE to allow late binding > of the miner's preferred destination (or not SIGHASH_NONE and an OP_TRUE) ; > > or if GETINPUT opcode existed: > TR{Nums, {<H+1> CLTV <PK> CHECKSIG, <H> CLTV 1 GETINPUT <X> EQUAL}} > > ---------- > > As you note, it won't be spending a known output, because of the hash > boundaries. But I can still think of protocol applications where creating a > false connector could be harmful. > > > ------------ > > > The rule I propose mitigates this issue because it would be disallowed to > have any deserializable txn as an interior node. > On Friday, June 5, 2026 at 5:39:49 PM UTC-4 Antoine Poinsot wrote: > >> Jeremy, >> >> Good point. I think this could be more straightforwardly restated like >> so. "Invalidating 64-byte transactions only fixes malleability in one >> direction: confusing a leaf for a node. However malleability in the other >> direction may also be exploited by grinding an inner node to trick an SPV >> verifier that accepts proofs for 64-byte transactions." >> >> So you are correct that BIP 54 should only claim that invalidating >> 64-byte transactions addresses the issue with no change to SPV verifiers >> for those SPV verifiers that expect proofs for transactions that cannot be >> 64-byte (i.e. checking for deposits to any interesting scripts, or more >> generally any transaction through which value actually flows). >> >> In fact this is also true for any SPV verifier that expects proofs for >> transactions which could be 64-byte, as long as it is computationally >> infeasible to grind those transactions. Interestingly, this is true of the >> connector output example you gave! If 64-byte transactions were invalid and >> a miner wanted to attack a protocol by faking an SPV proof that a specific >> connector output was spent, it would need to grind over 256 bits for any >> inner node to match that prevout. So really the attacking miner would only >> ever be able to fake a proof for a 64-byte transaction that anybody else >> would be able to create anyways. >> >> So i think the point stands that preventing malleability only in one >> direction (by invalidating 64-byte transactions) is sufficient, and does >> not require SPV verifiers to do anything. Do you have a counter-example? >> >> Antoine >> On Monday, June 1st, 2026 at 4:22 PM, jeremy <jeremy....@gmail.com> >> wrote: >> >> Antoine, >> >> Rejecting nodes with any valid tx in path, without this rule, is >> problematic, because it _can_ be possible for an attacking miner to >> engineer that scenario by grinding one TXID leaf to mask a subtree, which >> could have major consequences. Third party malleability vulnerability to >> deposit / withdrawal masking is a serious bug. Worth thinking that through >> very carefully before recommending these mitigations. Do you have an >> end-to-end working example of such a mitigation that doesn't have these >> issues? >> >> > This is incorrect for any bridge, wallet, or deposit system that does >> not receive funds to a script that either burns the funds or that anyone >> can spend. >> >> The problem is that from the perspective of a wide variety of layer 2 >> protocols, you actually do want to be able to simply close out a UTXO and >> prove a UTXO is spent. >> >> In the current L2 protocol design space, value doesn't always flow >> directly along the output, the UTXO may be being used as a connector input, >> and the spend of that output may be making a different output available >> after a timeout and excluding an alternative spend. >> >> Best, >> >> Jeremy >> >> -- >> >> You received this message because you are subscribed to the Google Groups >> "Bitcoin Development Mailing List" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to bitcoindev+...@googlegroups.com. >> >> To view this discussion visit >> https://groups.google.com/d/msgid/bitcoindev/45558bbd-762c-45a4-a4a1-6105d7462a8en%40googlegroups.com >> . >> >> >> -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/178b550d-841f-43d6-8c4f-67e909cc17bfn%40googlegroups.com. [-- Attachment #1.2: Type: text/html, Size: 9695 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions 2026-06-01 17:46 [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions jeremy 2026-06-01 18:49 ` 'Antoine Poinsot' via Bitcoin Development Mailing List @ 2026-06-09 18:30 ` Matt Corallo 1 sibling, 0 replies; 11+ messages in thread From: Matt Corallo @ 2026-06-09 18:30 UTC (permalink / raw) To: jeremy, Bitcoin Development Mailing List Hey Jeremy, While this is certainly an interesting alternative mitigation against some attacks, the fact that it implies that miners have to change their block-building software to handle potential malicious transaction selection which can cause them to create invalid blocks seems to make this a total nonstarter. Not breaking existing deployed software, including miners who in some cases have custom block-building logic is obviously an important goal for soft forks. Given there's no (known) use of 64-byte transactions anywhere (and they've been non-standard for a longggg time!) its hard to argue banning 64-byte transactions would have any similar issues. As others have noted, banning 64-byte transactions seems to be a more complete fix as well. Matt On 6/1/26 1:46 PM, jeremy wrote: > Esteemed Colleagues, > > As a result of some of my research on 64-byte transactions, I'd like to discuss an alternative soft > fork proposal that preserves the ability to encode 64-byte transactions while offering protection to > SPV users (who must make a small patch to validate the path property). > > The rule, stated simply, is: > > A block is invalid if any Merkle Tree 64-byte preimage has the exact byte structure of a minimal > one-input, one-output, witness stripped transaction. > > [With the miracle of GPT,] I've drafted a relatively complete BIP for discussion. > > Happy International Children's Day, > > Jeremy > > p.s. I will later propose potentially a couple other mitigations separately, for discussion as well. > > ---------------------------------------------------------------------------------------------------- > > BIP: TBD > Layer: Consensus (soft fork) > Title: Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions > Author: TBD > Status: Draft > Type: Standards Track > Created: 2026-06-01 > License: BSD-2-Clause > > *Abstract* > > This document specifies a consensus rule that invalidates a block if any transaction Merkle tree > internal node preimage encodes a minimal 64-byte transaction. > > For each internal Merkle node, Bitcoin computes: > > parent = SHA256d(left || right) > > * > * > > where leftand rightare 32-byte hashes. The 64-byte string left || rightis the internal node preimage. > > After activation, a block is invalid if any such 64-byte preimage has the exact byte structure of a > minimal one-input, one-output, non-witness transaction. > > This prevents a 64-byte transaction serialization from being malleated into an internal Merkle node > preimage in SPV transaction inclusion proofs. It does not make 64-byte transactions invalid in general. > > *Motivation* > > Bitcoin transaction identifiers and transaction Merkle internal nodes are both computed with double > SHA256: > > txid = SHA256d(serialized_transaction) > > parent = SHA256d(left_child_hash || right_child_hash) > > * > * > > If a valid transaction serialization is exactly 64 bytes, the same byte string can also be > interpreted as the concatenation of two 32-byte Merkle child hashes: > > serialized_transaction = left_child_hash || right_child_hash > > * > * > > This creates an ambiguity between a transaction leaf preimage and an internal node preimage. > > An SPV verifier that accepts a Merkle proof without authenticating the full tree shape can be made > to accept a proof terminating at an internal node rather than at an actual transaction leaf. > > This proposal removes that ambiguity by forbidding Merkle internal node preimages that have the only > practical 64-byte transaction encoding shape. > > *SegWit and transaction identifiers* > > Since SegWit activation, Bitcoin transactions have two related identifiers: > > txid = SHA256d(legacy serialization) > > wtxid = SHA256d(witness serialization) > > * > * > > The distinction is important for understanding this proposal. > > A SegWit transaction is serialized on the wire as: > > nVersion > > marker > > flag > > vin > > vout > > witness > > nLockTime > > * > * > > where: > > marker = 0x00 > > flag = 0x01 > > * > * > > The marker and flag bytes indicate that witness data is present. > > However, the transaction identifier (txid) is not computed from this witness serialization. Instead, > the txidis computed from the legacy serialization: > > nVersion > > vin > > vout > > nLockTime > > * > * > > with the marker, flag, and witness fields omitted. > > Therefore: > > txid = SHA256d(non-witness serialization) > > * > * > > while: > > wtxid = SHA256d(full witness serialization) > > * > * > > The transaction Merkle root committed in the block header is built from transaction identifiers > (txids), not witness transaction identifiers (wtxids). > > Consequently: > > Merkle root = Merkle(txid_0, txid_1, ..., txid_n) > > * > * > > and not: > > Merkle(wtxid_0, wtxid_1, ..., wtxid_n) > > * > * > > This means that the marker byte (0x00), flag byte (0x01), and witness data never appear in the > transaction Merkle tree committed by the block header. > > SegWit does define a separate witness Merkle tree whose root is committed through the coinbase > witness commitment, but that witness Merkle tree is distinct from the transaction Merkle tree > discussed in this proposal. > > As a result, the ambiguity addressed by this proposal concerns only transaction identifiers (txids) > and the transaction Merkle root. The SegWit marker and flag bytes are irrelevant to the transaction > Merkle root because they are excluded from txidserialization. > > *Minimal 64-byte transaction shape* > > This proposal is concerned with the serialization used to compute a transaction's txid. > > For legacy transactions, and for SegWit transactions when computing the txid, the serialization > format is: > > 4 bytes nVersion > > 1 byte vin count = 0x01 > > 36 bytes prevout > > 1 byte scriptSig length = x > > x bytes scriptSig > > 4 bytes nSequence > > 1 byte vout count = 0x01 > > 8 bytes nValue > > 1 byte scriptPubKey length = y > > y bytes scriptPubKey > > 4 bytes nLockTime > > * > * > > Notably, this serialization does not include: > > marker > > flag > > witness stack > > * > * > > because those fields are excluded from txidcomputation. > > The fixed overhead is: > > 4 + 1 + 36 + 1 + 4 + 1 + 8 + 1 + 4 = 60 bytes > > * > * > > Therefore, for total serialized size 64: > > x + y = 4 > > * > * > > There are exactly five possible script-length splits: > > scriptSig length scriptPubKey length > > 0 4 > > 1 3 > > 2 2 > > 3 1 > > 4 0 > > * > * > > This proposal defines a forbidden Merkle internal node preimage as a 64-byte byte string satisfying > one of those five layouts and whose single output value is in the consensus money range. > > *Specification* > > After activation, a block is invalid if any transaction Merkle internal node preimage encodes a > minimal 64-byte transaction. > > For every internal Merkle parent computation in the transaction Merkle tree: > > parent = SHA256d(left || right) > > * > * > > where leftand rightare 32-byte child hashes, define: > > P = left || right > > * > * > > The block is invalid if Psatisfies all of the following: > > 1. > > P[4] == 0x01. > > 2. > > P[41]is one of 0, 1, 2, 3, 4. > > 3. > > Let x = P[41]. > > 4. > > Let sequence_pos = 42 + x. > > 5. > > Let vout_count_pos = sequence_pos + 4. > > 6. > > Let value_pos = vout_count_pos + 1. > > 7. > > Let scriptpubkey_len_pos = value_pos + 8. > > 8. > > P[vout_count_pos] == 0x01. > > 9. > > P[scriptpubkey_len_pos] == 4 - x. > > 10. > > Let locktime_pos = scriptpubkey_len_pos + 1 + (4 - x). > > 11. > > locktime_pos + 4 == 64. > > 12. > > The 8-byte little-endian integer at P[value_pos..value_pos+7]is in MoneyRange. > > Equivalently, the forbidden preimage is a 64-byte serialization of a one-input, one-output, non- > witness transaction with single-byte CompactSize counts and script lengths, where the two script > lengths sum to 4 and the output value is in range. > > For clarity, "non-witness transaction" here refers to the serialization used for txidcomputation. > Even for SegWit transactions, the transaction Merkle tree uses txids, so the marker byte, flag byte, > and witness data are excluded. > > This rule applies to every transaction Merkle internal node used to compute the block header's > transaction Merkle root. > > *Odd-entry duplication* > > If a Merkle level has an odd number of entries, Bitcoin duplicates the final hash: > > parent = SHA256d(last || last) > > * > * > > The preimage: > > last || last > > * > * > > MUST be checked by the same rule. > > *SPV verification rule* > > An SPV verifier relying on this soft fork MUST reject a Merkle proof if any branch preimage in the > proof encodes a minimal 64-byte transaction under the predicate above. > > For each branch step, the verifier knows: > > 1. > > The current hash. > > 2. > > The sibling hash. > > 3. > > The branch direction. > > It reconstructs: > > P = left_child_hash || right_child_hash > > * > * > > The verifier MUST check: > > IsForbiddenMerkleInternalNodePreimage(P) == false > > * > * > > for every branch preimage in the proof. > > If any branch preimage passes the forbidden-preimage predicate, the proof MUST be rejected. > > The verifier still performs the ordinary Merkle path computation and block header proof-of-work > validation. > > *Rationale* > > The known 64-byte transaction SPV malleability issue requires a byte string that is both: > > a valid 64-byte transaction serialization > > * > * > > and: > > a transaction Merkle internal node preimage > > * > * > > This proposal forbids that overlap at the Merkle internal node boundary. > > The rule is narrower than invalidating all 64-byte transactions. A 64-byte transaction remains valid > unless its exact serialization appears as a transaction Merkle internal node preimage in the same > block's transaction Merkle tree. > > The rule also avoids adding a general transaction validity rule that exists only to protect Merkle > proof semantics. > > *Why SegWit does not eliminate the ambiguity* > > It is sometimes assumed that SegWit automatically removes this ambiguity because SegWit transactions > contain the marker and flag bytes: > > 00 01 > > * > * > > However, the ambiguity exists at the txidlayer, not at the witness-serialization layer. > > The transaction Merkle root in the block header is computed from txids, and txidsare computed from > the serialization that excludes: > > marker > > flag > > witness > > * > * > > Therefore the relevant byte string remains: > > nVersion > > vin > > vout > > nLockTime > > * > * > > exactly as before SegWit. > > The witness serialization affects the wtxid, but the block header's transaction Merkle root does not > commit to wtxids. > > As a result, the existence of the SegWit marker and flag bytes does not prevent a txidpreimage from > having the same byte structure as a Merkle internal node preimage. > > The ambiguity addressed by this proposal therefore remains relevant in the SegWit era. > > *Contrast with a 64-byte transaction invalidity rule* > > A direct alternative is: > > A transaction is invalid if its serialized size is exactly 64 bytes. > > * > * > > That rule has several advantages: > > 1. > > It is simple to specify. > > 2. > > It is simple for SPV verifiers to implement. > > 3. > > It removes the original ambiguity by eliminating all valid 64-byte transaction leaves. > > However, it is not correct to describe that rule as automatically fixing all light clients. > > A 64-byte transaction invalidity rule protects an SPV verifier only if the verifier enforces the new > rule when interpreting the claimed transaction. Existing or application-specific SPV verifiers that > merely receive a byte string and a Merkle branch may remain vulnerable if they do not parse the > claimed transaction and reject exactly-64-byte transaction serializations. > > More generally, a consensus rule invalidating 64-byte transactions does not prevent arbitrary > internal node preimages from existing. It only prevents those preimages from being valid Bitcoin > transactions under upgraded consensus rules. A bridge, wallet, or deposit system that accepts SPV- > style proofs but performs incomplete transaction parsing may still be induced to treat an internal > node preimage as an application-level event. > > For example, suppose an application-level SPV verifier treats a proved byte string as a "deposit" if > some field inside the alleged transaction matches a registered deposit address, deposit script, or > deposit commitment, but does not fully enforce the upgraded transaction-validity rule. An attacker > may be able to grind child hashes so that: > > left_child_hash || right_child_hash > > * > * > > has bytes that the application interprets as a deposit transaction or deposit commitment. In some > systems, the attacker may also be able to choose or register deposit data that matches bytes already > present in the left-hand side of an internal node preimage. > > This is not a failure of upgraded full-node consensus. It is a failure of the assumption that > changing full-node transaction validity automatically upgrades every SPV verifier and every bridge, > wallet, or application that consumes SPV-style proofs. > > Therefore, both approaches require light-client changes: > > 64-byte transaction invalidity: > > Light clients must reject claimed 64-byte transaction serializations. > > * > * > > Merkle-internal-node preimage invalidity: > > Light clients must reject proofs containing forbidden internal branch preimages. > > * > * > > The 64-byte transaction invalidity rule is simpler for light clients that correctly implement it, > but it is broader at the transaction layer. This proposal places the rule at the Merkle ambiguity > boundary and preserves 64-byte transactions generally. > > In summary: > > 64-byte transaction invalidity: > > - Simpler SPV rule when implemented correctly. > > - Broader transaction validity change. > > - Invalidates all 64-byte transactions. > > - Does not automatically fix SPV applications that fail to enforce the new rule. > > * > * > > Merkle-internal-node preimage invalidity: > > - Preserves 64-byte transactions generally. > > - Places the rule at the Merkle ambiguity boundary. > > - Requires SPV verifiers to parse all branch preimages. > > - Directly forbids the ambiguous internal-node preimage condition. > > * > Minimal C++ implementation sketch* > > This implementation checks only the minimal forbidden 64-byte shape. It does not invoke the full > transaction deserializer. > > The function returns trueif the 64-byte preimage is forbidden. > > static constexpr int64_t COIN = 100000000; > > static constexpr int64_t MAX_MONEY = 21000000 * COIN; > > * > * > > static inline bool MoneyRange(int64_t nValue) > > { > > return nValue >= 0 && nValue <= MAX_MONEY; > > } > > * > * > > static inline uint64_t ReadLE64(const unsigned char* p) > > { > > return uint64_t{p[0]} > > | (uint64_t{p[1]} << 8) > > | (uint64_t{p[2]} << 16) > > | (uint64_t{p[3]} << 24) > > | (uint64_t{p[4]} << 32) > > | (uint64_t{p[5]} << 40) > > | (uint64_t{p[6]} << 48) > > | (uint64_t{p[7]} << 56); > > } > > * > * > > static bool IsForbiddenMerkleInternalNodePreimage64(const unsigned char p[64]) > > { > > // Minimal 64-byte legacy transaction shape: > > // > > // 4 bytes nVersion > > // 1 byte vin count = 0x01 > > // 36 bytes prevout > > // 1 byte scriptSig length = x > > // x bytes scriptSig > > // 4 bytes nSequence > > // 1 byte vout count = 0x01 > > // 8 bytes nValue > > // 1 byte scriptPubKey length = y > > // y bytes scriptPubKey > > // 4 bytes nLockTime > > // > > // Since the fixed overhead is 60 bytes, x + y must equal 4. > > * > * > > if (p[4] != 0x01) { > > return false; > > } > > * > * > > const unsigned int x = p[41]; > > * > * > > switch (x) { > > case 0: > > if (p[46] != 0x01) return false; > > if (p[55] != 0x04) return false; > > break; > > * > * > > case 1: > > if (p[47] != 0x01) return false; > > if (p[56] != 0x03) return false; > > break; > > * > * > > case 2: > > if (p[48] != 0x01) return false; > > if (p[57] != 0x02) return false; > > break; > > * > * > > case 3: > > if (p[49] != 0x01) return false; > > if (p[58] != 0x01) return false; > > break; > > * > * > > case 4: > > if (p[50] != 0x01) return false; > > if (p[59] != 0x00) return false; > > break; > > * > * > > default: > > return false; > > } > > * > * > > const size_t value_pos = 47 + x; > > const uint64_t raw_value = ReadLE64(p + value_pos); > > * > * > > if (raw_value > static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { > > return false; > > } > > * > * > > const int64_t nValue = static_cast<int64_t>(raw_value); > > * > * > > if (!MoneyRange(nValue)) { > > return false; > > } > > * > * > > return true; > > } > > * > * > > static bool IsForbiddenMerkleInternalNode( > > const uint256& left, > > const uint256& right) > > { > > unsigned char p[64]; > > * > * > > std::memcpy(p, left.begin(), 32); > > std::memcpy(p + 32, right.begin(), 32); > > * > * > > return IsForbiddenMerkleInternalNodePreimage64(p); > > } > > * > * > > A Merkle parent computation then checks the preimage before hashing: > > static uint256 ComputeMerkleParentChecked( > > const uint256& left, > > const uint256& right, > > bool& invalid) > > { > > if (IsForbiddenMerkleInternalNode(left, right)) { > > invalid = true; > > return uint256{}; > > } > > * > * > > unsigned char p[64]; > > std::memcpy(p, left.begin(), 32); > > std::memcpy(p + 32, right.begin(), 32); > > * > * > > return Hash(Span<const unsigned char>(p, 64)); > > } > > * > * > > This is the intended minimal rule. It checks the five possible 64-byte one-input, one-output > transaction layouts directly. > > *Miner considerations* > > Accidental violations by honest miners are expected to be rare. > > Adversarial violations are possible. An attacker may grind transaction identifiers so that two > transactions, if placed as siblings in the transaction Merkle tree, form: > > txid_A || txid_B > > * > * > > which encodes a forbidden minimal 64-byte transaction. > > An attacker may attempt to influence sibling placement by fee rate, package construction, direct > miner submission, or transaction ordering effects. > > Therefore miners MUST check candidate block templates before mining. Miners MUST NOT rely on > accidental violation probability. > > *Merkle construction failure recovery* > > If a candidate block template violates this rule, the miner usually does not need to discard the > entire template. The violation is local to one or more internal Merkle node preimages: > > left_child_hash || right_child_hash > > * > * > > A miner can usually repair the candidate block by changing transaction order so that the offending > pair of child hashes no longer appears as siblings at the violating Merkle tree level. > > *Recommended recovery procedure* > > When Merkle root construction fails because an internal node preimage is forbidden, mining software > SHOULD use the following procedure: > > 1. > > Record each offending internal node preimage. > > 2. > > Identify the transaction subtree contributing to each offending child hash. > > 3. > > Attempt to repair the block by shuffling transaction order while preserving consensus > transaction-order constraints. > > 4. > > Recompute the Merkle root and re-run the internal-node preimage check. > > 5. > > If the shuffled template passes, mine the repaired template. > > 6. > > If shuffling fails repeatedly, remove one or more transactions contributing to the offending > subtree and rebuild the template. > > *Preserving transaction-order constraints* > > A shuffle MUST NOT violate transaction dependency ordering. > > If transaction Bspends an output created by transaction Ain the same block, then AMUST appear before B. > > The coinbase transaction MUST remain the first transaction in the block. > > Mining software SHOULD shuffle only transactions whose relative order is not constrained by in-block > dependencies, or use a randomized topological ordering of the block's transaction dependency graph. > > *Simple shuffle algorithm* > > A simple repair algorithm is: > > 1. Keep the coinbase fixed at index 0. > > 2. Build a dependency graph for all non-coinbase transactions. > > 3. Generate a randomized topological ordering of the graph. > > 4. Construct the Merkle tree using that ordering. > > 5. Reject the ordering if any internal node preimage is forbidden. > > 6. Retry with a new randomized topological ordering. > > * > * > > This changes Merkle sibling relationships without violating in-block transaction dependencies. > > *Repeated failure* > > If randomized repair fails repeatedly, mining software SHOULD remove transactions contributing to > the repeated offending subtree. > > A reasonable policy is: > > If Merkle construction fails after 2 independent shuffle attempts, > > remove at least one transaction from each repeatedly offending pair or subtree. > > * > * > > For a bottom-level violation, the offending subtree usually corresponds to two sibling transaction > identifiers: > > txid_A || txid_B > > * > * > > In that case, the miner may remove either tx_Aor tx_B. > > For a higher-level violation, each child hash commits to a subtree containing multiple transactions. > In that case, the miner may: > > 1. Try another dependency-preserving shuffle. > > 2. If the same higher-level violation recurs, remove one transaction from one child subtree. > > 3. Prefer removing the lowest-feerate removable transaction that does not force removal of higher- > feerate descendants. > > * > * > > This policy does not need to identify a malicious transaction. It only needs to produce a valid > block template with minimal fee loss. > > *Fee impact* > > The expected fee impact for honest block templates should be negligible because accidental > violations are rare. > > If an adversary intentionally creates transactions that cause violations when paired, shuffling will > usually defeat the attempt without fee loss. If shuffling does not repair the template, removing one > or more offending transactions bounds the miner's exposure. > > The adversary's practical effect is limited to potentially causing some transactions to be omitted > from a candidate block template. The rule prevents upgraded miners from mining invalid blocks, > provided miners check the Merkle construction before mining. > > *Relation to unupgraded miners* > > Because accidental violations are rare, unupgraded miners are unlikely to encounter the rule during > ordinary operation. > > However, an adversary can construct transaction pairs intended to trigger the rule under specific > sibling placement. > > Unupgraded miners that do not enforce this rule may mine a block that upgraded nodes reject after > activation. Low accidental probability improves deployment safety but is not a substitute for miner > enforcement. > > *Probability analysis* > > This section estimates accidental violation probability under simplified randomness assumptions. > > *Random left || right* > > Assume the 64-byte internal node preimage is uniformly random. > > For the preimage to encode a minimal one-input, one-output 64-byte transaction, it must satisfy: > > vin_count = 0x01 > > scriptSig_len = x, where x ∈ {0,1,2,3,4} > > vout_count = 0x01 at the position determined by x > > scriptPubKey_len = 4 - x > > nValue ∈ [0, MAX_MONEY] > > * > * > > Ignoring nValue, the structural probability is approximately: > > 5 / 256^3 > > * > * > > because there are five valid (scriptSig_len, scriptPubKey_len)splits, and three one-byte constraints: > > vin_count > > vout_count > > scriptPubKey_len > > * > * > > Numerically: > > 5 / 256^3 ≈ 2.980232238769531e-7 > > * > * > > or approximately: > > 1 in 3,355,443 > > * > * > > Including the output value money range: > > MAX_MONEY = 21,000,000 * 100,000,000 > > = 2,100,000,000,000,000 > > * > * > > For a uniformly random unsigned 64-bit output value, the probability of being in range is approximately: > > (MAX_MONEY + 1) / 2^64 > > ≈ 1.1384122811097797e-4 > > * > * > > Therefore the approximate probability that a random 64-byte preimage is structurally valid and has > an in-range output value is: > > (5 / 256^3) * ((MAX_MONEY + 1) / 2^64) > > ≈ 3.392733219831406e-11 > > * > * > > or approximately: > > 1 in 29,475,000,000 > > * > Random left || left* > > For an odd-entry duplicated Merkle node, the preimage has the form: > > left || left > > * > * > > where the first 32 bytes equal the last 32 bytes. > > Let the 32-byte half be: > > A[0..31] > > * > * > > Then: > > P[0..31] = A[0..31] > > P[32..63] = A[0..31] > > * > * > > For the same one-input, one-output 64-byte transaction shape: > > P[4] = 0x01 > > P[41] = scriptSig_len = x > > P[vout_count_pos] = 0x01 > > P[scriptpubkey_len_pos] = 4 - x > > * > * > > Because positions after byte 31 alias positions in the first half: > > P[i] = A[i mod 32] > > * > * > > The relevant positions are: > > vin_count_pos = 4 > > script_len_pos = 41 ≡ 9 mod 32 > > vout_count_pos = 46 + x ≡ 14 + x mod 32 > > scriptpubkey_len_pos = 55 + x ≡ 23 + x mod 32 > > * > * > > The constraints are: > > A[4] = 0x01 > > A[9] = x > > A[14 + x] = 0x01 > > A[23 + x] = 4 - x > > * > * > > For each fixed x, these are four independent one-byte constraints under the random-half model. > > Thus the structural probability is approximately: > > 5 / 256^4 > > ≈ 1.1641532182693481e-9 > > * > * > > or approximately: > > 1 in 858,993,459 > > * > * > > The output value begins at: > > value_pos = 47 + x > > * > * > > which aliases to an 8-byte window in the random 32-byte half: > > A[15 + x .. 22 + x] > > * > * > > Using the same simplified independence approximation, the probability of being in MoneyRangeis > approximately: > > (MAX_MONEY + 1) / 2^64 > > ≈ 1.1384122811097797e-4 > > * > * > > So the approximate probability that a random left || leftpreimage is structurally valid and has an > in-range output value is: > > (5 / 256^4) * ((MAX_MONEY + 1) / 2^64) > > ≈ 1.3252864140005492e-13 > > * > * > > or approximately: > > 1 in 7,545,600,000,000 > > * > Block-level accidental probability* > > A block with ntransactions has approximately n - 1internal Merkle nodes, plus duplicated-node cases > depending on tree shape. > > Using the rough random left || rightestimate: > > p ≈ 3.39e-11 > > * > * > > A block with 10,000 transactions has approximate accidental violation probability: > > 1 - (1 - p)^9999 ≈ 3.39e-7 > > * > * > > or roughly: > > 1 in 2,950,000 blocks > > * > * > > This is a simplified estimate. Actual txids are not perfect independent random samples in all cases, > duplicated nodes have lower estimated probability, and additional implementation details may reduce > or alter the rate. > > The deployment-relevant conclusion is: > > Honest accidental violations should be rare. > > Adversarial violations are possible. > > Miners must enforce the rule. > > * > Backward compatibility* > > This is a soft fork. Blocks violating the new rule were previously valid and become invalid after > activation. > > Unupgraded full nodes may accept violating blocks after activation. Activation therefore requires > ordinary soft-fork deployment procedures. > > Unupgraded SPV clients remain vulnerable to the legacy proof ambiguity. SPV clients must update > their Merkle proof validation logic to obtain the benefit of this rule. > > *Test vectors* > > Test vectors should include: > > 1. > > A block whose transaction Merkle internal node preimages do not encode minimal 64-byte > transactions. The block is valid. > > 2. > > A block containing a 64-byte transaction whose serialization does not appear as an internal node > preimage. The block is valid. > > 3. > > A block where an internal node preimage encodes a minimal 64-byte transaction. The block is invalid. > > 4. > > A block where an odd-entry duplicated preimage h || hencodes a minimal 64-byte transaction. The > block is invalid. > > 5. > > An SPV proof where one branch preimage encodes a minimal 64-byte transaction. The proof is rejected. > > 6. > > An SPV proof for a 64-byte transaction where no branch preimage encodes a minimal 64-byte > transaction. The proof is accepted if otherwise valid. > > *Open questions* > > 1. > > Should the rule include only the explicit minimal 64-byte legacy transaction shape above, or > should it call the full consensus transaction deserializer? > > 2. > > Should future transaction serialization changes be required to preserve this exact forbidden- > preimage invariant? > > 3. > > Should pre-activation relay policy discourage transaction pairs that can form forbidden sibling > preimages? > > 4. > > Should mining software standardize a recovery procedure for failed Merkle construction, or > should this remain implementation-specific? > > 5. > > Should SPV proof formats include an explicit version bit indicating branch-preimage checking > support? > > -- > You received this message because you are subscribed to the Google Groups "Bitcoin Development > Mailing List" group. > To unsubscribe from this group and stop receiving emails from it, send an email to > bitcoindev+unsubscribe@googlegroups.com <mailto:bitcoindev+unsubscribe@googlegroups.com>. > To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/f97afcc5-54ba-4284-8e9b- > e8c35c7101f6n%40googlegroups.com <https://groups.google.com/d/msgid/bitcoindev/ > f97afcc5-54ba-4284-8e9b-e8c35c7101f6n%40googlegroups.com?utm_medium=email&utm_source=footer>. -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/00be6fe9-7178-4069-9722-5595fde55b72%40mattcorallo.com. ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-06-09 18:40 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-06-01 17:46 [bitcoindev] Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions jeremy 2026-06-01 18:49 ` 'Antoine Poinsot' via Bitcoin Development Mailing List 2026-06-01 20:17 ` jeremy 2026-06-02 12:36 ` Greg Sanders 2026-06-02 18:15 ` jeremy 2026-06-03 1:05 ` Antoine Riard 2026-06-03 15:07 ` jeremy 2026-06-05 21:34 ` 'Antoine Poinsot' via Bitcoin Development Mailing List 2026-06-09 16:28 ` jeremy 2026-06-09 16:37 ` jeremy 2026-06-09 18:30 ` Matt Corallo
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox