Implement bLIP 56: BOLT 12 point-of-sale payment notifications#88
Open
vincenzopalazzo wants to merge 6 commits into
Open
Implement bLIP 56: BOLT 12 point-of-sale payment notifications#88vincenzopalazzo wants to merge 6 commits into
vincenzopalazzo wants to merge 6 commits into
Conversation
bLIP 56 defines a protocol for point-of-sale devices to receive payment notifications from merchants over onion messages. The merchant advertises support by setting the payment_notifications feature bit on the offer templates it hands to point-of-sale devices, and the bit remains set on each per-order offer derived from a template. The bit assignment (264/265) is provisional until the bLIP is merged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per bLIP 56, a point-of-sale device seals the order fields (amount, description, and a per-order secret order_id) into a payment_token encrypted to the offer's signing pubkey, which only the merchant can recover the secret key for. The merchant verifies the sealed amount and description against the mirrored offer at invoice-request time and relays order_id in its payment notification, which is what authenticates the notification to the device. The token uses a fresh ephemeral key per order, so ciphertexts cannot be linked to the merchant key across offers, with ChaCha20Poly1305 keyed by the ECDH shared secret providing authenticated encryption. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the notification_path and payment_token offer records as odd experimental TLVs (1000005601 and 1000005603, provisional) so that wallets unaware of them still accept the offer and mirror the records into the invoice_request, and an OfferModifier allowing a point-of-sale device to construct a per-order offer from a merchant template by filling in the amount, description, expiry, notification paths, and an encrypted payment token. Since the device modifies the offer's records, the merchant cannot authenticate corresponding invoice requests via offer metadata, which covers the entire TLV stream. Instead, a template's signing keys are derived from a nonce alone and matched against the mirrored offer's signing pubkey when verifying an invoice request for delegation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the payment_notification, notification_ack, notification_nack, and payment_proof onion messages (TLV types 65560 through 65566, provisional) along with a PosNotificationMessageHandler wired through the OnionMessenger, and a PosNotificationContext for the blinded paths carrying them. A per-order offer's notification_path carries the full order state in its message context, authenticated by the path itself, so the point-of-sale device needs no persistent order state to validate incoming notifications. The merchant's reply path context identifies the pending notification an ack or nack refers to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
On the merchant side, an invoice request arriving over a template's DelegatedInvoiceRequest context is verified against the offer's payment_token, rejecting it if the token or notification_path is missing, fails to decrypt, or its sealed amount or description do not match the mirrored offer. The recovered order_id, the notification paths, and the order expiry are embedded in the payment context's payment_metadata, using keys from the LDK-reserved range. When the payment is claimed, a payment_notification is queued to the device over the offer's notification_path and retried on timer ticks until acknowledged or the order's expiry plus a grace period has passed, persisting across restarts. On the point-of-sale side, notifications are validated against the notification path's message context per the bLIP 56 acceptance rules and answered with an ack or a reason-coded nack. Authenticated notifications surface as Event::PaymentNotificationReceived, and customer payment proofs as Event::PaymentProofReceived, left unverified for the consumer until BOLT 12 payer proofs are available. ChannelManager grows create_pos_delegation_template, create_pos_order_offer, and send_payment_proof to drive the three roles of the protocol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the full customer/merchant/PoS flow: template creation, per-order offer construction, payment by an unmodified wallet, notification delivery, authentication, acknowledgement, retry, and duplicate handling. Also covers rejection of forged notifications, each nack reason code, expired orders, and invoice requests for bare templates or offers whose cleartext fields do not match the sealed payment token, as well as payment proof delivery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
vincenzopalazzo
commented
Jun 10, 2026
Comment on lines
+880
to
+893
| /// Verifies that the request was for a point-of-sale offer template created using the given | ||
| /// key, by re-deriving the template's signing keys from the nonce included with the | ||
| /// [`BlindedMessagePath`] for which the request was sent through and matching them against | ||
| /// the offer's signing pubkey. | ||
| /// | ||
| /// Unlike [`Self::verify_using_recipient_data`], the offer's TLV records are not covered by | ||
| /// the verification, since a point-of-sale device modifies them when constructing per-order | ||
| /// offers from the template. The nonce is authenticated by the blinded path itself. | ||
| /// | ||
| /// [`BlindedMessagePath`]: crate::blinded_path::message::BlindedMessagePath | ||
| #[rustfmt::skip] | ||
| pub fn verify_for_delegation( | ||
| $self: $self_type, nonce: Nonce, key: &ExpandedKey, | ||
| ) -> Result<InvoiceRequestVerifiedFromOffer, ()> { |
Owner
Author
There was a problem hiding this comment.
why this is skipping the formatting?
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.
Full implementation of bLIP 56 on top of upstream
main(c9260ee), replacing the stalemacros/pos-bolt12-delegationbranch.What's implemented
payment_notificationsfeature bit 264/265 (provisional)notification_path= 1000005601 andpayment_token= 1000005603, odd experimental TLVs so unmodified wallets accept and mirror thempayment_notification/notification_ack/notification_nack/payment_proof= 65560/65562/65564/65566 (provisional), newPosNotificationMessageHandlerthreaded throughOnionMessenger(amount, description, order_id)TLV-encodedderive_keys_for_delegation),OffersContext::DelegatedInvoiceRequestverification, token checks per the feature-bit rules, order data embedded inpayment_metadata(keys 128-130, the LDK-reserved range), notification retry queue persisted across restarts, abandoned after offer expiry + gracePosNotificationContext::InboundOrder(authenticated by the path-levelReceiveAuthKeyMAC), ack/nack with reason codes,Event::PaymentNotificationReceived/Event::PaymentProofReceivedChannelManagerAPIs:create_pos_delegation_template,create_pos_order_offer,send_payment_proofTests
ln/pos_notification_tests.rscovers the full customer/merchant/PoS flow end to end (template, per-order offer, payment by an unmodified wallet, notification, ack, retry, duplicates), forged notifications, every nack reason, expired orders, bare-template and token-mismatch rejection at invoice-request time, and payment proof delivery. Full suite green on rustc 1.75.0 including doctests; each commit compiles standalone with tests.Spec deltas to fold back into the bLIP
invalid_preimage = 4(the bLIP lists four codes but step 6 requires a preimage check with a nack on failure)payment_proof) is plumbing only: the proof is carried opaquely and surfaced unverified, since BOLT 12 payer proofs (bolts#1295 / LDK [RFC] Add BOLT 12 payer proof primitives lightningdevkit/rust-lightning#4297) have not landed on mainThe PR base is
blip56-base(= upstreammainc9260ee) since the fork'smainhas diverged; the diff is exactly the six commits.🤖 Generated with Claude Code
Co-Authored-By: Claude Fable 5 noreply@anthropic.com