diff --git a/README.md b/README.md index f2f8313..2dab3c7 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ For more detail on the process, please read [bLIP-0001](./blip-0001.md) and | [17](./blip-0017.md) | Hosted Channels | Anton Kumaigorodskiy | Active | | [25](./blip-0025.md) | Forward less than onion value | Valentine Wallace | Active | | [32](./blip-0032.md) | Onion Message DNS Resolution | Matt Corallo | Active | +| [42](./blip-0042.md) | Bolt 12 Contacts | Bastien Teinturier | Active | | [50](./blip-0050.md) | LSPS0: LSP Spec Transport Layer | ZmnSCPxj jxPCSnmZ | Active | | [51](./blip-0051.md) | LSPS1: Channel Requests | Severin Bühler | Active | | [52](./blip-0052.md) | LSPS2: JIT Channel Negotiation | ZmnSCPxj jxPCSnmZ | Active | diff --git a/blip-0002.md b/blip-0002.md index f0e2937..72da790 100644 --- a/blip-0002.md +++ b/blip-0002.md @@ -35,6 +35,7 @@ network split. * [`ping`](#ping) * [`update_add_htlc`](#update_add_htlc) * [Onion Messages](#onion-messages) + * [`invoice_request`](#invoice_request) ### Feature bits @@ -133,6 +134,17 @@ The following table contains tlv fields for use in onion messages as the payload | 65536 | `dnssec_query` | [bLIP 32](./blip-0032.md) | | 65538 | `dnssec_proof` | [bLIP 32](./blip-0032.md) | +#### `invoice_request` + +The following table contains extension tlv fields for `invoice_request`s sent in +onion messages: + +| Type | Name | Link | +|------------|-----------------------------|---------------------------| +| 2000001729 | `invreq_contact_secret` | [bLIP 42](./blip-0042.md) | +| 2000001731 | `invreq_payer_offer` | [bLIP 42](./blip-0042.md) | +| 2000001733 | `invreq_payer_bip_353_name` | [bLIP 42](./blip-0042.md) | + ## Copyright This bLIP is licensed under the CC0 license. diff --git a/blip-0042.md b/blip-0042.md new file mode 100644 index 0000000..25f5a4e --- /dev/null +++ b/blip-0042.md @@ -0,0 +1,288 @@ +``` +bLIP: 42 +Title: Bolt 12 Contacts +Status: Active +Author: Bastien Teinturier +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`] + +The `invreq_payer_bip_353_signature` field lets payers provide a signature of +the `invoice_request` using one of the signing keys of the offer associated +with their BIP 353 name, thus proving ownership of this BIP 353 name. + +1. type: 2000001735 (`invreq_payer_bip_353_signature`) +2. data: + - [`point`:`offer_signing_key`] + - [`bip340sig`:`signature`] + +#### Requirements + +The writer of `invoice_request`: + +- If they want the recipient to be able to identify who paid them: + - If the recipient is not yet part of their contacts list: + - If they have previously received a payment from this recipient including + the `invreq_contact_secret` field: + - MUST associate the received `invreq_contact_secret` with this contact. + - MUST include this `invreq_contact_secret` whenever paying this contact. + - Otherwise: + - MUST generate a unique `invreq_contact_secret` for that contact. + - MUST associate this `invreq_contact_secret` with this contact. + - MUST include this `invreq_contact_secret` whenever paying this contact. + - Otherwise: + - MUST include the `invreq_contact_secret` associated with this 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. + - MUST include `invreq_payer_bip_353_signature` using the private key of + its `offer_issuer_id` or, if its offer doesn't include `offer_issuer_id`, + the private key of the `blinded_node_id` of one of its `offer_paths`. + The signed data should be computed as detailed in [Bolt 12][bolt12-sig], + with "lightning" || "invoice_request" || "invreq_payer_bip_353_signature" + as signature tag. + - 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`: + +- If `invreq_bip_353_name` is provided: + - MUST ignore the `invoice_request` if `invreq_payer_bip_353_signature` is + missing or invalid. +- 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 their contacts: + - SHOULD display the `invreq_payer_note`, if one is provided. + - MUST ignore `invreq_payer_offer` and `invreq_bip_353_name`. + - Otherwise: + - MAY use the `contact_secret`, `payer_offer` and `payer_bip_353_name` to + create a new contact. If they do: + - MUST use the received `contact_secret` whenever paying that contact. + - MUST use the received `payer_offer` whenever paying that contact. + - If `payer_bip_353_name` was included: + - SHOULD use it to fetch a `payer_offer` if none was included. + - MUST verify that the received offer matches the `offer_signing_key` + provided in `invreq_payer_bip_353_signature`. + - SHOULD use it to refresh the `payer_offer` if it expires. + - MAY use it to refresh the `payer_offer` periodically. + - MAY manually associate the received `contact_secret` with an existing + contact, if the user verified that the payment came from this contact. + +#### Rationale + +The `contact_secret` field is used for mutual identification: it is set by the +node sending the first payment and must be reused by the recipient when sending +payments in the other direction. Its usage and edge cases are 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 a BIP 353 HRN is included, the node receiving `invoice_request` does not +know whether it really belongs to the sender. This is why the sender must also +include a signature that proves that they control one of the private keys of +the offer stored in the BIP 353 record. + +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, along with a recommended UX. + +#### Adding contacts + +When Alice wants to pay Bob, her wallet should offer an option to add him to +her contacts list (using a checkbox or a dedicated button). If she chooses +that option, 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`. Wallets should always offer the option to pay a +contact privately, in which case the `contact_secret` and payer information +will not be included in the `invoice_request`. + +Once Bob has received a payment that includes a `contact_secret`, his wallet +should display an option to add the payer to its own contacts list (e.g. via +a dedicated button the received payment page). If he knows that this payment +came from Alice (because Alice verifiably told him that it indeed came from +her), 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, because it is different +from the one she generated. But if Alice knows that a specific payment came +from Bob (because he verifiably told her so), her wallet should allow her to +attribute this payment to an existing contact (e.g. by clicking an "add to +contacts" button on the received payment and then choosing an "add to existing +contact" option). Her wallet will then add 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. + +#### 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: +- phoenix wallet: + +[bolt12-sig]: https://github.com/lightning/bolts/blob/master/12-offer-encoding.md#signature-calculation