Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
4 changes: 4 additions & 0 deletions blip-0002.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
187 changes: 187 additions & 0 deletions blip-0066.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
```
bLIP: 66
Title: Static Invoice Server Protocol
Status: Draft
Author: Valentine Wallace <vwallace@protonmail.com>
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.