Support NextHop::ShortChannelId in BlindedPath
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 9 Apr 2024 21:14:29 +0000 (16:14 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 28 May 2024 21:35:41 +0000 (16:35 -0500)
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
fuzz/src/refund_deser.rs
lightning/src/blinded_path/message.rs
lightning/src/blinded_path/mod.rs
lightning/src/onion_message/functional_tests.rs
lightning/src/onion_message/messenger.rs

index 414ce1cdd1cd2c17431d68ea9c1015495b9e074a..1aad5bfd897b77f9eab9363611354e56e1f88def 100644 (file)
@@ -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<T: secp256k1::Signing + secp256k1::Verification>(
        invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>
 ) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
        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![
index 14b136ec35b6d06837c09e997523fc5d093944c1..660ad5e2c1dae2b6d1b4c0a45b92071be22d0d37 100644 (file)
@@ -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<T: secp256k1::Signing + secp256k1::Verification>(
        refund: &Refund, signing_pubkey: PublicKey, secp_ctx: &Secp256k1<T>
 ) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
        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![
index 9a282a5f87d42658599870dd7446d5eef2e1ea5c..1a0f63d46000401cd5ffbec7a87c1dd42bf7e786 100644 (file)
@@ -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<u64>,
+}
+
 /// 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<T: secp256k1::Signing + secp256k1::Verification>(
-       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
+       secp_ctx: &Secp256k1<T>, intermediate_nodes: &[ForwardNode], recipient_node_id: PublicKey,
+       session_priv: &SecretKey
 ) -> Result<Vec<BlindedHop>, 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
index 7f4cfe2e3008eb25e46ed8a8dc10a21bf13a6a7b..5abf53ec166561b351c17255eeaf7424cf2677fb 100644 (file)
@@ -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<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
                recipient_node_id: PublicKey, entropy_source: ES, secp_ctx: &Secp256k1<T>
        ) -> Result<Self, ()> 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<ES: Deref, T: secp256k1::Signing + secp256k1::Verification>(
-               node_pks: &[PublicKey], entropy_source: ES, secp_ctx: &Secp256k1<T>
+               intermediate_nodes: &[message::ForwardNode], recipient_node_id: PublicKey,
+               entropy_source: ES, secp_ctx: &Secp256k1<T>
        ) -> Result<Self, ()> 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(|_| ())?,
                })
        }
 
index 764a2bdcbdcd198f901e722e388d3d3bcc4e86b6..029038a9fe769cc258088cbac6e9230c6477e827 100644 (file)
@@ -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);
 
index b10bd185100ddfcd03a8ced2d68ef9677b1bf667..9fd6fd95f95a1ea3cdd82bb3ebaea9b0848f8ed4 100644 (file)
@@ -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::<Result<Vec<_>, _>>();