From: Jeffrey Czyz Date: Sat, 13 Apr 2024 22:29:24 +0000 (-0500) Subject: BOLT 12 variants of PaymentPurpose X-Git-Tag: v0.0.123-beta~7^2~4 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=54ca54d1917ce5fea627f9144bcacb9cdca4c095;p=rust-lightning 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. --- diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index ad7b229b5..e243d478b 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 41ba17806..94df71b69 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 0693f3c64..4b120748a 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 35dbfd419..cf52d946e 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,