Skip to content

Instantly share code, notes, and snippets.

@t-bast
Created January 31, 2020 10:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t-bast/e5c6da5c6d9dd3bd51397c1458a51ad0 to your computer and use it in GitHub Desktop.
Save t-bast/e5c6da5c6d9dd3bd51397c1458a51ad0 to your computer and use it in GitHub Desktop.
Phoenix Privacy

On-chain Privacy in Phoenix

Phoenix users never fund channels themselves; it's always the routing node they're connecting to that opens channels to them via pay-to-open. Let's call such a node an LSP (Lightning Service Provider). Note that for now Phoenix only supports one LSP (Acinq).

We will show that this model provides good UTXO privacy for Phoenix users (and good unlinkability between on-chain and off-chain identities).

Pay-to-Open

Here is an overview of the flow when a Phoenix wallet doesn't have inbound liquidity:

Phoenix                   LSP                      Any node
   |                       |         add_htlc         |
   |                       |<-------------------------|
   |    pay_to_open_req    |                          |
   |<----------------------|                          |
   |         ok            |                          |
   |---------------------->|                          |
   |     open_channel      |                          |
   |<----------------------|                          |
   |                       |       fulfill_htlc       |
   |                       |------------------------->|
   |                       |                          |

The incoming payment on the right is a pure lightning payment, without any on-chain footprint.

Let's dive into the open_channel flow with a focus on the public keys that may be broadcast to the lightning network or appear in the blockchain:

Phoenix                                              LSP
   |        open_channel(funding_pubkey_LSP)          |
   |<-------------------------------------------------|
   |        accept_channel(funding_pubkey_P)          |
   |------------------------------------------------->|
   |                                                  |---+
   |                                                  |   | creates funding tx with spending condition:
   |                                                  |   | `2 <funding_pubkey_LSP> <funding_pubkey_P> 2 OP_CHECKMULTISIG`
   |                funding_created                   |<--+
   |<-------------------------------------------------|
   |                funding_signed                    |
   |------------------------------------------------->|
   |                                                  |---+
   |                                                  |   | broadcast funding tx, wait for `N` confirmations
   |                funding_locked                    |<--+
   |<-------------------------------------------------|
   |                funding_locked                    |
   |------------------------------------------------->|
   |                                                  |

It's important that the channel stays unannounced: that means only the LSP knowns the mapping from Phoenix's node_id to its funding_pubkey. Other network participants cannot link Phoenix's off-chain activity to the channel's on-chain funding tx.

All the UTXOs that are spent by the funding tx belong to the LSP. That means that the Phoenix user currently has no on-chain footprint whatsoever. Now three things can happen:

  • The channel stays open: this is perfect for the Phoenix user, who still doesn't have any on-chain footprint.
  • The channel closes after the Phoenix user has emptied his balance (via normal HTLCs or swaps): only the LSP will have UTXOs in the commitment tx, the Phoenix user has no left-over UTXOs so no on-chain footprint. Note that this only works because we allow Phoenix users to have no channel reserve: otherwise Phoenix would always have an UTXO to redeem from the commitment tx.
  • The LSP does an uncooperative channel close while the Phoenix user has a non-empty balance: the Phoenix user now has an on-chain footprint. However only the LSP can link it with his off-chain node_id and won't be able to link it to other on-chain UTXOs. Note that this costs fees only for the LSP (because the LSP is the channel funder) and adds delays for the LSP's outputs.

Swaps

While the channel is open, the only way the Phoenix user will generate an on-chain footprint is via swaps (swap-in/swap-out).

Swap-In

When swapping-in, the Phoenix user should use a different LSP than the one with whom she has a channel, and should use a decoy channel_id and node_id (e.g. here).

Let's call LSP the node with whom Phoenix has a channel and SSP the swap provider. SSP can link the swap UTXOs to a node_id, but this node_id is a decoy (not the real Phoenix node_id). LSP simply forwards an HTLC to Phoenix and has no way of linking it to the swap UTXOs (it can't even know this HTLC is coming from a swap).

The only way to restore the link is to have LSP and SSP collide. This can be avoided by running your own SSP (or LSP).

Swap-Out

When swapping-out, the Phoenix user should use a different LSP than the one with whom she has a channel. Let's call it SSP.

SSP can link the swap UTXOs to a payment_hash (but not to a node_id). LSP simply forwards an HTLC and has no way of linking it to the swap UTXOs (it can't even know this HTLC will trigger a swap).

The only way to restore the link is to have LSP and SSP collide. This can be avoided by running your own SSP (or LSP).

Tor

Phoenix users should use Tor. It allows them to frequently change their node_id and present a different identity to the LSP (or manage a set of multiple node_ids). Otherwise the LSP could link a node_id to a set of IP addresses (in particular if the user connects from his home or work WiFi).

Without Tor the LSP could even match on-chain and off-chain activity simply by matching IP addresses.

Channels funded by Phoenix

It seems to me that even if Phoenix funded the channel, it would still benefit from almost the same privacy. The important point is to keep the channel unannouced to avoid leaking the funding_pubkey <-> node_id mapping to the whole network.

The only difference is that if the channel closes, Phoenix will have a new UTXO that is linked to the UTXOs used to open the channel. But it can't be linked to off-chain activity.

Protecting from your LSP

If you are using a mobile wallet, the "full nodes" you connect to WILL KNOW. There's no point trying to look like a "full node", you just can't. Your mobile won't be always online, won't be able to route payments reliably, won't have enough channels to look like a routing node, etc.

That means these nodes will know that when they route a payment to you, you are the recipient. They will also know that when you route a payment through them, you are the payer. If you don't want to trust any LSP with that information, you should run your own LSP and connect to it. That will give you the best privacy you can achieve.

But that model isn't for every user. This is highly technical and costly to do. However, there are mitigations we can use to ensure you don't leak too much information to the LSPs you choose to connect to.

Phoenix could manage a set of node_ids (instead of a single one). Used in combination with Tor, your LSP won't be able to know that multiple node_ids belong to the same user. You will need to have channels with each of these node_id (can't share them as that would leak information).

Why not simply use decoy node_ids? Because that protects your real node_id from senders (when they don't collude with your LSP) but not from your LSP; your LSP will always know the node_id you used to create channels, which is your long-term identity.

Potential attacks and mitigations

Linking swaps to HTLCs by observing amounts

The LSP could watch every UTXO spent/created on-chain to try to guess that HTLCs are in fact swaps. If the LSP forwards an outgoing HTLC for 150500 sats, and then sees on-chain a new UTXO of 150k sats he can guess that it's likely a swap-out. The same heuristic goes for swap-in.

This can be easily defeated by using MPP between Phoenix and the SSP. Simply the fact that MPP is possible already defeats that heuristic.

@t-bast
Copy link
Author

t-bast commented Jan 31, 2020

I think the biggest short-term privacy gains we can aim for are:

  • decoy node_ids and channel_ids: gives you privacy from everyone except your LSP
  • handling multiple node_ids (once we have Tor): gives you privacy from your LSP

Next steps are then:

Longer term is to standardize swap servers to allow many swap servers in the network; that will provide a bigger anonymity set and better unlinkability between on-chain and off-chain activity. When that's done we should let users choose their swap server if they want to (it's better for them to use a different one from the LSP).

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