Skip to content

Commit to payment_metadata in inbound payment HMAC#4528

Open
TheBlueMatt wants to merge 9 commits intolightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
Open

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt wants to merge 9 commits intolightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata

Conversation

@TheBlueMatt
Copy link
Copy Markdown
Collaborator

When payment_metadata is set in a BOLT 11 invoice, users expect to
receive it back as-is in the payment onion. In order to ensure it
isn't tampered with, they presumably will add an HMAC, or worse, not
add one and forget that it can be tampered with.

Instead, here we include it in the HMAC computation for the payment
secret. This ensures that the sender must relay the correct
metadata for the payment to be accepted by the receiver, binding
the metadata to the payment cryptographically.

The metadata is only included in the HMAC when present, so existing
payments without metadata continue to verify correctly. However,
this does break receiving payments with metadata today. On an
upgrade this seems acceptable to me given we have seen almost no
use of payment metadata in practice.

@ldk-reviews-bot
Copy link
Copy Markdown

ldk-reviews-bot commented Mar 31, 2026

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Comment thread pending_changelog/matt-commit-to-metadata.txt Outdated
Comment thread lightning/src/ln/inbound_payment.rs
Comment thread lightning/src/ln/channelmanager.rs
Comment thread lightning/src/ln/channelmanager.rs
@ldk-claude-review-bot
Copy link
Copy Markdown
Collaborator

ldk-claude-review-bot commented Mar 31, 2026

I've completed a thorough review of the entire PR diff. Let me verify my prior inline comment corrections.

My prior review left the following comments:

  1. pending_changelog/matt-commit-to-metadata.txt:2 — typo "comitted". Now resolved — the current text shows "committed".
  2. lightning/src/ln/inbound_payment.rs:182 — claimed None and Some(&[]) are indistinguishable. This was incorrect — the length prefix ((metadata.len() as u64).to_le_bytes()) adds 8 zero bytes for Some(&[]) but nothing for None, so they ARE properly distinguished.
  3. lightning/src/ln/channelmanager.rs:15078 — missing docs for get_payment_preimage's payment_metadata param. Still applies.
  4. lightning/src/ln/channelmanager.rs:14994 — missing docs for create_inbound_payment's payment_metadata param. Still applies.

No new issues found beyond what was already flagged. Here is my summary:

Review Summary

No new issues found. All prior comments have been reviewed for current applicability.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2Resolved. The typo "comitted" has been corrected to "committed".
  • lightning/src/ln/inbound_payment.rs:182Correction: My prior comment about None vs Some(&[]) being cryptographically indistinguishable was incorrect. The length prefix ((metadata.len() as u64).to_le_bytes()) is always included when metadata is Some, even for empty metadata, making them properly distinguishable. The implementation is correct.
  • lightning/src/ln/channelmanager.rs:14994Still applicable. create_inbound_payment docs don't mention payment_metadata.
  • lightning/src/ln/channelmanager.rs:15078Still applicable. get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed here.

Areas verified as correct:

  • HMAC computation is consistent across all four sites: create, create_from_hash, verify/derive_ldk_payment_preimage, and get_payment_preimage.
  • Length-prefixed metadata in HMAC properly distinguishes None from Some(&[]).
  • SpontaneousPayment intentionally does not commit to metadata (no invoice metadata for keysend).
  • MPP parts enforce metadata consistency via check_merge (outbound_payment.rs:909).
  • InvoiceBuilder::payment_metadata() correctly defaults to required; optional_payment_metadata() available for legacy compat.
  • create_bolt11_invoice properly passes metadata to both the HMAC creation and the invoice builder.
  • All callers of modified public APIs updated with the new parameter.
  • BOLT 12 and phantom invoice paths correctly pass None for metadata.

Cross-cutting observations:

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata and later calls get_payment_preimage(..., None), it will silently fail with APIMisuseError. A test here would catch regressions and serve as documentation.

@ldk-reviews-bot
Copy link
Copy Markdown

🔔 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.

Copy link
Copy Markdown
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

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

LGTM aside from CI and one or two of Claude's doc nits

///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting
/// it. It is committed to, however, so cannot be modified by the sender.
pub payment_metadata: Option<Vec<u8>>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: this could've been a separate commit

Comment on lines +14209 to +14210
let raw_invoice = if let Some(payment_metadata) = payment_metadata {
invoice.payment_metadata(payment_metadata).build_raw()
Copy link
Copy Markdown
Contributor

@elnosh elnosh Apr 23, 2026

Choose a reason for hiding this comment

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

check length of payment_metadata and return error if greater than max allowed length of field in invoice?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, lightning-invoice isn't aware of a limit - if there is one we should enforce it everywhere which seems like an orthogonal PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't it be aware of the protocol limit here? https://github.com/TheBlueMatt/rust-lightning/blob/4bf195c5edae74699e9d7a9f598fa99a04679c29/lightning-invoice/src/ser.rs#L427

so passing metadata in the Bolt11InvoiceParameters above this would cause ldk to panic.

	#[test]
	fn test_create_invoice_payment_metadata_too_long() {
		let chanmon_cfgs = create_chanmon_cfgs(2);
		let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
		let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
		let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
		let description = Bolt11InvoiceDescription::Direct(
			Description::new("Some description".to_string()).unwrap(),
		);
		let invoice_params = Bolt11InvoiceParameters {
			amount_msats: Some(10_000),
			description,
			payment_metadata: Some(vec![0; 640]),
			..Default::default()
		};
		let _ = nodes[1].node.create_bolt11_invoice(invoice_params);
	}

@tnull
Copy link
Copy Markdown
Contributor

tnull commented May 6, 2026

@TheBlueMatt any chance to get this into 0.3 still? We'd need it to make lightningdevkit/ldk-node#899 safe, which we want to do given we're now doing #4584 ^^

And, given this PR breaks backwards compat. for payment metadata users, we'll probably want to have the breakage happen before we start using payment metadata in LDK Node (i.e. lightningdevkit/ldk-node#899).

Feel free to object, but for that reason I'm adding this to the 0.3 milestone.

@tnull tnull self-requested a review May 6, 2026 12:33
@tnull tnull added this to the 0.3 milestone May 6, 2026
TheBlueMatt and others added 6 commits May 6, 2026 19:04
`payment_metadata` is a separate concept at the BOLT 11 layer
(similar to payment secret, but arbitrary-sized) and at the BOLT 12
layer, so referring to payment information as "payment metadata" is
confusing. Instead, use simply "payment info".
When payment_metadata is set in a BOLT 11 invoice, users expect to
receive it back as-is in the payment onion. In order to ensure it
isn't tampered with, they presumably will add an HMAC, or worse, not
add one and forget that it can be tampered with.

Instead, here we include it in the HMAC computation for the payment
secret. This ensures that the sender must relay the correct
metadata for the payment to be accepted by the receiver, binding
the metadata to the payment cryptographically.

The metadata is only included in the HMAC when present, so existing
payments without metadata continue to verify correctly. However,
this does break receiving payments with metadata today. On an
upgrade this seems acceptable to me given we have seen almost no
use of payment metadata in practice.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@TheBlueMatt TheBlueMatt force-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9d Compare May 6, 2026 19:30
@TheBlueMatt
Copy link
Copy Markdown
Collaborator Author

Rebased and ~addressed feedback.

Now that we commit to payment metadata fields and require them
implicitly as a part of payments, we should match that in
`lightning-invoice` - instead marking them as required by default.
@TheBlueMatt TheBlueMatt force-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195c Compare May 6, 2026 19:33
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.13%. Comparing base (5455058) to head (4bf195c).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
lightning/src/ln/inbound_payment.rs 95.23% 4 Missing ⚠️
lightning/src/ln/channelmanager.rs 87.50% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4528      +/-   ##
==========================================
+ Coverage   86.11%   86.13%   +0.01%     
==========================================
  Files         157      157              
  Lines      108772   108823      +51     
  Branches   108772   108823      +51     
==========================================
+ Hits        93668    93730      +62     
+ Misses      12487    12478       -9     
+ Partials     2617     2615       -2     
Flag Coverage Δ
tests 86.13% <94.26%> (+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.

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.

6 participants