Add onion messages module + enable the construction of blinded routes
authorValentine Wallace <vwallace@protonmail.com>
Fri, 27 May 2022 23:25:15 +0000 (16:25 -0700)
committerValentine Wallace <vwallace@protonmail.com>
Tue, 2 Aug 2022 16:11:11 +0000 (12:11 -0400)
Blinded routes can be provided as destinations for onion messages, when the
recipient prefers to remain anonymous.

We also add supporting utilities for constructing blinded path keys, and
control TLVs structs representing blinded payloads prior to being
encoded/encrypted. These utilities and struct will be re-used in upcoming
commits for sending and receiving/forwarding onion messages.

Finally, add utilities for reading the padding from an onion message's
encrypted TLVs without an intermediate Vec.

lightning/src/lib.rs
lightning/src/ln/mod.rs
lightning/src/ln/onion_utils.rs
lightning/src/onion_message/blinded_route.rs [new file with mode: 0644]
lightning/src/onion_message/mod.rs [new file with mode: 0644]
lightning/src/onion_message/utils.rs [new file with mode: 0644]

index ba6d6bc71910e53ad37236dc30b6e19cd947a4d9..2e6b3ab3c0a99cb2d438b15982ed954d4bd35e94 100644 (file)
@@ -17,7 +17,7 @@
 //! figure out how best to make networking happen/timers fire/things get written to disk/keys get
 //! generated/etc. This makes it a good candidate for tight integration into an existing wallet
 //! instead of having a rather-separate lightning appendage to a wallet.
-//! 
+//!
 //! `default` features are:
 //!
 //! * `std` - enables functionalities which require `std`, including `std::io` trait implementations and things which utilize time
@@ -76,6 +76,8 @@ pub mod util;
 pub mod chain;
 pub mod ln;
 pub mod routing;
+#[allow(unused)]
+mod onion_message; // To be exposed after sending/receiving OMs is supported in PeerManager.
 
 #[cfg(feature = "std")]
 /// Re-export of either `core2::io` or `std::io`, depending on the `std` feature flag.
index 9522e83d18d1cbfa02ddcbe9d0b7ef91ba2d4925..c2190d62a115ffc3eb37cbc42bff8d85b0a8c2a4 100644 (file)
@@ -43,7 +43,7 @@ pub mod channel;
 #[cfg(not(fuzzing))]
 pub(crate) mod channel;
 
-mod onion_utils;
+pub(crate) mod onion_utils;
 pub mod wire;
 
 // Older rustc (which we support) refuses to let us call the get_payment_preimage_hash!() macro
index b223a344dbe204aafcd511c5efc2d4e035cf1ba9..57228b92d04e452268703df0c44947f0daba06e0 100644 (file)
@@ -43,6 +43,14 @@ pub(super) struct OnionKeys {
        pub(super) mu: [u8; 32],
 }
 
+#[inline]
+pub(crate) fn gen_rho_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] {
+       assert_eq!(shared_secret.len(), 32);
+       let mut hmac = HmacEngine::<Sha256>::new(&[0x72, 0x68, 0x6f]); // rho
+       hmac.input(&shared_secret);
+       Hmac::from_engine(hmac).into_inner()
+}
+
 #[inline]
 pub(super) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) {
        assert_eq!(shared_secret.len(), 32);
diff --git a/lightning/src/onion_message/blinded_route.rs b/lightning/src/onion_message/blinded_route.rs
new file mode 100644 (file)
index 0000000..be6e2a0
--- /dev/null
@@ -0,0 +1,153 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Creating blinded routes and related utilities live here.
+
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
+
+use chain::keysinterface::{KeysInterface, Sign};
+use super::utils;
+use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
+use util::ser::{VecWriter, Writeable, Writer};
+
+use core::iter::FromIterator;
+use io;
+use prelude::*;
+
+/// Onion messages can be sent and received to blinded routes, which serve to hide the identity of
+/// the recipient.
+pub struct BlindedRoute {
+       /// To send to a blinded route, the sender first finds a route to the unblinded
+       /// `introduction_node_id`, which can unblind its [`encrypted_payload`] to find out the onion
+       /// message's next hop and forward it along.
+       ///
+       /// [`encrypted_payload`]: BlindedHop::encrypted_payload
+       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,
+       /// The hops composing the blinded route.
+       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,
+       /// 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>,
+}
+
+impl BlindedRoute {
+       /// Create a blinded route to be forwarded along `node_pks`. The last node pubkey in `node_pks`
+       /// will be the destination node.
+       ///
+       /// Errors if less than two 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<Signer: Sign, K: KeysInterface, T: secp256k1::Signing + secp256k1::Verification>
+               (node_pks: &[PublicKey], keys_manager: &K, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
+       {
+               if node_pks.len() < 2 { return Err(()) }
+               let blinding_secret_bytes = keys_manager.get_secure_random_bytes();
+               let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
+               let introduction_node_id = node_pks[0];
+
+               Ok(BlindedRoute {
+                       introduction_node_id,
+                       blinding_point: PublicKey::from_secret_key(secp_ctx, &blinding_secret),
+                       blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret).map_err(|_| ())?,
+               })
+       }
+}
+
+/// Construct blinded hops for the given `unblinded_path`.
+fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
+       secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
+) -> Result<Vec<BlindedHop>, secp256k1::Error> {
+       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| {
+               if let Some((prev_ss, prev_blinded_node_id)) = prev_ss_and_blinded_node_id {
+                       if let Some(pk) = unblinded_pk {
+                               let payload = ForwardTlvs {
+                                       next_node_id: pk,
+                                       next_blinding_override: None,
+                               };
+                               blinded_hops.push(BlindedHop {
+                                       blinded_node_id: prev_blinded_node_id,
+                                       encrypted_payload: encrypt_payload(payload, prev_ss),
+                               });
+                       } else { debug_assert!(false); }
+               }
+               prev_ss_and_blinded_node_id = Some((encrypted_payload_ss, blinded_node_id));
+       })?;
+
+       if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
+               let final_payload = ReceiveTlvs { path_id: None };
+               blinded_hops.push(BlindedHop {
+                       blinded_node_id: final_blinded_node_id,
+                       encrypted_payload: encrypt_payload(final_payload, final_ss),
+               });
+       } else { debug_assert!(false) }
+
+       Ok(blinded_hops)
+}
+
+/// Encrypt TLV payload to be used as a [`BlindedHop::encrypted_payload`].
+fn encrypt_payload<P: Writeable>(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec<u8> {
+       let mut writer = VecWriter(Vec::new());
+       let write_adapter = ChaChaPolyWriteAdapter::new(encrypted_tlvs_ss, &payload);
+       write_adapter.write(&mut writer).expect("In-memory writes cannot fail");
+       writer.0
+}
+
+/// 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.
+       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>,
+}
+
+/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
+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]>,
+}
+
+impl Writeable for ForwardTlvs {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               // TODO: write padding
+               encode_tlv_stream!(writer, {
+                       (4, self.next_node_id, required),
+                       (8, self.next_blinding_override, option)
+               });
+               Ok(())
+       }
+}
+
+impl Writeable for ReceiveTlvs {
+       fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
+               // TODO: write padding
+               encode_tlv_stream!(writer, {
+                       (6, self.path_id, option),
+               });
+               Ok(())
+       }
+}
diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs
new file mode 100644 (file)
index 0000000..fabe8d5
--- /dev/null
@@ -0,0 +1,16 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Onion Messages: sending, receiving, forwarding, and ancillary utilities live here
+
+mod blinded_route;
+mod utils;
+
+// Re-export structs so they can be imported with just the `onion_message::` module prefix.
+pub use self::blinded_route::{BlindedRoute, BlindedHop};
diff --git a/lightning/src/onion_message/utils.rs b/lightning/src/onion_message/utils.rs
new file mode 100644 (file)
index 0000000..785a373
--- /dev/null
@@ -0,0 +1,79 @@
+// This file is Copyright its original authors, visible in version control
+// history.
+//
+// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
+// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
+// You may not use this file except in accordance with one or both of these
+// licenses.
+
+//! Onion message utility methods live here.
+
+use bitcoin::hashes::{Hash, HashEngine};
+use bitcoin::hashes::hmac::{Hmac, HmacEngine};
+use bitcoin::hashes::sha256::Hash as Sha256;
+use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};
+use bitcoin::secp256k1::ecdh::SharedSecret;
+
+use ln::onion_utils;
+
+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],
+       session_priv: &SecretKey, mut callback: FType
+) -> Result<(), secp256k1::Error> {
+       let mut msg_blinding_point_priv = session_priv.clone();
+       let mut msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
+       let mut onion_packet_pubkey_priv = msg_blinding_point_priv.clone();
+       let mut onion_packet_pubkey = msg_blinding_point.clone();
+
+       macro_rules! build_keys {
+               ($pk: expr, $blinded: expr) => {
+                       let encrypted_data_ss = SharedSecret::new(&$pk, &msg_blinding_point_priv);
+
+                       let blinded_hop_pk = if $blinded { $pk } else {
+                               let hop_pk_blinding_factor = {
+                                       let mut hmac = HmacEngine::<Sha256>::new(b"blinded_node_id");
+                                       hmac.input(encrypted_data_ss.as_ref());
+                                       Hmac::from_engine(hmac).into_inner()
+                               };
+                               let mut unblinded_pk = $pk;
+                               unblinded_pk.mul_assign(secp_ctx, &hop_pk_blinding_factor)?;
+                               unblinded_pk
+                       };
+                       let onion_packet_ss = SharedSecret::new(&blinded_hop_pk, &onion_packet_pubkey_priv);
+
+                       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);
+
+                       let msg_blinding_point_blinding_factor = {
+                               let mut sha = Sha256::engine();
+                               sha.input(&msg_blinding_point.serialize()[..]);
+                               sha.input(encrypted_data_ss.as_ref());
+                               Sha256::from_engine(sha).into_inner()
+                       };
+
+                       msg_blinding_point_priv.mul_assign(&msg_blinding_point_blinding_factor)?;
+                       msg_blinding_point = PublicKey::from_secret_key(secp_ctx, &msg_blinding_point_priv);
+
+                       let onion_packet_pubkey_blinding_factor = {
+                               let mut sha = Sha256::engine();
+                               sha.input(&onion_packet_pubkey.serialize()[..]);
+                               sha.input(onion_packet_ss.as_ref());
+                               Sha256::from_engine(sha).into_inner()
+                       };
+                       onion_packet_pubkey_priv.mul_assign(&onion_packet_pubkey_blinding_factor)?;
+                       onion_packet_pubkey = PublicKey::from_secret_key(secp_ctx, &onion_packet_pubkey_priv);
+               };
+       }
+
+       for pk in unblinded_path {
+               build_keys!(*pk, false);
+       }
+       Ok(())
+}