Skip to content

Instantly share code, notes, and snippets.

Last active August 18, 2023 14:11
Show Gist options
  • Save bigspider/041ebd0842c0dcc74d8af087c1783b63 to your computer and use it in GitHub Desktop.
Save bigspider/041ebd0842c0dcc74d8af087c1783b63 to your computer and use it in GitHub Desktop.
Eltoo with MATT

Eltoo is a mechanism to perform off-chain state updates to a payment channel (or more generally: a state channel). The idea is to combine the following:

  • have an incremental number attached to each state update;
  • allow any state with a higher incremental number to replace the current state;
  • have a timelock on any effective use of the state; that is, any spending condition that requires a certain state should be timelocked, in order to allow other parties in the protocol to publish the latest state, if an attempt to use an old state is performed.

In the following, we assume the case of a 2-party protocol; however, it might be possible to generalize to multiple parties.

Solutions with additional opcodes


Adding an opcode like OP_CHECKSIGFROMSTACK would allow an easy mechanism for eltoo replacement: parties in the contract simply sign the hash of (i, state_i). One spending condition of the contract allows to replace the current state (i, state_i) with (j, state_j) as long as j > i, and the state is signed by both parties. Any other spending conditions that depends on the validity of the published state is timelocked, to allow the other party to update the state if necessary.



Solutions with pure MATT

Fraud proofs (aka the bazooka)

Without CSFS, one can use the fraud proof machinery to simulate CSFS. Interesting that it's possible in theory, but not desirable in practice for such a simple primitive.

Pre-signed state-update UTXOs

In the current semantics of MATT, there is no way to access and work with arbitrary signed data within Script.

In order to obviate to this, we can however take advantage of the data embedding logic for inputs of the transaction, in order to allow a UTXO (containing the state channel contract) to perform on-chain eltoo-style replacement.

Requirement: The semantics of CHECKCONTRACTVERIFY (CCV) must allow inspecting other inputs, rather than just the current input or an output as originally proposed.

Data update UTXO

These (virtual) UTXOs are used to pre-sign specific transactions that can be used to perform an off-chain state update. In order to update the channel to some new state (i, state_i), Alice and Bob would sign transactions sending both DataAnchor UTXOs to a Update utxo with committed data (i, state_i). The only spending path is a script with a key MuSig2(Alice_update_pk, Bob_update_pk), and it checks that the output is an [Onchain](i, state_i) script below.

[Update](i, state_i)

  • (keypath) NUMS
  • MuSig2(Alice_update_pk, Bob_update_pk) CHECKSIG, then send to [Onchain](i, state_i)) using CCV

Remark: could be more efficient to use CTV instead of CCV here! However, the ctv-hash would need to be added in the embedded data, as the taptree of [Update] is referenced in the [Offchain] script spend, and must be immutable across state updates.

Channel structure

MATTOO channels

A channel is created by first signing transactions spending the (virtual) anchor UTXOs to an [Update](0, state_0) output, then committing funds to a UTXO [Offchain] that contains the following spending conditions:

[Offchain] (off-chain execution)

  • (keypath) a MuSig2 aggregate key of both parties.
  • If spent together with a [Update] script with embedded data (i, state_i), send to [Onchain](i, state_i)

This is the structure of the Onchain version of the channel:

[Onchain](i, state_i)

  • (keypath) a MuSig2 aggregate key of both parties.
  • If spent together with a [Update] script with embedded data (j, state_j), and j > i, send to [Onchain](j, state_j)
  • TODO: add application-specific timelocked spending conditions based on the current state


Both parties choose one of their UTXOs and they lock it (that is, not spend it) until off-chain execution is in progress.

Alice creates a transaction spending this UTXO to [Update](0, state_0).

Bob does the same.

They both presign the spend of these (virtual) UTXOs with ANYONECANPAY using the script path, and with first output [Onchain](0, state_0)

Off-chain update

In order to update the state to (i+1, state_{i+1}) off-chain, the parties would pre-sign a spend of the other party's (virtual) [Update](i+1, state_{i+1}) with SIGHASH_ANYONECANPAY, with first output [Onchain](i, state_i).

Note: in a state channel, the party who's "turn" is to move would sign the other party's virtual Update first; in a payment channel, the party whose balance is decreasing signs the other party's Update first.

Cooperative closure

Alice and Bob use the keypath of [Offchain] to close the channel. In the cooperative case, just two P2TR transactions go on-chain, with no script spends.

Moving on-chain (uncooperative update/settlement)

The party that wants to move the contract on-chain publishes:

  • the presigned transaction spending their locked UTXO to create the last [Update](i, state_i)
  • a transaction spending the [Offchain] UTXO using the script path, together with the Update tx, creating the [Onchain](i, state_i) UTXO

Note: a third party could spend the [Update](i, state_i) due to ANYONECANPAY, therefore preventing the normal spending of the [Offchain] UTXO; however, this is pointless since they would have to create the expected [Onchain](i, state_i) output thanks to the CCV in the [Update] UTXO, which can therefore continue the contract execution as expected.

The keypath allows parties to move the contract back off-chain if cooperation is restored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment