This post was initially sends for publication on the bitcoin-dev block ML 900708 - 2025-06-11 GMT

Hi James,

Thanks for your post.

I think you can break the current version of CTV in the way it's currently proposed as a
NOP refurbishment (i think OP_NOP4), which makes it a legacy script.

Currently, there is a max limit of 80_000 sigops per-block (`MAX_BLOCK_SIGOPS_COST`
in `src/consensus/consensus.h). That limit is applied for all legacy, p2sh and segwit
scripts, at time of `Chainstate::ConnectBlock`:

   // GetTransactionSigOpCost counts 3 types of sigops:
   // * legacy (always)
   // * p2sh (when P2SH enabled in flags and excludes coinbase)
   // * witness (when witness enabled in flags and excludes coinbase)
   nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);
   if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) {
      state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-blk-sigops", "too many sigops");
      break;
   }

This enforced limit means that any block with 80_001 signature operation
within is going to be rejected by the receiving full-node ("too-many-sigops").
where a signature operation is any opcode like a CHECKSIG or CHECKMULTISIG
(`GetSigOpCount()` in `src/script/script.h).

While signature operations is not necessarily somehting you're going to think
about when you design and deploy second-layers or contract protocol (even for
coinpool we only make assumptions of 1000-sized off-chain constructions, so
1000 sigs at max in the redeemscript), this signature operation limit is
obviously weighted in by block template construction to ensure a valid block
is generated by the network and not an insane amount of watt has been wasted.

E.g, the default block template algorithm attached in core is using this limit:

   // TODO: switch to weight-based accounting for packages instead of vsize-based accounting.
   if (nBlockWeight + WITNESS_SCALE_FACTOR * packageSize >= m_options.nBlockMaxWeight) {
      return false;
   }
   if (nBlockSigOpsCost + packageSigOpsCost >= MAX_BLOCK_SIGOPS_COST) {
      return false;
   }
   return true;

While it is well-established that many miners are running their own block
construction algorithms, one can assume this limit exist for all while it's
more unclear if they're selecting the highest feerate, *highest sigops* txn
too. This selection of highest feerate, *highest sigops* block opens the door
to an interesting exploitation for any use-cases with timelocks.

Namely, let's say you have a use-case U which is locking funds in a redeem
script S with path either alice_sig OR bob_timelock + bob_sig. Any adversary
(i.e Bob) can fulfill the sequence of blocks from current chain tip leading
up to bob_timelock to *censor* alice of redeeming the funds with high-feerate,
high-sigops junk txn.

Here a testcase on some bitcoin core 28.x branch doing so with empty CHECKMULTISIG
as they have the highest ratio of sigops accounting per unit of feerate that one
has to pay for:

https://github.com/ariard/bitcoin/commit/b85a426c43cb7000788a55ea140b73a68da9ce4e

A honest counterparty to the use-case U can indeed over-bid in feerate to get
her legit time-sensitive tx confirming in block template over the adversary's junk.
However, game-theory wise the counterparty is limited by the max amount of funds
locked in the shared coins U. E.g if the use-case U has a weight unit surface of 20_000
WU and an amount of 100_000 satoshis, the honest counterparty will at most burn
5 satoshis per WU.

This is a clear limit that the adversary, who is a counterparty to the locked
funds, can evaluate ahead and from then be break-even by finding N+1 use-case U
of amount U or inferior where N is the timelock duration.

While this "block sigops overflow" attacks present a lot of "maybe", most notably
what is the txn selection algorithm for block template runned by the high-hashrate
miners over the network, it's still put a wonder for any CTV use-case with a script
of the following form:

   OP_IF
       <my_little_vault_hash OP_CTV>
   OP_ELSE
       <alice_bob_their_family_aggregated_pubkey> OP_CHECKSIG
   OP_ENDIF

A simple upgrade can be to overhaul CTV design on top of a OP_SUCCESS or another
tapscript upgrade paths, as tapscripts spends do not see their signature ops
accounted in the per-block limit (BIP342 per-script sigops budget). The only
way to further exploit would be inflate the txn spending the script, which should
be correctly bounded by use-cases designers, I think.

As far as I know, this problem has always been there since the activation of
SegWit in 2017, and if I'm correct - but please don't trust, verify - the potential
exposure of any shared UTXO with timelocks and competing interest has always been
there...However, as far as I know this ill-design limit for time-sensitive use-cases
has never been really been discussed among devs circlces and my own awareness of
this problem is itself quite recent.

That's all for the "letterocracy" and the ones who're currently thinking that
covenant soft-forks are seriously technically reviewed...

The only thing I'll add I'm still eager to review in good faith _both_ your
proposal of CTV+CSFS and Poinsot's BIP54 in the future (in fine he's not personally
responsible for what I did say on the BIP repos issue) in the future.

If I'm correct on this "block sigops overflow" problem, there might be anyway
things to re-think about, at least being sure we do not introduce new issues of
this domain for current or upcoming use-cases.

Best,
Antoine "the evil one"
OTS hash: 3598fa5db5a6f60639f938b58d85c2b563f4ff7728061eeb166998b7280287ce