-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bolt 12 introduces offers, which are static lightning "addresses". An offer can be stored and reused to pay the same node many times. It then becomes natural to associate Bolt 12 offers to your friends and contacts. When sending payments to contacts, you may want them to know that the payment came from you. We propose a scheme to optionally include contact information in outgoing payments to allow the recipient to: - detect that the payment is coming from one of their known contacts - otherwise, be able to add the payer to their contacts list - send funds back to the payer without additional interaction This feature provides a better UX for lightning wallets, by making payments between contacts look very similar to fiat payment applications.
- Loading branch information
Showing
3 changed files
with
254 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
``` | ||
bLIP: 42 | ||
Title: Bolt 12 Contacts | ||
Status: Active | ||
Author: Bastien Teinturier <[email protected]> | ||
Created: 2024-07-19 | ||
License: CC0 | ||
``` | ||
|
||
## Abstract | ||
|
||
Bolt 12 introduces offers, which are static lightning "addresses". An offer can | ||
be stored and reused to pay the same node many times. It then becomes natural to | ||
associate Bolt 12 offers to your friends and contacts. | ||
|
||
When sending payments to contacts, you may want them to know that the payment | ||
came from you. We propose a scheme to optionally include contact information in | ||
outgoing payments to allow the recipient to: | ||
|
||
- detect that the payment is coming from one of their known contacts | ||
- otherwise, be able to add the payer to their contacts list | ||
- send funds back to the payer without additional interaction | ||
|
||
This feature provides a better UX for lightning wallets, by making payments | ||
between contacts look very similar to fiat payment applications. | ||
|
||
## Copyright | ||
|
||
This bLIP is licensed under the CC0 license. | ||
|
||
## Motivation | ||
|
||
This feature provides a better UX for lightning wallets, by making payments | ||
between contacts look very similar to fiat payment applications. | ||
|
||
## Specification | ||
|
||
### Invoice Request TLVs | ||
|
||
The `invreq_contact_secret` field is an identifier for a contact pair: | ||
|
||
1. type: 2000001729 (`invreq_contact_secret`) | ||
2. data: | ||
- [`32*byte`:`contact_secret`] | ||
|
||
The `invreq_payer_offer` field lets payers reveal a Bolt 12 offer that can | ||
be used by contacts to pay them back: | ||
|
||
1. type: 2000001731 (`invreq_payer_offer`) | ||
2. data: | ||
- [`...*byte`:`payer_offer`] | ||
|
||
The `invreq_payer_bip_353_name` field lets payers reveal their BIP 353 name | ||
to allow contacts to pay them back: | ||
|
||
1. type: 2000001733 (`invreq_payer_bip_353_name`) | ||
2. data: | ||
- [`u8`:`name_len`] | ||
- [`name_len*byte`:`name`] | ||
- [`u8`:`domain_len`] | ||
- [`domain_len*byte`:`domain`] | ||
|
||
#### Requirements | ||
|
||
The writer of `invoice_request`, when paying one of their contacts: | ||
|
||
- If it wants the payer to be able to identify who paid them: | ||
- MUST include the `invreq_contact_secret` associated with that contact. | ||
- MUST include either `invreq_payer_offer` or `invreq_payer_bip_353_name`. | ||
- If it includes `invreq_payer_bip_353_name`: | ||
- MUST set `name` to the post-₿, pre-@ part of the BIP 353 HRN. | ||
- MUST set `domain` to the post-@ part of the BIP 353 HRN. | ||
- If it includes `invreq_payer_offer`: | ||
- MUST encode `payer_offer` as a TLV stream of its individual records. | ||
- If the encoded offer is more than 300 bytes long: | ||
- SHOULD NOT include `invreq_payer_offer`. | ||
- SHOULD include `invreq_payer_bip_353_name` instead. | ||
- Otherwise: | ||
- MUST NOT include `invreq_contact_secret`, `invreq_payer_offer` or | ||
`invreq_bip_353_name`. | ||
|
||
The reader of `invoice_request`: | ||
|
||
- MUST send back an `invoice` including the `invoice_request` contact fields | ||
provided by the sender, as specified in Bolt 12. | ||
- After the invoice has been paid, if `invreq_contact_secret` was included: | ||
- If it matches one of its contacts: | ||
- SHOULD display the `invreq_payer_note`, if one is provided. | ||
- Otherwise: | ||
- MAY use the `contact_secret`, `payer_offer` and `payer_bip_353_name` to | ||
create a new contact. | ||
- If it creates a new contact based on this received payment, the | ||
received `contact_secret` MUST be used when paying that contact. | ||
- MAY associate the received `contact_secret` with an existing contact. | ||
- MUST ignore `invreq_payer_offer` and `invreq_bip_353_name` if it already | ||
has an offer for this contact. | ||
|
||
#### Rationale | ||
|
||
The `contact_secret` field is used for mutual identification: its usage is | ||
detailed in the [Contact Secrets](#contact-secrets) section below. | ||
|
||
Nodes generally don't store every `invoice_request` they receive, because that | ||
would expose them to DoS. They instead include the fields they would like to | ||
store in the `path_id` field of the blinded path(s) of the `invoice` they send | ||
back. Since this `path_id` will then be included in payment onions, which are | ||
limited to 1300 bytes, nodes must ensure that the resulting `path_id` isn't too | ||
large, which would constrain the payment paths that can be used by the payer. | ||
We thus recommend only including offers that are smaller than 300 bytes in | ||
`invreq_payer_offer`, or a small BIP 353 HRN. | ||
|
||
When payments are coming from known contacts, there is less risk that the | ||
`payer_note` that is optionally included contains spam. It is thus recommended | ||
to display it, while we generally don't recommend displaying `payer_note`s | ||
coming from unknown payers. | ||
|
||
When receiving payments from existing contacts, the offer and BIP 353 HRN must | ||
be ignored: this ensures that if the `contact_secret` was leaked, a malicious | ||
node impersonating our contact cannot redirect our future payments to their | ||
own offers. | ||
|
||
### Contact Secrets | ||
|
||
The main mechanism of this proposal is the exchange of `contact_secret`s. | ||
This section details various scenarios that may occur and how to correctly | ||
deal with each of them. | ||
|
||
#### Adding contacts | ||
|
||
When Alice adds Bob to her contacts list from an offer she received from Bob, | ||
she generates a random `contact_secret`. For all future payments made to Bob | ||
where she wants to reveal that she's the payer, Alice will include this same | ||
`contact_secret`. | ||
|
||
Once Bob has received a payment that includes a `contact_secret`, he may add | ||
the payer to its own contacts list. If he knows that this payment came from | ||
Alice, he's able to add Alice to his contacts and pay her back using the | ||
`payer_offer` or `payer_bip_353_name` she provided. For all future payments | ||
made to Alice where Bob wants to reveal that he's the payer, Bob will include | ||
the `contact_secret` generated by Alice. Note that in this case, Bob doesn't | ||
generate a different `contact_secret`, because he already has one available | ||
that was created by Alice, which he knows Alice will be able to use to identify | ||
payments. | ||
|
||
However, if Bob adds Alice to his contacts list without using the payment he | ||
received from her, or if he adds her to his contacts list on another wallet | ||
than the one used to receive Alice's payment, Bob will generate a different | ||
random `contact_secret`. For all payments made to Alice where he wants to | ||
reveal that he's the payer, he will use that new `contact_secret`. When Alice | ||
receives those payments, she won't be able to automatically identify that it's | ||
coming from Bob based on the `contact_secret` alone. But Alice is usually able | ||
to know that a specific payment came from Bob: she can then detect that Bob | ||
used a different `contact_secret` from the one she initially created, and she | ||
can store that additional `contact_secret` to the list of secrets Bob may use | ||
when paying her. This action automatically reconciles past and future payments | ||
made from Bob. | ||
|
||
A contact entry thus contains the following information: | ||
|
||
- `primary_contact_secret`: the first `contact_secret` used, which must be used | ||
for *all* outgoing payments to this contact and may either have been created | ||
by us (if we made the first payment) or by our contact (if we added them to | ||
our contacts list based on a payment we received). | ||
- `additional_remote_contact_secrets`: a list of secondary `contact_secret`s | ||
that our contact may use when paying us, obtained by manually associating | ||
payments with our existing contact. | ||
|
||
Note that wallets can also automatically reconcile payments when they detect | ||
that the `payer_offer` or `payer_bip_353_name` received matches an existing | ||
contact but uses a different `contact_secret`. | ||
|
||
#### Leaked contact secrets | ||
|
||
Contact secrets shouldn't be shared publicly, as that would let other people | ||
make payments that appear to be coming from you. This doesn't allow stealing | ||
funds though: even if the impersonator includes their own offer in a payment | ||
they make on your behalf in the `invreq_payer_offer` field, the receiving node | ||
will ignore it if they have already stored your contact information. If they | ||
haven't, they have no reason to create a new contact based on this payment. | ||
|
||
### Deterministic derivation | ||
|
||
When creating a new contact, we recommend using the following deterministic | ||
derivation for the `contact_secret` field: | ||
|
||
- For a given Bolt 12 offer, we define its `offer_node_id` as: | ||
- If the offer contains `offer_issuer_id`: | ||
- `offer_node_id = offer_issuer_id`. | ||
- Otherwise, the offer must contain `offer_paths`: | ||
- `offer_node_id` is set to the last `blinded_node_id` of the first | ||
`path`. | ||
- The private key for the `offer_node_id` is called `offer_priv_key`. | ||
- When paying `remote_offer` for which we include our `local_offer` in the | ||
`invreq_payer_offer` field: | ||
- We compute the ECDH of the two `offer_node_id`s: | ||
- `shared_key = local_offer.offer_priv_key * remote_offer.offer_node_id`. | ||
- We use a tagged hash to derive the `contact_secret`: | ||
- `contact_secret = SHA256("blip42_contact_secret" || shared_key)`. | ||
|
||
Using this deterministic derivation has multiple benefits. First of all, it | ||
guarantees that both nodes independently derive the same `contact_secret` when | ||
using the same set of offers, which removes the need to reconcile secrets when | ||
nodes concurrently add each other to their contacts list. | ||
|
||
It is particularly useful for wallets that use a single offer that is created | ||
deterministically from the user's seed: this ensures that the `contact_secret` | ||
can also be restored from seed. | ||
|
||
#### Test vector | ||
|
||
The following test vectors use the deterministic derivation from the previous | ||
section. | ||
|
||
```json | ||
[ | ||
{ | ||
"comment": "derive deterministic contact_secret when both offers use blinded paths only", | ||
"alice_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsrejlwh4vyz70s46r62vtakl4sxztqj6gxjged0wx0ly8qtrygufcsyq5agaes6v605af5rr9ydnj9srneudvrmc73n7evp72tzpqcnd28puqr8a3wmcff9wfjwgk32650vl747m2ev4zsjagzucntctlmcpc6vhmdnxlywneg5caqz0ansr45z2faxq7unegzsnyuduzys7kzyugpwcmhdqqj0h70zy92p75pseunclwsrwhaelvsqy9zsejcytxulndppmykcznn7y5h", | ||
"alice_offer_priv_key": "4ed1a01dae275f7b7ba503dbae23dddd774a8d5f64788ef7a768ed647dd0e1eb", | ||
"alice_offer_node_id": "0284c9c6f04487ac22710176377680127dfcf110aa0fa8186793c7dd01bafdcfd9", | ||
"bob_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsz4n88s74qhussxsu0vs3c4unck4yelk67zdc29ree3sztvjn7pc9qyqlcpj54jnj67aa9rd2n5dhjlxyfmv3vgqymrks2nf7gnf5u200mn5qrxfrxh9d0ug43j5egklhwgyrfv3n84gyjd2aajhwqxa0cc7zn37sncrwptz4uhlp523l83xpjx9dw72spzecrtex3ku3h3xpepeuend5rtmurekfmnqsq6kva9yr4k3dtplku9v6qqyxr5ep6lls3hvrqyt9y7htaz9qj", | ||
"bob_offer_priv_key": "12afb8248c7336e6aea5fe247bc4bac5dcabfb6017bd67b32c8195a6c56b8333", | ||
"bob_offer_node_id": "035e4d1b7237898390e7999b6835ef83cd93b98200d599d29075b45ab0fedc2b34", | ||
"contact_secret": "810641fab614f8bc1441131dc50b132fd4d1e2ccd36f84b887bbab3a6d8cc3d8" | ||
}, | ||
{ | ||
"comment": "derive deterministic contact_secret when one offer uses both blinded paths and issuer_id", | ||
"alice_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsrejlwh4vyz70s46r62vtakl4sxztqj6gxjged0wx0ly8qtrygufcsyq5agaes6v605af5rr9ydnj9srneudvrmc73n7evp72tzpqcnd28puqr8a3wmcff9wfjwgk32650vl747m2ev4zsjagzucntctlmcpc6vhmdnxlywneg5caqz0ansr45z2faxq7unegzsnyuduzys7kzyugpwcmhdqqj0h70zy92p75pseunclwsrwhaelvsqy9zsejcytxulndppmykcznn7y5h", | ||
"alice_offer_priv_key": "4ed1a01dae275f7b7ba503dbae23dddd774a8d5f64788ef7a768ed647dd0e1eb", | ||
"alice_offer_node_id": "0284c9c6f04487ac22710176377680127dfcf110aa0fa8186793c7dd01bafdcfd9", | ||
"bob_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsz4n88s74qhussxsu0vs3c4unck4yelk67zdc29ree3sztvjn7pc9qyqlcpj54jnj67aa9rd2n5dhjlxyfmv3vgqymrks2nf7gnf5u200mn5qrxfrxh9d0ug43j5egklhwgyrfv3n84gyjd2aajhwqxa0cc7zn37sncrwptz4uhlp523l83xpjx9dw72spzecrtex3ku3h3xpepeuend5rtmurekfmnqsq6kva9yr4k3dtplku9v6qqyxr5ep6lls3hvrqyt9y7htaz9qjzcssy065ctv38c5h03lu0hlvq2t4p5fg6u668y6pmzcg64hmdm050jxx", | ||
"bob_offer_priv_key": "bcaafa8ed73da11437ce58c7b3458567a870168c0da325a40292fed126b97845", | ||
"bob_offer_node_id": "023f54c2d913e2977c7fc7dfec029750d128d735a39341d8b08d56fb6edf47c8c6", | ||
"contact_secret": "4e0aa72cc42eae9f8dc7c6d2975bbe655683ada2e9abfdfe9f299d391ed9736c" | ||
} | ||
] | ||
``` | ||
|
||
## Reference Implementations | ||
|
||
- lightning-kmp: <https://github.com/ACINQ/lightning-kmp/pull/719> |