Skip to content

Latest commit

 

History

History
295 lines (240 loc) · 15 KB

blip-0042.md

File metadata and controls

295 lines (240 loc) · 15 KB
bLIP: 42
Title: Bolt 12 Contacts
Status: Active
Author: Bastien Teinturier <bastien@acinq.fr>
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

When creating a new payment contact:

  • MUST receive user authorization to reveal payment information to the contact.
  • If the node has previously received a payment from this contact including the invreq_contact_secret field:
    • MUST associate the received invreq_contact_secret, invreq_payer_offer and invreq_payer_bip_353_name with this contact.
    • If invreq_payer_bip_353_name was included:
      • SHOULD use it to fetch an invreq_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 invreq_payer_offer if it expires.
      • MAY use it to refresh the invreq_payer_offer periodically.
  • Otherwise:
    • MUST generate a unique invreq_contact_secret for that contact, using deterministic derivation.
    • MUST associate this invreq_contact_secret with this contact.
    • MUST include this invreq_contact_secret whenever paying this contact.

The writer of invoice_request:

  • If they want the recipient to be able to identify who paid them:
    • If the recipient is not a payment contact:
      • MUST seek user authorization to create a payment contact matching that recipient.
    • 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, 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_payer_bip_353_name.

The reader of invoice_request:

  • If invreq_payer_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.

When an invoice has been paid:

  • If invreq_contact_secret was included in the invoice_request:
    • If it matches an existing payment contact:
      • SHOULD display the invreq_payer_note, if one is provided.
      • MUST ignore invreq_payer_offer and invreq_payer_bip_353_name.
    • Otherwise:
      • MAY seek authorization from the user to add a new contact or update an existing contact based on this received payment (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 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_notes 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_secrets. 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 contact_secret (see details about generation in the deterministic derivation section). For all future payments made to Bob where she wants to reveal that she's the payer, Alice will include this 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 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_secrets 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 use 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_ids:
      • 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.

[
  {
    "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