From d85e0d619a5225cbf73bdf7cac7f400529cb6a73 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sat, 2 Mar 2024 12:28:20 -0600 Subject: [PATCH 01/10] Generalize onion message ForwardTlvs::next_node_id Allow either using a node id or a short channel id when forwarding an onion message. This allows for a more compact representation of blinded paths, which is advantageous for reducing offer QR code size. Follow-up commits will implement handling the short channel id case as it requires looking up the destination node id. --- lightning/src/blinded_path/message.rs | 35 ++++++++++++++++++------ lightning/src/onion_message/messenger.rs | 21 ++++++++------ lightning/src/onion_message/packet.rs | 18 ++++++++---- 3 files changed, 51 insertions(+), 23 deletions(-) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index bdcbd7726f7..967ef612c31 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -19,8 +19,8 @@ use core::ops::Deref; /// TLVs to encode in an intermediate onion message packet's hop data. When provided in a blinded /// route, they are encoded into [`BlindedHop::encrypted_payload`]. pub(crate) struct ForwardTlvs { - /// The node id of the next hop in the onion message's path. - pub(crate) next_node_id: PublicKey, + /// The next hop in the onion message's path. + pub(crate) next_hop: NextHop, /// Senders to a blinded path use this value to concatenate the route they find to the /// introduction node with the blinded path. pub(crate) next_blinding_override: Option, @@ -34,11 +34,25 @@ pub(crate) struct ReceiveTlvs { pub(crate) path_id: Option<[u8; 32]>, } +/// The next hop to forward the onion message along its path. +#[derive(Debug)] +pub enum NextHop { + /// The node id of the next hop. + NodeId(PublicKey), + /// The short channel id leading to the next hop. + ShortChannelId(u64), +} + impl Writeable for ForwardTlvs { fn write(&self, writer: &mut W) -> Result<(), io::Error> { + let (next_node_id, short_channel_id) = match self.next_hop { + NextHop::NodeId(pubkey) => (Some(pubkey), None), + NextHop::ShortChannelId(scid) => (None, Some(scid)), + }; // TODO: write padding encode_tlv_stream!(writer, { - (4, self.next_node_id, required), + (2, short_channel_id, option), + (4, next_node_id, option), (8, self.next_blinding_override, option) }); Ok(()) @@ -61,9 +75,8 @@ pub(super) fn blinded_hops( ) -> Result, secp256k1::Error> { let blinded_tlvs = unblinded_path.iter() .skip(1) // The first node's TLVs contains the next node's pubkey - .map(|pk| { - ControlTlvs::Forward(ForwardTlvs { next_node_id: *pk, next_blinding_override: None }) - }) + .map(|pk| ForwardTlvs { next_hop: NextHop::NodeId(*pk), next_blinding_override: None }) + .map(|tlvs| ControlTlvs::Forward(tlvs)) .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None }))); utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv) @@ -80,9 +93,13 @@ pub(crate) fn advance_path_by_one { + Ok(ChaChaPolyReadAdapter { + readable: ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override }) + }) => { + let mut next_node_id = match next_hop { + NextHop::NodeId(pubkey) => pubkey, + NextHop::ShortChannelId(_) => todo!(), + }; let mut new_blinding_point = match next_blinding_override { Some(blinding_point) => blinding_point, None => { diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index e213bcbb0e1..85e19cc56e5 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -16,7 +16,7 @@ use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; -use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, NextHop, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; use crate::sign::{EntropySource, NodeSigner, Recipient}; @@ -538,7 +538,7 @@ pub trait CustomOnionMessageHandler { #[derive(Debug)] pub enum PeeledOnion { /// Forwarded onion, with the next node id and a new onion - Forward(PublicKey, OnionMessage), + Forward(NextHop, OnionMessage), /// Received onion message, with decrypted contents, path_id, and reply path Receive(ParsedOnionMessageContents, Option<[u8; 32]>, Option) } @@ -647,9 +647,9 @@ where Ok(PeeledOnion::Receive(message, path_id, reply_path)) }, Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs { - next_node_id, next_blinding_override + next_hop, 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 + // TODO: we need to check whether `next_hop` 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 @@ -685,7 +685,7 @@ where onion_routing_packet: outgoing_packet, }; - Ok(PeeledOnion::Forward(next_node_id, onion_message)) + Ok(PeeledOnion::Forward(next_hop, onion_message)) }, Err(e) => { log_trace!(logger, "Errored decoding onion message packet: {:?}", e); @@ -961,7 +961,12 @@ where }, } }, - Ok(PeeledOnion::Forward(next_node_id, onion_message)) => { + Ok(PeeledOnion::Forward(next_hop, onion_message)) => { + let next_node_id = match next_hop { + NextHop::NodeId(pubkey) => pubkey, + NextHop::ShortChannelId(_) => todo!(), + }; + let mut message_recipients = self.message_recipients.lock().unwrap(); if outbound_buffer_full(&next_node_id, &message_recipients) { log_trace!( @@ -1146,7 +1151,7 @@ fn packet_payloads_and_keys(r: &mut R) -> Result { _init_and_read_tlv_stream!(r, { (1, _padding, option), - (2, _short_channel_id, option), + (2, short_channel_id, option), (4, next_node_id, option), (6, path_id, option), (8, next_blinding_override, option), }); let _padding: Option = _padding; - let _short_channel_id: Option = _short_channel_id; - let valid_fwd_fmt = next_node_id.is_some() && path_id.is_none(); - let valid_recv_fmt = next_node_id.is_none() && next_blinding_override.is_none(); + let next_hop = match (short_channel_id, next_node_id) { + (Some(_), Some(_)) => return Err(DecodeError::InvalidValue), + (Some(scid), None) => Some(NextHop::ShortChannelId(scid)), + (None, Some(pubkey)) => Some(NextHop::NodeId(pubkey)), + (None, None) => None, + }; + + let valid_fwd_fmt = next_hop.is_some() && path_id.is_none(); + let valid_recv_fmt = next_hop.is_none() && next_blinding_override.is_none(); let payload_fmt = if valid_fwd_fmt { ControlTlvs::Forward(ForwardTlvs { - next_node_id: next_node_id.unwrap(), + next_hop: next_hop.unwrap(), next_blinding_override, }) } else if valid_recv_fmt { From b783736356d17704852ea121a325b2978884ad9a Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 12 Dec 2023 14:51:22 -0600 Subject: [PATCH 02/10] Add IndexedMap::get_key_value Useful for applying Option::map when needing both the key and value for when needing a reference to the key with a longer lifetime. --- lightning/src/util/indexed_map.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lightning/src/util/indexed_map.rs b/lightning/src/util/indexed_map.rs index 4f694bd2b6e..97788ffe68a 100644 --- a/lightning/src/util/indexed_map.rs +++ b/lightning/src/util/indexed_map.rs @@ -56,6 +56,11 @@ impl IndexedMap { self.map.get_mut(key) } + /// Fetches the key-value pair corresponding to the supplied key, if one exists. + pub fn get_key_value(&self, key: &K) -> Option<(&K, &V)> { + self.map.get_key_value(key) + } + #[inline] /// Returns true if an element with the given `key` exists in the map. pub fn contains_key(&self, key: &K) -> bool { From 63ebaccca3c284c28f24537c2c0c034f9cc9c3c4 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Mar 2024 16:42:30 -0500 Subject: [PATCH 03/10] Add BlindedPath::introduction_node_id method Blinded paths use a pubkey to identify the introduction node, but it will soon allow using a directed short channel id instead. Add an introduction_node_id method to BlindedPath to facilitate lookup in the latter case. --- lightning/src/blinded_path/mod.rs | 9 +++++++++ lightning/src/routing/router.rs | 20 ++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index e70f310f5e1..5d199269520 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -17,6 +17,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; use crate::ln::msgs::DecodeError; use crate::offers::invoice::BlindedPayInfo; +use crate::routing::gossip::{NodeId, ReadOnlyNetworkGraph}; use crate::sign::EntropySource; use crate::util::ser::{Readable, Writeable, Writer}; @@ -125,6 +126,14 @@ impl BlindedPath { ).map_err(|_| ())?, })) } + + /// Returns the introduction [`NodeId`] of the blinded path. + pub fn public_introduction_node_id<'a>( + &self, network_graph: &'a ReadOnlyNetworkGraph + ) -> Option<&'a NodeId> { + let node_id = NodeId::from_pubkey(&self.introduction_node_id); + network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key) + } } impl Writeable for BlindedPath { diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index e8276712ee8..b01d853be33 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -1863,17 +1863,17 @@ where L::Target: Logger { } }, Payee::Blinded { route_hints, .. } => { - if route_hints.iter().all(|(_, path)| &path.introduction_node_id == our_node_pubkey) { + if route_hints.iter().all(|(_, path)| path.public_introduction_node_id(network_graph) == Some(&our_node_id)) { return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError}); } for (_, blinded_path) in route_hints.iter() { if blinded_path.blinded_hops.len() == 0 { return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError}); - } else if &blinded_path.introduction_node_id == our_node_pubkey { + } else if blinded_path.public_introduction_node_id(network_graph) == Some(&our_node_id) { log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring"); } else if blinded_path.blinded_hops.len() == 1 && route_hints.iter().any( |(_, p)| p.blinded_hops.len() == 1 - && p.introduction_node_id != blinded_path.introduction_node_id) + && p.public_introduction_node_id(network_graph) != blinded_path.public_introduction_node_id(network_graph)) { return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError}); } @@ -5301,10 +5301,15 @@ mod tests { if let Some(bt) = &path.blinded_tail { assert_eq!(path.hops.len() + if bt.hops.len() == 1 { 0 } else { 1 }, 2); if bt.hops.len() > 1 { - assert_eq!(path.hops.last().unwrap().pubkey, + let network_graph = network_graph.read_only(); + assert_eq!( + NodeId::from_pubkey(&path.hops.last().unwrap().pubkey), payment_params.payee.blinded_route_hints().iter() .find(|(p, _)| p.htlc_maximum_msat == path.final_value_msat()) - .map(|(_, p)| p.introduction_node_id).unwrap()); + .and_then(|(_, p)| p.public_introduction_node_id(&network_graph)) + .copied() + .unwrap() + ); } else { assert_eq!(path.hops.last().unwrap().pubkey, nodes[2]); } @@ -7432,7 +7437,10 @@ mod tests { assert_eq!(tail.final_value_msat, 1001); let final_hop = route.paths[0].hops.last().unwrap(); - assert_eq!(final_hop.pubkey, blinded_path.introduction_node_id); + assert_eq!( + NodeId::from_pubkey(&final_hop.pubkey), + *blinded_path.public_introduction_node_id(&network_graph).unwrap() + ); if tail.hops.len() > 1 { assert_eq!(final_hop.fee_msat, blinded_payinfo.fee_base_msat as u64 + blinded_payinfo.fee_proportional_millionths as u64 * tail.final_value_msat / 1000000); From e0bc6fa8b2fc67baee03e86aee1e04f5191d08e9 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Wed, 20 Mar 2024 16:43:03 -0500 Subject: [PATCH 04/10] Add source reference to BlindedPathCandidate hop Blinded paths currently contain a node id for the introduction node. However, a compact representation will allow a directed short channel id instead. Update BlindedPathCandidate and OneHopBlindedPathCandidate to store a NodeId reference from either the NetworkGraph or from the user- provided first hops. This approach avoids looking up the introduction node id on demand, which may not be resolvable. Thus, that would require returning an Option from CandidateRouteHop::source and handle None accordingly. --- lightning/src/routing/router.rs | 68 +++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index b01d853be33..576c8a306c5 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -1181,6 +1181,11 @@ pub struct PrivateHopCandidate<'a> { /// A [`CandidateRouteHop::Blinded`] entry. #[derive(Clone, Debug)] pub struct BlindedPathCandidate<'a> { + /// The node id of the introduction node, resolved from either the [`NetworkGraph`] or first + /// hops. + /// + /// This is not exported to bindings users as lifetimes are not expressible in most languages. + pub source_node_id: &'a NodeId, /// Information about the blinded path including the fee, HTLC amount limits, and /// cryptographic material required to build an HTLC through the given path. /// @@ -1196,6 +1201,11 @@ pub struct BlindedPathCandidate<'a> { /// A [`CandidateRouteHop::OneHopBlinded`] entry. #[derive(Clone, Debug)] pub struct OneHopBlindedPathCandidate<'a> { + /// The node id of the introduction node, resolved from either the [`NetworkGraph`] or first + /// hops. + /// + /// This is not exported to bindings users as lifetimes are not expressible in most languages. + pub source_node_id: &'a NodeId, /// Information about the blinded path including the fee, HTLC amount limits, and /// cryptographic material required to build an HTLC terminating with the given path. /// @@ -1409,8 +1419,8 @@ impl<'a> CandidateRouteHop<'a> { CandidateRouteHop::FirstHop(hop) => *hop.payer_node_id, CandidateRouteHop::PublicHop(hop) => *hop.info.source(), CandidateRouteHop::PrivateHop(hop) => hop.hint.src_node_id.into(), - CandidateRouteHop::Blinded(hop) => hop.hint.1.introduction_node_id.into(), - CandidateRouteHop::OneHopBlinded(hop) => hop.hint.1.introduction_node_id.into(), + CandidateRouteHop::Blinded(hop) => *hop.source_node_id, + CandidateRouteHop::OneHopBlinded(hop) => *hop.source_node_id, } } /// Returns the target node id of this hop, if known. @@ -2515,26 +2525,38 @@ where L::Target: Logger { // earlier than general path finding, they will be somewhat prioritized, although currently // it matters only if the fees are exactly the same. for (hint_idx, hint) in payment_params.payee.blinded_route_hints().iter().enumerate() { - let intro_node_id = NodeId::from_pubkey(&hint.1.introduction_node_id); - let have_intro_node_in_graph = - // Only add the hops in this route to our candidate set if either - // we have a direct channel to the first hop or the first hop is - // in the regular network graph. - first_hop_targets.get(&intro_node_id).is_some() || - network_nodes.get(&intro_node_id).is_some(); - if !have_intro_node_in_graph || our_node_id == intro_node_id { continue } + // Only add the hops in this route to our candidate set if either + // we have a direct channel to the first hop or the first hop is + // in the regular network graph. + let source_node_id = match hint.1.public_introduction_node_id(network_graph) { + Some(node_id) => node_id, + None => { + let node_id = NodeId::from_pubkey(&hint.1.introduction_node_id); + match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) { + Some(node_id) => node_id, + None => continue, + } + }, + }; + if our_node_id == *source_node_id { continue } let candidate = if hint.1.blinded_hops.len() == 1 { - CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, hint_idx }) - } else { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, hint_idx }) }; + CandidateRouteHop::OneHopBlinded( + OneHopBlindedPathCandidate { source_node_id, hint, hint_idx } + ) + } else { + CandidateRouteHop::Blinded(BlindedPathCandidate { source_node_id, hint, hint_idx }) + }; let mut path_contribution_msat = path_value_msat; if let Some(hop_used_msat) = add_entry!(&candidate, 0, path_contribution_msat, 0, 0_u64, 0, 0) { path_contribution_msat = hop_used_msat; } else { continue } - if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hint.1.introduction_node_id)) { - sort_first_hop_channels(first_channels, &used_liquidities, recommended_value_msat, - our_node_pubkey); + if let Some(first_channels) = first_hop_targets.get(source_node_id) { + let mut first_channels = first_channels.clone(); + sort_first_hop_channels( + &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, @@ -2630,9 +2652,11 @@ where L::Target: Logger { .saturating_add(1); // Searching for a direct channel between last checked hop and first_hop_targets - if let Some(first_channels) = first_hop_targets.get_mut(target) { - sort_first_hop_channels(first_channels, &used_liquidities, - recommended_value_msat, our_node_pubkey); + if let Some(first_channels) = first_hop_targets.get(target) { + let mut first_channels = first_channels.clone(); + sort_first_hop_channels( + &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, @@ -2677,9 +2701,11 @@ where L::Target: Logger { // Note that we *must* check if the last hop was added as `add_entry` // always assumes that the third argument is a node to which we have a // path. - if let Some(first_channels) = first_hop_targets.get_mut(&NodeId::from_pubkey(&hop.src_node_id)) { - sort_first_hop_channels(first_channels, &used_liquidities, - recommended_value_msat, our_node_pubkey); + if let Some(first_channels) = first_hop_targets.get(&NodeId::from_pubkey(&hop.src_node_id)) { + let mut first_channels = first_channels.clone(); + sort_first_hop_channels( + &mut first_channels, &used_liquidities, recommended_value_msat, our_node_pubkey + ); for details in first_channels { let first_hop_candidate = CandidateRouteHop::FirstHop(FirstHopCandidate { details, payer_node_id: &our_node_id, From 7002180261d495f15c12f9a341af1e7dbcac2990 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 15 Mar 2024 16:52:36 -0500 Subject: [PATCH 05/10] Generalize BlindedPath::introduction_node_id field Allow using either a node id or a directed short channel id in blinded paths. This allows for a more compact representation of blinded paths, which is advantageous for reducing offer QR code size. Follow-up commits will implement handling the directed short channel id case in OnionMessenger as it requires resolving the introduction node in MessageRouter. --- fuzz/src/router.rs | 4 +- lightning/src/blinded_path/message.rs | 6 +- lightning/src/blinded_path/mod.rs | 90 +++++++++++++++++--- lightning/src/ln/offers_tests.rs | 30 +++---- lightning/src/offers/invoice.rs | 4 +- lightning/src/offers/offer.rs | 12 +-- lightning/src/offers/refund.rs | 12 +-- lightning/src/offers/test_utils.rs | 6 +- lightning/src/onion_message/messenger.rs | 40 ++++++--- lightning/src/routing/router.rs | 101 ++++++++++++++--------- lightning/src/routing/scoring.rs | 4 +- 11 files changed, 212 insertions(+), 97 deletions(-) diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs index ad4373c4793..afe028131c1 100644 --- a/fuzz/src/router.rs +++ b/fuzz/src/router.rs @@ -11,7 +11,7 @@ use bitcoin::blockdata::constants::ChainHash; use bitcoin::blockdata::script::Builder; use bitcoin::blockdata::transaction::TxOut; -use lightning::blinded_path::{BlindedHop, BlindedPath}; +use lightning::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use lightning::chain::transaction::OutPoint; use lightning::ln::ChannelId; use lightning::ln::channelmanager::{self, ChannelDetails, ChannelCounterparty}; @@ -363,7 +363,7 @@ pub fn do_test(data: &[u8], out: Out) { }); } (payinfo, BlindedPath { - introduction_node_id: hop.src_node_id, + introduction_node: IntroductionNode::NodeId(hop.src_node_id), blinding_point: dummy_pk, blinded_hops, }) diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 967ef612c31..1d2d624c8af 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}; +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::blinded_path::utils; use crate::io; use crate::io::Cursor; @@ -96,7 +96,7 @@ pub(crate) fn advance_path_by_one { - let mut next_node_id = match next_hop { + let next_node_id = match next_hop { NextHop::NodeId(pubkey) => pubkey, NextHop::ShortChannelId(_) => todo!(), }; @@ -108,7 +108,7 @@ pub(crate) fn advance_path_by_one Err(()) diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 5d199269520..daa9f033d94 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -29,11 +29,11 @@ use crate::prelude::*; #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BlindedPath { /// To send to a blinded path, the sender first finds a route to the unblinded - /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion + /// `introduction_node`, which can unblind its [`encrypted_payload`] to find out the onion /// message or payment's next hop and forward it along. /// /// [`encrypted_payload`]: BlindedHop::encrypted_payload - pub introduction_node_id: PublicKey, + pub introduction_node: IntroductionNode, /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion /// message or payment. /// @@ -43,6 +43,29 @@ pub struct BlindedPath { pub blinded_hops: Vec, } +/// The unblinded node in a [`BlindedPath`]. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub enum IntroductionNode { + /// The node id of the introduction node. + NodeId(PublicKey), + /// The short channel id of the channel leading to the introduction node. The [`Direction`] + /// identifies which side of the channel is the introduction node. + DirectedShortChannelId(Direction, u64), +} + +/// The side of a channel that is the [`IntroductionNode`] in a [`BlindedPath`]. [BOLT 7] defines +/// which nodes is which in the [`ChannelAnnouncement`] message. +/// +/// [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)] +pub enum Direction { + /// The lesser node id when compared lexicographically in ascending order. + NodeOne, + /// The greater node id when compared lexicographically in ascending order. + NodeTwo, +} + /// 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. @@ -75,10 +98,10 @@ impl BlindedPath { if node_pks.is_empty() { return Err(()) } 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 = node_pks[0]; + let introduction_node = IntroductionNode::NodeId(node_pks[0]); Ok(BlindedPath { - introduction_node_id, + introduction_node, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: message::blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?, }) @@ -112,6 +135,9 @@ impl BlindedPath { payee_tlvs: payment::ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: &ES, secp_ctx: &Secp256k1 ) -> Result<(BlindedPayInfo, Self), ()> { + let introduction_node = IntroductionNode::NodeId( + intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id) + ); let blinding_secret_bytes = entropy_source.get_secure_random_bytes(); let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted"); @@ -119,7 +145,7 @@ impl BlindedPath { intermediate_nodes, &payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta )?; Ok((blinded_payinfo, BlindedPath { - introduction_node_id: intermediate_nodes.first().map_or(payee_node_id, |n| n.node_id), + introduction_node, blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret), blinded_hops: payment::blinded_hops( secp_ctx, intermediate_nodes, payee_node_id, payee_tlvs, &blinding_secret @@ -127,18 +153,41 @@ impl BlindedPath { })) } - /// Returns the introduction [`NodeId`] of the blinded path. + /// Returns the introduction [`NodeId`] of the blinded path, if it is publicly reachable (i.e., + /// it is found in the network graph). pub fn public_introduction_node_id<'a>( &self, network_graph: &'a ReadOnlyNetworkGraph ) -> Option<&'a NodeId> { - let node_id = NodeId::from_pubkey(&self.introduction_node_id); - network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key) + match &self.introduction_node { + IntroductionNode::NodeId(pubkey) => { + let node_id = NodeId::from_pubkey(pubkey); + network_graph.nodes().get_key_value(&node_id).map(|(key, _)| key) + }, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + network_graph + .channel(*scid) + .map(|c| match direction { + Direction::NodeOne => &c.node_one, + Direction::NodeTwo => &c.node_two, + }) + }, + } } } impl Writeable for BlindedPath { fn write(&self, w: &mut W) -> Result<(), io::Error> { - self.introduction_node_id.write(w)?; + match &self.introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey.write(w)?, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match direction { + Direction::NodeOne => 0u8.write(w)?, + Direction::NodeTwo => 1u8.write(w)?, + } + scid.write(w)?; + }, + } + self.blinding_point.write(w)?; (self.blinded_hops.len() as u8).write(w)?; for hop in &self.blinded_hops { @@ -150,7 +199,17 @@ impl Writeable for BlindedPath { impl Readable for BlindedPath { fn read(r: &mut R) -> Result { - let introduction_node_id = Readable::read(r)?; + let mut first_byte: u8 = Readable::read(r)?; + let introduction_node = match first_byte { + 0 => IntroductionNode::DirectedShortChannelId(Direction::NodeOne, Readable::read(r)?), + 1 => IntroductionNode::DirectedShortChannelId(Direction::NodeTwo, Readable::read(r)?), + 2|3 => { + use io::Read; + let mut pubkey_read = core::slice::from_mut(&mut first_byte).chain(r.by_ref()); + IntroductionNode::NodeId(Readable::read(&mut pubkey_read)?) + }, + _ => return Err(DecodeError::InvalidValue), + }; let blinding_point = Readable::read(r)?; let num_hops: u8 = Readable::read(r)?; if num_hops == 0 { return Err(DecodeError::InvalidValue) } @@ -159,7 +218,7 @@ impl Readable for BlindedPath { blinded_hops.push(Readable::read(r)?); } Ok(BlindedPath { - introduction_node_id, + introduction_node, blinding_point, blinded_hops, }) @@ -171,3 +230,12 @@ impl_writeable!(BlindedHop, { encrypted_payload }); +impl Direction { + /// Returns the [`NodeId`] from the inputs corresponding to the direction. + pub fn select_node_id<'a>(&self, node_a: &'a NodeId, node_b: &'a NodeId) -> &'a NodeId { + match self { + Direction::NodeOne => core::cmp::min(node_a, node_b), + Direction::NodeTwo => core::cmp::max(node_a, node_b), + } + } +} diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index e75bd2c70e1..4c8c1ecba03 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -42,7 +42,7 @@ use bitcoin::network::constants::Network; use core::time::Duration; -use crate::blinded_path::BlindedPath; +use crate::blinded_path::{BlindedPath, IntroductionNode}; use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose}; use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry, self}; use crate::ln::functional_test_utils::*; @@ -260,8 +260,8 @@ fn prefers_non_tor_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), bob_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_ne!(path.introduction_node_id, bob_id); - assert_ne!(path.introduction_node_id, charlie_id); + assert_ne!(path.introduction_node, IntroductionNode::NodeId(bob_id)); + assert_ne!(path.introduction_node, IntroductionNode::NodeId(charlie_id)); } // Use a one-hop blinded path when Bob is announced and all his peers are Tor-only. @@ -275,7 +275,7 @@ fn prefers_non_tor_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), bob_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } } @@ -325,7 +325,7 @@ fn prefers_more_connected_nodes_in_blinded_paths() { assert_ne!(offer.signing_pubkey(), bob_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, nodes[4].node.get_our_node_id()); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(nodes[4].node.get_our_node_id())); } } @@ -374,7 +374,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { assert_ne!(offer.signing_pubkey(), alice_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } let payment_id = PaymentId([1; 32]); @@ -395,7 +395,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); assert_eq!(invoice_request.amount_msats(), None); assert_ne!(invoice_request.payer_id(), david_id); - assert_eq!(reply_path.unwrap().introduction_node_id, charlie_id); + assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(charlie_id)); let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); charlie.onion_messenger.handle_onion_message(&alice_id, &onion_message); @@ -408,7 +408,7 @@ fn creates_and_pays_for_offer_using_two_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } route_bolt12_payment(david, &[charlie, bob, alice], &invoice); @@ -469,7 +469,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { assert_ne!(refund.payer_id(), david_id); assert!(!refund.paths().is_empty()); for path in refund.paths() { - assert_eq!(path.introduction_node_id, charlie_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(charlie_id)); } expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -488,7 +488,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } route_bolt12_payment(david, &[charlie, bob, alice], &invoice); @@ -522,7 +522,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { assert_ne!(offer.signing_pubkey(), alice_id); assert!(!offer.paths().is_empty()); for path in offer.paths() { - assert_eq!(path.introduction_node_id, alice_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } let payment_id = PaymentId([1; 32]); @@ -535,7 +535,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message); assert_eq!(invoice_request.amount_msats(), None); assert_ne!(invoice_request.payer_id(), bob_id); - assert_eq!(reply_path.unwrap().introduction_node_id, bob_id); + assert_eq!(reply_path.unwrap().introduction_node, IntroductionNode::NodeId(bob_id)); let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap(); bob.onion_messenger.handle_onion_message(&alice_id, &onion_message); @@ -545,7 +545,7 @@ fn creates_and_pays_for_offer_using_one_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, alice_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } route_bolt12_payment(bob, &[alice], &invoice); @@ -585,7 +585,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { assert_ne!(refund.payer_id(), bob_id); assert!(!refund.paths().is_empty()); for path in refund.paths() { - assert_eq!(path.introduction_node_id, bob_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(bob_id)); } expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -599,7 +599,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() { assert_ne!(invoice.signing_pubkey(), alice_id); assert!(!invoice.payment_paths().is_empty()); for (_, path) in invoice.payment_paths() { - assert_eq!(path.introduction_node_id, alice_id); + assert_eq!(path.introduction_node, IntroductionNode::NodeId(alice_id)); } route_bolt12_payment(bob, &[alice], &invoice); diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index f2fb387942d..fbfcffe2ad8 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1455,7 +1455,7 @@ mod tests { use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures}; use crate::ln::inbound_payment::ExpandedKey; @@ -1804,7 +1804,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index f7b75138b51..d390552f866 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -1078,7 +1078,7 @@ mod tests { use bitcoin::secp256k1::Secp256k1; use core::num::NonZeroU64; use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::features::OfferFeatures; use crate::ln::inbound_payment::ExpandedKey; @@ -1249,7 +1249,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] }, @@ -1395,7 +1395,7 @@ mod tests { fn builds_offer_with_paths() { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1403,7 +1403,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1585,7 +1585,7 @@ mod tests { fn parses_offer_with_paths() { let offer = OfferBuilder::new("foo".into(), pubkey(42)) .path(BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1593,7 +1593,7 @@ mod tests { ], }) .path(BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, diff --git a/lightning/src/offers/refund.rs b/lightning/src/offers/refund.rs index 16014cd3c0b..03253fb6400 100644 --- a/lightning/src/offers/refund.rs +++ b/lightning/src/offers/refund.rs @@ -907,7 +907,7 @@ mod tests { use core::time::Duration; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::KeyMaterial; use crate::ln::channelmanager::PaymentId; use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures}; @@ -1062,7 +1062,7 @@ mod tests { let payment_id = PaymentId([1; 32]); let blinded_path = BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1151,7 +1151,7 @@ mod tests { fn builds_refund_with_paths() { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1159,7 +1159,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, @@ -1368,7 +1368,7 @@ mod tests { let past_expiry = Duration::from_secs(0); let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -1376,7 +1376,7 @@ mod tests { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, diff --git a/lightning/src/offers/test_utils.rs b/lightning/src/offers/test_utils.rs index b4329803016..149ba15c3a2 100644 --- a/lightning/src/offers/test_utils.rs +++ b/lightning/src/offers/test_utils.rs @@ -13,7 +13,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey}; use bitcoin::secp256k1::schnorr::Signature; use core::time::Duration; -use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::sign::EntropySource; use crate::ln::PaymentHash; use crate::ln::features::BlindedHopFeatures; @@ -69,7 +69,7 @@ pub(super) fn privkey(byte: u8) -> SecretKey { pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { let paths = vec![ BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] }, @@ -77,7 +77,7 @@ pub(crate) fn payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { ], }, BlindedPath { - introduction_node_id: pubkey(40), + introduction_node: IntroductionNode::NodeId(pubkey(40)), blinding_point: pubkey(41), blinded_hops: vec![ BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] }, diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 85e19cc56e5..5334d6db25e 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; +use crate::blinded_path::{BlindedPath, IntroductionNode}; use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, NextHop, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; @@ -444,7 +444,12 @@ impl Destination { fn first_node(&self) -> PublicKey { match self { Destination::Node(node_id) => *node_id, - Destination::BlindedPath(BlindedPath { introduction_node_id: node_id, .. }) => *node_id, + Destination::BlindedPath(BlindedPath { introduction_node, .. }) => { + match introduction_node { + IntroductionNode::NodeId(pubkey) => *pubkey, + IntroductionNode::DirectedShortChannelId(..) => todo!(), + } + }, } } } @@ -569,9 +574,13 @@ 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 introduction_node_id = match blinded_path.introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey, + IntroductionNode::DirectedShortChannelId(..) => todo!(), + }; let our_node_id = node_signer.get_node_id(Recipient::Node) .map_err(|()| SendError::GetNodeIdFailed)?; - if blinded_path.introduction_node_id == our_node_id { + if introduction_node_id == our_node_id { advance_path_by_one(blinded_path, node_signer, &secp_ctx) .map_err(|()| SendError::BlindedPathAdvanceFailed)?; } @@ -583,10 +592,14 @@ where let (first_node_id, blinding_point) = if let Some(first_node_id) = intermediate_nodes.first() { (*first_node_id, 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), + match &destination { + Destination::Node(pk) => (*pk, PublicKey::from_secret_key(&secp_ctx, &blinding_secret)), + Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, .. }) => { + match introduction_node { + IntroductionNode::NodeId(pubkey) => (*pubkey, *blinding_point), + IntroductionNode::DirectedShortChannelId(..) => todo!(), + } + } } }; let (packet_payloads, packet_keys) = packet_payloads_and_keys( @@ -1136,9 +1149,16 @@ fn packet_payloads_and_keys (None, 0), + Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, blinded_hops }) => { + let introduction_node_id = match introduction_node { + IntroductionNode::NodeId(pubkey) => pubkey, + IntroductionNode::DirectedShortChannelId(..) => todo!(), + }; + (Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) + }, + }; let num_unblinded_hops = num_hops - num_blinded_hops; let mut unblinded_path_idx = 0; diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index 576c8a306c5..f1901d7c501 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -11,7 +11,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, self}; -use crate::blinded_path::{BlindedHop, BlindedPath}; +use crate::blinded_path::{BlindedHop, BlindedPath, Direction, IntroductionNode}; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::ln::PaymentHash; use crate::ln::channelmanager::{ChannelDetails, PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA}; @@ -1735,8 +1735,20 @@ impl<'a> fmt::Display for LoggedCandidateHop<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { CandidateRouteHop::Blinded(BlindedPathCandidate { hint, .. }) | CandidateRouteHop::OneHopBlinded(OneHopBlindedPathCandidate { hint, .. }) => { - "blinded route hint with introduction node id ".fmt(f)?; - hint.1.introduction_node_id.fmt(f)?; + "blinded route hint with introduction node ".fmt(f)?; + match &hint.1.introduction_node { + IntroductionNode::NodeId(pubkey) => write!(f, "id {}", pubkey)?, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + match direction { + Direction::NodeOne => { + write!(f, "one on channel with SCID {}", scid)?; + }, + Direction::NodeTwo => { + write!(f, "two on channel with SCID {}", scid)?; + }, + } + } + } " and blinding point ".fmt(f)?; hint.1.blinding_point.fmt(f) }, @@ -2530,12 +2542,27 @@ where L::Target: Logger { // in the regular network graph. let source_node_id = match hint.1.public_introduction_node_id(network_graph) { Some(node_id) => node_id, - None => { - let node_id = NodeId::from_pubkey(&hint.1.introduction_node_id); - match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) { - Some(node_id) => node_id, - None => continue, - } + None => match &hint.1.introduction_node { + IntroductionNode::NodeId(pubkey) => { + let node_id = NodeId::from_pubkey(&pubkey); + match first_hop_targets.get_key_value(&node_id).map(|(key, _)| key) { + Some(node_id) => node_id, + None => continue, + } + }, + IntroductionNode::DirectedShortChannelId(direction, scid) => { + let first_hop = first_hop_targets.iter().find(|(_, channels)| + channels + .iter() + .any(|details| Some(*scid) == details.get_outbound_payment_scid()) + ); + match first_hop { + Some((counterparty_node_id, _)) => { + direction.select_node_id(&our_node_id, counterparty_node_id) + }, + None => continue, + } + }, }, }; if our_node_id == *source_node_id { continue } @@ -3249,7 +3276,7 @@ fn build_route_from_hops_internal( #[cfg(test)] mod tests { - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::routing::gossip::{NetworkGraph, P2PGossipSync, NodeId, EffectiveCapacity}; use crate::routing::utxo::UtxoResult; use crate::routing::router::{get_route, build_route_from_hops_internal, add_random_cltv_offset, default_node_features, @@ -5116,7 +5143,7 @@ mod tests { // MPP to a 1-hop blinded path for nodes[2] let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }], }; @@ -5134,18 +5161,18 @@ mod tests { // MPP to 3 2-hop blinded paths let mut blinded_path_node_0 = blinded_path.clone(); - blinded_path_node_0.introduction_node_id = nodes[0]; + blinded_path_node_0.introduction_node = IntroductionNode::NodeId(nodes[0]); blinded_path_node_0.blinded_hops.push(blinded_path.blinded_hops[0].clone()); let mut node_0_payinfo = blinded_payinfo.clone(); node_0_payinfo.htlc_maximum_msat = 50_000; let mut blinded_path_node_7 = blinded_path_node_0.clone(); - blinded_path_node_7.introduction_node_id = nodes[7]; + blinded_path_node_7.introduction_node = IntroductionNode::NodeId(nodes[7]); let mut node_7_payinfo = blinded_payinfo.clone(); node_7_payinfo.htlc_maximum_msat = 60_000; let mut blinded_path_node_1 = blinded_path_node_0.clone(); - blinded_path_node_1.introduction_node_id = nodes[1]; + blinded_path_node_1.introduction_node = IntroductionNode::NodeId(nodes[1]); let mut node_1_payinfo = blinded_payinfo.clone(); node_1_payinfo.htlc_maximum_msat = 180_000; @@ -7231,7 +7258,7 @@ mod tests { // Make sure this works for blinded route hints. let blinded_path = BlindedPath { - introduction_node_id: intermed_node_id, + introduction_node: IntroductionNode::NodeId(intermed_node_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42), encrypted_payload: vec![] }, @@ -7265,7 +7292,7 @@ mod tests { #[test] fn blinded_route_ser() { let blinded_path_1 = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(42), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(42)), blinding_point: ln_test_utils::pubkey(43), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(44), encrypted_payload: Vec::new() }, @@ -7273,7 +7300,7 @@ mod tests { ], }; let blinded_path_2 = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(46), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(46)), blinding_point: ln_test_utils::pubkey(47), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(48), encrypted_payload: Vec::new() }, @@ -7332,7 +7359,7 @@ mod tests { // account for the blinded tail's final amount_msat. let mut inflight_htlcs = InFlightHtlcs::new(); let blinded_path = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(43), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)), blinding_point: ln_test_utils::pubkey(48), blinded_hops: vec![BlindedHop { blinded_node_id: ln_test_utils::pubkey(49), encrypted_payload: Vec::new() }], }; @@ -7347,7 +7374,7 @@ mod tests { maybe_announced_channel: false, }, RouteHop { - pubkey: blinded_path.introduction_node_id, + pubkey: ln_test_utils::pubkey(43), node_features: NodeFeatures::empty(), short_channel_id: 43, channel_features: ChannelFeatures::empty(), @@ -7371,7 +7398,7 @@ mod tests { fn blinded_path_cltv_shadow_offset() { // Make sure we add a shadow offset when sending to blinded paths. let blinded_path = BlindedPath { - introduction_node_id: ln_test_utils::pubkey(43), + introduction_node: IntroductionNode::NodeId(ln_test_utils::pubkey(43)), blinding_point: ln_test_utils::pubkey(44), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(45), encrypted_payload: Vec::new() }, @@ -7389,7 +7416,7 @@ mod tests { maybe_announced_channel: false, }, RouteHop { - pubkey: blinded_path.introduction_node_id, + pubkey: ln_test_utils::pubkey(43), node_features: NodeFeatures::empty(), short_channel_id: 43, channel_features: ChannelFeatures::empty(), @@ -7431,7 +7458,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let mut blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: Vec::with_capacity(num_blinded_hops), }; @@ -7489,7 +7516,7 @@ mod tests { let random_seed_bytes = keys_manager.get_secure_random_bytes(); let mut invalid_blinded_path = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(43), encrypted_payload: vec![0; 43] }, @@ -7505,7 +7532,7 @@ mod tests { }; let mut invalid_blinded_path_2 = invalid_blinded_path.clone(); - invalid_blinded_path_2.introduction_node_id = ln_test_utils::pubkey(45); + invalid_blinded_path_2.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(45)); let payment_params = PaymentParameters::blinded(vec![ (blinded_payinfo.clone(), invalid_blinded_path.clone()), (blinded_payinfo.clone(), invalid_blinded_path_2)]); @@ -7519,7 +7546,7 @@ mod tests { _ => panic!("Expected error") } - invalid_blinded_path.introduction_node_id = our_id; + invalid_blinded_path.introduction_node = IntroductionNode::NodeId(our_id); let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo.clone(), invalid_blinded_path.clone())]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); match get_route(&our_id, &route_params, &network_graph, None, Arc::clone(&logger), &scorer, @@ -7531,7 +7558,7 @@ mod tests { _ => panic!("Expected error") } - invalid_blinded_path.introduction_node_id = ln_test_utils::pubkey(46); + invalid_blinded_path.introduction_node = IntroductionNode::NodeId(ln_test_utils::pubkey(46)); invalid_blinded_path.blinded_hops.clear(); let payment_params = PaymentParameters::blinded(vec![(blinded_payinfo, invalid_blinded_path)]); let route_params = RouteParameters::from_payment_params_and_value(payment_params, 1001); @@ -7560,7 +7587,7 @@ mod tests { let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let blinded_path_1 = BlindedPath { - introduction_node_id: nodes[2], + introduction_node: IntroductionNode::NodeId(nodes[2]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7657,7 +7684,7 @@ mod tests { get_channel_details(Some(1), nodes[1], InitFeatures::from_le_bytes(vec![0b11]), 10_000_000)]; let blinded_path = BlindedPath { - introduction_node_id: nodes[1], + introduction_node: IntroductionNode::NodeId(nodes[1]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7726,7 +7753,7 @@ mod tests { 18446744073709551615)]; let blinded_path = BlindedPath { - introduction_node_id: nodes[1], + introduction_node: IntroductionNode::NodeId(nodes[1]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7782,7 +7809,7 @@ mod tests { let amt_msat = 21_7020_5185_1423_0019; let blinded_path = BlindedPath { - introduction_node_id: our_id, + introduction_node: IntroductionNode::NodeId(our_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7801,7 +7828,7 @@ mod tests { (blinded_payinfo.clone(), blinded_path.clone()), (blinded_payinfo.clone(), blinded_path.clone()), ]; - blinded_hints[1].1.introduction_node_id = nodes[6]; + blinded_hints[1].1.introduction_node = IntroductionNode::NodeId(nodes[6]); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) @@ -7834,7 +7861,7 @@ mod tests { let amt_msat = 21_7020_5185_1423_0019; let blinded_path = BlindedPath { - introduction_node_id: our_id, + introduction_node: IntroductionNode::NodeId(our_id), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7858,7 +7885,7 @@ mod tests { blinded_hints[1].0.htlc_minimum_msat = 21_7020_5185_1423_0019; blinded_hints[1].0.htlc_maximum_msat = 1844_6744_0737_0955_1615; - blinded_hints[2].1.introduction_node_id = nodes[6]; + blinded_hints[2].1.introduction_node = IntroductionNode::NodeId(nodes[6]); let bolt12_features = channelmanager::provided_bolt12_invoice_features(&config); let payment_params = PaymentParameters::blinded(blinded_hints.clone()) @@ -7905,7 +7932,7 @@ mod tests { let htlc_min = 2_5165_8240; let payment_params = if blinded_payee { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -7985,7 +8012,7 @@ mod tests { let htlc_mins = [1_4392, 19_7401, 1027, 6_5535]; let payment_params = if blinded_payee { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8086,7 +8113,7 @@ mod tests { cltv_expiry_delta: 10, features: BlindedHopFeatures::empty(), }, BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, @@ -8136,7 +8163,7 @@ mod tests { let htlc_mins = [49_0000, 1125_0000]; let payment_params = { let blinded_path = BlindedPath { - introduction_node_id: nodes[0], + introduction_node: IntroductionNode::NodeId(nodes[0]), blinding_point: ln_test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: ln_test_utils::pubkey(42 as u8), encrypted_payload: Vec::new() }, diff --git a/lightning/src/routing/scoring.rs b/lightning/src/routing/scoring.rs index 4850479b899..4cb9144d339 100644 --- a/lightning/src/routing/scoring.rs +++ b/lightning/src/routing/scoring.rs @@ -2152,7 +2152,7 @@ impl Readable for ChannelLiquidity { #[cfg(test)] mod tests { use super::{ChannelLiquidity, HistoricalBucketRangeTracker, ProbabilisticScoringFeeParameters, ProbabilisticScoringDecayParameters, ProbabilisticScorer}; - use crate::blinded_path::{BlindedHop, BlindedPath}; + use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode}; use crate::util::config::UserConfig; use crate::ln::channelmanager; @@ -3567,7 +3567,7 @@ mod tests { let mut path = payment_path_for_amount(768); let recipient_hop = path.hops.pop().unwrap(); let blinded_path = BlindedPath { - introduction_node_id: path.hops.last().as_ref().unwrap().pubkey, + introduction_node: IntroductionNode::NodeId(path.hops.last().as_ref().unwrap().pubkey), blinding_point: test_utils::pubkey(42), blinded_hops: vec![ BlindedHop { blinded_node_id: test_utils::pubkey(44), encrypted_payload: Vec::new() } From 32a5139eb599f6ff565f4422e4da6e9421d74094 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 18 Mar 2024 21:58:04 -0500 Subject: [PATCH 06/10] Resolve IntroductionNode::DirectedShortChannelId When OnionMessenger creates an OnionMessage to a Destination, the latter may contain an IntroductionNode::DirectedShortChannelId inside a BlindedPath. Resolve these in DefaultMessageRouter and handle unresolved ones in OnionMessenger. --- lightning/src/onion_message/messenger.rs | 66 ++++++++++++++++++------ 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 5334d6db25e..5be5ba83e3a 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -23,7 +23,7 @@ use crate::sign::{EntropySource, NodeSigner, Recipient}; use crate::ln::features::{InitFeatures, NodeFeatures}; use crate::ln::msgs::{self, OnionMessage, OnionMessageHandler, SocketAddress}; use crate::ln::onion_utils; -use crate::routing::gossip::{NetworkGraph, NodeId}; +use crate::routing::gossip::{NetworkGraph, NodeId, ReadOnlyNetworkGraph}; use super::packet::OnionMessageContents; use super::packet::ParsedOnionMessageContents; use super::offers::OffersMessageHandler; @@ -318,15 +318,21 @@ where ES::Target: EntropySource, { fn find_path( - &self, sender: PublicKey, peers: Vec, destination: Destination + &self, sender: PublicKey, peers: Vec, mut destination: Destination ) -> Result { - let first_node = destination.first_node(); + let network_graph = self.network_graph.deref().read_only(); + destination.resolve(&network_graph); + + let first_node = match destination.first_node() { + Some(first_node) => first_node, + None => return Err(()), + }; + if peers.contains(&first_node) || sender == first_node { Ok(OnionMessagePath { intermediate_nodes: vec![], destination, first_node_addresses: None }) } else { - let network_graph = self.network_graph.deref().read_only(); let node_announcement = network_graph .node(&NodeId::from_pubkey(&first_node)) .and_then(|node_info| node_info.announcement_info.as_ref()) @@ -416,11 +422,11 @@ pub struct OnionMessagePath { impl OnionMessagePath { /// Returns the first node in the path. - pub fn first_node(&self) -> PublicKey { + pub fn first_node(&self) -> Option { self.intermediate_nodes .first() .copied() - .unwrap_or_else(|| self.destination.first_node()) + .or_else(|| self.destination.first_node()) } } @@ -434,6 +440,22 @@ pub enum Destination { } impl Destination { + /// Attempts to resolve the [`IntroductionNode::DirectedShortChannelId`] of a + /// [`Destination::BlindedPath`] to a [`IntroductionNode::NodeId`], if applicable, using the + /// provided [`ReadOnlyNetworkGraph`]. + pub fn resolve(&mut self, network_graph: &ReadOnlyNetworkGraph) { + if let Destination::BlindedPath(path) = self { + if let IntroductionNode::DirectedShortChannelId(..) = path.introduction_node { + if let Some(pubkey) = path + .public_introduction_node_id(network_graph) + .and_then(|node_id| node_id.as_pubkey().ok()) + { + path.introduction_node = IntroductionNode::NodeId(pubkey); + } + } + } + } + pub(super) fn num_hops(&self) -> usize { match self { Destination::Node(_) => 1, @@ -441,13 +463,13 @@ impl Destination { } } - fn first_node(&self) -> PublicKey { + fn first_node(&self) -> Option { match self { - Destination::Node(node_id) => *node_id, + Destination::Node(node_id) => Some(*node_id), Destination::BlindedPath(BlindedPath { introduction_node, .. }) => { match introduction_node { - IntroductionNode::NodeId(pubkey) => *pubkey, - IntroductionNode::DirectedShortChannelId(..) => todo!(), + IntroductionNode::NodeId(pubkey) => Some(*pubkey), + IntroductionNode::DirectedShortChannelId(..) => None, } }, } @@ -492,6 +514,10 @@ pub enum SendError { /// /// [`NodeSigner`]: crate::sign::NodeSigner GetNodeIdFailed, + /// The provided [`Destination`] has a blinded path with an unresolved introduction node. An + /// attempt to resolve it in the [`MessageRouter`] when finding an [`OnionMessagePath`] likely + /// failed. + UnresolvedIntroductionNode, /// We attempted to send to a blinded path where we are the introduction node, and failed to /// advance the blinded path to make the second hop the new introduction node. Either /// [`NodeSigner::ecdh`] failed, we failed to tweak the current blinding point to get the @@ -576,7 +602,9 @@ where if let Destination::BlindedPath(ref mut blinded_path) = destination { let introduction_node_id = match blinded_path.introduction_node { IntroductionNode::NodeId(pubkey) => pubkey, - IntroductionNode::DirectedShortChannelId(..) => todo!(), + IntroductionNode::DirectedShortChannelId(..) => { + return Err(SendError::UnresolvedIntroductionNode); + }, }; let our_node_id = node_signer.get_node_id(Recipient::Node) .map_err(|()| SendError::GetNodeIdFailed)?; @@ -597,14 +625,16 @@ where Destination::BlindedPath(BlindedPath { introduction_node, blinding_point, .. }) => { match introduction_node { IntroductionNode::NodeId(pubkey) => (*pubkey, *blinding_point), - IntroductionNode::DirectedShortChannelId(..) => todo!(), + IntroductionNode::DirectedShortChannelId(..) => { + return Err(SendError::UnresolvedIntroductionNode); + }, } } } }; let (packet_payloads, packet_keys) = packet_payloads_and_keys( - &secp_ctx, &intermediate_nodes, destination, contents, reply_path, &blinding_secret) - .map_err(|e| SendError::Secp256k1(e))?; + &secp_ctx, &intermediate_nodes, destination, contents, reply_path, &blinding_secret + )?; let prng_seed = entropy_source.get_secure_random_bytes(); let onion_routing_packet = construct_onion_message_packet( @@ -1144,7 +1174,7 @@ pub type SimpleRefOnionMessenger< fn packet_payloads_and_keys( secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], destination: Destination, message: T, mut reply_path: Option, session_priv: &SecretKey -) -> Result<(Vec<(Payload, [u8; 32])>, Vec), secp256k1::Error> { +) -> Result<(Vec<(Payload, [u8; 32])>, Vec), SendError> { let num_hops = unblinded_path.len() + destination.num_hops(); let mut payloads = Vec::with_capacity(num_hops); let mut onion_packet_keys = Vec::with_capacity(num_hops); @@ -1154,7 +1184,9 @@ fn packet_payloads_and_keys { let introduction_node_id = match introduction_node { IntroductionNode::NodeId(pubkey) => pubkey, - IntroductionNode::DirectedShortChannelId(..) => todo!(), + IntroductionNode::DirectedShortChannelId(..) => { + return Err(SendError::UnresolvedIntroductionNode); + }, }; (Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) }, @@ -1206,7 +1238,7 @@ fn packet_payloads_and_keys Date: Wed, 20 Mar 2024 14:31:30 -0500 Subject: [PATCH 07/10] 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 f2bae246fab..91fcb9bf2d4 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 1d2d624c8af..df7f8e7ad61 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 daa9f033d94..07fa7b77024 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 fa8a0b2163d..49ced4db335 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 3a506b57fe2..ee4c027a10e 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 5be5ba83e3a..3074ee64bb6 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 >; From 3f90d77122edd1d7cc6fc484b2fa55353c524dcb Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Fri, 29 Mar 2024 11:24:54 -0500 Subject: [PATCH 08/10] Cache introduction_node_id lookup in get_route --- lightning/src/routing/router.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index f1901d7c501..b25820eca26 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -1874,6 +1874,9 @@ where L::Target: Logger { return Err(LightningError{err: "Cannot send a payment of 0 msat".to_owned(), action: ErrorAction::IgnoreError}); } + let introduction_node_id_cache = payment_params.payee.blinded_route_hints().iter() + .map(|(_, path)| path.public_introduction_node_id(network_graph)) + .collect::>(); match &payment_params.payee { Payee::Clear { route_hints, node_id, .. } => { for route in route_hints.iter() { @@ -1885,17 +1888,19 @@ where L::Target: Logger { } }, Payee::Blinded { route_hints, .. } => { - if route_hints.iter().all(|(_, path)| path.public_introduction_node_id(network_graph) == Some(&our_node_id)) { + if introduction_node_id_cache.iter().all(|introduction_node_id| *introduction_node_id == Some(&our_node_id)) { return Err(LightningError{err: "Cannot generate a route to blinded paths if we are the introduction node to all of them".to_owned(), action: ErrorAction::IgnoreError}); } - for (_, blinded_path) in route_hints.iter() { + for ((_, blinded_path), introduction_node_id) in route_hints.iter().zip(introduction_node_id_cache.iter()) { if blinded_path.blinded_hops.len() == 0 { return Err(LightningError{err: "0-hop blinded path provided".to_owned(), action: ErrorAction::IgnoreError}); - } else if blinded_path.public_introduction_node_id(network_graph) == Some(&our_node_id) { + } else if *introduction_node_id == Some(&our_node_id) { log_info!(logger, "Got blinded path with ourselves as the introduction node, ignoring"); } else if blinded_path.blinded_hops.len() == 1 && - route_hints.iter().any( |(_, p)| p.blinded_hops.len() == 1 - && p.public_introduction_node_id(network_graph) != blinded_path.public_introduction_node_id(network_graph)) + route_hints + .iter().zip(introduction_node_id_cache.iter()) + .filter(|((_, p), _)| p.blinded_hops.len() == 1) + .any(|(_, p_introduction_node_id)| p_introduction_node_id != introduction_node_id) { return Err(LightningError{err: format!("1-hop blinded paths must all have matching introduction node ids"), action: ErrorAction::IgnoreError}); } @@ -2540,7 +2545,7 @@ where L::Target: Logger { // Only add the hops in this route to our candidate set if either // we have a direct channel to the first hop or the first hop is // in the regular network graph. - let source_node_id = match hint.1.public_introduction_node_id(network_graph) { + let source_node_id = match introduction_node_id_cache[hint_idx] { Some(node_id) => node_id, None => match &hint.1.introduction_node { IntroductionNode::NodeId(pubkey) => { From b7f73fa18866324017406dd7003b2dde86108d16 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Sun, 7 Apr 2024 21:10:29 +0200 Subject: [PATCH 09/10] Resolve blinded path when creating onion message Add a version of the create_onion_message utility function that attempts to resolved the introduction node of the destination's BlindedPath using a ReadOnlyNetworkGraph`. Otherwise, if given a path using the compact representation, using create_onion_message would fail. Keep the current version for use internally and for external uses where the blinded path is known to be resolved. --- lightning/src/onion_message/messenger.rs | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 3074ee64bb6..1ce0b1805bc 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -577,11 +577,42 @@ pub enum PeeledOnion { Receive(ParsedOnionMessageContents, Option<[u8; 32]>, Option) } + +/// Creates an [`OnionMessage`] with the given `contents` for sending to the destination of +/// `path`, first calling [`Destination::resolve`] on `path.destination` with the given +/// [`ReadOnlyNetworkGraph`]. +/// +/// Returns the node id of the peer to send the message to, the message itself, and any addresses +/// needed to connect to the first node. +pub fn create_onion_message_resolving_destination< + ES: Deref, NS: Deref, NL: Deref, T: OnionMessageContents +>( + entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, + network_graph: &ReadOnlyNetworkGraph, secp_ctx: &Secp256k1, + mut path: OnionMessagePath, contents: T, reply_path: Option, +) -> Result<(PublicKey, OnionMessage, Option>), SendError> +where + ES::Target: EntropySource, + NS::Target: NodeSigner, + NL::Target: NodeIdLookUp, +{ + path.destination.resolve(network_graph); + create_onion_message( + entropy_source, node_signer, node_id_lookup, secp_ctx, path, contents, reply_path, + ) +} + /// Creates an [`OnionMessage`] with the given `contents` for sending to the destination of /// `path`. /// /// 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. +/// +/// Returns [`SendError::UnresolvedIntroductionNode`] if: +/// - `destination` contains a blinded path with an [`IntroductionNode::DirectedShortChannelId`], +/// - unless it can be resolved by [`NodeIdLookUp::next_node_id`]. +/// Use [`create_onion_message_resolving_destination`] instead to resolve the introduction node +/// first with a [`ReadOnlyNetworkGraph`]. pub fn create_onion_message( entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL, secp_ctx: &Secp256k1, path: OnionMessagePath, contents: T, From fe114efa8bd3214dde08cf5073a8b4898f49a435 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Mon, 15 Apr 2024 11:00:28 -0500 Subject: [PATCH 10/10] Fix typos --- lightning/src/onion_message/messenger.rs | 2 +- lightning/src/routing/router.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 1ce0b1805bc..1d7a730fa36 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -606,7 +606,7 @@ where /// `path`. /// /// 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. +/// needed to connect to the first node. /// /// Returns [`SendError::UnresolvedIntroductionNode`] if: /// - `destination` contains a blinded path with an [`IntroductionNode::DirectedShortChannelId`], diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index b25820eca26..59ec3f61862 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -1144,11 +1144,11 @@ pub struct FirstHopCandidate<'a> { /// /// [`find_route`] validates this prior to constructing a [`CandidateRouteHop`]. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub details: &'a ChannelDetails, /// The node id of the payer, which is also the source side of this candidate route hop. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub payer_node_id: &'a NodeId, } @@ -1158,7 +1158,7 @@ pub struct PublicHopCandidate<'a> { /// Information about the channel, including potentially its capacity and /// direction-specific information. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub info: DirectedChannelInfo<'a>, /// The short channel ID of the channel, i.e. the identifier by which we refer to this /// channel. @@ -1170,11 +1170,11 @@ pub struct PublicHopCandidate<'a> { pub struct PrivateHopCandidate<'a> { /// Information about the private hop communicated via BOLT 11. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub hint: &'a RouteHintHop, /// Node id of the next hop in BOLT 11 route hint. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub target_node_id: &'a NodeId } @@ -1189,7 +1189,7 @@ pub struct BlindedPathCandidate<'a> { /// Information about the blinded path including the fee, HTLC amount limits, and /// cryptographic material required to build an HTLC through the given path. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub hint: &'a (BlindedPayInfo, BlindedPath), /// Index of the hint in the original list of blinded hints. /// @@ -1211,7 +1211,7 @@ pub struct OneHopBlindedPathCandidate<'a> { /// /// Note that the [`BlindedPayInfo`] is ignored here. /// - /// This is not exported to bindings users as lifetimes are not expressable in most languages. + /// This is not exported to bindings users as lifetimes are not expressible in most languages. pub hint: &'a (BlindedPayInfo, BlindedPath), /// Index of the hint in the original list of blinded hints. ///