Eltoo on LN: Idea to Implementation
Greg “instagibbs” Sanders
Idea
latest state output
funding output
“rebinding”
Update transaction
settlement transaction
Idea
Building out end to end specification/implementation of eltoo channels for Core Lightning, with feedback from community and Inquisition signet/Liquid operation
Deciding if BIP118(ANYPREVOUT) sufficient for useful eltoo, or are more expressive changes required
Seeing how simple can we make the protocol
Project Goals
Use Taproot’s “unknown pubkey” type to opt into new signature hash that omits prevout(and sometimes execution script/value)
Consensus Changes
Package Relay: Allow child to pay for 0/low-fee parent to get into mempool
Anti-Pinning Measures: “[bitcoin-dev] New transaction policies (nVersion=3) for contracting protocols”
Assumed Mempool Changes
Non-empty Taproot Annex relay, ala OP_RETURN (or a spare 32 bytes otherwise lying around committed to by signatures?)
“Ephemeral Anchors”:
Assumed Mempool Changes
Transaction Flow
> Alice & Bob do N channel updates
> Alice gets Update T-1 mined
> Bob submits Update T+1 within shared_delay blocks
> Bob sweeps HTLC/funds after shared_delay blocks
HTLC Forwards
LN Today
Eltoo-based Draft BOLT:
Two unexpected tricks required
State Output: tr(aggregated_key, {EXPR_UPDATE(n+1), EXPR_SETTLE(n)})
EXPR_SETTLE(n) = <CovSig(TL(n))> <1_G> OP_CHECKSIG
EXPR_UPDATE(x) = and(pk(aggregated_key),after(TL(x)))
Transaction Structure
State Output: tr(aggregated_key, {EXPR_UPDATE(n+1), EXPR_SETTLE(n)})
EXPR_SETTLE(n) = <CovSig(TL(n))> <1_G> OP_CHECKSIG
EXPR_UPDATE(x) = and(pk(aggregated_key),after(TL(x)))
Transaction Structure
Alice+Bob MuSig2, commit to update and settle path
State Output: tr(aggregated_key, {EXPR_UPDATE(n+1), EXPR_SETTLE(n)})
EXPR_SETTLE(n) = <CovSig(TL(n))> <1_G> OP_CHECKSIG
EXPR_UPDATE(x) = and(pk(aggregated_key),after(TL(x)))
Transaction Structure
Signature-in-script, since ANYPREVOUTANYSCRIPT omits script as well
CTV-like trick to avoid “hostage” situation with no message roundtrips
State Output: tr(aggregated_key, {EXPR_UPDATE(n+1), EXPR_SETTLE(n)})
EXPR_SETTLE(n) = <CovSig(TL(n))> <1_G> OP_CHECKSIG
EXPR_UPDATE(x) = and(pk(aggregated_key),after(TL(x)))
Transaction Structure
Public key generator G, privkey of “1”
“Pre-signed” by anyone at any time with access to settlement state
State Output: tr(aggregated_key, {EXPR_UPDATE(n+1), EXPR_SETTLE(n)})
EXPR_SETTLE(n) = <CovSig(TL(n))> <1_G> OP_CHECKSIG
EXPR_UPDATE(x) = and(pk(aggregated_key),after(TL(x)))
Transaction Structure
Update path using Alice+Bob MuSig2 key, anything “newer” can spend this
State Output: tr(aggregated_key, {EXPR_UPDATE(n+1), EXPR_SETTLE(n)})
EXPR_SETTLE(n) = <CovSig(TL(n))> <1_G> OP_CHECKSIG
EXPR_UPDATE(x) = and(pk(aggregated_key),after(TL(x)))
Transaction Structure
Oops: If old update tx gets mined, O(1) honest node can’t recompute this, as it has forgotten old state! No way of making control block to spend via update path :(
EXPR_SETTLE(n): <CovSig(TL(n))> <1_G> OP_CHECKSIG
Transaction Structure
What’s Working
What’s Not
LN today is great!
Complexity is the enemy of security
Consensus BIP mistakes tend to be discovered once built on
Wallets 🤝 V3 transactions + Ephemeral Anchors
Final Thoughts
Special thanks to:
Blockstream
And discussion/review: AJ, remeyers, ariard, darosior, glozow, cdecker, tbast, rusty, sanket1729, and many others
##eltoo on libera.chat gsanders@blockstream.com/@theinstagibbs
Thanks!