use crate::sync::{Arc, Mutex};
use crate::prelude::*;
-/// A sender, receiver and forwarder of onion messages. In upcoming releases, this object will be
-/// used to retrieve invoices and fulfill invoice requests from [offers]. Currently, only sending
-/// and receiving custom onion messages is supported.
+/// A sender, receiver and forwarder of [`OnionMessage`]s.
+///
+/// # Handling Messages
+///
+/// `OnionMessenger` implements [`OnionMessageHandler`], making it responsible for either forwarding
+/// messages to peers or delegating to the appropriate handler for the message type. Currently, the
+/// available handlers are:
+/// * [`OffersMessageHandler`], for responding to [`InvoiceRequest`]s and paying [`Bolt12Invoice`]s
+/// * [`CustomOnionMessageHandler`], for handling user-defined message types
+///
+/// # Sending Messages
+///
+/// [`OnionMessage`]s are sent initially using [`OnionMessenger::send_onion_message`]. When handling
+/// a message, the matched handler may return a response message which `OnionMessenger` will send
+/// on its behalf.
///
/// # Example
///
/// onion_messenger.send_onion_message(path, message, reply_path);
/// ```
///
-/// [offers]: <https://github.com/lightning/bolts/pull/798>
-/// [`OnionMessenger`]: crate::onion_message::OnionMessenger
+/// [`OnionMessage`]: crate::ln::msgs::OnionMessage
+/// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub struct OnionMessenger<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref>
where
ES::Target: EntropySource,
fn read_custom_message<R: io::Read>(&self, message_type: u64, buffer: &mut R) -> Result<Option<Self::CustomMessage>, msgs::DecodeError>;
}
+/// A processed incoming onion message, containing either a Forward (another onion message)
+/// or a Receive payload with decrypted contents.
+pub enum PeeledOnion<CM: CustomOnionMessageContents> {
+ /// 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<CM>, Option<[u8; 32]>, Option<BlindedPath>)
+}
+
+/// Create an onion message with contents `message` to the destination of `path`.
+/// Returns (introduction_node_id, onion_msg)
+pub fn create_onion_message<ES: Deref, NS: Deref, T: CustomOnionMessageContents>(
+ entropy_source: &ES, node_signer: &NS, secp_ctx: &Secp256k1<secp256k1::All>,
+ path: OnionMessagePath, message: OnionMessageContents<T>, reply_path: Option<BlindedPath>,
+) -> Result<(PublicKey, msgs::OnionMessage), SendError>
+where
+ ES::Target: EntropySource,
+ NS::Target: NodeSigner,
+{
+ let OnionMessagePath { intermediate_nodes, mut destination } = path;
+ if let Destination::BlindedPath(BlindedPath { ref blinded_hops, .. }) = destination {
+ if blinded_hops.len() < 2 {
+ return Err(SendError::TooFewBlindedHops);
+ }
+ }
+
+ 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.
+ if intermediate_nodes.len() == 0 {
+ if let Destination::BlindedPath(ref mut blinded_path) = destination {
+ let our_node_id = node_signer.get_node_id(Recipient::Node)
+ .map_err(|()| SendError::GetNodeIdFailed)?;
+ if blinded_path.introduction_node_id == our_node_id {
+ advance_path_by_one(blinded_path, node_signer, &secp_ctx)
+ .map_err(|()| SendError::BlindedPathAdvanceFailed)?;
+ }
+ }
+ }
+
+ let blinding_secret_bytes = entropy_source.get_secure_random_bytes();
+ let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
+ let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
+ (intermediate_nodes[0], PublicKey::from_secret_key(&secp_ctx, &blinding_secret))
+ } else {
+ match destination {
+ Destination::Node(pk) => (pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)),
+ Destination::BlindedPath(BlindedPath { introduction_node_id, blinding_point, .. }) =>
+ (introduction_node_id, blinding_point),
+ }
+ };
+ let (packet_payloads, packet_keys) = packet_payloads_and_keys(
+ &secp_ctx, &intermediate_nodes, destination, message, reply_path, &blinding_secret)
+ .map_err(|e| SendError::Secp256k1(e))?;
+
+ let prng_seed = entropy_source.get_secure_random_bytes();
+ let onion_routing_packet = construct_onion_message_packet(
+ packet_payloads, packet_keys, prng_seed).map_err(|()| SendError::TooBigPacket)?;
+
+ Ok((introduction_node_id, msgs::OnionMessage {
+ blinding_point,
+ onion_routing_packet
+ }))
+}
+
+/// Decode one layer of an incoming onion message
+/// Returns either a Forward (another onion message), or Receive (decrypted content)
+pub fn peel_onion<NS: Deref, L: Deref, CMH: Deref>(
+ node_signer: NS, secp_ctx: &Secp256k1<secp256k1::All>, logger: L, custom_handler: CMH,
+ msg: &msgs::OnionMessage,
+) -> Result<PeeledOnion<<<CMH>::Target as CustomOnionMessageHandler>::CustomMessage>, ()>
+where
+ NS::Target: NodeSigner,
+ L::Target: Logger,
+ CMH::Target: CustomOnionMessageHandler,
+{
+ 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(());
+ }
+ };
+ let onion_decode_ss = {
+ let blinding_factor = {
+ let mut hmac = HmacEngine::<Sha256>::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(());
+ }
+ }
+ };
+ 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::<<<CMH as Deref>::Target as CustomOnionMessageHandler>::CustomMessage> {
+ message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
+ }, None)) => {
+ 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(())
+ }
+ };
+ 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(())
+ }
+ }
+ }
+ },
+ 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(())
+ },
+ _ => {
+ log_trace!(logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa");
+ Err(())
+ },
+ }
+}
+
impl<ES: Deref, NS: Deref, L: Deref, MR: Deref, OMH: Deref, CMH: Deref>
OnionMessenger<ES, NS, L, MR, OMH, CMH>
where
&self, path: OnionMessagePath, message: OnionMessageContents<T>,
reply_path: Option<BlindedPath>
) -> Result<(), SendError> {
- let OnionMessagePath { intermediate_nodes, mut destination } = path;
- if let Destination::BlindedPath(BlindedPath { ref blinded_hops, .. }) = destination {
- if blinded_hops.len() < 2 {
- return Err(SendError::TooFewBlindedHops);
- }
- }
-
- 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.
- if intermediate_nodes.len() == 0 {
- if let Destination::BlindedPath(ref mut blinded_path) = destination {
- let our_node_id = self.node_signer.get_node_id(Recipient::Node)
- .map_err(|()| SendError::GetNodeIdFailed)?;
- if blinded_path.introduction_node_id == our_node_id {
- advance_path_by_one(blinded_path, &self.node_signer, &self.secp_ctx)
- .map_err(|()| SendError::BlindedPathAdvanceFailed)?;
- }
- }
- }
-
- let blinding_secret_bytes = self.entropy_source.get_secure_random_bytes();
- let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
- let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
- (intermediate_nodes[0], PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret))
- } else {
- match destination {
- Destination::Node(pk) => (pk, PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
- Destination::BlindedPath(BlindedPath { introduction_node_id, blinding_point, .. }) =>
- (introduction_node_id, blinding_point),
- }
- };
- let (packet_payloads, packet_keys) = packet_payloads_and_keys(
- &self.secp_ctx, &intermediate_nodes, destination, message, reply_path, &blinding_secret)
- .map_err(|e| SendError::Secp256k1(e))?;
-
- let prng_seed = self.entropy_source.get_secure_random_bytes();
- let onion_routing_packet = construct_onion_message_packet(
- packet_payloads, packet_keys, prng_seed).map_err(|()| SendError::TooBigPacket)?;
+ let (introduction_node_id, onion_msg) = create_onion_message(
+ &self.entropy_source, &self.node_signer, &self.secp_ctx,
+ path, message, reply_path
+ )?;
let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
if outbound_buffer_full(&introduction_node_id, &pending_per_peer_msgs) { return Err(SendError::BufferFull) }
match pending_per_peer_msgs.entry(introduction_node_id) {
hash_map::Entry::Vacant(_) => Err(SendError::InvalidFirstHop),
hash_map::Entry::Occupied(mut e) => {
- e.get_mut().push_back(msgs::OnionMessage { blinding_point, onion_routing_packet });
+ e.get_mut().push_back(onion_msg);
Ok(())
}
}
/// 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::<Sha256>::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 peel_onion(
+ &*self.node_signer, &self.secp_ctx, &*self.logger, &*self.custom_handler, msg
) {
- Ok((Payload::Receive::<<<CMH as Deref>::Target as CustomOnionMessageHandler>::CustomMessage> {
- message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
- }, None)) => {
+ Ok(PeeledOnion::Receive(message, path_id, reply_path)) => {
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" });
-
let response = match message {
OnionMessageContents::Offers(msg) => {
self.offers_handler.handle_message(msg)
.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);
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<(), ()> {