Skip to content

Instantly share code, notes, and snippets.

@antiochp
Last active March 5, 2020 12:17
Show Gist options
  • Save antiochp/e54fece52dc408d738bf434a14680988 to your computer and use it in GitHub Desktop.
Save antiochp/e54fece52dc408d738bf434a14680988 to your computer and use it in GitHub Desktop.

Elder Channels (3)

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

Channel Setup

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.

Closing the Channel

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.

Updating Channel State

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.

Assumptions

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

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.

Local Storage Requirements

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

Per Round Requirements

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
@antiochp
Copy link
Author

The outputs OutA and OutB in each "settle" tx define who gets what when the channel is closed so by definition each settle tx can only be built after deciding how the funds are to be distributed on each channel update.

The "close" and "revert" txs in comparison simply move funds in their entirety between the various multisig outputs.
For example from Outchannel to Outclose_A.
These "close" and "revert" txs could potentially be pre-built during initial channel construction as long as something was missing from them and they were not yet full valid txs.

I think we can do this with "adaptor signatures".

Alice and Bob construct Txclose_A_2 such that the kernel requires a secret s to complete the signature.
The corresponding previous state revocation Txrevoke_A_1 is built requiring the same secret s to complete the signature.

Multiple instances of these "close" and "revoke" txs can be built during the initial channel negotiation between Alice and Bob.
Wallet key management would need to be robust and reliable here.

This way we increase local storage requirements as we would now store many partially built "close" and "revoke" txs (one for each possible channel update).
But per-round communication would be minimized.

Per Round Requirements (Assuming pre-built close and revoke txs)

  • 2 multisig "settle" kernels + offsets
  • 2 individual "settle" outputs + rangeproofs

We would also need some mechanism to reveal s each round to allow parties to complete their respective txs locally.


I think we can take this a step further if we pre-build the "settle" tx kernels themselves.
These tx kernels can be built without the specific outputs themselves. Alice and Bob just need to pre-select private keys to use for these "settle" txs.

Then each round reduces to simply building and sharing the final outputs OutA and OutB and their associated rangeproofs. And then revealing s to allow both parties to complete the "close", "revoke" and "settle" txs locally.

Per Round Requirements (Assuming pre-built close, revoke, settle txs)

  • 2 individual "settle" outputs + rangeproofs

Initial Channel Negotiation

Alice and Bob can initiate a payment channel by -

  • funding the multisig output Outchannel
  • deciding on an initial limit on the number of channel updates
  • pre-building the necessary adaptor signature constrained txs for these channel updates

Alice and Bob can always agree to extend the channel by building txs to support additional channel updates so this initial limit can be increased at any time.


I'm not sure if the trade-off between initial setup requirements vs. per-round requirements are worth it but its something to think about.

@antiochp
Copy link
Author

antiochp commented Nov 19, 2019

@tromp Wondering if we can potentially leverage "tx folding" here for channel updates. These state updates are basically self-self txs (just with multisig for multiple participants).

Rather than each channel update introducing a new kernel can we just do it via excess values (treated as adjustments to kernel offset) -

  1. state0 -> state1, excess1
  2. state1 -> state2, excess2
  3. state2 -> state3, excess3

i.e. If latest state is state 3 then previous excess1 and excess2 are known by both participants.
So if an attempt to close and settle an old invalidated state occurs then we can revoke this immediately back to that prior state and then construct a "folded" tx that immediately closes to the latest valid state?

Alice attempts to close old state 1. Bob revokes back to state 1 then "folds" subsequent state updates into final tx to close at current state 3.

Maybe we have discussed this approach before? I don't recall. But I don't think we had a good understanding of "folding" at the time.

Basically we can fold and aggregate big long chains of sequential state updates, to get from any prior invalidated state to the latest state, without incurring the cost of any intermediate kernels and all intermediate outputs get cut-through.
It just becomes an issue of correctly adjusting the final kernel offset to account for intermediate state updates.

@tromp
Copy link

tromp commented Nov 19, 2019

I think we need new kernels to be referenced by the relative height locked kernels of the settling txs.

@antiochp
Copy link
Author

antiochp commented Nov 19, 2019

That is still handled by the close and settle pair of txs.
So state1 can be closed and then settled (with a relative lock height between the close and settle txs).
But close1 can also be revoked if state2 is known (the revoke tx is built as part of the state update).

So channel state updates are handled via folding: state1 -> state2 -> state3 -> ... state_n
Channel close involves a tx moving state_n to close_n with a corresponding kernel.
Channel settle involves spending close_n to settleA and settleB with a relative lock height against the close kernel.

Revocation is handled by allowing close_3 to be opened back to state_4, which can then be immediately folded to state_n and closed.
If revocation always progresses to next state then funds cannot be locked up indefinitely by repeated close and revoke txs.

@tromp
Copy link

tromp commented Nov 19, 2019

Are the state_i outputs?
Can you elaborate what you are proposing to change from the above Per Round Requirements:
2 multisig "revoke" kernels + offsets
2 multisig "close" kernels + offsets
2 multisig "settle" kernels + offsets
2 individual "settle" outputs + rangeproofs
?

@antiochp
Copy link
Author

antiochp commented Nov 19, 2019

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

@tromp
Copy link

tromp commented Nov 19, 2019

So this is an outline for the next iteration, Elder Channels (4), where each channel payment gets a new Close output, with a single close tx for both Alice and Bob, where the previous close tx is revoked by the paying party revealing their previous excess?
I think that's a nice proposal, worthy of its own page (maybe linking back to all previous versions)...

@antiochp
Copy link
Author

Yes - planning on putting (another) one together. 👍

@antiochp
Copy link
Author

I think that rough proposal for Elder Channels (4) is fundamentally flawed.
Any excess revealed for say state1 -> state2 can simply be used to "reverse" the tx, state2 -> state1 in this case.
So I suspect it is possible to transition back to old invalid states even after attempting to close the latest state - either locking funds up indefinitely or potentially settling the channel at old invalid state.

@tromp
Copy link

tromp commented Nov 20, 2019

If a channel payment from Alice to Bob takes the channel from initial state1 to state2, then Alice reveals her excess from Out_channel to Out_close1.
Bob can use this to revert Alice's obsolete close tx, with a new tx that spends Out_close1 to (Out_channel to) Out_close2, which Alice cannot undo, as she doesn't know Bob's excess from Out_channel to Out_close2.

@antiochp
Copy link
Author

which Alice cannot undo, as she doesn't know Bob's excess from Out_channel to Out_close2.

Yes thanks - that's the bit I was missing 👍. The excess necessary to revoke has not yet been revealed for the latest closed state.

@antiochp
Copy link
Author

antiochp commented Mar 5, 2020

Linking here as I had not recognized quite how similar this is to the earlier concept of triggers described here -https://lists.launchpad.net/mimblewimble/msg00025.html

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