A payment channel can be opened between two endpoints Alice and Bob.
3 multisig outputs are created to maintain channel funds. One "channel" output holding funds while the channel is active and two "close" outputs used to hold funds when closing the channel, one for each endpoint.
- Outchannel
- Outclose_A
- Outclose_B
A pair of multisig "close" and "settle" transactions are built. These are specific to Alice and a matching pair are built for Bob. The "close" tx for Alice will spend funds from the shared "channel" output to Alice's "close" output. The "settle" tx will spend funds from the "close" output and release them out to Alice and Bob.
The "settle" kernel has a relative lock height on the "close" kernel. After closing the channel there is a delay before the funds can settle and be released to Alice and Bob.
Txclose_A_0 (Inchannel -> Outclose_A, Kernclose_A_0)
Txsettle_A_0 (Inclose_A -> [OutA', OutB'], Kernsettle_A_0,close_A_0,1440)
Alice gets their "close" tx. Bob gets his matching "close" tx. Both Alice and Bob get both "settle" txs, to prevent funds getting locked up in either "close" output.
Once these "close" and "settle" txs have been negotiated a tx can be built to fund the multisig "channel" output.
Txfund ([InA, InB] -> Outchannel, Kernfund)
The channel is open once the funding tx has been broadcast and confirmed.
Alice and Bob can negotiate a mutual channel close by building a tx that release the funds from the channel.
Txmut (Inchannel -> [OutA', OutB'], Kernmut)
Alternatively they can decide to unilaterally close the channel by broadcasting their multsig "close" tx built earlier.
The original multisig "channel" output and two multisig "close" multisig outputs exist for the duration of the channel.
To update the state of the channel a new pair of "close" and "settle" txs are negotiated, with the new "settle" tx distributing funds to Alice and Bob as necessary.
The initial channel state may release funds 50/50 to Alice and Bob with the updated state releasing funds 60/40 for example.
Txclose_A_1 (Inchannel -> Outclose_A, Kernclose_A_1)
Txsettle_A_1 (Inclose_A -> [OutA', OutB'], Kernsettle_A_1,close_A_1,1440)
These txs are for Alice and a matching pair of txs are built for Bob.
Note we reuse the existing "channel" and "close" outputs. Each "settle" kernel has a relative lock height against against the "close" kernel. This constrains each "settle" to the corresponding "close".
At this point Alice can close the latest state (1) as before. There is a problem here though as they can also attempt to close a previous (revoked) state to claim an incorrect split of funds.
To prevent this a "revoke" tx is also built that spends funds from a multisig "close" output back to the multisig "channel" output. These are negotiated such that Bob can revoke an attempt to close a previous state by Alice.
Txrevoke_B_0 (Inclose_A -> Outchannel, Kernrevoke_B_0,close_A_0,0)
This tx kernel has a relative lock height of 0 against the corresponding "close" kernel. Every "close" has a corresponding "settle" and every previous close has a corresponding "revoke".
The most recent "close" is the only state that cannot be revoked.
A "revoke" can always be broadcast before the corresponding "settle" due to the different relative lock heights.
For every previous "close" tx attributable to Alice, Bob possesses the corrresponding "revoke" tx.
If Alice were to attempt to close a previous state, Bob would immediately revoke this attempt and close the channel at the latest state themselves. Bob can aggregate and cut-through the "revoke" and "close" txs.
Txclose_A_0 (Inchannel -> Outclose_A, Kernclose_A_0)
Txrevoke_B_0 (Inclose_A -> Outchannel, Kernrevoke_B_0,close_A_0,0)
Txclose_B_1 (Inchannel -> Outclose_B, Kernclose_B_1)
=> Txrevoke_close_B (Inclose_A -> Outclose_B, [Kernrevoke_B_0,close_A_0,0, Kernclose_B_1])
This aggregate tx spends from the previous close state attributable to Alice into the latest close state attributable to Bob.
This latest close state cannot be revoked and after the delay, Bob (or Alice) can settle the channel and release funds according to the latest channel state.
Note it is unsafe for Bob to leave the channel open if Alice ever attempts to close a previous state. The delay between the "close" and "settle" starts on first entry to the state and does not reset. Alice could attempt a second close on that previous state and settle immediately.
Bob would be unwise to attempt closing at a different old state for the same reasons.
Bob must immediately "revoke and close" if Alice ever attempts to close on an old channel state.
Bob must broadcast only the aggregate cut-through "revoke and close" tx. If the individual "revoke" tx was ever revealed, Alice could use it to lock funds up by repeatedly closing and revoking until the delay expired. The kernel offset in the "revoke and close" tx prevents Alice from recovering the individual "revoke" tx.
The "settle" tx assumes support for duplicate UTXOs in Grin/MW. This is not currently the case and Alice could lock funds up by creating a duplicate OutA' and preventing the settle tx from confirming. This can be resolved by building separate settle txs for Alice and Bob with each producing a single output. Support for duplicate UTXOs would permit the single settle tx described here to work.
Fees have been ignored. Any tx can have fees "added" through aggregation with another tx carrying the appropriate combined fees. Any "close" tx or "settle" tx involved in the channel design can simply be aggregated with a fee paying tx provided by Alice or Bob. This simplifes the channel design significantly.
Both Alice and Bob must maintain the following -
- 2 latest "settle" txs (1 each for Alice and Bob)
- 1 latest "close" kernel + offset
- all previous "revoke" kernels + offsets
Each state update for the channel requires the following -
- 2 multisig "revoke" kernels + offsets
- 2 multisig "close" kernels + offsets
- 2 multisig "settle" kernels + offsets
- 2 individual "settle" outputs + rangeproofs
Yes each state is now a new output + rangeproof.
I don't think we need the per-party txs with the folding approach.
But each state update now involves a new output + rangeproof.
So I think the per round requirements are something like -
1 multisig state output + rangeproof
1 foldable "update" tx (offset adjustment)
1 "close" kernel + offset
1 "settle" tx (2 outputs + 2 rangeproofs + kernel + offset)
1 "revoke" kernel + offset
3 outputs + 3 rangeproofs + 3 kernels + 3 offsets + 1 offset adjustment
vs. what is proposed in the gist -
2 outputs + 2 rangeproofs + 6 kernels + 6 offsets
But we gain simplicity because we no longer need to maintain per-party close and settle state.
We do not need to identify which party closed and which party can therefore revoke etc.
Edit: Maybe the state update outputs do not need corresponding rangeproofs. If they are always folded (and cut-through) then they never appear on chain. So maybe its actually -
3 outputs + 2 rangeproofs + 3 kernels + 3 offsets + 1 offset adjustment
vs.
2 outputs + 2 rangeproofs + 6 kernels + 6 offsets
i.e. We replace 3 kernels and 3 offsets with a single output and an offset adjustment.
Edit: Need to think about this a bit more but I suspect the "revoke" tx will always be foldable as well and does not require its own kernel.
The only txs that do require a kernel and will appear on chain are the "close" and "settle" txs.
So (assuming this is actually true and works) per round requirements are -
1 multisig state output
1 foldable "update" tx (just an offset adjustment)
1 "close" kernel + offset
1 "settle" kernel + offset
2 "settle" outputs + rangeproofs
1 foldable "revoke" tx (just an offset adjustment)
3 outputs + 2 rangeproofs + 2 kernels + 2 offsets + 2 offset adjustments
vs.
2 outputs + 2 rangeproofs + 6 kernels + 6 offsets