Skip to content

bolt12: add InvoiceRequest codec and structural validators#10832

Open
bitromortac wants to merge 13 commits into
lightningnetwork:masterfrom
bitromortac:2604-bolt12-1b
Open

bolt12: add InvoiceRequest codec and structural validators#10832
bitromortac wants to merge 13 commits into
lightningnetwork:masterfrom
bitromortac:2604-bolt12-1b

Conversation

@bitromortac

@bitromortac bitromortac commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Based on #10789 (last five commits), part of #10736.

Adds the BOLT 12 InvoiceRequest message struct with PureTLVMessage-based Encode/Decode and the structural ValidateInvoiceRequestRead/Write validators. Signature verification and offer cross-validation are deferred to the Invoice and bolt12handler layers respectively.

I added a commit to address the discussion around the SubscribeOnionMessages rpc. There were other requests concerning the lnwire onion message implementation, which I haven't addressed yet. I'll open an extra PR for that.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, 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 codec and validation logic for BOLT 12 InvoiceRequest messages. It establishes a new bolt12 package dedicated to the encoding, decoding, and structural validation of offers and invoice requests. Furthermore, it refactors the existing BlindedPath implementation to support the introduction node variants required by the specification, ensuring that both Pubkey and Sciddir forms are correctly handled throughout the codebase, including RPC surfaces.

Highlights

  • InvoiceRequest Implementation: Added the InvoiceRequest struct along with its associated TLV-based encoding and decoding logic.
  • Structural Validation: Implemented comprehensive structural validators for both Offers and InvoiceRequests to ensure spec compliance.
  • BlindedPath Refactoring: Refactored BlindedPath to support IntroductionNode variants, specifically adding support for both Pubkey and Sciddir forms.
  • RPC and Wire Updates: Updated lnrpc and lnwire packages to accommodate the new BlindedPath structure and ensure consistent handling across the daemon.
New Features

🧠 You can now enable Memory (public preview) 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.

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 the 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 counterproductive. 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.

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.

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

Copy link
Copy Markdown

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 implements the core BOLT 12 Offer and Invoice Request functionality, introducing the bolt12 package for TLV encoding, decoding, and extensive validation. It also refactors lnwire to include a shared BlindedPath implementation supporting both pubkey and sciddir introduction nodes, while updating the RPC and onion messaging layers to utilize these new types. Review feedback focused on improving documentation and comment maintainability, specifically recommending the removal of fragile line number references, fixing a broken documentation block in the validation logic, and ensuring the release notes accurately reflect the inclusion of the invoice_request codec.

Comment thread bolt12/validate.go Outdated
Comment thread bolt12/validate.go Outdated
Comment thread docs/release-notes/release-notes-0.22.0.md
@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label May 22, 2026
@github-actions

Copy link
Copy Markdown

🔴 PR Severity: CRITICAL

Automated classification | 32 files total (22 non-excluded) | 2,252 non-excluded lines changed

🔴 Critical (8 files)
  • lnwire/blinded_path.go — Lightning wire protocol: new blinded path type definition
  • lnwire/bounds.go — Lightning wire protocol: new TLV bounds declarations
  • lnwire/custom_records.go — Lightning wire protocol: custom records update
  • lnwire/intro_node.go — Lightning wire protocol: new intro node definition
  • lnwire/onion_msg_payload.go — Lightning wire protocol: onion message payload refactor
  • lnwire/pure_tlv.go — Lightning wire protocol: TLV serialization changes
  • lnwire/test_utils.go — Lightning wire protocol: test utilities in critical package
  • rpcserver.go — Core RPC server coordination
🟠 High (1 file)
  • routing/route/blindedroute.go — Payment routing: blinded route update
🟡 Medium (12 files)
  • bolt12/decode.go — New bolt12 package: TLV decode
  • bolt12/doc.go — New bolt12 package: package documentation
  • bolt12/invoice_request.go — New bolt12 package: invoice request codec
  • bolt12/offer.go — New bolt12 package: offer type
  • bolt12/pure_tlv.go — New bolt12 package: TLV primitives
  • bolt12/subtypes.go — New bolt12 package: subtype definitions
  • bolt12/tlv_types.go — New bolt12 package: TLV type constants
  • bolt12/validate.go — New bolt12 package: structural validators
  • go.mod — Dependency update
  • lnrpc/lightning.proto — RPC API definition changes
  • lnrpc/lightning.swagger.json — API swagger changes
  • onionmessage/onion_endpoint.go — Onion message endpoint update
🟢 Low (1 file)
  • docs/release-notes/release-notes-0.22.0.md — Release notes
Excluded from counting (10 files)
  • bolt12/helpers_test.go, bolt12/invoice_request_test.go, bolt12/offer_test.go, bolt12/subtypes_test.go, bolt12/validate_test.go — test files
  • lnwire/blinded_path_test.go, lnwire/custom_records_test.go, lnwire/onion_msg_payload_test.go, lnwire/pure_tlv_test.go — test files
  • lnrpc/lightning.pb.go — auto-generated protobuf

Analysis

This PR introduces BOLT 12 invoice request codec and structural validators, touching the lnwire package (Lightning wire protocol messages) extensively with 7 new/modified files covering blinded paths, onion message payloads, TLV serialization, and related types. Changes to rpcserver.go further anchor this at CRITICAL. Two severity-bump conditions are also satisfied: 22 non-excluded files (>20 threshold) and 2,252 non-excluded lines changed (>500 threshold), though the base classification is already CRITICAL from the lnwire and rpcserver.go changes.

The new bolt12/ package itself would be MEDIUM in isolation, but the deep integration with lnwire wire protocol message structures and core RPC server requires expert review of encoding correctness, TLV type assignments, and compatibility with the existing Lightning protocol implementation.


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

@saubyk saubyk added the bolt12 label May 22, 2026
@saubyk saubyk added this to the v0.22.0 milestone May 22, 2026
@saubyk saubyk added this to lnd v0.22 May 22, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in lnd v0.22 May 22, 2026
@saubyk saubyk moved this from Backlog to In progress in lnd v0.22 May 22, 2026
Add UnsignedRangeFunc and the SerialiseFieldsToSignFn /
ExtraSignedFieldsFromTypeMapFn variants so callers with non-BOLT 7 v2
signed ranges (e.g. BOLT 12, which reserves only 240-1000) can plug in
their own predicate. The existing SerialiseFieldsToSign and
ExtraSignedFieldsFromTypeMap entry points keep their behaviour by
delegating to the Fn variants with InUnsignedRange.
This gives us easier optional tlv field handling, which we will use for
the following message definitions.
Introduce the canonical lnwire.BlindedPath / BlindedPaths codec with a
sealed IntroductionNode sum-type covering both the BOLT 4 pubkey and
sciddir variants. The codec gates every variable-length subfield against
an io.LimitedReader. It fails closed on the encoder side so invalid
input never hits the wire.

This commit is a pure addition: no existing caller changes. Subsequent
commits migrate OnionMessagePayload and the bolt12 message structs to
consume the new codec.
Switch OnionMessagePayload.ReplyPath from *sphinx.BlindedPath to
*lnwire.BlindedPath. The reply-path TLV is now produced and consumed by
(*lnwire.BlindedPath).Record(), which honours the BOLT 4 sciddir_or_pubkey
introduction-node form. The legacy decoder gated on a 67-byte minimum
length and silently rejected reply paths whose introduction node used
the 9-byte sciddir variant.

The legacy replyPathRecord / replyPathSize / encodeReplyPath /
decodeReplyPath / blindedHopSize / encodeBlindedHop / decodeBlindedHop
helpers and the unused ErrNoHops sentinel are deleted.

Consumers update mechanically: routing/route's
OnionMessageBlindedPathToSphinxPath replyPath parameter, the
onionmessage.OnionMessageUpdate field, the rpcserver onion-message
subscription bridge, and the lnwire test utilities now use the lnwire
type directly. The new TestOnionMessagePayloadRoundTrip "sciddir intro
reply path" subtest pins the BOLT 4 spec fix.
Introduce the ChainsRecord subtype used by the offer_chains and
invoice_chains TLV fields. Decoding caps the count at maxOfferChains to
bound allocation.
The Offer struct models a long-lived, reusable BOLT 12 payment template.
It defines TLV fields as optional records and exposes Encode/DecodeOffer
for round-trip serialization. The struct implements
lnwire.PureTLVMessage; AllRecords filters the decoded TypeMap through
bolt12InUnsignedRange to derive any signed-range extras the encoder must
re-emit, keeping offer_id and the Merkle root stable across encoders
that understand a wider set of even/odd extensions.
ValidateOfferRead and ValidateOfferWrite enforce the codec-side portion
of the BOLT 12 offer reader and writer requirements. Reader rules cover
TLV range, even-feature-bit rejection, chain mismatch, dependency rules
between offer_amount/description/currency, missing issuer identity,
zero-hop blinded paths, and offer expiry. Writer rules mirror the same
dependency and identity guards plus a defense-in-depth empty-
offer_chains rejection.

offer_currency is validated against the ISO 4217 registry via
golang.org/x/text/currency (now a direct dependency); offer_issuer_id is
verified to be an on-curve SEC1 compressed point on both read and write
paths. Encode invokes Validate so invalid bytes never reach the wire.
Document that the introduction_node field in an OnionMessageUpdate's
reply_path is passed through verbatim from the wire, potentially
carrying either a 33-byte pubkey or a 9-byte sciddir form. Subscribers
wishing to reply must resolve sciddir forms against their local channel
graph.

The SubscribeOnionMessages bridge is refactored to use a new
marshallBlindedPath helper, ensuring a nil reply path remains nil in the
RPC response rather than being emitted as an empty struct.
The InvoiceRequest is the BOLT 12 message that links a payer to an
offer: it mirrors the offer's fields so the issuer can stay stateless,
and adds the payer-specific fields and Schnorr signature that prove the
request.

It implements lnwire.PureTLVMessage so it round-trips through the shared
TLV codec.
This ensures we copy all fields when replying to an offer.
ValidateInvoiceRequestRead and ValidateInvoiceRequestWrite enforce the
structural BOLT 12 requirements an invoice request can be checked
against on its own. The reader validates incoming requests. The writer
catches out-of-range types in decoded-then-mutated requests before they
leave the local boundary. Type 240 carries the signature and sits
outside the allowed range by spec design. Both validators skip it
during the range scan.

Two reader MUSTs are deferred. Schnorr signature verification against
the merkle root keyed by invreq_payer_id lands with the Invoice
message, where the merkle and signing primitives are shared. Offer
cross-validation requires an Offer reference the structural validator
does not carry, and lands in the bolt12handler layer where both the
request and the stored Offer are in scope.
Add a BOLT 12 release note for the invoice_request codec, completing the
offer/invoice_request entries for the bolt12 package in 0.22.0.
@bitromortac bitromortac marked this pull request as ready for review June 11, 2026 13:31
@bitromortac

Copy link
Copy Markdown
Collaborator Author

This is ready for review. @Abdulkbk and @vctt94, thank you for your reviews on the other PR. A review here would be greatly appreciated here as well, if you find the time.

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

Labels

bolt12 severity-critical Requires expert review - security/consensus critical

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants