Skip to content

Add recommendations for receiver-side random delays #1263

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
16 changes: 16 additions & 0 deletions 02-peer-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -2417,6 +2417,8 @@ A receiving node:
- MUST return an error in the `update_fail_htlc` sent to the link which
originally sent the HTLC, using the `failure_code` given and setting the
data to `sha256_of_onion`.
- SHOULD add a random delay before sending `update_fulfill_htlc`,
`update_fail_htlc`, `update_fail_malformed_htlc`.

#### Rationale

Expand All @@ -2439,6 +2441,20 @@ such detection is left as an option.
Nodes inside a blinded route must use `invalid_onion_blinding` to avoid
leaking information to senders trying to probe the blinded route.

Receiving nodes should wait for a random amount of time before responding to
incoming HTLCs with `update_fulfill_htlc` / `update_fail_malformed_htlc` /
`update_fail_htlc` to impair the capabilities of a potential adversary trying
to deanonymize them based on message timing analysis. The delay distribution
and parameters should be chosen so that they disallow an adversary to be
certain about the origin of any response messages while keeping efficiency in
mind, i.e., the chosen approach should aim to maximize the adversarial
uncertainty gained per millisecond added delay. In practice this could mean to
start a limited random walk on the graph and for each traversed hop add a
plausible per-hop delay sampled from a suitable random latency distribution
(e.g., log-normal). In aggregate, this would create a plausible extended path
similar to the 'shadow route extension' as discussed in [BOLT
#7](07-routing-gossip.md#recommendations-for-routing).

### Committing Updates So Far: `commitment_signed`

When a node has changes for the remote commitment, it can apply them,
Expand Down
2 changes: 1 addition & 1 deletion 04-onion-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ The _origin node_:
- MUST NOT expose the `channel_update` to third-parties in any other
context, including applying the `channel_update` to the local network
graph, send the `channel_update` to peers as gossip, etc.
- SHOULD then retry routing and sending the payment.
- SHOULD then retry routing and sending the payment over a different path.
- MAY use the data specified in the various failure types for debugging
purposes.

Expand Down
5 changes: 5 additions & 0 deletions 07-routing-gossip.md
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,11 @@ This effectively creates a _shadow route extension_ to the actual route and
provides better protection against this attack vector than simply picking a
random offset would.

Similarly, senders that consider the historical payment latency over candidate
hops as input for their routing algorithm should exclude the final hop from
such scoring, to account for the receiver-induced delay (see [BOLT
#2](02-peer-protocol.md#removing-an-htlc-update_fulfill_htlc-update_fail_htlc-and-update_fail_malformed_htlc)).

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also add a recommendation to introduce a retry delay or make sure to use different paths when retrying a path? Perhaps adding to the timing analysis note in bolt 04.

(maybe this exists, I just grepped for delay + retry and couldn't find it)

Copy link
Contributor Author

@tnull tnull Jun 6, 2025

Choose a reason for hiding this comment

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

I don't think adding sender-side delays is necessary, as generally for most nodes it makes a lot of sense to avoid previously-failed paths on retry. I think LND is the only exception to this as it still employs some carve-outs for nodes that respond with one of a specific set of failure codes. As we showed in the paper this behavior is exploitable and likely should be dropped, but I don't know if we need to add a recommendation/requirement for that?

Copy link
Contributor

Choose a reason for hiding this comment

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

as generally for most nodes it makes a lot of sense to avoid previously-failed paths on retry

Perhaps we just add a recommendation to not retry the same path here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now added a commit making that line a bit more specific. Let me know if you think it's sufficient.

Other more advanced considerations involve diversification of route selection,
to avoid single points of failure and detection, and balancing of local
channels.
Expand Down
8 changes: 8 additions & 0 deletions 12-offer-encoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ The reader:
- otherwise (no `offer_amount`):
- MUST reject the invoice request if it does not contain `invreq_amount`.
- SHOULD send an invoice in response using the `onionmsg_tlv` `reply_path`.
- SHOULD add a random delay before sending the invoice.
- otherwise (no `offer_issuer_id` or `offer_paths`, not a response to our offer):
- MUST reject the invoice request if any of the following are present:
- `offer_chains`, `offer_features` or `offer_quantity_max`.
Expand Down Expand Up @@ -714,6 +715,7 @@ A writer of an invoice:
`invreq_chain`.
- if the invoice is in response to an `invoice_request`:
- MUST copy all non-signature fields from the invoice request (including unknown fields).
- SHOULD add a random delay before sending the invoice.
- if `invreq_amount` is present:
- MUST set `invoice_amount` to `invreq_amount`
- otherwise:
Expand Down Expand Up @@ -847,6 +849,11 @@ a response to an invoice request, that field must have existed due
to the invoice request requirements, and we also require it to be mirrored
here.

As the sender of an invoice request might attempt to utilize the time
difference between sending the `invreq` and receiving the corresponding
`invoice` or `invoice_error` response to draw conclusions on the identity of
the receiver (i.e., writer of the response), the latter should wait for a
random amount of time before responding to an `invreq` message.

# Invoice Errors

Expand Down Expand Up @@ -880,6 +887,7 @@ A writer of an invoice_error:
- MUST set `suggested_value` to a valid field for that `tlv_fieldnum`.
- otherwise:
- MUST NOT set `suggested_value`.
- SHOULD add a random delay before sending the `invoice_error`.

A reader of an invoice_error:
FIXME!
Expand Down