Skip to content

Commit

Permalink
Support async payments in BOLT 12.
Browse files Browse the repository at this point in the history
This builds on prior commits by adding the ability to fetch an invoice from an
always-online node on behalf of an often-offline recipient, e.g. a mobile node.

The idea is that often-offline recipients will supply some always-online node
such as their wallet vendor with a static (i.e. `payment_hash`-less) invoice to
return on its behalf. The recipient will then publish an offer containing
blinded paths that terminate at this always-online node, who payers can request
the invoice from if the recipient is offline at the time. After receiving the
static invoice, payers will commence the protocol outlined in [1] to send the
HTLC asynchronously.

[1]: https://lists.linuxfoundation.org/pipermail/lightning-dev/2021-October/003307.html
  • Loading branch information
valentinewallace committed Jun 3, 2024
1 parent eb30b0b commit edc93aa
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 19 deletions.
13 changes: 13 additions & 0 deletions 04-onion-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ This is formatted according to the Type-Length-Value format defined in [BOLT #1]
1. type: 18 (`total_amount_msat`)
2. data:
* [`tu64`:`total_msat`]
1. type: 5482373484 (`sender_provided_payment_preimage`)
2. data:
* [`32*byte`:`payment_preimage`]

`short_channel_id` is the ID of the outgoing channel used to route the
message; the receiving peer should operate the other end of this channel.
Expand All @@ -239,6 +242,9 @@ The requirements ensure consistency in responding to an unexpected
`outgoing_cltv_value`, whether it is the final node or not, to avoid
leaking its position in the route.

`sender_provided_payment_preimage` is set in the case that the recipient is
often-offline and another node provided a static BOLT 12 invoice on their behalf.

### Requirements

The creator of `encrypted_recipient_data` (usually, the recipient of payment):
Expand Down Expand Up @@ -269,6 +275,12 @@ The writer of the TLV `payload`:
- MUST use the current block height as a baseline value.
- if a [random offset](07-routing-gossip.md#recommendations-for-routing) was added to improve privacy:
- SHOULD add the offset to the baseline value.
- if paying to a static BOLT 12 invoice:
- MUST set `sender_provided_payment_preimage` to randomly generated unique bytes.
- MUST set `update_add_htlc.payment_hash` to match the SHA256 hash of
`sender_provided_payment_preimage`.
- otherwise:
- MUST NOT set `sender_provided_payment_preimage`.
- MUST NOT include any other tlv field.
- For every node outside of a blinded route:
- MUST include `amt_to_forward` and `outgoing_cltv_value`.
Expand Down Expand Up @@ -319,6 +331,7 @@ The reader:
- MUST return an error if `amt_to_forward` is below what it expects for the payment.
- MUST return an error if incoming `cltv_expiry` < `outgoing_cltv_value`.
- MUST return an error if incoming `cltv_expiry` < `current_block_height` + `min_final_cltv_expiry_delta`.
- MUST use `sender_provided_payment_preimage` when claiming the HTLC, if present
- Otherwise (it is not part of a blinded route):
- MUST return an error if `blinding_point` is set in the incoming `update_add_htlc` or `current_blinding_point` is present.
- MUST return an error if `amt_to_forward` or `outgoing_cltv_value` are not present.
Expand Down
7 changes: 7 additions & 0 deletions 09-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The Context column decodes as follows:
* `C+`: presented in the `channel_announcement` message, but always even (required).
* `9`: presented in [BOLT 11](11-payment-encoding.md) invoices.
* `B`: presented in the `allowed_features` field of a blinded path.
* `R`: presented in [BOLT 12](12-offers.md) invoice requests.

| Bits | Name | Description | Context | Dependencies | Link |
|-------|-----------------------------------|-----------------------------------------------------------|----------|---------------------------|-----------------------------------------------------------------------|
Expand All @@ -51,11 +52,17 @@ The Context column decodes as follows:
| 48/49 | `option_payment_metadata` | Payment metadata in tlv record | 9 | | [BOLT #11](11-payment-encoding.md#tagged-fields) |
| 50/51 | `option_zeroconf` | Understands zeroconf channel types | IN | `option_scid_alias` | [BOLT #2][bolt02-channel-ready] |
| 52/53 | `option_htlc_hold` | Hold HTLCs and forward on receipt of an onion message | IN | `option_onion_messages` |
| 56/57 | `option_om_mailbox` | Store-and-forward onion messages for often-offline peers | IN | `option_onion_messages` | [BOLT #12](bolt12-offers.md) |
| 59 | `static_invoice_pay` | Supports paying BOLT 12 static invoices | R | `option_onion_messages` | [BOLT #12](bolt12-offers.md) |

## Definitions

We define `option_anchors` as `option_anchor_outputs || option_anchors_zero_fee_htlc_tx`.

We define `option_om_mailbox` as the ability to store an onion message on behalf
of an offline peer, and forward it once the peer comes online (subject to rate
limiting).

## Requirements

The origin node:
Expand Down
92 changes: 73 additions & 19 deletions 12-offer-encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Here we use "user" as shorthand for the individual user's lightning
node and "merchant" as the shorthand for the node of someone who is
selling or has sold something.

There are two basic payment flows supported by BOLT 12:
There are three basic payment flows supported by BOLT 12:

The general user-pays-merchant flow is:
1. A merchant publishes an *offer*, such as on a web page or a QR code.
Expand All @@ -59,6 +59,19 @@ The merchant-pays-user flow (e.g. ATM or refund):
3. The merchant confirms the *invoice_node_id* to ensure it's about to pay the correct
person, and makes a payment to the invoice.

The pay-mobile-user flow (e.g. paying a friend back to their mobile node):
1. The mobile user supplies some always-online node with a static (i.e.
`payment_hash`-less) invoice to return on its behalf. This always-online node may
be the mobile user's channel counterparty, wallet vendor, or another node on the
network that it has an out-of-band relationship with.
2. The mobile user publishes an offer that contains blinded paths that terminate
at the always-online node.
3. The payer sends an `invoice_request` to the always-online node, who replies
with the static invoice previously provided by the mobile user if the mobile user
is offline. If they are online, the `invoice_request` is forwarded to the mobile
user as usual.
4. The payer makes a payment to the mobile user as indicated by the invoice.

## Payment Proofs and Payer Proofs

Note that the normal lightning "proof of payment" can only demonstrate that an
Expand All @@ -71,6 +84,9 @@ to request the invoice. In addition, the Merkle construction of the BOLT 12
invoice signature allows the user to reveal invoice fields in case
of a dispute selectively.

Payers will not get proofs in the case that they received a static invoice from the
payee, see the pay-mobile-user flow above.

# Encoding

Each of the forms documented here are in
Expand Down Expand Up @@ -255,8 +271,9 @@ A writer of an offer:
after midnight 1 January 1970, UTC that invoice_request should not be
attempted.
- if it is connected only by private channels:
- MUST include `offer_paths` containing one or more paths to the node from
publicly reachable nodes.
- MUST include `offer_paths` containing one or more paths to the node
that will reply to the `invoice_request`, using introduction nodes that are
publicly reachable.
- otherwise:
- MAY include `offer_paths`.
- if it includes `offer_paths`:
Expand All @@ -274,6 +291,8 @@ A writer of an offer:
- MUST set `offer_quantity_max` to 0.
- otherwise:
- MUST NOT set `offer_quantity_max`.
- if it is often-offline and the invoice may be provided by another node on their behalf:
- MUST NOT include more than 1 chain in `offer_chains`.

A reader of an offer:
- if the offer contains any TLV fields greater or equal to 80:
Expand Down Expand Up @@ -409,6 +428,20 @@ for [Signature Calculation](#signature-calculation).
2. data:
* [`bip340sig`:`sig`]

## Invoice Request Features

| Bits | Description | Name |
|------|----------------------------------|-----------------------------|
| 59 | Supports paying static invoices | static_invoice_pay/optional |

Setting `static_invoice_pay` indicates that the payer supports receiving a
`payment_hash`-less invoice in response to their `invoice_request`, and
subsequently setting `sender_provided_payment_preimage` in their payment onion.

Useful if the payee is often offline and the invoice is being returned on
their behalf by another node, to avoid trusting that other node to not reuse a
`payment_hash`.

## Requirements for Invoice Requests

The writer:
Expand Down Expand Up @@ -486,7 +519,14 @@ The reader:
- MUST fail the request if bitcoin is not a supported chain.
- otherwise:
- MUST fail the request if `invreq_chain`.`chain` is not a supported chain.

- if receiving the `invoice_request` on behalf of an often-offline payee:
- if the payee is online:
- MUST forward the `invoice_request` to the payee
- otherwise (payee is offline):
- if `invreq_features` supports `static_invoice_pay`:
- MUST reply with the static invoice previously provided by the payee
- otherwise:
- MUST reply with `invoice_error`

## Rationale

Expand Down Expand Up @@ -515,10 +555,11 @@ informative for the payer to know how the sender claims

# Invoices

Invoices are a payment request, and when the payment is made,
it can be combined with the invoice to form a cryptographic receipt.
Invoices are a payment request. If `invoice_payment_hash` is set, then when the
payment is made, the payment preimage can be combined with the invoice to form a
cryptographic receipt.

The recipient sends an `invoice` in response to an `invoice_request` using
The recipient creates an `invoice` for responding to an `invoice_request` using
the `onion_message` `invoice` field.

1. `tlv_stream`: `invoice`
Expand Down Expand Up @@ -604,6 +645,9 @@ the `onion_message` `invoice` field.
1. type: 176 (`invoice_node_id`)
2. data:
* [`point`:`node_id`]
1. type: 178 (`invoice_message_paths`)
2. data:
* [`...*blinded_path`:`paths`]
1. type: 240 (`signature`)
2. data:
* [`bip340sig`:`sig`]
Expand Down Expand Up @@ -642,21 +686,25 @@ may (due to capacity limits on a single channel) require it.
A writer of an invoice:
- MUST set `invoice_created_at` to the number of seconds since Midnight 1
January 1970, UTC when the invoice was created.
- MUST set `invoice_amount` to the minimum amount it will accept, in units of
the minimal lightning-payable unit (e.g. milli-satoshis for bitcoin) for
`invreq_chain`.
- if the invoice is in response to an `invoice_request`:
- if `invoice_payment_hash` is set and the invoice is in response to an `invoice_request`:
- MUST copy all non-signature fields from the `invoice_request` (including unknown fields).
- if `invreq_amount` is present:
- MUST set `invoice_amount` to `invreq_amount`
- otherwise:
- MUST set `invoice_amount` to the *expected amount*.
- otherwise (invoice not requested, e.g. for user to scan directly):
- otherwise if the invoice was not requested (e.g. for user to scan directly):
- MUST set `invreq_chain` as it would for an invoice_request.
- MUST set `offer_description` as it would for an offer.
- MUST NOT set `invreq_payer_id` or `offer_node_id`.
- MUST set `invoice_payment_hash` to the SHA256 hash of the
`payment_preimage` that will be given in return for payment.
- if the invoice is intended to be provided by a node other than the recipient:
- MUST NOT set `invoice_payment_hash`.
- MUST NOT set `invoice_amount`.
- MUST include `invoice_message_paths` containing at least two paths to
the recipient, where the penultimate hop supports `option_om_mailbox`.
- MUST NOT set any `invoice_request` TLV fields (i.e. 0 or in the 80-159 range)
- otherwise:
- MUST set `invoice_payment_hash` to the SHA256 hash of the
`payment_preimage` that will be given in return for payment.
- if `offer_node_id` is present:
- MUST set `invoice_node_id` to `offer_node_id`.
- otherwise:
Expand All @@ -682,15 +730,16 @@ A writer of an invoice:
- MUST include `invoice_blindedpay` with exactly one `blinded_payinfo` for each `blinded_path` in `paths`, in order.
- MUST set `features` in each `blinded_payinfo` to match `encrypted_data_tlv`.`allowed_features` (or empty, if no `allowed_features`).
- SHOULD ignore any payment which does not use one of the paths.
- if `offer_node_id` is present, and `invreq_payer_id` is identical to a previous `invoice_request`:
- MAY simply reuse the previous invoice.
- if `offer_node_id` is present, and `invreq_payer_id` is identical to a previous `invoice_request`, or if providing invoices on behalf of an often offline recipient:
- MAY reuse the previous invoice.
- otherwise:
- MUST NOT reuse a previous invoice.

A reader of an invoice:
- MUST reject the invoice if `invoice_amount` is not present.
- MUST reject the invoice if `invoice_created_at` is not present.
- MUST reject the invoice if `invoice_payment_hash` is not present.
- if `static_invoice_pay` was not supported in `invreq_features`:
- MUST reject the invoice if `invoice_payment_hash` is not present.
- MUST reject the invoice if `invoice_amount` is not present.
- MUST reject the invoice if `invoice_node_id` is not present.
- if `invoice_features` contains unknown _odd_ bits that are non-zero:
- MUST ignore the bit.
Expand All @@ -707,7 +756,8 @@ A reader of an invoice:
- MUST NOT use the corresponding `invoice_paths`.`path` if `payinfo`.`features` has any unknown even bits set.
- MUST reject the invoice if this leaves no usable paths.
- if the invoice is a response to an `invoice_request`:
- MUST reject the invoice if all fields less than type 160 do not exactly match the `invoice_request`.
- if `invoice_payment_hash` is set:
- MUST reject the invoice if all fields less than type 160 do not exactly match the `invoice_request`.
- if `offer_node_id` is present (invoice_request for an offer):
- MUST reject the invoice if `invoice_node_id` is not equal to `offer_node_id`.
- otherwise (invoice_request without an offer):
Expand All @@ -727,6 +777,10 @@ A reader of an invoice:
- MUST ignore any `fallback_address` for which `version` is greater than 16.
- MUST ignore any `fallback_address` for which `address` is less than 2 or greater than 40 bytes.
- MUST ignore any `fallback_address` for which `address` does not meet known requirements for the given `version`
- if `invoice_payment_hash` is unset:
- MUST reject the invoice if `invoice_message_paths` is not present or is empty.
- MUST pay asynchronously using the `held_htlc_available` onion message
flow, where the onion message is sent over `invoice_message_paths`.

## Rationale

Expand Down

0 comments on commit edc93aa

Please sign in to comment.