From 445ec8d50840c9a3377bba80351c09df46527060 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 5 Jun 2024 13:41:07 -0400 Subject: [PATCH] Onion message payload for async payments 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 | 3 + lightning/src/onion_message/async_payments.rs | 110 ++++++++++++++++++ lightning/src/onion_message/messenger.rs | 1 + lightning/src/onion_message/mod.rs | 1 + lightning/src/onion_message/packet.rs | 11 ++ 5 files changed, 126 insertions(+) create mode 100644 lightning/src/onion_message/async_payments.rs diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index eca43afe..c965ba50 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -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 index 00000000..aa6779fb --- /dev/null +++ b/lightning/src/onion_message/async_payments.rs @@ -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 or the MIT license +// , 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(&self, w: &mut W) -> Result<(), io::Error> { + match self { + Self::HeldHtlcAvailable(message) => message.write(w), + Self::ReleaseHeldHtlc(message) => message.write(w), + } + } +} + +impl ReadableArgs for AsyncPaymentsMessage { + fn read(r: &mut R, tlv_type: u64) -> Result { + 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), + } + } +} diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 0fb72c52..c991c5bc 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -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) diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index 05a8b7d6..1693c5a9 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -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; diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 75ec3cd9..47b1a031 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -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 { pub enum ParsedOnionMessageContents { /// 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 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 Writeable for ParsedOnionMessageContents { fn write(&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::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)); -- 2.30.2