From d792afb08cdc20a30786728c3bfe816dd3b59e47 Mon Sep 17 00:00:00 2001 From: Jeffrey Czyz Date: Tue, 9 Apr 2024 16:14:29 -0500 Subject: [PATCH] Support NextHop::ShortChannelId in BlindedPath When sending an onion message to a blinded path, the short channel id between hops isn't need in each hop's encrypted_payload since it is not a payment. However, using the short channel id instead of the node id gives a more compact representation. Update BlindedPath::new_for_message to allow for this. --- fuzz/src/invoice_request_deser.rs | 15 +++++- fuzz/src/refund_deser.rs | 15 +++++- lightning/src/blinded_path/message.rs | 34 ++++++++++--- lightning/src/blinded_path/mod.rs | 16 +++--- .../src/onion_message/functional_tests.rs | 51 ++++++++++++++----- lightning/src/onion_message/messenger.rs | 18 +++++-- 6 files changed, 116 insertions(+), 33 deletions(-) diff --git a/fuzz/src/invoice_request_deser.rs b/fuzz/src/invoice_request_deser.rs index 414ce1cdd..1aad5bfd8 100644 --- a/fuzz/src/invoice_request_deser.rs +++ b/fuzz/src/invoice_request_deser.rs @@ -11,6 +11,7 @@ use bitcoin::secp256k1::{KeyPair, Parity, PublicKey, Secp256k1, SecretKey, self} use crate::utils::test_logger; use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::message::ForwardNode; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; use lightning::ln::features::BlindedHopFeatures; @@ -73,9 +74,19 @@ fn build_response( invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1 ) -> Result { let entropy_source = Randomness {}; + let intermediate_nodes = [ + [ + ForwardNode { node_id: pubkey(43), short_channel_id: None }, + ForwardNode { node_id: pubkey(44), short_channel_id: None }, + ], + [ + ForwardNode { node_id: pubkey(45), short_channel_id: None }, + ForwardNode { node_id: pubkey(46), short_channel_id: None }, + ], + ]; let paths = vec![ - BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(), - BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[0], pubkey(42), &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[1], pubkey(42), &entropy_source, secp_ctx).unwrap(), ]; let payinfo = vec![ diff --git a/fuzz/src/refund_deser.rs b/fuzz/src/refund_deser.rs index 14b136ec3..660ad5e2c 100644 --- a/fuzz/src/refund_deser.rs +++ b/fuzz/src/refund_deser.rs @@ -11,6 +11,7 @@ use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey, self}; use crate::utils::test_logger; use core::convert::TryFrom; use lightning::blinded_path::BlindedPath; +use lightning::blinded_path::message::ForwardNode; use lightning::sign::EntropySource; use lightning::ln::PaymentHash; use lightning::ln::features::BlindedHopFeatures; @@ -62,9 +63,19 @@ fn build_response( refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1 ) -> Result { let entropy_source = Randomness {}; + let intermediate_nodes = [ + [ + ForwardNode { node_id: pubkey(43), short_channel_id: None }, + ForwardNode { node_id: pubkey(44), short_channel_id: None }, + ], + [ + ForwardNode { node_id: pubkey(45), short_channel_id: None }, + ForwardNode { node_id: pubkey(46), short_channel_id: None }, + ], + ]; let paths = vec![ - BlindedPath::new_for_message(&[pubkey(43), pubkey(44), pubkey(42)], &entropy_source, secp_ctx).unwrap(), - BlindedPath::new_for_message(&[pubkey(45), pubkey(46), pubkey(42)], &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[0], pubkey(42), &entropy_source, secp_ctx).unwrap(), + BlindedPath::new_for_message(&intermediate_nodes[1], pubkey(42), &entropy_source, secp_ctx).unwrap(), ]; let payinfo = vec![ diff --git a/lightning/src/blinded_path/message.rs b/lightning/src/blinded_path/message.rs index 9a282a5f8..1a0f63d46 100644 --- a/lightning/src/blinded_path/message.rs +++ b/lightning/src/blinded_path/message.rs @@ -1,3 +1,7 @@ +//! Data structures and methods for constructing [`BlindedPath`]s to send a message over. +//! +//! [`BlindedPath`]: crate::blinded_path::BlindedPath + use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; #[allow(unused_imports)] @@ -16,6 +20,17 @@ use crate::util::ser::{FixedLengthReader, LengthReadableArgs, Writeable, Writer} use core::mem; use core::ops::Deref; +/// An intermediate node, and possibly a short channel id leading to the next node. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct ForwardNode { + /// This node's pubkey. + pub node_id: PublicKey, + /// The channel between `node_id` and the next hop. If set, the constructed [`BlindedHop`]'s + /// `encrypted_payload` will use this instead of the next [`ForwardNode::node_id`] for a more + /// compact representation. + pub short_channel_id: Option, +} + /// 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 { @@ -60,17 +75,24 @@ impl Writeable for ReceiveTlvs { } } -/// Construct blinded onion message hops for the given `unblinded_path`. +/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`. pub(super) fn blinded_hops( - secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], session_priv: &SecretKey + secp_ctx: &Secp256k1, intermediate_nodes: &[ForwardNode], recipient_node_id: PublicKey, + session_priv: &SecretKey ) -> Result, secp256k1::Error> { - let blinded_tlvs = unblinded_path.iter() + let pks = intermediate_nodes.iter().map(|node| &node.node_id) + .chain(core::iter::once(&recipient_node_id)); + let tlvs = pks.clone() .skip(1) // The first node's TLVs contains the next node's pubkey - .map(|pk| ForwardTlvs { next_hop: NextMessageHop::NodeId(*pk), next_blinding_override: None }) - .map(|tlvs| ControlTlvs::Forward(tlvs)) + .zip(intermediate_nodes.iter().map(|node| node.short_channel_id)) + .map(|(pubkey, scid)| match scid { + Some(scid) => NextMessageHop::ShortChannelId(scid), + None => NextMessageHop::NodeId(*pubkey), + }) + .map(|next_hop| ControlTlvs::Forward(ForwardTlvs { next_hop, next_blinding_override: None })) .chain(core::iter::once(ControlTlvs::Receive(ReceiveTlvs { path_id: None }))); - utils::construct_blinded_hops(secp_ctx, unblinded_path.iter(), blinded_tlvs, session_priv) + utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv) } // Advance the blinded onion message path by one hop, so make the second hop into the new diff --git a/lightning/src/blinded_path/mod.rs b/lightning/src/blinded_path/mod.rs index 7f4cfe2e3..5abf53ec1 100644 --- a/lightning/src/blinded_path/mod.rs +++ b/lightning/src/blinded_path/mod.rs @@ -10,7 +10,7 @@ //! Creating blinded paths and related utilities live here. pub mod payment; -pub(crate) mod message; +pub mod message; pub(crate) mod utils; use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; @@ -124,7 +124,7 @@ impl BlindedPath { pub fn one_hop_for_message( recipient_node_id: PublicKey, entropy_source: ES, secp_ctx: &Secp256k1 ) -> Result where ES::Target: EntropySource { - Self::new_for_message(&[recipient_node_id], entropy_source, secp_ctx) + Self::new_for_message(&[], recipient_node_id, entropy_source, secp_ctx) } /// Create a blinded path for an onion message, to be forwarded along `node_pks`. The last node @@ -133,17 +133,21 @@ impl BlindedPath { /// Errors if no hops are provided or if `node_pk`(s) are invalid. // TODO: make all payloads the same size with padding + add dummy hops pub fn new_for_message( - node_pks: &[PublicKey], entropy_source: ES, secp_ctx: &Secp256k1 + intermediate_nodes: &[message::ForwardNode], recipient_node_id: PublicKey, + entropy_source: ES, secp_ctx: &Secp256k1 ) -> Result where ES::Target: EntropySource { - if node_pks.is_empty() { return Err(()) } + let introduction_node = IntroductionNode::NodeId( + intermediate_nodes.first().map_or(recipient_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"); - let introduction_node = IntroductionNode::NodeId(node_pks[0]); Ok(BlindedPath { 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(|_| ())?, + blinded_hops: message::blinded_hops( + secp_ctx, intermediate_nodes, recipient_node_id, &blinding_secret, + ).map_err(|_| ())?, }) } diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 764a2bdcb..029038a9f 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -10,6 +10,7 @@ //! Onion message testing and test utilities live here. use crate::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; +use crate::blinded_path::message::ForwardNode; use crate::events::{Event, EventsProvider}; use crate::ln::features::{ChannelFeatures, InitFeatures}; use crate::ln::msgs::{self, DecodeError, OnionMessageHandler}; @@ -327,7 +328,7 @@ fn one_blinded_hop() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id], &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let blinded_path = BlindedPath::new_for_message(&[], nodes[1].node_id, &*nodes[1].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap(); nodes[1].custom_message_handler.expect_message(TestCustomMessage::Response); @@ -340,7 +341,8 @@ fn two_unblinded_two_blinded() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[3].node_id, nodes[4].node_id], &*nodes[4].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ForwardNode { node_id: nodes[3].node_id, short_channel_id: None }]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[4].node_id, &*nodes[4].entropy_source, &secp_ctx).unwrap(); let path = OnionMessagePath { intermediate_nodes: vec![nodes[1].node_id, nodes[2].node_id], destination: Destination::BlindedPath(blinded_path), @@ -358,7 +360,11 @@ fn three_blinded_hops() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id, nodes[2].node_id, nodes[3].node_id], &*nodes[3].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[3].node_id, &*nodes[3].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap(); @@ -391,7 +397,11 @@ fn we_are_intro_node() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_message(&[nodes[0].node_id, nodes[1].node_id, nodes[2].node_id], &*nodes[2].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[0].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[2].node_id, &*nodes[2].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg.clone(), destination, None).unwrap(); @@ -399,7 +409,8 @@ fn we_are_intro_node() { pass_along_path(&nodes); // Try with a two-hop blinded path where we are the introduction node. - let blinded_path = BlindedPath::new_for_message(&[nodes[0].node_id, nodes[1].node_id], &*nodes[1].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ForwardNode { node_id: nodes[0].node_id, short_channel_id: None }]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[1].node_id, &*nodes[1].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap(); nodes[1].custom_message_handler.expect_message(TestCustomMessage::Response); @@ -414,7 +425,8 @@ fn invalid_blinded_path_error() { let test_msg = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); - let mut blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id, nodes[2].node_id], &*nodes[2].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; + let mut blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[2].node_id, &*nodes[2].entropy_source, &secp_ctx).unwrap(); blinded_path.blinded_hops.clear(); let destination = Destination::BlindedPath(blinded_path); let err = nodes[0].messenger.send_onion_message(test_msg, destination, None).unwrap_err(); @@ -433,7 +445,11 @@ fn reply_path() { destination: Destination::Node(nodes[3].node_id), first_node_addresses: None, }; - let reply_path = BlindedPath::new_for_message(&[nodes[2].node_id, nodes[1].node_id, nodes[0].node_id], &*nodes[0].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ]; + let reply_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[0].node_id, &*nodes[0].entropy_source, &secp_ctx).unwrap(); nodes[0].messenger.send_onion_message_using_path(path, test_msg.clone(), Some(reply_path)).unwrap(); nodes[3].custom_message_handler.expect_message(TestCustomMessage::Request); pass_along_path(&nodes); @@ -443,9 +459,17 @@ fn reply_path() { pass_along_path(&nodes); // Destination::BlindedPath - let blinded_path = BlindedPath::new_for_message(&[nodes[1].node_id, nodes[2].node_id, nodes[3].node_id], &*nodes[3].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ]; + let blinded_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[3].node_id, &*nodes[3].entropy_source, &secp_ctx).unwrap(); let destination = Destination::BlindedPath(blinded_path); - let reply_path = BlindedPath::new_for_message(&[nodes[2].node_id, nodes[1].node_id, nodes[0].node_id], &*nodes[0].entropy_source, &secp_ctx).unwrap(); + let intermediate_nodes = [ + ForwardNode { node_id: nodes[2].node_id, short_channel_id: None }, + ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }, + ]; + let reply_path = BlindedPath::new_for_message(&intermediate_nodes, nodes[0].node_id, &*nodes[0].entropy_source, &secp_ctx).unwrap(); nodes[0].messenger.send_onion_message(test_msg, destination, Some(reply_path)).unwrap(); nodes[3].custom_message_handler.expect_message(TestCustomMessage::Request); @@ -525,8 +549,9 @@ fn requests_peer_connection_for_buffered_messages() { let secp_ctx = Secp256k1::new(); add_channel_to_graph(&nodes[0], &nodes[1], &secp_ctx, 42); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let blinded_path = BlindedPath::new_for_message( - &[nodes[1].node_id, nodes[2].node_id], &*nodes[0].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, &*nodes[0].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); @@ -562,8 +587,9 @@ fn drops_buffered_messages_waiting_for_peer_connection() { let secp_ctx = Secp256k1::new(); add_channel_to_graph(&nodes[0], &nodes[1], &secp_ctx, 42); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let blinded_path = BlindedPath::new_for_message( - &[nodes[1].node_id, nodes[2].node_id], &*nodes[0].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, &*nodes[0].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); @@ -611,8 +637,9 @@ fn intercept_offline_peer_oms() { let message = TestCustomMessage::Response; let secp_ctx = Secp256k1::new(); + let intermediate_nodes = [ForwardNode { node_id: nodes[1].node_id, short_channel_id: None }]; let blinded_path = BlindedPath::new_for_message( - &[nodes[1].node_id, nodes[2].node_id], &*nodes[2].entropy_source, &secp_ctx + &intermediate_nodes, nodes[2].node_id, &*nodes[2].entropy_source, &secp_ctx ).unwrap(); let destination = Destination::BlindedPath(blinded_path); diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index b10bd1851..9fd6fd95f 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, IntroductionNode, NextMessageHop, NodeIdLookUp}; -use crate::blinded_path::message::{advance_path_by_one, ForwardTlvs, ReceiveTlvs}; +use crate::blinded_path::message::{advance_path_by_one, ForwardNode, ForwardTlvs, ReceiveTlvs}; use crate::blinded_path::utils; use crate::events::{Event, EventHandler, EventsProvider}; use crate::sign::{EntropySource, NodeSigner, Recipient}; @@ -71,6 +71,7 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// # use bitcoin::hashes::hex::FromHex; /// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey, self}; /// # use lightning::blinded_path::{BlindedPath, EmptyNodeIdLookUp}; +/// # use lightning::blinded_path::message::ForwardNode; /// # use lightning::sign::{EntropySource, KeysManager}; /// # use lightning::ln::peer_handler::IgnoringMessageHandler; /// # use lightning::onion_message::messenger::{Destination, MessageRouter, OnionMessagePath, OnionMessenger}; @@ -145,8 +146,11 @@ pub(super) const MAX_TIMER_TICKS: usize = 2; /// /// // Create a blinded path to yourself, for someone to send an onion message to. /// # let your_node_id = hop_node_id1; -/// let hops = [hop_node_id3, hop_node_id4, your_node_id]; -/// let blinded_path = BlindedPath::new_for_message(&hops, &keys_manager, &secp_ctx).unwrap(); +/// let hops = [ +/// ForwardNode { node_id: hop_node_id3, short_channel_id: None }, +/// ForwardNode { node_id: hop_node_id4, short_channel_id: None }, +/// ]; +/// let blinded_path = BlindedPath::new_for_message(&hops, your_node_id, &keys_manager, &secp_ctx).unwrap(); /// /// // Send a custom onion message to a blinded path. /// let destination = Destination::BlindedPath(blinded_path); @@ -435,8 +439,12 @@ where }); let paths = peer_info.into_iter() - .map(|(pubkey, _, _)| vec![pubkey, recipient]) - .map(|node_pks| BlindedPath::new_for_message(&node_pks, &*self.entropy_source, secp_ctx)) + .map(|(node_id, _, _)| vec![ForwardNode { node_id, short_channel_id: None }]) + .map(|intermediate_nodes| { + BlindedPath::new_for_message( + &intermediate_nodes, recipient, &*self.entropy_source, secp_ctx + ) + }) .take(MAX_PATHS) .collect::, _>>(); -- 2.39.5