From 9f1ffab24c4870d0f9846d75a693f2a784648a60 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Mar 2024 14:31:30 -0500 Subject: [PATCH] Look up node id from scid in OnionMessenger When forwarding onion messages, the next node may be represented by a short channel id instead of a node id. Parameterize OnionMessenger with a NodeIdLookUp trait to find which node is the next hop. Implement the trait for ChannelManager for forwarding to channel counterparties. Also use this trait when advancing a blinded path one hop when the sender is the introduction node. --- fuzz/src/onion_message.rs | 7 +- lightning/src/blinded_path/message.rs | 18 +++-- lightning/src/blinded_path/mod.rs | 38 +++++++++- lightning/src/ln/channelmanager.rs | 19 ++++- lightning/src/ln/functional_test_utils.rs | 5 +- .../src/onion_message/functional_tests.rs | 6 +- lightning/src/onion_message/messenger.rs | 71 ++++++++++++------- 7 files changed, 125 insertions(+), 39 deletions(-) diff --git a/fuzz/src/onion_message.rs b/fuzz/src/onion_message.rs index f2bae246..91fcb9bf 100644 --- a/fuzz/src/onion_message.rs +++ b/fuzz/src/onion_message.rs @@ -6,7 +6,7 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::schnorr; -use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; use lightning::ln::features::InitFeatures; use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler}; use lightning::ln::script::ShutdownScript; @@ -36,12 +36,13 @@ pub fn do_test(data: &[u8], logger: &L) { node_secret: secret, counter: AtomicU64::new(0), }; + let node_id_lookup = EmptyNodeIdLookUp {}; let message_router = TestMessageRouter {}; let offers_msg_handler = TestOffersMessageHandler {}; let custom_msg_handler = TestCustomMessageHandler {}; let onion_messenger = OnionMessenger::new( - &keys_manager, &keys_manager, logger, &message_router, &offers_msg_handler, - &custom_msg_handler + &keys_manager, &keys_manager, logger, &node_id_lookup, &message_router, + &offers_msg_handler, &custom_msg_handler ); let peer_node_id = { diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 1d2d624c..df7f8e7a 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -3,7 +3,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; #[allow(unused_imports)] use crate::prelude::*; -use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; use crate::blinded_path::utils; use crate::io; use crate::io::Cursor; @@ -84,9 +84,14 @@ pub(super) fn blinded_hops( // Advance the blinded onion message path by one hop, so make the second hop into the new // introduction node. -pub(crate) fn advance_path_by_one( - path: &mut BlindedPath, node_signer: &NS, secp_ctx: &Secp256k1 -) -> Result<(), ()> where NS::Target: NodeSigner { +pub(crate) fn advance_path_by_one( + path: &mut BlindedPath, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1 +) -> Result<(), ()> +where + NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, + T: secp256k1::Signing + secp256k1::Verification, +{ let control_tlvs_ss = node_signer.ecdh(Recipient::Node, &path.blinding_point, None)?; let rho = onion_utils::gen_rho_from_shared_secret(&control_tlvs_ss.secret_bytes()); let encrypted_control_tlvs = path.blinded_hops.remove(0).encrypted_payload; @@ -98,7 +103,10 @@ pub(crate) fn advance_path_by_one { let next_node_id = match next_hop { NextHop::NodeId(pubkey) => pubkey, - NextHop::ShortChannelId(_) => todo!(), + NextHop::ShortChannelId(scid) => match node_id_lookup.next_node_id(scid) { + Some(pubkey) => pubkey, + None => return Err(()), + }, }; let mut new_blinding_point = match next_blinding_override { Some(blinding_point) => blinding_point, diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index daa9f033..07fa7b77 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -58,7 +58,7 @@ pub enum IntroductionNode { /// /// [BOLT 7]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_announcement-message /// [`ChannelAnnouncement`]: crate::ln::msgs::ChannelAnnouncement -#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub enum Direction { /// The lesser node id when compared lexicographically in ascending order. NodeOne, @@ -66,6 +66,29 @@ pub enum Direction { NodeTwo, } +/// An interface for looking up the node id of a channel counterparty for the purpose of forwarding +/// an [`OnionMessage`]. +/// +/// [`OnionMessage`]: crate::ln::msgs::OnionMessage +pub trait NodeIdLookUp { + /// Returns the node id of the forwarding node's channel counterparty with `short_channel_id`. + /// + /// Here, the forwarding node is referring to the node of the [`OnionMessenger`] parameterized + /// by the [`NodeIdLookUp`] and the counterparty to one of that node's peers. + /// + /// [`OnionMessenger`]: crate::onion_message::messenger::OnionMessenger + fn next_node_id(&self, short_channel_id: u64) -> Option; +} + +/// A [`NodeIdLookUp`] that always returns `None`. +pub struct EmptyNodeIdLookUp {} + +impl NodeIdLookUp for EmptyNodeIdLookUp { + fn next_node_id(&self, _short_channel_id: u64) -> Option { + None + } +} + /// An encrypted payload and node id corresponding to a hop in a payment or onion message path, to /// be encoded in the sender's onion packet. These hops cannot be identified by outside observers /// and thus can be used to hide the identity of the recipient. @@ -238,4 +261,17 @@ impl Direction { Direction::NodeTwo => core::cmp::max(node_a, node_b), } } + + /// Returns the [`PublicKey`] from the inputs corresponding to the direction. + pub fn select_pubkey<'a>(&self, node_a: &'a PublicKey, node_b: &'a PublicKey) -> &'a PublicKey { + let (node_one, node_two) = if NodeId::from_pubkey(node_a) < NodeId::from_pubkey(node_b) { + (node_a, node_b) + } else { + (node_b, node_a) + }; + match self { + Direction::NodeOne => node_one, + Direction::NodeTwo => node_two, + } + } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index fa8a0b21..49ced4db 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -31,7 +31,7 @@ use bitcoin::secp256k1::{SecretKey,PublicKey}; use bitcoin::secp256k1::Secp256k1; use bitcoin::{secp256k1, Sequence}; -use crate::blinded_path::BlindedPath; +use crate::blinded_path::{BlindedPath, NodeIdLookUp}; use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; @@ -10433,6 +10433,23 @@ where } } +impl +NodeIdLookUp for ChannelManager +where + M::Target: chain::Watch<::EcdsaSigner>, + T::Target: BroadcasterInterface, + ES::Target: EntropySource, + NS::Target: NodeSigner, + SP::Target: SignerProvider, + F::Target: FeeEstimator, + R::Target: Router, + L::Target: Logger, +{ + fn next_node_id(&self, short_channel_id: u64) -> Option { + self.short_to_chan_info.read().unwrap().get(&short_channel_id).map(|(pubkey, _)| *pubkey) + } +} + /// Fetches the set of [`NodeFeatures`] flags that are provided by or required by /// [`ChannelManager`]. pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures { diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 3a506b57..ee4c027a 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -415,6 +415,7 @@ type TestOnionMessenger<'chan_man, 'node_cfg, 'chan_mon_cfg> = OnionMessenger< DedicatedEntropy, &'node_cfg test_utils::TestKeysInterface, &'chan_mon_cfg test_utils::TestLogger, + &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, &'node_cfg test_utils::TestMessageRouter<'chan_mon_cfg>, &'chan_man TestChannelManager<'node_cfg, 'chan_mon_cfg>, IgnoringMessageHandler, @@ -3199,8 +3200,8 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec, Arc, Arc, + Arc, Arc>>, Arc, @@ -175,6 +176,7 @@ fn create_nodes_using_secrets(secrets: Vec) -> Vec { let entropy_source = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet)); let node_signer = Arc::new(test_utils::TestNodeSigner::new(secret_key)); + let node_id_lookup = Arc::new(EmptyNodeIdLookUp {}); let message_router = Arc::new( DefaultMessageRouter::new(network_graph.clone(), entropy_source.clone()) ); @@ -185,7 +187,7 @@ fn create_nodes_using_secrets(secrets: Vec) -> Vec { node_id: node_signer.get_node_id(Recipient::Node).unwrap(), entropy_source: entropy_source.clone(), messenger: OnionMessenger::new( - entropy_source, node_signer, logger.clone(), message_router, + entropy_source, node_signer, logger.clone(), node_id_lookup, message_router, offers_message_handler, custom_message_handler.clone() ), custom_message_handler, diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 5be5ba83..3074ee64 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -15,7 +15,7 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; -use crate::blinded_path::{BlindedPath, IntroductionNode}; +use crate::blinded_path::{BlindedPath, IntroductionNode, NodeIdLookUp}; use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, NextHop, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; @@ -70,7 +70,7 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # use bitcoin::hashes::_export::_core::time::Duration; /// # use bitcoin::hashes::hex::FromHex; /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self}; -/// # use lightning::blinded_path::BlindedPath; +/// # use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; /// # use lightning::sign::{EntropySource, KeysManager}; /// # use lightning::ln::peer_handler::IgnoringMessageHandler; /// # use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath, OnionMessenger}; @@ -111,14 +111,15 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # let hop_node_id1 = PublicKey::from_secret_key(&secp_ctx, &node_secret); /// # let (hop_node_id3, hop_node_id4) = (hop_node_id1, hop_node_id1); /// # let destination_node_id = hop_node_id1; +/// # let node_id_lookup = EmptyNodeIdLookUp {}; /// # let message_router = Arc::new(FakeMessageRouter {}); /// # let custom_message_handler = IgnoringMessageHandler {}; /// # let offers_message_handler = IgnoringMessageHandler {}; /// // Create the onion messenger. This must use the same `keys_manager` as is passed to your /// // ChannelManager. /// let onion_messenger = OnionMessenger::new( -/// &keys_manager, &keys_manager, logger, message_router, &offers_message_handler, -/// &custom_message_handler +/// &keys_manager, &keys_manager, logger, &node_id_lookup, message_router, +/// &offers_message_handler, &custom_message_handler /// ); /// # #[derive(Debug)] @@ -155,11 +156,12 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice -pub struct OnionMessenger +pub struct OnionMessenger where ES::Target: EntropySource, NS::Target: NodeSigner, L::Target: Logger, + NL::Target: NodeIdLookUp, MR::Target: MessageRouter, OMH::Target: OffersMessageHandler, CMH::Target: CustomOnionMessageHandler, @@ -169,6 +171,7 @@ where logger: L, message_recipients: Mutex>, secp_ctx: Secp256k1, + node_id_lookup: NL, message_router: MR, offers_handler: OMH, custom_handler: CMH, @@ -579,13 +582,15 @@ pub enum PeeledOnion { /// /// Returns the node id of the peer to send the message to, the message itself, and any addresses /// need to connect to the first node. -pub fn create_onion_message( - entropy_source: &ES, node_signer: &NS, secp_ctx: &Secp256k1, - path: OnionMessagePath, contents: T, reply_path: Option, +pub fn create_onion_message( + entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, + secp_ctx: &Secp256k1, path: OnionMessagePath, contents: T, + reply_path: Option, ) -> Result<(PublicKey, OnionMessage, Option>), SendError> where ES::Target: EntropySource, NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, { let OnionMessagePath { intermediate_nodes, mut destination, first_node_addresses } = path; if let Destination::BlindedPath(BlindedPath { ref blinded_hops, .. }) = destination { @@ -600,16 +605,19 @@ where // 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)?; let introduction_node_id = match blinded_path.introduction_node { IntroductionNode::NodeId(pubkey) => pubkey, - IntroductionNode::DirectedShortChannelId(..) => { - return Err(SendError::UnresolvedIntroductionNode); + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match node_id_lookup.next_node_id(scid) { + Some(next_node_id) => *direction.select_pubkey(&our_node_id, &next_node_id), + None => return Err(SendError::UnresolvedIntroductionNode), + } }, }; - let our_node_id = node_signer.get_node_id(Recipient::Node) - .map_err(|()| SendError::GetNodeIdFailed)?; if introduction_node_id == our_node_id { - advance_path_by_one(blinded_path, node_signer, &secp_ctx) + advance_path_by_one(blinded_path, node_signer, node_id_lookup, &secp_ctx) .map_err(|()| SendError::BlindedPathAdvanceFailed)?; } } @@ -741,12 +749,13 @@ where } } -impl -OnionMessenger +impl +OnionMessenger where ES::Target: EntropySource, NS::Target: NodeSigner, L::Target: Logger, + NL::Target: NodeIdLookUp, MR::Target: MessageRouter, OMH::Target: OffersMessageHandler, CMH::Target: CustomOnionMessageHandler, @@ -754,8 +763,8 @@ where /// Constructs a new `OnionMessenger` to send, forward, and delegate received onion messages to /// their respective handlers. pub fn new( - entropy_source: ES, node_signer: NS, logger: L, message_router: MR, offers_handler: OMH, - custom_handler: CMH + entropy_source: ES, node_signer: NS, logger: L, node_id_lookup: NL, message_router: MR, + offers_handler: OMH, custom_handler: CMH ) -> Self { let mut secp_ctx = Secp256k1::new(); secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); @@ -765,6 +774,7 @@ where message_recipients: Mutex::new(new_hash_map()), secp_ctx, logger, + node_id_lookup, message_router, offers_handler, custom_handler, @@ -847,7 +857,8 @@ where log_trace!(self.logger, "Constructing onion message {}: {:?}", log_suffix, contents); let (first_node_id, onion_message, addresses) = create_onion_message( - &self.entropy_source, &self.node_signer, &self.secp_ctx, path, contents, reply_path + &self.entropy_source, &self.node_signer, &self.node_id_lookup, &self.secp_ctx, path, + contents, reply_path, )?; let mut message_recipients = self.message_recipients.lock().unwrap(); @@ -943,12 +954,13 @@ fn outbound_buffer_full(peer_node_id: &PublicKey, buffer: &HashMap EventsProvider -for OnionMessenger +impl EventsProvider +for OnionMessenger where ES::Target: EntropySource, NS::Target: NodeSigner, L::Target: Logger, + NL::Target: NodeIdLookUp, MR::Target: MessageRouter, OMH::Target: OffersMessageHandler, CMH::Target: CustomOnionMessageHandler, @@ -964,12 +976,13 @@ where } } -impl OnionMessageHandler -for OnionMessenger +impl OnionMessageHandler +for OnionMessenger where ES::Target: EntropySource, NS::Target: NodeSigner, L::Target: Logger, + NL::Target: NodeIdLookUp, MR::Target: MessageRouter, OMH::Target: OffersMessageHandler, CMH::Target: CustomOnionMessageHandler, @@ -1007,7 +1020,13 @@ where Ok(PeeledOnion::Forward(next_hop, onion_message)) => { let next_node_id = match next_hop { NextHop::NodeId(pubkey) => pubkey, - NextHop::ShortChannelId(_) => todo!(), + NextHop::ShortChannelId(scid) => match self.node_id_lookup.next_node_id(scid) { + Some(pubkey) => pubkey, + None => { + log_trace!(self.logger, "Dropping forwarded onion messager: unable to resolve next hop using SCID {}", scid); + return + }, + }, }; let mut message_recipients = self.message_recipients.lock().unwrap(); @@ -1145,6 +1164,7 @@ pub type SimpleArcOnionMessenger = OnionMessenger< Arc, Arc, Arc, + Arc>, Arc>>, Arc, Arc>>, Arc>, IgnoringMessageHandler @@ -1164,8 +1184,9 @@ pub type SimpleRefOnionMessenger< &'a KeysManager, &'a KeysManager, &'b L, - &'i DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>, - &'j SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>, + &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>, + &'j DefaultMessageRouter<&'g NetworkGraph<&'b L>, &'b L, &'a KeysManager>, + &'i SimpleRefChannelManager<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, M, T, F, L>, IgnoringMessageHandler >; -- 2.30.2