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/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/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/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/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/msgs.rs b/lightning/src/ln/msgs.rs index 3e44cfcf024..01863a9071c 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,6 +31,8 @@ use bitcoin::blockdata::script::Script; 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; @@ -40,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, ReadableArgs, Writeable, Writer, FixedLengthReader, HighZeroBytesDroppedVarInt, Hostname}; use ln::{PaymentPreimage, PaymentHash, PaymentSecret}; @@ -304,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 { @@ -993,6 +1003,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()) { @@ -1327,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)?; @@ -1372,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 b223a344dbe..f81c619d735 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}; @@ -30,21 +30,29 @@ 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 { +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] -pub(super) fn gen_rho_mu_from_shared_secret(shared_secret: &[u8]) -> ([u8; 32], [u8; 32]) { +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(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 @@ -74,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()[..]); @@ -187,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 { @@ -210,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)] @@ -229,12 +238,50 @@ 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)) +} + +/// 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 + } +} + +pub(crate) fn payloads_serialized_length(payloads: &Vec) -> usize { + payloads.iter().map(|p| p.serialized_length() + 32 /* HMAC */).sum() } -/// 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 { +/// 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 +{ 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)); @@ -243,7 +290,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 :( } @@ -251,7 +298,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); @@ -263,29 +310,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 @@ -534,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. @@ -546,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 { @@ -564,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", @@ -578,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 @@ -616,10 +722,11 @@ 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 = 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: @@ -631,12 +738,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 } }, } @@ -775,7 +878,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()); @@ -854,7 +957,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/blinded_route.rs b/lightning/src/onion_message/blinded_route.rs new file mode 100644 index 00000000000..d18372e3b00 --- /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 + 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 + pub(super) blinding_point: PublicKey, + /// The hops composing the blinded route. + 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. + 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. + pub(super) 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, 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 { + 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. + 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. + pub(super) 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. + pub(super) 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/functional_tests.rs b/lightning/src/onion_message/functional_tests.rs new file mode 100644 index 00000000000..695064e467c --- /dev/null +++ b/lightning/src/onion_message/functional_tests.rs @@ -0,0 +1,142 @@ +// 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, SendError}; +use util::enforcing_trait_impls::EnforcingSigner; +use util::test_utils; + +use bitcoin::network::constants::Network; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; + +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); +} + +#[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); +} + +#[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 new file mode 100644 index 00000000000..7eba3cdd254 --- /dev/null +++ b/lightning/src/onion_message/messenger.rs @@ -0,0 +1,383 @@ +// 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::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, Recipient, 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; +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]. 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, +{ + keys_manager: K, + logger: L, + pending_messages: Mutex>>, + secp_ctx: Secp256k1, + // Coming soon: + // invoice_handler: InvoiceHandler, + // 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(), + } + } +} + +/// 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, + /// The provided [`Destination`] was an invalid [`BlindedRoute`], due to having fewer than two + /// blinded hops. + TooFewBlindedHops, +} + +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, + } + } + + /// 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 { + (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) + .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).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()); + pending_msgs.push( + msgs::OnionMessage { + blinding_point, + onion_routing_packet: onion_packet, + } + ); + 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"); + }, + }; + } + + #[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 +// 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; + +/// 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)) +} + +/// 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 + // 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 { return Err(()) }; + + 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 new file mode 100644 index 00000000000..2e23b76ada3 --- /dev/null +++ b/lightning/src/onion_message/mod.rs @@ -0,0 +1,33 @@ +// 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 +//! +//! 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; +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}; +pub use self::messenger::{Destination, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger}; +pub(crate) use self::packet::Packet; diff --git a/lightning/src/onion_message/packet.rs b/lightning/src/onion_message/packet.rs new file mode 100644 index 00000000000..a3414d844ed --- /dev/null +++ b/lightning/src/onion_message/packet.rs @@ -0,0 +1,250 @@ +// 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 bitcoin::secp256k1::ecdh::SharedSecret; + +use ln::msgs::DecodeError; +use ln::onion_utils; +use super::blinded_route::{ForwardTlvs, ReceiveTlvs}; +use util::chacha20poly1305rfc::{ChaChaPolyReadAdapter, ChaChaPolyWriteAdapter}; +use util::ser::{FixedLengthReader, LengthRead, LengthReadable, LengthReadableArgs, Readable, ReadableArgs, Writeable, Writer}; + +use core::cmp; +use io::{self, Read}; +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 { + 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) } + pub(super) hop_data: Vec, + pub(super) 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, + }) + } +} + +/// 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(()) + } +} + +// 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 {}) + } +} diff --git a/lightning/src/onion_message/utils.rs b/lightning/src/onion_message/utils.rs new file mode 100644 index 00000000000..9b95183e74b --- /dev/null +++ b/lightning/src/onion_message/utils.rs @@ -0,0 +1,100 @@ +// 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 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, 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(); + 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, $encrypted_payload: 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, $encrypted_payload); + (encrypted_data_ss, onion_packet_ss) + }} + } + + macro_rules! build_keys_in_loop { + ($pk: expr, $blinded: expr, $encrypted_payload: expr) => { + let (encrypted_data_ss, onion_packet_ss) = build_keys!($pk, $blinded, $encrypted_payload); + + let msg_blinding_point_blinding_factor = { + let mut sha = Sha256::engine(); + 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_in_loop!(*pk, false, None); + } + if let Some(dest) = destination { + match dest { + Destination::Node(pk) => { + build_keys!(pk, false, None); + }, + Destination::BlindedRoute(BlindedRoute { blinded_hops, .. }) => { + for hop in blinded_hops { + build_keys_in_loop!(hop.blinded_node_id, true, Some(hop.encrypted_payload)); + } + }, + } + } + Ok(()) +} 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 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() }