Skip to content

Instantly share code, notes, and snippets.

@t-bast
Created May 12, 2021 17:21
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t-bast/a7779d82cd1ccf6c99f610d05fc7bc2f to your computer and use it in GitHub Desktop.
Save t-bast/a7779d82cd1ccf6c99f610d05fc7bc2f to your computer and use it in GitHub Desktop.
Phoenix in a splicing future (a.k.a death to all swaps)

Phoenix in a splicing future (a.k.a death to all swaps)

Phoenix implements trusted swaps for users' convenience (to allow easy onboarding and offboarding). But it's not a satisfying solution for the following reasons:

  • It uses two on-chain transactions for swap-in where one should be sufficient (one transaction from the user to Acinq followed by a channel open)
  • Swap-out feerates are unpredictable (because we may fund the swap-out with unconfirmed previous outputs) which is frustrating for users
  • It forces Acinq to use its own utxos, which doesn't scale well and is an operational burden
  • If Acinq doesn't have any utxos available and the mempool is completely full, swaps are stuck which is also frustrating for users

Fortunately, the recent work on dual funding and splicing paves the way for much better solutions. A big thanks to niftynei and rustyrussell for their early work on these protocols.

Getting funds in a channel

Let's imagine that we have an existing 100 mBTC channel and want to add 10 mBTC to it (most likely because we don't have sending liquidity). The flow would look like:

Bitcoin Cold Wallet                        Phoenix                           Acinq
       |                                      |                                |
       |                                      |              <1>               |
       |                                      |      can I haz splice plz?     |
       |                                      |------------------------------->|
       |                                      |<-------------------------------|
       |                 <2>                  |           go for it!           |
       |     can I haz bitcoins? <PSBT>       |                                |
       |<-------------------------------------|                                |
       |------------------------------------->|                                |
       |       here you go: <PSBT>            |                                |
       |                                      |              <3>               |
       |                                      |      can I haz sig plz?        |
       |                                      |------------------------------->|
       |                                      |<-------------------------------|
       |                                      |        <tx_complete>           |
       |                                      |                                |

Here is a more detailed breakdown of each step (fees are omitted for simplicity):

  1. Phoenix tells Acinq that it wants to splice 10 mBTC in the channel. Phoenix creates a PSBT with one input (the channel output in the funding tx) and one 110 mBTC output (the new funding output). This PSBT goes through the creator and updater roles, but has no signatures yet.
  2. Phoenix sends this PSBT to its cold wallet, requesting it to contribute 10 mBTC. The cold wallet adds inputs and optionnally a change output. Then the cold wallet signs its inputs (with SIGHASH_ALL).
  3. Phoenix adds its partial signature for the 2-of-2 funding output to the PSBT and sends it to Acinq. The peer verifies the PSBT and adds its partial signature for the 2-of-2 funding output. It should then be able to finalize the PSBT and broadcast the resulting splice tx.

Voilà, the wallet user has been able to swap some funds in and Acinq didn't have to contribute any utxo. The transactions look like this:

+------------+                +-----------+
| funding tx |-------+------->| commit tx |
+------------+       |        +-----------+
                     |
                     |        +-----------+        +-----------+
                     +------->|           |------->| commit tx |
                              | splice tx |        +-----------+
                     +------->|           |----+
+-------------+      |        +-----------+    | change    +-------------+
| cold wallet |------+                         +---------->| cold wallet |
+-------------+                                            +-------------+

This makes the process much more predictable for wallet users: they decide the feerate and can use coin control in their cold wallet. The UX flow is simple enough as well:

  1. The user clicks Receive -> On-Chain and chooses the amount
  2. A QR code is displayed containing a PSBT
  3. The user scans this QR code from his cold wallet and instructs it to add 10 mBTC
  4. The user obtains a new QR code with the PSBT signed by the cold wallet
  5. The user scans that QR code inside Phoenix and this will complete the flow

Getting funds out of a channel

Getting funds out is even easier. Let's imagine again that we have an existing 100 mBTC channel and want to send 10 mBTC back on-chain. The user simply needs to create a receiving address from his cold wallet, and start a splice that sends 10 mBTC to this address:

+------------+                +-----------+
| funding tx |-------+------->| commit tx |
+------------+       |        +-----------+
                     |
                     |        +-----------+        +-----------+
                     +------->|           |------->| commit tx |
                              | splice tx |        +-----------+
                              |           |----+
                              +-----------+    | 10 mBTC   +-------------+
                                               +---------->| cold wallet |
                                                           +-------------+

Note that this shrinks the capacity of the channel. This can be an issue compared to the legacy swap-out, which didn't change the channel capacity and provided incoming liquidity to the user. But we can add the option to request liquidity (against a fee) and Acinq will in that case add utxos to the splice. We would simply need to use a custom TLV to transfer the fee directly to the initial main outputs of the new commit tx.

Additional goodies

An interesting side-note is that it means we only need one channel (c-lightning was right 😅). It's much better than having multiple channels for a whole lot of reasons:

  • Smaller graph, less gossip
  • Smaller backups
  • More intuitive balance: combined with trampoline, it means we know exactly how much we can send/receive (no MPP surprises)

Note however that there are still reasons to have multiple channels, but they don't apply to wallets:

  • The number of pending HTLCs in a channel is limited; you may need multiple channels for better throughput on congested paths
  • Are there others?

Whenever the user splices some funds in, it can pay some fees to the Acinq node and the Acinq node will also add funds to provide incoming liquidity.

Whenever the user receives an HTLC for which is doesn't have enough receiving liquidity, the Acinq node can splice additional funds in to carry this HTLC. If the user trusts that Acinq will not double-spend this splice, they can consider it complete without confirmation and the HTLC can be relayed. That operation is trusted: a trustless alternative is to hold the HTLC until the splice is confirmed (at the cost of keeping liquidity locked downstream).

Open questions

  • When adding funds, can we avoid choosing the amount before-hand? It would either require the cold wallet to sign with something else than SIGHASH_ALL which is dangerous, or more back-and-forth between the cold wallet and Phoenix which provides a terrible UX...
  • We need to be able to cancel a splice (if the user stops in the middle of the flow or realizes that he doesn't have funds left on his cold wallet): is that supported?
@rustyrussell
Copy link

  1. You don't have to commit to amount upfront, but negotiation is interactive, so you can't always tell it's finished. Imagine both peers want to wait for it to be finished to send to their cold wallet: that's not going to work! You can try to wait for the other side to finish before signing off, but it won't always work. Simpler (but worse UX) is getting a partial PSBT from cold storage and then going back at the end to have it sign the whole tx (if you have insight into the cold storage you can generate the partial yourself ofc).
  2. Yes, you can cancel by removing one of the required inputs, though maybe we should create a more elegant mechanism :)

@t-bast
Copy link
Author

t-bast commented May 14, 2021

Simpler (but worse UX) is getting a partial PSBT from cold storage and then going back at the end to have it sign the whole tx (if you have insight into the cold storage you can generate the partial yourself ofc).

Exactly, that's what I'd like to avoid because it's really clunky from a UX perspective.

I guess we could tweak the interactive-tx flow to specify amounts from the start (via new tlv fields), and abort if the resulting splice tx doesn't match these initial amounts, can't we? This way we would preserve the simpler UX for users with only one round-trip between their lightning wallet and their cold wallet. I'm happy to start doing that off-spec, only between phoenix and the acinq node, and if it works well we can then add it to the spec.

Yes, you can cancel by removing one of the required inputs, though maybe we should create a more elegant mechanism :)

Yeah it would be great to have an explicit way to abort, if only to translate it more obviously in users' wallet UX (and in internal state machines).

@Kixunil
Copy link

Kixunil commented Aug 30, 2021

My thoughts:

From the proposed protocol it appears PSBT is transferred signed before new commit TX is signed. It's quite dangerous because if there's no previous funding TX (empty wallet - initial use) and it gets for any reason accidentally broadcasted too soon the sats are stuck. This also requires scanning a huge QR code twice, has the issue of not being able to change the amount and risks deadlock. If the wallet of the user doesn't support PSBT it won't be possible to use at all.

I propose to instead use BIP78 to perform the operation. It fixes all the problems above. It works like this:

  1. The user is presented with BIP21 link with internal ephemeral (but still seed-derived) address + pj= parameter
  2. The wallet of the user creates initial signed transaction sending to the ephemeral address and sends it to the receiver
  3. The receiver sends the input(s) from PSBT to the other node
  4. After full negotiation the final PSBT is sent back to the wallet to be signed
  5. The sender wallet signs PSBT and broadcasts.

If anything fails there's fallback sending to ephemeral address. The wallet can then automatically attempt to retry. (Mainly when sender wallet doesn't support this.)

The only issue I see today is having to trust Acinq server that would supposedly work as a bridge between the wallets. This could be fixed by a simple addition of encryption to BIP78.

There's nearly identical experimental project of mine that does this with single-funder channel opening and I see no reason why it shouldn't work for splicing as well.

Are there others?

In case of forwarding nodes the trick with having announced and unannounced channel to improve privacy will be useful until LN stops leaking UTXOs in channel IDs. This is irrelevant to Phoenix, though.

@t-bast
Copy link
Author

t-bast commented Aug 30, 2021

From the proposed protocol it appears PSBT is transferred signed before new commit TX is signed.

No, don't worry, that's not the case, the diagram is a simplification but this will be using dual funding so commit tx will always be signed before we complete the funding tx.

I propose to instead use BIP78 to perform the operation. It fixes all the problems above. It works like this:

You're replacing the splicing protocol by the payjoin protocol, and Acinq would act as a coordinator between Phoenix and the bitcoin cold wallet, is that right?

Does that means the cold wallet needs to support payjoin? If that's the case, it will be unlikely (especially if we want to add an encryption step to avoid Acinq learning anything it shouldn't learn), isn't it? Whereas the current protocol only needs the cold wallet to have PSBT support and all the splicing logic is implemented in Phoenix.

I really want to avoid having to ping every bitcoin wallet maintainer for payjoin support and potential bug fixes. I want this to require the most minimal feature set it needs from bitcoin wallets, otherwise there's no way this will be useful to enough Phoenix users. But once there is wide enough payjoin support in the bitcoin wallet ecosystem we can definitely revisit this (and I urge you to ask bitcoin wallets to implement this support if that's important to you).

@Kixunil
Copy link

Kixunil commented Aug 30, 2021

commit tx will always be signed before we complete the funding tx.

That requires more back-and-forth with PSBTS :(

You're replacing the splicing protocol by the payjoin protocol,

Not replacing splicing, it's replacing manual coordination with automated. The rest is correct.

Does that means the cold wallet needs to support payjoin?

To optimize yes, to work at all no.

it will be unlikely (especially if we want to add an encryption step to avoid Acinq learning anything it shouldn't learn), isn't it?

I'm not really so sure it's unlikely. There are already three wallets supporting it, fourth has a PR open, fifth is planned, I have WIP client with Bitcoind backend so even that is possible. All this just because of privacy. Avoiding two transactions will be much stronger motivation whenever fees are high enough. Wallets without it will become irrelevant.

Remember, there's still fallback to classic address (thanks to BIP21). Anything that doesn't support it will just cause two transactions as today. Contrast that with users having to manually transfer a bunch of PSBTs over humongous QR codes. I bet most users would rather pay more sats. Phoenix is currently regarded as one of the most user-friendly LN wallets if not the most user friendly wallet. I can't imagine this still being the case after one QR code is replaced with several QR codes (users don't care why).

once there is wide enough payjoin support

This is just cursed cycle, there's not enough payjoin support because motivation is low because LN wallets don't implement this because there's not enough support. We gotta break it somewhere and I believe that having a good mobile wallet and a good node app (loptos) implement it to create competitive pressure is more feasible than convincing all developers that if they implement PayJoin then some day LN wallets will use it to optimize fees. Implementing the server is not too hard, I had POC done in a day. Loptos will have big note that best wallets are Wasabi, Blue Wallet and BTCPayServer hot wallet. Any wallet wanting to be on that list will have to implement BIP78.

I urge you to ask bitcoin wallets to implement this support if that's important to you

I'm already doing more than that and intend to do even more. :)

  • Wrote loptos
  • Reported bug to BTCPayServer that got fixed recently
  • In progress of writing Rust client library (should be feasible to create bridges to other langs)
  • Tested PR against Electrum - found and reported a bug

@t-bast
Copy link
Author

t-bast commented Aug 31, 2021

Thanks for insisting on this, this is a lot of interesting information, I didn't think it was getting that much traction.
I'll look into this in detail once we get closer to implementing this feature.

Do you think this could be useful for splicing as whole (at the lightning spec level) or does it only add value for the specific case of having the lightning and bitcoin wallets collaborate?

@Kixunil
Copy link

Kixunil commented Aug 31, 2021

I'm not really sure what you're looking for regarding lightning level spec. I was thinking about this protocol for a while and found some more potential for improvement but I don't feel there's much to change in LN spec. Just maybe make extra sure that double spending funding of transaction is not penalized (it can happen if BIP78 fails). Also maybe add the ability to signal time limit (BIP78 mandates one minute timeout) but I don't know if it changes anything.

I also had ideas on paying via BIP78 from splice but that would require changes to the protocol that seem a bit crazy even to me.

Oh, one more thing, BIP78 mandates that inputs/outputs are not reordered only inserted at random positions. So splicing should be made compatible with that, didn't check it that deeply to tell right now.

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