Generalize onion message ForwardTlvs::next_node_id
authorJeffrey Czyz <jkczyz@gmail.com>
Sat, 2 Mar 2024 18:28:20 +0000 (12:28 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Thu, 11 Apr 2024 00:11:19 +0000 (19:11 -0500)
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
lightning/src/onion_message/messenger.rs
lightning/src/onion_message/packet.rs

index bdcbd7726f71189175407bc3dd125aad2c1db199..967ef612c318ad189ee93e0cb9c1b2dc63b8a529 100644 (file)
@@ -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<PublicKey>,
@@ -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<W: Writer>(&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<T: secp256k1::Signing + secp256k1::Verification>(
 ) -> Result<Vec<BlindedHop>, 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<NS: Deref, T: secp256k1::Signing + secp256k1::
        let mut s = Cursor::new(&encrypted_control_tlvs);
        let mut reader = FixedLengthReader::new(&mut s, encrypted_control_tlvs.len() as u64);
        match ChaChaPolyReadAdapter::read(&mut reader, rho) {
-               Ok(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(ForwardTlvs {
-                       mut next_node_id, next_blinding_override,
-               })}) => {
+               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 => {
index e213bcbb0e1dc50d9fda8983f164e82611db26d9..85e19cc56e535bb364117c271871922683292a26 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;
-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<T: OnionMessageContents> {
        /// 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<T>, Option<[u8; 32]>, Option<BlindedPath>)
 }
@@ -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<T: OnionMessageContents, S: secp256k1::Signing + sec
                                if let Some(ss) = prev_control_tlvs_ss.take() {
                                        payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
                                                ForwardTlvs {
-                                                       next_node_id: unblinded_pk_opt.unwrap(),
+                                                       next_hop: NextHop::NodeId(unblinded_pk_opt.unwrap()),
                                                        next_blinding_override: None,
                                                }
                                        )), ss));
@@ -1156,7 +1161,7 @@ fn packet_payloads_and_keys<T: OnionMessageContents, S: secp256k1::Signing + sec
                        } else if let Some((intro_node_id, blinding_pt)) = intro_node_id_blinding_pt.take() {
                                if let Some(control_tlvs_ss) = prev_control_tlvs_ss.take() {
                                        payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
-                                               next_node_id: intro_node_id,
+                                               next_hop: NextHop::NodeId(intro_node_id),
                                                next_blinding_override: Some(blinding_pt),
                                        })), control_tlvs_ss));
                                }
index d9349fdadbfaba6c1f0d08c265c48bf14da49bb7..510f0ea025a0d615b0f54292d865602ac7e103a6 100644 (file)
@@ -13,7 +13,7 @@ use bitcoin::secp256k1::PublicKey;
 use bitcoin::secp256k1::ecdh::SharedSecret;
 
 use crate::blinded_path::BlindedPath;
-use crate::blinded_path::message::{ForwardTlvs, ReceiveTlvs};
+use crate::blinded_path::message::{ForwardTlvs, NextHop, ReceiveTlvs};
 use crate::blinded_path::utils::Padding;
 use crate::ln::msgs::DecodeError;
 use crate::ln::onion_utils;
@@ -284,20 +284,26 @@ impl Readable for ControlTlvs {
        fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
                _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> = _padding;
-               let _short_channel_id: Option<u64> = _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 {