Skip to content

Include Nonce in payer_metadata again#4685

Open
jkczyz wants to merge 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-06-include-payer-nonce
Open

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

Conversation

@jkczyz

@jkczyz jkczyz 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 verify the invoice, but is still used when handling a received Bolt12Invoice to confirm it arrived over the reply path created for that payment, preventing a payee from de-anonymizing us by minting invoices and delivering them to candidate nodes over other paths to observe which one we pay.

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

Consistent with my prior analysis. The slicing at line 326 is safe since it's only reached after verify_metadata confirms the length. The PR has not changed since my previous thorough review and no new concrete issues are present.

No issues found.

I re-verified the critical verification path (signer::verify_payer_metadata) and confirmed it remains internally consistent with the metadata format change (encrypted_payment_id || nonce). The IV-byte selection, derives_payer_keys() agreement between build and verify, the verify_using_metadata + extracted == payment_id substitution in flow.rs, the OffersContext/serialization cleanup, and RetryableInvoiceRequest.nonce: Option<Nonce> are all consistent with no dangling references or panics.

Cross-cutting note (non-blocking, for maintainer confirmation only): invoice requests created by the prior commit (df5d7ea7b) carry 32-byte payer metadata without the nonce. If such a request is still in flight across an upgrade, the new verify_using_metadata path would reject the returning invoice. The downgrade direction is handled via the retained nonce field, and this format appears to have been unreleased, so this is likely acceptable.

jkczyz and others added 2 commits June 11, 2026 23:43
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>
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 verify the invoice, it is still used when handling a received
Bolt12Invoice to confirm the invoice arrived over the reply path created
for that payment. This prevents a payee from de-anonymizing us by minting
invoices and delivering them to candidate nodes over other paths to
observe which one we pay.

Co-Authored-By: Claude <noreply@anthropic.com>
@jkczyz jkczyz force-pushed the 2026-06-include-payer-nonce branch from bd796d0 to bc88836 Compare June 12, 2026 04:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants