bitcoincoding.dev / Experiments

Answers Robin Linus · BitVM bridges

Spend A only if B is co-spent

A tapscript construction that binds a spend to a specific second UTXO through sha_prevouts, using only OP_CAT and OP_CHECKSIGFROMSTACK. One signature passes both CSFS and CHECKSIG, so the script can read the real transaction.

2026 · 04Inquisition signet✓ 2 tx confirmed30/30 checks passed

The question

Robin Linus's thread How CTV+CSFS improves BitVM bridges needs a connector: an operator's step must consume one specific UTXO. CTV commits to a transaction's outputs, not its inputs, and ajtowns showed that the scriptSig-based workaround is open to substitution.

Can tapscript itself demand that a particular outpoint is spent in the same transaction?

What was built

How it works

witness (bottom -> top): sig(64) post_c(56) post_b(57) post_a(57) pre(10) sha(32) a_outpoint(36)

<B_outpoint> CAT SHA256 SWAP DUP ROT EQUALVERIFY   # sha == SHA256(a_outpoint || B)
SWAP SIZE 10 EQUALVERIFY                            # pre is 10 bytes -> sha sits at offset 10
SWAP CAT SWAP CAT SWAP CAT SWAP CAT                 # pre||sha||post_a||post_b||post_c
<tag[0:32]> <tag[32:64]> CAT SWAP CAT SHA256        # TapSighash(preimage)
OVER SWAP <pk> CHECKSIGFROMSTACK VERIFY             # sig valid over witness preimage
<pk> CHECKSIG                                       # same sig valid over real sighash

If C replaces B, the real sha_prevouts is SHA256(A || C). Supplying it fails the EQUALVERIFY; supplying SHA256(A || B) makes the witness preimage differ from the real one, so the same signature cannot pass both checks.

On chain

StepTransactionBlockNote
Spend A + Bb7959a71…4606300156
Spend A + B (second round)7311da6e…3558300156

A + C (C substituted for B): rejected by testmempoolaccept, never broadcast.

Check it yourself

The repository re-checks every claim on this page (no keys, no node).

git clone https://github.com/aaron-recompile/sighash-prevout-binding
cd sighash-prevout-binding
python3 verify_onchain.py
30/30 checks passed

What it does not show

Sources

Technical record · facts.json
{
  "page": "https://bitcoincoding.dev/experiments/prevout-binding/",
  "title": "Spend A only if B is co-spent",
  "context": "Answers Robin Linus · BitVM bridges",
  "author": "Aaron Zhang (Aaron Recompile)",
  "date": "2026-04-28",
  "network": "Bitcoin Inquisition signet",
  "summary": "A tapscript construction that binds a spend to a specific second UTXO through sha_prevouts, using only OP_CAT and OP_CHECKSIGFROMSTACK. One signature passes both CSFS and CHECKSIG, so the script can read the real transaction.",
  "transactions": [
    {
      "role": "Spend A + B",
      "txid": "b7959a71b74b8bbed69d52c22a9b3b90af176391be2d0b7f33eb1a6473304606",
      "block": 300156,
      "explorer": "https://mempool.space/signet/tx/b7959a71b74b8bbed69d52c22a9b3b90af176391be2d0b7f33eb1a6473304606"
    },
    {
      "role": "Spend A + B (second round)",
      "txid": "7311da6e30886fab6594b633bcb97e4436d49f02ecc244d4bc7c59cb1ea83558",
      "block": 300156,
      "explorer": "https://mempool.space/signet/tx/7311da6e30886fab6594b633bcb97e4436d49f02ecc244d4bc7c59cb1ea83558"
    }
  ],
  "verify": {
    "repository": "https://github.com/aaron-recompile/sighash-prevout-binding",
    "command": "python3 verify_onchain.py",
    "result": "30/30 checks passed",
    "needs": "no keys, no node"
  },
  "does_not_show": [
    "A signet experiment, not a bridge implementation.",
    "Assumes SIGHASH_DEFAULT, no annex and a script-path spend (the fixed 212-byte preimage layout).",
    "Same-signature binding needs a participating signer; keyless covenants are out of scope."
  ],
  "sources": [
    {
      "title": "Delving Bitcoin · How CTV+CSFS improves BitVM bridges, the question (#28)",
      "url": "https://delvingbitcoin.org/t/how-ctv-csfs-improves-bitvm-bridges/1591/28"
    },
    {
      "title": "Delving Bitcoin · the same thread, on-chain evidence (#29)",
      "url": "https://delvingbitcoin.org/t/how-ctv-csfs-improves-bitvm-bridges/1591/29"
    },
    {
      "title": "Delving Bitcoin · Taproot-native prevout binding via sighash preimage decomposition",
      "url": "https://delvingbitcoin.org/t/taproot-native-prevout-binding-via-sighash-preimage-decomposition/2483"
    }
  ],
  "negative_test": "A + C (C substituted for B): rejected by testmempoolaccept, never broadcast."
}