LearnBitcoin

Bitcoin Dev Archive 2026

Prohibit Merkle Internal Node Preimages That Encode Minimal 64-Byte Transactions

18 messages, 1 to 29 Jun 2026. Started by Jeremy Rubin.

Jeremy Rubin (8), Antoine Poinsot (4), Antoine Riard (2), Greg Sanders, Matt Corallo, Murch, Sjors Provoost

Permalink Original thread on gnusha.org

Jeremy Rubin permalink original

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?
Antoine Poinsot in reply to Jeremy Rubin permalink original

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:

Quoted text (1 line)
> 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:

Quoted text (981 lines)
> 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.
Jeremy Rubin in reply to Antoine Poinsot permalink original

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?

Quoted text (1 line)
> 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

Greg Sanders in reply to Jeremy Rubin permalink original

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:

Quoted text (28 lines)
> 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
>
Jeremy Rubin in reply to Greg Sanders permalink original
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:

Quoted text (39 lines)
> 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
>>
>
Antoine Riard in reply to Jeremy Rubin permalink original

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 :

Quoted text (57 lines)
>
> 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
>>>
>>
Jeremy Rubin in reply to Antoine Riard permalink original

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:

Quoted text (80 lines)
> 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
>>>>
>>>
Antoine Poinsot in reply to Jeremy Rubin permalink original

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:

Quoted text (18 lines)
> 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.
Jeremy Rubin in reply to Antoine Poinsot permalink original

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:

Quoted text (72 lines)
> 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
> .
>
>
>
Jeremy Rubin in reply to Jeremy Rubin permalink original

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:

Quoted text (125 lines)
> 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
>> .
>>
>>
>>
Matt Corallo in reply to Jeremy Rubin permalink original

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:

Quoted text (1337 lines)
> 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>.
Antoine Poinsot in reply to Jeremy Rubin permalink original

I'd rather say we are at the edge of steeply diminishing returns, which is how we end up with bad protocol designs.

It's correct that if someone were to design an SPV protocol that builds chain(s) of transactions on top of an anyone-can-spend connector output, whose expected script could fit into a 64-byte transaction, then a miner could create a fake connector output that renders the chain(s) of transactions invalid before they risk being made invalid anyhow by double spending the anyone-can-spend-yet-timelocked connector output.

It's highly unclear to me how this design could ever be useful, but crucially it's important to think on the margin here. We could simply invalidate 64-byte transactions and make all well-known use cases secure, with no patching required from those SPV verifiers. Going one step further and also requiring that no inner node of the Merkle tree is also a valid bitcoin tx serialization would introduce substantial complexity and attack surface in block template creation, for no benefit but making far-fetched constructions secure (without patching their SPV verifiers, which is always an option). That does not seem worth it. On Tuesday, June 9th, 2026 at 2:39 PM, jeremy <jeremy.l.rubin@gmail•com> wrote:

Quoted text (76 lines)
> 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.
Jeremy Rubin in reply to Matt Corallo permalink original

SDL sent me a note clarifying I missed a rule to restrict the prevout index to in-range. The updated rule should be:

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.
   13.
   * The 4-byte little-ending integer at P[4+1+32] is less than or equal to
   VOutRange*

*VOutRange is *111,111 ( from 1e6 / (1 byte length + 8 bytes amount)).

--------

This contributes another 15.2 bits, giving approximately 51 bits of grinding for an invalid right hand side.

This is still feasible adversarially, but unlikely to ever happen by accident (or in deeper internal nodes).

In addition, we can also add a policy rule to no accept or realy txns which happen to hash to this 51 bit target shape -- this doesn't need to be consensus enforced, but provides a bit of protection to legacy nodes.

--------------

This doesn't seem to effect the adversarial analysis much, just makes it more expensive. If you can get a valid RHS and LHS sent to a legacy node and predict their Txn Ordering you could cause a violation.

On Tuesday, June 9, 2026 at 2:39:53 PM UTC-4 Matt Corallo wrote:

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:

Quoted text (3 lines)
> Esteemed Colleagues,
>
> As a result of some of my research on 64-byte transactions, I'd like to

discuss an alternative soft

Quoted text (1 line)
> fork proposal that preserves the ability to encode 64-byte transactions

while offering protection to

Quoted text (5 lines)
> 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

Quoted text (3 lines)
> one-input, one-output, witness stripped transaction.
>
> [With the miracle of GPT,] I've drafted a relatively complete BIP for

discussion.

Quoted text (6 lines)
>
> Happy International Children's Day,
>
> Jeremy
>
> p.s. I will later propose potentially a couple other mitigations

separately, for discussion as well.

Quoted text (2 lines)
>
>

----------------------------------------------------------------------------------------------------

Quoted text (4 lines)
>
> BIP: TBD
> Layer: Consensus (soft fork)
> Title: Prohibit Merkle Internal Node Preimages That Encode Minimal

64-Byte Transactions

Quoted text (9 lines)
> 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

Quoted text (10 lines)
> 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.

Quoted text (2 lines)
>
> After activation, a block is invalid if any such 64-byte preimage has the

exact byte structure of a

Quoted text (3 lines)
> minimal one-input, one-output, non-witness transaction.
>
> This prevents a 64-byte transaction serialization from being malleated

into an internal Merkle node

Quoted text (1 line)
> preimage in SPV transaction inclusion proofs. It does not make 64-byte

transactions invalid in general.

Quoted text (4 lines)
>
> *Motivation*
>
> Bitcoin transaction identifiers and transaction Merkle internal nodes are

both computed with double

Quoted text (10 lines)
> 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

Quoted text (8 lines)
> 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.

Quoted text (2 lines)
>
> An SPV verifier that accepts a Merkle proof without authenticating the

full tree shape can be made

Quoted text (1 line)
> to accept a proof terminating at an internal node rather than at an

actual transaction leaf.

Quoted text (2 lines)
>
> This proposal removes that ambiguity by forbidding Merkle internal node

preimages that have the only

Quoted text (5 lines)
> practical 64-byte transaction encoding shape.
>
> *SegWit and transaction identifiers*
>
> Since SegWit activation, Bitcoin transactions have two related

identifiers:

Quoted text (41 lines)
>
> 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,

Quoted text (30 lines)
> 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

Quoted text (17 lines)
> (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

Quoted text (3 lines)
> transaction Merkle tree committed by the block header.
>
> SegWit does define a separate witness Merkle tree whose root is committed

through the coinbase

Quoted text (1 line)
> witness commitment, but that witness Merkle tree is distinct from the

transaction Merkle tree

Quoted text (3 lines)
> discussed in this proposal.
>
> As a result, the ambiguity addressed by this proposal concerns only

transaction identifiers (txids)

Quoted text (1 line)
> and the transaction Merkle root. The SegWit marker and flag bytes are

irrelevant to the transaction

Quoted text (5 lines)
> 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.

Quoted text (2 lines)
>
> For legacy transactions, and for SegWit transactions when computing the

txid, the serialization

Quoted text (72 lines)
> 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

Quoted text (1 line)
> one of those five layouts and whose single output value is in the

consensus money range.

Quoted text (4 lines)
>
> *Specification*
>
> After activation, a block is invalid if any transaction Merkle internal

node preimage encodes a

Quoted text (3 lines)
> minimal 64-byte transaction.
>
> For every internal Merkle parent computation in the transaction Merkle

tree:

Quoted text (62 lines)
>
> 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.

Quoted text (2 lines)
>
> Equivalently, the forbidden preimage is a 64-byte serialization of a

one-input, one-output, non-

Quoted text (1 line)
> witness transaction with single-byte CompactSize counts and script

lengths, where the two script

Quoted text (3 lines)
> lengths sum to 4 and the output value is in range.
>
> For clarity, "non-witness transaction" here refers to the serialization

used for txidcomputation.

Quoted text (1 line)
> Even for SegWit transactions, the transaction Merkle tree uses txids, so

the marker byte, flag byte,

Quoted text (3 lines)
> and witness data are excluded.
>
> This rule applies to every transaction Merkle internal node used to

compute the block header's

Quoted text (5 lines)
> transaction Merkle root.
>
> *Odd-entry duplication*
>
> If a Merkle level has an odd number of entries, Bitcoin duplicates the

final hash:

Quoted text (18 lines)
>
> 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

Quoted text (33 lines)
> 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.

Quoted text (2 lines)
>
> The verifier still performs the ordinary Merkle path computation and

block header proof-of-work

Quoted text (5 lines)
> validation.
>
> *Rationale*
>
> The known 64-byte transaction SPV malleability issue requires a byte

string that is both:

Quoted text (16 lines)
>
> 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

Quoted text (1 line)
> unless its exact serialization appears as a transaction Merkle internal

node preimage in the same

Quoted text (3 lines)
> block's transaction Merkle tree.
>
> The rule also avoids adding a general transaction validity rule that

exists only to protect Merkle

Quoted text (5 lines)
> proof semantics.
>
> *Why SegWit does not eliminate the ambiguity*
>
> It is sometimes assumed that SegWit automatically removes this ambiguity

because SegWit transactions

Quoted text (8 lines)
> contain the marker and flag bytes:
>
> 00 01
>
> *
> *
>
> However, the ambiguity exists at the txidlayer, not at the

witness-serialization layer.

Quoted text (2 lines)
>
> The transaction Merkle root in the block header is computed from txids,

and txidsare computed from

Quoted text (27 lines)
> 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

Quoted text (3 lines)
> commit to wtxids.
>
> As a result, the existence of the SegWit marker and flag bytes does not

prevent a txidpreimage from

Quoted text (3 lines)
> having the same byte structure as a Merkle internal node preimage.
>
> The ambiguity addressed by this proposal therefore remains relevant in

the SegWit era.

Quoted text (23 lines)
>
> *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.

Quoted text (2 lines)
>
> However, it is not correct to describe that rule as automatically fixing

all light clients.

Quoted text (2 lines)
>
> A 64-byte transaction invalidity rule protects an SPV verifier only if

the verifier enforces the new

Quoted text (1 line)
> rule when interpreting the claimed transaction. Existing or

application-specific SPV verifiers that

Quoted text (1 line)
> merely receive a byte string and a Merkle branch may remain vulnerable if

they do not parse the

Quoted text (1 line)
> claimed transaction and reject exactly-64-byte transaction

serializations.

Quoted text (2 lines)
>
> More generally, a consensus rule invalidating 64-byte transactions does

not prevent arbitrary

Quoted text (1 line)
> internal node preimages from existing. It only prevents those preimages

from being valid Bitcoin

Quoted text (1 line)
> transactions under upgraded consensus rules. A bridge, wallet, or deposit

system that accepts SPV-

Quoted text (1 line)
> style proofs but performs incomplete transaction parsing may still be

induced to treat an internal

Quoted text (3 lines)
> node preimage as an application-level event.
>
> For example, suppose an application-level SPV verifier treats a proved

byte string as a "deposit" if

Quoted text (1 line)
> some field inside the alleged transaction matches a registered deposit

address, deposit script, or

Quoted text (1 line)
> deposit commitment, but does not fully enforce the upgraded

transaction-validity rule. An attacker

Quoted text (8 lines)
> 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

Quoted text (1 line)
> systems, the attacker may also be able to choose or register deposit data

that matches bytes already

Quoted text (3 lines)
> 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

Quoted text (1 line)
> changing full-node transaction validity automatically upgrades every SPV

verifier and every bridge,

Quoted text (14 lines)
> 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.

Quoted text (5 lines)
>
> *
> *
>
> The 64-byte transaction invalidity rule is simpler for light clients that

correctly implement it,

Quoted text (1 line)
> but it is broader at the transaction layer. This proposal places the rule

at the Merkle ambiguity

Quoted text (13 lines)
> 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.

Quoted text (18 lines)
>
> *
> *
>
> 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

Quoted text (48 lines)
> 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])

Quoted text (124 lines)
>
> {
>
>     // 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())) {

Quoted text (96 lines)
>
>         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

Quoted text (7 lines)
> 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

Quoted text (10 lines)
> 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

Quoted text (3 lines)
> miner submission, or transaction ordering effects.
>
> Therefore miners MUST check candidate block templates before mining.

Miners MUST NOT rely on

Quoted text (5 lines)
> accidental violation probability.
>
> *Merkle construction failure recovery*
>
> If a candidate block template violates this rule, the miner usually does

not need to discard the

Quoted text (1 line)
> entire template. The violation is local to one or more internal Merkle

node preimages:

Quoted text (7 lines)
>
> left_child_hash || right_child_hash
>
> *
> *
>
> A miner can usually repair the candidate block by changing transaction

order so that the offending

Quoted text (1 line)
> pair of child hashes no longer appears as siblings at the violating

Merkle tree level.

Quoted text (4 lines)
>
> *Recommended recovery procedure*
>
> When Merkle root construction fails because an internal node preimage is

forbidden, mining software

Quoted text (9 lines)
> SHOULD use the following procedure:
>
> 1.
>
> Record each offending internal node preimage.
>
> 2.
>
> Identify the transaction subtree contributing to each offending child

hash.

Quoted text (4 lines)
>
> 3.
>
> Attempt to repair the block by shuffling transaction order while

preserving consensus

Quoted text (13 lines)
> 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

Quoted text (7 lines)
> 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.

Quoted text (4 lines)
>
> 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

Quoted text (1 line)
> dependencies, or use a randomized topological ordering of the block's

transaction dependency graph.

Quoted text (21 lines)
>
> *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.

Quoted text (4 lines)
>
> *Repeated failure*
>
> If randomized repair fails repeatedly, mining software SHOULD remove

transactions contributing to

Quoted text (7 lines)
> 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.

Quoted text (5 lines)
>
> *
> *
>
> For a bottom-level violation, the offending subtree usually corresponds

to two sibling transaction

Quoted text (10 lines)
> 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.

Quoted text (5 lines)
> 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.

Quoted text (2 lines)
>
> 3. Prefer removing the lowest-feerate removable transaction that does not

force removal of higher-

Quoted text (6 lines)
> feerate descendants.
>
> *
> *
>
> This policy does not need to identify a malicious transaction. It only

needs to produce a valid

Quoted text (5 lines)
> block template with minimal fee loss.
>
> *Fee impact*
>
> The expected fee impact for honest block templates should be negligible

because accidental

Quoted text (3 lines)
> violations are rare.
>
> If an adversary intentionally creates transactions that cause violations

when paired, shuffling will

Quoted text (1 line)
> usually defeat the attempt without fee loss. If shuffling does not repair

the template, removing one

Quoted text (3 lines)
> or more offending transactions bounds the miner's exposure.
>
> The adversary's practical effect is limited to potentially causing some

transactions to be omitted

Quoted text (1 line)
> from a candidate block template. The rule prevents upgraded miners from

mining invalid blocks,

Quoted text (5 lines)
> 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

Quoted text (3 lines)
> ordinary operation.
>
> However, an adversary can construct transaction pairs intended to trigger

the rule under specific

Quoted text (3 lines)
> sibling placement.
>
> Unupgraded miners that do not enforce this rule may mine a block that

upgraded nodes reject after

Quoted text (1 line)
> activation. Low accidental probability improves deployment safety but is

not a substitute for miner

Quoted text (5 lines)
> enforcement.
>
> *Probability analysis*
>
> This section estimates accidental violation probability under simplified

randomness assumptions.

Quoted text (6 lines)
>
> *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:

Quoted text (22 lines)
>
> 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:

Quoted text (34 lines)
>
> 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:

Quoted text (9 lines)
>
> (MAX_MONEY + 1) / 2^64
>
> ≈ 1.1384122811097797e-4
>
> *
> *
>
> Therefore the approximate probability that a random 64-byte preimage is

structurally valid and has

Quoted text (88 lines)
> 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.

Quoted text (32 lines)
>
> 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

Quoted text (10 lines)
> approximately:
>
> (MAX_MONEY + 1) / 2^64
>
> ≈ 1.1384122811097797e-4
>
> *
> *
>
> So the approximate probability that a random left || leftpreimage is

structurally valid and has an

Quoted text (17 lines)
> 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

Quoted text (10 lines)
> 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:

Quoted text (14 lines)
>
> 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,

Quoted text (1 line)
> duplicated nodes have lower estimated probability, and additional

implementation details may reduce

Quoted text (14 lines)
> 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

Quoted text (3 lines)
> activation.
>
> Unupgraded full nodes may accept violating blocks after activation.

Activation therefore requires

Quoted text (3 lines)
> ordinary soft-fork deployment procedures.
>
> Unupgraded SPV clients remain vulnerable to the legacy proof ambiguity.

SPV clients must update

Quoted text (9 lines)
> 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

Quoted text (5 lines)
> transactions. The block is valid.
>
> 2.
>
> A block containing a 64-byte transaction whose serialization does not

appear as an internal node

Quoted text (5 lines)
> preimage. The block is valid.
>
> 3.
>
> A block where an internal node preimage encodes a minimal 64-byte

transaction. The block is invalid.

Quoted text (4 lines)
>
> 4.
>
> A block where an odd-entry duplicated preimage h || hencodes a minimal

64-byte transaction. The

Quoted text (5 lines)
> block is invalid.
>
> 5.
>
> An SPV proof where one branch preimage encodes a minimal 64-byte

transaction. The proof is rejected.

Quoted text (4 lines)
>
> 6.
>
> An SPV proof for a 64-byte transaction where no branch preimage encodes a

minimal 64-byte

Quoted text (7 lines)
> 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

Quoted text (5 lines)
> should it call the full consensus transaction deserializer?
>
> 2.
>
> Should future transaction serialization changes be required to preserve

this exact forbidden-

Quoted text (5 lines)
> preimage invariant?
>
> 3.
>
> Should pre-activation relay policy discourage transaction pairs that can

form forbidden sibling

Quoted text (5 lines)
> preimages?
>
> 4.
>
> Should mining software standardize a recovery procedure for failed Merkle

construction, or

Quoted text (5 lines)
> should this remain implementation-specific?
>
> 5.
>
> Should SPV proof formats include an explicit version bit indicating

branch-preimage checking

Quoted text (4 lines)
> support?
>
> --
> You received this message because you are subscribed to the Google Groups

"Bitcoin Development

Quoted text (2 lines)
> Mailing List" group.
> To unsubscribe from this group and stop receiving emails from it, send an

email to

Quoted text (2 lines)
> bitcoindev+...@googlegroups•com <mailto:bitcoindev+...@googlegroups•com>.
> To view this discussion visit

https://groups.google.com/d/msgid/bitcoindev/f97afcc5-54ba-4284-8e9b-

Quoted text (1 line)
> e8c35c7101f6n%40googlegroups.com <

https://groups.google.com/d/msgid/bitcoindev/

Quoted text (1 line)
> f97afcc5-54ba-4284-8e9b-e8c35c7101f6n%

40googlegroups.com?utm_medium=email&utm_source=footer>.

Murch in reply to Jeremy Rubin permalink original

Hi Jeremy,

As AJ pointed out in "[BIP-0054] 64-Byte Transactions and Potential Legitimate Uses", forbidding 64-byte stripped transactions would at most incur that transaction creators have to pad a to-be-invented use for an anyone-can-spend output script with an `OP_NOP` if they hit the 64-byte case, or if the uncertainty whether the case will be hit is the issue, to circumvent it per paying for one extra byte by always padding.

Could you please explain why potentially needing to add a 1-byte padding for an "esoteric use case" is a bigger burden for the ecosystem than saddling every node with checking thousands of hashes during block validation on whether they match a valid transaction pattern?

As far as I’m following this conversation so far, this proposal’s trade-offs don’t feel convincing to me.

Cheers, Murch

On 2026-06-09 09:37, jeremy wrote:

Quoted text (149 lines)
> 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 <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
> <mailto:bitcoindev+unsubscribe@googlegroups•com>.
> To view this discussion visit https://groups.google.com/d/msgid/
> bitcoindev/178b550d-841f-43d6-8c4f-67e909cc17bfn%40googlegroups.com
> <https://groups.google.com/d/msgid/
> bitcoindev/178b550d-841f-43d6-8c4f-67e909cc17bfn%40googlegroups.com?
> utm_medium=email&utm_source=footer>.
Jeremy Rubin in reply to Jeremy Rubin permalink original

Adding a note & reference that Rootstock uses a similar mitigation for it's SPV proofs:

See the below code:

https://github.com/rsksmart/bitcoinj-thin/blob/f89816871d644436e34cd8d0a70c3c8ef41b0836/src/main/java/co/rsk/bitcoinj/core/PartialMerkleTree.java#L187

And SDL's blog post on the topic: https://bitslog.com/2018/06/09/leaf-node-weakness-in-bitcoin-merkle-tree-design

So enforcing this rule would align with existing deployed systems.

On Monday, June 1, 2026 at 2:02:51 PM UTC-4 jeremy wrote:

Quoted text (1196 lines)
> 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?
>
>
Antoine Poinsot in reply to Jeremy Rubin permalink original

It would align with a deployed system. Other deployed systems use different workarounds.

For instance BitVM uses the alternative mitigation also proposed by Sergio. BitVM's implementation: https://github.com/BitVM/BitVM/pull/295. Sergio's description of the idea: https://bitslog.com/2018/08/21/simple-change-to-the-bitcoin-merkleblock-command-to-protect-from-leaf-node-weakness-in-transaction-merkle-tree/. This post notably opens with:

Quoted text (1 line)
> While this solves the problem, it is by no means a “clean” solution: it creates false-negative cases (with very low probability) and it reduces verification efficiency.

Other bridge applications check the depth of the proof vs a proof for the coinbase transaction: https://github.com/threshold-network/tbtc-v2/blob/cf4d12f71b08511bc2185802be88e302b7bef656/solidity/contracts/bridge/BitcoinTx.sol#L194-L197.

More importantly, this is not a good standard by which to judge mitigations. The goal of a consensus-enforced mitigation is to make SPV verifiers secure without requiring them to implement one of the workarounds. Your proposal, besides the substantial costs it would impose on full nodes, does not achieve this goal. Simply invalidating 64-byte transactions does (for all known and non-esoteric imaginable ones). On Tuesday, June 16th, 2026 at 4:22 PM, jeremy <jeremy.l.rubin@gmail•com> wrote:

Quoted text (991 lines)
> Adding a note & reference that Rootstock uses a similar mitigation for it's SPV proofs:
>
> See the below code:
> https://github.com/rsksmart/bitcoinj-thin/blob/f89816871d644436e34cd8d0a70c3c8ef41b0836/src/main/java/co/rsk/bitcoinj/core/PartialMerkleTree.java#L187
>
> And SDL's blog post on the topic: https://bitslog.com/2018/06/09/leaf-node-weakness-in-bitcoin-merkle-tree-design
>
> So enforcing this rule would align with existing deployed systems.
> On Monday, June 1, 2026 at 2:02:51 PM UTC-4 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 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/2cae3f35-2058-4e41-8fe9-d2ef06259e0dn%40googlegroups.com.
Sjors Provoost in reply to Jeremy Rubin permalink original
Quoted text (7 lines)
> Op 1 jun 2026, om 19:46 heeft jeremy <jeremy.l.rubin@gmail•com> het volgende geschreven:
>
> 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.

I like the idea of fixing the problem as close to the (merkle, haha) root of the problem. But is there a more elegant and succinct way to implement IsForbiddenMerkleInternalNodePreimage64?

Otherwise I prefer to wait 80 years for a proper fix, rather than add this complexity to consensus code. Even if we can't have the 64 byte exception (which I still prefer).

After 2106, the fix can be a simple tweak to the leaf hash calculation:

if (!hardfork) return tx.getHash().ToUint256();

// Fix Merkle tree, and drop the separate witness commitment by committing // witness data directly in the transaction Merkle tree. return (HashWriter{TaggedHash("TaggedWtxid")} << TX_WITH_WITNESS(tx)).GetSHA256();

Here's a rough sketch: https://github.com/Sjors/bitcoin/tree/2026/06/merkle

The upgrade mechanism isn't important in this context, but I implemented the following rules:

1. a new header format, growing timestamp from 32 to 64 bits
  - mask one byte to use for nonce space, if we think two billion years is enough
2. the block version must be negative, if and only if it uses the new header format
  - let's the header/block deserialiser know in the first 4 bytes how long the header is
  - current nodes will reject such blocks, because BIP34 deployment burned nVersion < 2
    - therefore it's not used for signalling or nonce grinding
  - no historical blocks have a negative version
3. new headers must have timestamp >= 2^32
  - makes it a clean break both ways
  - maybe require old headers can't connect to a new header
- Sjors
Quoted text (71 lines)
> 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;
> }
>
>
Antoine Riard in reply to Sjors Provoost permalink original

Hi Jeremy,

Okay, so the main intuition is only a reduction of the "seam" to a specific structure, by checking that all txid merkle has a specific 1-input, 1-output, tx. Would be good if you have benchmark for iterating on a max-depth-tree-min -size tx on a i5 or i7 to have an idea of the perf measurement. Somehow, without more information, I can only echo my protocol colleagues that your solution is naively coming with more CPU cycles consumption.

On another point, from your discussion with the other Antoine, what is observed is very interesting. It's not only a SPV proof for e.g let's say a bridge protocol using connector output, thar your consideration can be intereesting. It's also that the whole merkle tree can be seen a *scriptable surface*. Assuming that all the other txn ids are fixed points (easy fulfilled assumption if you're a miner and you can choose the template), a remaining txid slot can be used to convey *information* from a fixed spent UTXO (where the prover would grind on the remaining entropy bits to get a satisfying solution).

Somehow allowing to build interesting cryptographic puzzles, giving zero-knowledge properties to a simple merkle branch. What theoretically you could do with it ? Super compact proof that a UTXO has been spent and a 1-bit of information has been exchanged. Of course, the current BIP54 fix ruling out 64 bytes tx would still allow to build that kind of proofs, you would just have to special case this in the prover structure.

Anyway, about BIP54, I'm +1 to have a long validation block cost and timewarp fixes, I'm shrug about getting a better fix for coinbase id collision, we have more time to come with one, ruling out 64 byte tx to make secure SPV verifier, I've been always less interested by it, personally.

Best, Antoine OTS: 62a4928f9aaa678a225f6ca55564049d6ba19ae4d006b890cdfc945248aebb9e

Le Monday, June 22, 2026 à 3:27:54 PM UTC+1, Sjors Provoost a écrit :

Quoted text (135 lines)
>
>
> > Op 1 jun 2026, om 19:46 heeft jeremy <jeremy....@gmail•com> het
> volgende geschreven:
> >
> > 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.
>
> I like the idea of fixing the problem as close to the (merkle, haha) root
> of the problem. But is there a more elegant and succinct way to implement
> IsForbiddenMerkleInternalNodePreimage64?
>
> Otherwise I prefer to wait 80 years for a proper fix, rather than add this
> complexity to consensus code. Even if we can't have the 64 byte exception
> (which I still prefer).
>
> After 2106, the fix can be a simple tweak to the leaf hash calculation:
>
> if (!hardfork) return tx.getHash().ToUint256();
>
> // Fix Merkle tree, and drop the separate witness commitment by committing
> // witness data directly in the transaction Merkle tree.
> return (HashWriter{TaggedHash("TaggedWtxid")} <<
> TX_WITH_WITNESS(tx)).GetSHA256();
>
> Here's a rough sketch:
> https://github.com/Sjors/bitcoin/tree/2026/06/merkle
>
>
> The upgrade mechanism isn't important in this context, but I implemented
> the following rules:
>
> 1. a new header format, growing timestamp from 32 to 64 bits
> - mask one byte to use for nonce space, if we think two billion years is
> enough
>
> 2. the block version must be negative, if and only if it uses the new
> header format
> - let's the header/block deserialiser know in the first 4 bytes how long
> the header is
> - current nodes will reject such blocks, because BIP34 deployment burned
> nVersion < 2
> - therefore it's not used for signalling or nonce grinding
> - no historical blocks have a negative version
>
> 3. new headers must have timestamp >= 2^32
> - makes it a clean break both ways
> - maybe require old headers can't connect to a new header
>
> - Sjors
>
> > 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;
> > }
> >
> >
>
>