Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

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

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

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

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

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

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

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

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Include Nonce in payer_metadata again - #4685

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Jun 15, 2026
Merged

Include Nonce in payer_metadata again#4685
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce

Conversation

@jkczyz

@jkczyzjkczyz commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

InvoiceRequest and Refund payer metadata originally contained a nonce used to derive the payer signing keys and authenticate any corresponding invoices. df5d7ea elided it once it was also included in the OffersContext of blinded reply paths, but that makes verifying a Bolt12Invoice depend on state outside the invoice itself. Upcoming payment proofs need the invoice signing keys derivable from the invoice request alone, so this includes the nonce in the payer metadata again and removes it from the outbound payment OffersContext variants.

The two commits:

  • Include payer nonce in payer metadata again. Verify invoices via Bolt12Invoice::verify_using_metadata rather than the context's nonce, so Bolt12Invoice::verify_using_payer_data is removed.

  • Remove nonce from outbound payment OffersContexts. Drop it from OffersContext::OutboundPaymentForOffer and OffersContext::OutboundPaymentForRefund, along with enqueue_invoice_request's nonce parameter. The payment_id is kept in both variants: it is no longer needed to confirm the invoice is for a request or refund we created, but is checked against the payment id recovered from a received Bolt12Invoice's payer metadata to ensure it arrived over the blinded path created for that payment, preventing an attacker from reusing one payment's blinded path to deliver another's invoice and correlate the two as ours.

Compatibility

Invoices for invoice requests and refunds with blinded paths created by prior versions will no longer verify, since their payer metadata lacks the nonce; such outstanding payments will fail and must be retried with a new payment_id. Refunds without blinded paths are unaffected. Pending RetryableInvoiceRequests still persist the nonce, so payments retried after a downgrade continue to work.

@ldk-reviews-bot

ldk-reviews-bot commented Jun 12, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt 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.

@ldk-claude-review-bot

ldk-claude-review-bot commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Confirmed: nonce is a non-optional field always set in MetadataMaterial::new (signer.rs:234), so appending self.nonce.as_slice() at line 262 is always valid, and derive_metadata_and_keys now matches derive_metadata (line 246) in producing the encrypted_payment_id || nonce [+ hmac] layout. This is the same commit I reviewed previously, and the verification path remains internally consistent.

No issues found.

The PR is unchanged since my prior review pass. I re-verified the core build/verify agreement:

  • derive_metadata_and_keys (signer.rs:262) appends the nonce, matching derive_metadata (signer.rs:246); the nonce field is always populated, so no panic risk.
  • The verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext nonce removal/serialization cleanup, the refund IV-bytes selection in invoice.rs, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent — no dangling references, panics, or logic errors.

Cross-cutting note (non-blocking, acknowledged in the PR description): invoices for invoice-requests/refunds-with-paths created by the prior commit (df5d7ea7b) carry 32-byte payer metadata lacking the nonce, so returning invoices for in-flight payments across an upgrade would fail verify_using_metadata and must be retried with a new payment_id. The downgrade direction is preserved via the retained nonce field.

InvoiceRequest and Refund have payer metadata consisting of an
encrypted payment id and, originally, a nonce used to derive the payer
signing keys and authenticate any corresponding invoices. The nonce was
elided to save space once it was included in the OffersContext of
blinded reply paths, but that means verifying a Bolt12Invoice requires
state outside the invoice itself. Upcoming payment proofs (lightningdevkit#4297) need
the invoice signing keys derivable from the invoice request alone, so
include the nonce in the payer metadata again and verify invoices using
it rather than the context's nonce.
This breaks verification of invoices for invoice requests and refunds
with blinded paths created by prior versions, as their payer metadata
lacks the nonce; such payments will fail and must be retried with a new
payment id. Refunds without blinded paths are unaffected, as their
metadata always included the nonce.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836CompareJune 12, 2026 04:44
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt! 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.

TheBlueMatt
TheBlueMatt previously approved these changes Jun 15, 2026
/// [`InvoiceRequest`] and for deriving its signing keys.
/// Used when handling a received [`Bolt12Invoice`] to confirm it arrived over the reply path
/// created for this payment, rather than one an attacker could use to learn our identity by
/// observing which payment we make. The invoice itself is verified using its payer metadata.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"learn our identity by observing which payment we make" is a bit ambiguous. Maybe mention explicitly "reuse a blinded path from a different payment and correlate payments made by us"?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Updated to be more precise.

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@TheBlueMattTheBlueMatt added this to the 0.3 milestone Jun 15, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Presumed dep of #4297

Now that the payer nonce is included in the payer metadata of
InvoiceRequest and Refund, Bolt12Invoice verification no longer needs
the nonce from the blinded path's OffersContext. Remove it from
OffersContext::OutboundPaymentForOffer and
OffersContext::OutboundPaymentForRefund, along with
enqueue_invoice_request's nonce parameter, which only existed to supply
it. The nonce in RetryableInvoiceRequest is no longer used either but
is still persisted -- and retained when reading state written by prior
versions -- so that such versions can retry the payment and verify the
resulting invoice after a downgrade.
The payment_id is kept in both variants, however. While no longer needed
to confirm the invoice is for an invoice request or refund we created,
it is checked against the payment id recovered from a received
Bolt12Invoice's payer metadata to ensure the invoice arrived over the
blinded path created for that payment. This prevents an attacker from
reusing the blinded path of one of our payments to deliver another
payment's invoice and correlate the two as ours.
Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-06-include-payer-nonce branch from 2f53b8a to b910f8eCompareJune 15, 2026 15:11
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is a quite-straightforward change, just gonna land.

@TheBlueMatt
TheBlueMatt merged commit 85a8cb1 into lightningdevkit:mainJun 15, 2026
1 check passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt