Onion message payload for async payments
authorValentine Wallace <vwallace@protonmail.com>
Wed, 5 Jun 2024 17:41:07 +0000 (13:41 -0400)
committerValentine Wallace <vwallace@protonmail.com>
Thu, 20 Jun 2024 14:09:03 +0000 (10:09 -0400)
Async payments uses onion messages to indicate when HTLCs are held and
released. Add these types along with the necessary parsing and encoding.

lightning/src/ln/offers_tests.rs
lightning/src/onion_message/async_payments.rs [new file with mode: 0644]
lightning/src/onion_message/messenger.rs
lightning/src/onion_message/mod.rs
lightning/src/onion_message/packet.rs

index eca43afee8a020b59223a8edaeae26527de5cf9a..c965ba50e8d3d4345dd6bb891fdf004a33e75a76 100644 (file)
@@ -194,6 +194,7 @@ fn extract_invoice_request<'a, 'b, 'c>(
                                OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice),
                                OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
                        },
+                       ParsedOnionMessageContents::AsyncPayments(message) => panic!("Unexpected async payments message: {:?}", message),
                        ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
                },
                Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
@@ -209,6 +210,7 @@ fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage)
                                OffersMessage::Invoice(invoice) => invoice,
                                OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
                        },
+                       ParsedOnionMessageContents::AsyncPayments(message) => panic!("Unexpected async payments message: {:?}", message),
                        ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
                },
                Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
@@ -226,6 +228,7 @@ fn extract_invoice_error<'a, 'b, 'c>(
                                OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice),
                                OffersMessage::InvoiceError(error) => error,
                        },
+                       ParsedOnionMessageContents::AsyncPayments(message) => panic!("Unexpected async payments message: {:?}", message),
                        ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
                },
                Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
diff --git a/lightning/src/onion_message/async_payments.rs b/lightning/src/onion_message/async_payments.rs
new file mode 100644 (file)
index 0000000..aa6779f
--- /dev/null
@@ -0,0 +1,110 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Message handling for async payments.
+
+use crate::io;
+use crate::ln::msgs::DecodeError;
+use crate::onion_message::packet::OnionMessageContents;
+use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
+
+// TLV record types for the `onionmsg_tlv` TLV stream as defined in BOLT 4.
+const HELD_HTLC_AVAILABLE_TLV_TYPE: u64 = 72;
+const RELEASE_HELD_HTLC_TLV_TYPE: u64 = 74;
+
+/// Possible async payment messages sent and received via an [`OnionMessage`].
+///
+/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
+#[derive(Clone, Debug)]
+pub enum AsyncPaymentsMessage {
+       /// An HTLC is being held upstream for the often-offline recipient, to be released via
+       /// [`ReleaseHeldHtlc`].
+       HeldHtlcAvailable(HeldHtlcAvailable),
+
+       /// Releases the HTLC corresponding to an inbound [`HeldHtlcAvailable`] message.
+       ReleaseHeldHtlc(ReleaseHeldHtlc),
+}
+
+/// An HTLC destined for the recipient of this message is being held upstream. The reply path
+/// accompanying this onion message should be used to send a [`ReleaseHeldHtlc`] response, which
+/// will cause the upstream HTLC to be released.
+#[derive(Clone, Debug)]
+pub struct HeldHtlcAvailable {
+       /// The secret that will be used by the recipient of this message to release the held HTLC.
+       pub payment_release_secret: [u8; 32],
+}
+
+/// Releases the HTLC corresponding to an inbound [`HeldHtlcAvailable`] message.
+#[derive(Clone, Debug)]
+pub struct ReleaseHeldHtlc {
+       /// Used to release the HTLC held upstream if it matches the corresponding
+       /// [`HeldHtlcAvailable::payment_release_secret`].
+       pub payment_release_secret: [u8; 32],
+}
+
+impl OnionMessageContents for ReleaseHeldHtlc {
+       fn tlv_type(&self) -> u64 {
+               RELEASE_HELD_HTLC_TLV_TYPE
+       }
+       fn msg_type(&self) -> &'static str {
+               "Release Held HTLC"
+       }
+}
+
+impl_writeable_tlv_based!(HeldHtlcAvailable, {
+       (0, payment_release_secret, required),
+});
+
+impl_writeable_tlv_based!(ReleaseHeldHtlc, {
+       (0, payment_release_secret, required),
+});
+
+impl AsyncPaymentsMessage {
+       /// Returns whether `tlv_type` corresponds to a TLV record for async payment messages.
+       pub fn is_known_type(tlv_type: u64) -> bool {
+               match tlv_type {
+                       HELD_HTLC_AVAILABLE_TLV_TYPE | RELEASE_HELD_HTLC_TLV_TYPE => true,
+                       _ => false,
+               }
+       }
+}
+
+impl OnionMessageContents for AsyncPaymentsMessage {
+       fn tlv_type(&self) -> u64 {
+               match self {
+                       Self::HeldHtlcAvailable(_) => HELD_HTLC_AVAILABLE_TLV_TYPE,
+                       Self::ReleaseHeldHtlc(msg) => msg.tlv_type(),
+               }
+       }
+       fn msg_type(&self) -> &'static str {
+               match &self {
+                       Self::HeldHtlcAvailable(_) => "Held HTLC Available",
+                       Self::ReleaseHeldHtlc(msg) => msg.msg_type(),
+               }
+       }
+}
+
+impl Writeable for AsyncPaymentsMessage {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               match self {
+                       Self::HeldHtlcAvailable(message) => message.write(w),
+                       Self::ReleaseHeldHtlc(message) => message.write(w),
+               }
+       }
+}
+
+impl ReadableArgs<u64> for AsyncPaymentsMessage {
+       fn read<R: io::Read>(r: &mut R, tlv_type: u64) -> Result<Self, DecodeError> {
+               match tlv_type {
+                       HELD_HTLC_AVAILABLE_TLV_TYPE => Ok(Self::HeldHtlcAvailable(Readable::read(r)?)),
+                       RELEASE_HELD_HTLC_TLV_TYPE => Ok(Self::ReleaseHeldHtlc(Readable::read(r)?)),
+                       _ => Err(DecodeError::InvalidValue),
+               }
+       }
+}
index 0fb72c52d2784a03973bfb3ac2bbf846fc3daba6..c991c5bccc574cf3b0cd866670bcbc8d22572c92 100644 (file)
@@ -1428,6 +1428,7 @@ where
                                                let response_instructions = self.offers_handler.handle_message(msg, responder);
                                                let _ = self.handle_onion_message_response(response_instructions);
                                        },
+                                       ParsedOnionMessageContents::AsyncPayments(_msg) => todo!(),
                                        ParsedOnionMessageContents::Custom(msg) => {
                                                let responder = reply_path.map(
                                                        |reply_path| Responder::new(reply_path, path_id)
index 05a8b7d6fbd033922fb140ea97d326287aec8757..1693c5a9911727651588ab7ca65f78ee264884ef 100644 (file)
@@ -21,6 +21,7 @@
 //! [blinded paths]: crate::blinded_path::BlindedPath
 //! [`OnionMessenger`]: self::messenger::OnionMessenger
 
+pub mod async_payments;
 pub mod messenger;
 pub mod offers;
 pub mod packet;
index 75ec3cd90ab83c811b18b1283eab668ca7ddcb1d..47b1a0313d791a43c5d52c286b919bba2f151b1a 100644 (file)
@@ -17,6 +17,7 @@ use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs};
 use crate::blinded_path::utils::Padding;
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils;
+use super::async_payments::AsyncPaymentsMessage;
 use super::messenger::CustomOnionMessageHandler;
 use super::offers::OffersMessage;
 use crate::crypto::streams::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
@@ -128,6 +129,8 @@ pub(super) enum Payload<T: OnionMessageContents> {
 pub enum ParsedOnionMessageContents<T: OnionMessageContents> {
        /// A message related to BOLT 12 Offers.
        Offers(OffersMessage),
+       /// A message related to async payments.
+       AsyncPayments(AsyncPaymentsMessage),
        /// A custom onion message specified by the user.
        Custom(T),
 }
@@ -139,12 +142,14 @@ impl<T: OnionMessageContents> OnionMessageContents for ParsedOnionMessageContent
        fn tlv_type(&self) -> u64 {
                match self {
                        &ParsedOnionMessageContents::Offers(ref msg) => msg.tlv_type(),
+                       &ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.tlv_type(),
                        &ParsedOnionMessageContents::Custom(ref msg) => msg.tlv_type(),
                }
        }
        fn msg_type(&self) -> &'static str {
                match self {
                        ParsedOnionMessageContents::Offers(ref msg) => msg.msg_type(),
+                       ParsedOnionMessageContents::AsyncPayments(ref msg) => msg.msg_type(),
                        ParsedOnionMessageContents::Custom(ref msg) => msg.msg_type(),
                }
        }
@@ -154,6 +159,7 @@ impl<T: OnionMessageContents> Writeable for ParsedOnionMessageContents<T> {
        fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
                match self {
                        ParsedOnionMessageContents::Offers(msg) => Ok(msg.write(w)?),
+                       ParsedOnionMessageContents::AsyncPayments(msg) => Ok(msg.write(w)?),
                        ParsedOnionMessageContents::Custom(msg) => Ok(msg.write(w)?),
                }
        }
@@ -255,6 +261,11 @@ for Payload<ParsedOnionMessageContents<<H as CustomOnionMessageHandler>::CustomM
                                        message = Some(ParsedOnionMessageContents::Offers(msg));
                                        Ok(true)
                                },
+                               tlv_type if AsyncPaymentsMessage::is_known_type(tlv_type) => {
+                                       let msg = AsyncPaymentsMessage::read(msg_reader, tlv_type)?;
+                                       message = Some(ParsedOnionMessageContents::AsyncPayments(msg));
+                                       Ok(true)
+                               },
                                _ => match handler.read_custom_message(msg_type, msg_reader)? {
                                        Some(msg) => {
                                                message = Some(ParsedOnionMessageContents::Custom(msg));