From a799fc9b30f039bb921f0745439a64b4964d75ca Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 14 Feb 2023 17:41:18 -0600 Subject: [PATCH] Onion message payload for BOLT 12 Offers 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 | 5 +- lightning/src/onion_message/mod.rs | 1 + lightning/src/onion_message/offers.rs | 109 +++++++++++++++++++++++ lightning/src/onion_message/packet.rs | 29 +++--- 4 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 lightning/src/onion_message/offers.rs diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 865700609..dbfa0bc63 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -216,8 +216,8 @@ impl OnionMessenger 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 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), } }, diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index 713b83c62..0fcf3cc79 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 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 index 000000000..36363c90a --- /dev/null +++ b/lightning/src/onion_message/offers.rs @@ -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 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 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) -> Result { + 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(&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 ReadableArgs<(u64, &L)> for OffersMessage { + fn read(r: &mut R, read_args: (u64, &L)) -> Result { + 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), + } + } +} diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index d4f08109c..9322f9489 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -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 { /// The contents of an onion message. In the context of offers, this would be the invoice, invoice /// request, or invoice error. pub enum OnionMessageContents { - // 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 OnionMessageContents { /// 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 OnionMessageContents { impl Writeable for OnionMessageContents { fn write(&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 Writeable for (Payload, [u8; 32]) { impl ReadableArgs<(SharedSecret, &H, &L)> for Payload<::CustomMessage> { fn read(r: &mut R, args: (SharedSecret, &H, &L)) -> Result { - 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< { - 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<