From 54ca54d1917ce5fea627f9144bcacb9cdca4c095 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sat, 13 Apr 2024 17:29:24 -0500 Subject: [PATCH] BOLT 12 variants of PaymentPurpose 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 | 23 +++++ lightning/src/events/mod.rs | 100 +++++++++++++++++++++- lightning/src/ln/channelmanager.rs | 12 +-- lightning/src/ln/functional_test_utils.rs | 32 +++++-- 4 files changed, 150 insertions(+), 17 deletions(-) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index ad7b229b..e243d478 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -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(&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(&self, _w: &mut W) -> Result<(), io::Error> { Ok(()) diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs index 41ba1780..94df71b6 100644 --- a/lightning/src/events/mod.rs +++ b/lightning/src/events/mod.rs @@ -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, + /// 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, + /// 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 { 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), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0693f3c6..4b120748 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -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, invoice_expiry_delta_secs: u32, min_final_cltv_expiry_delta: Option) -> Result<(PaymentHash, PaymentSecret), ()> { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 35dbfd41..cf52d946 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -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 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, -- 2.30.2