Add PaymentContext for async payments

This context is stored in the blinded payment paths we put in static invoices
and is useful to authenticate payments over these paths to the recipient.

We can't reuse Bolt12OfferContext for this because we don't have access to the
invoice request fields at static invoice creation time.
This commit is contained in:
Valentine Wallace 2024-06-24 15:51:27 -04:00
parent 542deeb4dd
commit 84f200f0ca
No known key found for this signature in database
GPG key ID: FD3E106A2CE099B4
3 changed files with 43 additions and 10 deletions

View file

@ -349,6 +349,11 @@ pub enum PaymentContext {
/// [`Offer`]: crate::offers::offer::Offer
Bolt12Offer(Bolt12OfferContext),
/// The payment was made for a static invoice requested from a BOLT 12 [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
AsyncBolt12Offer(AsyncBolt12OfferContext),
/// The payment was made for an invoice sent for a BOLT 12 [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
@ -378,6 +383,18 @@ pub struct Bolt12OfferContext {
pub invoice_request: InvoiceRequestFields,
}
/// The context of a payment made for a static invoice requested from a BOLT 12 [`Offer`].
///
/// [`Offer`]: crate::offers::offer::Offer
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AsyncBolt12OfferContext {
/// The [`Nonce`] used to verify that an inbound [`InvoiceRequest`] corresponds to this static
/// invoice's offer.
///
/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
pub offer_nonce: Nonce,
}
/// The context of a payment made for an invoice sent for a BOLT 12 [`Refund`].
///
/// [`Refund`]: crate::offers::refund::Refund
@ -627,6 +644,7 @@ impl_writeable_tlv_based_enum_legacy!(PaymentContext,
// 0 for Unknown removed in version 0.1.
(1, Bolt12Offer),
(2, Bolt12Refund),
(3, AsyncBolt12Offer),
);
impl<'a> Writeable for PaymentContextRef<'a> {
@ -651,6 +669,10 @@ impl_writeable_tlv_based!(Bolt12OfferContext, {
(2, invoice_request, required),
});
impl_writeable_tlv_based!(AsyncBolt12OfferContext, {
(0, offer_nonce, required),
});
impl_writeable_tlv_based!(Bolt12RefundContext, {});
#[cfg(test)]

View file

@ -181,27 +181,32 @@ impl PaymentPurpose {
pub(crate) fn from_parts(
payment_preimage: Option<PaymentPreimage>, payment_secret: PaymentSecret,
payment_context: Option<PaymentContext>,
) -> Self {
) -> Result<Self, ()> {
match payment_context {
None => {
PaymentPurpose::Bolt11InvoicePayment {
Ok(PaymentPurpose::Bolt11InvoicePayment {
payment_preimage,
payment_secret,
}
})
},
Some(PaymentContext::Bolt12Offer(context)) => {
PaymentPurpose::Bolt12OfferPayment {
Ok(PaymentPurpose::Bolt12OfferPayment {
payment_preimage,
payment_secret,
payment_context: context,
}
})
},
Some(PaymentContext::Bolt12Refund(context)) => {
PaymentPurpose::Bolt12RefundPayment {
Ok(PaymentPurpose::Bolt12RefundPayment {
payment_preimage,
payment_secret,
payment_context: context,
}
})
},
Some(PaymentContext::AsyncBolt12Offer(_context)) => {
// This code will change to return Self::Bolt12OfferPayment when we add support for async
// receive.
Err(())
},
}
}
@ -1865,7 +1870,8 @@ impl MaybeReadable for Event {
(13, payment_id, option),
});
let purpose = match payment_secret {
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context),
Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context)
.map_err(|()| msgs::DecodeError::InvalidValue)?,
None if payment_preimage.is_some() => PaymentPurpose::SpontaneousPayment(payment_preimage.unwrap()),
None => return Err(msgs::DecodeError::InvalidValue),
};

View file

@ -6253,11 +6253,16 @@ where
match claimable_htlc.onion_payload {
OnionPayload::Invoice { .. } => {
let payment_data = payment_data.unwrap();
let purpose = events::PaymentPurpose::from_parts(
let purpose = match events::PaymentPurpose::from_parts(
payment_preimage,
payment_data.payment_secret,
payment_context,
);
) {
Ok(purpose) => purpose,
Err(()) => {
fail_htlc!(claimable_htlc, payment_hash);
},
};
check_total_value!(purpose);
},
OnionPayload::Spontaneous(preimage) => {