Skip to content

Comments

BOLT 12: first draft of payer proofs#1295

Open
rustyrussell wants to merge 1 commit intolightning:masterfrom
rustyrussell:guilt/payer-proofs
Open

BOLT 12: first draft of payer proofs#1295
rustyrussell wants to merge 1 commit intolightning:masterfrom
rustyrussell:guilt/payer-proofs

Conversation

@rustyrussell
Copy link
Collaborator

Needs:

  1. Implementation.
  2. Test vectors
  3. Review

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Some minor comments from a first pass. Should have more feedback once we attempt to implement this.

* [`bip340sig`:`sig`]
1. type: 242 (`preimage`)
2. data:
* [`32*byte`:`premage`]
Copy link
Contributor

Choose a reason for hiding this comment

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

s/premage/preimage


The non-signature elements of a payer proof are identical to the
`invoice` tlv_stream, with the exception that `invreq_metadata` cannot
be included. Various fields are omitted for privacy: numbers
Copy link
Contributor

Choose a reason for hiding this comment

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

s/are/may be


A writer of a payer_proof:
- MUST NOT include `invreq_metadata`.
- MUST include `invreq_payer_id`, `invoice_payment_hash`, `invoice_node_id`, `signature` and (if present) `invoice_features` from the invoice.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you include a rationale for requiring invoice_features?

- `omitted_tlvs` contains 0.
- `omitted_tlvs` contains signature TLV element number (240 through 1000 inclusive).
- `omitted_tlvs` contains the number of an included TLV field.
- `omitted_tlvs` contains more than one number larger than the largest included non-signature TLV element.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I understand this restriction. Why can't more than one such TLV be omitted? The example contradicts this (41 and 42), IIUC.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops, this was from an earlier draft, and should be removed!


Note that the signature TLV 250 is not included in the merkle tree.

`leaf_hashes` contains the nonce hashes for the present non-signature TLVS:
Copy link
Contributor

Choose a reason for hiding this comment

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

s/TLVS/TLVs

1. type: 250 (`payer_signature`)
2. data:
* [`bip340sig`:`sig`]
* [`...*utf8`:`note`]
Copy link
Contributor

Choose a reason for hiding this comment

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

Presumably, this may be empty?

vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Jan 2, 2026
Implements the payer proof extension to BOLT 12 as specified in
lightning/bolts#1295. This allows proving
that a BOLT 12 invoice was paid by demonstrating possession of the
payment preimage, a valid invoice signature, and a payer signature.

Key additions:
- Extend merkle.rs with selective disclosure primitives for creating
  and reconstructing merkle trees with partial TLV disclosure
- Add payer_proof.rs with PayerProof, PayerProofBuilder, and
  UnsignedPayerProof types for building and verifying payer proofs
- Support bech32 encoding with "lnp" prefix

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@vincenzopalazzo vincenzopalazzo left a comment

Choose a reason for hiding this comment

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

Thanks for this spec! I am finishing to implement it in ldk and have some feedback from implementation experience:

  1. The nonce hash notation needs clarification (see inline comment)
  2. Test vectors would be extremely valuable for cross-implementation compatibility. Wondering if you already had some draft implementation where we can compare the tests vectors?

Happy to provide my implementation's test vectors once the ambiguities are resolved.

- For each non-signature TLV in the invoice in ascending-type order:
- If the field is to be included in the payer_proof:
- MUST copy it into the payer_proof.
- MUST append the nonce (H("LnNonce"||TLV0,type)) to `leaf_hashes`.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: is ambiguous. It could mean:

  • H(H("LnNonce"||TLV0) || H("LnNonce"||TLV0) || type) (tagged hash style), or
  • H("LnNonce" || TLV0 || type) (simple concatenation)

Comment on lines +1108 to +1114
Thus, `missing_hashes` contains the following hashes in left-to-right
order:

1. Merkle of H("LnLeaf",TLV0) and H("LnNonce"||TLV0,0)
2. Merkle of (Merkle of H("LnLeaf",TLV20) and H("LnNonce"||TLV0,20))
and (Merkle of H("LnLeaf",TLV30) and H("LnNonce"||TLV0,30))
3. Merkle of H("LnLeaf",TLV50) and H("LnNonce"||TLV0,50)
Copy link
Contributor

Choose a reason for hiding this comment

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

The example shows only 3 missing_hashes but TLV 60 is also omitted.

Looking at the tree, when the subtree (40, [50]) combines with [60] at level 2, shouldn't we need a 4th hash for 60?

Am I misunderstanding the tree structure, or is this example incomplete?

vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Jan 4, 2026
Implements the payer proof extension to BOLT 12 as specified in
lightning/bolts#1295. This allows proving
that a BOLT 12 invoice was paid by demonstrating possession of the
payment preimage, a valid invoice signature, and a payer signature.

Key additions:
- Extend merkle.rs with selective disclosure primitives for creating
  and reconstructing merkle trees with partial TLV disclosure
- Add payer_proof.rs with PayerProof, PayerProofBuilder, and
  UnsignedPayerProof types for building and verifying payer proofs
- Support bech32 encoding with "lnp" prefix
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Jan 4, 2026
Implements the payer proof extension to BOLT 12 as specified in
lightning/bolts#1295. This allows proving
that a BOLT 12 invoice was paid by demonstrating possession of the
payment preimage, a valid invoice signature, and a payer signature.

Key additions:
- Extend merkle.rs with selective disclosure primitives for creating
  and reconstructing merkle trees with partial TLV disclosure
- Add payer_proof.rs with PayerProof, PayerProofBuilder, and
  UnsignedPayerProof types for building and verifying payer proofs
- Support bech32 encoding with "lnp" prefix
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Jan 4, 2026
Implements the payer proof extension to BOLT 12 as specified in
lightning/bolts#1295. This allows proving
that a BOLT 12 invoice was paid by demonstrating possession of the
payment preimage, a valid invoice signature, and a payer signature.

Key additions:
- Extend merkle.rs with selective disclosure primitives for creating
  and reconstructing merkle trees with partial TLV disclosure
- Add payer_proof.rs with PayerProof, PayerProofBuilder, and
  UnsignedPayerProof types for building and verifying payer proofs
- Support bech32 encoding with "lnp" prefix
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Jan 5, 2026
Implements the payer proof extension to BOLT 12 as specified in
lightning/bolts#1295. This allows proving
that a BOLT 12 invoice was paid by demonstrating possession of the
payment preimage, a valid invoice signature, and a payer signature.

Key additions:
- Extend merkle.rs with selective disclosure primitives for creating
  and reconstructing merkle trees with partial TLV disclosure
- Add payer_proof.rs with PayerProof, PayerProofBuilder, and
  UnsignedPayerProof types for building and verifying payer proofs
- Support bech32 encoding with "lnp" prefix
* [`32*byte`:`premage`]
1. type: 244 (`omitted_tlvs`)
2. data:
* [`...*bigsize`:`missing`]
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should strictly require that these be "minimized" on the read side.

vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Jan 20, 2026
Implements the payer proof extension to BOLT 12 as specified in
lightning/bolts#1295. This allows proving
that a BOLT 12 invoice was paid by demonstrating possession of the
payment preimage, a valid invoice signature, and a payer signature.

Key additions:
- Extend merkle.rs with selective disclosure primitives for creating
  and reconstructing merkle trees with partial TLV disclosure
- Add payer_proof.rs with PayerProof, PayerProofBuilder, and
  UnsignedPayerProof types for building and verifying payer proofs
- Support bech32 encoding with "lnp" prefix
vincenzopalazzo added a commit to vincenzopalazzo/payer-proof-test-vectors that referenced this pull request Jan 20, 2026
Add a Rust CLI tool that generates and verifies test vectors for BOLT 12
payer proofs as specified in lightning/bolts#1295. The tool uses the
rust-lightning implementation from lightningdevkit/rust-lightning#4297.

Features:
- Generate deterministic test vectors with configurable seed
- Verify test vectors from JSON files
- Support for basic proofs, proofs with notes, and invalid test cases
- Uses refund flow for explicit payer key control

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Feb 22, 2026
Implements payer proofs for BOLT 12 invoices as specified in
lightning/bolts#1295.

A PayerProof cryptographically proves that a BOLT 12 invoice was paid
by demonstrating possession of the payment preimage, a valid invoice
signature over a merkle root (via selective disclosure), and a payer
signature proving who authorized the payment.

Key components:
- `PayerProofBuilder`: constructs proofs with selective disclosure,
  supporting both direct signing and derived key signing from
  `ExpandedKey` + `Nonce`
- `PayerProof`: verifiable proof with bech32 encoding (lnp HRP)
- Selective disclosure via merkle tree: omitted TLV fields are
  represented by minimized markers and missing hashes, allowing
  verification of the invoice signature without revealing all fields
- `compute_selective_disclosure` / `reconstruct_merkle_root`: build
  and verify merkle proofs using n-node in-place tree traversal
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Feb 22, 2026
Implements payer proofs for BOLT 12 invoices as specified in
lightning/bolts#1295. A payer proof
cryptographically demonstrates that a BOLT 12 invoice was paid
using selective disclosure of invoice fields, the payment preimage,
and signatures from both the invoice issuer and the payer.
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Feb 22, 2026
Implements payer proofs for BOLT 12 invoices as specified in
lightning/bolts#1295. A payer proof
cryptographically demonstrates that a BOLT 12 invoice was paid
using selective disclosure of invoice fields, the payment preimage,
and signatures from both the invoice issuer and the payer.
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Feb 24, 2026
Implement payer proofs for BOLT 12 invoices as specified in
lightning/bolts#1295. A payer proof
cryptographically demonstrates that a BOLT 12 invoice was paid
using selective disclosure of invoice fields, the payment preimage,
and signatures from both the invoice issuer and the payer.

The selective disclosure mechanism uses a merkle tree over the
invoice's TLV fields, allowing the payer to reveal only chosen
fields while proving the full invoice was signed by the issuer.
Privacy-preserving omitted markers hide the actual TLV type numbers
of undisclosed fields.

PayerProofBuilder provides two signing modes: an explicit signing
function for callers with direct key access, and automatic key
re-derivation from ExpandedKey + Nonce + PaymentId for the common
case where invoice requests used deriving_signing_pubkey.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
vincenzopalazzo added a commit to vincenzopalazzo/rust-lightning that referenced this pull request Feb 24, 2026
Implement payer proofs for BOLT 12 invoices as specified in
lightning/bolts#1295. A payer proof
cryptographically demonstrates that a BOLT 12 invoice was paid
using selective disclosure of invoice fields, the payment preimage,
and signatures from both the invoice issuer and the payer.

The selective disclosure mechanism uses a merkle tree over the
invoice's TLV fields, allowing the payer to reveal only chosen
fields while proving the full invoice was signed by the issuer.
Privacy-preserving omitted markers hide the actual TLV type numbers
of undisclosed fields.

PayerProofBuilder provides two signing modes: an explicit signing
function for callers with direct key access, and automatic key
re-derivation from ExpandedKey + Nonce + PaymentId for the common
case where invoice requests used deriving_signing_pubkey.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

4 participants