A Vulnerability on Bitcoin Protocols Using One-Time Signatures

In the realm of BitVM-like protocols, where computations and commitments are verified on-chain through hash-based techniques, one-time signatures (OTS) such as Winternitz are frequently used. These schemes offer a simple and script-compatible way to commit to values and prove computations in Taproot-based contracts.
We discovered a vulnerability that can be exploited by a malicious party to steal funds by forcing a timeout, especially in turn-based protocols. The issue arises when the victim is unable to submit their on-chain response due to Bitcoin's transaction size and policy constraints.
🧩 The Vulnerability
In the Taproot script used on these protocols, parties often rely on OP_SHA256 or OP_HASH160 to verify an input value committed earlier. Typically, this value is used in another transaction by the counterparty to prove that it is incorrect—using a script that verifies the input after verifying it with a one-time signature.
These verification scripts are agreed upon during the protocol's setup phase. The public key of the one-time signature is known in advance, but the private key remains secret until a participant decides to reveal it in order to commit a value.
The problem is that these scripts often do not check the size of the preimage. While the hash result has a fixed size (32 or 20 bytes), the preimage used during verification can be anything up to 520 bytes (the stack element limit in Bitcoin Script). This opens the door to an unexpected attack.
🎯 The Exploit
A malicious party (e.g. the prover) can publish a commitment with a large preimage—say 400 or 500 bytes. Since they know their own verification script and can optimize the witness size, they are able to publish a valid transaction that includes the large preimage and the corresponding signature.
The other party (e.g. the verifier), in turn, attempts to respond by posting a transaction containing their version of the verification script and the same preimage. But this time, the increased witness size might exceed transaction limits, especially when combined with their more complex script or other inputs.
As a result, they cannot post their transaction at all—either because it's non-standard, too large for consensus rules, or too expensive to relay.
Since these protocols are often turn-based, the attacker can now wait out the timeout, win the dispute, and steal the funds.
There is another attack vector that can be exploited. If the client application responsible for decoding the transaction witness expects the preimage to be a fixed-size binary string (20 or 32 bytes), it may be unable to parse the witness and extract the value. This could cause the client to halt with an error, raise an exception, or skip transaction processing altogether. In all these cases, the client would fail to perform the challenge, and if the issue is not fixed in time, the attacker would win.
🔐 Mitigation: Enforce Preimage Size
The fix is simple and should be included in all verification scripts:
OP_DUP OP_SIZE <20 or 32> OP_EQUALVERIFY
Depending on whether you're using OP_HASH160 (20 bytes) or OP_SHA256 (32 bytes), this enforces that the preimage is exactly the size of the hash output, preventing the attack.
In some systems, the design may intentionally allow preimages of smaller size—such as 128-bit values—without introducing any security degradation in the specific application. In such cases, instead of OP_EQUALVERIFY, it may be appropriate to use OP_GREATERTHAN followed by OP_VERIFY to sanitize the witness accordingly.
🛠️ Alternative Mitigations
In addition to enforcing the preimage size in Script, here are two alternative mitigation strategies that can be implemented at the protocol design level:
1. Zero-Knowledge Proof (ZKP) During Setup
During the setup phase, the committing party can provide a zero-knowledge proof that the preimage used to derive the public hash has the expected size (e.g., 32 bytes). This allows the verification script to rely on an out-of-band guarantee, rather than on-chain enforcement. While this adds complexity and cost, it may be suitable in systems already using ZKPs for other purposes.
2. Double Hash Commitment Structure
In protocols using Lamport or similar one-time signature schemes, it's possible to structure commitments so that the first verification script uses a double-hashed value (i.e., H(H(preimage))) as the public key.
- In the initial commitment, the owner reveals a preimage such that H(H(preimage)) matches the expected value.
- The secondary verification script—used by the counterparty—only requires H(preimage) as input.
This ensures that the counterparty never has to publish the original (potentially large) preimage, but only the intermediate hash, which is of fixed and predictable size.
✅ Summary
- This vulnerability affects Taproot-based protocols using one-time signatures (e.g. Winternitz).
- The issue arises when the size of the hash preimage is not constrained.
- A malicious party can use a large preimage to prevent the other party from responding within transaction size limits
- The fix is to enforce preimage size in the script using OP_DUP OP_SIZE
OP_EQUALVERIFY.
If you're building BitVM-style or hash-commitment protocols using Taproot scripts, review your OTS verification logic now.