Onion message payload for BOLT 12 Offers
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 14 Feb 2023 23:41:18 +0000 (17:41 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 13 Jun 2023 18:07:47 +0000 (13:07 -0500)
BOLT 12 Offers makes use of onion messages to request and respond with
invoices. Add these types and an error type to OnionMessageContents
along with the necessary parsing and encoding.

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

index 865700609450380ad2314d8f26b36cb6401bc0c1..dbfa0bc636a3de78ac0cdf5f87ab6f31960583c8 100644 (file)
@@ -216,8 +216,8 @@ impl<ES: Deref, NS: Deref, L: Deref, CMH: Deref> OnionMessenger<ES, NS, L, CMH>
                                return Err(SendError::TooFewBlindedHops);
                        }
                }
-               let OnionMessageContents::Custom(ref msg) = message;
-               if msg.tlv_type() < 64 { return Err(SendError::InvalidMessage) }
+
+               if message.tlv_type() < 64 { return Err(SendError::InvalidMessage) }
 
                // If we are sending straight to a blinded path and we are the introduction node, we need to
                // advance the blinded path by 1 hop so the second hop is the new introduction node.
@@ -342,6 +342,7 @@ impl<ES: Deref, NS: Deref, L: Deref, CMH: Deref> OnionMessageHandler for OnionMe
                                        "Received an onion message with path_id {:02x?} and {} reply_path",
                                                path_id, if reply_path.is_some() { "a" } else { "no" });
                                match message {
+                                       OnionMessageContents::Offers(_msg) => todo!(),
                                        OnionMessageContents::Custom(msg) => self.custom_handler.handle_custom_message(msg),
                                }
                        },
index 713b83c62d67d3e0a2fdcc39fd5a004d8a744e6c..0fcf3cc791bc68d6b1f83fd15c0898a83701bae4 100644 (file)
@@ -21,6 +21,7 @@
 //! [blinded paths]: crate::blinded_path::BlindedPath
 
 mod messenger;
+mod offers;
 mod packet;
 #[cfg(test)]
 mod functional_tests;
diff --git a/lightning/src/onion_message/offers.rs b/lightning/src/onion_message/offers.rs
new file mode 100644 (file)
index 0000000..36363c9
--- /dev/null
@@ -0,0 +1,109 @@
+// 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 BOLT 12 Offers.
+
+use core::convert::TryFrom;
+use crate::io::{self, Read};
+use crate::ln::msgs::DecodeError;
+use crate::offers::invoice_error::InvoiceError;
+use crate::offers::invoice_request::InvoiceRequest;
+use crate::offers::invoice::Invoice;
+use crate::offers::parse::ParseError;
+use crate::util::logger::Logger;
+use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
+
+use crate::prelude::*;
+
+// TLV record types for the `onionmsg_tlv` TLV stream as defined in BOLT 4.
+const INVOICE_REQUEST_TLV_TYPE: u64 = 64;
+const INVOICE_TLV_TYPE: u64 = 66;
+const INVOICE_ERROR_TLV_TYPE: u64 = 68;
+
+/// Possible BOLT 12 Offers messages sent and received via an [`OnionMessage`].
+///
+/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
+#[derive(Debug)]
+pub enum OffersMessage {
+       /// A request for an [`Invoice`] for a particular [`Offer`].
+       ///
+       /// [`Offer`]: crate::offers::offer::Offer
+       InvoiceRequest(InvoiceRequest),
+
+       /// An [`Invoice`] sent in response to an [`InvoiceRequest`] or a [`Refund`].
+       ///
+       /// [`Refund`]: crate::offers::refund::Refund
+       Invoice(Invoice),
+
+       /// An error from handling an [`OffersMessage`].
+       InvoiceError(InvoiceError),
+}
+
+impl OffersMessage {
+       /// Returns whether `tlv_type` corresponds to a TLV record for Offers.
+       pub fn is_known_type(tlv_type: u64) -> bool {
+               match tlv_type {
+                       INVOICE_REQUEST_TLV_TYPE | INVOICE_TLV_TYPE | INVOICE_ERROR_TLV_TYPE => true,
+                       _ => false,
+               }
+       }
+
+       /// The TLV record type for the message as used in an `onionmsg_tlv` TLV stream.
+       pub fn tlv_type(&self) -> u64 {
+               match self {
+                       OffersMessage::InvoiceRequest(_) => INVOICE_REQUEST_TLV_TYPE,
+                       OffersMessage::Invoice(_) => INVOICE_TLV_TYPE,
+                       OffersMessage::InvoiceError(_) => INVOICE_ERROR_TLV_TYPE,
+               }
+       }
+
+       fn parse(tlv_type: u64, bytes: Vec<u8>) -> Result<Self, ParseError> {
+               match tlv_type {
+                       INVOICE_REQUEST_TLV_TYPE => Ok(Self::InvoiceRequest(InvoiceRequest::try_from(bytes)?)),
+                       INVOICE_TLV_TYPE => Ok(Self::Invoice(Invoice::try_from(bytes)?)),
+                       _ => Err(ParseError::Decode(DecodeError::InvalidValue)),
+               }
+       }
+}
+
+impl Writeable for OffersMessage {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               match self {
+                       OffersMessage::InvoiceRequest(message) => message.write(w),
+                       OffersMessage::Invoice(message) => message.write(w),
+                       OffersMessage::InvoiceError(message) => message.write(w),
+               }
+       }
+}
+
+impl<L: Logger> ReadableArgs<(u64, &L)> for OffersMessage {
+       fn read<R: Read>(r: &mut R, read_args: (u64, &L)) -> Result<Self, DecodeError> {
+               let (tlv_type, logger) = read_args;
+               if tlv_type == INVOICE_ERROR_TLV_TYPE {
+                       return Ok(Self::InvoiceError(InvoiceError::read(r)?));
+               }
+
+               let mut bytes = Vec::new();
+               r.read_to_end(&mut bytes).unwrap();
+
+               match Self::parse(tlv_type, bytes) {
+                       Ok(message) => Ok(message),
+                       Err(ParseError::Decode(e)) => Err(e),
+                       Err(ParseError::InvalidSemantics(e)) => {
+                               log_trace!(logger, "Invalid semantics for TLV type {}: {:?}", tlv_type, e);
+                               Err(DecodeError::InvalidValue)
+                       },
+                       Err(ParseError::InvalidSignature(e)) => {
+                               log_trace!(logger, "Invalid signature for TLV type {}: {:?}", tlv_type, e);
+                               Err(DecodeError::InvalidValue)
+                       },
+                       Err(_) => Err(DecodeError::InvalidValue),
+               }
+       }
+}
index d4f08109cd9858e0b62c88fbaffe59249b07efee..9322f9489bcd42d80f5dfc6161650dedd1f0f15b 100644 (file)
@@ -16,6 +16,7 @@ use crate::blinded_path::{BlindedPath, ForwardTlvs, ReceiveTlvs};
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils;
 use super::messenger::CustomOnionMessageHandler;
+use super::offers::OffersMessage;
 use crate::util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter};
 use crate::util::logger::Logger;
 use crate::util::ser::{BigSize, FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer};
@@ -109,10 +110,8 @@ pub(super) enum Payload<T: CustomOnionMessageContents> {
 /// The contents of an onion message. In the context of offers, this would be the invoice, invoice
 /// request, or invoice error.
 pub enum OnionMessageContents<T: CustomOnionMessageContents> {
-       // Coming soon:
-       // Invoice,
-       // InvoiceRequest,
-       // InvoiceError,
+       /// A message related to BOLT 12 Offers.
+       Offers(OffersMessage),
        /// A custom onion message specified by the user.
        Custom(T),
 }
@@ -123,6 +122,7 @@ impl<T: CustomOnionMessageContents> OnionMessageContents<T> {
        /// This is not exported to bindings users as methods on non-cloneable enums are not currently exportable
        pub fn tlv_type(&self) -> u64 {
                match self {
+                       &OnionMessageContents::Offers(ref msg) => msg.tlv_type(),
                        &OnionMessageContents::Custom(ref msg) => msg.tlv_type(),
                }
        }
@@ -132,6 +132,7 @@ impl<T: CustomOnionMessageContents> OnionMessageContents<T> {
 impl<T: CustomOnionMessageContents> Writeable for OnionMessageContents<T> {
        fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
                match self {
+                       OnionMessageContents::Offers(msg) => Ok(msg.write(w)?),
                        OnionMessageContents::Custom(msg) => Ok(msg.write(w)?),
                }
        }
@@ -205,7 +206,7 @@ impl<T: CustomOnionMessageContents> Writeable for (Payload<T>, [u8; 32]) {
 impl<H: CustomOnionMessageHandler, L: Logger>
 ReadableArgs<(SharedSecret, &H, &L)> for Payload<<H as CustomOnionMessageHandler>::CustomMessage> {
        fn read<R: Read>(r: &mut R, args: (SharedSecret, &H, &L)) -> Result<Self, DecodeError> {
-               let (encrypted_tlvs_ss, handler, _logger) = args;
+               let (encrypted_tlvs_ss, handler, logger) = args;
 
                let v: BigSize = Readable::read(r)?;
                let mut rd = FixedLengthReader::new(r, v.0);
@@ -223,13 +224,19 @@ ReadableArgs<(SharedSecret, &H, &L)> for Payload<<H as CustomOnionMessageHandler
                        if message_type.is_some() { return Err(DecodeError::InvalidValue) }
 
                        message_type = Some(msg_type);
-                       match handler.read_custom_message(msg_type, msg_reader) {
-                               Ok(Some(msg)) => {
-                                       message = Some(msg);
+                       match msg_type {
+                               tlv_type if OffersMessage::is_known_type(tlv_type) => {
+                                       let msg = OffersMessage::read(msg_reader, (tlv_type, logger))?;
+                                       message = Some(OnionMessageContents::Offers(msg));
                                        Ok(true)
                                },
-                               Ok(None) => Ok(false),
-                               Err(e) => Err(e),
+                               _ => match handler.read_custom_message(msg_type, msg_reader)? {
+                                       Some(msg) => {
+                                               message = Some(OnionMessageContents::Custom(msg));
+                                               Ok(true)
+                                       },
+                                       None => Ok(false),
+                               },
                        }
                });
                rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;
@@ -247,7 +254,7 @@ ReadableArgs<(SharedSecret, &H, &L)> for Payload<<H as CustomOnionMessageHandler
                                Ok(Payload::Receive {
                                        control_tlvs: ReceiveControlTlvs::Unblinded(tlvs),
                                        reply_path,
-                                       message: OnionMessageContents::Custom(message.unwrap()),
+                                       message: message.unwrap(),
                                })
                        }
                }