Technical Review

BIP-110: 7 Heuristics That Filter Non-Financial Data

How the Reduced Data Temporary Softfork identifies and blocks data embedding attacks across every Bitcoin script type - with code-level evidence from the shipped Knots implementation.

Overview

What BIP-110 does

BIP-110 introduces a temporary soft fork that restricts transaction patterns primarily used for data embedding rather than financial transfers. It targets the specific script features exploited by Ordinals inscriptions, Runes, and other non-financial uses that have consumed 41% of block space over the last 90 days.

The filtering is implemented through 7 consensus-level heuristics, each closing a distinct data embedding vector. Together, simulations show they achieve a 99% reduction in non-financial transactions with 99.95% accuracy and zero legitimate smart contracts blocked.

The rules are enforced via a new script verification flag, SCRIPT_VERIFY_REDUCED_DATA (bit 21), which is activated through a modified BIP9 deployment with a 55% threshold and auto-expiry after approximately one year.

7 Heuristics
99% Spam filtered
99.95% Accuracy
0 Legit contracts blocked
1
Heuristic

Output Script Size Limit

The attack

Data can be embedded directly in output scripts (scriptPubKey). Any script larger than a standard payment type is almost certainly carrying non-financial data. Standard payments are compact: P2PKH (25 bytes), P2SH (23 bytes), P2WPKH (22 bytes), P2WSH (34 bytes), P2TR (34 bytes).

The defense

BIP-110 limits output scripts to 34 bytes for regular outputs and 83 bytes for OP_RETURN outputs. The 34-byte limit accommodates the largest standard payment type (P2WSH/P2TR at 34 bytes). The 83-byte OP_RETURN limit preserves the pre-Core-v30 data carrier policy.

What it catches

Any attempt to embed data in oversized output scripts. This was a common vector before witness data became the preferred method. The 83-byte OP_RETURN limit reverses Core v30's 1,250x increase (from 80 bytes to ~100 KB) at the consensus level.

consensus/consensus.h
static constexpr unsigned int
  MAX_OUTPUT_SCRIPT_SIZE{34};
static constexpr unsigned int
  MAX_OUTPUT_DATA_SIZE{83};
consensus/tx_verify.cpp
for (const auto& txout : tx.vout) {
  if (txout.scriptPubKey.empty())
    continue;
  const auto limit =
    (txout.scriptPubKey[0] == OP_RETURN)
      ? MAX_OUTPUT_DATA_SIZE
      : MAX_OUTPUT_SCRIPT_SIZE;
  if (txout.scriptPubKey.size() > limit)
    return state.Invalid(...,
      "bad-txns-vout-script-toolarge");
}
2
Heuristic

Data Push Size Limit

The attack

Ordinals inscriptions embed arbitrary data (images, video, code) as large witness pushes - up to 520 bytes per push, with multiple pushes per transaction. This is the primary data embedding method, responsible for the majority of non-financial block space usage since February 2023.

The defense

BIP-110 reduces the maximum data push size from 520 bytes to 256 bytes in both script evaluation and witness stack validation. The 256-byte limit is sufficient for all legitimate cryptographic operations (signatures, public keys, hash preimages) while making bulk data embedding impractical.

What it catches

Any inscription or data blob that relies on pushes larger than 256 bytes. This directly targets the Ordinals inscription envelope format, which uses large witness data pushes to store arbitrary content on-chain. The P2SH redeemScript push is exempted to avoid breaking existing P2SH transactions.

script/interpreter.h
static constexpr unsigned int
  MAX_SCRIPT_ELEMENT_SIZE_REDUCED = 256;
script/interpreter.cpp - EvalScript
const unsigned int max_element_size =
  (flags & SCRIPT_VERIFY_REDUCED_DATA)
    ? MAX_SCRIPT_ELEMENT_SIZE_REDUCED
    : MAX_SCRIPT_ELEMENT_SIZE;

// In script evaluation:
if (vchPushValue.size() > max_element_size)
  return set_error(serror,
    SCRIPT_ERR_PUSH_SIZE);
script/interpreter.cpp - Witness stack
// In witness stack validation:
for (const valtype& elem : stack) {
  if (elem.size() > max_element_size)
    return set_error(serror,
      SCRIPT_ERR_PUSH_SIZE);
}
3
Heuristic

Tapscript OP_IF/OP_NOTIF Ban

The attack

Ordinals inscriptions use a specific pattern inside tapscript: an OP_FALSE OP_IF ... OP_ENDIF envelope. The data between OP_IF and OP_ENDIF is never executed but is committed to by the witness, storing arbitrary data on-chain. This is the core mechanism behind the Ordinals inscription protocol.

The defense

BIP-110 bans OP_IF and OP_NOTIF entirely in tapscript. This eliminates the inscription envelope pattern at the consensus level. The ban only applies to tapscript (witness v1 script path spending) - legacy script and witness v0 are unaffected.

What it catches

All Ordinals-style inscriptions that use the OP_FALSE OP_IF envelope. This is the single most impactful heuristic, as the inscription envelope is the dominant data embedding method. Legitimate use of OP_IF in tapscript is limited - conditional payment logic can use other constructs or legacy script types.

script/interpreter.cpp - EvalScript
// Inside OP_IF/OP_NOTIF handling,
// after MINIMALIF check in tapscript:

// REDUCED_DATA bans OP_IF/OP_NOTIF
// entirely in tapscript; reuses
// MINIMALIF error code as this is a
// stricter form of the same restriction
if (flags & SCRIPT_VERIFY_REDUCED_DATA) {
  return set_error(serror,
    SCRIPT_ERR_TAPSCRIPT_MINIMALIF);
}
Only triggers in tapscript (SigVersion::TAPSCRIPT). Legacy and witness v0 scripts are unaffected.
4
Heuristic

Taproot Annex Ban

The attack

The taproot annex is a reserved field in the witness stack (prefixed with 0x50) that is currently non-standard but valid at the consensus level. It provides an unrestricted space for arbitrary data embedding - any bytes can be placed in the annex and they are committed to by the transaction signature.

The defense

BIP-110 makes the presence of a taproot annex a consensus failure. If the witness stack contains an annex (detected by the 0x50 prefix tag on the last stack element when spending taproot), the transaction is rejected.

What it catches

Any attempt to use the taproot annex for data storage. While the annex is currently non-standard in policy, it was available as a consensus-level data embedding vector. Closing it prevents future exploitation as a bypass for the other heuristics.

script/interpreter.cpp - VerifyWitnessProgram
if (stack.size() >= 2
    && !stack.back().empty()
    && stack.back()[0] == ANNEX_TAG) {
  // Drop annex (non-standard)
  const valtype& annex =
    SpanPopBack(stack);

  if (flags & SCRIPT_VERIFY_REDUCED_DATA) {
    return set_error(serror,
      SCRIPT_ERR_PUSH_SIZE);
  }

  execdata.m_annex_hash =
    (HashWriter{} << annex).GetSHA256();
  execdata.m_annex_present = true;
}
The annex is detected and rejected before any further processing. This closes a data vector that was consensus-valid but policy-non-standard.
5
Heuristic

Taproot Tree Depth Limit

The attack

Taproot allows up to 128 levels of Merkle tree depth in script-path spending. Each level adds 32 bytes to the control block. A deep tree can encode data in the tree structure itself - each branch commitment is a hash that can embed chosen content, and the control block (up to 4,129 bytes) carries these commitments.

The defense

BIP-110 limits the taproot tree depth to 7 levels, reducing the maximum control block from 4,129 bytes to 257 bytes. A depth of 7 supports up to 128 leaf scripts, which is more than sufficient for any legitimate multi-path spending policy.

What it catches

Data embedding via deep Merkle tree structures. Deep trees have no legitimate use case - even complex covenant proposals rarely exceed 4-5 levels. The limit also reduces the computational cost of validating taproot script-path spends.

script/interpreter.h
static constexpr size_t
  TAPROOT_CONTROL_MAX_NODE_COUNT_REDUCED
    = 7;
static constexpr size_t
  TAPROOT_CONTROL_MAX_SIZE_REDUCED
    = TAPROOT_CONTROL_BASE_SIZE
    + TAPROOT_CONTROL_NODE_SIZE
    * TAPROOT_CONTROL_MAX_NODE_COUNT_REDUCED;
// = 33 + 32 * 7 = 257 bytes
script/interpreter.cpp
const unsigned int max_control_size =
  (flags & SCRIPT_VERIFY_REDUCED_DATA)
    ? TAPROOT_CONTROL_MAX_SIZE_REDUCED
    : TAPROOT_CONTROL_MAX_SIZE;

if (control.size() < TAPROOT_CONTROL_BASE_SIZE
    || control.size() > max_control_size
    || ...) {
  return set_error(serror,
    SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
}
6
Heuristic

Upgradable Features Blocked

The attack

Bitcoin reserves several "upgradable" features for future soft forks: unknown witness program versions (v2-v16), unknown taproot leaf versions, and OP_SUCCESS opcodes. Under normal rules, these are treated as "anyone-can-spend" at the consensus level (only blocked by relay policy). This makes them ideal data vectors - embed data in an output using an unknown witness version, and any miner can include the spending transaction.

The defense

BIP-110 elevates three DISCOURAGE flags from policy to consensus:

  • DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM - blocks unknown witness versions
  • DISCOURAGE_UPGRADABLE_TAPROOT_VERSION - blocks unknown taproot leaf versions
  • DISCOURAGE_OP_SUCCESS - blocks OP_SUCCESS opcodes

These are bundled as REDUCED_DATA_MANDATORY_VERIFY_FLAGS along with SCRIPT_VERIFY_REDUCED_DATA.

What it catches

Any attempt to use reserved upgrade paths for data embedding. Without this heuristic, an attacker could bypass all other restrictions by simply using witness version 2 or an OP_SUCCESS opcode. This is a critical defense-in-depth measure.

script/interpreter.h
static constexpr unsigned int
  REDUCED_DATA_MANDATORY_VERIFY_FLAGS{
    0
    | SCRIPT_VERIFY_REDUCED_DATA
    | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
    | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION
    | SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS
  };
validation.cpp - GetBlockScriptFlags
if (DeploymentActiveAt(block_index,
    chainman,
    Consensus::DEPLOYMENT_REDUCED_DATA)) {
  flags |=
    REDUCED_DATA_MANDATORY_VERIFY_FLAGS;
}
Previously, these flags were only enforced in relay policy. BIP-110 elevates them to consensus, blocking the use of reserved upgrade paths for data storage.
7
Heuristic

Pay-to-Anchor Empty Witness

The attack

Pay-to-Anchor (P2A) is a witness version 1 output type designed for fee-bumping via CPFP (child-pays-for-parent). Under normal rules, P2A outputs can be spent with any witness data. An attacker could embed data in the witness stack when spending a P2A output, using it as a data carrier.

The defense

BIP-110 requires that P2A outputs are spent with an empty witness stack. Since P2A is designed solely for fee-bumping (the output value is typically dust), there is no legitimate reason to include witness data.

What it catches

Data embedding via P2A witness stacks. While P2A is relatively new and not yet widely exploited for data embedding, this heuristic preemptively closes the vector.

script/interpreter.cpp - VerifyWitnessProgram
// Before BIP-110:
} else if (!is_p2sh
    && CScript::IsPayToAnchor(
         witversion, program)) {
  return true;
}

// After BIP-110:
} else if (stack.empty()
    && !is_p2sh
    && CScript::IsPayToAnchor(
         witversion, program)) {
  return true;
}
The single addition of stack.empty() ensures P2A can only be spent with an empty witness. Any witness data causes the spend to fall through to the DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM check, which BIP-110 elevates to consensus.
Safety mechanism

The Grandfather Clause

All 7 heuristics include a critical safety mechanism: the UTXO height exemption. Inputs spending UTXOs created before the activation height are exempt from the new script verification rules. This means:

  • No existing funds are affected - pre-activation UTXOs can always be spent
  • Pre-signed transactions remain valid
  • Only new transactions creating post-activation UTXOs must comply

The exemption is implemented via per-input flag overrides in ConnectBlock. For each input, if the UTXO's creation height is before activation, REDUCED_DATA_MANDATORY_VERIFY_FLAGS is stripped from that input's verification flags.

validation.cpp - ConnectBlock
const auto reduced_data_start_height =
  DeploymentActiveAt(*pindex, m_chainman,
    Consensus::DEPLOYMENT_REDUCED_DATA)
  ? m_chainman.m_versionbitscache.StateSinceHeight(
      pindex->pprev, params.GetConsensus(),
      Consensus::DEPLOYMENT_REDUCED_DATA)
  : std::numeric_limits<int>::max();

// Per-input exemption
for (size_t j = 0; j < tx.vin.size(); j++) {
  prevheights[j] =
    view.AccessCoin(tx.vin[j].prevout).nHeight;
  if (prevheights[j] < reduced_data_start_height) {
    flags_per_input.resize(tx.vin.size(), flags);
    flags_per_input[j] =
      flags & ~REDUCED_DATA_MANDATORY_VERIFY_FLAGS;
  }
}
Deployment

Activation Mechanism

BIP-110 uses a modified BIP9 deployment with three extensions to the standard versionbits state machine:

ParameterValuePurpose
bit4Version bit for miner signaling
nStartTimeDec 1, 2025When signaling can begin
threshold1,109 / 2,016 (55%)Lower than BIP9's 95% because rules are temporary
max_activation_height965,664Mandatory signaling deadline (~Sep 2026)
active_duration52,416 blocksAuto-expiry after ~1 year

The state machine adds an EXPIRED terminal state: after active_duration blocks past activation, the deployment deactivates automatically. If not renewed, the chain returns to previous rules.

Mandatory signaling enforces block version bit 4 for two retarget periods before max_activation_height. Blocks without the signal are rejected at the header level, before full block download.

References

Sources