-
Notifications
You must be signed in to change notification settings - Fork 505
Support async payments in BOLT 12 #1149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
5bee6ab
625994f
e8efd2c
5d69269
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1972,6 +1972,25 @@ is destined, is described in [BOLT #4](04-onion-routing.md). | |
* [`sha256`:`payment_hash`] | ||
* [`u32`:`cltv_expiry`] | ||
* [`1366*byte`:`onion_routing_packet`] | ||
* [`update_add_htlc_tlvs`:`tlvs`] | ||
|
||
1. `tlv_stream`: `update_add_htlc_tlvs` | ||
2. types: | ||
1. type: 0 (`hold_htlc`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to get a better feel for the design space: In a model where the sender LSP does the routing (I think Breez works like that, and also Phoenix with trampoline?), what extra options would that give? Holding the htlc at the LSP makes sense of course, so that the sender can go offline. When the LSP knows the final destination though, they could just keep trying to complete the payment until is succeeds, and then claim the incoming htlc with the preimage. This would cut out all (not reliable?) onion message communication for hold and release. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline, but we currently expect async recipient wallets to come online infrequently and briefly. So it wouldn't be ideal for the sender's LSP to send a bunch of HTLCs and lock up liquidity repeatedly over a period of days until the recipient comes online. Onion message reliability is indeed a bit tbd though, although with direct-connect it becomes a lot more reliable today at the cost of privacy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that makes sense. Didn't realize that the online periods may be so short that it might be impossible to hit. |
||
2. data: | ||
* [`32*bytes`:`payment_release_secret`] | ||
|
||
#### TLV fields for `held_htlc_available` | ||
1. `tlv_stream`: `held_htlc_available` | ||
2. types: | ||
|
||
#### TLV fields for `release_held_htlc` | ||
|
||
1. `tlv_stream`: `release_held_htlc` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there interesting edge cases where the release is signaled, but then the receiver quickly goes offline after all? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may justify the sender's LSP resending the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case trampoline is also going to help right? Because if there's just a single sender-determined route a retry is not possible because of onion reuse prevention? |
||
2. types: | ||
1. type: 0 (`payment_release_secret`) | ||
2. data: | ||
* [`32*bytes`:`payment_release_secret`] | ||
|
||
1. `tlv_stream`: `update_add_htlc_tlvs` | ||
2. types: | ||
|
@@ -2016,6 +2035,19 @@ A sending node: | |
- MUST increase the value of `id` by 1 for each successive offer. | ||
- if it is relaying a payment inside a blinded route: | ||
- MUST set `path_key` (see [Route Blinding](04-onion-routing.md#route-blinding)) | ||
- MUST NOT include a `hold_htlc` TLV unless the sending node expects the | ||
final recipient of the HTLC to be offline at the time the HTLC would arrive | ||
- MUST NOT include a `hold_htlc` TLV unless the sending node expects to be | ||
offline for an extended duration starting soon. | ||
- If the `hold_htlc` TLV is present: | ||
- MUST immediately send at least two onion messages across at least two | ||
different paths to the final HTLC recipient. | ||
- Each onion message MUST contain a `held_htlc_available` TLV. | ||
- Each onion message MUST contain a unique `reply_path`s which terminates | ||
at the reciever of the `update_add_htlc` message. | ||
- Each `reply_path` MUST contain a `release_held_htlc` TLV for the | ||
`update_add_htlc` recipient in the `encrypted_data_tlvs` with a | ||
`payment_release_secret` matching that in the `hold_htlc` TLV. | ||
|
||
`id` MUST NOT be reset to 0 after the update is complete (i.e. after `revoke_and_ack` has | ||
been received). It MUST continue incrementing instead. | ||
|
@@ -2047,6 +2079,12 @@ A receiving node: | |
- MUST respond with an error as detailed in [Failure Messages](04-onion-routing.md#failure-messages) | ||
- Otherwise: | ||
- MUST follow the requirements for the reader of `payload` in [Payload Format](04-onion-routing.md#payload-format) | ||
- if the `hold_htlc` TLV is present: | ||
- MUST NOT forward the HTLC until a corresponding `release_held_htlc` onion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it known what value LSPs typically use for max htlcs on their channels? If the LSP is the chan initiator, they may want to keep it low to avoid a high commit tx absolute fee. This would then limit the number of outstanding async payments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, although maybe worth noting that the HTLC slot is only taken up on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For small tips it may not be a problem because those do not add to the commit tx weight and fee risk for the LSP. Although the risk then is that the LSP might lose the htlc values in a close event. |
||
message is received with a matching `payment_release_secret`. | ||
- Upon receipt of a `release_held_htlc` onion message with a matching | ||
`payment_release_secret` the HTLC SHOULD be treated as any HTLC without | ||
the `hold_htlc` TLV and forwarded as usual. | ||
|
||
The `onion_routing_packet` contains an obfuscated list of hops and instructions for each hop along the path. | ||
It commits to the HTLC by setting the `payment_hash` as associated data, i.e. includes the `payment_hash` in the computation of HMACs. | ||
|
@@ -2082,6 +2120,19 @@ maintaining its channel reserve (because of the increased weight of the | |
commitment transaction), resulting in a degraded channel. See [#728](https://github.com/lightningnetwork/lightning-rfc/issues/728) | ||
for more details. | ||
|
||
For often-offline recipients, e.g. mobile clients, nodes can use the | ||
`hold_htlc` TLV to prevent further forwarding of an HTLC until the recipient | ||
comes online. As long as the final recipients' counterparty is online and | ||
storing onion messages for the recipient, the recipient can reply to the onion | ||
message when they come online, unblock the HTLC, and expect to receive it | ||
quickly thereafter. | ||
|
||
Note that if the sender expects to be online when the recipient comes online, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this an expectation that can be had? I am wondering what this means to the user of a mobile app. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed offline (as with many of these comments), but this could apply to custodial senders who are always-online. |
||
they can utilize the `release_held_htlc` onion message without utilizing the | ||
`hold_htlc` TLV - they can simply send a `held_htlc_available` onion message | ||
to the final recipient and wait to send any HTLC at all until they receive a | ||
`release_held_htlc` message back. | ||
|
||
### Removing an HTLC: `update_fulfill_htlc`, `update_fail_htlc`, and `update_fail_malformed_htlc` | ||
|
||
For simplicity, a node can only remove HTLCs added by the other node. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -58,6 +58,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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does this relate to the PTLC requirement that was mentioned in the original ML post, is that still a requirement? Maybe not because the payment is keysend now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm understanding your question correctly, we still need PTLCs to get proof-of-payment back for async payments. AJ outlined a scheme for this here: https://diyhpl.us/~bryan/irc/bitcoin/bitcoin-dev/linuxfoundation-pipermail/lightning-dev/2023-January/003831.txt There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that was indeed my question. Thanks for the link, understood. |
||
`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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The blinded paths, that isn't a strict requirement, or is it? Also wondering how pathfinding works for the sender. They only get to do one hold htlc without retries, so it must be first time right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
IIUC it's not a strict requirement, no.
Discussed offline but the plan is to use trampoline such that the sender locks in their HTLC with their LSP using a trampoline onion, allowing said LSP to retry on their behalf. |
||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would you distinguish between the offline and online case - can't the offline flow be used always for simplicity? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's nice to fall back to the regular BOLT 12 flow because then the sender will get a fresh invoice/proof of payment, though as we talked about this won't happen in most cases. |
||
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 | ||
|
@@ -70,6 +83,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 | ||
|
@@ -261,8 +277,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`: | ||
|
@@ -282,6 +299,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 outside the inclusive ranges: 1 to 79 and 1000000000 to 1999999999: | ||
|
@@ -441,6 +460,20 @@ while still allowing signature validation. | |
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this 'keysend' in LND terms? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah! The keysend term doesn't exist in the BOLTs right now so ended up going in this direction with naming... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. Send provided preimage is more descriptive too. |
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you would trust another node to not reuse a hash, does that then still offer much above just letting an LSP receive the money for you and trusting them to hand it over? Maybe it is not necessary to describe the option to not use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking it would be good to explain the reasoning behind needing a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was just wondering if wording should be stronger. That you can't just trust another node to provide an invoice, and that sender provided preimage is the only way to make it safe? |
||
`payment_hash`. | ||
|
||
## Requirements for Invoice Requests | ||
|
||
The writer: | ||
|
@@ -530,7 +563,14 @@ The reader: | |
- MUST reject the invoice request if bitcoin is not a supported chain. | ||
- otherwise: | ||
- MUST reject the invoice 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 | ||
|
||
|
@@ -561,10 +601,11 @@ The requirement to use `offer_paths` if present, ensures a node does not reveal | |
|
||
# Invoices | ||
|
||
Invoices are a payment request, and when the payment is made, | ||
the payment preimage 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` | ||
|
@@ -653,6 +694,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`] | ||
|
@@ -691,17 +735,21 @@ 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*. | ||
- 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 | ||
- otherwise: | ||
- MUST set `invoice_payment_hash` to the SHA256 hash of the | ||
`payment_preimage` that will be given in return for payment. | ||
- if `offer_issuer_id` is present: | ||
- MUST set `invoice_node_id` to the `offer_issuer_id` | ||
- otherwise, if `offer_paths` is present: | ||
|
@@ -727,11 +775,14 @@ 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 providing invoices on behalf of an often offline recipient: | ||
- MAY reuse the 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 `invreq_chain` is not present: | ||
- MUST reject the invoice if bitcoin is not a supported chain. | ||
|
@@ -753,7 +804,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 in ranges 0 to 159 and 1000000000 to 2999999999 (inclusive) do not exactly match the invoice request. | ||
- if `invoice_payment_hash` is set: | ||
- MUST reject the invoice if all fields in ranges 0 to 159 and 1000000000 to 2999999999 (inclusive) do not exactly match the invoice request. | ||
- if `offer_issuer_id` is present (invoice_request for an offer): | ||
- MUST reject the invoice if `invoice_node_id` is not equal to `offer_issuer_id` | ||
- otherwise, if `offer_paths` is present (invoice_request for an offer without id): | ||
|
@@ -782,6 +834,10 @@ A reader of an invoice: | |
- MUST reject the invoice if it arrived via a blinded path. | ||
- otherwise (derived from an offer): | ||
- MUST reject the invoice if it did not arrive via invoice request `onionmsg_tlv` `reply_path`. | ||
- 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 | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline: maybe signal
hold_htlc
in the onion, so that the option remains open to park htlcs at a more remote node? Potentially as a way to unburden the sender LSP from async payments.