Skip to content

Onion message forwarding#10089

Open
gijswijs wants to merge 22 commits intolightningnetwork:masterfrom
gijswijs:onion-messaging-1
Open

Onion message forwarding#10089
gijswijs wants to merge 22 commits intolightningnetwork:masterfrom
gijswijs:onion-messaging-1

Conversation

@gijswijs
Copy link
Collaborator

With this PR we add basic forwarding functionality for onion messages. It builds on PR #9868.

It adds OnionMessagePayload struct to the lnwire package.
It also depends on the not yet merged (PR 68)[https://github.com/lightningnetwork/lightning-onion/pull/68] in the lightning-onion package. For now it uses that package from a forked version.

The msgmux endpoint for onion messages is updated to parse the onion message packet, and forward the onion based on the acquired information.

The SubscribeOnionMessages endpoint is updated to pass along any decrypted information. This endpoint is currently solely meant for itests, although it could have practical use in the future.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @gijswijs, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the foundational functionality for forwarding onion messages within the Lightning Network. It integrates a new OnionMessage wire protocol message, enables its routing through the existing Sphinx onion processing infrastructure, and provides RPC endpoints for sending and subscribing to these messages. This work lays the groundwork for future application-layer protocols like BOLT12 offers, allowing for privacy-preserving communication beyond just payments.

Highlights

  • Onion Message Wire Protocol: Introduces a new lnwire.OnionMessage type to the wire protocol, enabling the transmission of onion-encrypted messages between Lightning Network nodes. This message type is distinct from HTLC-carrying onion packets and is designed for application-layer communication.
  • Sphinx Onion Processing Integration: Extends the existing Sphinx onion processing logic within htlcswitch/hop to correctly parse and handle OnionMessage payloads. This includes adapting the hop iterator to recognize onion messages, extracting forwarding information (such as the NextNodeID for node-based routing), and performing message-specific TLV validations.
  • New RPC Endpoints for Onion Messages: Adds SendOnionMessage and SubscribeOnionMessages to the LND RPC API. These new endpoints allow external applications and users to programmatically send onion messages to peers and subscribe to a stream of incoming onion messages, facilitating the development of new privacy-preserving communication features.
  • Dedicated Message Endpoint and Forwarding Logic: Implements a new onion_message.OnionEndpoint that integrates with the msgmux to process incoming lnwire.OnionMessages. This endpoint is responsible for decrypting the onion blob, determining if the message is for the local node or needs forwarding, and then either dispatching it to subscribers or relaying it to the next hop in the blinded path.
  • Enhanced Blinded Path Handling: Introduces specific logic for blinded paths within onion messages, including the addition of NextNodeID to ForwardingInfo. This ensures that onion messages, which do not carry payment-related information like CLTV deltas or amounts, are correctly processed and forwarded along their intended blinded routes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces onion message forwarding, a significant feature that touches multiple parts of the codebase. The changes are generally well-structured, with new functionality encapsulated in the onion_message package and corresponding updates to lnwire, htlcswitch, and the RPC layer. The inclusion of integration tests for both direct and forwarded onion messages is a great addition. I've identified a few issues that need attention: a critical bug in handling dummy hops for onion messages, a high-severity issue with TLV decoding that could lead to panics, and a medium-severity issue regarding message routing logic that could cause confusion and potential bugs. Addressing these will improve the correctness and maintainability of the new functionality.

@saubyk saubyk added this to lnd v0.20 Jul 22, 2025
@saubyk saubyk moved this to In progress in lnd v0.20 Jul 22, 2025
@gijswijs gijswijs force-pushed the onion-messaging-1 branch 5 times, most recently from 9157266 to 83a36aa Compare September 1, 2025 13:53
@gijswijs gijswijs force-pushed the onion-messaging-1 branch 2 times, most recently from 953b2d8 to 3f475ce Compare September 16, 2025 09:48
@saubyk saubyk added this to the v0.21.0 milestone Sep 24, 2025
@saubyk saubyk added this to v0.21 Sep 24, 2025
@saubyk saubyk removed this from lnd v0.20 Sep 24, 2025
@saubyk saubyk moved this to In progress in v0.21 Sep 24, 2025
// forwarding information for an onion message. It contains either
// next_node_id or short_channel_id for each non-final node. It MAY contain
// the path_id for the final node.
bytes encrypted_recipient_data = 5;
Copy link
Member

Choose a reason for hiding this comment

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

If this is intended for users to consume, shouldn't this be decrypted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not sure we even need this OnionMessageUpdate to be honest.
I needed it so that I could have itests that allowed me to test e2e onion messaging: Use SendOnionMessage to kick things of at Alice's end, and use SubscribeOnionMessages at Dave's end to see if everything comes through as expected.

That being said, if somebody would like to build something on top of onion message support (like LNDK is built on top of SubscribeCustomMessages), you would need SubscribeOnionMessages and you would want this to be decrypted.

@Abdulkbk
Copy link
Contributor

Since #9868 has been merged, I think this needs to be updated, wanna take a look.

@ziggie1984 ziggie1984 changed the base branch from master to 0-21-0-staging October 18, 2025 14:48
@gijswijs gijswijs force-pushed the onion-messaging-1 branch 2 times, most recently from 5a7e61d to dbb64f4 Compare October 30, 2025 12:43
@yyforyongyu yyforyongyu force-pushed the 0-21-0-staging branch 2 times, most recently from 86fd4b7 to 57eb251 Compare November 4, 2025 11:50
Roasbeef and others added 6 commits February 3, 2026 10:59
In this commit, we add a series of examples that show how the package
can be used in the wild. They can be run as normal Example tests.
In this commit, we add a readme which serves as a general introduction
to the pacakge, and also the motivation of the package. It serves as a
manual for developers that may wish to interact with the package.
This commit introduces a new Mailbox interface that abstracts the
message queue implementation for actors. Previously, actors used a
direct channel for their mailbox, which limited flexibility and made
it difficult to implement alternative mailbox strategies.

The new Mailbox interface provides methods for sending, receiving, and
draining messages, with full context support for cancellation. The
Receive method leverages Go 1.23's iter.Seq pattern, providing a clean
iterator-based API that allows natural for-range loops over messages.

The ChannelMailbox implementation maintains the existing channel-based
behavior while conforming to the new interface. It stores the actor's
context internally, ensuring both caller and actor contexts are
properly respected during send and receive operations. This simplifies
context handling compared to complex context merging approaches.

This abstraction enables future implementations such as priority
mailboxes, persistent mailboxes, or bounded mailboxes with overflow
strategies, without requiring changes to the actor implementation.
This commit adds thorough test coverage for the new Mailbox interface
and ChannelMailbox implementation. The tests verify correct behavior
across various scenarios including successful sends, context
cancellation, mailbox closure, and concurrent operations.

The test suite specifically validates that the mailbox respects both
the caller's context and the actor's context during send and receive
operations. This ensures that actors properly shut down when their
context is cancelled, and that callers can cancel operations without
affecting the actor's lifecycle.

Additional tests cover edge cases such as zero-capacity mailboxes
(which default to a capacity of 1), draining messages after closure,
and concurrent sends from multiple goroutines. The concurrent test
uses 10 senders each sending 100 messages to verify thread-safety
and proper message ordering.

All tests pass with the race detector enabled, confirming the
implementation is free from data races.
This commit refactors the Actor implementation to use the new Mailbox
interface instead of directly managing a channel. This change
significantly simplifies the actor's message processing loop and
improves separation of concerns.

The main changes include replacing the direct channel field with a
Mailbox interface, updating NewActor to create a ChannelMailbox
instance, and refactoring the process method to use the iterator
pattern provided by mailbox.Receive. The new implementation uses a
clean for-range loop over the mailbox's message iterator, eliminating
the complex select statement that previously handled both message
reception and context cancellation.

The Tell and Ask methods in actorRefImpl have been simplified to use
the mailbox's Send method, which internally handles both the caller's
context and the actor's context. This eliminates the need for complex
select statements in these methods and ensures consistent context
handling throughout the actor system.

Message draining during shutdown is now handled through the mailbox's
Drain method, providing a cleaner separation between normal message
processing and cleanup operations. The actor still properly sends
unprocessed messages to the Dead Letter Office and completes pending
promises with appropriate errors during shutdown.
The new wire message defines the OnionMessagePayload, FinalHopPayload,
ReplyPath, and related TLV encoding/decoding logic.
@gijswijs
Copy link
Collaborator Author

gijswijs commented Feb 3, 2026

By popular demand I've rebased the PR. No fixups. Good luck with the range-diff! 😄

@lightninglabs-deploy
Copy link
Collaborator

🔴 PR Severity: CRITICAL

Classified based on code analysis | 22 files (excl. tests/generated) | ~1,780 lines changed (excl. tests/generated)

🔴 Critical (5 files)
  • lnwire/features.go - Lightning wire protocol feature bit definitions
  • lnwire/onion_msg_payload.go - New wire protocol message payload structure (+412 lines)
  • peer/brontide.go - Core encrypted peer connection handling modifications
  • server.go - Core server coordination for onion message routing
  • rpcserver.go - RPC server changes for onion message subscription
🟠 High (3 files)
  • lnrpc/lightning.proto - RPC/API definition changes
  • feature/default_sets.go - Feature bit management updates
  • feature/manager.go - Feature manager modifications
🟡 Medium (14 files)
  • onionmessage/actor.go - New actor-based message handling
  • onionmessage/endpoint.go - Onion message endpoint implementation
  • onionmessage/hop.go - Hop processing logic
  • onionmessage/resolver.go - Resolver implementation
  • onionmessage/errors.go - Error definitions
  • onionmessage/onion_endpoint.go - Endpoint removal
  • msgmux/msg_router.go - Message multiplexer routing
  • record/blinded_data.go - TLV blinded data records
  • record/hop.go - TLV hop records
  • routing/pathfind.go - Pathfinding updates
  • routing/route/blindedroute.go - Blinded route structures
  • lncfg/protocol.go - Protocol configuration
  • lncfg/protocol_integration.go - Integration configuration
  • sample-lnd.conf - Configuration sample
🟢 Low (Test files, docs, generated files - excluded from classification)
  • 22 files in actor/* package (new actor system)
  • Multiple *_test.go files
  • Multiple .pb.go auto-generated files
  • docs/release-notes/release-notes-0.21.0.md
  • CI/dependency files

Analysis

This PR implements onion message forwarding, a significant protocol extension that touches multiple critical systems:

Critical concerns:

  1. Wire Protocol Changes: New message payload structures in lnwire/ affect the Lightning protocol implementation
  2. Peer Connection Layer: Modifications to peer/brontide.go impact encrypted peer communication
  3. Core Server Coordination: Changes to server.go and rpcserver.go affect the main request handling paths
  4. Multiple Critical Packages: Touches lnwire, peer connection layer, and server coordination simultaneously

Severity bumps applied:

  • Base severity: CRITICAL (due to lnwire/, peer/, server.go, rpcserver.go)
  • File count: 22 non-excluded files (above 20 threshold) ✓
  • Lines changed: ~1,780 non-excluded lines (well above 500 threshold) ✓
  • Multiple distinct critical packages affected ✓

Recommendation: This requires expert review from maintainers familiar with:

  • Lightning wire protocol specifications
  • Peer connection and message routing architecture
  • Onion routing cryptography and packet forwarding
  • BOLT 12 onion message specifications

To override, add a severity-override-{critical,high,medium,low} label.

@gijswijs gijswijs force-pushed the onion-messaging-1 branch 2 times, most recently from 78f0be1 to c9c1a87 Compare February 3, 2026 21:40
@lightninglabs-deploy

This comment was marked as duplicate.

@Abdulkbk
Copy link
Contributor

Abdulkbk commented Feb 4, 2026

By popular demand I've rebased the PR. No fixups. Good luck with the range-diff! 😄

Much better now without the fixups :)

Update lightning-onion to commit that includes onion-messaging support.
Adds the NewNonFinalBlindedRouteDataOnionMessage function to create
blinded route data specifically for onion messages.
@lightninglabs-deploy

This comment was marked as duplicate.

@gijswijs
Copy link
Collaborator Author

gijswijs commented Feb 4, 2026

Just pushed a tag for the lightning-onion package after the merge of lightningnetwork/lightning-onion#75. Updated this PR to import the latest version of lightning-onion. Merging the actor PR and fixing any CI/CD issues that might remain are the only todos left (bar any comment from the reviewers that still might come).

@Abdulkbk
Copy link
Contributor

Abdulkbk commented Feb 4, 2026

I'm running interop between CLN -> LND -> CLN to forward onion messages.

Logs

2026-02-04 13:43:28 2026-02-04 12:43:28.486 [DBG] PEER: Peer(02a99b66705b797f47f786c848162b1d70eef0b4cdfe8cfe73ca3011464abcb8b3): Received OnionMessage(unknown msg type=*lnwire.OnionMessage) from 02a99b66705b797f47f786c848162b1d70eef0b4cdfe8cfe73ca3011464abcb8b3@172.18.0.5:9735
2026-02-04 13:43:28 2026-02-04 12:43:28.487 [DBG] OMSG: OnionEndpoint received OnionMessage peer=02a99b66705b797f47f786c848162b1d70eef0b4cdfe8cfe73ca3011464abcb8b3 path_key=0368741ca643 onion_blob=000276bad4d28c91d208 blob_length=1366
2026-02-04 13:43:28 2026-02-04 12:43:28.497 [DBG] OMSG: Forwarding onion message peer=02a99b66705b797f47f786c848162b1d70eef0b4cdfe8cfe73ca3011464abcb8b3 path_key=0368741ca643 next_node_id=032e4058d94d
2026-02-04 13:43:28 2026-02-04 12:43:28.497 [DBG] PEER: Peer(032e4058d94d354a365f55e068855d2c265f44f0fbd8fd236a289583086943b35d): Sending OnionMessage(unknown msg type=*lnwire.OnionMessage) to 032e4058d94d354a365f55e068855d2c265f44f0fbd8fd236a289583086943b35d@172.18.0.3:9735
2026-02-04 13:43:28 2026-02-04 12:43:28.511 [DBG] PEER: Peer(032e4058d94d354a365f55e068855d2c265f44f0fbd8fd236a289583086943b35d): Received OnionMessage(unknown msg type=*lnwire.OnionMessage) from 032e4058d94d354a365f55e068855d2c265f44f0fbd8fd236a289583086943b35d@172.18.0.3:9735
2026-02-04 13:43:28 2026-02-04 12:43:28.511 [DBG] OMSG: OnionEndpoint received OnionMessage peer=032e4058d94d354a365f55e068855d2c265f44f0fbd8fd236a289583086943b35d path_key=038663df6392 onion_blob=000221b9ac6f05239bd8 blob_length=1366
2026-02-04 13:43:28 2026-02-04 12:43:28.516 [DBG] OMSG: Forwarding onion message peer=032e4058d94d354a365f55e068855d2c265f44f0fbd8fd236a289583086943b35d path_key=038663df6392 next_node_id=02a99b66705b
2026-02-04 13:43:28 2026-02-04 12:43:28.516 [DBG] PEER: Peer(02a99b66705b797f47f786c848162b1d70eef0b4cdfe8cfe73ca3011464abcb8b3): Sending OnionMessage(unknown msg type=*lnwire.OnionMessage) to 02a99b66705b797f47f786c848162b1d70eef0b4cdfe8cfe73ca3011464abcb8b3@172.18.0.5:9735

I'm getting unknown type in logs: Sending OnionMessage(unknown msg type=*lnwire.OnionMessage), looks like it's coming from here.

Initialize a sphinx router without persistent replay protection logging
for onion message processing. Onion messages don't require replay
protection since they don't involve payment routing.
Introduce OnionPeerActor to handle the sending of onion message to each
peer. The actor is registered with the receptionist pattern to enable
message routing through the actor system. Also adds onion message
feature bits to the protocol, so that the actor is only spawned when the
peer supports onion messages.
Add onion message forwarding capability using the OnionPeerActor for
communication. Messages are routed through a receptionist pattern where
each peer has a dedicated OnionPeerActor for handling message sends.

The OnionEndpoint uses the sphinx router for decoding and decrypting the
onion message packet and the encrypted recipient data in the payload of
the onion messages.
Change SubscribeOnionMessages RPC to return OnionMessageUpdate instead
of OnionMessage, including the decrypted payload to enable payload
inspection in integration tests. The encrypted recipient data is still
not decrypted here.
Previously, each peer created its own OnionEndpoint during Start(),
requiring SphinxOnionMsg and OnionMessageServer to be passed through the
peer config. This added unnecessary fields to the peer's config struct.

This commit refactors onion message handling so that:
- The OnionEndpoint is created once at server startup
- The shared endpoint is passed to peers via config
- Peers simply register the endpoint with their message router

This reduces the peer config surface and properly encapsulates the
OnionEndpoint's dependencies (sphinx router, resolver, message server)
at the server level where they naturally belong.
This commit adds a configuration flag to disable onion messaging support.
When set, lnd will:
- Not advertise the onion messages feature bit (39) in init and node
announcements
- Skip creating the OnionEndpoint at server startup
- Not register an onion message handler with peers, so incoming onion
messages are not processed
Add an LRU cache to GraphNodeResolver to avoid repeated database lookups
when resolving SCIDs to node public keys. The cache stores up to 1000
compressed pubkey entries, which is sufficient for typical onion message
forwarding scenarios.

This change also introduces a NewGraphNodeResolver constructor to
properly initialize the cache, replacing direct struct literal usage.
@lightninglabs-deploy

This comment was marked as duplicate.

@gijswijs
Copy link
Collaborator Author

gijswijs commented Feb 4, 2026

I'm getting unknown type in logs: Sending OnionMessage(unknown msg type=*lnwire.OnionMessage), looks like it's coming from here.

@Abdulkbk , that's a great catch. Updated messageSummary to log a brief, non-sensitive summary for lnwire.OnionMessage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

6 participants