Skip to content

Instantly share code, notes, and snippets.

@t-bast
Last active February 6, 2024 12:15
Show Gist options
  • Save t-bast/78fd797a7da570d293a8663908d3339b to your computer and use it in GitHub Desktop.
Save t-bast/78fd797a7da570d293a8663908d3339b to your computer and use it in GitHub Desktop.
Future of lightning address

Lightning Address

Lightning Address is a very popular protocol that brings UX improvements that users love. We'd like to provide those UX benefits without its privacy and security drawbacks.

Issues with the current lightning address protocol

As described here, the lightning address protocol requires payment senders to make an HTTP request to the recipient's domain owner. This has some inconvenient side effects:

  1. The payment sender reveals their IP address to the recipient's domain owner, who knows both the sender and the recipient.
  2. The domain owner can swap invoices to steal some of the payment.
  3. It introduces a dependency on DNS servers and the need for an HTTP stack on the sender side.

We can do better and fix or mitigate some of these issues, without compromising on UX. We need two somewhat distinct mechanisms:

  1. A way to privately obtain the node_id associated with a given domain.
  2. A way to privately contact that domain to obtain the recipient's payment details.

User story

Alice wants to pay bob@domain.com without any other prior information. She doesn't want to reveal:

  • her identity to Bob (payment sender privacy)
  • her identity to the manager of domain.com (payment sender privacy)
  • the fact that she wants to pay bob@domain.com to her LSP (payment recipient privacy)

Option 1: use DNS records to link domains to nodes

A first proposal would be to use a DNS record to obtain the node_id associated with a given domain.

Obtain a blinded path to the node associated with a domain

Domain owners add a DNS TXT record for their domain containing a blinded path to their node. They may include an empty path if they wish to directly reveal their node_id.

hostname record type value TTL
_lnaddress.domain.com. TXT path:<blinded_path> path expiry

Alice can then make a DNS query to obtain that blinded path.

    Alice                                                              DNS server
      |                                                                     |
      | dig TXT _lnaddress.domain.com                                       |
      |-------------------------------------------------------------------->|
      |              _lnaddress.domain.com. IN TXT "path:c3056fb73aa623..." |
      |<--------------------------------------------------------------------|

❓ What encoding should we use for the blinded path option? Bech32m with the lnp prefix?

⚠️ Alice should query that DNS record using DoH for privacy. She should also query multiple DoH servers to protect from malicious ones.

⚠️ Alice should check the AD flag is correctly set (DNSSEC).

Obtain a Bolt 12 offer from the recipient

Now that Alice has a way to reach the node that owns Bob's domain, she needs to contact them to obtain a Bolt 12 offer from Bob. We use an onion_message for that, which has the following benefits:

  • Alice doesn't reveal her identity (IP address or node_id) to Bob or Bob's domain
  • Alice doesn't reveal Bob's identity (IP address or node_id) to her LSP
  • Alice doesn't even need to know the IP address for Bob's domain's lightning node
    Alice                          Alice's LSP                          Bob's LSP                            Bob
      |                                 |                                   |                                 |
      |          onion_message          |                                   |                                 |
      |-------------------------------->|          onion_message            |                                 |
      |                                 |  get_offer_from = bob@domain.com  |                                 |
      |                                 |---------------------------------->|                                 |
      |                                 |                                   |             wake_up             |
      |                                 |                                   |-------------------------------->|
      |                                 |                                   |              offer              |
      |                                 |                                   |<--------------------------------|
      |                                 |          onion_message            |                                 |
      |                                 |<----------------------------------|                                 |
      |          onion_message          |                                   |                                 |
      |       bob's bolt12 offer        |                                   |                                 |
      |       bob's LSP signature       |                                   |                                 |
      |<--------------------------------|                                   |                                 |

Note that Alice cannot verify that the offer she receives is really from Bob: she has to TOFU (trust on first use). But that's something we fundamentally cannot fix if the only information Alice has is bob@domain.com. However, Alice obtains a signed statement from Bob's LSP that attests that bob@domain.com is associated with the Bolt12 offer she receives. If she later discovers that this was invalid, she can publish that proof to show the world that Bob's LSP is malicious.

Otherwise, since there needs to be some out-of-band communication where the recipient advertizes their lightning address (e.g. on social media), some kind of verification code could be attached (hash of the node_id?). The sender's wallet could optionally add a manual verification step of that verification code. This would only need to be done once, since Alice can then reuse the same offer to fetch new invoices.

Advantages and drawbacks

The main advantage of this proposal is that it is simple, inexpensive and relies on standard mechanisms. Its drawback is that domain owners need to be able to publish DNS TXT records, but that is widely supported.

Option 2: use node_announcement to link nodes to specific domains

This proposal is only based on the lightning network, without any dependency on DNS or HTTP stacks (apart from certificate validation).

Obtain the node_id associated with a domain

We add fields to node_announcement to let nodes advertize which domains they own. Those fields would typically contain a signature of the node_id using the private key for the corresponding domain TLS certificate, along with its certificate chain. Alice can then simply sync node_announcements that contain domain links with her LSP:

    Alice                                             LSP
      |                                                |
      |                  node_announcement(foobar.com) |
      |<-----------------------------------------------|
      |                  node_announcement(domain.xyz) |
      |<-----------------------------------------------|
      |                    node_announcement(ln.stuff) |
      |<-----------------------------------------------|
      |                                            ... |
      |<-----------------------------------------------|

Obtain a Bolt 12 offer from the recipient

This uses exactly the same onion message mechanism as the previous proposal.

Advantages and drawbacks

The main advantage of this proposal is that it relies entirely on lightning protocol messages. Its drawback is that Alice needs to sync some node_announcements to obtain the domain owner's node_id. Alice also needs to validate the certificate chain, which is old school annoying crypto. It also doesn't allow domain owners to keep their node_id private (which may be useful for small community-based nodes).

Option 3: use DNS records to directly store Bolt 12 offers

Another option would be to make domain owners create one DNS TXT record for each of their user, directly containing their Bolt 12 offer:

hostname record type value TTL
bob._lnaddress.domain.com. TXT lno1qqx2n6mw2fh2... offer expiry
    Alice                                                              DNS server
      |                                                                     |
      | dig TXT bob._lnaddress.domain.com                                   |
      |-------------------------------------------------------------------->|
      |             bob._lnaddress.domain.com. IN TXT "lno1qqx2n6mw2fh2..." |
      |<--------------------------------------------------------------------|

⚠️ Alice should query that DNS record using DoH for privacy. She should also query multiple DoH servers to protect from malicious ones.

⚠️ Alice should check the AD flag is correctly set (DNSSEC).

Note that this has the same TOFU properties as option 1.

Advantages and drawbacks

The main advantage of this proposal is that it is straightforward for the sender and doesn't require any addition to the lightning protocol.

There are some drawbacks though, mostly for the domain owner, because they will need to create a lot of DNS records (one per user). If they're using a cloud provider, there will be limitations in the number of records they are allowed to create. They may not have programmatic access to perform that operation automatically (when a user creates their lightning address).

Examples

The following sections contain detailed protocol flows, to dispel common misunderstandings. In the following examples:

  • Larry runs an LSP and owns larry-is-satoshi.com
  • Larry wants to let his users have addresses associated with larry-is-satoshi.com
  • Bob is using Larry as his LSP and wants to claim bob@larry-is-satoshi.com
  • Alice wants to pay Bob using the address above

Protocol flow when domain owner only supports option 3

Bob registers bob@larry-is-satoshi.com with Larry. Larry creates a DNS TXT record to store Bob's offer.

  Bob                                                     Larry
   |     I'd like to use bob@larry-is-satoshi.com           |
   |            My offer is lno1pg9kyc...                   |
   |------------------------------------------------------->|
   |                                                        |-------+
   |                                                        |       | create DNS TXT record:
   |                                                        |       | bob._lnaddress.larry-is-satoshi.com.
   |                                                        |       | lno1pg9kyc...
   |                                                        |<------+
   |   Sure, it's at bob._lnaddress.larry-is-satoshi.com.   |
   |<-------------------------------------------------------|

Alice wants to pay Bob: Bob gives her his address bob@larry-is-satoshi.com.

  Alice                                                           DNS server
    |                                                                  |
    | dig TXT bob._lnaddress.larry-is-satoshi.com                      |
    |----------------------------------------------------------------->|
    |      bob._lnaddress.larry-is-satoshi.com. IN TXT "lno1pg9kyc..." |
    |<-----------------------------------------------------------------|

She now has Bob's offer and can pay it.

Protocol flow when domain owner only supports option 1

Larry creates a single DNS TXT record to map his node_id to larry-is-satoshi.com.

hostname record type value TTL
_lnaddress.larry-is-satoshi.com. TXT path:<blinded_path_to_larry> path expiry

If Larry doesn't need to hide his node_id (which is the most likely use case), the blinded path is empty and only contains his node_id. That blinded path never expires, so Larry is free to set any TTL for the DNS record.

Bob registers bob@larry-is-satoshi.com with Larry. Larry stores a mapping internally from this "address" to Bob's node_id. The details here will likely be specific to each wallet and won't specified here.

  Bob                                               Larry
   |     I'd like to use bob@larry-is-satoshi.com     |
   |------------------------------------------------->|
   |        sure, I'll map that to your node_id       |
   |<-------------------------------------------------|

Alice wants to pay Bob: Bob gives her his address bob@larry-is-satoshi.com. She tries option 3 first (which would direclty provide her with Bob's offer) and falls back to option 1.

  Alice                                                                          DNS server
    |                                                                                 |
    | dig TXT bob._lnaddress.larry-is-satoshi.com                                     |
    |-------------------------------------------------------------------------------->|
    |                                                              no matching record |
    |<--------------------------------------------------------------------------------|
    | dig TXT _lnaddress.larry-is-satoshi.com                                         |
    |-------------------------------------------------------------------------------->|
    |          _lnaddress.larry-is-satoshi.com. IN TXT "path:<blinded_path_to_larry>" |
    |<--------------------------------------------------------------------------------|
    |
    |                                         Larry
    |               onion_message               |
    | get_offer_from = bob@larry-is-satoshi.com |
    |------------------------------------------>|
    |                                           |------+
    |                                           |      | check internal mapping
    |                                           |      | to get bob's node_id
    |                                           |<-----+
    |                                           |                                          Bob
    |                                           |                  wake_up                  |
    |                                           |------------------------------------------>|
    |                                           |                   offer                   |
    |                                           |<------------------------------------------|
    |               onion_message               |
    |         bob's offer = lno1pg9kyc...       |
    |          larry's signature = ...          |
    |<------------------------------------------|

She now has Bob's offer and can pay it.

@rustyrussell
Copy link

I'm not sure 1 makes sense (vs 3). It implies bob@foo.com and alice@foo.com are using the same node_id, in which case it's going to know whether you're paying Bob or Alice? And you need #3 for the "email provider" case.

Case 1 makes more sense for vendor tag validation in offers/invoices, but that's a separate proposal I think. It should also return a bolt12 offer format, sans description. We currently don't allow descriptionless offers, but it has always made sense for this "unsolicited payment" case.

@t-bast
Copy link
Author

t-bast commented Dec 15, 2023

I'm not sure 1 makes sense (vs 3). It implies bob@foo.com and alice@foo.com are using the same node_id, in which case it's going to know whether you're paying Bob or Alice? And you need #3 for the "email provider" case.

I think option 1 is necessary, as not everyone will be able to provide option 3. In practice, I expect option 3 to be mostly for end users who have their own domain.

In option 1, Alice and Bob are indeed using the same LSP, and that LSP will know that payments will be coming for Alice or Bob when someone requests an offer. But that's ok, since the LSP doesn't learn who the payer is (since it comes from an onion message). The LSP would learn that information anyway whenever a payment comes for Alice or Bob, when they relay update_add_htlc?

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