From 6cc741ffdbe5e2469bddc599a174378315946b33 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 30 Oct 2024 14:20:50 -0400 Subject: [PATCH] Add onion message AsyncPaymentsContext for inbound payments This context is included in static invoice's blinded message paths, provided back to us in HeldHtlcAvailable onion messages for blinded path authentication. In future work, we will check if this context is valid and respond with a ReleaseHeldHtlc message to release the upstream payment if so. We also add creation methods for the hmac used for authenticating the blinded path using the static invoice's corresponding offer id. --- lightning/src/blinded_path/message.rs | 28 +++++++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 7 ++++++- lightning/src/offers/offer.rs | 16 +++++++++++++++ lightning/src/offers/signer.rs | 20 +++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 4d96434dd63..d0803a5aca5 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -25,6 +25,7 @@ use crate::ln::msgs::DecodeError; use crate::ln::onion_utils; use crate::types::payment::PaymentHash; use crate::offers::nonce::Nonce; +use crate::offers::offer::OfferId; use crate::onion_message::packet::ControlTlvs; use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; use crate::sign::{EntropySource, NodeSigner, Recipient}; @@ -402,6 +403,28 @@ pub enum AsyncPaymentsContext { /// containing the expected [`PaymentId`]. hmac: Hmac, }, + /// Context contained within the [`BlindedMessagePath`]s we put in static invoices, provided back + /// to us in corresponding [`HeldHtlcAvailable`] messages. + /// + /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable + InboundPayment { + /// The ID of the [`Offer`] that this [`BlindedMessagePath`]'s static invoice corresponds to. + /// Useful to authenticate that this blinded path was created by us for asynchronously paying + /// one of our offers. + /// + /// [`Offer`]: crate::offers::offer::Offer + offer_id: OfferId, + /// A nonce used for authenticating that a [`HeldHtlcAvailable`] message is valid for a + /// preceding static invoice. + /// + /// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable + nonce: Nonce, + /// Authentication code for the [`OfferId`]. + /// + /// Prevents the recipient from being able to deanonymize us by creating a blinded path to us + /// containing the expected [`OfferId`]. + hmac: Hmac, + }, } impl_writeable_tlv_based_enum!(MessageContext, @@ -433,6 +456,11 @@ impl_writeable_tlv_based_enum!(AsyncPaymentsContext, (2, nonce, required), (4, hmac, required), }, + (1, InboundPayment) => { + (0, offer_id, required), + (2, nonce, required), + (4, hmac, required), + }, ); /// Contains a simple nonce for use in a blinded path's context. diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d6214d71083..446a717d030 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -11747,7 +11747,12 @@ where fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { #[cfg(async_payments)] { - let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context; + let (payment_id, nonce, hmac) = match _context { + AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } => { + (payment_id, nonce, hmac) + }, + _ => return + }; if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return } if let Err(e) = self.send_payment_for_static_invoice(payment_id) { log_trace!( diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index bf66850794c..d1c8bcd6f11 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -98,6 +98,13 @@ use crate::offers::signer::{Metadata, MetadataMaterial, self}; use crate::util::ser::{CursorReadable, HighZeroBytesDroppedBigSize, Readable, WithoutLength, Writeable, Writer}; use crate::util::string::PrintableString; +#[cfg(async_payments)] +use { + bitcoin::hashes::hmac::Hmac, + bitcoin::hashes::sha256::Hash as Sha256, + crate::ln::inbound_payment, +}; + #[cfg(not(c_bindings))] use { crate::offers::invoice_request::InvoiceRequestBuilder, @@ -134,6 +141,15 @@ impl OfferId { let tagged_hash = TaggedHash::from_tlv_stream(Self::ID_TAG, tlv_stream); Self(tagged_hash.to_bytes()) } + + /// Constructs an HMAC to include in [`AsyncPaymentsContext::InboundPayment`] for the offer id + /// along with the given [`Nonce`]. + #[cfg(async_payments)] + pub fn hmac_for_static_invoice( + &self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey, + ) -> Hmac { + signer::hmac_for_static_invoice_offer_id(*self, nonce, expanded_key) + } } impl Borrow<[u8]> for OfferId { diff --git a/lightning/src/offers/signer.rs b/lightning/src/offers/signer.rs index d8caa2175fe..f05c8bd7e2b 100644 --- a/lightning/src/offers/signer.rs +++ b/lightning/src/offers/signer.rs @@ -20,6 +20,8 @@ use crate::ln::channelmanager::PaymentId; use crate::ln::inbound_payment::{ExpandedKey, IV_LEN}; use crate::offers::merkle::TlvRecord; use crate::offers::nonce::Nonce; +#[cfg(async_payments)] +use crate::offers::offer::OfferId; use crate::util::ser::Writeable; use crate::prelude::*; @@ -46,6 +48,10 @@ const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16]; // HMAC input for a `PaymentHash`. The HMAC is used in `OffersContext::InboundPayment`. const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16]; +// HMAC input for an `OfferId`. The HMAC is used in `AsyncPaymentsContext::InboundPayment`. +#[cfg(async_payments)] +const ASYNC_PAYMENT_OFFER_ID_HMAC_INPUT: &[u8; 16] = &[8; 16]; + /// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be /// verified. #[derive(Clone)] @@ -459,3 +465,17 @@ fn hmac_for_payment_id( Hmac::from_engine(hmac) } + +#[cfg(async_payments)] +pub(crate) fn hmac_for_static_invoice_offer_id( + offer_id: OfferId, nonce: Nonce, expanded_key: &ExpandedKey, +) -> Hmac { + const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ID ~~~"; + let mut hmac = expanded_key.hmac_for_offer(); + hmac.input(IV_BYTES); + hmac.input(&nonce.0); + hmac.input(ASYNC_PAYMENT_OFFER_ID_HMAC_INPUT); + hmac.input(&offer_id.0); + + Hmac::from_engine(hmac) +}