Skip to content

Add stateless BOLT 12 payer proof support - #1045

Merged
tnull merged 1 commit into
lightningdevkit:mainfrom
vincenzopalazzo:claude/bolt12-payer-proof-stateless
Aug 12, 2026
Merged

Add stateless BOLT 12 payer proof support#1045
tnull merged 1 commit into
lightningdevkit:mainfrom
vincenzopalazzo:claude/bolt12-payer-proof-stateless

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Member

Supersedes #845, reworked per the review feedback there: no payment-store persistence, and the API is stateless so we stay free to change or drop it once the verification side is worked out.

Summary

Exposes Bolt12Payment::create_payer_proof, which builds a BOLT 12 payer proof for a payment this node made:

let proof = node.bolt12_payment().create_payer_proof(
    payment_id,        // from Event::PaymentSuccessful
    payment_preimage,  // from Event::PaymentSuccessful
    paid_invoice,      // Event::PaymentSuccessful::bolt12_invoice
    Some(options),
)?;

PayerProofOptions controls which optional invoice fields are selectively disclosed (offer description, offer issuer, invoice amount, invoice creation time, plus arbitrary extra TLV types) and lets the payer attach a note. A proof always commits to the payer id, payment hash, and issuer signing pubkey.

Nothing is persisted: PaymentKind and the payment store are untouched, so there's no serialization change and no compatibility note. Callers hold on to the invoice themselves if they want to build a proof later.

Payments settled via a static invoice, i.e., async payments, can't be proven this way and are rejected with PayerProofUnavailable.

Why a node-side method at all

It was suggested that, since the invoice is already exposed on Event::PaymentSuccessful, we could skip the API and just document building a proof with LDK's methods directly. That doesn't work today — both LDK entry points need key material LDK Node deliberately doesn't expose:

  • PaidBolt12Invoice::prove_payer_derived(preimage, expanded_key, payment_id, secp) takes an &ExpandedKey.
  • PaidBolt12Invoice::prove_payer(preimage) avoids that, but returns an UnsignedPayerProof that must be signed with the derived payer key, which means Bolt12Invoice::derive_payer_signing_keys(expanded_key, secp)ExpandedKey again.

In LDK Node ExpandedKey is only reachable via KeysManager, which is pub(crate), and Node::sign_message is a node-key BIP137 message signer, so it's no help here. Hence one thin method that supplies the expanded key; everything else is caller-provided.

Happy to expose a lower-level signing hook instead if that's preferred.

Test plan

  • cargo fmt --all -- --check
  • cargo check --lib --tests, cargo check --lib --features uniffi
  • cargo clippy --lib -- -A warnings -D clippy::unwrap_used -A clippy::tabs_in_doc_comments
  • cargo test --lib
  • cargo test --test integration_tests_rust simple_bolt12_send_receive — extended to build a proof from values captured off the event, asserting the preimage, disclosed amount and note, and that undisclosed fields stay absent
  • Python UniFFI bindings generate

Disclosure

This PR was prepared with AI assistance (Claude Code).

🤖 Generated with Claude Code

@ldk-reviews-bot

ldk-reviews-bot commented Aug 12, 2026

Copy link
Copy Markdown

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

@vincenzopalazzo
vincenzopalazzo force-pushed the claude/bolt12-payer-proof-stateless branch from 2f704b6 to 395bd23 Compare August 12, 2026 10:54

@tnull tnull left a comment

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.

One question, otherwise LGTM

Feel free to undraft.

Comment thread src/payment/bolt12.rs Outdated
@tnull tnull added this to the 0.8 milestone Aug 12, 2026
@vincenzopalazzo
vincenzopalazzo force-pushed the claude/bolt12-payer-proof-stateless branch from 395bd23 to aa9f3fb Compare August 12, 2026 11:08
@vincenzopalazzo
vincenzopalazzo marked this pull request as ready for review August 12, 2026 11:09
Comment thread src/ffi/types.rs
Comment thread src/payment/bolt12.rs
Comment thread src/ffi/types.rs
Comment thread src/payment/bolt12.rs Outdated
Comment thread tests/integration_tests_rust.rs
Expose `Bolt12Payment::create_payer_proof`, which builds a BOLT 12 payer
proof for a payment this node made, with `PayerProofOptions` controlling
which optional invoice fields are selectively disclosed.

The method is stateless: the payment id, payment preimage, and paid
invoice are all taken from `Event::PaymentSuccessful` and handed back to
us by the caller, so nothing is read from or written to the payment
store. The node only contributes the expanded key needed to re-derive the
payer signing key, which is the one part users can't supply themselves.

Keeping it stateless means we don't have to decide up front where paid
BOLT 12 invoices should eventually live, and leaves us free to change or
drop this API once the verification side is worked out.

Payments settled via a static invoice, i.e., async payments, can't be
proven this way and are rejected with `PayerProofUnavailable`.

This commit was written with AI assistance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vincenzopalazzo
vincenzopalazzo force-pushed the claude/bolt12-payer-proof-stateless branch from aa9f3fb to d848ff5 Compare August 12, 2026 12:09
@vincenzopalazzo
vincenzopalazzo requested a review from tnull August 12, 2026 12:23

@tnull tnull left a comment

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.

LGTM.

Two non-blocking nits: commit description still mentions PayerProofUnavailable and the commit shows as 'Unverified' here on Github, might want to start signing commits.

@tnull
tnull merged commit 5dfb695 into lightningdevkit:main Aug 12, 2026
22 of 25 checks passed
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