Skip to content

Commit b37d046

Browse files
committed
blip-0042: Bolt 12 Contacts
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.
1 parent 8b09c57 commit b37d046

File tree

3 files changed

+274
-0
lines changed

3 files changed

+274
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ For more detail on the process, please read [bLIP-0001](./blip-0001.md) and
2828
| [17](./blip-0017.md) | Hosted Channels | Anton Kumaigorodskiy | Active |
2929
| [25](./blip-0025.md) | Forward less than onion value | Valentine Wallace | Active |
3030
| [32](./blip-0032.md) | Onion Message DNS Resolution | Matt Corallo | Active |
31+
| [42](./blip-0042.md) | Bolt 12 Contacts | Bastien Teinturier | Active |
3132
| [50](./blip-0050.md) | LSPS0: LSP Spec Transport Layer | ZmnSCPxj jxPCSnmZ | Active |
3233
| [51](./blip-0051.md) | LSPS1: Channel Requests | Severin Bühler | Active |
3334
| [52](./blip-0052.md) | LSPS2: JIT Channel Negotiation | ZmnSCPxj jxPCSnmZ | Active |

blip-0002.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ network split.
3535
* [`ping`](#ping)
3636
* [`update_add_htlc`](#update_add_htlc)
3737
* [Onion Messages](#onion-messages)
38+
* [`invoice_request`](#invoice_request)
3839

3940
### Feature bits
4041

@@ -133,6 +134,17 @@ The following table contains tlv fields for use in onion messages as the payload
133134
| 65536 | `dnssec_query` | [bLIP 32](./blip-0032.md) |
134135
| 65538 | `dnssec_proof` | [bLIP 32](./blip-0032.md) |
135136

137+
#### `invoice_request`
138+
139+
The following table contains extension tlv fields for `invoice_request`s sent in
140+
onion messages:
141+
142+
| Type | Name | Link |
143+
|------------|-----------------------------|---------------------------|
144+
| 2000001729 | `invreq_contact_secret` | [bLIP 42](./blip-0042.md) |
145+
| 2000001731 | `invreq_payer_offer` | [bLIP 42](./blip-0042.md) |
146+
| 2000001733 | `invreq_payer_bip_353_name` | [bLIP 42](./blip-0042.md) |
147+
136148
## Copyright
137149

138150
This bLIP is licensed under the CC0 license.

blip-0042.md

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
```
2+
bLIP: 42
3+
Title: Bolt 12 Contacts
4+
Status: Active
5+
Author: Bastien Teinturier <[email protected]>
6+
Created: 2024-07-19
7+
License: CC0
8+
```
9+
10+
## Abstract
11+
12+
Bolt 12 introduces offers, which are static lightning "addresses". An offer can
13+
be stored and reused to pay the same node many times. It then becomes natural to
14+
associate Bolt 12 offers to your friends and contacts.
15+
16+
When sending payments to contacts, you may want them to know that the payment
17+
came from you. We propose a scheme to optionally include contact information in
18+
outgoing payments to allow the recipient to:
19+
20+
- detect that the payment is coming from one of their known contacts
21+
- otherwise, be able to add the payer to their contacts list
22+
- send funds back to the payer without additional interaction
23+
24+
This feature provides a better UX for lightning wallets, by making payments
25+
between contacts look very similar to fiat payment applications.
26+
27+
## Copyright
28+
29+
This bLIP is licensed under the CC0 license.
30+
31+
## Motivation
32+
33+
This feature provides a better UX for lightning wallets, by making payments
34+
between contacts look very similar to fiat payment applications.
35+
36+
## Specification
37+
38+
### Invoice Request TLVs
39+
40+
The `invreq_contact_secret` field is an identifier for a contact pair:
41+
42+
1. type: 2000001729 (`invreq_contact_secret`)
43+
2. data:
44+
- [`32*byte`:`contact_secret`]
45+
46+
The `invreq_payer_offer` field lets payers reveal a Bolt 12 offer that can
47+
be used by contacts to pay them back:
48+
49+
1. type: 2000001731 (`invreq_payer_offer`)
50+
2. data:
51+
- [`...*byte`:`payer_offer`]
52+
53+
The `invreq_payer_bip_353_name` field lets payers reveal their BIP 353 name
54+
to allow contacts to pay them back:
55+
56+
1. type: 2000001733 (`invreq_payer_bip_353_name`)
57+
2. data:
58+
- [`u8`:`name_len`]
59+
- [`name_len*byte`:`name`]
60+
- [`u8`:`domain_len`]
61+
- [`domain_len*byte`:`domain`]
62+
63+
#### Requirements
64+
65+
The writer of `invoice_request`:
66+
67+
- If they want the recipient to be able to identify who paid them:
68+
- If the recipient is not yet part of their contacts list:
69+
- If they have previously received a payment from this recipient including
70+
the `invreq_contact_secret` field:
71+
- MUST associate the received `invreq_contact_secret` with this contact.
72+
- MUST include this `invreq_contact_secret` whenever paying this contact.
73+
- Otherwise:
74+
- MUST generate a unique `invreq_contact_secret` for that contact.
75+
- MUST associate this `invreq_contact_secret` with this contact.
76+
- MUST include this `invreq_contact_secret` whenever paying this contact.
77+
- Otherwise:
78+
- MUST include the `invreq_contact_secret` associated with this contact.
79+
- MUST include either `invreq_payer_offer` or `invreq_payer_bip_353_name`.
80+
- If it includes `invreq_payer_bip_353_name`:
81+
- MUST set `name` to the post-₿, pre-@ part of the BIP 353 HRN.
82+
- MUST set `domain` to the post-@ part of the BIP 353 HRN.
83+
- If it includes `invreq_payer_offer`:
84+
- MUST encode `payer_offer` as a TLV stream of its individual records.
85+
- If the encoded offer is more than 300 bytes long:
86+
- SHOULD NOT include `invreq_payer_offer`.
87+
- SHOULD include `invreq_payer_bip_353_name` instead.
88+
- Otherwise:
89+
- MUST NOT include `invreq_contact_secret`, `invreq_payer_offer` or
90+
`invreq_bip_353_name`.
91+
92+
The reader of `invoice_request`:
93+
94+
- MUST send back an `invoice` including the `invoice_request` contact fields
95+
provided by the sender, as specified in Bolt 12.
96+
- After the invoice has been paid, if `invreq_contact_secret` was included:
97+
- If it matches one of their contacts:
98+
- SHOULD display the `invreq_payer_note`, if one is provided.
99+
- MUST ignore `invreq_payer_offer` and `invreq_bip_353_name`.
100+
- Otherwise:
101+
- MAY use the `contact_secret`, `payer_offer` and `payer_bip_353_name` to
102+
create a new contact. If they do:
103+
- MUST use the received `contact_secret` whenever paying that contact.
104+
- MUST use the received `payer_offer` whenever paying that contact.
105+
- If `payer_bip_353_name` was included:
106+
- SHOULD use it to fetch a `payer_offer` if none was included.
107+
- SHOULD use it to refresh the `payer_offer` if it expires.
108+
- MAY use it to refresh the `payer_offer` periodically.
109+
- MAY manually associate the received `contact_secret` with an existing
110+
contact, if the user verified that the payment came from this contact.
111+
112+
#### Rationale
113+
114+
The `contact_secret` field is used for mutual identification: it is set by the
115+
node sending the first payment and must be reused by the recipient when sending
116+
payments in the other direction. Its usage and edge cases are detailed in the
117+
[Contact Secrets](#contact-secrets) section below.
118+
119+
Nodes generally don't store every `invoice_request` they receive, because that
120+
would expose them to DoS. They instead include the fields they would like to
121+
store in the `path_id` field of the blinded path(s) of the `invoice` they send
122+
back. Since this `path_id` will then be included in payment onions, which are
123+
limited to 1300 bytes, nodes must ensure that the resulting `path_id` isn't too
124+
large, which would constrain the payment paths that can be used by the payer.
125+
We thus recommend only including offers that are smaller than 300 bytes in
126+
`invreq_payer_offer`, or a small BIP 353 HRN.
127+
128+
When payments are coming from known contacts, there is less risk that the
129+
`payer_note` that is optionally included contains spam. It is thus recommended
130+
to display it, while we generally don't recommend displaying `payer_note`s
131+
coming from unknown payers.
132+
133+
When receiving payments from existing contacts, the offer and BIP 353 HRN must
134+
be ignored: this ensures that if the `contact_secret` was leaked, a malicious
135+
node impersonating our contact cannot redirect our future payments to their
136+
own offers.
137+
138+
### Contact Secrets
139+
140+
The main mechanism of this proposal is the exchange of `contact_secret`s.
141+
This section details various scenarios that may occur and how to correctly
142+
deal with each of them, along with a recommended UX.
143+
144+
#### Adding contacts
145+
146+
When Alice wants to pay Bob, her wallet should offer an option to add him to
147+
her contacts list (using a checkbox or a dedicated button). If she chooses
148+
that option, she generates a random `contact_secret`. For all future payments
149+
made to Bob where she wants to reveal that she's the payer, Alice will include
150+
this same `contact_secret`. Wallets should always offer the option to pay a
151+
contact privately, in which case the `contact_secret` and payer information
152+
will not be included in the `invoice_request`.
153+
154+
Once Bob has received a payment that includes a `contact_secret`, his wallet
155+
should display an option to add the payer to its own contacts list (e.g. via
156+
a dedicated button the received payment page). If he knows that this payment
157+
came from Alice (because Alice verifiably told him that it indeed came from
158+
her), he's able to add Alice to his contacts and pay her back using the
159+
`payer_offer` or `payer_bip_353_name` she provided. For all future payments
160+
made to Alice where Bob wants to reveal that he's the payer, Bob will include
161+
the `contact_secret` generated by Alice. Note that in this case, Bob doesn't
162+
generate a different `contact_secret`, because he already has one available
163+
that was created by Alice, which he knows Alice will be able to use to identify
164+
payments.
165+
166+
However, if Bob adds Alice to his contacts list without using the payment he
167+
received from her, or if he adds her to his contacts list on another wallet
168+
than the one used to receive Alice's payment, Bob will generate a different
169+
random `contact_secret`. For all payments made to Alice where he wants to
170+
reveal that he's the payer, he will use that new `contact_secret`. When Alice
171+
receives those payments, she won't be able to automatically identify that it's
172+
coming from Bob based on the `contact_secret` alone, because it is different
173+
from the one she generated. But if Alice knows that a specific payment came
174+
from Bob (because he verifiably told her so), her wallet should allow her to
175+
attribute this payment to an existing contact (e.g. by clicking an "add to
176+
contacts" button on the received payment and then choosing an "add to existing
177+
contact" option). Her wallet will then add that additional `contact_secret` to
178+
the list of secrets Bob may use when paying her. This action automatically
179+
reconciles past and future payments made from Bob.
180+
181+
A contact entry thus contains the following information:
182+
183+
- `primary_contact_secret`: the first `contact_secret` used, which must be used
184+
for *all* outgoing payments to this contact and may either have been created
185+
by us (if we made the first payment) or by our contact (if we added them to
186+
our contacts list based on a payment we received).
187+
- `additional_remote_contact_secrets`: a list of secondary `contact_secret`s
188+
that our contact may use when paying us, obtained by manually associating
189+
payments with our existing contact.
190+
191+
#### Leaked contact secrets
192+
193+
Contact secrets shouldn't be shared publicly, as that would let other people
194+
make payments that appear to be coming from you. This doesn't allow stealing
195+
funds though: even if the impersonator includes their own offer in a payment
196+
they make on your behalf in the `invreq_payer_offer` field, the receiving node
197+
will ignore it if they have already stored your contact information. If they
198+
haven't, they have no reason to create a new contact based on this payment.
199+
200+
### Deterministic derivation
201+
202+
When creating a new contact, we recommend using the following deterministic
203+
derivation for the `contact_secret` field:
204+
205+
- For a given Bolt 12 offer, we define its `offer_node_id` as:
206+
- If the offer contains `offer_issuer_id`:
207+
- `offer_node_id = offer_issuer_id`.
208+
- Otherwise, the offer must contain `offer_paths`:
209+
- `offer_node_id` is set to the last `blinded_node_id` of the first
210+
`path`.
211+
- The private key for the `offer_node_id` is called `offer_priv_key`.
212+
- When paying `remote_offer` for which we include our `local_offer` in the
213+
`invreq_payer_offer` field:
214+
- We compute the ECDH of the two `offer_node_id`s:
215+
- `shared_key = local_offer.offer_priv_key * remote_offer.offer_node_id`.
216+
- We use a tagged hash to derive the `contact_secret`:
217+
- `contact_secret = SHA256("blip42_contact_secret" || shared_key)`.
218+
219+
Using this deterministic derivation has multiple benefits. First of all, it
220+
guarantees that both nodes independently derive the same `contact_secret` when
221+
using the same set of offers, which removes the need to reconcile secrets when
222+
nodes concurrently add each other to their contacts list.
223+
224+
It is particularly useful for wallets that use a single offer that is created
225+
deterministically from the user's seed: this ensures that the `contact_secret`
226+
can also be restored from seed.
227+
228+
#### Test vector
229+
230+
The following test vectors use the deterministic derivation from the previous
231+
section.
232+
233+
```json
234+
[
235+
{
236+
"comment": "derive deterministic contact_secret when both offers use blinded paths only",
237+
"alice_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsrejlwh4vyz70s46r62vtakl4sxztqj6gxjged0wx0ly8qtrygufcsyq5agaes6v605af5rr9ydnj9srneudvrmc73n7evp72tzpqcnd28puqr8a3wmcff9wfjwgk32650vl747m2ev4zsjagzucntctlmcpc6vhmdnxlywneg5caqz0ansr45z2faxq7unegzsnyuduzys7kzyugpwcmhdqqj0h70zy92p75pseunclwsrwhaelvsqy9zsejcytxulndppmykcznn7y5h",
238+
"alice_offer_priv_key": "4ed1a01dae275f7b7ba503dbae23dddd774a8d5f64788ef7a768ed647dd0e1eb",
239+
"alice_offer_node_id": "0284c9c6f04487ac22710176377680127dfcf110aa0fa8186793c7dd01bafdcfd9",
240+
"bob_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsz4n88s74qhussxsu0vs3c4unck4yelk67zdc29ree3sztvjn7pc9qyqlcpj54jnj67aa9rd2n5dhjlxyfmv3vgqymrks2nf7gnf5u200mn5qrxfrxh9d0ug43j5egklhwgyrfv3n84gyjd2aajhwqxa0cc7zn37sncrwptz4uhlp523l83xpjx9dw72spzecrtex3ku3h3xpepeuend5rtmurekfmnqsq6kva9yr4k3dtplku9v6qqyxr5ep6lls3hvrqyt9y7htaz9qj",
241+
"bob_offer_priv_key": "12afb8248c7336e6aea5fe247bc4bac5dcabfb6017bd67b32c8195a6c56b8333",
242+
"bob_offer_node_id": "035e4d1b7237898390e7999b6835ef83cd93b98200d599d29075b45ab0fedc2b34",
243+
"contact_secret": "810641fab614f8bc1441131dc50b132fd4d1e2ccd36f84b887bbab3a6d8cc3d8"
244+
},
245+
{
246+
"comment": "derive deterministic contact_secret when one offer uses both blinded paths and issuer_id",
247+
"alice_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsrejlwh4vyz70s46r62vtakl4sxztqj6gxjged0wx0ly8qtrygufcsyq5agaes6v605af5rr9ydnj9srneudvrmc73n7evp72tzpqcnd28puqr8a3wmcff9wfjwgk32650vl747m2ev4zsjagzucntctlmcpc6vhmdnxlywneg5caqz0ansr45z2faxq7unegzsnyuduzys7kzyugpwcmhdqqj0h70zy92p75pseunclwsrwhaelvsqy9zsejcytxulndppmykcznn7y5h",
248+
"alice_offer_priv_key": "4ed1a01dae275f7b7ba503dbae23dddd774a8d5f64788ef7a768ed647dd0e1eb",
249+
"alice_offer_node_id": "0284c9c6f04487ac22710176377680127dfcf110aa0fa8186793c7dd01bafdcfd9",
250+
"bob_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsz4n88s74qhussxsu0vs3c4unck4yelk67zdc29ree3sztvjn7pc9qyqlcpj54jnj67aa9rd2n5dhjlxyfmv3vgqymrks2nf7gnf5u200mn5qrxfrxh9d0ug43j5egklhwgyrfv3n84gyjd2aajhwqxa0cc7zn37sncrwptz4uhlp523l83xpjx9dw72spzecrtex3ku3h3xpepeuend5rtmurekfmnqsq6kva9yr4k3dtplku9v6qqyxr5ep6lls3hvrqyt9y7htaz9qjzcssy065ctv38c5h03lu0hlvq2t4p5fg6u668y6pmzcg64hmdm050jxx",
251+
"bob_offer_priv_key": "bcaafa8ed73da11437ce58c7b3458567a870168c0da325a40292fed126b97845",
252+
"bob_offer_node_id": "023f54c2d913e2977c7fc7dfec029750d128d735a39341d8b08d56fb6edf47c8c6",
253+
"contact_secret": "4e0aa72cc42eae9f8dc7c6d2975bbe655683ada2e9abfdfe9f299d391ed9736c"
254+
}
255+
]
256+
```
257+
258+
## Reference Implementations
259+
260+
- lightning-kmp: <https://github.com/ACINQ/lightning-kmp/pull/719>
261+
- phoenix wallet: <https://github.com/ACINQ/phoenix/pull/645>

0 commit comments

Comments
 (0)