Contract-level Relative Timelocks (or, let's talk about ancestry proofs and singletons)

MUON has an interesting interplay here… x.com

Note that you still end up needing some sort of recursive proof inside R here. But MUON does the job of ensuring that no Tx Update can be broadcast without the right R being created for it, and makes Tx Update non-malleable.

Describing the above graph:

Tx Open:

  • Inputs: …
  • Outputs: V_O ← N Sats

V_O.program ← tr(musig(A, B), {CTVHASH(kickoff) CTV})

Tx Kickoff:

  • Inputs: V_O
  • Outputs:
    • R[0] ← 0 sats / dust
    • V_K ← N Sats

V_K.program = tr(musig(A, B), {})

Updates are setup as follows:

Tx Update[i]:

  • Sequence: 2 weeks
  • Inputs V_K
  • Outputs
    • MUON X_i ← 0 Sats
    • Alice ← k*N
    • Bob ← (1-k) *N

Tx Ratchet i:

  • nLockTime i
  • Input R[i]
  • Output
    • R[i+1]

R[i]'s program:

tr(NUMS_H, { ratchet, cospend })

ratchet: <CTVHASH(Ratchet TX i+1)> CTV <musig(a,b)> CSFS [i] CLTV

cospend: 1 GETINPUT <COutpoint(MUON X_i)> EQUALVERIFY <CTVHash(Tx Exit)> CTV

Tx Exit:

  • nSequence: 1 day (min time between last update?)
  • Inputs: R[i] (via cospend path), MUON X_i
  • Outputs: OP_RETURN Update[i].details_to_reconstruct

muon X_i.program: tr(NUMS_H, {<CTVHash(Tx Exit)> CTV 0 GETINPUT <R[i]> EQUALVERIFY })


How signing works:

First you open the protocol to create V_O.

Then you create the updates off of V_K (go ahead and sign – MUON X means a spend must exit).

You then create the ratchet update off of R, and exchange the sigs.

Note that you still end up needing some sort of recursive proof inside R here. But MUON does the job of ensuring that no Tx Update can be broadcast without the right R being created for it, and makes Tx Update non-malleable.

1 Like