Skip to content

htlcswitch: forward blinded payments addressed by node_id#10942

Open
bitromortac wants to merge 5 commits into
lightningnetwork:masterfrom
bitromortac:2606-fix-10937-a
Open

htlcswitch: forward blinded payments addressed by node_id#10942
bitromortac wants to merge 5 commits into
lightningnetwork:masterfrom
bitromortac:2606-fix-10937-a

Conversation

@bitromortac

Copy link
Copy Markdown
Collaborator

Fixes #10937.

When lnd is the introduction node of a blinded path, the recipient may identify the next hop by next_node_id instead of a short_channel_id. lnd only handled the channel-ID case and failed such forwards with next SCID not set for non-final blinded hop.

The next hop now becomes an fn.Either[ShortChannelID, pubkey]: decode yields the peer's key when only next_node_id is present, and the switch resolves it through the same non-strict getLinks path that already load-balances across a peer's channels. outgoingChanID (the circuit key) stays a short channel ID, set once a channel is chosen, so private and alias channels need no special handling.

Alternative considered

Resolving the node ID to a channel at decode time is smaller but makes onion decoding depend on live link state and re-implements alias handling the switch already owns. Resolving in the switch keeps decode pure and is correct for alias channels by construction.

@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label Jun 30, 2026
@github-actions

Copy link
Copy Markdown

PR Severity: CRITICAL

Highest-severity file determines level | 10 files (excl. tests) | ~259 lines changed (excl. tests)

CRITICAL files (8):

  • contractcourt/htlc_incoming_contest_resolver.go - on-chain HTLC dispute resolution
  • htlcswitch/hop/forwarding_info.go - HTLC forwarding, payment routing state machine
  • htlcswitch/hop/iterator.go - HTLC hop iterator
  • htlcswitch/hop/payload.go - HTLC payload handling
  • htlcswitch/link.go - HTLC link (channel forwarding)
  • htlcswitch/mock.go - htlcswitch package mock helpers
  • htlcswitch/packet.go - HTLC packet definitions
  • htlcswitch/switch.go - HTLC switch (core payment routing state machine)

MEDIUM files (1):

  • witness_beacon.go - root-level Go file not in a categorized critical/high package

LOW files (1):

  • docs/release-notes/release-notes-0.22.0.md - release notes documentation

Analysis:

This PR touches two distinct critical packages (contractcourt and htlcswitch), independently triggering CRITICAL severity. The bulk of changes are in htlcswitch/hop/ and htlcswitch/switch.go, at the heart of Lightning payment forwarding. The large itest addition (lnd_route_blinding_test.go, +417 lines) is excluded as a test file. Expert review of htlcswitch and contractcourt changes is required.

To override, add a severity-override-{critical,high,medium,low} label.
<!-- pr-severity-bot -->

@saubyk saubyk added this to the v0.21.2 milestone Jun 30, 2026
@saubyk saubyk added this to v0.21 Jun 30, 2026
@saubyk saubyk moved this to In progress in v0.21 Jun 30, 2026
The forwarding next hop is currently always a short channel ID. To allow a
blinded route to identify the next hop by node ID instead, change
ForwardingInfo.NextHop to fn.Either[lnwire.ShortChannelID, [33]byte], where
the Left is the outgoing channel ID and the Right (wired up in a follow-up
commit) is the next node's public key.

This commit is a pure representational change with no behavioural effect:
every next hop is still a channel ID. The Either is encapsulated behind
ForwardingInfo methods so callers never destructure it directly: IsExit()
is the single source of truth for exit-hop detection (used by the link and
the contract court) and NextHopChannel() yields the outgoing SCID.
Some implementations (e.g. Core Lightning) identify the next hop in a
blinded route by the next node's ID (next_node_id) instead of a short
channel ID. Decode such a hop into a node-ID next hop, the Right of
ForwardingInfo.NextHop, holding the next node's public key. The switch
resolves that key to one of our channels with the peer in a later commit.

BOLT 4 requires a non-final blinded hop to carry exactly one of
short_channel_id or next_node_id, so a hop that sets both is rejected.
Fixes lightningnetwork#10937: forward a blinded-route payment when the
recipient identifies the next hop by node ID rather than a short channel
ID. The htlcPacket carries the decoded next hop to the switch, whose
handlePacketAdd resolves the pubkey to the peer's links via getLinks() and
lets the existing non-strict forwarding logic load-balance across the
peer's channels.

outgoingChanID stays a ShortChannelID. It is the persisted CircuitKey and
is set to the selected channel after non-strict selection. The circular
route check filters candidate channels before selection so a valid forward
is never rejected by landing on the incoming channel.
Add integration tests for an lnd introduction node forwarding a blinded
payment whose non-final hops identify the next hop by node ID (next_node_id)
rather than a short channel ID, as produced by other implementations:

  - testBlindedRouteNextNodeID: the outgoing channel is public.
  - testBlindedRouteNextNodeIDPrivateChannel: the outgoing channel is
    private, so the node ID resolves to an SCID alias.
  - testBlindedRouteNextNodeIDRestart: the introduction node is restarted
    while the HTLC is in flight, exercising forwarding-package replay and
    re-decode of the node-ID blinded hop.
@bitromortac bitromortac marked this pull request as ready for review July 8, 2026 14:22
@ziggie1984 ziggie1984 added the backport-v0.21.x-branch This label triggers a backport to branch `v0.21.x-branch ` label Jul 9, 2026
@saubyk saubyk requested review from yyforyongyu and ziggie1984 July 9, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-v0.21.x-branch This label triggers a backport to branch `v0.21.x-branch ` severity-critical Requires expert review - security/consensus critical

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

LND 0.21-beta blinded path forwarding failure with CLN BOLT12 offer

3 participants