From 4e5381a50fa524cb183a3d92dee05cb4f2950255 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 27 May 2022 16:25:15 -0700 Subject: [PATCH 01/11] Add onion messages module + enable the construction of blinded routes 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 | 4 +- lightning/src/ln/mod.rs | 2 +- lightning/src/ln/onion_utils.rs | 8 + lightning/src/onion_message/blinded_route.rs | 153 +++++++++++++++++++ lightning/src/onion_message/mod.rs | 16 ++ lightning/src/onion_message/utils.rs | 79 ++++++++++ 6 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 lightning/src/onion_message/blinded_route.rs create mode 100644 lightning/src/onion_message/mod.rs create mode 100644 lightning/src/onion_message/utils.rs diff --git a/lightning/src/lib.rs b/lightning/src/lib.rs index ba6d6bc7191..2e6b3ab3c0a 100644 --- a/lightning/src/lib.rs +++ b/lightning/src/lib.rs @@ -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. diff --git a/lightning/src/ln/mod.rs b/lightning/src/ln/mod.rs index 9522e83d18d..c2190d62a11 100644 --- a/lightning/src/ln/mod.rs +++ b/lightning/src/ln/mod.rs @@ -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 diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index b223a344dbe..57228b92d04 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -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::::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 index 00000000000..be6e2a01ca5 --- /dev/null +++ b/lightning/src/onion_message/blinded_route.rs @@ -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 or the MIT license +// , 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, +} + +/// 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, +} + +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 + (node_pks: &[PublicKey], keys_manager: &K, secp_ctx: &Secp256k1) -> Result + { + 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( + secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], session_priv: &SecretKey +) -> Result, 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(payload: P, encrypted_tlvs_ss: [u8; 32]) -> Vec { + 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, +} + +/// 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(&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(&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 index 00000000000..fabe8d58eda --- /dev/null +++ b/lightning/src/onion_message/mod.rs @@ -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 or the MIT license +// , 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 index 00000000000..785a373caa4 --- /dev/null +++ b/lightning/src/onion_message/utils.rs @@ -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 or the MIT license +// , 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)>( + secp_ctx: &Secp256k1, 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::::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(()) +} From 33ff2746ef3d1a36aab4f07cd1a7b512b6e411da Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 27 May 2022 17:47:15 -0700 Subject: [PATCH 02/11] Add onion_message::Packet and adapt construct_onion_packet_with_init_noise for it We need to add a new Packet struct because onion message packet hop_data fields can be of variable length, whereas regular payment packets are always 1366 bytes. Co-authored-by: Valentine Wallace Co-authored-by: Jeffrey Czyz --- lightning/src/ln/msgs.rs | 13 +++++ lightning/src/ln/onion_utils.rs | 66 ++++++++++++++------- lightning/src/onion_message/mod.rs | 1 + lightning/src/onion_message/packet.rs | 82 +++++++++++++++++++++++++++ lightning/src/util/ser.rs | 8 +++ 5 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 lightning/src/onion_message/packet.rs diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 3e44cfcf024..424dbafe661 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,6 +31,7 @@ use bitcoin::blockdata::script::Script; use bitcoin::hash_types::{Txid, BlockHash}; use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; +use ln::onion_utils; use prelude::*; use core::fmt; @@ -993,6 +994,18 @@ pub(crate) struct OnionPacket { pub(crate) hmac: [u8; 32], } +impl onion_utils::Packet for OnionPacket { + type Data = onion_utils::FixedSizeOnionPacket; + fn new(pubkey: PublicKey, hop_data: onion_utils::FixedSizeOnionPacket, hmac: [u8; 32]) -> Self { + Self { + version: 0, + public_key: Ok(pubkey), + hop_data: hop_data.0, + hmac, + } + } +} + impl PartialEq for OnionPacket { fn eq(&self, other: &OnionPacket) -> bool { for (i, j) in self.hop_data.iter().zip(other.hop_data.iter()) { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 57228b92d04..914c8e03c80 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -30,7 +30,7 @@ use bitcoin::secp256k1; use prelude::*; use io::{Cursor, Read}; -use core::convert::TryInto; +use core::convert::{AsMut, TryInto}; use core::ops::Deref; pub(super) struct OnionKeys { @@ -195,8 +195,8 @@ pub(super) fn build_onion_payloads(path: &Vec, total_msat: u64, paymen pub(crate) const ONION_DATA_LEN: usize = 20*65; #[inline] -fn shift_arr_right(arr: &mut [u8; ONION_DATA_LEN], amt: usize) { - for i in (amt..ONION_DATA_LEN).rev() { +fn shift_slice_right(arr: &mut [u8], amt: usize) { + for i in (amt..arr.len()).rev() { arr[i] = arr[i-amt]; } for i in 0..amt { @@ -218,14 +218,15 @@ pub(super) fn route_size_insane(payloads: &Vec) -> bool { false } -/// panics if route_size_insane(paylods) +/// panics if route_size_insane(payloads) pub(super) fn construct_onion_packet(payloads: Vec, onion_keys: Vec, prng_seed: [u8; 32], associated_data: &PaymentHash) -> msgs::OnionPacket { let mut packet_data = [0; ONION_DATA_LEN]; let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]); chacha.process(&[0; ONION_DATA_LEN], &mut packet_data); - construct_onion_packet_with_init_noise(payloads, onion_keys, packet_data, associated_data) + construct_onion_packet_with_init_noise::<_, _>( + payloads, onion_keys, FixedSizeOnionPacket(packet_data), Some(associated_data)) } #[cfg(test)] @@ -237,12 +238,34 @@ pub(super) fn construct_onion_packet_bogus_hopdata(payloads: Vec< let mut chacha = ChaCha20::new(&prng_seed, &[0; 8]); chacha.process(&[0; ONION_DATA_LEN], &mut packet_data); - construct_onion_packet_with_init_noise(payloads, onion_keys, packet_data, associated_data) + construct_onion_packet_with_init_noise::<_, _>( + payloads, onion_keys, FixedSizeOnionPacket(packet_data), Some(associated_data)) } -/// panics if route_size_insane(paylods) -fn construct_onion_packet_with_init_noise(mut payloads: Vec, onion_keys: Vec, mut packet_data: [u8; ONION_DATA_LEN], associated_data: &PaymentHash) -> msgs::OnionPacket { +/// Since onion message packets and onion payment packets have different lengths but are otherwise +/// identical, we use this trait to allow `construct_onion_packet_with_init_noise` to return either +/// type. +pub(crate) trait Packet { + type Data: AsMut<[u8]>; + fn new(pubkey: PublicKey, hop_data: Self::Data, hmac: [u8; 32]) -> Self; +} + +// Needed for rustc versions older than 1.47 to avoid E0277: "arrays only have std trait +// implementations for lengths 0..=32". +pub(crate) struct FixedSizeOnionPacket(pub(crate) [u8; ONION_DATA_LEN]); + +impl AsMut<[u8]> for FixedSizeOnionPacket { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +/// panics if route_size_insane(payloads) +fn construct_onion_packet_with_init_noise( + mut payloads: Vec, onion_keys: Vec, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> P +{ let filler = { + let packet_data = packet_data.as_mut(); const ONION_HOP_DATA_LEN: usize = 65; // We may decrease this eventually after TLV is common let mut res = Vec::with_capacity(ONION_HOP_DATA_LEN * (payloads.len() - 1)); @@ -251,7 +274,7 @@ fn construct_onion_packet_with_init_noise(mut payloads: Vec, if i == payloads.len() - 1 { break; } let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]); - for _ in 0..(ONION_DATA_LEN - pos) { // TODO: Batch this. + for _ in 0..(packet_data.len() - pos) { // TODO: Batch this. let mut dummy = [0; 1]; chacha.process_in_place(&mut dummy); // We don't have a seek function :( } @@ -259,7 +282,7 @@ fn construct_onion_packet_with_init_noise(mut payloads: Vec, let mut payload_len = LengthCalculatingWriter(0); payload.write(&mut payload_len).expect("Failed to calculate length"); pos += payload_len.0 + 32; - assert!(pos <= ONION_DATA_LEN); + assert!(pos <= packet_data.len()); res.resize(pos, 0u8); chacha.process_in_place(&mut res); @@ -271,29 +294,28 @@ fn construct_onion_packet_with_init_noise(mut payloads: Vec, for (i, (payload, keys)) in payloads.iter_mut().zip(onion_keys.iter()).rev().enumerate() { let mut payload_len = LengthCalculatingWriter(0); payload.write(&mut payload_len).expect("Failed to calculate length"); - shift_arr_right(&mut packet_data, payload_len.0 + 32); + + let packet_data = packet_data.as_mut(); + shift_slice_right(packet_data, payload_len.0 + 32); packet_data[0..payload_len.0].copy_from_slice(&payload.encode()[..]); packet_data[payload_len.0..(payload_len.0 + 32)].copy_from_slice(&hmac_res); let mut chacha = ChaCha20::new(&keys.rho, &[0u8; 8]); - chacha.process_in_place(&mut packet_data); + chacha.process_in_place(packet_data); if i == 0 { packet_data[ONION_DATA_LEN - filler.len()..ONION_DATA_LEN].copy_from_slice(&filler[..]); } let mut hmac = HmacEngine::::new(&keys.mu); - hmac.input(&packet_data); - hmac.input(&associated_data.0[..]); + hmac.input(packet_data); + if let Some(associated_data) = associated_data { + hmac.input(&associated_data.0[..]); + } hmac_res = Hmac::from_engine(hmac).into_inner(); } - msgs::OnionPacket { - version: 0, - public_key: Ok(onion_keys.first().unwrap().ephemeral_pubkey), - hop_data: packet_data, - hmac: hmac_res, - } + P::new(onion_keys.first().unwrap().ephemeral_pubkey, packet_data, hmac_res) } /// Encrypts a failure packet. raw_packet can either be a @@ -783,7 +805,7 @@ mod tests { }, ); - let packet = super::construct_onion_packet_with_init_noise(payloads, onion_keys, [0; super::ONION_DATA_LEN], &PaymentHash([0x42; 32])); + let packet: msgs::OnionPacket = super::construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, super::FixedSizeOnionPacket([0; super::ONION_DATA_LEN]), Some(&PaymentHash([0x42; 32]))); // Just check the final packet encoding, as it includes all the per-hop vectors in it // anyway... assert_eq!(packet.encode(), hex::decode("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a716a996c7845c93d90e4ecbb9bde4ece2f69425c99e4bc820e44485455f135edc0d10f7d61ab590531cf08000179a333a347f8b4072f216400406bdf3bf038659793d4a1fd7b246979e3150a0a4cb052c9ec69acf0f48c3d39cd55675fe717cb7d80ce721caad69320c3a469a202f1e468c67eaf7a7cd8226d0fd32f7b48084dca885d56047694762b67021713ca673929c163ec36e04e40ca8e1c6d17569419d3039d9a1ec866abe044a9ad635778b961fc0776dc832b3a451bd5d35072d2269cf9b040f6b7a7dad84fb114ed413b1426cb96ceaf83825665ed5a1d002c1687f92465b49ed4c7f0218ff8c6c7dd7221d589c65b3b9aaa71a41484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f9172307c7268724c3618e6817abd793adc214a0dc0bc616816632f27ea336fb56dfd").unwrap()); @@ -862,7 +884,7 @@ mod tests { }), ); - let packet = super::construct_onion_packet_with_init_noise(payloads, onion_keys, [0; super::ONION_DATA_LEN], &PaymentHash([0x42; 32])); + let packet: msgs::OnionPacket = super::construct_onion_packet_with_init_noise::<_, _>(payloads, onion_keys, super::FixedSizeOnionPacket([0; super::ONION_DATA_LEN]), Some(&PaymentHash([0x42; 32]))); // Just check the final packet encoding, as it includes all the per-hop vectors in it // anyway... assert_eq!(packet.encode(), hex::decode("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71a060daf367132b378b3a3883c0e2c0e026b8900b2b5cdbc784e1a3bb913f88a9c50f7d61ab590531cf08000178a333a347f8b4072ed056f820f77774345e183a342ec4729f3d84accf515e88adddb85ecc08daba68404bae9a8e8d7178977d7094a1ae549f89338c0777551f874159eb42d3a59fb9285ad4e24883f27de23942ec966611e99bee1cee503455be9e8e642cef6cef7b9864130f692283f8a973d47a8f1c1726b6e59969385975c766e35737c8d76388b64f748ee7943ffb0e2ee45c57a1abc40762ae598723d21bd184e2b338f68ebff47219357bd19cd7e01e2337b806ef4d717888e129e59cd3dc31e6201ccb2fd6d7499836f37a993262468bcb3a4dcd03a22818aca49c6b7b9b8e9e870045631d8e039b066ff86e0d1b7291f71cefa7264c70404a8e538b566c17ccc5feab231401e6c08a01bd5edfc1aa8e3e533b96e82d1f91118d508924b923531929aea889fcdf050597c681185f336b1da63b0939aa2b7c50b21b5eb7b6ad66c81fab98a3cdf73f658149e7e9ced4edde5d38c9b8f92e16f6b4ab13d7fca6a0e4ecc9f9de611a90da6e99c39551094c56e3196f282c5dffd9fc4b2fc12f3bca8e6fe47eb45fbdd3be21a8a8d200797eae3c9a0497132f92410d804977408494dff49dd3d8bce248e0b74fd9e6f0f7102c25ddfa02bd9ad9f746abbfa337ef811d5345a9e16b60de1767b209645ba40bd1f9a5f75bc04feca9b27c5554be4fe83fac2cb83aa447a817bb85ae966c68b420063833fada375e2f515965e687a45699632902672c654d1d18d7bcbf55e8fa57f63f2da449f8e1e606e8722df081e5f193fc4179feb99ad22819afdeef211f7c54afdba92aeef0c00b7bc2b65a4813c01f907a8377585708f2d4c940a25328e585714c8ded0a9a4d7a6de1027c1cb7a0198cd3db68b58c0704dfd0cfbe624e9cd18cc0ae5d96697bb476708b9ee0403d211e64e0d5a7683a7a9a140c02f0ff1c6e67a302941b4052bdea8a63e70a3ad62c5b89c698f1fd3c7685cb49705096cad702d02d93bcb1c27a409f4c9bddec001205ca4a2740f19b50900be81c7e847f1a863deea8d35701f1355cad8db57b1d4eb2ab4e29587734785abfb46ddede71928213d7d089dfdeda052827f459f1688cc0935bd47e7bcec27427c8376dcce7e22699567c0d145f8a7db33f6758815f1f15f9f7a9760dec4f34ae095edda4c64e9735bdd029c4e32c2ee31ba47ec5e6bdb97813d52dbd15b4e0b7a2c7f790ae64104d99f38c127f0a093288fa34144adb16b8968d4fa7656fcec99de8503dd46d3b03620a71c7cd085364abd30dccf7fbda25a1cdc102600149c9af1c97aa0372cd2e1909f28ac5c686f432b310e79528c9b8b9e8f314c1e74621ce6308ad2278b81d460892e0d9dd38b7c76d58be6dfd10ae7583ee1e7ef5b3f6f78dc60af0950df1b00cc55b6d178ba2e476bea0eaeef49323b83f05804159e7aef4eed4cc60dd07be76f067dfd0bcfb0b806b69ba921336a20c43c832d0cab8fa3ddeb29e3bf07b0d98a112eb07802756235a49d44a8b82a950d84e95e01971f0e106ccb337f07384e21620e0ad39e16ed9edca123226cf55ac44f449eeb53e38a7f27d101806e4823e4efcc887414240ee6826c4a5cb1c6443ad36ebf905a435c1d9054e54173911b17b5b40f60b3d9fd5f12eac54ca1e20191f5f18544d5fd3d665e9bcef96fb44b76110aa64d9db4c86c9513cbdad546538e8aec521fbe83ceac5e74a15629f1ed0b870a1d0d1e5680b6d6100d1bd3f3b9043bd35b8919c4088f1949b8be89e4701eb870f8ed64fafa446c78df3ea").unwrap()); diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index fabe8d58eda..37858c1a4a6 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -10,6 +10,7 @@ //! Onion Messages: sending, receiving, forwarding, and ancillary utilities live here mod blinded_route; +mod packet; mod utils; // Re-export structs so they can be imported with just the `onion_message::` module prefix. diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs new file mode 100644 index 00000000000..37f178f5521 --- /dev/null +++ b/lightning/src/onion_message/packet.rs @@ -0,0 +1,82 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Structs and enums useful for constructing and reading an onion message packet. + +use bitcoin::secp256k1::PublicKey; + +use ln::msgs::DecodeError; +use ln::onion_utils; +use util::ser::{LengthRead, LengthReadable, Readable, Writeable, Writer}; + +use core::cmp; +use io; +use prelude::*; + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct Packet { + version: u8, + public_key: PublicKey, + // Unlike the onion packets used for payments, onion message packets can have payloads greater + // than 1300 bytes. + // TODO: if 1300 ends up being the most common size, optimize this to be: + // enum { ThirteenHundred([u8; 1300]), VarLen(Vec) } + hop_data: Vec, + hmac: [u8; 32], +} + +impl onion_utils::Packet for Packet { + type Data = Vec; + fn new(public_key: PublicKey, hop_data: Vec, hmac: [u8; 32]) -> Packet { + Self { + version: 0, + public_key, + hop_data, + hmac, + } + } +} + +impl Writeable for Packet { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.version.write(w)?; + self.public_key.write(w)?; + w.write_all(&self.hop_data)?; + self.hmac.write(w)?; + Ok(()) + } +} + +impl LengthReadable for Packet { + fn read(r: &mut R) -> Result { + const READ_BUFFER_SIZE: usize = 4096; + + let version = Readable::read(r)?; + let public_key = Readable::read(r)?; + + let mut hop_data = Vec::new(); + let hop_data_len = r.total_bytes() as usize - 66; // 1 (version) + 33 (pubkey) + 32 (HMAC) = 66 + let mut read_idx = 0; + while read_idx < hop_data_len { + let mut read_buffer = [0; READ_BUFFER_SIZE]; + let read_amt = cmp::min(hop_data_len - read_idx, READ_BUFFER_SIZE); + r.read_exact(&mut read_buffer[..read_amt]); + hop_data.extend_from_slice(&read_buffer[..read_amt]); + read_idx += read_amt; + } + + let hmac = Readable::read(r)?; + Ok(Packet { + version, + public_key, + hop_data, + hmac, + }) + } +} diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index 5b1a86a6a95..ecf85839a5a 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -244,6 +244,14 @@ pub(crate) trait LengthReadableArgs

where Self: Sized fn read(reader: &mut R, params: P) -> Result; } +/// A trait that various higher-level rust-lightning types implement allowing them to be read in +/// from a Read, requiring the implementer to provide the total length of the read. +pub(crate) trait LengthReadable where Self: Sized +{ + /// Reads a Self in from the given LengthRead + fn read(reader: &mut R) -> Result; +} + /// A trait that various rust-lightning types implement allowing them to (maybe) be read in from a Read /// /// (C-not exported) as we only export serialization to/from byte arrays instead From 6017379b8e7943a1967bbc2cabcdb37d07c12531 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 11 Jul 2022 16:27:10 -0400 Subject: [PATCH 03/11] KeysInterface: add new ecdh method This method will help us avoid retrieving our node secret, something we want to get rid of entirely. It will be used in upcoming commits when decoding the onion message packet, and in future PRs to help us get rid of KeysInterface::get_node_secret usages across the codebase --- fuzz/src/chanmon_consistency.rs | 9 +++++++++ fuzz/src/full_stack.rs | 9 +++++++++ lightning/src/chain/keysinterface.rs | 23 +++++++++++++++++++++++ lightning/src/ln/channel.rs | 2 ++ lightning/src/util/test_utils.rs | 5 +++++ 5 files changed, 48 insertions(+) diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index 615f3aad995..c511e4ff31c 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -54,6 +54,7 @@ use utils::test_logger::{self, Output}; use utils::test_persister::TestPersister; use bitcoin::secp256k1::{PublicKey,SecretKey}; +use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::Secp256k1; @@ -165,6 +166,14 @@ impl KeysInterface for KeyProvider { Ok(SecretKey::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]).unwrap()) } + fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result { + let mut node_secret = self.get_node_secret(recipient)?; + if let Some(tweak) = tweak { + node_secret.mul_assign(tweak).map_err(|_| ())?; + } + Ok(SharedSecret::new(other_key, &node_secret)) + } + fn get_inbound_payment_key_material(&self) -> KeyMaterial { KeyMaterial([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, self.node_id]) } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index b4ca316ed0d..40c77f06662 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -51,6 +51,7 @@ use utils::test_logger; use utils::test_persister::TestPersister; use bitcoin::secp256k1::{PublicKey,SecretKey}; +use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::Secp256k1; @@ -269,6 +270,14 @@ impl KeysInterface for KeyProvider { Ok(self.node_secret.clone()) } + fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result { + let mut node_secret = self.get_node_secret(recipient)?; + if let Some(tweak) = tweak { + node_secret.mul_assign(tweak).map_err(|_| ())?; + } + Ok(SharedSecret::new(other_key, &node_secret)) + } + fn get_inbound_payment_key_material(&self) -> KeyMaterial { self.inbound_payment_key.clone() } diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 4231d08259a..9a3baea8bb4 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -27,6 +27,7 @@ use bitcoin::hash_types::WPubkeyHash; use bitcoin::secp256k1::{SecretKey, PublicKey}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature, Signing}; +use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::{secp256k1, Witness}; @@ -404,6 +405,12 @@ pub trait KeysInterface { /// This method must return the same value each time it is called with a given `Recipient` /// parameter. fn get_node_secret(&self, recipient: Recipient) -> Result; + /// Gets the ECDH shared secret of our [`node secret`] and `other_key`, multiplying by `tweak` if + /// one is provided. Note that this tweak can be applied to `other_key` instead of our node + /// secret, though this is less efficient. + /// + /// [`node secret`]: Self::get_node_secret + fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result; /// Get a script pubkey which we send funds to when claiming on-chain contestable outputs. /// /// This method should return a different value each time it is called, to avoid linking @@ -1133,6 +1140,14 @@ impl KeysInterface for KeysManager { } } + fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result { + let mut node_secret = self.get_node_secret(recipient)?; + if let Some(tweak) = tweak { + node_secret.mul_assign(tweak).map_err(|_| ())?; + } + Ok(SharedSecret::new(other_key, &node_secret)) + } + fn get_inbound_payment_key_material(&self) -> KeyMaterial { self.inbound_payment_key.clone() } @@ -1217,6 +1232,14 @@ impl KeysInterface for PhantomKeysManager { } } + fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result { + let mut node_secret = self.get_node_secret(recipient)?; + if let Some(tweak) = tweak { + node_secret.mul_assign(tweak).map_err(|_| ())?; + } + Ok(SharedSecret::new(other_key, &node_secret)) + } + fn get_inbound_payment_key_material(&self) -> KeyMaterial { self.inbound_payment_key.clone() } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index c5133a85058..2da743033d3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -6583,6 +6583,7 @@ mod tests { use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1::ffi::Signature as FFISignature; use bitcoin::secp256k1::{SecretKey,PublicKey}; + use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; @@ -6621,6 +6622,7 @@ mod tests { type Signer = InMemorySigner; fn get_node_secret(&self, _recipient: Recipient) -> Result { panic!(); } + fn ecdh(&self, _recipient: Recipient, _other_key: &PublicKey, _tweak: Option<&[u8; 32]>) -> Result { panic!(); } fn get_inbound_payment_key_material(&self) -> KeyMaterial { panic!(); } fn get_destination_script(&self) -> Script { let secp_ctx = Secp256k1::signing_only(); diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 4f3d800be5d..92383c91e8f 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -36,6 +36,7 @@ use bitcoin::network::constants::Network; use bitcoin::hash_types::{BlockHash, Txid}; use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1, ecdsa::Signature}; +use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::RecoverableSignature; use regex; @@ -74,6 +75,7 @@ impl keysinterface::KeysInterface for OnlyReadsKeysInterface { type Signer = EnforcingSigner; fn get_node_secret(&self, _recipient: Recipient) -> Result { unreachable!(); } + fn ecdh(&self, _recipient: Recipient, _other_key: &PublicKey, _tweak: Option<&[u8; 32]>) -> Result { unreachable!(); } fn get_inbound_payment_key_material(&self) -> KeyMaterial { unreachable!(); } fn get_destination_script(&self) -> Script { unreachable!(); } fn get_shutdown_scriptpubkey(&self) -> ShutdownScript { unreachable!(); } @@ -599,6 +601,9 @@ impl keysinterface::KeysInterface for TestKeysInterface { fn get_node_secret(&self, recipient: Recipient) -> Result { self.backing.get_node_secret(recipient) } + fn ecdh(&self, recipient: Recipient, other_key: &PublicKey, tweak: Option<&[u8; 32]>) -> Result { + self.backing.ecdh(recipient, other_key, tweak) + } fn get_inbound_payment_key_material(&self) -> keysinterface::KeyMaterial { self.backing.get_inbound_payment_key_material() } From 4c8dc2c2a0b9589298d937bf16061ae0ac99b31e Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 22 Jun 2022 17:03:06 -0400 Subject: [PATCH 04/11] Add baseline OnionMessenger and msgs::OnionMessage and its serialization OnionMessenger will be hooked up to the PeerManager to send and receive OMs in a follow-up PR. --- lightning/src/ln/msgs.rs | 34 +++++++++++- lightning/src/onion_message/messenger.rs | 71 ++++++++++++++++++++++++ lightning/src/onion_message/mod.rs | 3 + 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 lightning/src/onion_message/messenger.rs diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 424dbafe661..950d6a35947 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -32,6 +32,7 @@ use bitcoin::hash_types::{Txid, BlockHash}; use ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; use ln::onion_utils; +use onion_message; use prelude::*; use core::fmt; @@ -41,7 +42,7 @@ use io_extras::read_to_end; use util::events::MessageSendEventsProvider; use util::logger; -use util::ser::{Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname}; +use util::ser::{LengthReadable, Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname}; use ln::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -305,6 +306,14 @@ pub struct UpdateAddHTLC { pub(crate) onion_routing_packet: OnionPacket, } + /// An onion message to be sent or received from a peer +#[derive(Clone, Debug, PartialEq)] +pub struct OnionMessage { + /// Used in decrypting the onion packet's payload. + pub blinding_point: PublicKey, + pub(crate) onion_routing_packet: onion_message::Packet, +} + /// An update_fulfill_htlc message to be sent or received from a peer #[derive(Clone, Debug, PartialEq)] pub struct UpdateFulfillHTLC { @@ -1340,6 +1349,29 @@ impl_writeable_msg!(UpdateAddHTLC, { onion_routing_packet }, {}); +impl Readable for OnionMessage { + fn read(r: &mut R) -> Result { + let blinding_point: PublicKey = Readable::read(r)?; + let len: u16 = Readable::read(r)?; + let mut packet_reader = FixedLengthReader::new(r, len as u64); + let onion_routing_packet: onion_message::Packet = ::read(&mut packet_reader)?; + Ok(Self { + blinding_point, + onion_routing_packet, + }) + } +} + +impl Writeable for OnionMessage { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.blinding_point.write(w)?; + let onion_packet_len = self.onion_routing_packet.serialized_length(); + (onion_packet_len as u16).write(w)?; + self.onion_routing_packet.write(w)?; + Ok(()) + } +} + impl Writeable for FinalOnionHopData { fn write(&self, w: &mut W) -> Result<(), io::Error> { self.payment_secret.0.write(w)?; diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs new file mode 100644 index 00000000000..e2d7b51a9b5 --- /dev/null +++ b/lightning/src/onion_message/messenger.rs @@ -0,0 +1,71 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for +//! more information. + +use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; + +use chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Sign}; +use ln::msgs; +use util::logger::Logger; + +use core::ops::Deref; +use sync::{Arc, Mutex}; +use prelude::*; + +/// A sender, receiver and forwarder of onion messages. In upcoming releases, this object will be +/// used to retrieve invoices and fulfill invoice requests from [offers]. +/// +/// [offers]: +pub struct OnionMessenger + where K::Target: KeysInterface, + L::Target: Logger, +{ + keys_manager: K, + logger: L, + pending_messages: Mutex>>, + secp_ctx: Secp256k1, + // Coming soon: + // invoice_handler: InvoiceHandler, + // custom_handler: CustomHandler, // handles custom onion messages +} + +impl OnionMessenger + where K::Target: KeysInterface, + L::Target: Logger, +{ + /// Constructs a new `OnionMessenger` to send, forward, and delegate received onion messages to + /// their respective handlers. + pub fn new(keys_manager: K, logger: L) -> Self { + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&keys_manager.get_secure_random_bytes()); + OnionMessenger { + keys_manager, + pending_messages: Mutex::new(HashMap::new()), + secp_ctx, + logger, + } + } +} + +// TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it +// produces +/// Useful for simplifying the parameters of [`SimpleArcChannelManager`] and +/// [`SimpleArcPeerManager`]. See their docs for more details. +/// +///[`SimpleArcChannelManager`]: crate::ln::channelmanager::SimpleArcChannelManager +///[`SimpleArcPeerManager`]: crate::ln::peer_handler::SimpleArcPeerManager +pub type SimpleArcOnionMessenger = OnionMessenger, Arc>; +/// Useful for simplifying the parameters of [`SimpleRefChannelManager`] and +/// [`SimpleRefPeerManager`]. See their docs for more details. +/// +///[`SimpleRefChannelManager`]: crate::ln::channelmanager::SimpleRefChannelManager +///[`SimpleRefPeerManager`]: crate::ln::peer_handler::SimpleRefPeerManager +pub type SimpleRefOnionMessenger<'a, 'b, L> = OnionMessenger; diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index 37858c1a4a6..291030f4e26 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -10,8 +10,11 @@ //! Onion Messages: sending, receiving, forwarding, and ancillary utilities live here mod blinded_route; +mod messenger; mod packet; 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(crate) use self::packet::Packet; From 9051c38ebe42e171fd0fcfa22d2b9ff6a1607b3b Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 27 May 2022 18:31:27 -0700 Subject: [PATCH 05/11] Support sending onion messages 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 | 32 +++-- lightning/src/onion_message/blinded_route.rs | 18 +-- lightning/src/onion_message/messenger.rs | 140 ++++++++++++++++++- lightning/src/onion_message/mod.rs | 2 +- lightning/src/onion_message/packet.rs | 76 ++++++++++ lightning/src/onion_message/utils.rs | 31 +++- 6 files changed, 275 insertions(+), 24 deletions(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 914c8e03c80..ce91d0d04f7 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -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::::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(payloads: &Vec) -> 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>>( + payloads: Vec, onion_keys: Vec, 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( mut payloads: Vec, onion_keys: Vec, mut packet_data: P::Data, associated_data: Option<&PaymentHash>) -> P { diff --git a/lightning/src/onion_message/blinded_route.rs b/lightning/src/onion_message/blinded_route.rs index be6e2a01ca5..d18372e3b00 100644 --- a/lightning/src/onion_message/blinded_route.rs +++ b/lightning/src/onion_message/blinded_route.rs @@ -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, + pub(super) blinded_hops: Vec, } /// 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, + pub(super) encrypted_payload: Vec, } impl BlindedRoute { @@ -78,7 +78,7 @@ fn blinded_hops( 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(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, + pub(super) next_blinding_override: Option, } /// 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 { diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index e2d7b51a9b5..f4cb57f28df 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -10,10 +10,14 @@ //! 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 // 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 OnionMessenger where K::Target: KeysInterface, L::Target: Logger, @@ -53,6 +74,36 @@ impl OnionMessenger 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 = OnionMessenger = OnionMessenger; + +/// 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( + secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], destination: Destination, session_priv: &SecretKey +) -> Result<(Vec<(Payload, [u8; 32])>, Vec), 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, 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) +} diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index 291030f4e26..9236341fa61 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -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; diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index 37f178f5521..ed0206a2adb 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -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, + // message: Message, + } +} + +// Coming soon: +// enum Message { +// InvoiceRequest(InvoiceRequest), +// Invoice(Invoice), +// InvoiceError(InvoiceError), +// CustomMessage, +// } + +/// 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), + /// 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), + /// 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(&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(()) + } +} diff --git a/lightning/src/onion_message/utils.rs b/lightning/src/onion_message/utils.rs index 785a373caa4..9b95183e74b 100644 --- a/lightning/src/onion_message/utils.rs +++ b/lightning/src/onion_message/utils.rs @@ -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)>( - secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], + FType: FnMut(PublicKey, SharedSecret, PublicKey, [u8; 32], Option, Option>)>( + secp_ctx: &Secp256k1, unblinded_path: &[PublicKey], destination: Option, 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 { + ($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 { + 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 { + 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(()) } From bf007ea7632b7b95c280440bc50713e55784e315 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 27 May 2022 18:39:56 -0700 Subject: [PATCH 06/11] Implement receiving and forwarding onion messages This required adapting `onion_utils::decode_next_hop` to work for both payments and onion messages. Currently we just print out the path_id of any onion messages we receive. In the future, these received onion messages will be redirected to their respective handlers: i.e. an invoice_request will go to an InvoiceHandler, custom onion messages will go to a custom handler, etc. --- lightning/src/ln/channelmanager.rs | 4 +- lightning/src/ln/msgs.rs | 10 ++- lightning/src/ln/onion_utils.rs | 88 +++++++++++++++---- lightning/src/onion_message/messenger.rs | 96 +++++++++++++++++++- lightning/src/onion_message/packet.rs | 106 +++++++++++++++++++++-- 5 files changed, 277 insertions(+), 27 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 36c14242d9e..40218089e3e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2159,7 +2159,7 @@ impl ChannelMana } } - let next_hop = match onion_utils::decode_next_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) { + let next_hop = match onion_utils::decode_next_payment_hop(shared_secret, &msg.onion_routing_packet.hop_data[..], msg.onion_routing_packet.hmac, msg.payment_hash) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { return_malformed_err!(err_msg, err_code); @@ -3058,7 +3058,7 @@ impl ChannelMana let phantom_secret_res = self.keys_manager.get_node_secret(Recipient::PhantomNode); if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id) { let phantom_shared_secret = SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap()).secret_bytes(); - let next_hop = match onion_utils::decode_next_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) { + let next_hop = match onion_utils::decode_next_payment_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner(); diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 950d6a35947..01863a9071c 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -42,7 +42,7 @@ use io_extras::read_to_end; use util::events::MessageSendEventsProvider; use util::logger; -use util::ser::{LengthReadable, Readable, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname}; +use util::ser::{LengthReadable, Readable, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname}; use ln::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -1417,6 +1417,14 @@ impl Writeable for OnionHopData { } } +// ReadableArgs because we need onion_utils::decode_next_hop to accommodate payment packets and +// onion message packets. +impl ReadableArgs<()> for OnionHopData { + fn read(r: &mut R, _arg: ()) -> Result { + ::read(r) + } +} + impl Readable for OnionHopData { fn read(mut r: &mut R) -> Result { use bitcoin::consensus::encode::{Decodable, Error, VarInt}; diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index ce91d0d04f7..145eb8acbae 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -15,7 +15,7 @@ use routing::gossip::NetworkUpdate; use routing::router::RouteHop; use util::chacha20::{ChaCha20, ChaChaReader}; use util::errors::{self, APIError}; -use util::ser::{Readable, Writeable, LengthCalculatingWriter}; +use util::ser::{Readable, ReadableArgs, Writeable, LengthCalculatingWriter}; use util::logger::Logger; use bitcoin::hashes::{Hash, HashEngine}; @@ -82,7 +82,7 @@ pub(super) fn gen_ammag_from_shared_secret(shared_secret: &[u8]) -> [u8; 32] { Hmac::from_engine(hmac).into_inner() } -pub(super) fn next_hop_packet_pubkey(secp_ctx: &Secp256k1, mut packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result { +pub(crate) fn next_hop_packet_pubkey(secp_ctx: &Secp256k1, mut packet_pubkey: PublicKey, packet_shared_secret: &[u8; 32]) -> Result { let blinding_factor = { let mut sha = Sha256::engine(); sha.input(&packet_pubkey.serialize()[..]); @@ -580,7 +580,50 @@ pub(super) fn process_onion_failure(secp_ctx: & } else { unreachable!(); } } -/// Data decrypted from the onion payload. +/// An input used when decoding an onion packet. +pub(crate) trait DecodeInput { + type Arg; + /// If Some, this is the input when checking the hmac of the onion packet. + fn payment_hash(&self) -> Option<&PaymentHash>; + /// Read argument when decrypting our hop payload. + fn read_arg(self) -> Self::Arg; +} + +impl DecodeInput for PaymentHash { + type Arg = (); + fn payment_hash(&self) -> Option<&PaymentHash> { + Some(self) + } + fn read_arg(self) -> Self::Arg { () } +} + +impl DecodeInput for SharedSecret { + type Arg = SharedSecret; + fn payment_hash(&self) -> Option<&PaymentHash> { + None + } + fn read_arg(self) -> Self::Arg { self } +} + +/// Allows `decode_next_hop` to return the next hop packet bytes for either payments or onion +/// message forwards. +pub(crate) trait NextPacketBytes: AsMut<[u8]> { + fn new(len: usize) -> Self; +} + +impl NextPacketBytes for FixedSizeOnionPacket { + fn new(_len: usize) -> Self { + Self([0 as u8; ONION_DATA_LEN]) + } +} + +impl NextPacketBytes for Vec { + fn new(len: usize) -> Self { + vec![0 as u8; len] + } +} + +/// Data decrypted from a payment's onion payload. pub(crate) enum Hop { /// This onion payload was for us, not for forwarding to a next-hop. Contains information for /// verifying the incoming payment. @@ -592,11 +635,12 @@ pub(crate) enum Hop { /// HMAC of the next hop's onion packet. next_hop_hmac: [u8; 32], /// Bytes of the onion packet we're forwarding. - new_packet_bytes: [u8; 20*65], + new_packet_bytes: [u8; ONION_DATA_LEN], }, } /// Error returned when we fail to decode the onion packet. +#[derive(Debug)] pub(crate) enum OnionDecodeErr { /// The HMAC of the onion packet did not match the hop data. Malformed { @@ -610,11 +654,27 @@ pub(crate) enum OnionDecodeErr { }, } -pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result { +pub(crate) fn decode_next_payment_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], payment_hash: PaymentHash) -> Result { + match decode_next_hop(shared_secret, hop_data, hmac_bytes, payment_hash) { + Ok((next_hop_data, None)) => Ok(Hop::Receive(next_hop_data)), + Ok((next_hop_data, Some((next_hop_hmac, FixedSizeOnionPacket(new_packet_bytes))))) => { + Ok(Hop::Forward { + next_hop_data, + next_hop_hmac, + new_packet_bytes + }) + }, + Err(e) => Err(e), + } +} + +pub(crate) fn decode_next_hop, N: NextPacketBytes>(shared_secret: [u8; 32], hop_data: &[u8], hmac_bytes: [u8; 32], decode_input: D) -> Result<(R, Option<([u8; 32], N)>), OnionDecodeErr> { let (rho, mu) = gen_rho_mu_from_shared_secret(&shared_secret); let mut hmac = HmacEngine::::new(&mu); hmac.input(hop_data); - hmac.input(&payment_hash.0[..]); + if let Some(payment_hash) = decode_input.payment_hash() { + hmac.input(&payment_hash.0[..]); + } if !fixed_time_eq(&Hmac::from_engine(hmac).into_inner(), &hmac_bytes) { return Err(OnionDecodeErr::Malformed { err_msg: "HMAC Check failed", @@ -624,7 +684,7 @@ pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_byt let mut chacha = ChaCha20::new(&rho, &[0u8; 8]); let mut chacha_stream = ChaChaReader { chacha: &mut chacha, read: Cursor::new(&hop_data[..]) }; - match ::read(&mut chacha_stream) { + match R::read(&mut chacha_stream, decode_input.read_arg()) { Err(err) => { let error_code = match err { msgs::DecodeError::UnknownVersion => 0x4000 | 1, // unknown realm byte @@ -662,10 +722,10 @@ pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_byt chacha_stream.read_exact(&mut next_bytes).unwrap(); assert_ne!(next_bytes[..], [0; 32][..]); } - return Ok(Hop::Receive(msg)); + return Ok((msg, None)); // We are the final destination for this packet } else { - let mut new_packet_bytes = [0; 20*65]; - let read_pos = chacha_stream.read(&mut new_packet_bytes).unwrap(); + let mut new_packet_bytes = N::new(hop_data.len()); + let read_pos = chacha_stream.read(new_packet_bytes.as_mut()).unwrap(); #[cfg(debug_assertions)] { // Check two things: @@ -677,12 +737,8 @@ pub(crate) fn decode_next_hop(shared_secret: [u8; 32], hop_data: &[u8], hmac_byt } // Once we've emptied the set of bytes our peer gave us, encrypt 0 bytes until we // fill the onion hop data we'll forward to our next-hop peer. - chacha_stream.chacha.process_in_place(&mut new_packet_bytes[read_pos..]); - return Ok(Hop::Forward { - next_hop_data: msg, - next_hop_hmac: hmac, - new_packet_bytes, - }) + chacha_stream.chacha.process_in_place(&mut new_packet_bytes.as_mut()[read_pos..]); + return Ok((msg, Some((hmac, new_packet_bytes)))) // This packet needs forwarding } }, } diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index f4cb57f28df..3b18b1186fe 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -10,9 +10,12 @@ //! LDK sends, receives, and forwards onion messages via the [`OnionMessenger`]. See its docs for //! more information. +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 chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Sign}; +use chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Recipient, Sign}; use ln::msgs; use ln::onion_utils; use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs}; @@ -104,6 +107,97 @@ impl OnionMessenger ); Ok(()) } + + /// Handle an incoming onion message. Currently, if a message was destined for us we will log, but + /// soon we'll delegate the onion message to a handler that can generate invoices or send + /// payments. + pub fn handle_onion_message(&self, _peer_node_id: &PublicKey, msg: &msgs::OnionMessage) { + let control_tlvs_ss = match self.keys_manager.ecdh(Recipient::Node, &msg.blinding_point, None) { + Ok(ss) => ss, + Err(e) => { + log_error!(self.logger, "Failed to retrieve node secret: {:?}", e); + return + } + }; + let onion_decode_ss = { + let blinding_factor = { + let mut hmac = HmacEngine::::new(b"blinded_node_id"); + hmac.input(control_tlvs_ss.as_ref()); + Hmac::from_engine(hmac).into_inner() + }; + match self.keys_manager.ecdh(Recipient::Node, &msg.onion_routing_packet.public_key, + Some(&blinding_factor)) + { + Ok(ss) => ss.secret_bytes(), + Err(()) => { + log_trace!(self.logger, "Failed to compute onion packet shared secret"); + return + } + } + }; + match onion_utils::decode_next_hop(onion_decode_ss, &msg.onion_routing_packet.hop_data[..], + msg.onion_routing_packet.hmac, control_tlvs_ss) + { + Ok((Payload::Receive { + control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }) + }, None)) => { + log_info!(self.logger, "Received an onion message with path_id: {:02x?}", path_id); + }, + Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs { + next_node_id, 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 + // 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 routes with dummy hops currently, we should be ok to not handle this + // for now. + let new_pubkey = match onion_utils::next_hop_packet_pubkey(&self.secp_ctx, msg.onion_routing_packet.public_key, &onion_decode_ss) { + Ok(pk) => pk, + Err(e) => { + log_trace!(self.logger, "Failed to compute next hop packet pubkey: {}", e); + return + } + }; + let outgoing_packet = Packet { + version: 0, + public_key: new_pubkey, + hop_data: new_packet_bytes, + hmac: next_hop_hmac, + }; + + let mut pending_per_peer_msgs = self.pending_messages.lock().unwrap(); + let pending_msgs = pending_per_peer_msgs.entry(next_node_id).or_insert(Vec::new()); + pending_msgs.push( + msgs::OnionMessage { + blinding_point: match next_blinding_override { + Some(blinding_point) => blinding_point, + None => { + let blinding_factor = { + let mut sha = Sha256::engine(); + sha.input(&msg.blinding_point.serialize()[..]); + sha.input(control_tlvs_ss.as_ref()); + Sha256::from_engine(sha).into_inner() + }; + let mut next_blinding_point = msg.blinding_point; + if let Err(e) = next_blinding_point.mul_assign(&self.secp_ctx, &blinding_factor[..]) { + log_trace!(self.logger, "Failed to compute next blinding point: {}", e); + return + } + next_blinding_point + }, + }, + onion_routing_packet: outgoing_packet, + }, + ); + }, + Err(e) => { + log_trace!(self.logger, "Errored decoding onion message packet: {:?}", e); + }, + _ => { + log_trace!(self.logger, "Received bogus onion message packet, either the sender encoded a final hop as a forwarding hop or vice versa"); + }, + }; + } } // TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs index ed0206a2adb..a3414d844ed 100644 --- a/lightning/src/onion_message/packet.rs +++ b/lightning/src/onion_message/packet.rs @@ -10,15 +10,16 @@ //! Structs and enums useful for constructing and reading an onion message packet. use bitcoin::secp256k1::PublicKey; +use bitcoin::secp256k1::ecdh::SharedSecret; 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 util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; +use util::ser::{FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer}; use core::cmp; -use io; +use io::{self, Read}; use prelude::*; // Per the spec, an onion message packet's `hop_data` field length should be @@ -28,14 +29,14 @@ pub(super) const BIG_PACKET_HOP_DATA_LEN: usize = 32768; #[derive(Clone, Debug, PartialEq)] pub(crate) struct Packet { - version: u8, - public_key: PublicKey, + pub(super) version: u8, + pub(super) public_key: PublicKey, // Unlike the onion packets used for payments, onion message packets can have payloads greater // than 1300 bytes. // TODO: if 1300 ends up being the most common size, optimize this to be: // enum { ThirteenHundred([u8; 1300]), VarLen(Vec) } - hop_data: Vec, - hmac: [u8; 32], + pub(super) hop_data: Vec, + pub(super) hmac: [u8; 32], } impl onion_utils::Packet for Packet { @@ -156,3 +157,94 @@ impl Writeable for (Payload, [u8; 32]) { Ok(()) } } + +// Uses the provided secret to simultaneously decode and decrypt the control TLVs. +impl ReadableArgs for Payload { + fn read(mut r: &mut R, encrypted_tlvs_ss: SharedSecret) -> Result { + use bitcoin::consensus::encode::{Decodable, Error, VarInt}; + let v: VarInt = Decodable::consensus_decode(&mut r) + .map_err(|e| match e { + Error::Io(ioe) => DecodeError::from(ioe), + _ => DecodeError::InvalidValue + })?; + + let mut rd = FixedLengthReader::new(r, v.0); + // TODO: support reply paths + let mut _reply_path_bytes: Option> = Some(Vec::new()); + let mut read_adapter: Option> = None; + let rho = onion_utils::gen_rho_from_shared_secret(&encrypted_tlvs_ss.secret_bytes()); + decode_tlv_stream!(&mut rd, { + (2, _reply_path_bytes, vec_type), + (4, read_adapter, (option: LengthReadableArgs, rho)) + }); + rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?; + + match read_adapter { + None => return Err(DecodeError::InvalidValue), + Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Forward(tlvs)}) => { + Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs))) + }, + Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs)}) => { + Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs)}) + }, + } + } +} + +/// When reading a packet off the wire, we don't know a priori whether the packet is to be forwarded +/// or received. Thus we read a ControlTlvs rather than reading a ForwardControlTlvs or +/// ReceiveControlTlvs directly. +pub(super) enum ControlTlvs { + /// This onion message is intended to be forwarded. + Forward(ForwardTlvs), + /// This onion message is intended to be received. + Receive(ReceiveTlvs), +} + +impl Readable for ControlTlvs { + fn read(mut r: &mut R) -> Result { + let mut _padding: Option = None; + let mut _short_channel_id: Option = None; + let mut next_node_id: Option = None; + let mut path_id: Option<[u8; 32]> = None; + let mut next_blinding_override: Option = None; + decode_tlv_stream!(&mut r, { + (1, _padding, option), + (2, _short_channel_id, option), + (4, next_node_id, option), + (6, path_id, option), + (8, next_blinding_override, option), + }); + + 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 payload_fmt = if valid_fwd_fmt { + ControlTlvs::Forward(ForwardTlvs { + next_node_id: next_node_id.unwrap(), + next_blinding_override, + }) + } else if valid_recv_fmt { + ControlTlvs::Receive(ReceiveTlvs { + path_id, + }) + } else { + return Err(DecodeError::InvalidValue) + }; + + Ok(payload_fmt) + } +} + +/// Reads padding to the end, ignoring what's read. +pub(crate) struct Padding {} +impl Readable for Padding { + #[inline] + fn read(reader: &mut R) -> Result { + loop { + let mut buf = [0; 8192]; + if reader.read(&mut buf[..])? == 0 { break; } + } + Ok(Self {}) + } +} From b26fb851cd08882e0fec4c1f67b6ba946c3138ae Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 23 Jun 2022 15:45:33 -0400 Subject: [PATCH 07/11] Significantly expand onion message documentation --- lightning/src/onion_message/messenger.rs | 50 +++++++++++++++++++++++- lightning/src/onion_message/mod.rs | 11 ++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 3b18b1186fe..77542872022 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -28,9 +28,56 @@ use sync::{Arc, Mutex}; use prelude::*; /// A sender, receiver and forwarder of onion messages. In upcoming releases, this object will be -/// used to retrieve invoices and fulfill invoice requests from [offers]. +/// used to retrieve invoices and fulfill invoice requests from [offers]. Currently, only sending +/// and receiving empty onion messages is supported. +/// +/// # Example +/// +// Needs to be `ignore` until the `onion_message` module is made public, otherwise this is a test +// failure. +/// ```ignore +/// # extern crate bitcoin; +/// # use bitcoin::hashes::_export::_core::time::Duration; +/// # use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +/// # use lightning::chain::keysinterface::{InMemorySigner, KeysManager, KeysInterface}; +/// # use lightning::onion_message::{BlindedRoute, Destination, OnionMessenger}; +/// # use lightning::util::logger::{Logger, Record}; +/// # use std::sync::Arc; +/// # struct FakeLogger {}; +/// # impl Logger for FakeLogger { +/// # fn log(&self, record: &Record) { unimplemented!() } +/// # } +/// # let seed = [42u8; 32]; +/// # let time = Duration::from_secs(123456); +/// # let keys_manager = KeysManager::new(&seed, time.as_secs(), time.subsec_nanos()); +/// # let logger = Arc::new(FakeLogger {}); +/// # let node_secret = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap(); +/// # let secp_ctx = Secp256k1::new(); +/// # let hop_node_id1 = PublicKey::from_secret_key(&secp_ctx, &node_secret); +/// # let (hop_node_id2, hop_node_id3, hop_node_id4) = (hop_node_id1, hop_node_id1, +/// hop_node_id1); +/// # let destination_node_id = hop_node_id1; +/// # +/// // Create the onion messenger. This must use the same `keys_manager` as is passed to your +/// // ChannelManager. +/// let onion_messenger = OnionMessenger::new(&keys_manager, logger); +/// +/// // Send an empty onion message to a node id. +/// let intermediate_hops = [hop_node_id1, hop_node_id2]; +/// onion_messenger.send_onion_message(&intermediate_hops, Destination::Node(destination_node_id)); +/// +/// // Create a blinded route 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_route = BlindedRoute::new::(&hops, &keys_manager, &secp_ctx).unwrap(); +/// +/// // Send an empty onion message to a blinded route. +/// # let intermediate_hops = [hop_node_id1, hop_node_id2]; +/// onion_messenger.send_onion_message(&intermediate_hops, Destination::BlindedRoute(blinded_route)); +/// ``` /// /// [offers]: +/// [`OnionMessenger`]: crate::onion_message::OnionMessenger pub struct OnionMessenger where K::Target: KeysInterface, L::Target: Logger, @@ -79,6 +126,7 @@ impl OnionMessenger } /// Send an empty onion message to `destination`, routing it through `intermediate_nodes`. + /// See [`OnionMessenger`] for example usage. 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"); diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index 9236341fa61..a874fbc4335 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -8,6 +8,17 @@ // licenses. //! Onion Messages: sending, receiving, forwarding, and ancillary utilities live here +//! +//! Onion messages are multi-purpose messages sent between peers over the lightning network. In the +//! near future, they will be used to communicate invoices for [offers], unlocking use cases such as +//! static invoices, refunds and proof of payer. Further, you will be able to accept payments +//! without revealing your node id through the use of [blinded routes]. +//! +//! LDK sends and receives onion messages via the [`OnionMessenger`]. See its documentation for more +//! information on its usage. +//! +//! [offers]: +//! [blinded routes]: crate::onion_message::BlindedRoute mod blinded_route; mod messenger; From eaff561e244943f6383ec9fb26e3f71dc628a104 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sat, 4 Jun 2022 13:22:20 -0700 Subject: [PATCH 08/11] Add test utilities and integration tests for onion messages --- .../src/onion_message/functional_tests.rs | 107 ++++++++++++++++++ lightning/src/onion_message/messenger.rs | 8 ++ lightning/src/onion_message/mod.rs | 2 + 3 files changed, 117 insertions(+) create mode 100644 lightning/src/onion_message/functional_tests.rs diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs new file mode 100644 index 00000000000..510852cef83 --- /dev/null +++ b/lightning/src/onion_message/functional_tests.rs @@ -0,0 +1,107 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Onion message testing and test utilities live here. + +use chain::keysinterface::{KeysInterface, Recipient}; +use super::{BlindedRoute, Destination, OnionMessenger}; +use util::enforcing_trait_impls::EnforcingSigner; +use util::test_utils; + +use bitcoin::network::constants::Network; +use bitcoin::secp256k1::{PublicKey, Secp256k1}; + +use sync::Arc; + +struct MessengerNode { + keys_manager: Arc, + messenger: OnionMessenger, Arc>, + logger: Arc, +} + +impl MessengerNode { + fn get_node_pk(&self) -> PublicKey { + let secp_ctx = Secp256k1::new(); + PublicKey::from_secret_key(&secp_ctx, &self.keys_manager.get_node_secret(Recipient::Node).unwrap()) + } +} + +fn create_nodes(num_messengers: u8) -> Vec { + let mut res = Vec::new(); + for i in 0..num_messengers { + let logger = Arc::new(test_utils::TestLogger::with_id(format!("node {}", i))); + let seed = [i as u8; 32]; + let keys_manager = Arc::new(test_utils::TestKeysInterface::new(&seed, Network::Testnet)); + res.push(MessengerNode { + keys_manager: keys_manager.clone(), + messenger: OnionMessenger::new(keys_manager, logger.clone()), + logger, + }); + } + res +} + +fn pass_along_path(mut path: Vec, expected_path_id: Option<[u8; 32]>) { + let mut prev_node = path.remove(0); + let num_nodes = path.len(); + for (idx, node) in path.into_iter().enumerate() { + let events = prev_node.messenger.release_pending_msgs(); + assert_eq!(events.len(), 1); + let onion_msg = { + let msgs = events.get(&node.get_node_pk()).unwrap(); + assert_eq!(msgs.len(), 1); + msgs[0].clone() + }; + node.messenger.handle_onion_message(&prev_node.get_node_pk(), &onion_msg); + if idx == num_nodes - 1 { + node.logger.assert_log_contains( + "lightning::onion_message::messenger".to_string(), + format!("Received an onion message with path_id: {:02x?}", expected_path_id).to_string(), 1); + } + prev_node = node; + } +} + +#[test] +fn one_hop() { + let nodes = create_nodes(2); + + nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk())).unwrap(); + pass_along_path(nodes, None); +} + +#[test] +fn two_unblinded_hops() { + let nodes = create_nodes(3); + + nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk())).unwrap(); + pass_along_path(nodes, None); +} + +#[test] +fn two_unblinded_two_blinded() { + let nodes = create_nodes(5); + + let secp_ctx = Secp256k1::new(); + let blinded_route = BlindedRoute::new::(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx).unwrap(); + + nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedRoute(blinded_route)).unwrap(); + pass_along_path(nodes, None); +} + +#[test] +fn three_blinded_hops() { + let nodes = create_nodes(4); + + let secp_ctx = Secp256k1::new(); + let blinded_route = BlindedRoute::new::(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx).unwrap(); + + nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap(); + pass_along_path(nodes, None); +} diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 77542872022..930d90ebac3 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -246,6 +246,14 @@ impl OnionMessenger }, }; } + + #[cfg(test)] + pub(super) fn release_pending_msgs(&self) -> HashMap> { + let mut pending_msgs = self.pending_messages.lock().unwrap(); + let mut msgs = HashMap::new(); + core::mem::swap(&mut *pending_msgs, &mut msgs); + msgs + } } // TODO: parameterize the below Simple* types with OnionMessenger and handle the messages it diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index a874fbc4335..966b10b60fb 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -24,6 +24,8 @@ mod blinded_route; mod messenger; mod packet; mod utils; +#[cfg(test)] +mod functional_tests; // Re-export structs so they can be imported with just the `onion_message::` module prefix. pub use self::blinded_route::{BlindedRoute, BlindedHop}; From 6500c99f29d626c78c7848586a0018453f64fe45 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 23 Jun 2022 15:47:25 -0400 Subject: [PATCH 09/11] Add SendError enum for onion messages and error on too-big packets --- .../src/onion_message/functional_tests.rs | 18 ++++++++++-- lightning/src/onion_message/messenger.rs | 28 +++++++++++++++---- lightning/src/onion_message/mod.rs | 2 +- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 510852cef83..560028c5bba 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -10,12 +10,12 @@ //! Onion message testing and test utilities live here. use chain::keysinterface::{KeysInterface, Recipient}; -use super::{BlindedRoute, Destination, OnionMessenger}; +use super::{BlindedRoute, Destination, OnionMessenger, SendError}; use util::enforcing_trait_impls::EnforcingSigner; use util::test_utils; use bitcoin::network::constants::Network; -use bitcoin::secp256k1::{PublicKey, Secp256k1}; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use sync::Arc; @@ -105,3 +105,17 @@ fn three_blinded_hops() { nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap(); pass_along_path(nodes, None); } + +#[test] +fn too_big_packet_error() { + // Make sure we error as expected if a packet is too big to send. + let nodes = create_nodes(1); + + let hop_secret = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap(); + let secp_ctx = Secp256k1::new(); + let hop_node_id = PublicKey::from_secret_key(&secp_ctx, &hop_secret); + + let hops = [hop_node_id; 400]; + let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id)).unwrap_err(); + assert_eq!(err, SendError::TooBigPacket); +} diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 930d90ebac3..6f5d9e8a52c 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -108,6 +108,18 @@ impl Destination { } } +/// Errors that may occur when [sending an onion message]. +/// +/// [sending an onion message]: OnionMessenger::send_onion_message +#[derive(Debug, PartialEq)] +pub enum SendError { + /// Errored computing onion message packet keys. + Secp256k1(secp256k1::Error), + /// Because implementations such as Eclair will drop onion messages where the message packet + /// exceeds 32834 bytes, we refuse to send messages where the packet exceeds this size. + TooBigPacket, +} + impl OnionMessenger where K::Target: KeysInterface, L::Target: Logger, @@ -127,7 +139,7 @@ impl OnionMessenger /// Send an empty onion message to `destination`, routing it through `intermediate_nodes`. /// See [`OnionMessenger`] for example usage. - pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), secp256k1::Error> { + pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), SendError> { 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 { @@ -140,10 +152,12 @@ impl OnionMessenger } }; let (packet_payloads, packet_keys) = packet_payloads_and_keys( - &self.secp_ctx, intermediate_nodes, destination, &blinding_secret)?; + &self.secp_ctx, intermediate_nodes, destination, &blinding_secret) + .map_err(|e| SendError::Secp256k1(e))?; let prng_seed = self.keys_manager.get_secure_random_bytes(); - let onion_packet = construct_onion_message_packet(packet_payloads, packet_keys, prng_seed); + let onion_packet = construct_onion_message_packet( + packet_payloads, packet_keys, prng_seed).map_err(|()| SendError::TooBigPacket)?; 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()); @@ -343,7 +357,8 @@ fn packet_payloads_and_keys( Ok((payloads, onion_packet_keys)) } -fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys: Vec, prng_seed: [u8; 32]) -> Packet { +/// Errors if the serialized payload size exceeds onion_message::BIG_PACKET_HOP_DATA_LEN +fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys: Vec, prng_seed: [u8; 32]) -> Result { // 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 @@ -353,7 +368,8 @@ fn construct_onion_message_packet(payloads: Vec<(Payload, [u8; 32])>, onion_keys SMALL_PACKET_HOP_DATA_LEN } else if payloads_ser_len <= BIG_PACKET_HOP_DATA_LEN { BIG_PACKET_HOP_DATA_LEN - } else { payloads_ser_len }; + } else { return Err(()) }; - onion_utils::construct_onion_message_packet::<_, _>(payloads, onion_keys, prng_seed, hop_data_len) + Ok(onion_utils::construct_onion_message_packet::<_, _>( + payloads, onion_keys, prng_seed, hop_data_len)) } diff --git a/lightning/src/onion_message/mod.rs b/lightning/src/onion_message/mod.rs index 966b10b60fb..2e23b76ada3 100644 --- a/lightning/src/onion_message/mod.rs +++ b/lightning/src/onion_message/mod.rs @@ -29,5 +29,5 @@ mod functional_tests; // 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::{Destination, OnionMessenger, SimpleArcOnionMessenger, SimpleRefOnionMessenger}; +pub use self::messenger::{Destination, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger}; pub(crate) use self::packet::Packet; From 39397d4e14ed87e62cf8f313de749b7b5785561e Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 23 Jun 2022 15:51:43 -0400 Subject: [PATCH 10/11] Error when attempting to send an OM to a blinded route with 0 hops --- .../src/onion_message/functional_tests.rs | 21 +++++++++++++++++++ lightning/src/onion_message/messenger.rs | 8 +++++++ 2 files changed, 29 insertions(+) diff --git a/lightning/src/onion_message/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs index 560028c5bba..695064e467c 100644 --- a/lightning/src/onion_message/functional_tests.rs +++ b/lightning/src/onion_message/functional_tests.rs @@ -119,3 +119,24 @@ fn too_big_packet_error() { let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id)).unwrap_err(); assert_eq!(err, SendError::TooBigPacket); } + +#[test] +fn invalid_blinded_route_error() { + // Make sure we error as expected if a provided blinded route has 0 or 1 hops. + let mut nodes = create_nodes(3); + let (node1, node2, node3) = (nodes.remove(0), nodes.remove(0), nodes.remove(0)); + + // 0 hops + let secp_ctx = Secp256k1::new(); + let mut blinded_route = BlindedRoute::new::(&[node2.get_node_pk(), node3.get_node_pk()], &*node3.keys_manager, &secp_ctx).unwrap(); + blinded_route.blinded_hops.clear(); + let err = node1.messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap_err(); + assert_eq!(err, SendError::TooFewBlindedHops); + + // 1 hop + let mut blinded_route = BlindedRoute::new::(&[node2.get_node_pk(), node3.get_node_pk()], &*node3.keys_manager, &secp_ctx).unwrap(); + blinded_route.blinded_hops.remove(0); + assert_eq!(blinded_route.blinded_hops.len(), 1); + let err = node1.messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route)).unwrap_err(); + assert_eq!(err, SendError::TooFewBlindedHops); +} diff --git a/lightning/src/onion_message/messenger.rs b/lightning/src/onion_message/messenger.rs index 6f5d9e8a52c..7eba3cdd254 100644 --- a/lightning/src/onion_message/messenger.rs +++ b/lightning/src/onion_message/messenger.rs @@ -118,6 +118,9 @@ pub enum SendError { /// Because implementations such as Eclair will drop onion messages where the message packet /// exceeds 32834 bytes, we refuse to send messages where the packet exceeds this size. TooBigPacket, + /// The provided [`Destination`] was an invalid [`BlindedRoute`], due to having fewer than two + /// blinded hops. + TooFewBlindedHops, } impl OnionMessenger @@ -140,6 +143,11 @@ impl OnionMessenger /// Send an empty onion message to `destination`, routing it through `intermediate_nodes`. /// See [`OnionMessenger`] for example usage. pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination) -> Result<(), SendError> { + if let Destination::BlindedRoute(BlindedRoute { ref blinded_hops, .. }) = destination { + if blinded_hops.len() < 2 { + return Err(SendError::TooFewBlindedHops); + } + } 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 { From 17ec697f8f09d608f6a3d2703ed4f4409773b4bf Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 12 Jul 2022 16:04:42 -0400 Subject: [PATCH 11/11] Fix possible incomplete read bug on onion packet decode Pre-existing to this PR, we were reading next packet bytes with io::Read::read, which is not guaranteed to read all the bytes we need, only guaranteed to read *some* bytes. We fix this to be read_exact, which is guaranteed to read all the next hop packet bytes. --- lightning/src/ln/onion_utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 145eb8acbae..f81c619d735 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -725,7 +725,8 @@ pub(crate) fn decode_next_hop, N: NextPa return Ok((msg, None)); // We are the final destination for this packet } else { let mut new_packet_bytes = N::new(hop_data.len()); - let read_pos = chacha_stream.read(new_packet_bytes.as_mut()).unwrap(); + let read_pos = hop_data.len() - chacha_stream.read.position() as usize; + chacha_stream.read_exact(&mut new_packet_bytes.as_mut()[..read_pos]).unwrap(); #[cfg(debug_assertions)] { // Check two things: