An Investigation Starting from a Bitcoin Taproot Script Vulnerability

2026-07-28

Origin

I saw a thread on bitcointalk: How to handle weak scripts?

It described a Taproot script vulnerability that allowed anyone to spend the UTXO without a valid signature. On mempool.space, transaction f460...5bc2 clearly marks the input as unsigned, something rarely seen on Bitcoin:

The script described in the thread, and actually executed by that transaction, was:

OP_PUSHBYTES_32 fa9b5ec193f735c41b804fc6ace1d28e81a299fc815c0f5009dd2dd7d0293c3b
OP_CHECKSIG
OP_IF
OP_PUSHBYTES_32 ff1275b635cd160914cfe1bc516f521abde0fc8ae3fd92ae01ca16449e4758e9
OP_ENDIF

It can be simplified to:

<pubkey A>
OP_CHECKSIG
OP_IF
    <pubkey B>
OP_ENDIF

Taproot uses a stack-based script and takes some mental effort to understand. Translated roughly into JavaScript, it means:

function isTrue(...params)
{
  var pubkeyA = params[-1];
  params--;
  if (checkSign(pubkeyA))
  {
    return true;
  }
  else
  {
    //
  }
  return params[-1];
}

Under normal usage, if the input is [pubkeyA], checkSign(pubkeyA) is true and the function returns true, which is reasonable.

If the input is [pubkeyC], checkSign(pubkeyA) is false, the empty else branch does nothing, and the function returns null rather than true. This also seems reasonable.

The vulnerability is that params is an array, and a user does not have to provide only one parameter.

An attacker can provide the unexpected combination [true, pubkeyC]. After the empty else branch, return params[0] returns true.

There are several basic problems here:

  1. The code does not handle the else branch.
  2. Bitcoin Taproot scripts have no type checking, only opcodes.
  3. The script author did not understand Taproot script usage correctly and overlooked that a witness may submit any number of parameters.

Analysis

This so-called script vulnerability is simply the result of the author writing careless code. The “author” here is not Bitcoin’s creator, but merely a Bitcoin user. Taproot is comparable to an Ethereum smart contract: anyone can write script rules determining how money is controlled and spent. Bitcoin Script is complex and not Turing-complete, so many people overlook that Bitcoin supports custom scripts.

This is clearly not a Bitcoin protocol vulnerability. Bitcoin consensus only executes the script rules correctly; whether the script logic itself is vulnerable is unrelated to the protocol.

In practice, any Bitcoin wallet or ecosystem project relying on Taproot script logic would have its code reviewed by an auditing company. Such a basic error should be extremely rare on mainnet.

Taproot scripts also provide strong privacy. We can know that an address beginning with bc1p is Taproot and that 0xc0 is the current leaf version, but before a UTXO is spent the network cannot see the script’s contents. At the moment it is spent, all the money will probably be transferred away. When a P2TR UTXO is created, the chain stores only the root hash of the tree. Any path that unlocks the root is a valid input, and only during spending does the chain verify the particular leaf and key path. Even if one leaf is revealed, the chain can see only that leaf, not the other leaves beneath the same root.

From the beginning, Bitcoin encouraged using a new address for every transfer, and most reliable wallets do so. This greatly reduces the chance that a vulnerable script can be exploited again.

From both technical and commercial perspectives, vulnerable scripts are exceptionally rare individual cases caused entirely by their authors. The chance of a vulnerable script holding funds on mainnet is very small.

Investigation

Example Address

Could such a vulnerable script really exist on the network, allowing funds to be transferred without a signature?

Starting from bc1p...cn8z, several questions arise.

  1. If this address still held money, how could it be transferred out?

Wallet apps obviously do not provide this function. Bitcoin transactions are also complicated: the originating transaction and output index of each UTXO must be supplied as parameters. This example script, taproot-script-path-demo.mjs, can be run as follows:

TXID='0618d93bc4eef77f4b47c4b4b188011f95c7ccb929e6226df2774b88b5d4693d'
VOUT='0'
INPUT_SATS='15000'
DESTINATION='bc1px6jn77jd4tplp94q46svkuutc53wtn24dk34wxp4dfur6tvez89swvlcmj'
FEE_SATS='1000'

node outputs/taproot-script-path-demo.mjs \
  "$TXID" \
  "$VOUT" \
  "$INPUT_SATS" \
  "$DESTINATION" \
  "$FEE_SATS"

If you accidentally send money to bc1p...cn8z, this script can move it without the private key. Be careful: xOnlyKey and payload are hard-coded. Another target address requires different values. What are those values? They become known only after the P2TR output has been spent.

Thus, even if we know an on-chain address has funds and a vulnerable script, we cannot touch the money without the entire tree structure and the xOnlyKey and payload values. From this perspective, Bitcoin assets are still highly secure.

  1. Could the author have sent money to another P2TR output with the same vulnerability and forgotten to withdraw it?

Another example script, audit-taproot-neighborhood.mjs, checks this. The result was no. The author moved all funds from every related address; no such elementary vulnerability remained.

Scanning the Entire Network

This program scans the Bitcoin network for similar script vulnerabilities: tapscript_weak_finder

Its logic is:

  1. Scan every transaction in every block and identify Taproot scripts with version 0xc0.
  2. Use a lightweight interpreter implemented in the program to process witness parameters.
  3. Test whether the script can pass without a signature using three parameter strategies:
    1. Arrange [01, <empty signature>] for zero, one, and two parameters, producing twelve combinations.
    2. Expand the atoms from 01 and <empty signature> to small integers and constants such as 02, 03, true, and false.
    3. For previously revealed witness parameters such as [[data, sigA, sigB]], try variants including [data, "", sigB] and [data, sigA, ""].
  4. Record scripts that require no signature, always return OP_SUCCESS80, or use public-key types not yet defined by the specification.

Scan Results

Of course I will share the results. Running this open-source program will produce the same result and conclusion. I scanned from block 709632, where Taproot activated on mainnet, through block 959701, the latest height at the time.

The program scanned 71,037 blocks, 127,119,410 transactions, and 1,000,061 Taproot addresses. Of these, 427,378 were scripts in which the program recognized no problem, while 12 were potentially vulnerable weak scripts.

Among those 12, eight were weak scripts requiring no signature, one was a non-vulnerable script that always returned OP_SUCCESS80, and three were non-vulnerable scripts containing opcodes unsupported by the scanner’s interpreter.

Result Analysis

Let us examine the potentially weak scripts one by one. The raw, unprocessed output is in result_report.json, from which the addresses, transactions, and scripts below can be reconstructed.

1

The first Taproot address appeared at block 758737: bc1p...2354

It appeared in two abnormal transactions:

The conclusion first: these were created by highly professional developers to demonstrate Bitcoin mainnet’s BIP342 rules.

The first transaction, 00c1...c40e, contains two witness parameters and was used for testing.

The second, 73be...2a7e, is outrageous: it contains 500,001 witness parameters, while its decoded OP_RETURN text says “you’ll run cln. and you’ll be happy.”

Tapscript normally allows no more than 1,000 elements in the initial stack. The transaction succeeded with 500,001 because BIP342 specifies that if a script contains OP_SUCCESSx, it succeeds regardless of its element count. OP_SUCCESSx consists of opcodes not currently defined but reserved for possible future use, so BIP342 specifies this behavior.

The transaction was clearly demonstrating that situation. Its enormous parameter count cost more than $700 in fees.

2

The next addresses appeared at block 771740:

These three scripts contained OP_CHECKSEQUENCEVERIFY, which the scanner did not implement and therefore marked as abnormal. The opcode requires waiting a specified number of blocks before funds can be used and is typical of timelock scripts.

3

At block 775722, the scanner found bc1p...amq6.

This script had a genuine logic vulnerability and allowed funds to move without any signature. It was clearly an Ordinals data wrapper, perhaps created by a developer experimenting with an Ordinals-like protocol. All funds were transferred out at the time, so the address is now empty.

There were several other addresses, not many, mostly Ordinals tests, so I will not analyze each in detail:

Conclusion

I scanned every block and transaction from Taproot activation through the present. A small number of scripts resembling the vulnerability described in How to handle weak scripts? really did appear, but none of those addresses still held funds.

This investigation also suggests two questions:

  1. Are there more complex kinds of script vulnerability on Bitcoin?
  2. Could Ethereum script vulnerabilities be scanned using similar methods?