bitcoincoding.dev / Experiments

Builds on Christian Decker · eltoo

APO from scratch: a BIP-118 signer and an eltoo state chain

Two steps. First the primitive: an independent BIP-118 (ANYPREVOUT) signer, written outside Bitcoin Core's test framework and cross-validated by Inquisition's consensus code, with an on-chain proof that one signature spends two coins. Then the construction: a three-round eltoo state chain built on it and settled with OP_CHECKTEMPLATEVERIFY.

2026 · 04Inquisition signet✓ 6 tx confirmed37/37 checks passed

The question

Eltoo, proposed by Christian Decker, Rusty Russell and Olaoluwa Osuntokun, lets a payment channel replace its state instead of punishing old states. Its enabling primitive is SIGHASH_ANYPREVOUT (BIP 118): a signature that does not commit to the coin it spends.

Bitcoin Core's wallet and command line cannot produce such a signature, so anyone who wants to exercise APO on Inquisition signet has to build the signer first. Following ajtowns' survey of CTV, APO and CAT activity on signet, this work adds that missing piece and then uses it end to end.

Part 1 · The primitive

A BIP-118 signer, cross-validated by consensus

The signing message Msg118 / Ext118 and the final TaggedHash("TapSighash", 0x00 || Msg118 || Ext118) are implemented in Python straight from the BIP, outside Bitcoin Core's test framework. Inquisition's C++ consensus code rebuilds the same digest from the transaction; if the two disagree by one byte, the transaction is rejected.

What BIP 118 changes is what the message leaves out:

FieldBIP 342 (SigMsg)BIP 118 (Msg118, 0x41)
nVersion, nLockTimeyesyes
sha_prevouts (which coins are spent)yesno
sha_sequencesyesno
input amount and scriptPubKeyyesyes
sha_outputsyesyes
tapleaf_hashyesyes
key_version0x000x01

On-chain proof: one signature, two coins

Two 50,000-sat UTXOs at the same address, under a single-leaf tapscript <0x01||xonly> OP_CHECKSIG. The first spend is signed; its whole witness (a 65-byte signature ending in 0x41, the 35-byte leaf, the 33-byte control block) is copied byte for byte onto a second transaction spending the other coin. Both confirmed in the same block. Changing the output address or amount in the second spend makes it fail: the input can be rebound, the destination cannot.

StepTransactionBlockNote
Spend A (signed)03c0577c…f3a4298280prevout 4b645108…:0
Spend B (witness copied, not re-signed)46091190…1a43298280prevout 543c97f7…:0

The outpoint drops out of the digest, and key_version 0x01 separates the signature domain so an APO signature cannot be replayed against an ordinary key.

Part 2 · The construction

A three-round eltoo state chain

With the signer in hand, every channel state becomes a three-leaf Taproot output. An APO update spends the current state and creates the next; the spent state's CTV leaf loses its only input and can never settle, so only the latest state can pay out. That is the eltoo invariant, executed end to end with current tooling.

each state UTXO (P2TR, three leaves)

  ctv_settle   <template_hash> OP_CHECKTEMPLATEVERIFY       # this state's payout
  apo_update   <0x01 || xonly_pubkey> OP_CHECKSIG          # next state replaces this one
  csv_escape   <2> OP_CHECKSEQUENCEVERIFY OP_DROP <pk> OP_CHECKSIG

fund -> v1 -> v2 -> v3   (two APO updates)
settle v3 through ctv_settle: witness = script + control block, empty stack
StepTransactionBlockNote
Fund → state v1386dbb6a…9c4129805950,000 sats
APO update v1 → v2096e31cc…560129828047,000 sats
APO update v2 → v3091309b7…d7f629828044,000 sats
CTV settlement of v313957f49…f9b729828016,400 / 14,350 / 10,250 sats

The two rebinding transactions in Part 1 are a standalone demonstration, not part of this chain.

Check it yourself

The repository re-checks every claim on this page (no keys, no node: it recomputes the BIP-118 digest for every APO signature).

git clone https://github.com/aaron-recompile/eltoo-apo-state-chain
cd eltoo-apo-state-chain
python3 verify_onchain.py
37/37 checks passed

What it does not show

Discussion

Sources

Technical record · facts.json
{
  "page": "https://bitcoincoding.dev/experiments/eltoo-apo-ctv/",
  "title": "APO from scratch: a BIP-118 signer and an eltoo state chain",
  "context": "Builds on Christian Decker · eltoo",
  "author": "Aaron Zhang (Aaron Recompile)",
  "date": "2026-04-11",
  "network": "Bitcoin Inquisition signet",
  "summary": "Two steps. First the primitive: an independent BIP-118 (ANYPREVOUT) signer, written outside Bitcoin Core's test framework and cross-validated by Inquisition's consensus code, with an on-chain proof that one signature spends two coins. Then the construction: a three-round eltoo state chain built on it and settled with OP_CHECKTEMPLATEVERIFY.",
  "transactions": [
    {
      "role": "Spend A (signed)",
      "txid": "03c0577c1d47da32804d098187644d0eee18b448aded2f427cd02193c070f3a4",
      "block": 298280,
      "explorer": "https://mempool.space/signet/tx/03c0577c1d47da32804d098187644d0eee18b448aded2f427cd02193c070f3a4",
      "note": "prevout 4b645108…:0",
      "part": "Part 1 · The primitive"
    },
    {
      "role": "Spend B (witness copied, not re-signed)",
      "txid": "46091190c74d8fd4b39be67a2e945a19b021850e7f8d9e378f5eb11722ae1a43",
      "block": 298280,
      "explorer": "https://mempool.space/signet/tx/46091190c74d8fd4b39be67a2e945a19b021850e7f8d9e378f5eb11722ae1a43",
      "note": "prevout 543c97f7…:0",
      "part": "Part 1 · The primitive"
    },
    {
      "role": "Fund → state v1",
      "txid": "386dbb6aa23fcc35a69d34e3c0f760b185482467abc936196d3def19d54a9c41",
      "block": 298059,
      "explorer": "https://mempool.space/signet/tx/386dbb6aa23fcc35a69d34e3c0f760b185482467abc936196d3def19d54a9c41",
      "note": "50,000 sats",
      "part": "Part 2 · The construction"
    },
    {
      "role": "APO update v1 → v2",
      "txid": "096e31ccbd8f5460b2730ec4f757ee1b01acf9dde3e3b8cb55fbb534fe195601",
      "block": 298280,
      "explorer": "https://mempool.space/signet/tx/096e31ccbd8f5460b2730ec4f757ee1b01acf9dde3e3b8cb55fbb534fe195601",
      "note": "47,000 sats",
      "part": "Part 2 · The construction"
    },
    {
      "role": "APO update v2 → v3",
      "txid": "091309b73e299436ff12fceda7e54e28c6f1817fdf3405bca3343ad15198d7f6",
      "block": 298280,
      "explorer": "https://mempool.space/signet/tx/091309b73e299436ff12fceda7e54e28c6f1817fdf3405bca3343ad15198d7f6",
      "note": "44,000 sats",
      "part": "Part 2 · The construction"
    },
    {
      "role": "CTV settlement of v3",
      "txid": "13957f49d01aa21ed2aa28df7aa78f357d51a2a2fbfb434af05fdc75ad0ff9b7",
      "block": 298280,
      "explorer": "https://mempool.space/signet/tx/13957f49d01aa21ed2aa28df7aa78f357d51a2a2fbfb434af05fdc75ad0ff9b7",
      "note": "16,400 / 14,350 / 10,250 sats",
      "part": "Part 2 · The construction"
    }
  ],
  "verify": {
    "repository": "https://github.com/aaron-recompile/eltoo-apo-state-chain",
    "command": "python3 verify_onchain.py",
    "result": "37/37 checks passed",
    "needs": "no keys, no node: it recomputes the BIP-118 digest for every APO signature"
  },
  "does_not_show": [
    "No Lightning protocol stack: no BOLT messaging, two-node exchange or HTLC routing.",
    "The CSV timeout leaf is committed in every state but was not exercised.",
    "One demo key signs every update; no FROST or MuSig2 aggregation.",
    "Updates run sequentially; this is the execution layer underneath a channel, not a channel implementation."
  ],
  "sources": [
    {
      "title": "BIP 118: SIGHASH_ANYPREVOUT for Taproot Scripts",
      "url": "https://github.com/bitcoin/bips/blob/master/bip-0118.mediawiki"
    },
    {
      "title": "ajtowns · CTV, APO, CAT activity on signet",
      "url": "https://delvingbitcoin.org/t/ctv-apo-cat-activity-on-signet/1257"
    }
  ],
  "discussion": [
    {
      "title": "Delving Bitcoin · BIP 118 signing from scratch + on-chain rebinding proof (Part 1)",
      "url": "https://delvingbitcoin.org/t/2411"
    },
    {
      "title": "Delving Bitcoin · Eltoo State Chain on Signet: Three Rounds, Six Transactions (Part 2), with a reply from Christian Decker",
      "url": "https://delvingbitcoin.org/t/2413"
    },
    {
      "title": "bitcoindev mailing list · Eltoo / LN-Symmetry on Inquisition Signet: BIP 118 + 3-Round State Chain, with replies from Greg Sanders and Sergio Demian Lerner",
      "url": "https://mailing-list.bitcoindevs.xyz/bitcoindev/CAEmKuUHJEf8R0ZPa--Jm=e1ToT2-f1CUqp+dRLP=XZ+A3mLBzA@mail.gmail.com/"
    }
  ],
  "code": [
    {
      "title": "BIP-118 signer · btcaaron/bip118.py",
      "url": "https://github.com/aaron-recompile/btcaaron/blob/180935dc864c9a36dd725cf8f54a43d183c17b1a/btcaaron/bip118.py"
    },
    {
      "title": "Tests: digest invariance and signing",
      "url": "https://github.com/aaron-recompile/btcaaron/blob/180935dc864c9a36dd725cf8f54a43d183c17b1a/tests/test_bip118.py"
    }
  ]
}