-
-
Save ajtowns/53e0f735f4d5c06a681429d937200aa5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
anyprevout based eltoo design with only two participants, A and B | |
funding transaction is multisig A,B | |
for each channel state from 0..n, create transactions: | |
UA: | |
spends funding tx, A holds B's signatures, SIGHASH_ALL, no ANYPREVOUT | |
reveals state number in nlocktime/nsequence | |
all funds go to: | |
tapscript 1: "1 CHECKSIGVERIFY n CLTV" where n is state number+500M | |
tapscript 2: "sig_G(S,ANYPREVOUTANYSCRIPT|ALL) G CHECKSIG" | |
internal pubkey: musig(A,B)/1 | |
has ephemeral output for CPFP fees | |
reveals "sig_G(S,ANYPREVOUTANYSCRIPT|ALL)" via the annex | |
UB: | |
same thing, but swapping A/B, and internal pubkey is musig(A,B)/2 | |
S: | |
spends UA or UB via tapscript 2 | |
nSequence set to shared delay | |
outputs go to normal balance/htlcs/etc | |
RA: | |
spends "UB", outputs go to normal balance/htlcs/etc | |
no nSequence delay | |
nlocktime set to state number+500M+1 | |
A holds B's partial ANYPREVOUTANYSCRIPT signature | |
RB: likewise, swapping A/B | |
Logic for A is: | |
* every update, you calculate three signatures (F->UB, UA->RB and sig_G(S)) and send the first two | |
* when doing a unilateral close, you finish signing UA and publish it, | |
then either wait until you can post S, or observe B publishing RB, | |
at which point you claim your balance/htlcs | |
* if B does a unilateral close, you publish RA in order to access your funds immediately, | |
(and also prevent B from publishing an out of date S if their UB was out of date) | |
If B publishes UB before you have the signature for RA, then you can wait for the the shared delay to complete | |
and publish S yourself. | |
Griefing is prevented because after starting a unilateral close, you can't publish any more transactions | |
until either the shared delay timeout finishes, or the other party publishes a transaction. | |
Pinning is prevented because RA can be published on top of UA while it's still in the mempool | |
(and as RA only has an APOAS signature, RA can spend UA's ephemeral output). | |
To add penalties: | |
- change UA/UB to an SINGLE|ANYONECANPAY signature, skip ephemeral output | |
- change the output amount for UA/UB to be channel-capacity + penalty | |
- split S into SA/SB - UA sends to SA, UB sends to SB; SA adds penalty to A's balance, SB adds penalty to B's balance | |
- decrement RA/RB's nLockTime by one, so that they can only spend UB/UA from *prior* states | |
- RA adds penalty to A's balance, RB adds penalty to B's balance | |
To avoid the annex commitment: | |
- the annex commitment is needed in case B publishes an old UB -- in that case for A to spend it with the current | |
RA, A needs to derive a path to tapscript 1, which requires knowing S | |
- so instead we drop the commitment to S entirely and replace it with a scriptless scripts approach. | |
- we drop UA/UB's second tapscript | |
- we send our half of the musig to spend UB to S to B | |
- we no longer send a signature spending F to UB, but instead a partial signature, dependent on | |
revealing B's signature spending UB to RA. | |
- thus, as soon as we see UB on-chain, we can either spend with our latest agreed RA; or we can used B's signature | |
to finish calculating the newest RA and spend with that | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Funding transaction is F. | |
Transactions for state n are: | |
UA.n CA.n SA.n RA.n (publishable by A) | |
UB.n CB.n SB.n RB.n (publishable by B) | |
The valid payment paths are: | |
F -> money (cooperative close) | |
F -> UA.n -(delay)-> SA.n -> money (unilateral close) | |
F -> UA.n -> CB.n -> money (semi-cooperative close) | |
F -> UA.k -> RB.n -> money (attempted cheating by A, k < n) | |
F -> UB.n -(delay)-> SB.n -> money (unilateral close) | |
F -> UB.n -> CA.n -> money (semi-cooperative close) | |
F -> UB.k -> RA.n -> money (attempted cheating by B, k < n) | |
Keys are A, B, P=musig(A,B), Pa = musig(A,B,1), Pb = musig(A,B,2) | |
F pays to P | |
We describe UA.n, CA.n, SA.n and RA.n only; the others just have A/B | |
(and Pa/Pb) swapped | |
F: | |
input: whatever | |
output: | |
* value = channel capacity | |
scriptPubKey = | |
internal key: P | |
tapscript: "apo(A) CHECKSIG NOTIF 1 ELSE apo(B) ENDIF CHECKSIG" | |
(apo(x) is just X as a bip118 pubkey, ie prefixed with 1, | |
script is "or(pk(1),and(pk(A),pk(B)))" | |
aka "c:andor(pk(A),pk_k(1),pk_k(B))") | |
* whatever | |
UA.n: | |
locktime: encodes lower 24 bits of "n" | |
input: | |
* F | |
- (keypath, musig, SINGLE|ANYONECANPAY) | |
- or (tapscript, musig, SINGLE|ANYPREVOUT) [DualFund] | |
- or (tapscript, sig(B, SINGLE or SINGLE|ANYPREVOUT)) | |
nsequence encodes upper 24 bits of "n" | |
output: | |
* value = capacity + penalty | |
scriptPubKey = | |
internal key: Pa | |
tapscript: "IF CODESEP n OP_CLTV DROP ENDIF 1 CHECKSIG" | |
SA.n: | |
input: | |
* UA.n nSequence = to-self-delay | |
- (keypath, musig, ALL) | |
- or (tapscript, musig, codesep=FFFF, ALL|ANYPREVOUT) [DualFund] | |
output: | |
* distribute according to channel state, with A's balance increased | |
by "penalty" | |
CA.n: | |
input: | |
* UB.n nSequence = 0 | |
- (keypath, musig, ALL) | |
- or (tapscript, musig, codesep=FFFF, ALL|ANYPREVOUT) [DualFund] | |
output: | |
* distribute according to channel state, with B's balance increased | |
by "penalty" | |
RA.n: | |
nlocktime=500,000,000 + n | |
input: | |
* UB.k nSequence = 0 | |
- (tapscript, musig, codesep=1, ALL|ANYPREVOUTANYSCRIPT) | |
output: | |
* distribute according to channel state, with A's balance increased by | |
"penalty" | |
When A proposes a new state, she needs to provide the following signatures | |
to B: | |
- spending F to UB.n via tapscript. A creates an adaptor signature | |
that requires B to reveal their signature on CA.n to complete. | |
- musig2 spending UB.n to SB.n | |
- musig2 spending UA.n to CB.n | |
- musig2 spending UA.k to RB.n | |
After receiving a signature for UB.n to CA.n, should provide B with a | |
musig2 signature spending F to UB.n. | |
This requires pre-sharing 8 musig2 nonce pairs in advance of each state | |
update. | |
For the adaptor signature for F->UB.n, A calculates: | |
* musig2 public nonces for UB.n->CB.n, giving Ra and Rb | |
* B's signature target for UB.n->CB.n, ie: | |
T = Rb + H(Ra+Rb, muA*A + muB*B, sigmsg(CB.n))*muB*B | |
* an adaptor signature for F to UB.n via tapscript path: | |
s' = r + H(R+T, A, sigmsg(UB.n))*A | |
based on T above, with fresh nonce R, sends (s', R) to B, | |
and stores s'. | |
B can use this signature on chain, by first calculating t s.t t*G=T | |
(as B knows the discrete log of both Rb and B), then setting s=(s'+t) | |
with (s,R) being a valid signature by A to spend F to UA.x. | |
If A sees the signature on chain, she can calculate t=(s-s'), and combine | |
that with her own partial musig (u,Ra) to produce a valid signature | |
(t+u,Ra+Rb) spending UB.n to CB.n. | |
[DualFund] used if F is dual funded and not yet confirmed, meaning txid | |
may not be known | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment