[BitVM3 Bounty] Critical: Operator can forge watchtower-inclusion bitmap to defeat disprove and claim illegitimate Take
Summary
A malicious operator can obtain a Take (Take1/Take2) reimbursement it is not entitled to by forging the included_watchtowers bitmap inside the operator-proof circuit. The forgery is enabled by an unbound Taproot signature check; the forged bitmap then disables the on-chain disprove hashlock — the only protection against a fraudulent operator claim. Result: unauthorized withdrawal / peg insolvency.
Affected components
Circuit (GOATNetwork/bitvm-node):
crates/bitcoin-light-client-circuit/src/signature.rs:45-72 — verify_taproot_leaf_schnorr_signature
crates/bitcoin-light-client-circuit/src/lib.rs:48-52 — OperatorPublicOutputs
crates/bitcoin-light-client-circuit/src/lib.rs:255-291 — watchtower inclusion loop
crates/bitcoin-light-client-circuit/src/lib.rs:595-620 — verify_included_watchtower_challenge
circuits/operator-proof/guest/src/main.rs:22-48 — watchtower tx/prevout/pubkey/script read as PRIVATE prover inputs
Bitcoin-side game (GOATNetwork/BitVM, branch GA):
goat/src/transactions/take1.rs:60-140, goat/src/transactions/take2.rs:69-137,246-259 — Take assembly (no proof verification)
- Malicious operator initiates a peg-out/kickoff for a claim a watchtower can legitimately challenge.
- Honest watchtower publishes its genuine challenge and attempts the disprove hashlock. Because the committed bit is 1, the disprove script fails (disprove_scripts.rs:194-209: disprove only succeeds when the bit is 0).
Two linked defects: (1) the Taproot signature check authenticates a watchtower challenge against attacker-supplied prev_out/pubkey with no binding to the real spent UTXO, letting the operator fabricate "included" challenges and flip inclusion bits; (2) the included_watchtowers bitmap gates the disprove hashlock — setting a bit to 1 makes an honest watchtower's disprove fail. The Take transactions perform no proof verification, so once disprove is neutralized, reimbursement is gated only by a relative timelock.
- spending_tx.input[0].previous_output = OutPoint{ txid: T_real, vout: 0 }.
- verify_taproot_leaf_schnorr_signature(&script, &spending_tx, 0, &prev_out, &P_atk, &sig) returns Ok(()) — inclusion asserted for a fabricated challenge not signed by the real watchtower and not spending the claimed UTXO.
- Bind the verified prevout to the tx's real input (assert supplied prev_out equals the UTXO spent by input[0]; ideally derive it from an SPV inclusion proof of the watchtower-challenge tx).
- Require SPV/header-chain inclusion of each watchtower-challenge tx before setting its inclusion bit — never trust prover-supplied txs.
- Bind the watchtower pubkey to the registered watchtower set.
- Use the real input index (don't hardcode 0 / reuse vout) and prefer SIGHASH_DEFAULT/SIGHASH_ALL with Prevouts::All(...).
[BitVM3 Bounty] Critical: Operator can forge watchtower-inclusion bitmap to defeat disprove and claim illegitimate Take
Summary
A malicious operator can obtain a Take (Take1/Take2) reimbursement it is not entitled to by forging the included_watchtowers bitmap inside the operator-proof circuit. The forgery is enabled by an unbound Taproot signature check; the forged bitmap then disables the on-chain disprove hashlock — the only protection against a fraudulent operator claim. Result: unauthorized withdrawal / peg insolvency.
Affected components
Circuit (GOATNetwork/bitvm-node):
crates/bitcoin-light-client-circuit/src/signature.rs:45-72 — verify_taproot_leaf_schnorr_signature
crates/bitcoin-light-client-circuit/src/lib.rs:48-52 — OperatorPublicOutputs
crates/bitcoin-light-client-circuit/src/lib.rs:255-291 — watchtower inclusion loop
crates/bitcoin-light-client-circuit/src/lib.rs:595-620 — verify_included_watchtower_challenge
circuits/operator-proof/guest/src/main.rs:22-48 — watchtower tx/prevout/pubkey/script read as PRIVATE prover inputs
Bitcoin-side game (GOATNetwork/BitVM, branch GA):
goat/src/transactions/take1.rs:60-140, goat/src/transactions/take2.rs:69-137,246-259 — Take assembly (no proof verification)
Two linked defects: (1) the Taproot signature check authenticates a watchtower challenge against attacker-supplied prev_out/pubkey with no binding to the real spent UTXO, letting the operator fabricate "included" challenges and flip inclusion bits; (2) the included_watchtowers bitmap gates the disprove hashlock — setting a bit to 1 makes an honest watchtower's disprove fail. The Take transactions perform no proof verification, so once disprove is neutralized, reimbursement is gated only by a relative timelock.