|
| 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`, when paying one of their contacts: |
| 66 | + |
| 67 | +- If it wants the payer to be able to identify who paid them: |
| 68 | + - MUST include the `invreq_contact_secret` associated with that contact. |
| 69 | + - MUST include either `invreq_payer_offer` or `invreq_payer_bip_353_name`. |
| 70 | + - If it includes `invreq_payer_bip_353_name`: |
| 71 | + - MUST set `name` to the post-₿, pre-@ part of the BIP 353 HRN. |
| 72 | + - MUST set `domain` to the post-@ part of the BIP 353 HRN. |
| 73 | + - If it includes `invreq_payer_offer`: |
| 74 | + - MUST encode `payer_offer` as a TLV stream of its individual records. |
| 75 | + - If the encoded offer is more than 300 bytes long: |
| 76 | + - SHOULD NOT include `invreq_payer_offer`. |
| 77 | + - SHOULD include `invreq_payer_bip_353_name` instead. |
| 78 | +- Otherwise: |
| 79 | + - MUST NOT include `invreq_contact_secret`, `invreq_payer_offer` or |
| 80 | + `invreq_bip_353_name`. |
| 81 | + |
| 82 | +The reader of `invoice_request`: |
| 83 | + |
| 84 | +- MUST send back an `invoice` including the `invoice_request` contact fields |
| 85 | + provided by the sender, as specified in Bolt 12. |
| 86 | +- After the invoice has been paid, if `invreq_contact_secret` was included: |
| 87 | + - If it matches one of its contacts: |
| 88 | + - SHOULD display the `invreq_payer_note`, if one is provided. |
| 89 | + - Otherwise: |
| 90 | + - MAY use the `contact_secret`, `payer_offer` and `payer_bip_353_name` to |
| 91 | + create a new contact. |
| 92 | + - If it creates a new contact based on this received payment, the |
| 93 | + received `contact_secret` MUST be used when paying that contact. |
| 94 | + - MAY associate the received `contact_secret` with an existing contact. |
| 95 | + - MUST ignore `invreq_payer_offer` and `invreq_bip_353_name` if it already |
| 96 | + has an offer for this contact. |
| 97 | + |
| 98 | +#### Rationale |
| 99 | + |
| 100 | +The `contact_secret` field is used for mutual identification: its usage is |
| 101 | +detailed in the [Contact Secrets](#contact-secrets) section below. |
| 102 | + |
| 103 | +Nodes generally don't store every `invoice_request` they receive, because that |
| 104 | +would expose them to DoS. They instead include the fields they would like to |
| 105 | +store in the `path_id` field of the blinded path(s) of the `invoice` they send |
| 106 | +back. Since this `path_id` will then be included in payment onions, which are |
| 107 | +limited to 1300 bytes, nodes must ensure that the resulting `path_id` isn't too |
| 108 | +large, which would constrain the payment paths that can be used by the payer. |
| 109 | +We thus recommend only including offers that are smaller than 300 bytes in |
| 110 | +`invreq_payer_offer`, or a small BIP 353 HRN. |
| 111 | + |
| 112 | +When payments are coming from known contacts, there is less risk that the |
| 113 | +`payer_note` that is optionally included contains spam. It is thus recommended |
| 114 | +to display it, while we generally don't recommend displaying `payer_note`s |
| 115 | +coming from unknown payers. |
| 116 | + |
| 117 | +When receiving payments from existing contacts, the offer and BIP 353 HRN must |
| 118 | +be ignored: this ensures that if the `contact_secret` was leaked, a malicious |
| 119 | +node impersonating our contact cannot redirect our future payments to their |
| 120 | +own offers. |
| 121 | + |
| 122 | +### Contact Secrets |
| 123 | + |
| 124 | +The main mechanism of this proposal is the exchange of `contact_secret`s. |
| 125 | +This section details various scenarios that may occur and how to correctly |
| 126 | +deal with each of them. |
| 127 | + |
| 128 | +#### Adding contacts |
| 129 | + |
| 130 | +When Alice adds Bob to her contacts list from an offer she received from Bob, |
| 131 | +she generates a random `contact_secret`. For all future payments made to Bob |
| 132 | +where she wants to reveal that she's the payer, Alice will include this same |
| 133 | +`contact_secret`. |
| 134 | + |
| 135 | +Once Bob has received a payment that includes a `contact_secret`, he may add |
| 136 | +the payer to its own contacts list. If he knows that this payment came from |
| 137 | +Alice, he's able to add Alice to his contacts and pay her back using the |
| 138 | +`payer_offer` or `payer_bip_353_name` she provided. For all future payments |
| 139 | +made to Alice where Bob wants to reveal that he's the payer, Bob will include |
| 140 | +the `contact_secret` generated by Alice. Note that in this case, Bob doesn't |
| 141 | +generate a different `contact_secret`, because he already has one available |
| 142 | +that was created by Alice, which he knows Alice will be able to use to identify |
| 143 | +payments. |
| 144 | + |
| 145 | +However, if Bob adds Alice to his contacts list without using the payment he |
| 146 | +received from her, or if he adds her to his contacts list on another wallet |
| 147 | +than the one used to receive Alice's payment, Bob will generate a different |
| 148 | +random `contact_secret`. For all payments made to Alice where he wants to |
| 149 | +reveal that he's the payer, he will use that new `contact_secret`. When Alice |
| 150 | +receives those payments, she won't be able to automatically identify that it's |
| 151 | +coming from Bob based on the `contact_secret` alone. But Alice is usually able |
| 152 | +to know that a specific payment came from Bob: she can then detect that Bob |
| 153 | +used a different `contact_secret` from the one she initially created, and she |
| 154 | +can store that additional `contact_secret` to the list of secrets Bob may use |
| 155 | +when paying her. This action automatically reconciles past and future payments |
| 156 | +made from Bob. |
| 157 | + |
| 158 | +A contact entry thus contains the following information: |
| 159 | + |
| 160 | +- `primary_contact_secret`: the first `contact_secret` used, which must be used |
| 161 | + for *all* outgoing payments to this contact and may either have been created |
| 162 | + by us (if we made the first payment) or by our contact (if we added them to |
| 163 | + our contacts list based on a payment we received). |
| 164 | +- `additional_remote_contact_secrets`: a list of secondary `contact_secret`s |
| 165 | + that our contact may use when paying us, obtained by manually associating |
| 166 | + payments with our existing contact. |
| 167 | + |
| 168 | +Note that wallets can also automatically reconcile payments when they detect |
| 169 | +that the `payer_offer` or `payer_bip_353_name` received matches an existing |
| 170 | +contact but uses a different `contact_secret`. |
| 171 | + |
| 172 | +#### Leaked contact secrets |
| 173 | + |
| 174 | +Contact secrets shouldn't be shared publicly, as that would let other people |
| 175 | +make payments that appear to be coming from you. This doesn't allow stealing |
| 176 | +funds though: even if the impersonator includes their own offer in a payment |
| 177 | +they make on your behalf in the `invreq_payer_offer` field, the receiving node |
| 178 | +will ignore it if they have already stored your contact information. If they |
| 179 | +haven't, they have no reason to create a new contact based on this payment. |
| 180 | + |
| 181 | +### Deterministic derivation |
| 182 | + |
| 183 | +When creating a new contact, we recommend using the following deterministic |
| 184 | +derivation for the `contact_secret` field: |
| 185 | + |
| 186 | +- For a given Bolt 12 offer, we define its `offer_node_id` as: |
| 187 | + - If the offer contains `offer_issuer_id`: |
| 188 | + - `offer_node_id = offer_issuer_id`. |
| 189 | + - Otherwise, the offer must contain `offer_paths`: |
| 190 | + - `offer_node_id` is set to the last `blinded_node_id` of the first |
| 191 | + `path`. |
| 192 | +- The private key for the `offer_node_id` is called `offer_priv_key`. |
| 193 | +- When paying `remote_offer` for which we include our `local_offer` in the |
| 194 | + `invreq_payer_offer` field: |
| 195 | + - We compute the ECDH of the two `offer_node_id`s: |
| 196 | + - `shared_key = local_offer.offer_priv_key * remote_offer.offer_node_id`. |
| 197 | + - We use a tagged hash to derive the `contact_secret`: |
| 198 | + - `contact_secret = SHA256("blip42_contact_secret" || shared_key)`. |
| 199 | + |
| 200 | +Using this deterministic derivation has multiple benefits. First of all, it |
| 201 | +guarantees that both nodes independently derive the same `contact_secret` when |
| 202 | +using the same set of offers, which removes the need to reconcile secrets when |
| 203 | +nodes concurrently add each other to their contacts list. |
| 204 | + |
| 205 | +It is particularly useful for wallets that use a single offer that is created |
| 206 | +deterministically from the user's seed: this ensures that the `contact_secret` |
| 207 | +can also be restored from seed. |
| 208 | + |
| 209 | +#### Test vector |
| 210 | + |
| 211 | +The following test vectors use the deterministic derivation from the previous |
| 212 | +section. |
| 213 | + |
| 214 | +```json |
| 215 | +[ |
| 216 | + { |
| 217 | + "comment": "derive deterministic contact_secret when both offers use blinded paths only", |
| 218 | + "alice_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsrejlwh4vyz70s46r62vtakl4sxztqj6gxjged0wx0ly8qtrygufcsyq5agaes6v605af5rr9ydnj9srneudvrmc73n7evp72tzpqcnd28puqr8a3wmcff9wfjwgk32650vl747m2ev4zsjagzucntctlmcpc6vhmdnxlywneg5caqz0ansr45z2faxq7unegzsnyuduzys7kzyugpwcmhdqqj0h70zy92p75pseunclwsrwhaelvsqy9zsejcytxulndppmykcznn7y5h", |
| 219 | + "alice_offer_priv_key": "4ed1a01dae275f7b7ba503dbae23dddd774a8d5f64788ef7a768ed647dd0e1eb", |
| 220 | + "alice_offer_node_id": "0284c9c6f04487ac22710176377680127dfcf110aa0fa8186793c7dd01bafdcfd9", |
| 221 | + "bob_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsz4n88s74qhussxsu0vs3c4unck4yelk67zdc29ree3sztvjn7pc9qyqlcpj54jnj67aa9rd2n5dhjlxyfmv3vgqymrks2nf7gnf5u200mn5qrxfrxh9d0ug43j5egklhwgyrfv3n84gyjd2aajhwqxa0cc7zn37sncrwptz4uhlp523l83xpjx9dw72spzecrtex3ku3h3xpepeuend5rtmurekfmnqsq6kva9yr4k3dtplku9v6qqyxr5ep6lls3hvrqyt9y7htaz9qj", |
| 222 | + "bob_offer_priv_key": "12afb8248c7336e6aea5fe247bc4bac5dcabfb6017bd67b32c8195a6c56b8333", |
| 223 | + "bob_offer_node_id": "035e4d1b7237898390e7999b6835ef83cd93b98200d599d29075b45ab0fedc2b34", |
| 224 | + "contact_secret": "810641fab614f8bc1441131dc50b132fd4d1e2ccd36f84b887bbab3a6d8cc3d8" |
| 225 | + }, |
| 226 | + { |
| 227 | + "comment": "derive deterministic contact_secret when one offer uses both blinded paths and issuer_id", |
| 228 | + "alice_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsrejlwh4vyz70s46r62vtakl4sxztqj6gxjged0wx0ly8qtrygufcsyq5agaes6v605af5rr9ydnj9srneudvrmc73n7evp72tzpqcnd28puqr8a3wmcff9wfjwgk32650vl747m2ev4zsjagzucntctlmcpc6vhmdnxlywneg5caqz0ansr45z2faxq7unegzsnyuduzys7kzyugpwcmhdqqj0h70zy92p75pseunclwsrwhaelvsqy9zsejcytxulndppmykcznn7y5h", |
| 229 | + "alice_offer_priv_key": "4ed1a01dae275f7b7ba503dbae23dddd774a8d5f64788ef7a768ed647dd0e1eb", |
| 230 | + "alice_offer_node_id": "0284c9c6f04487ac22710176377680127dfcf110aa0fa8186793c7dd01bafdcfd9", |
| 231 | + "bob_offer": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcsesp0grlulxv3jygx83h7tghy3233sqd6xlcccvpar2l8jshxrtwvtcsz4n88s74qhussxsu0vs3c4unck4yelk67zdc29ree3sztvjn7pc9qyqlcpj54jnj67aa9rd2n5dhjlxyfmv3vgqymrks2nf7gnf5u200mn5qrxfrxh9d0ug43j5egklhwgyrfv3n84gyjd2aajhwqxa0cc7zn37sncrwptz4uhlp523l83xpjx9dw72spzecrtex3ku3h3xpepeuend5rtmurekfmnqsq6kva9yr4k3dtplku9v6qqyxr5ep6lls3hvrqyt9y7htaz9qjzcssy065ctv38c5h03lu0hlvq2t4p5fg6u668y6pmzcg64hmdm050jxx", |
| 232 | + "bob_offer_priv_key": "bcaafa8ed73da11437ce58c7b3458567a870168c0da325a40292fed126b97845", |
| 233 | + "bob_offer_node_id": "023f54c2d913e2977c7fc7dfec029750d128d735a39341d8b08d56fb6edf47c8c6", |
| 234 | + "contact_secret": "4e0aa72cc42eae9f8dc7c6d2975bbe655683ada2e9abfdfe9f299d391ed9736c" |
| 235 | + } |
| 236 | +] |
| 237 | +``` |
| 238 | + |
| 239 | +## Reference Implementations |
| 240 | + |
| 241 | +- lightning-kmp: <https://github.com/ACINQ/lightning-kmp/pull/719> |
0 commit comments