bolt12: add InvoiceRequest codec and structural validators#10832
bolt12: add InvoiceRequest codec and structural validators#10832bitromortac wants to merge 11 commits into
InvoiceRequest codec and structural validators#10832Conversation
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 struct represents a BOLT 12 payment request as defined in the offers specification. It mirrors relevant fields from the original offer and adds payer-specific fields (metadata, amount, quantity, payer_id, note, reply paths, BIP 353 name) and a Schnorr signature. The struct implements lnwire.PureTLVMessage and supports round-trip serialization via Encode/DecodeInvoiceRequest. getInvoiceRequestOfferChains and checkInvreqQuantity provide spec-mandated defaults and coupling checks between the offer and request layers.
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.
Summary of ChangesHello, 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
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 AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| // rejected (even = must-understand). Type 240 carries the signature and | ||
| // sits outside the allowed range by spec design; skip it here as | ||
| // ValidateInvoiceRequestWrite does (validate.go:144). |
There was a problem hiding this comment.
Referencing specific line numbers in comments is fragile as they quickly become stale when the file is modified. It's better to refer to the function name or the logic itself to maintain clarity without risk of obsolescence.
| // rejected (even = must-understand). Type 240 carries the signature and | |
| // sits outside the allowed range by spec design; skip it here as | |
| // ValidateInvoiceRequestWrite does (validate.go:144). | |
| // rejected (even = must-understand). Type 240 carries the signature and | |
| // sits outside the allowed range by spec design; skip it here as | |
| // ValidateInvoiceRequestWrite does. |
| // offerAllowedRange returns true if the TLV type falls within the allowed | ||
|
|
||
| // ranges for offer messages: 1-79 and 1000000000-1999999999. |
There was a problem hiding this comment.
There is an accidental blank line in the middle of the function comment, which breaks the documentation block and prevents it from being correctly associated with the function by documentation tools.
| // offerAllowedRange returns true if the TLV type falls within the allowed | |
| // ranges for offer messages: 1-79 and 1000000000-1999999999. | |
| // offerAllowedRange returns true if the TLV type falls within the allowed | |
| // ranges for offer messages: 1-79 and 1000000000-1999999999. |
| * [Initial BOLT 12 Offer codec](https://github.com/lightningnetwork/lnd/pull/10789): | ||
| add a new `bolt12/` package with the BOLT 12 `offer` TLV codec and full | ||
| reader/writer validation, plus a typed `lnwire.BlindedPath` introduction-node | ||
| codec shared by HTLC routing and onion messaging. |
There was a problem hiding this comment.
The release notes should be updated to reflect that this pull request adds both the offer and invoice_request codecs and validators, as the current description only mentions the offer codec.
| * [Initial BOLT 12 Offer codec](https://github.com/lightningnetwork/lnd/pull/10789): | |
| add a new `bolt12/` package with the BOLT 12 `offer` TLV codec and full | |
| reader/writer validation, plus a typed `lnwire.BlindedPath` introduction-node | |
| codec shared by HTLC routing and onion messaging. | |
| * [Initial BOLT 12 Offer and InvoiceRequest codec](https://github.com/lightningnetwork/lnd/pull/10789): | |
| add a new `bolt12/` package with the BOLT 12 `offer` and `invoice_request` TLV | |
| codecs and full reader/writer validation, plus a typed `lnwire.BlindedPath` | |
| introduction-node codec shared by HTLC routing and onion messaging. |
|
🔴 PR Severity: CRITICAL
🔴 Critical (8 files)
🟠 High (1 file)
🟡 Medium (12 files)
🟢 Low (1 file)
⚪ Excluded from counting (10 files)
Analysis This PR introduces BOLT 12 invoice request codec and structural validators, touching the The new To override, add a |
Based on #10789 (last three commits), part of #10736.
Adds the BOLT 12
InvoiceRequestmessage struct withPureTLVMessage-basedEncode/Decodeand the structuralValidateInvoiceRequestRead/Writevalidators. Signature verification and offer cross-validation are deferred to theInvoiceandbolt12handlerlayers respectively.Todo: