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
189 changes: 180 additions & 9 deletions 04-onion-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ A node:
* [Packet Structure](#packet-structure)
* [Payload Format](#payload-format)
* [Basic Multi-Part Payments](#basic-multi-part-payments)
* [Trampoline Payments](#trampoline-payments)
* [Route Blinding](#route-blinding)
* [Inside encrypted_recipient_data: encrypted_data_tlv](Inside-encrypted_recipient_data-encrypted_data_tlv)
* [Accepting and Forwarding a Payment](#accepting-and-forwarding-a-payment)
Expand Down Expand Up @@ -208,12 +209,32 @@ This is formatted according to the Type-Length-Value format defined in [BOLT #1]
1. type: 12 (`current_path_key`)
2. data:
* [`point`:`path_key`]
1. type: 14 (`outgoing_node_id`)
2. data:
* [`point`:`outgoing_node_id`]
1. type: 16 (`payment_metadata`)
2. data:
* [`...*byte`:`payment_metadata`]
1. type: 18 (`total_amount_msat`)
2. data:
* [`tu64`:`total_msat`]
1. type: 20 (`trampoline_onion_packet`)
2. data:
* [`byte`:`version`]
* [`point`:`public_key`]
* [`...*byte`:`hop_payloads`]
* [`32*byte`:`hmac`]
1. type: 21 (`recipient_features`)
2. data:
* [`...*byte`:`features`]
1. type: 22 (`recipient_blinded_paths`)
2. data:
* [`...*payment_blinded_path`:`paths`]

1. subtype: `payment_blinded_path`
2. data:
* [`path`:`blinded_path`]
* [`blinded_payinfo`:`payment_info`]

`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 Down Expand Up @@ -277,10 +298,10 @@ The writer of the TLV `payload`:
- For every node outside of a blinded route:
- MUST include `amt_to_forward` and `outgoing_cltv_value`.
- For every non-final node:
- MUST include `short_channel_id`
- MUST include `short_channel_id` or `outgoing_node_id`
- MUST NOT include `payment_data`
- For the final node:
- MUST NOT include `short_channel_id`
- MUST NOT include `short_channel_id` nor `outgoing_node_id`
- if the recipient provided `payment_secret`:
- MUST include `payment_data`
- MUST set `payment_secret` to the one provided
Expand Down Expand Up @@ -427,7 +448,8 @@ constrained in which paths they can take when retrying payments along specific
paths. However, no individual HTLC may be for less than the difference between
the total paid and `total_msat`.

The restriction on sending an HTLC once the set is over the agreed total prevents the preimage being released before all
The restriction on sending an HTLC once the set is over the agreed
total prevents the preimage being released before all
the partial payments have arrived: that would allow any intermediate
node to immediately claim any outstanding partial payments.

Expand All @@ -436,6 +458,108 @@ otherwise meets the amount criterion (eg. some other failure, or
invoice timeout), however if it were to fulfill only some of them,
intermediary nodes could simply claim the remaining ones.

### Trampoline Payments

Trampoline payments allow nodes with an incomplete view of the network to
delegate the construction of parts of the route to trampoline nodes.

The origin node only needs to select a set of trampoline nodes and to know a
route to the first trampoline node. Each trampoline node is responsible for
finding its own route to the next trampoline node. The last trampoline node
must be the final recipient, or it must receive a list of blinded paths to
which it should relay the payment.

The `trampoline_onion_packet` has a variable size to allow implementations to
choose their own trade-off between flexibility and privacy. It's recommended to
add trailing filler data to the `trampoline_onion_packet` when using a small
number of hops. It uses the same onion construction as the `onion_packet` and
is embedded inside an `onion_packet`.

Trampoline nodes are free to use as many hops as they want between themselves
as long as they are able to create a route that satisfies the `cltv` and `fees`
requirements contained in the onion.

#### Requirements

A sending node:

- If the invoice doesn't support the `trampoline_routing` feature:
- If it is a Bolt 12 invoice containing non-empty blinded paths:
- MAY use trampoline routing to pay that invoice.
- In the trampoline onion payload for the last trampoline node:
- MUST include a subset of the invoice's blinded paths in `recipient_blinded_paths`.
- MUST NOT include `outgoing_node_id`.
- SHOULD include the invoice features in `recipient_features`.
- Otherwise:
- MUST NOT use trampoline routing to pay that invoice.
- MUST ensure that each hop in the `trampoline_onion_packet` supports `trampoline_routing`.
- When paying a Bolt 11 invoice:
- MUST encrypt the `trampoline_onion_packet` with the same construction as `onion_packet`:
- MUST include `amt_to_forward` and `outgoing_cltv_value` for each hop.
- MUST use `outgoing_node_id` instead of `short_channel_id` to identify the next trampoline node.
- MUST include the invoice's `payment_secret` in the _last_ trampoline hop's payload.
- MAY add trailing filler data similar to what is done in the `onion_packet`.
- When paying a Bolt 12 invoice that supports the `trampoline_routing` feature:
- For each node in the invoice's blinded path that the sender wants to use:
- MUST create a trampoline onion payload which:
- MUST include the `encrypted_recipient_data`.
- For the first node in the blinded route:
- MUST include the `current_path_key` provided in the invoice.
- For the final node:
- MUST include `amt_to_forward`, `outgoing_cltv_value` and `total_amount_msat`.
- MUST NOT include any other field.
- MAY prepend additional trampoline nodes where the trampoline onion payload:
- MUST include `amt_to_forward` and `outgoing_cltv_value`.
- MUST include `outgoing_node_id`.
- MUST use a different `session_key` for the `trampoline_onion_packet` and the `onion_packet`.
- MUST include the `trampoline_onion_packet` tlv in the _last_ hop's payload of the `onion_packet`.
- If it sends a multi-part payment:
- MUST generate a random `payment_secret` to use in the outer onion.
- MUST NOT use the invoice's `payment_secret` in the outer onion.

When processing a `trampoline_onion_packet`, a receiving node:

- If it doesn't support `trampoline_routing`:
- MUST report a route failure to the origin node.
- Otherwise, if it supports `trampoline_routing`:
- MUST process the `trampoline_onion_packet` as an `onion_packet`.
- MUST fail the HTLC if dictated by the requirements under [Failure Messages](#failure-messages).
- If it is part of a blinded path:
- If it is the introduction node of the blinded path:
- MUST send `update_fail_htlc` with the most relevant error message.
- Otherwise:
- MUST send `update_fail_htlc` with the `invalid_onion_blinding` error message.
- When receiving an error message from the next node:
- MUST discard its content instead of re-encrypting it.
- MUST send `update_fail_htlc` with `invalid_onion_blinding` instead.
- If it is not the final node:
- If the incoming payment is a multi-part payment:
- MUST wait to receive all the payment parts before forwarding.
- If `encrypted_recipient_data` is included:
- If `current_path_key` is not set in the trampoline onion or the outer onion:
- MUST reject the payment.
- If `current_path_key` is set in both the trampoline onion and the outer onion:
- MUST reject the payment.
- Otherwise:
- MUST use `current_path_key` from the trampoline onion (when it is the introduction node
of the blinded path) or the outer onion (when it is an intermediate node) to decrypt it.
- MUST validate its content as it would for the non-trampoline case.
- MUST include the next `current_path_key` in the `hop_payload` for the next trampoline node.
Comment on lines +544 to +547
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of current_path_key in trampoline + blinded paths doesn't quite match our error handling instructions.

For trampoline:

  • Introduction node gets current_path_key in trampoline onion
  • Relaying node gets current_path_key in regular onion

Our existing error handling in bolt-02:

  • Introduction node (non-receiving): should send update_fail_htlc because current_path_key is in onion.
  • Relaying node should send update_malformed_htlc because current_path_key is in update_add_htlc.

With the current wording, we'll always return update_fail_htlc because we get our current_path_key from the onion (regular or trampoline) and never in update_add_htlc.

I'm not sure whether there's a probing issue here, but we don't achieve the same "blinded tunnel / always fails at introduction" effect that we do with regular blinded payments, as the sender can now learn which blinded trampoline its payment failed at.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks for pointing it out! Here is my current thinking about what we should do:

  • when failing at an intermediate trampoline node (inside the blinded path), I don't think we should send update_fail_malformed_htlc: if the previous node was not a trampoline node (because the previous trampoline node inserted a "normal" path to reach us), they are unaware that they are sitting between two blinded trampoline nodes, and we'd be giving away information by sending them update_fail_malformed_htlc instead of update_fail_htlc
  • so trampoline nodes inside a blinded path should always use update_fail_htlc with invalid_onion_blinding
  • BUT the first trampoline node (the introduction node) should ignore the downstream error and return its own error (instead of re-wrapping the downstream trampoline error onion): it is best effort, but if the introduction point is honest then the sender will not learn where the payment failed inside the blinded path

How does that sound?

Copy link
Copy Markdown
Contributor

@carlaKC carlaKC Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable to me 👍

Assuming that we'd also return regular errors if we're the introduction trampoline and the final node?
(really don't see the point of a blinded trampoline to yourself, but to match the current spec)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that we'd also return regular errors if we're the introduction trampoline and the final node?
(really don't see the point of a blinded trampoline to yourself, but to match the current spec)

Yes, that sounds good 👍

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarified in 644b82f. I think the introduction node can even return whatever error message it wants (even if it is not the final node), it won't leak anything from the blinded path itself.

- MUST compute a route to the next trampoline node.
- MUST include the peeled `trampoline_onion_packet` in the `hop_payload` for the next trampoline node.
- If it uses a multi-part payment to forward to the next trampoline node:
- MUST generate a random `payment_secret` to use in the outer onion.
- If `recipient_blinded_paths` is included:
- MUST forward the payment using the blinded paths provided.
- MAY use features included in `recipient_features`.
- If it is the final node:
- MUST reject the payment if:
- The outer onion's `outgoing_cltv_value` is smaller than the trampoline onion's `outgoing_cltv_value`.
- If this is a multi-part payment:
- The outer onion's `total_msat` is smaller than the trampoline onion's `amt_to_forward`.
- Otherwise:
- The outer onion's `amt_to_forward` is smaller than the trampoline onion's `amt_to_forward`.

## Route Blinding

1. subtype: `blinded_path`
Expand Down Expand Up @@ -634,10 +758,13 @@ Encrypted recipient data is created by the final recipient to give to the
sender, containing instructions for the node on how to handle the message (it can also be created by the sender themselves: the node forwarding cannot tell). It's used
in both payment onions and onion messages onions. See [Route Blinding](#route-blinding).


# Accepting and Forwarding a Payment

Once a node has decoded the payload it either accepts the payment locally, or forwards it to the peer indicated as the next hop in the payload.
Once a node has decoded the payload it either accepts the payment locally, or
forwards it to the peer indicated as the next hop in the payload.

When using trampoline routing, the next hop is not necessarily a direct peer,
but otherwise it is.

## Non-strict Forwarding

Expand Down Expand Up @@ -698,8 +825,10 @@ This allows the final node to check these values and return errors if needed,
but it also eliminates the possibility of probing attacks by the second-to-last
node. Such attacks could, otherwise, attempt to discover if the receiving peer is the
last one by re-sending HTLCs with different amounts/expiries.

The final node will extract its onion payload from the HTLC it has received and
compare its values against those of the HTLC. See the
compare its values against those of the HTLC. When using trampoline payments,
the final node will extract these from the trampoline onion payload. See the
[Returning Errors](#returning-errors) section below for more details.

If not for the above, since it need not forward payments, the final node could
Expand Down Expand Up @@ -1068,6 +1197,27 @@ key, and computes the HMAC, using each hop's `um` key.
The origin node can detect the sender of the error message by matching the
`hmac` field with the computed HMAC.

For trampoline payments, the flow is the same. Intermediate trampoline nodes
first decrypt the downstream error using the `ammag` and `um` keys for their
forward path. If the error comes from an intermediate node _before_ the next
trampoline node, they may replace it with their own error for the origin node,
otherwise they must encrypt on top of the next trampoline node's error.

Intermediate trampoline hops apply the obfuscation step twice: first with the
`ammag` key derived from their trampoline shared secret, then with the `ammag`
key derived from their outer onion shared secret. This ensures that the error
message is encrypted for the original sender. Note that it is also possible
for intermediate trampoline hops to only do the obfuscation with the `ammag`
key derived from their outer onion shared secret, in which case the error
message will be encrypted for the previous trampoline hop, which may or may
not be the original sender (this can make sense for some error messages like
`mpp_timeout`).

The origin node first iteratively decrypts the error message using the keys
derived from the outer onion's shared secrets. If the result does not match
the `hmac` field, it then continues decrypting using the keys derived from
the trampoline onion's shared secrets.

The association between the forward and return packets is handled outside of
this onion routing protocol, e.g. via association with an HTLC in a payment
channel.
Expand Down Expand Up @@ -1295,13 +1445,35 @@ reasonable time.

An error occurred within the blinded path.

1. type: NODE|25 (`temporary_trampoline_failure`)

The trampoline node was unable to relay the payment to the next trampoline
node, but may be able to handle it, or others, later.
This error usually indicates that routes were found but failed because of
temporary failures at intermediate hops.

1. type: NODE|26 (`trampoline_fee_or_expiry_insufficient`)
2. data:
* [`u32`:`fee_base_msat`]
* [`u32`:`fee_proportional_millionths`]
* [`u16`:`cltv_expiry_delta`]

The fee amount or cltv value was below that required by the trampoline node to
forward to the next trampoline node, but there are routes available if the
sender retries with the fees and cltv provided in the error data.

1. type: PERM|27 (`unknown_next_trampoline`)

The trampoline onion specified an `outgoing_node_id` that cannot be reached
from the processing node.

### Requirements

An _erring node_:
- if `path_key` is set in the incoming `update_add_htlc`:
- MUST return an `invalid_onion_blinding` error.
- if `current_path_key` is set in the onion payload and it is not the
final node:
payment recipient:
- MUST return an `invalid_onion_blinding` error.
- otherwise:
- MUST select one of the above error codes when creating an error message.
Expand All @@ -1324,8 +1496,7 @@ An _erring node_ MAY:
A _forwarding node_ MUST:
- if `path_key` is set in the incoming `update_add_htlc`:
- return an `invalid_onion_blinding` error.
- if `current_path_key` is set in the onion payload and it is not the
final node:
- if `current_path_key` is set in the onion payload:
- return an `invalid_onion_blinding` error.
- otherwise:
- select one of the above error codes when creating an error message.
Expand Down
3 changes: 2 additions & 1 deletion 09-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The Context column decodes as follows:
| 46/47 | `option_scid_alias` | Supply channel aliases for routing | IN | | [BOLT #2][bolt02-channel-ready] |
| 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] |

| 56/57 | `trampoline_routing` | This node supports trampoline routing | IN9 | | [BOLT #4](bolt04-trampoline) |

## Requirements

Expand Down Expand Up @@ -105,4 +105,5 @@ This work is licensed under a [Creative Commons Attribution 4.0 International Li
[bolt07-query]: 07-routing-gossip.md#query-messages
[bolt04-mpp]: 04-onion-routing.md#basic-multi-part-payments
[bolt04-route-blinding]: 04-onion-routing.md#route-blinding
[bolt04-trampoline]: 04-onion-routing.md#trampoline-payments
[ml-sighash-single-harmful]: https://lists.linuxfoundation.org/pipermail/lightning-dev/2020-September/002796.html
10 changes: 6 additions & 4 deletions 12-offer-encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -675,10 +675,12 @@ the `onion_message` `invoice` field.

## Invoice Features

| Bits | Description | Name |
|------|----------------------------------|----------------|
| 16 | Multi-part-payment support | MPP/compulsory |
| 17 | Multi-part-payment support | MPP/optional |
| Bits | Description | Name |
|------|----------------------------------|-----------------------|
| 16 | Multi-part-payment support | MPP/compulsory |
| 17 | Multi-part-payment support | MPP/optional |
| 56 | Trampoline routing support | Trampoline/compulsory |
| 57 | Trampoline routing support | Trampoline/optional |

The 'MPP support' invoice feature indicates that the payer MUST (16) or
MAY (17) use multiple part payments to pay the invoice.
Expand Down
Loading