Skip to content

Onion messaging support #68

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 11 commits into
base: master
Choose a base branch
from

Conversation

gijswijs
Copy link

@gijswijs gijswijs commented Jul 8, 2025

This PR adds support for onion messaging to the lightning-onion package. It does the following things:

  • pulls in the test vectors from the BOLT 4 spec for onion messaging
  • adds test TestBuildOnionMessageBlindedRoute that tests BuildBlindedRoute, decryptBlindedHopData and NextEphemeral against this vector.
  • add test TestOnionMessageRouteBlinding which verifies that the onion message packet from the test vector can be processed correctly by the nodes in a blinded route.
  • add test TestTLVPayloadMessagePacket that creates a onion message with payload and the blinded route from the test vector.

It also adds support for zero-length payloads and longer than 1300 bytes payloads that are specifically supported by the spec, but not tested by the test vectors supplied. Both features are accompanied by their own unit tests.

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 significantly extends the lightning-onion package by integrating full support for onion messaging, a distinct type of onion routing. The changes involve fundamental modifications to how onion packets are structured, created, and processed, enabling greater flexibility in payload sizes and strict adherence to TLV encoding for messages. This enhancement is crucial for future-proofing the library and supporting advanced messaging capabilities within the Lightning Network.

Highlights

  • Onion Messaging Support: This pull request introduces comprehensive support for 'onion messaging' within the lightning-onion package, distinguishing it from traditional payment onions. This includes new packet structures, creation, and processing logic tailored for onion messages.
  • Flexible Routing Info Sizes: The fixed-size RoutingInfo field in OnionPacket has been replaced with a dynamic slice ([]byte), enabling support for variable payload sizes. New constants StandardRoutingInfoSize (1300 bytes) and JumboRoutingInfoSize (32768 bytes) are introduced to accommodate different onion message lengths, including those exceeding the previous 1300-byte limit.
  • Payload Type Enforcement: Onion messages are now strictly enforced to use TLV (Type-Length-Value) payloads, aligning with BOLT 7 specifications. The packet creation and processing logic has been updated to validate this, rejecting legacy payloads for onion messages.
  • Updated Packet Creation and Processing: The NewOnionPacket and ProcessOnionPacket functions now accept functional options (OnionPacketOption, ProcessOnionOpt) to specify if the packet is an onion message. This allows for specialized handling, such as dynamic routing info sizing and payload type validation, based on the packet's purpose.
  • New Test Vectors and Comprehensive Testing: New test vectors from the BOLT 4 spec for onion messaging have been integrated. Dedicated tests (TestBuildOnionMessageBlindedRoute, TestOnionMessageRouteBlinding, TestTLVPayloadMessagePacket) have been added to validate blinded route construction, packet processing, and TLV payload handling for onion messages. Tests for zero-length and larger than 1300-byte payloads are also included.
  • CLI Tool Enhancement: A new --onion-message flag has been added to the cmd/main.go CLI tool, allowing users to generate onion message packets directly.
  • Dependency Updates: Several Go module dependencies, including btcd, btcec/v2, stretchr/testify, and golang.org/x/crypto, have been updated to their latest versions. New indirect dependencies like lnd/tlv have also been added.
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.

@gijswijs
Copy link
Author

gijswijs commented Jul 8, 2025

/gemini review

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 comprehensive support for onion messaging. The core logic changes are well-implemented. The review focuses on improving the new tests, identifying areas for improvement such as code clarity and test correctness.

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 support for onion messaging, including handling of blinded routes and variable-sized payloads. The changes include new logic for packet creation and processing, and are supported by test vectors and new unit tests. I have a few suggestions to improve code clarity and robustness.

@gijswijs gijswijs force-pushed the onion-messaging branch 2 times, most recently from 6aa8a12 to dd68d45 Compare July 9, 2025 07:47
@saubyk saubyk added this to lnd v0.20 Jul 10, 2025
@saubyk saubyk moved this to In progress in lnd v0.20 Jul 15, 2025
@saubyk
Copy link
Collaborator

saubyk commented Jul 15, 2025

cc: @yyforyongyu @ellemouton for review

Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

Nice tests! Finish one round, mostly on the code format and tests, will do another round soon to load the context.

Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

Second round done. My main comments are,

  • the legacy vs empty tlv stream check, and wonder whether it's needed, and if so, can we do it in a higher layer instead. Also wondering if we could drop the support for legacy payload.
  • code format needs to be fixed.
  • need to make it compile, ran linter and got
> golangci-lint run
cmd/main.go:1: : # github.com/gijswijs/lightning-onion/cmd
cmd/main.go:212:25: undefined: sphinx.OnionPacketOption
cmd/main.go:214:40: undefined: sphinx.WithOnionMessage
cmd/main.go:217:14: cannot use ... in call to non-variadic sphinx.NewOnionPacket (typecheck)
package main

I also noticed that we don't have any CI here, maybe a minimal CI that includes jobs like unit test, lint, and check commits would be a good starting point, and it should be easy to add as we can just copy files from lnd. Another PR tho.

sphinx.go Outdated
totalPayloadSize := paymentPath.TotalPayloadSize()

var routingInfoLen int
maxRoutingInfoErr := ErrStandardRoutingInfoSizeExceeded
Copy link
Member

Choose a reason for hiding this comment

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

var maxRoutingInfoErr error

Copy link
Author

Choose a reason for hiding this comment

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

You don't like short variable declarations? I'm not sure what you want here.


// If this is an onion message, we only expect TLV
// payloads.
if cfg.isOnionMessage && isLegacy {
Copy link
Member

Choose a reason for hiding this comment

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

Think this legacy vs empty tlv kinda increases the complexity a lot - I wonder if we need to check it here, like a higher layer should be able to catch and error out there?

Copy link
Author

Choose a reason for hiding this comment

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

@ellemouton made a similar remark here: #68 (comment)

NewOnionPacket is exposed, so the higher layer you reference is outside of this library. Do we want to offload those checks to the ones importing this library?

I think those checks make the library more easy to use, because they give useful error messages.

My $0.02.

// header. The MaxPayloadSize bytes at the end are used to
// encrypt/decrypt the fillers when processing the packet of generating
// the HMACs when creating the packet.
numStreamBytes = routingInfoSize * 2
Copy link
Member

Choose a reason for hiding this comment

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

I really like the docs here, and wonder if we could somehow retain it, like make numStreamBytes a method that takes routingInfoLen and returns it, otherwise I see routingInfoLen * 2 and will be wondering why the *2

@yyforyongyu
Copy link
Member

/gemini review

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 comprehensive support for onion messaging, including blinded routes and variable-sized onion packets, which is a significant feature addition. The implementation is well-supported by new tests, including vectors from the BOLT specification.

My review focuses on enhancing the code quality. I've suggested improvements to test robustness by replacing ignored errors with explicit checks, and a simplification of some conditional logic to improve readability and maintainability. These suggestions align with general Go best practices for writing clear and robust code.

sphinx.go Outdated
Comment on lines 230 to 232
routingInfoLen = StandardRoutingInfoSize
default:
routingInfoLen = JumboRoutingInfoSize
Copy link
Collaborator

Choose a reason for hiding this comment

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

the spec says that the length of the packet for onion messages should be either 1366 or 32834 but not that it must. ie, i think you need to support it being other lengths too.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, it says should but why support random lengths? This is what the rationale is in the spec.

len allows larger messages to be sent than the standard 1300 bytes allowed for an HTLC onion, but this should be used sparingly as it reduces the anonymity set, hence the recommendation that it either looks like an HTLC onion, or if larger, be a fixed size.

Let's nudge people in using this correctly and automatically switch to the fixed large size if they cross the standard routing info size threshold.

sphinx.go Outdated
Comment on lines 618 to 621
// If this is an onion message, a blinding point must be provided.
if cfg.isOnionMessage && cfg.blindingPoint == nil {
return nil, fmt.Errorf("blinding point must be provided for " +
"onion messages")
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

and that assocData is empty. But imo, this is a business logic check that should happen at a higher level

Copy link
Author

Choose a reason for hiding this comment

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

Hmm, not sure if I agree. I would say this is distinct from business logic and qualifies as validation logic. It's not really a business policy that you need to supply a blinding point, it's a fundamental building block of onion messages as such. Also, this is a package that people use, and it's helpful to supply them with meaningful error messages.

But we can probably debate this for hours. I'm ok with it either way. Just say the word.

@@ -41,12 +41,25 @@ const (
LegacyHopDataSize = (RealmByteSize + AddressSize + AmtForwardSize +
OutgoingCLTVSize + NumPaddingBytes + HMACSize)

Copy link
Collaborator

Choose a reason for hiding this comment

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

when you say breaking change - i think you just mean that compilation breaks - which imo i not a problem & we dont need to account for it. at the time that we update LND to point to the new onion pkg version, we just fix the compilation

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, correct, compilation breaks because people currently use MaxPayloadSize which is exposed. I tried to make this PR such that people could update the lightning-onion package without changing their code. I'm not sure how widely used lightning-onion is at this moment. If we only use it in lnd than obviously this isn't a concern.

payload.go Outdated
Comment on lines 92 to 95
func (hp *HopPayload) Decode(r io.Reader, isMessage ...bool) error {
// To preserve backwards compatibility, we'll default to isMessage being
// false if it is not provided.
isMsg := len(isMessage) > 0 && isMessage[0]
Copy link
Collaborator

Choose a reason for hiding this comment

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

same comment as previous commit. i dont think we need to do this. Also adding a variadic param here doesnt make sense.

Copy link
Author

Choose a reason for hiding this comment

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

afaik, using a variadic argument is idiomatic Go for maintaining backward compatibility. It doesn't break existing callers.

But I guess we're ok with breaking existing callers, so I'll go ahead and remove this.

gijswijs added 3 commits July 25, 2025 14:27
This commit removes an unused var and changes bytes.Compare to the
idiomatic bytes.Equal.
This commit adds the spec test vector for blinded onion messages. It
also adds a test that tests BuildBlindedRoute, decryptBlindedHopData and
NextEphemeral against this vector.
We add TestOnionMessageRouteBlinding which verifies that the onion
message packet from the test vector can be processed correctly by the
nodes in a blinded route.
@lightninglabs-deploy
Copy link

@gijswijs, remember to re-request review from reviewers when ready

TestTLVPayloadMessagePacket creates a onion message with payload and the
blinded route from the test vector. It then checks if the onion packet
we create is equal to the one provided in the test vector.
Since the onion message payload can be zero-length, we need to decode it
correctly. This commit adds a boolean flag to the HopPayload Decode that
tells whether the payload is an onion message payload or not. If it is,
the payload is decoded as a tlv payload also if the first byte is 0x00.

sphinx_test: Add zero-length payload om test
Onion messages allow for payloads that exceed 1300 bytes, in which case
the payload should become 32768 bytes. This commit introduces support
for those jumbo packets.

sphinx_test: test jumbo size onion message packets

This commit adds a helper function to create onion messages of a
specified length. This helper is then used to test the handling of
packets larger than 1300 bytes specifically for onion messages.
When we are parsing onion messages, we must ensure that a blinding point
is provided.
The field MaxPayloadSize is added to the sphinx package to allow for
backwards compatibility with the old sphinx package. Removing it would
have been a breaking change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In progress
Development

Successfully merging this pull request may close these issues.

5 participants