Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Commit to payment_metadata in inbound payment HMAC by TheBlueMatt · Pull Request #4528 · lightningdevkit/rust-lightning · GitHub
Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Commit to payment_metadata in inbound payment HMAC by TheBlueMatt · Pull Request #4528 · lightningdevkit/rust-lightning · GitHub
Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Commit to payment_metadata in inbound payment HMAC by TheBlueMatt · Pull Request #4528 · lightningdevkit/rust-lightning · GitHub
Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Commit to payment_metadata in inbound payment HMAC by TheBlueMatt · Pull Request #4528 · lightningdevkit/rust-lightning · GitHub
Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Commit to payment_metadata in inbound payment HMAC by TheBlueMatt · Pull Request #4528 · lightningdevkit/rust-lightning · GitHub
Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Commit to payment_metadata in inbound payment HMAC by TheBlueMatt · Pull Request #4528 · lightningdevkit/rust-lightning · GitHub
Skip to content

Commit to payment_metadata in inbound payment HMAC - #4528

Merged
TheBlueMatt merged 3 commits into
lightningdevkit:mainfrom
TheBlueMatt:2026-03-commit-to-metadata
May 13, 2026
Merged

Commit to payment_metadata in inbound payment HMAC#4528
TheBlueMatt merged 3 commits into
lightningdevkit: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

ldk-reviews-bot commented Mar 31, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @valentinewallace 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 threadpending_changelog/matt-commit-to-metadata.txt Outdated
Comment threadlightning/src/ln/inbound_payment.rs
Comment threadlightning/src/ln/channelmanager.rs
Comment threadlightning/src/ln/channelmanager.rs
@ldk-claude-review-bot

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

Copy link
Copy Markdown
Collaborator

All call sites are accounted for and updated. I've thoroughly reviewed the entire PR diff. My prior review comments cover the substantive issues. Let me verify the changelog is fixed now.

The changelog has "committed" (not "comitted"), so that prior comment is resolved.

I have no new issues to report beyond what was already covered in my prior review pass. The HMAC computation is consistent across all code paths, all API callers are updated, and the test coverage is reasonable (though as noted previously, get_payment_preimage with non-None metadata lacks a dedicated test).

No new issues found beyond what was already flagged in prior review passes.

Prior comments status:

  • pending_changelog/matt-commit-to-metadata.txt:2 — Resolved (typo fixed).
  • lightning/src/ln/inbound_payment.rs:182 — Retracted (length prefix properly distinguishes None from Some(&[])).
  • lightning/src/ln/channelmanager.rs:14983 — Still applicable: create_inbound_payment docs don't mention the new payment_metadata parameter.
  • lightning/src/ln/channelmanager.rs:15067 — Still applicable: get_payment_preimage docs don't explain that the same payment_metadata used at creation time must be passed.

Cross-cutting concern (still applicable from prior review):

  • No test exercises get_payment_preimage with non-None metadata. If a user creates a payment with metadata via create_inbound_payment(..., Some(&metadata)) and later calls get_payment_preimage(hash, secret, None), it will silently return an APIMisuseError because the derived preimage won't match. A test would catch regressions and serve as documentation of this invariant.

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

@valentinewallacevalentinewallace left a comment

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.

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()

@elnoshelnoshApr 23, 2026

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.

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

Copy link
Copy Markdown
CollaboratorAuthor

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);
}

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.

somewhat late but shouldn't we check this? metadata passed should be small but it could still panic if the payment_metadata passed is above the limit - we also check it in the description

pubfnnew(description:String) -> Result<Description,CreationError>{
if description.len() > 639{
Err(CreationError::DescriptionTooLong)
}else{
Ok(Description(UntrustedString(description)))
}
}

@tnull

tnull commented May 6, 2026

Copy link
Copy Markdown
Contributor

@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
@tnulltnull added this to the 0.3 milestone May 6, 2026
@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch 4 times, most recently from cff21e1 to 971de9dCompareMay 6, 2026 19:30
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and ~addressed feedback.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 971de9d to 4bf195cCompareMay 6, 2026 19:33
@codecov

codecovBot commented May 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.26230% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (946ee09) to head (44828f7).
⚠️ Report is 7 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/inbound_payment.rs95.23%4 Missing ⚠️
lightning/src/ln/channelmanager.rs87.50%1 Missing and 2 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4528 +/- ##
==========================================
+ Coverage 86.11% 86.12% +0.01% 
==========================================
Files 157 157 Lines 108841 108922 +81 Branches 108841 108922 +81 ==========================================
+ Hits 93725 93812 +87 + Misses 12497 12495 -2 + Partials 2619 2615 -4 
FlagCoverage Δ
tests86.12% <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.

@tnulltnull left a comment

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.

Changes look good, feel free to squash.

const INFO_KEY_LEN: usize = 32;
const AMT_MSAT_LEN: usize = 8;
// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to
// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to

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.

It seems @jkczyz might have an opinion here. IIRC he's of the opinion 'everything is information, so naming something "info" doesn't add anything'.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yea, I don't love calling it just "info", I'm definitely open to better names. "metadata" is obviously out as ambiguous, but I don't have strong feelings at all.

/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
/// [`Event::PaymentClaimable::onion_fields`].
///
/// Note that because it is exposed to the sender in the invoice you should consider encrypting

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.

Utilities for encryption will be part of lightningdevkit/ldk-node#899, but I do wonder if we should maybe offer something similar upstream?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I mean without needing authentication its just "ChaCha it"? Not sure we need a utility to call chacha.

@TheBlueMatt
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from 4bf195c to ee26f5cCompareMay 11, 2026 00:08
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased and squashed.

TheBlueMattand others added 3 commits May 11, 2026 00:13
`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>
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
TheBlueMattforce-pushed the 2026-03-commit-to-metadata branch from ee26f5c to 44828f7CompareMay 11, 2026 00:13
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna land this, @tnull confirmed offline its at least good enough even if we have followups later.

@TheBlueMatt
TheBlueMatt merged commit 1ab220b into lightningdevkit:mainMay 13, 2026
37 of 41 checks passed

@tnulltnull left a comment

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.

Post-merge ACK

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@TheBlueMatt@ldk-reviews-bot@ldk-claude-review-bot@tnull@valentinewallace@elnosh