ln-symmetry started with a simple idea: Someone should take the eltoo
idea and take it as far as possible to prove out the concept, without requiring community consensus required
actions like softforks before proving it out.
The end result is research-level quality software, with basic functional tests of the common cases:
Ephemeral anchors + v3 usage for anti-pin
channel opens
payments (revamped + simplified channel state machine)
payments with hops (fast-forwards, aka 0.5 RTT forwards!)
unilateral closes
Reconnection logic at different stages of channel updates
It did not implement:
cooperative closes: There’s a lot of spec work to make this better for today’s channels, and it would nearly be copy-paste, so I didn’t bother
Proper persistence of the new keytypes/fields. In other words, if you restart your node, you’ll crash. Couldn’t quite figure it out
anchor spending support: Rusty was working hard at the time on option_anchors support; rebasing everything on top now should fix this
Gossip for these channels. So if you’re playing around with it, you need to use them as private channels
KISS: I felt almost too stupid to understand the current BOLTs. How much shorter and simpler can I make them with a new channel type using APO-like functionality? Answer is quite a bit, at least within BOLTs 2, 3, and 5.
De-risking eltoo technical challenges: What are we missing in our handwaves of an idea? There’s always something. Is that something fatal to the idea?
Key Takeaways
Pinning is super hard to avoid. At least 1/3 of the work I bet was designing the system to be as robust against pinning as possible. I think I completed the task, except for HTLC-Success cases which actually need to be pre-signed to be pin-resistant. It’s my biggest remaining design bug I know about.
eltoo-style channels have even longer than expect htlc expiry deltas to stay secure. No one had actually worked through the state machine before.
Much more flexible fee management: All outputs in settlement transactions(except HTLC-Success paths as noted before) are free to spend, allowing endogenous fees from all other output cases of balance and HTLC outputs. Reduces the burden on having a utxo pool for fees to only pay maximum once per unilteral close during the “challenge” period.
CTV(emulation) ended up being useful! It removed the necessity of round-trips from the payment protocol, allowing for “fast forwards” that are extremely simple, and would likely reduce payment times if widely adopted.
I’m less convinced than ever that penalties are the way forward in the future. Penalties are, in practice against a competent adversary, only as large as their reserve requirements, so “make sure it works right” seems to be the best deterrent. Make the expected chance of success essentially zero, and the incentive should be to collaboratively close to save fees.
Key work artifacts
Tons of anti-pinning research resulting in many milestones in the Package Relay Project including my novel invention of Ephemeral Anchors which builds on top of “V3” concept.
Incredible work and post. This kind of content makes me glad I’m subscribed to emails.
Can you elaborate on precisely why this is the case. I’m not sure I intuit why this is necessary. In theory the injunction period in the existing protocol is two rounds max: breach and justice. In eltoo, my understanding is that it is still two rounds: breach and fast-forward.
I’m thoroughly unsurprised by this as CTV/Covenant schemes can, in principle, give you state-graph cut-through on any state machine encoded into Bitcoin transactions. However, I do have a question as to whether alternative covenant schemes also give you this property? I suspect but am not certain that they can.
Fairly simple to explain and obvious in retrospect:
Alice and Bob do some channel ops
Alice then sends update_signed which is a MuSig2 partial sig
Bob smirks now that he’s the only one with the full signature, goes silent
Alice goes to chain with prior state, waits however long the “update phase” has a timelock for the “settle transaction” to be valid
Right before timelock expires, Bob publishes the final state
wait for this timelock to expire… publish settle tx
finally can settle HTLCs
that’s 2x the update phase timelock, not 1x. I know there’s some “sandwiching” you can do to compress it a bit(conversations between LN engineers I didn’t understand!), but it’s north of my once expected 1x.
In spec/implementation, we only pass around a single nonce per round. This is correct unless you use the “optimistic” updates from “simplified update” protocol proposed originally by Rusty. In other words, you can never send updates out of turn, with the associated latency. You have to prod your counter-party to give up their turn first and wait for them to respond. To fix the out of turn optimistic updates, you would need another nonce and associated logic to manage it safely.
I want to propose a new state; but I can’t just give you signed transactions for the new state, unless I’m sure I can claim my funds from that state. Normally that would mean I give you the signature for the update tx after I’ve received your signature for the settlement tx. But that’s an extra round.
So instead have the update tx use CTV (or APO-simulating-CTV) to allow spending via a settlement tx without the settlement tx needing a signature.
But then that means that spending the update tx is only possible if you know the CTV commitment to the settlement tx, which would normally mean O(n) storage, since you need to be able to spend every historical update tx to the current one, and each update tx can have a different settlement tx.
So instead we have the update tx store that information in the annex field, so that it can be recovered if an old update tx is broadcast, without needing to be stored.
The alternative approach is:
Give a partial adaptor signature for the update tx, where Bob completing the signature allows Alice to reconstruct a valid signature to broadcast the settlement tx
But that means doing adaptor signatures, and (I think) requires the spend of the update output in the settlement tx to have two CHECKSIGs (Alice with a normal sig, Bob via the adaptor recovery), so got put in the “too hard” basket for now.
I had a go at trying this out and failed pretty miserably. Some problems:
the feature bit used for eltoo was 50 which overlaps with zeroconf channels. should perhaps be 0xcc32? (ie the two byte ascii string ‘L2’, plus 32768 to put it into the experimental feature bits range) of course, if you do that, cln listpeers decides to put 13000 “0” characters in the “features” field, which gets pretty annoying
trying to create non-eltoo channels is just broken anyway, so connecting to normal peers isn’t useful
creating a channel between two nodes does seem to work
paying an invoice doesn’t seem to work: it couldn’t find a path to its peer somehow
cooperative shutdown doesn’t seem to work; the closing node thinks its closing, the other node doesn’t (eltoo_channeld-chan#1: billboard perm: Bad shutdown maybe?).
unilateral shutdown also doesn’t seem to work; segfault/nullptr deref? after sending the unilateral shutdown when trying to resolve txid
restarting nodes after stopping/crashing doesn’t work – the per_commit_remote value stored in the db isn’t a valid secp point, so loading it fails
I did manage to broadcast my update and settlement txs and pay for them with ephemeral anchors manually (the update tx was in the logs, and the settlement tx was able to be reconstructed just by dropping in the update tx’s txid).
I then got stuck trying to figure out the private keys in order to actually reclaim those funds, and that’s where I’m at for now.
Yeah, I’m not sure what was going on there, might just be that the amounts I was trying were too large (I think I tried 25 sBTC on a 50 sBTC channel?) and splitting into smaller amounts was what confused things or something. When I tried debugging I got lost in the maze of other failures
If anyone wants to try out instagibbs’ CLN implementation, I was able successfully force close a channel on inquisition signet using this method:
Let me know if it works/breaks for you!
Edit: here’s the entire thing in markdown!
The following is one way to broadcast an LN-symmetry (formerly eltoo) settlement tx on inquisition signet, using instagibbs’ original APO eltoo implementation on CLN.
build and install inquisition signet, you will need this fork of bitcoind for support of APO, ephemeral anchors, and taproot annex blobs
git clone https://github.com/bitcoin-inquisition/bitcoin
cd bitcoin
./autogen.sh
./configure
make -j$(( $(nproc) - 1))
sudo make install
build and install instagibbs’ eltoo_support branch of CLN
cd ..
git clone https://github.com/instagibbs/lightning -b eltoo_support
cd lightning
./configure --enable-developer --enable-experimental-features
make -j$(( $(nproc) - 1))
sudo make install
launch bitcoind on signet and make sure it connects to ajtowns’ inquisition node
If nothing else works you can ask for signet coins on IRC in #bitcoin-signet
wait for IBD to complete, then launch 2 inquisition nodes using startup_regtest.sh
sed -i 's/regtest/signet/g' contrib/startup_regtest.sh
source contrib/startup_regtest.sh
start_ln
Note: this branch of CLN does not support restarting the node, so you will have to nuke your node and start over if your node stops for any reason. So make sure you send all signet coins out of the node before stopping it, or your coins will be lost.
Send some coins to Node 1
l1-cli newaddr
bt-cli sendtoaddress <addr>
Wait for the deposit to confirm, then open a 100k-sat channel from Node 1 to Node 2
Wait a few confirmations for the channel to become active, then optionally pay a 10k-sat invoice from Node 1 to Node 2:
l2-cli invoice 10000000 test test
l1-cli pay <bolt11>
Now let’s force close the channel! (This implementation doesn’t support mutual closes, so don’t try or your funds will get stuck)
Get the raw last_update_tx from the listpeers call:
l1-cli listpeers
Broadcast it:
bt-cli sendrawtransaction <last_update_tx>
The tx will get stuck in your node’s mempool because it has an ephemeral anchor, which must be spent in order for the tx to be relayed.
CPFP using the ephemeral anchor. Take note of the txid of both the ephemeral anchor and one of our bitcoind wallet’s UTXOs. We’ll also need a new address to receive our CPFP:
Construct a new tx that spends both outputs back to our bitcoind wallet. I used 1,000 sats for the fee, probably a bit overkill, but this is fake money anyway:
Check to make sure the update tx has been relayed:
bt-cli getmempoolentry <update-txid>
If it says "unbroadcast": false, you’re good to go.
Once it gets confirmed in a block, you should be able to see the update tx at mempool - Bitcoin Explorer. If both txs say doesn’t show up in the next block, reach out to #bitcoin-signet and see if there’s anything wrong with inquisition relay.
Wait a few blocks for the challenge period to end, then grab the settlement tx and broadcast it too:
Make sure you CPFP this tx as well, as above, as it also has an ephemeral anchor.
Wait for the settlement tx to confirm. Find it on mempool.space as before.
To save your coins, wait 100 blocks for the channel to be forgotten, then sweep all funds from CLN back into bitcoind. Now you can safely stop your CLN nodes.
Congratulations, you’ve just force closed your first LN-symmetry channel on inqusition signet!
For my own clarification here, BTW: we do this in the current protocol without any issues, but for LN-symmetry we split the tx into two parts (update and settlement), so now the peer could spend the new update and withhold the new settlement.
But then that means that spending the update tx is only possible if you know the CTV commitment to the settlement tx, which would normally mean O(n) storage, since you need to be able to spend every historical update tx to the current one, and each update tx can have a different settlement tx.
AFAICT you only need two, assuming you wait for a reply before sending another update? You would always fast-forward to the latest update tx.
So instead have the update tx use CTV (or APO-simulating-CTV)
Adapter signatures is nicer, BUT note that CTV is more optimal than adapter signatures here! However, neither is actually ideal (requiring another tx for fees): what we want is a commitment scheme which lets the spender extract fee from its own output, and/or bring their own fee input.
Might be talking past each other, but the counter-party may play an old update state to which you’ve forgotten the opening of the taproot commitment to your update. You could remember O(n) fields rather than use the annex, of course.
I like CTV-like approach for another reason: it trivially generalized to multi-party channels. That said, yeah optimally we’d have some sort of TXHASH-like construct for settlement that would allow single tx endo/exo fees depending on how much is locked up in HTLCs and the like.
If they play an old update, you spend it with the new update? Wasn’t that the entire trick? Obviously I’m missing something (I’m guessing some Taproot detail?)
The update tx commits to the settlement tx in the tapscript path, but that means that spending the update tx means you have to know the settlement tx even if you want to do some other spend than the settlement tx, either in order to reconstruct the tapscript that includes the settlement commitment, or to reconstruct the merklepath to some other tapscript (since the merkleroot commits to the script that commits to the settlement tx) or to do a key path signature (since you need to tweak the internal key signature via the merkle root which commits to the script that commits to the settlement tx).
Bit of an update: Was learning to use Claude and got the branch rebased, with a few key updates and bug fixes in roughly a week of back and forth.
What’s in this branch:
Rebased on latest master
Works out of the box on bitcoin-inquisition 29.x on signet with TRUC/eph dust/P2A/1p1c package relay. Does automated anchor bumping for real unlike before (I migrated to a op_return commitment vs annex usage as well)
Support for restarting your node without crashing (lol)
Support co-op close (protocol a little jank similarly to legacy close but enough for testing)
Significantly more test coverage, though still PoC level
The CSFS+CTV variant lands close to the TEMPLATEHASH+CSFS migration
branch in spirit‚ using the post-Taproot opcode set that Inquisition
already activated, with on-chain TxIDs as the validation point. The
constructions are implemented as standalone Python scripts against
the inquisition bitcoind RPC, so they’re straightforward to re-run
on the LNHANCE signet (where CTV and CSFS are also active).
Happy to discuss any of the construction details, especially around
the rebinding ladder.