Support sending onion messages
authorValentine Wallace <vwallace@protonmail.com>
Sat, 28 May 2022 01:31:27 +0000 (18:31 -0700)
committerValentine Wallace <vwallace@protonmail.com>
Tue, 2 Aug 2022 23:17:27 +0000 (19:17 -0400)
This adds several utilities in service of then adding
OnionMessenger::send_onion_message, which can send to either an unblinded
pubkey or a blinded route. Sending custom TLVs and sending an onion message
containing a reply path are not yet supported.

We also need to split the construct_keys_callback macro into two macros to
avoid an unused assignment warning.

lightning/src/ln/onion_utils.rs
lightning/src/onion_message/blinded_route.rs
lightning/src/onion_message/messenger.rs
lightning/src/onion_message/mod.rs
lightning/src/onion_message/packet.rs
lightning/src/onion_message/utils.rs

index 914c8e03c804b2b1078fa74e57f3bd065230185d..ce91d0d04f753e20a6689133e13923a8903eaff3 100644 (file)
@@ -33,14 +33,14 @@ use io::{Cursor, Read};
 use core::convert::{AsMut, TryInto};
 use core::ops::Deref;
 
-pub(super) struct OnionKeys {
+pub(crate) struct OnionKeys {
        #[cfg(test)]
-       pub(super) shared_secret: SharedSecret,
+       pub(crate) shared_secret: SharedSecret,
        #[cfg(test)]
-       pub(super) blinding_factor: [u8; 32],
-       pub(super) ephemeral_pubkey: PublicKey,
-       pub(super) rho: [u8; 32],
-       pub(super) mu: [u8; 32],
+       pub(crate) blinding_factor: [u8; 32],
+       pub(crate) ephemeral_pubkey: PublicKey,
+       pub(crate) rho: [u8; 32],
+       pub(crate) mu: [u8; 32],
 }
 
 #[inline]
@@ -52,7 +52,7 @@ pub(crate) fn gen_rho_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
 }
 
 #[inline]
-pub(super) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
+pub(crate) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
        assert_eq!(shared_secret.len(), 32);
        ({
                let mut hmac = HmacEngine::<Sha256>::new(&[0x72, 0x68, 0x6f]); // rho
@@ -260,7 +260,23 @@ impl AsMut<[u8]> for FixedSizeOnionPacket {
        }
 }
 
-/// panics if route_size_insane(payloads)
+pub(crate) fn payloads_serialized_length<HD: Writeable>(payloads: &Vec<HD>) -> usize {
+       payloads.iter().map(|p| p.serialized_length() + 32 /* HMAC */).sum()
+}
+
+/// panics if payloads_serialized_length(payloads) > packet_data_len
+pub(crate) fn construct_onion_message_packet<HD: Writeable, P: Packet<Data = Vec<u8>>>(
+       payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, prng_seed: [u8; 32], packet_data_len: usize) -> P
+{
+       let mut packet_data = vec![0; packet_data_len];
+
+       let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]);
+       chacha.process_in_place(&mut packet_data);
+
+       construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, packet_data, None)
+}
+
+/// panics if payloads_serialized_length(payloads) > packet_data.len()
 fn construct_onion_packet_with_init_noise<HD: Writeable, P: Packet>(
        mut payloads: Vec<HD>, onion_keys: Vec<OnionKeys>, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> P
 {
index be6e2a01ca5ab8c8a28827555a1ae452ca156abf..d18372e3b009bb09d82a5d7b6eb62ebcdda7d4ca 100644 (file)
@@ -28,25 +28,25 @@ pub struct BlindedRoute {
        /// message's next hop and forward it along.
        ///
        /// [`encrypted_payload`]: BlindedHop::encrypted_payload
-       introduction_node_id: PublicKey,
+       pub(super) introduction_node_id: PublicKey,
        /// Used by the introduction node to decrypt its [`encrypted_payload`] to forward the onion
        /// message.
        ///
        /// [`encrypted_payload`]: BlindedHop::encrypted_payload
-       blinding_point: PublicKey,
+       pub(super) blinding_point: PublicKey,
        /// The hops composing the blinded route.
-       blinded_hops: Vec<BlindedHop>,
+       pub(super) blinded_hops: Vec<BlindedHop>,
 }
 
 /// Used to construct the blinded hops portion of a blinded route. These hops cannot be identified
 /// by outside observers and thus can be used to hide the identity of the recipient.
 pub struct BlindedHop {
        /// The blinded node id of this hop in a blinded route.
-       blinded_node_id: PublicKey,
+       pub(super) blinded_node_id: PublicKey,
        /// The encrypted payload intended for this hop in a blinded route.
        // The node sending to this blinded route will later encode this payload into the onion packet for
        // this hop.
-       encrypted_payload: Vec<u8>,
+       pub(super) encrypted_payload: Vec<u8>,
 }
 
 impl BlindedRoute {
@@ -78,7 +78,7 @@ fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
        let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
 
        let mut prev_ss_and_blinded_node_id = None;
-       utils::construct_keys_callback(secp_ctx, unblinded_path, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk| {
+       utils::construct_keys_callback(secp_ctx, unblinded_path, None, session_priv, |blinded_node_id, _, _, encrypted_payload_ss, unblinded_pk, _| {
                if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
                        if let Some(pk) = unblinded_pk {
                                let payload = ForwardTlvs {
@@ -117,10 +117,10 @@ fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec
 /// 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.
-       next_node_id: PublicKey,
+       pub(super) next_node_id: PublicKey,
        /// Senders to a blinded route use this value to concatenate the route they find to the
        /// introduction node with the blinded route.
-       next_blinding_override: Option<PublicKey>,
+       pub(super) next_blinding_override: Option<PublicKey>,
 }
 
 /// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
@@ -128,7 +128,7 @@ pub(crate) struct ReceiveTlvs {
        /// If `path_id` is `Some`, it is used to identify the blinded route that this onion message is
        /// sending to. This is useful for receivers to check that said blinded route is being used in
        /// the right context.
-       path_id: Option<[u8; 32]>,
+       pub(super) path_id: Option<[u8; 32]>,
 }
 
 impl Writeable for ForwardTlvs {
index e2d7b51a9b5255703108e7c4e1563d8b2fceb13c..f4cb57f28df3410949a67b8ffb3f7f7e48cc6ae1 100644 (file)
 //! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for
 //! more information.
 
-use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
 
 use chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Sign};
 use ln::msgs;
+use ln::onion_utils;
+use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs};
+use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
+use super::utils;
 use util::logger::Logger;
 
 use core::ops::Deref;
@@ -37,6 +41,23 @@ pub struct OnionMessenger<Signer: Sign, K: Deref, L: Deref>
        // custom_handler: CustomHandler, // handles custom onion messages
 }
 
+/// The destination of an onion message.
+pub enum Destination {
+       /// We're sending this onion message to a node.
+       Node(PublicKey),
+       /// We're sending this onion message to a blinded route.
+       BlindedRoute(BlindedRoute),
+}
+
+impl Destination {
+       pub(super) fn num_hops(&self) -> usize {
+               match self {
+                       Destination::Node(_) => 1,
+                       Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => blinded_hops.len(),
+               }
+       }
+}
+
 impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
        where K::Target: KeysInterface<Signer = Signer>,
              L::Target: Logger,
@@ -53,6 +74,36 @@ impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
                        logger,
                }
        }
+
+       /// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
+       pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), secp256k1::Error> {
+               let blinding_secret_bytes = self.keys_manager.get_secure_random_bytes();
+               let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
+               let (introduction_node_id, blinding_point) = if intermediate_nodes.len() != 0 {
+                       (intermediate_nodes[0], PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret))
+               } else {
+                       match destination {
+                               Destination::Node(pk) => (pk, PublicKey::from_secret_key(&self.secp_ctx, &blinding_secret)),
+                               Destination::BlindedRoute(BlindedRoute { introduction_node_id, blinding_point, .. }) =>
+                                       (introduction_node_id, blinding_point),
+                       }
+               };
+               let (packet_payloads, packet_keys) = packet_payloads_and_keys(
+                       &self.secp_ctx, intermediate_nodes, destination, &blinding_secret)?;
+
+               let prng_seed = self.keys_manager.get_secure_random_bytes();
+               let onion_packet = construct_onion_message_packet(packet_payloads, packet_keys, prng_seed);
+
+               let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap();
+               let pending_msgs = pending_per_peer_msgs.entry(introduction_node_id).or_insert(Vec::new());
+               pending_msgs.push(
+                       msgs::OnionMessage {
+                               blinding_point,
+                               onion_routing_packet: onion_packet,
+                       }
+               );
+               Ok(())
+       }
 }
 
 // TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it
@@ -69,3 +120,90 @@ pub type SimpleArcOnionMessenger<L> = OnionMessenger<InMemorySigner, Arc<KeysMan
 ///[`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager
 ///[`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager
 pub type SimpleRefOnionMessenger<'a, 'b, L> = OnionMessenger<InMemorySigner, &'a KeysManager, &'b L>;
+
+/// Construct onion packet payloads and keys for sending an onion message along the given
+/// `unblinded_path` to the given `destination`.
+fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
+       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, session_priv: &SecretKey
+) -> Result<(Vec<(Payload, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
+       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);
+
+       let (mut intro_node_id_blinding_pt, num_blinded_hops) = if let Destination::BlindedRoute(BlindedRoute {
+               introduction_node_id, blinding_point, blinded_hops }) = &destination {
+               (Some((*introduction_node_id, *blinding_point)), blinded_hops.len()) } else { (None, 0) };
+       let num_unblinded_hops = num_hops - num_blinded_hops;
+
+       let mut unblinded_path_idx = 0;
+       let mut blinded_path_idx = 0;
+       let mut prev_control_tlvs_ss = None;
+       utils::construct_keys_callback(secp_ctx, unblinded_path, Some(destination), session_priv, |_, onion_packet_ss, ephemeral_pubkey, control_tlvs_ss, unblinded_pk_opt, enc_payload_opt| {
+               if num_unblinded_hops != 0 && unblinded_path_idx < num_unblinded_hops {
+                       if let Some(ss) = prev_control_tlvs_ss.take() {
+                               payloads.push((Payload::Forward(ForwardControlTlvs::Unblinded(
+                                       ForwardTlvs {
+                                               next_node_id: unblinded_pk_opt.unwrap(),
+                                               next_blinding_override: None,
+                                       }
+                               )), ss));
+                       }
+                       prev_control_tlvs_ss = Some(control_tlvs_ss);
+                       unblinded_path_idx += 1;
+               } 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_blinding_override: Some(blinding_pt),
+                               })), control_tlvs_ss));
+                       }
+                       if let Some(encrypted_payload) = enc_payload_opt {
+                               payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(encrypted_payload)),
+                                       control_tlvs_ss));
+                       } else { debug_assert!(false); }
+                       blinded_path_idx += 1;
+               } else if blinded_path_idx < num_blinded_hops - 1 && enc_payload_opt.is_some() {
+                       payloads.push((Payload::Forward(ForwardControlTlvs::Blinded(enc_payload_opt.unwrap())),
+                               control_tlvs_ss));
+                       blinded_path_idx += 1;
+               } else if let Some(encrypted_payload) = enc_payload_opt {
+                       payloads.push((Payload::Receive {
+                               control_tlvs: ReceiveControlTlvs::Blinded(encrypted_payload),
+                       }, control_tlvs_ss));
+               }
+
+               let (rho, mu) = onion_utils::gen_rho_mu_from_shared_secret(onion_packet_ss.as_ref());
+               onion_packet_keys.push(onion_utils::OnionKeys {
+                       #[cfg(test)]
+                       shared_secret: onion_packet_ss,
+                       #[cfg(test)]
+                       blinding_factor: [0; 32],
+                       ephemeral_pubkey,
+                       rho,
+                       mu,
+               });
+       })?;
+
+       if let Some(control_tlvs_ss) = prev_control_tlvs_ss {
+               payloads.push((Payload::Receive {
+                       control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, })
+               }, control_tlvs_ss));
+       }
+
+       Ok((payloads, onion_packet_keys))
+}
+
+fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys: Vec<onion_utils::OnionKeys>, prng_seed: [u8; 32]) -> Packet {
+       // Spec rationale:
+       // "`len` allows larger messages to be sent than the standard 1300 bytes allowed for an HTLC
+       // onion, but this should be used sparingly as it is reduces anonymity set, hence the
+       // recommendation that it either look like an HTLC onion, or if larger, be a fixed size."
+       let payloads_ser_len = onion_utils::payloads_serialized_length(&payloads);
+       let hop_data_len = if payloads_ser_len <= SMALL_PACKET_HOP_DATA_LEN {
+               SMALL_PACKET_HOP_DATA_LEN
+       } else if payloads_ser_len <= BIG_PACKET_HOP_DATA_LEN {
+               BIG_PACKET_HOP_DATA_LEN
+       } else { payloads_ser_len };
+
+       onion_utils::construct_onion_message_packet::<_, _>(payloads, onion_keys, prng_seed, hop_data_len)
+}
index 291030f4e26c5ce7b9eccee6e33760debab430fe..9236341fa61d25b36e4b4d0e2b73692aaccccf73 100644 (file)
@@ -16,5 +16,5 @@ mod utils;
 
 // Re-export structs so they can be imported with just the `onion_message::` module prefix.
 pub use self::blinded_route::{BlindedRoute, BlindedHop};
-pub use self::messenger::{OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
+pub use self::messenger::{Destination, OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
 pub(crate) use self::packet::Packet;
index 37f178f5521ccfcc6f5f26738f949bc69945872b..ed0206a2adbd2c2f883902c215a94872a1a6116e 100644 (file)
@@ -13,12 +13,19 @@ use bitcoin::secp256k1::PublicKey;
 
 use ln::msgs::DecodeError;
 use ln::onion_utils;
+use super::blinded_route::{ForwardTlvs, ReceiveTlvs};
+use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
 use util::ser::{LengthRead, LengthReadable, Readable, Writeable, Writer};
 
 use core::cmp;
 use io;
 use prelude::*;
 
+// Per the spec, an onion message packet's `hop_data` field length should be
+// SMALL_PACKET_HOP_DATA_LEN if it fits, else BIG_PACKET_HOP_DATA_LEN if it fits.
+pub(super) const SMALL_PACKET_HOP_DATA_LEN: usize = 1300;
+pub(super) const BIG_PACKET_HOP_DATA_LEN: usize = 32768;
+
 #[derive(Clone, Debug, PartialEq)]
 pub(crate) struct Packet {
        version: u8,
@@ -80,3 +87,72 @@ impl LengthReadable for Packet {
                })
        }
 }
+
+/// Onion message payloads contain "control" TLVs and "data" TLVs. Control TLVs are used to route
+/// the onion message from hop to hop and for path verification, whereas data TLVs contain the onion
+/// message content itself, such as an invoice request.
+pub(super) enum Payload {
+       /// This payload is for an intermediate hop.
+       Forward(ForwardControlTlvs),
+       /// This payload is for the final hop.
+       Receive {
+               control_tlvs: ReceiveControlTlvs,
+               // Coming soon:
+               // reply_path: Option<BlindedRoute>,
+               // message: Message,
+       }
+}
+
+// Coming soon:
+// enum Message {
+//     InvoiceRequest(InvoiceRequest),
+//     Invoice(Invoice),
+//     InvoiceError(InvoiceError),
+//     CustomMessage<T>,
+// }
+
+/// Forward control TLVs in their blinded and unblinded form.
+pub(super) enum ForwardControlTlvs {
+       /// If we're sending to a blinded route, the node that constructed the blinded route has provided
+       /// this hop's control TLVs, already encrypted into bytes.
+       Blinded(Vec<u8>),
+       /// If we're constructing an onion message hop through an intermediate unblinded node, we'll need
+       /// to construct the intermediate hop's control TLVs in their unblinded state to avoid encoding
+       /// them into an intermediate Vec. See [`super::blinded_route::ForwardTlvs`] for more info.
+       Unblinded(ForwardTlvs),
+}
+
+/// Receive control TLVs in their blinded and unblinded form.
+pub(super) enum ReceiveControlTlvs {
+       /// See [`ForwardControlTlvs::Blinded`].
+       Blinded(Vec<u8>),
+       /// See [`ForwardControlTlvs::Unblinded`] and [`super::blinded_route::ReceiveTlvs`].
+       Unblinded(ReceiveTlvs),
+}
+
+// Uses the provided secret to simultaneously encode and encrypt the unblinded control TLVs.
+impl Writeable for (Payload, [u8; 32]) {
+       fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+               match &self.0 {
+                       Payload::Forward(ForwardControlTlvs::Blinded(encrypted_bytes)) |
+                       Payload::Receive { control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes)} => {
+                               encode_varint_length_prefixed_tlv!(w, {
+                                       (4, encrypted_bytes, vec_type)
+                               })
+                       },
+                       Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
+                               let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
+                               encode_varint_length_prefixed_tlv!(w, {
+                                       (4, write_adapter, required)
+                               })
+                       },
+                       Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs)} => {
+                               let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
+                               encode_varint_length_prefixed_tlv!(w, {
+                                       (4, write_adapter, required)
+                               })
+                       },
+               }
+               Ok(())
+       }
+}
index 785a373caa4c80d7783dc062208af9da28e087b3..9b95183e74bb8bc549ca0a6f252522ae80e050f7 100644 (file)
@@ -16,14 +16,16 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
 use bitcoin::secp256k1::ecdh::SharedSecret;
 
 use ln::onion_utils;
+use super::blinded_route::BlindedRoute;
+use super::messenger::Destination;
 
 use prelude::*;
 
 // TODO: DRY with onion_utils::construct_onion_keys_callback
 #[inline]
 pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verification,
-       FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>)>(
-       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey],
+       FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option<PublicKey>, Option<Vec<u8>>)>(
+       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Option<Destination>,
        session_priv: &SecretKey, mut callback: FType
 ) -> Result<(), secp256k1::Error> {
        let mut msg_blinding_point_priv = session_priv.clone();
@@ -32,7 +34,7 @@ pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verific
        let mut onion_packet_pubkey = msg_blinding_point.clone();
 
        macro_rules! build_keys {
-               ($pk: expr, $blinded: expr) => {
+               ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {{
                        let encrypted_data_ss = SharedSecret::new(&$pk, &msg_blinding_point_priv);
 
                        let blinded_hop_pk = if $blinded { $pk } else {
@@ -49,7 +51,14 @@ pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verific
 
                        let rho = onion_utils::gen_rho_from_shared_secret(encrypted_data_ss.as_ref());
                        let unblinded_pk_opt = if $blinded { None } else { Some($pk) };
-                       callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt);
+                       callback(blinded_hop_pk, onion_packet_ss, onion_packet_pubkey, rho, unblinded_pk_opt, $encrypted_payload);
+                       (encrypted_data_ss, onion_packet_ss)
+               }}
+       }
+
+       macro_rules! build_keys_in_loop {
+               ($pk: expr, $blinded: expr, $encrypted_payload: expr) => {
+                       let (encrypted_data_ss, onion_packet_ss) = build_keys!($pk, $blinded, $encrypted_payload);
 
                        let msg_blinding_point_blinding_factor = {
                                let mut sha = Sha256::engine();
@@ -73,7 +82,19 @@ pub(super) fn construct_keys_callback<T: secp256k1::Signing + secp256k1::Verific
        }
 
        for pk in unblinded_path {
-               build_keys!(*pk, false);
+               build_keys_in_loop!(*pk, false, None);
+       }
+       if let Some(dest) = destination {
+               match dest {
+                       Destination::Node(pk) => {
+                               build_keys!(pk, false, None);
+                       },
+                       Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => {
+                               for hop in blinded_hops {
+                                       build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload));
+                               }
+                       },
+               }
        }
        Ok(())
 }