Skip to content

htlcswitch/hop: require total_amount_msat for blinded final hops#10597

Open
erickcestari wants to merge 2 commits intolightningnetwork:masterfrom
erickcestari:add-bolt4-total-amount-check
Open

htlcswitch/hop: require total_amount_msat for blinded final hops#10597
erickcestari wants to merge 2 commits intolightningnetwork:masterfrom
erickcestari:add-bolt4-total-amount-check

Conversation

@erickcestari
Copy link
Contributor

Per BOLT 4, when encrypted_recipient_data is present on a final node, total_amount_msat MUST be present. This adds explicit validation at payload parsing time.

Found by differential fuzzing using bitcoinfuzz.

@gemini-code-assist
Copy link

Summary of Changes

Hello @erickcestari, 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 enhances the robustness and compliance of the HTLC switch by introducing a critical validation step for blinded payments. It ensures that final hops carrying encrypted recipient data correctly include the total_amount_msat field, preventing potential protocol violations and improving the reliability of payment routing in the Lightning Network.

Highlights

  • BOLT 4 Compliance: Implemented explicit validation to ensure total_amount_msat is present for blinded final hops when encrypted_recipient_data is used, aligning with BOLT 4 specifications.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • htlcswitch/hop/payload.go
    • Added validation for total_amount_msat on blinded final hops.
Activity
  • The issue was identified through differential fuzzing using bitcoinfuzz.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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 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. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

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.

@lightninglabs-deploy lightninglabs-deploy added the severity-critical Requires expert review - security/consensus critical label Feb 18, 2026
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 correctly implements the BOLT 4 requirement that total_amount_msat (TLV type 18) must be present when encrypted_recipient_data (TLV type 10) is provided on a final node. The changes are well-integrated into the existing ValidateParsedPayloadTypes function and follow the established coding patterns in the lnd repository. The validation logic accurately targets the final hop of a blinded path and returns the appropriate ErrInvalidPayload error with an OmittedViolation. The review was conducted with reference to the LND Repository Style Guide, specifically ensuring adherence to line length limits, logical code segmentation, and clear commenting of intentions.

  Per BOLT 4, when encrypted_recipient_data is present on a final node,
  total_amount_msat MUST be present. This adds explicit validation at
  payload parsing time.

  Found by differential fuzzing using bitcoinfuzz.
@erickcestari erickcestari force-pushed the add-bolt4-total-amount-check branch from 27029b7 to 774960d Compare February 18, 2026 18:17
@Roasbeef
Copy link
Member

This would fail further down the pipeline, correct?

@ziggie1984
Copy link
Collaborator

Yes, it would be caught further down the pipeline, but only indirectly. Blinded payments go through updateMpp in invoices/update.go (both MPP and blinded paths route through this function — it handles blinded payments when ctx.pathID != nil). In that function, totalAmt is set directly from ctx.totalAmtMsat, which defaults to 0 if the field is absent. The zero check at line 241 then rejects the HTLC with ResultHtlcSetTotalTooLow.

So while the HTLC would be rejected, the error reason is misleading — it implies the amount is too low rather than that a required field is missing. Adding the check at payload parsing time gives a precise ErrInvalidPayload{OmittedViolation} error and enforces the BOLT 4 requirement at the right layer.

0x0a, 0x03, 0x03, 0x02, 0x01,
// total_amount
0x12, 0x00,
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: the zero-length value here (0x12, 0x00) — type 18, length 0 — decodes as 0 for a tuint64, but is enough to mark the field as present in the parsed type map, which is all this layer checks. Worth a short comment to make that intent clear.

@ziggie1984
Copy link
Collaborator

As a follow-up, it might also be worth adding this validation on the sending side in PackHopPayload (routing/route/route.go), where AmtToForward and OutgoingTimeLock are already validated for blinded final hops via the optionalBlindedField helper. Adding the same check for TotalAmtMsat there would catch the issue early at route construction time (defense-in-depth against our own bugs), before the payment is even attempted. That said, this would be a separate PR since it has a different scope and risk profile.

Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

LGTM, thank you for working on this.

@lightninglabs-deploy
Copy link
Collaborator

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

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

Labels

severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants