Add stateless BOLT 12 payer proof support - #1045
Merged
tnull merged 1 commit intoAug 12, 2026
Merged
Conversation
|
👋 Thanks for assigning @tnull as a reviewer! |
vincenzopalazzo
force-pushed
the
claude/bolt12-payer-proof-stateless
branch
from
August 12, 2026 10:54
2f704b6 to
395bd23
Compare
tnull
reviewed
Aug 12, 2026
tnull
left a comment
Collaborator
There was a problem hiding this comment.
One question, otherwise LGTM
Feel free to undraft.
vincenzopalazzo
force-pushed
the
claude/bolt12-payer-proof-stateless
branch
from
August 12, 2026 11:08
395bd23 to
aa9f3fb
Compare
vincenzopalazzo
marked this pull request as ready for review
August 12, 2026 11:09
tnull
reviewed
Aug 12, 2026
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
force-pushed
the
claude/bolt12-payer-proof-stateless
branch
from
August 12, 2026 12:09
aa9f3fb to
d848ff5
Compare
tnull
approved these changes
Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:PayerProofOptionscontrols 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:
PaymentKindand 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 anUnsignedPayerProofthat must be signed with the derived payer key, which meansBolt12Invoice::derive_payer_signing_keys(expanded_key, secp)—ExpandedKeyagain.In LDK Node
ExpandedKeyis only reachable viaKeysManager, which ispub(crate), andNode::sign_messageis 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 -- --checkcargo check --lib --tests,cargo check --lib --features unifficargo clippy --lib -- -A warnings -D clippy::unwrap_used -A clippy::tabs_in_doc_commentscargo test --libcargo 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 absentDisclosure
This PR was prepared with AI assistance (Claude Code).
🤖 Generated with Claude Code