Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
164 changes: 158 additions & 6 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,94 @@ 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 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:
- MUST use `current_path_key` from the trampoline onion or the outer onion 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.
- 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 +744,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 +811,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 +1183,21 @@ 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
Copy link

@a-mpch a-mpch Sep 15, 2025

Choose a reason for hiding this comment

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

It is no clear to me that whenever the receiver is the first trampoline and something fails, should the receiver node just use the outer onion shared secret or would it use the double obfuscation?
It could be seen in different perspectives:

  1. payment reaches the trampoline and then try to route it, figuring out it is itself.
  2. Before trying to route it as trampoline, payment is received.

I didn't find a test case for this in eclair (don't know much of Scala tho)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, there is some subtlety here, this depends on who the error is encrypted for:

  • if the failing trampoline node wants the error to be for the previous trampoline node, they use the outer onion shared secret: this is used for example when the payment is using MPP but not all parts were received in time, which is an error made by the previous trampoline node which could (probably should) retry, so we don't want the error to go all the way back to the sender
  • if the failing trampoline node wants the error to be for the sender, they use the double obfuscation, which forces the previous trampoline node to relay the error upstream: this is used for example when receiving a payment that an invoice that was already paid, or that has a trampoline expiry that is too close

I don't think we can list all cases in the spec, but we probably should better explain the distinction between those two cases. Is that clear enough? Otherwise feel free to suggest the wording you'd like to see in the spec that would make this clear.

Copy link

Choose a reason for hiding this comment

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

I think that wording makes sense. Thanks!

`ammag` key derived from their trampoline shared secret, then with the `ammag`
key derived from their outer onion shared secret.

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,6 +1425,28 @@ 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_:
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