Skip to content

Introduce Stronger Typing for VerifiedInvoiceRequest and Refactor Invoice Building Flow #3964

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

Conversation

shaavan
Copy link
Member

@shaavan shaavan commented Jul 26, 2025

Previously, VerifiedInvoiceRequest used an optional field to decide between calling using_derived_key or using_explicit_key invoice builders. However, this approach relied on runtime checks, and didn't enforce the correct builder usage at compile time.

To address this, this PR introduces a stronger type distinction by parameterizing VerifiedInvoiceRequest with a SigningPubkeyStrategy. This ensures that only the appropriate builder method (using_derived_key or using_explicit_key) is available for the corresponding variant, enforcing correctness at compile time.

This change also refactors the downstream invoice_builder interface in OffersMessageFlow by taking the following steps:

  • Split the builder method based on the VerifiedInvoiceRequest variant, ensuring that only the correct method can be called, and shifting the responsibility of matching the variant to the user at compile time.
  • Move signing logic back to ChannelManager, aligning with the pattern used in other builders. This allows users to further customize the InvoiceBuilder before signing.
  • Introduce a closure for (payment_hash, payment_secret) generation, consolidating the amount_msats input into a single source of truth. This ensures consistency between invoice creation and payment path generation.

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Jul 26, 2025

👋 I see @valentinewallace was un-assigned.
If you'd like another reviewer assignment, please click here.

@shaavan
Copy link
Member Author

shaavan commented Jul 26, 2025

cc @jkczyz

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz jkczyz self-requested a review July 29, 2025 15:21
@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 2nd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 4th Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 3rd Reminder

Hey @jkczyz @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 5th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@shaavan
Copy link
Member Author

shaavan commented Aug 7, 2025

Updated from pr3964.01 to pr3964.02 (diff):
Addressed @jkczyz comments

Changes:

  1. Improved functional naming, and documentations.

Copy link

codecov bot commented Aug 7, 2025

Codecov Report

❌ Patch coverage is 84.18605% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.84%. Comparing base (ac8f897) to head (71669fd).

Files with missing lines Patch % Lines
lightning/src/ln/channelmanager.rs 84.72% 9 Missing and 2 partials ⚠️
lightning/src/offers/invoice_request.rs 81.48% 10 Missing ⚠️
lightning/src/offers/flow.rs 87.09% 0 Missing and 8 partials ⚠️
lightning/src/offers/invoice.rs 76.47% 3 Missing and 1 partial ⚠️
lightning/src/ln/offers_tests.rs 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3964      +/-   ##
==========================================
- Coverage   88.85%   88.84%   -0.01%     
==========================================
  Files         175      175              
  Lines      127710   127795      +85     
  Branches   127710   127795      +85     
==========================================
+ Hits       113478   113542      +64     
- Misses      11675    11686      +11     
- Partials     2557     2567      +10     
Flag Coverage Δ
fuzzing 21.83% <0.00%> (-0.03%) ⬇️
tests 88.68% <84.18%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot
Copy link

🔔 6th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 7th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 8th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@shaavan
Copy link
Member Author

shaavan commented Aug 15, 2025

Updated from pr3964.02 to pr3964.03 (diff):
Addressed @jkczyz comments

Changes:

  • Rename InvoiceSigningInfo -> InvoiceRequestVerifiedFromOffer.

In the upcoming commits, we will be phasing the current style of
VerifiedInvoiceRequest, in favour of newer version.
To keep the changes modular, and clean we rename the current
VerifiedInvoiceRequest to VerifiedInvoiceRequestLegacy.
In the following commits we will introduce `fields` function
for other types as well, so to keep code DRY we convert the
function to a macro.
This commit reintroduces `VerifiedInvoiceRequest`, now parameterized by
`SigningPubkeyStrategy`.

The key motivation is to restrict which functions can be called on a
`VerifiedInvoiceRequest` based on its strategy type. This enables
compile-time guarantees — ensuring that an incorrect `InvoiceBuilder`
cannot be constructed for a given request, and misuses are caught early.
This commit replaces the legacy `VerifiedInvoiceRequestLegacy`
with the new `InvoiceRequestVerifiedFromOffer` type in the codebase.
This change improves type safety and architectural clarity
by introducing dedicated `InvoiceBuilder` methods tied to
each variant of `VerifiedInvoiceRequestEnum`.

With this change, users are now required to match on the
enum variant before calling the corresponding builder method.
This pushes the responsibility of selecting the correct
builder to the user and ensures that invalid builder
usage is caught at compile time, rather than relying
on runtime checks.

The signing logic has also been moved from the builder
to the `ChannelManager`. This shift simplifies the
builder's role and aligns it with the rest of the API,
where builder methods return a configurable object that
can be extended before signing. The result is a more
consistent and predictable interface that separates
concerns cleanly and makes future maintenance easier.
To ensure correct Bolt12 payment flow behavior, the `amount_msats`
used for generating the `payment_hash`, `payment_secret`,
and payment path must remain consistent. Previously, these steps
could inadvertently diverge due to separate sources of `amount_msats`.

This commit refactors the interface to use a `get_payment_info` closure,
which captures the required variables and provides a single source of
truth for both payment info (payment_hash, payment_secret) and path
generation. This ensures consistency and eliminates subtle bugs
that could arise from mismatched amounts across the flow.
@shaavan
Copy link
Member Author

shaavan commented Aug 15, 2025

Updated from pr3964.03 to pr3964.04 (diff):

Changes:

  1. Rebase on main

@ldk-reviews-bot
Copy link

🔔 9th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 10th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot
Copy link

🔔 11th Reminder

Hey @valentinewallace! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@valentinewallace
Copy link
Contributor

I'm unfortunately not going to have time to review this in a timely manner, so unassigning myself and letting the bot take the wheel

@valentinewallace valentinewallace removed their request for review August 20, 2025 20:15
@ldk-reviews-bot
Copy link

✅ Added second reviewer: @joostjager

Copy link
Contributor

@joostjager joostjager left a comment

Choose a reason for hiding this comment

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

After an initial pass, my feelings about this PR are mixed. Like probably every other rust dev, I also like strong types and compile time checks. But I am not sure if the old situation with the keys option was a big enough problem to justify the code changes proposed in this PR. Especially because the end result may not be considered fully clean either (panic match arms, code duplication).

Other considerations are dev/review resources available, risk that every change introduces and priority. Perhaps it would be good for this PR (and PRs in general) to add a few lines to the description elaborating on that. Could be downstream advantages for example that aren't visible in the PR itself.

@@ -961,9 +961,43 @@ macro_rules! invoice_request_respond_with_derived_signing_pubkey_methods { (
}
} }

macro_rules! fields_accessor {
Copy link
Contributor

Choose a reason for hiding this comment

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

My impression is that we want to get rid of macros as much as possible, not introduce new ones?

.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
.features_unchecked(Bolt12InvoiceFeatures::unknown())
.build_and_sign(&secp_ctx).unwrap();
let verified_invoice_request = invoice_request
Copy link
Contributor

Choose a reason for hiding this comment

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

For review, I'd prefer all commits up to this one to be squashed. The pattern of 'rename - add new type - replace usages' makes it harder for me to see what the actual change is. This is what I am looking for in a diff:

image

},
};
let payment_paths = self
.create_blinded_payment_paths(
Copy link
Contributor

Choose a reason for hiding this comment

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

Not ideal that code needs to be duplicated now.

}
},
InvoiceRequestVerifiedFromOffer::ExplicitKeys(_) => {
panic!("expected invoice request with keys");
Copy link
Contributor

Choose a reason for hiding this comment

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

This match arm with a panic comes back a few times. It does feel a bit anti-pattern'ish. If we go for strong types to eliminate runtime checks, I wouldn't expect other runtime checks being added.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is really is acting more like a test assertion. The user would need to handle both case.

When reading an invoice request, the invoice signing mechanism is determined by parsing and verifying the underlying offer using its metadata (or more likely from the metadata included in the blinded path that the invoice request was received over). Note that this verification is specific to LDK's construction of offers, but it's not generalizable to offers constructed by other implementations, since they may use the metadata in their own way.

To make it more strongly typed, we'd need to pass in the ExpandedKey used to construct the underlying offer at parse time to determine its type. However, wherever you try to push the type checks, you end up with something that is / has an enum because you can't predict the resulting type before parsing it.

For instance, we can make it such that we have InvoiceRequest<S> that contains OfferContents<S>, but we still need a wrapper enum around InvoiceRequest. And you'd need to fail parsing if you can't authenticate it. We chose to have verification to be a separate step from parsing. That's cleaner and allows the offers module to be used independent of the rest of LDK, if desired.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I missed that this is indeed in tests. Yes, makes sense to have the panic there.

I still keep looking for where in this PR exactly advantage is taken of the strong typing. Is it that you could do something invalid with create_response_for_invoice_request ?

@joostjager
Copy link
Contributor

Final thought, not fully checked, is whether there is a simpler way to make the api safe by just moving the respond methods from InvoiceRequest to a place where they can't be misused.

@jkczyz
Copy link
Contributor

jkczyz commented Aug 21, 2025

Other considerations are dev/review resources available, risk that every change introduces and priority. Perhaps it would be good for this PR (and PRs in general) to add a few lines to the description elaborating on that. Could be downstream advantages for example that aren't visible in the PR itself.

Agreed that the PR description should include motivation for the change. Namely, use for custom handling of invoice requests / building custom invoices.

@joostjager
Copy link
Contributor

joostjager commented Aug 22, 2025

Agreed that the PR description should include motivation for the change. Namely, use for custom handling of invoice requests / building custom invoices.

Can I see this somewhere? So that it becomes more clear where the benefits are of the stronger typing.

)?;

let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?;
let entropy = &*self.entropy_source;
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: there is potential to remove some rustfmt::skip in this PR (in a separate commit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants