BOLT 12 variants of PaymentPurpose
authorJeffrey Czyz <jkczyz@gmail.com>
Sat, 13 Apr 2024 22:29:24 +0000 (17:29 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Thu, 18 Apr 2024 14:15:22 +0000 (09:15 -0500)
In order to provide more context in PaymentClaimable and PaymentClaimed
events, introduce new variants of PaymentPurpose for use with BOLT 12
payments. Separate variants are used for offers and refunds. An unknown
variant is used for backwards compatibility and ease of testing, but is
otherwise not publicly constructable.

lightning/src/blinded_path/payment.rs
lightning/src/events/mod.rs
lightning/src/ln/channelmanager.rs
lightning/src/ln/functional_test_utils.rs

index ad7b229b53fa1c830d042d24a892360666bf5b65..e243d478bd91621bdcb811f98420c7de194a40e3 100644 (file)
@@ -121,6 +121,12 @@ pub enum PaymentContext {
        Bolt12Refund(Bolt12RefundContext),
 }
 
+// Used when writing PaymentContext in Event::PaymentClaimable to avoid cloning.
+pub(crate) enum PaymentContextRef<'a> {
+       Bolt12Offer(&'a Bolt12OfferContext),
+       Bolt12Refund(&'a Bolt12RefundContext),
+}
+
 /// An unknown payment context.
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct UnknownPaymentContext(());
@@ -372,6 +378,23 @@ impl_writeable_tlv_based_enum!(PaymentContext,
        (2, Bolt12Refund),
 );
 
+impl<'a> Writeable for PaymentContextRef<'a> {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               match self {
+                       PaymentContextRef::Bolt12Offer(context) => {
+                               1u8.write(w)?;
+                               context.write(w)?;
+                       },
+                       PaymentContextRef::Bolt12Refund(context) => {
+                               2u8.write(w)?;
+                               context.write(w)?;
+                       },
+               }
+
+               Ok(())
+       }
+}
+
 impl Writeable for UnknownPaymentContext {
        fn write<W: Writer>(&self, _w: &mut W) -> Result<(), io::Error> {
                Ok(())
index 41ba17806d35b5c5bf31cdb51423775a7d8120cd..94df71b69eaab51e2f074a6b81d30902a0b002f4 100644 (file)
@@ -18,6 +18,7 @@ pub mod bump_transaction;
 
 pub use bump_transaction::BumpTransactionEvent;
 
+use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext, PaymentContextRef};
 use crate::sign::SpendableOutputDescriptor;
 use crate::ln::channelmanager::{InterceptId, PaymentId, RecipientOnionFields};
 use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS;
@@ -70,6 +71,46 @@ pub enum PaymentPurpose {
                /// [`ChannelManager::create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash
                payment_secret: PaymentSecret,
        },
+       /// A payment for a BOLT 12 [`Offer`].
+       ///
+       /// [`Offer`]: crate::offers::offer::Offer
+       Bolt12OfferPayment {
+               /// The preimage to the payment hash. If provided, this can be handed directly to
+               /// [`ChannelManager::claim_funds`].
+               ///
+               /// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
+               payment_preimage: Option<PaymentPreimage>,
+               /// The secret used to authenticate the sender to the recipient, preventing a number of
+               /// de-anonymization attacks while routing a payment.
+               ///
+               /// See [`PaymentPurpose::Bolt11InvoicePayment::payment_secret`] for further details.
+               payment_secret: PaymentSecret,
+               /// The context of the payment such as information about the corresponding [`Offer`] and
+               /// [`InvoiceRequest`].
+               ///
+               /// [`Offer`]: crate::offers::offer::Offer
+               /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+               payment_context: Bolt12OfferContext,
+       },
+       /// A payment for a BOLT 12 [`Refund`].
+       ///
+       /// [`Refund`]: crate::offers::refund::Refund
+       Bolt12RefundPayment {
+               /// The preimage to the payment hash. If provided, this can be handed directly to
+               /// [`ChannelManager::claim_funds`].
+               ///
+               /// [`ChannelManager::claim_funds`]: crate::ln::channelmanager::ChannelManager::claim_funds
+               payment_preimage: Option<PaymentPreimage>,
+               /// The secret used to authenticate the sender to the recipient, preventing a number of
+               /// de-anonymization attacks while routing a payment.
+               ///
+               /// See [`PaymentPurpose::Bolt11InvoicePayment::payment_secret`] for further details.
+               payment_secret: PaymentSecret,
+               /// The context of the payment such as information about the corresponding [`Refund`].
+               ///
+               /// [`Refund`]: crate::offers::refund::Refund
+               payment_context: Bolt12RefundContext,
+       },
        /// Because this is a spontaneous payment, the payer generated their own preimage rather than us
        /// (the payee) providing a preimage.
        SpontaneousPayment(PaymentPreimage),
@@ -80,6 +121,8 @@ impl PaymentPurpose {
        pub fn preimage(&self) -> Option<PaymentPreimage> {
                match self {
                        PaymentPurpose::Bolt11InvoicePayment { payment_preimage, .. } => *payment_preimage,
+                       PaymentPurpose::Bolt12OfferPayment { payment_preimage, .. } => *payment_preimage,
+                       PaymentPurpose::Bolt12RefundPayment { payment_preimage, .. } => *payment_preimage,
                        PaymentPurpose::SpontaneousPayment(preimage) => Some(*preimage),
                }
        }
@@ -87,6 +130,8 @@ impl PaymentPurpose {
        pub(crate) fn is_keysend(&self) -> bool {
                match self {
                        PaymentPurpose::Bolt11InvoicePayment { .. } => false,
+                       PaymentPurpose::Bolt12OfferPayment { .. } => false,
+                       PaymentPurpose::Bolt12RefundPayment { .. } => false,
                        PaymentPurpose::SpontaneousPayment(..) => true,
                }
        }
@@ -96,7 +141,18 @@ impl_writeable_tlv_based_enum!(PaymentPurpose,
        (0, Bolt11InvoicePayment) => {
                (0, payment_preimage, option),
                (2, payment_secret, required),
-       };
+       },
+       (4, Bolt12OfferPayment) => {
+               (0, payment_preimage, option),
+               (2, payment_secret, required),
+               (4, payment_context, required),
+       },
+       (6, Bolt12RefundPayment) => {
+               (0, payment_preimage, option),
+               (2, payment_secret, required),
+               (4, payment_context, required),
+       },
+       ;
        (2, SpontaneousPayment)
 );
 
@@ -1065,6 +1121,7 @@ impl Writeable for Event {
                                1u8.write(writer)?;
                                let mut payment_secret = None;
                                let payment_preimage;
+                               let mut payment_context = None;
                                match &purpose {
                                        PaymentPurpose::Bolt11InvoicePayment {
                                                payment_preimage: preimage, payment_secret: secret
@@ -1072,6 +1129,20 @@ impl Writeable for Event {
                                                payment_secret = Some(secret);
                                                payment_preimage = *preimage;
                                        },
+                                       PaymentPurpose::Bolt12OfferPayment {
+                                               payment_preimage: preimage, payment_secret: secret, payment_context: context
+                                       } => {
+                                               payment_secret = Some(secret);
+                                               payment_preimage = *preimage;
+                                               payment_context = Some(PaymentContextRef::Bolt12Offer(context));
+                                       },
+                                       PaymentPurpose::Bolt12RefundPayment {
+                                               payment_preimage: preimage, payment_secret: secret, payment_context: context
+                                       } => {
+                                               payment_secret = Some(secret);
+                                               payment_preimage = *preimage;
+                                               payment_context = Some(PaymentContextRef::Bolt12Refund(context));
+                                       },
                                        PaymentPurpose::SpontaneousPayment(preimage) => {
                                                payment_preimage = Some(*preimage);
                                        }
@@ -1090,6 +1161,7 @@ impl Writeable for Event {
                                        (8, payment_preimage, option),
                                        (9, onion_fields, option),
                                        (10, skimmed_fee_opt, option),
+                                       (11, payment_context, option),
                                });
                        },
                        &Event::PaymentSent { ref payment_id, ref payment_preimage, ref payment_hash, ref fee_paid_msat } => {
@@ -1320,6 +1392,7 @@ impl MaybeReadable for Event {
                                        let mut claim_deadline = None;
                                        let mut via_user_channel_id = None;
                                        let mut onion_fields = None;
+                                       let mut payment_context = None;
                                        read_tlv_fields!(reader, {
                                                (0, payment_hash, required),
                                                (1, receiver_node_id, option),
@@ -1332,11 +1405,30 @@ impl MaybeReadable for Event {
                                                (8, payment_preimage, option),
                                                (9, onion_fields, option),
                                                (10, counterparty_skimmed_fee_msat_opt, option),
+                                               (11, payment_context, option),
                                        });
                                        let purpose = match payment_secret {
-                                               Some(secret) => PaymentPurpose::Bolt11InvoicePayment {
-                                                       payment_preimage,
-                                                       payment_secret: secret
+                                               Some(secret) => match payment_context {
+                                                       Some(PaymentContext::Unknown(_)) | None => {
+                                                               PaymentPurpose::Bolt11InvoicePayment {
+                                                                       payment_preimage,
+                                                                       payment_secret: secret,
+                                                               }
+                                                       },
+                                                       Some(PaymentContext::Bolt12Offer(context)) => {
+                                                               PaymentPurpose::Bolt12OfferPayment {
+                                                                       payment_preimage,
+                                                                       payment_secret: secret,
+                                                                       payment_context: context,
+                                                               }
+                                                       },
+                                                       Some(PaymentContext::Bolt12Refund(context)) => {
+                                                               PaymentPurpose::Bolt12RefundPayment {
+                                                                       payment_preimage,
+                                                                       payment_secret: secret,
+                                                                       payment_context: context,
+                                                               }
+                                                       },
                                                },
                                                None if payment_preimage.is_some() => PaymentPurpose::SpontaneousPayment(payment_preimage.unwrap()),
                                                None => return Err(msgs::DecodeError::InvalidValue),
index 0693f3c642ea735048d4c119d01754769c89514e..4b120748ad80aef53d0adc20bfa93665caf322b2 100644 (file)
@@ -1477,6 +1477,8 @@ where
 ///             println!("Claiming spontaneous payment {}", payment_hash);
 ///             channel_manager.claim_funds(payment_preimage);
 ///         },
+///         // ...
+/// #         _ => {},
 ///     },
 ///     Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
 ///         assert_eq!(payment_hash, known_payment_hash);
@@ -8877,10 +8879,9 @@ where
        /// This differs from [`create_inbound_payment_for_hash`] only in that it generates the
        /// [`PaymentHash`] and [`PaymentPreimage`] for you.
        ///
-       /// The [`PaymentPreimage`] will ultimately be returned to you in the [`PaymentClaimable`], which
-       /// will have the [`PaymentClaimable::purpose`] be [`PaymentPurpose::Bolt11InvoicePayment`] with
-       /// its [`PaymentPurpose::Bolt11InvoicePayment::payment_preimage`] field filled in. That should
-       /// then be passed directly to [`claim_funds`].
+       /// The [`PaymentPreimage`] will ultimately be returned to you in the [`PaymentClaimable`] event, which
+       /// will have the [`PaymentClaimable::purpose`] return `Some` for [`PaymentPurpose::preimage`]. That
+       /// should then be passed directly to [`claim_funds`].
        ///
        /// See [`create_inbound_payment_for_hash`] for detailed documentation on behavior and requirements.
        ///
@@ -8900,8 +8901,7 @@ where
        /// [`claim_funds`]: Self::claim_funds
        /// [`PaymentClaimable`]: events::Event::PaymentClaimable
        /// [`PaymentClaimable::purpose`]: events::Event::PaymentClaimable::purpose
-       /// [`PaymentPurpose::Bolt11InvoicePayment`]: events::PaymentPurpose::Bolt11InvoicePayment
-       /// [`PaymentPurpose::Bolt11InvoicePayment::payment_preimage`]: events::PaymentPurpose::Bolt11InvoicePayment::payment_preimage
+       /// [`PaymentPurpose::preimage`]: events::PaymentPurpose::preimage
        /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
        pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32,
                min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()> {
index 35dbfd419a164fcd933276dabc3f8e26477846d6..cf52d946e34e0832e640b9d175dc1e98fff34cf4 100644 (file)
@@ -2133,6 +2133,14 @@ pub fn check_payment_claimable(
                                        assert_eq!(&expected_payment_preimage, payment_preimage);
                                        assert_eq!(expected_payment_secret, *payment_secret);
                                },
+                               PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, .. } => {
+                                       assert_eq!(&expected_payment_preimage, payment_preimage);
+                                       assert_eq!(expected_payment_secret, *payment_secret);
+                               },
+                               PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, .. } => {
+                                       assert_eq!(&expected_payment_preimage, payment_preimage);
+                                       assert_eq!(expected_payment_secret, *payment_secret);
+                               },
                                _ => {},
                        }
                },
@@ -2611,6 +2619,16 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event>
                                                                assert_eq!(our_payment_secret.unwrap(), *payment_secret);
                                                                assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
                                                        },
+                                                       PaymentPurpose::Bolt12OfferPayment { payment_preimage, payment_secret, .. } => {
+                                                               assert_eq!(expected_preimage, *payment_preimage);
+                                                               assert_eq!(our_payment_secret.unwrap(), *payment_secret);
+                                                               assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
+                                                       },
+                                                       PaymentPurpose::Bolt12RefundPayment { payment_preimage, payment_secret, .. } => {
+                                                               assert_eq!(expected_preimage, *payment_preimage);
+                                                               assert_eq!(our_payment_secret.unwrap(), *payment_secret);
+                                                               assert_eq!(Some(*payment_secret), onion_fields.as_ref().unwrap().payment_secret);
+                                                       },
                                                        PaymentPurpose::SpontaneousPayment(payment_preimage) => {
                                                                assert_eq!(expected_preimage.unwrap(), *payment_preimage);
                                                                assert_eq!(our_payment_secret, onion_fields.as_ref().unwrap().payment_secret);
@@ -2763,14 +2781,12 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
        let mut fwd_amt_msat = 0;
        match claim_event[0] {
                Event::PaymentClaimed {
-                       purpose: PaymentPurpose::SpontaneousPayment(preimage),
+                       purpose: PaymentPurpose::SpontaneousPayment(preimage)
+                               | PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(preimage), .. }
+                               | PaymentPurpose::Bolt12OfferPayment { payment_preimage: Some(preimage), .. }
+                               | PaymentPurpose::Bolt12RefundPayment { payment_preimage: Some(preimage), .. },
                        amount_msat,
                        ref htlcs,
-                       .. }
-               | Event::PaymentClaimed {
-                       purpose: PaymentPurpose::Bolt11InvoicePayment { payment_preimage: Some(preimage), ..},
-                       ref htlcs,
-                       amount_msat,
                        ..
                } => {
                        assert_eq!(preimage, our_payment_preimage);
@@ -2780,7 +2796,9 @@ pub fn pass_claimed_payment_along_route<'a, 'b, 'c, 'd>(args: ClaimAlongRouteArg
                        fwd_amt_msat = amount_msat;
                },
                Event::PaymentClaimed {
-                       purpose: PaymentPurpose::Bolt11InvoicePayment { .. },
+                       purpose: PaymentPurpose::Bolt11InvoicePayment { .. }
+                               | PaymentPurpose::Bolt12OfferPayment { .. }
+                               | PaymentPurpose::Bolt12RefundPayment { .. },
                        payment_hash,
                        amount_msat,
                        ref htlcs,