diff --git a/README.md b/README.md index e2a1c45..b4b6780 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,4 @@ For more detail on the process, please read [bLIP-0001](./blip-0001.md) and | [51](./blip-0051.md) | LSPS1: Channel Requests | Severin Bühler | Active | | [52](./blip-0052.md) | LSPS2: JIT Channel Negotiation | ZmnSCPxj jxPCSnmZ | Active | | [55](./blip-0055.md) | LSPS5: Webhook Registration | ZmnSCPxj jxPCSnmZ | Active | +| [66](./blip-0066.md) | Static Invoice Server Protocol | Valentine Wallace | Active | diff --git a/blip-0002.md b/blip-0002.md index f0e2937..10eb941 100644 --- a/blip-0002.md +++ b/blip-0002.md @@ -132,6 +132,10 @@ 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) | +| 65540 | `offer_paths_request` | [bLIP 66](./blip-0066.md) | +| 65542 | `offer_paths` | [bLIP 66](./blip-0066.md) | +| 65544 | `serve_static_invoice` | [bLIP 66](./blip-0066.md) | +| 65546 | `static_invoice_persisted` | [bLIP 66](./blip-0066.md) | ## Copyright diff --git a/blip-0066.md b/blip-0066.md new file mode 100644 index 0000000..4046d6f --- /dev/null +++ b/blip-0066.md @@ -0,0 +1,187 @@ +``` +bLIP: 66 +Title: Static Invoice Server Protocol +Status: Draft +Author: Valentine Wallace +Created: 2025-10-16 +License: CC0 +``` + +## Motivation + +To recap, under the async payments protocol either a sender or a sender's LSP will hold onto an +HTLC for a payment until the often-offline recipient sends them an onion message indicating that +the recipient has come online and is ready to receive the HTLC, at which point the HTLC will be +released. + +This design presents an issue for BOLT 12, because BOLT 12 requires recipients to be +online to respond to invoice requests from senders. Without an invoice from the recipient provided +at pay-time, the sender cannot lock in their held HTLC to begin with. + +Here we define a way for a sender to get a static or payment-hash-less BOLT 12 invoice created by +the often-offline recipient, even if the recipient is offline when the sender goes to pay. + +## Abstract + +This bLIP defines a protocol for always-online nodes acting as "static invoice servers" to assist +often-offline lightning recipients in receiving async payments. This protocol enables mobile wallets +and other frequently-offline nodes to delegate the task of responding to invoice requests from +payers, while maintaining custody of funds. The aforementioned invoice servers will respond to these +invoice requests with a static or payment-hash-less invoice, which can then be paid via the [async +payments protocol](https://github.com/lightning/bolts/pull/1149). + +## Specification + +Four new onion messages are defined, `offer_paths_request`, `offer_paths`, `serve_static_invoice`, +and `static_invoice_persisted`. + +1. type: 65540 (`offer_paths_request`) +2. `tlv_stream`: `offer_paths_request_tlvs` +3. types: + 1. type: 0 (`invoice_slot`) + 2. data: + - [`u16`: `slot_number`] + +1. type: 65542 (`offer_paths`) +2. `tlv_stream`: `offer_paths_tlvs` +3. types: + 1. type: 0 (`offer_paths`) + 2. data: + - [`...*blinded_path`: `paths`] + 3. type: 2 (`paths_absolute_expiry`) + 4. data: + - [`u64`: `seconds_from_epoch`] + +1. type: 65544 (`serve_static_invoice`) +2. `tlv_stream`: `serve_static_invoice_tlvs` +3. types: + 1. type: 0 (`static_invoice`) + 2. data: + - [`tlv_static_invoice:`: `static_inv`] + 3. type: 2 (`forward_invoice_request_path`) + 4. data: + - [`blinded_path: forward_invreq_path`] + +1. type: 65546 (`static_invoice_persisted`) + +### Overview + +The overall flow of the protocol is as follows: +1. Often-offline recipients are configured out-of-band with blinded paths to reach the static + invoice server, which allows the server to authenticate the `offer_paths_request` +2. Recipient sends `offer_paths_request` to request offer paths from the static invoice server +3. Server responds with `offer_paths` containing blinded paths that payers will eventually use to + send invoice requests to the server +4. Recipient creates an offer containing those blinded paths from the server, and also creates a + corresponding static invoice +5. Recipient sends `serve_static_invoice` containing the static invoice they just created +6. Server receives the static invoice, persists it, and responds with `static_invoice_persisted` to + confirm the offer is ready to receive async payments +7. If the server later receives an invoice request on the recipient's behalf, they respond with the + previously persisted static invoice and also forward the invoice request to the recipient if they + are online + +### Requirements: + +Static invoice server nodes which have an out-of-band relationship with an often-offline node, e.g. +a mobile lightning node: +* SHOULD provide at least 1 blinded path out-of-band to the often-offline node that the + often-offline node will send `offer_paths_request`s over + +Senders of `offer_paths_request`: +* MUST set `reply_path` in the `onionmsg_tlv` stream +* MAY include the `invoice_slot` in the `reply_path` to remember which slot the resulting offer + corresponds to and replace the corresponding invoice in the future + +Recipients of `offer_paths_request`: +* MUST authenticate the request using encrypted data from the blinded path they received the request + over +* SHOULD respond with an `offer_paths` message if authentication succeeds + +Senders of `offer_paths`: +* MUST include at least 1 blinded path that terminates at their node in the `offer_paths` message, + that payers will use in the future when sending `invoice_request`s for static invoices +* MUST construct the blinded offer path encrypted data such that the static invoice can be uniquely + identified and retrieved in the future in response to invoice requests. E.g., include an + identifier for the recipient as well as the `invoice_slot` from the preceding + `offer_paths_request` +* MUST set `reply_path` in the `onionmsg_tlv` stream +* MUST construct the reply path encrypted data such that the recipient can reuse the reply path in + the future to update the invoice, e.g. by including a recipient identifier and the `invoice_slot` + from the preceding `offer_paths_request` +* MAY set `paths_absolute_expiry` to indicate the absolute time when the included blinded offer + path(s) expire and can no longer be used + +Recipients of `offer_paths`: +* MUST authenticate the message using encrypted data from the blinded reply path they received the + `offer_paths` message over +* SHOULD check that `paths_absolute_expiry` is not too soon for the offer paths to be usable +* SHOULD respond with a `serve_static_invoice` message using the provided `reply_path` after + building an offer using the provided path(s) and corresponding static invoice +* SHOULD persist the reply path to the `offer_paths` message, which can be used later to update the + static invoice + +Senders of `serve_static_invoice`: +* When creating the offer corresponding to the static invoice, MUST use blinded paths from the + preceding `offer_paths` message +* MUST set `forward_invoice_request_path` to a blinded path that the invoice server can use to + forward invoice requests from payers to this recipient's node +* MUST set `reply_path` in the `onionmsg_tlv` stream + +Recipients of `serve_static_invoice`: +* MUST authenticate the message using encrypted data from the blinded reply path they received the + `serve_static_invoice` message over +* If the `invoice_slot` in the encrypted data from the blinded path they received this message over + corresponds to an existing invoice, SHOULD replace that invoice with this one and stop serving the + previous invoice +* SHOULD limit the amount of storage they will dedicate to persisting invoices for a particular + client +* SHOULD persist the `static_invoice` and `forward_invoice_request_path` and then respond with + `static_invoice_persisted` using the provided reply path + +Senders of `static_invoice_persisted`: +* SHOULD wait until the invoice is confirmed as persisted before sending this message + +Recipients of `static_invoice_persisted`: +* MUST authenticate the message using encrypted data from the blinded reply path they received the + `static_invoice_persisted` message over +* SHOULD mark the offer corresponding to the invoice as ready to receive async payments +* SHOULD update the persisted invoice periodically or when channels close/fees change, using the + previously persisted reply path to the preceding `offer_paths` message + +Static invoice servers: +* If an invoice request comes in corresponding to a static invoice that was previously persisted with +them: + * SHOULD retrieve the persisted invoice and respond to the payer with it + * SHOULD forward the invoice request to the recipient over the previously provided + `forward_invoice_request_path`, and awake them if possible, such as via the LSPS5 protocol + + +## Rationale + +* Q: If we're configuring the recipient with blinded paths from the static invoice server, but those + paths are used to retrieve offer blinded paths from the server, why not just configure the + recipient with the offer blinded paths to begin with? +* A: We want the recipient to have a variety of offers containing unique blinded paths to choose + from, for privacy reasons. They shouldn't have to reuse the same offer paths in all situations. + Implementations should aim to rotate the cached offers that are returned to the user. + +* Q: Why use blinded paths for server<>recipient authentication? +* A: The protocol is based on BOLT 12 so route blinding is known to be a supported mechanism by both + parties already. It also allows the sender and recipient to not be direct [channel] peers, if that + flexibility is desired. + + +## Universality + +For async payments to work with BOLT 12, we need some way for senders to get invoices from +often-offline recipients when they go to pay, that is self-custodial and ideally as trustless as +possible. This protocol provides a way, that is implemented in LDK, but wallets may have their own +way of accomplishing this. Therefore, it doesn't need to be universal in the BOLTs. + +## Reference Implementations +LDK: [1](https://github.com/lightningdevkit/rust-lightning/pull/3618) [2](https://github.com/lightningdevkit/rust-lightning/pull/3628) [3](https://github.com/lightningdevkit/rust-lightning/pull/4049) + +## Copyright + +This bLIP is licensed under the CC0 license.