diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 03dc6a5b002..e05936c52ab 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -246,6 +246,20 @@ pub trait CustomOnionMessageHandler { fn read_custom_message(&self, message_type: u64, buffer: &mut R) -> Result, msgs::DecodeError>; } +/// An processed incoming onion message, containing either a Forward (another onion) +/// or a Receive payload with decrypted contents +pub enum PeeledOnion where + CMH::Target: CustomOnionMessageHandler, +{ + /// Forwarded onion, with the next node id and a new onion + Forward(PublicKey, msgs::OnionMessage), + /// Received onion message, with decrypted contents, path_id, and reply path + Receive(OnionMessageContents<<::Target as CustomOnionMessageHandler>::CustomMessage>, Option<[u8; 32]>, Option) +} + +/// Errors that may occur when [receiving an onion message]. +#[derive(Debug, PartialEq, Eq)] +pub struct ReceiveError { } /// Create an onion message with contents `message` to the destination of `path`. /// Returns (introduction_node_id, onion_msg) @@ -357,6 +371,103 @@ where } } + /// Decode one layer of an incoming onion message + /// Returns either a Forward (another onion message), or Receive (decrypted content) + pub fn peel_onion( + node_signer: &NS, + secp_ctx: &Secp256k1, + logger: &L, + custom_handler: &CMH, + msg: &msgs::OnionMessage, + ) -> Result, ReceiveError> { + let control_tlvs_ss = match node_signer.ecdh(Recipient::Node, &msg.blinding_point, None) { + Ok(ss) => ss, + Err(e) => { + log_error!(logger, "Failed to retrieve node secret: {:?}", e); + return Err(ReceiveError {}); + } + }; + let onion_decode_ss = { + let blinding_factor = { + let mut hmac = HmacEngine::::new(b"blinded_node_id"); + hmac.input(control_tlvs_ss.as_ref()); + Hmac::from_engine(hmac).into_inner() + }; + match node_signer.ecdh(Recipient::Node, &msg.onion_routing_packet.public_key, + Some(&Scalar::from_be_bytes(blinding_factor).unwrap())) + { + Ok(ss) => ss.secret_bytes(), + Err(()) => { + log_trace!(logger, "Failed to compute onion packet shared secret"); + return Err(ReceiveError {}); + } + } + }; + match onion_utils::decode_next_untagged_hop( + onion_decode_ss, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, + (control_tlvs_ss, custom_handler.deref(), logger.deref()) + ) { + Ok((Payload::Receive::<<::Target as CustomOnionMessageHandler>::CustomMessage> { + message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path, + }, None)) => { + log_trace!(logger, + "Received an onion message with path_id {:02x?} and {} reply_path", + path_id, if reply_path.is_some() { "a" } else { "no" }); + + Ok(PeeledOnion::Receive(message, path_id, reply_path)) + }, + Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs { + next_node_id, next_blinding_override + })), Some((next_hop_hmac, new_packet_bytes)))) => { + // TODO: we need to check whether `next_node_id` is our node, in which case this is a dummy + // blinded hop and this onion message is destined for us. In this situation, we should keep + // unwrapping the onion layers to get to the final payload. Since we don't have the option + // of creating blinded paths with dummy hops currently, we should be ok to not handle this + // for now. + let new_pubkey = match onion_utils::next_hop_pubkey(&secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) { + Ok(pk) => pk, + Err(e) => { + log_trace!(logger, "Failed to compute next hop packet pubkey: {}", e); + return Err(ReceiveError {}) + } + }; + let outgoing_packet = Packet { + version: 0, + public_key: new_pubkey, + hop_data: new_packet_bytes, + hmac: next_hop_hmac, + }; + let onion_message = msgs::OnionMessage { + blinding_point: match next_blinding_override { + Some(blinding_point) => blinding_point, + None => { + match onion_utils::next_hop_pubkey( + &secp_ctx, msg.blinding_point, control_tlvs_ss.as_ref() + ) { + Ok(bp) => bp, + Err(e) => { + log_trace!(logger, "Failed to compute next blinding point: {}", e); + return Err(ReceiveError {}) + } + } + } + }, + onion_routing_packet: outgoing_packet, + }; + + Ok(PeeledOnion::Forward(next_node_id, onion_message)) + }, + Err(e) => { + log_trace!(logger, "Errored decoding onion message packet: {:?}", e); + Err(ReceiveError {}) + }, + _ => { + log_trace!(logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa"); + Err(ReceiveError {}) + }, + } + } + fn respond_with_onion_message( &self, response: OnionMessageContents, path_id: Option<[u8; 32]>, reply_path: Option @@ -457,40 +568,14 @@ where /// soon we'll delegate the onion message to a handler that can generate invoices or send /// payments. fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &msgs::OnionMessage) { - let control_tlvs_ss = match self.node_signer.ecdh(Recipient::Node, &msg.blinding_point, None) { - Ok(ss) => ss, - Err(e) => { - log_error!(self.logger, "Failed to retrieve node secret: {:?}", e); - return - } - }; - let onion_decode_ss = { - let blinding_factor = { - let mut hmac = HmacEngine::::new(b"blinded_node_id"); - hmac.input(control_tlvs_ss.as_ref()); - Hmac::from_engine(hmac).into_inner() - }; - match self.node_signer.ecdh(Recipient::Node, &msg.onion_routing_packet.public_key, - Some(&Scalar::from_be_bytes(blinding_factor).unwrap())) - { - Ok(ss) => ss.secret_bytes(), - Err(()) => { - log_trace!(self.logger, "Failed to compute onion packet shared secret"); - return - } - } - }; - match onion_utils::decode_next_untagged_hop( - onion_decode_ss, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, - (control_tlvs_ss, &*self.custom_handler, &*self.logger) + match Self::peel_onion::<<::Target as CustomOnionMessageHandler>::CustomMessage>( + &self.node_signer, + &self.secp_ctx, + &self.logger, + &self.custom_handler, + msg ) { - Ok((Payload::Receive::<<::Target as CustomOnionMessageHandler>::CustomMessage> { - message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path, - }, None)) => { - log_trace!(self.logger, - "Received an onion message with path_id {:02x?} and {} reply_path", - path_id, if reply_path.is_some() { "a" } else { "no" }); - + Ok(PeeledOnion::Receive(message, path_id, reply_path)) => { let response = match message { OnionMessageContents::Offers(msg) => { self.offers_handler.handle_message(msg) @@ -501,50 +586,11 @@ where .map(|msg| OnionMessageContents::Custom(msg)) }, }; - if let Some(response) = response { self.respond_with_onion_message(response, path_id, reply_path); } }, - Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs { - next_node_id, next_blinding_override - })), Some((next_hop_hmac, new_packet_bytes)))) => { - // TODO: we need to check whether `next_node_id` is our node, in which case this is a dummy - // blinded hop and this onion message is destined for us. In this situation, we should keep - // unwrapping the onion layers to get to the final payload. Since we don't have the option - // of creating blinded paths with dummy hops currently, we should be ok to not handle this - // for now. - let new_pubkey = match onion_utils::next_hop_pubkey(&self.secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) { - Ok(pk) => pk, - Err(e) => { - log_trace!(self.logger, "Failed to compute next hop packet pubkey: {}", e); - return - } - }; - let outgoing_packet = Packet { - version: 0, - public_key: new_pubkey, - hop_data: new_packet_bytes, - hmac: next_hop_hmac, - }; - let onion_message = msgs::OnionMessage { - blinding_point: match next_blinding_override { - Some(blinding_point) => blinding_point, - None => { - match onion_utils::next_hop_pubkey( - &self.secp_ctx, msg.blinding_point, control_tlvs_ss.as_ref() - ) { - Ok(bp) => bp, - Err(e) => { - log_trace!(self.logger, "Failed to compute next blinding point: {}", e); - return - } - } - } - }, - onion_routing_packet: outgoing_packet, - }; - + Ok(PeeledOnion::Forward(next_node_id, onion_message)) => { let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap(); if outbound_buffer_full(&next_node_id, &pending_per_peer_msgs) { log_trace!(self.logger, "Dropping forwarded onion message to peer {:?}: outbound buffer full", next_node_id); @@ -563,15 +609,12 @@ where e.get_mut().push_back(onion_message); log_trace!(self.logger, "Forwarding an onion message to peer {}", next_node_id); } - }; + } }, Err(e) => { - log_trace!(self.logger, "Errored decoding onion message packet: {:?}", e); - }, - _ => { - log_trace!(self.logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa"); - }, - }; + log_error!(self.logger, "Failed to process onion message {:?}", e); + } + } } fn peer_connected(&self, their_node_id: &PublicKey, init: &msgs::Init, _inbound: bool) -> Result<(), ()> { diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index ae8bae1fb14..a1c346cf609 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -27,7 +27,7 @@ mod packet; mod functional_tests; // Re-export structs so they can be imported with just the `onion_message::` module prefix. -pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger}; +pub use self::messenger::{CustomOnionMessageContents, CustomOnionMessageHandler, DefaultMessageRouter, Destination, MessageRouter, OnionMessageContents, OnionMessagePath, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger, PeeledOnion}; pub use self::offers::{OffersMessage, OffersMessageHandler}; pub use self::packet::Packet; pub(crate) use self::packet::ControlTlvs;