@@ -18,10 +18,14 @@ use lightning::ln::channelmanager::{OptionalOfferPaymentParams, PaymentId};
1818use lightning:: ln:: outbound_payment:: Retry ;
1919use lightning:: offers:: offer:: { Amount , Offer as LdkOffer , OfferFromHrn , Quantity } ;
2020use lightning:: offers:: parse:: Bolt12SemanticError ;
21+ use lightning:: offers:: payer_proof:: PaidBolt12Invoice as LdkPaidBolt12Invoice ;
22+ #[ cfg( not( feature = "uniffi" ) ) ]
23+ use lightning:: offers:: payer_proof:: PayerProof as LdkPayerProof ;
2124use lightning:: routing:: router:: RouteParametersConfig ;
22- use lightning:: sign:: EntropySource ;
25+ use lightning:: sign:: { EntropySource , NodeSigner } ;
2326#[ cfg( feature = "uniffi" ) ]
2427use lightning:: util:: ser:: { Readable , Writeable } ;
28+ use lightning_types:: payment:: PaymentPreimage ;
2529use lightning_types:: string:: UntrustedString ;
2630
2731use crate :: config:: { AsyncPaymentsRole , Config , LDK_PAYMENT_RETRY_TIMEOUT } ;
@@ -52,6 +56,11 @@ type HumanReadableName = lightning::onion_message::dns_resolution::HumanReadable
5256#[ cfg( feature = "uniffi" ) ]
5357type HumanReadableName = Arc < crate :: ffi:: HumanReadableName > ;
5458
59+ #[ cfg( not( feature = "uniffi" ) ) ]
60+ type PayerProof = LdkPayerProof ;
61+ #[ cfg( feature = "uniffi" ) ]
62+ type PayerProof = Arc < crate :: ffi:: PayerProof > ;
63+
5564/// A payment handler allowing to create and pay [BOLT 12] offers and refunds.
5665///
5766/// Should be retrieved by calling [`Node::bolt12_payment`].
@@ -70,6 +79,24 @@ pub struct Bolt12Payment {
7079 async_payments_role : Option < AsyncPaymentsRole > ,
7180}
7281
82+ /// Options controlling which optional fields are disclosed in a BOLT12 payer proof.
83+ #[ derive( Clone , Debug , PartialEq , Eq , Default ) ]
84+ #[ cfg_attr( feature = "uniffi" , derive( uniffi:: Record ) ) ]
85+ pub struct PayerProofOptions {
86+ /// An optional note attached to the payer proof itself.
87+ pub note : Option < String > ,
88+ /// Whether to include the offer description in the proof.
89+ pub include_offer_description : bool ,
90+ /// Whether to include the offer issuer in the proof.
91+ pub include_offer_issuer : bool ,
92+ /// Whether to include the invoice amount in the proof.
93+ pub include_invoice_amount : bool ,
94+ /// Whether to include the invoice creation timestamp in the proof.
95+ pub include_invoice_created_at : bool ,
96+ /// Additional TLV types to include in the selective disclosure set.
97+ pub extra_tlv_types : Vec < u64 > ,
98+ }
99+
73100impl Bolt12Payment {
74101 pub ( crate ) fn new (
75102 runtime : Arc < Runtime > , channel_manager : Arc < ChannelManager > ,
@@ -253,6 +280,33 @@ impl Bolt12Payment {
253280 . blinded_paths_for_async_recipient ( recipient_id, None )
254281 . or ( Err ( Error :: InvalidBlindedPaths ) )
255282 }
283+
284+ /// Retrieves the persisted payer proof context, i.e., the paid BOLT 12 invoice and the payment
285+ /// preimage, for a successful outbound BOLT 12 payment.
286+ fn payer_proof_context (
287+ & self , payment_id : & PaymentId ,
288+ ) -> Result < ( LdkPaidBolt12Invoice , PaymentPreimage ) , Error > {
289+ let payment = self . payment_store . get ( payment_id) . ok_or ( Error :: PayerProofUnavailable ) ?;
290+ if payment. direction != PaymentDirection :: Outbound
291+ || payment. status != PaymentStatus :: Succeeded
292+ {
293+ return Err ( Error :: PayerProofUnavailable ) ;
294+ }
295+
296+ match payment. kind {
297+ PaymentKind :: Bolt12Offer {
298+ preimage : Some ( preimage) ,
299+ bolt12_invoice : Some ( invoice) ,
300+ ..
301+ }
302+ | PaymentKind :: Bolt12Refund {
303+ preimage : Some ( preimage) ,
304+ bolt12_invoice : Some ( invoice) ,
305+ ..
306+ } => Ok ( ( invoice. into ( ) , preimage) ) ,
307+ _ => Err ( Error :: PayerProofUnavailable ) ,
308+ }
309+ }
256310}
257311
258312#[ cfg_attr( feature = "uniffi" , uniffi:: export) ]
@@ -393,6 +447,70 @@ impl Bolt12Payment {
393447 Ok ( payment_id)
394448 }
395449
450+ /// Create a payer proof for a previously succeeded outbound BOLT 12 payment.
451+ ///
452+ /// This requires a standard BOLT 12 invoice response, which we persist alongside the payment
453+ /// in the payment store. Payments that completed via a static invoice, i.e., async payments,
454+ /// do not support payer proofs.
455+ pub fn create_payer_proof (
456+ & self , payment_id : & PaymentId , options : Option < PayerProofOptions > ,
457+ ) -> Result < PayerProof , Error > {
458+ let ( paid_invoice, preimage) = self . payer_proof_context ( payment_id) ?;
459+
460+ let options = options. unwrap_or_default ( ) ;
461+ let expanded_key = self . keys_manager . get_expanded_key ( ) ;
462+ let secp_ctx = bitcoin:: secp256k1:: Secp256k1 :: new ( ) ;
463+
464+ let mut builder = paid_invoice
465+ . prove_payer_derived ( preimage, & expanded_key, * payment_id, & secp_ctx)
466+ . map_err ( |e| {
467+ log_error ! (
468+ self . logger,
469+ "Failed to initialize payer proof builder for {}: {:?}" ,
470+ payment_id,
471+ e
472+ ) ;
473+ Error :: PayerProofCreationFailed
474+ } ) ?;
475+
476+ for tlv_type in options. extra_tlv_types {
477+ builder = builder. include_type ( tlv_type) . map_err ( |e| {
478+ log_error ! (
479+ self . logger,
480+ "Failed to include TLV {} in payer proof for {}: {:?}" ,
481+ tlv_type,
482+ payment_id,
483+ e
484+ ) ;
485+ Error :: PayerProofCreationFailed
486+ } ) ?;
487+ }
488+
489+ if options. include_offer_description {
490+ builder = builder. include_offer_description ( ) ;
491+ }
492+ if options. include_offer_issuer {
493+ builder = builder. include_offer_issuer ( ) ;
494+ }
495+ if options. include_invoice_amount {
496+ builder = builder. include_invoice_amount ( ) ;
497+ }
498+ if options. include_invoice_created_at {
499+ builder = builder. include_invoice_created_at ( ) ;
500+ }
501+
502+ if let Some ( note) = options. note {
503+ builder = builder. with_proof_note ( note) ;
504+ }
505+
506+ let proof = builder. build_and_sign ( ) . map_err ( |e| {
507+ log_error ! ( self . logger, "Failed to build payer proof for {}: {:?}" , payment_id, e) ;
508+ Error :: PayerProofCreationFailed
509+ } ) ?;
510+
511+ Ok ( maybe_wrap ( proof) )
512+ }
513+
396514 /// Returns a payable offer that can be used to request and receive a payment of the amount
397515 /// given.
398516 pub fn receive (
0 commit comments