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.
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:
- The payment sender reveals their IP address to the recipient's domain owner, who knows both the sender and the recipient.
- The domain owner can swap invoices to steal some of the payment.
- 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:
- A way to privately obtain the
node_id
associated with a given domain. - A way to privately contact that domain to obtain the recipient's payment details.
Alice wants to pay [email protected]
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
[email protected]
to her LSP (payment recipient privacy)
A first proposal would be to use a DNS record to obtain the node_id
associated with a given 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?
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 = [email protected] | |
| |---------------------------------->| |
| | | 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 [email protected]
.
However, Alice obtains a signed statement from Bob's LSP that attests that [email protected]
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.
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.
This proposal is only based on the lightning network, without any dependency on DNS or HTTP stacks (apart from certificate validation).
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_announcement
s that contain domain links with her LSP:
Alice LSP
| |
| node_announcement(foobar.com) |
|<-----------------------------------------------|
| node_announcement(domain.xyz) |
|<-----------------------------------------------|
| node_announcement(ln.stuff) |
|<-----------------------------------------------|
| ... |
|<-----------------------------------------------|
This uses exactly the same onion message mechanism as the previous proposal.
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_announcement
s 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).
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..." |
|<--------------------------------------------------------------------|
Note that this has the same TOFU properties as option 1.
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).
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
[email protected]
- Alice wants to pay Bob using the address above
Bob registers [email protected]
with Larry.
Larry creates a DNS TXT record to store Bob's offer.
Bob Larry
| I'd like to use [email protected] |
| 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 [email protected]
.
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.
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 [email protected]
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 [email protected] |
|------------------------------------------------->|
| sure, I'll map that to your node_id |
|<-------------------------------------------------|
Alice wants to pay Bob: Bob gives her his address [email protected]
.
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 = [email protected] |
|------------------------------------------>|
| |------+
| | | 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.
I'm not sure 1 makes sense (vs 3). It implies [email protected] and [email protected] 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.