Skip to content

Commit

Permalink
Support custom onion messages
Browse files Browse the repository at this point in the history
In ser_macros, we now allow decode_tlv_stream to decode 1 custom TLV within a
specified range of TLV types. We also need to fail to deserialize if there is
more than 1 TLV in that specified range, otherwise peers could waste our
processing time.

This change is needed to support offers and async payment onion messages, for
the above peers-can-waste-cpu reason.
  • Loading branch information
valentinewallace committed Oct 7, 2022
1 parent a031ff9 commit a0997ed
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 58 deletions.
17 changes: 9 additions & 8 deletions fuzz/src/onion_message.rs

Large diffs are not rendered by default.

36 changes: 22 additions & 14 deletions lightning/src/onion_message/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
use chain::keysinterface::{KeysInterface, Recipient};
use ln::features::InitFeatures;
use ln::msgs::{self, OnionMessageHandler};
use super::{BlindedRoute, Destination, OnionMessenger, SendError};
use super::{BlindedRoute, Destination, OnionMessenger, SendError, Tlv};
use util::enforcing_trait_impls::EnforcingSigner;
use util::test_utils;

Expand Down Expand Up @@ -80,103 +80,111 @@ fn pass_along_path(path: &Vec<MessengerNode>, expected_path_id: Option<[u8; 32]>
#[test]
fn one_hop() {
let nodes = create_nodes(2);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };

nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), None).unwrap();
nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), test_tlv, None).unwrap();
pass_along_path(&nodes, None);
}

#[test]
fn two_unblinded_hops() {
let nodes = create_nodes(3);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };

nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk()), None).unwrap();
nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk()), test_tlv, None).unwrap();
pass_along_path(&nodes, None);
}

#[test]
fn two_unblinded_two_blinded() {
let nodes = create_nodes(5);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };

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), None).unwrap();
nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedRoute(blinded_route), test_tlv, None).unwrap();
pass_along_path(&nodes, None);
}

#[test]
fn three_blinded_hops() {
let nodes = create_nodes(4);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };

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), None).unwrap();
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), test_tlv, None).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(2);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };

let hop_node_id = nodes[1].get_node_pk();
let hops = [hop_node_id; 400];
let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id), None).unwrap_err();
let err = nodes[0].messenger.send_onion_message(&hops, Destination::Node(hop_node_id), test_tlv, None).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 nodes = create_nodes(3);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };

// 0 hops
let secp_ctx = Secp256k1::new();
let mut blinded_route = BlindedRoute::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
blinded_route.blinded_hops.clear();
let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), None).unwrap_err();
let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), test_tlv.clone(), None).unwrap_err();
assert_eq!(err, SendError::TooFewBlindedHops);

// 1 hop
let mut blinded_route = BlindedRoute::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx).unwrap();
blinded_route.blinded_hops.remove(0);
assert_eq!(blinded_route.blinded_hops.len(), 1);
let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), None).unwrap_err();
let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), test_tlv, None).unwrap_err();
assert_eq!(err, SendError::TooFewBlindedHops);
}

#[test]
fn reply_path() {
let nodes = create_nodes(4);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };
let secp_ctx = Secp256k1::new();

// Destination::Node
let reply_path = BlindedRoute::new(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();
nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::Node(nodes[3].get_node_pk()), Some(reply_path)).unwrap();
nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::Node(nodes[3].get_node_pk()), test_tlv.clone(), Some(reply_path)).unwrap();
pass_along_path(&nodes, None);
// Make sure the last node successfully decoded the reply path.
nodes[3].logger.assert_log_contains(
"lightning::onion_message::messenger".to_string(),
format!("Received an onion message with path_id: None and reply_path").to_string(), 1);
format!("Received an onion message with path_id None and a reply_path").to_string(), 1);

// Destination::BlindedRoute
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();
let reply_path = BlindedRoute::new(&[nodes[2].get_node_pk(), nodes[1].get_node_pk(), nodes[0].get_node_pk()], &*nodes[0].keys_manager, &secp_ctx).unwrap();

nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), Some(reply_path)).unwrap();
nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), test_tlv, Some(reply_path)).unwrap();
pass_along_path(&nodes, None);
nodes[3].logger.assert_log_contains(
"lightning::onion_message::messenger".to_string(),
format!("Received an onion message with path_id: None and reply_path").to_string(), 2);
format!("Received an onion message with path_id None and a reply_path").to_string(), 2);
}

#[test]
fn peer_buffer_full() {
let nodes = create_nodes(2);
let test_tlv = Tlv { tag: 4242, value: vec![42; 32] };
for _ in 0..188 { // Based on MAX_PER_PEER_BUFFER_SIZE in OnionMessenger
nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), None).unwrap();
nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), test_tlv.clone(), None).unwrap();
}
let err = nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), None).unwrap_err();
let err = nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), test_tlv, None).unwrap_err();
assert_eq!(err, SendError::BufferFull);
}
38 changes: 25 additions & 13 deletions lightning/src/onion_message/messenger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ use ln::features::{InitFeatures, NodeFeatures};
use ln::msgs::{self, OnionMessageHandler};
use ln::onion_utils;
use super::blinded_route::{BlindedRoute, ForwardTlvs, ReceiveTlvs};
pub use super::packet::Tlv;
use super::packet::{BIG_PACKET_HOP_DATA_LEN, ForwardControlTlvs, Packet, Payload, ReceiveControlTlvs, SMALL_PACKET_HOP_DATA_LEN};
use super::utils;
use util::events::OnionMessageProvider;
use util::logger::Logger;
use util::ser::Writeable;

use core::mem;
use core::ops::Deref;
use sync::{Arc, Mutex};
use prelude::*;
Expand All @@ -41,7 +43,7 @@ use prelude::*;
/// # 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::onion_message::{BlindedRoute, Destination, OnionMessenger, Tlv};
/// # use lightning::util::logger::{Logger, Record};
/// # use std::sync::Arc;
/// # struct FakeLogger {};
Expand All @@ -63,20 +65,25 @@ use prelude::*;
/// // ChannelManager.
/// let onion_messenger = OnionMessenger::new(&keys_manager, logger);
///
/// // Send an empty onion message to a node id.
/// // Send an custom onion message to a node id.
/// let intermediate_hops = [hop_node_id1, hop_node_id2];
/// let reply_path = None;
/// onion_messenger.send_onion_message(&intermediate_hops, Destination::Node(destination_node_id), reply_path);
/// # let your_tlv_type = 4242;
/// # let your_message_bytes = vec![42; 32];
/// let message = Tlv { tag: your_tlv_type, value: your_message_bytes };
/// onion_messenger.send_onion_message(&intermediate_hops, Destination::Node(destination_node_id), message, reply_path);
///
/// // 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.
/// // Send a custom onion message to a blinded route.
/// # let intermediate_hops = [hop_node_id1, hop_node_id2];
/// let reply_path = None;
/// onion_messenger.send_onion_message(&intermediate_hops, Destination::BlindedRoute(blinded_route), reply_path);
/// # let your_message_bytes = vec![42; 32];
/// let message = Tlv { tag: your_tlv_type, value: your_message_bytes };
/// onion_messenger.send_onion_message(&intermediate_hops, Destination::BlindedRoute(blinded_route), message, reply_path);
/// ```
///
/// [offers]: <https://github.com/lightning/bolts/pull/798>
Expand Down Expand Up @@ -147,9 +154,9 @@ impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
}
}

/// Send an empty onion message to `destination`, routing it through `intermediate_nodes`.
/// Send an onion message with payload `message` to `destination`, routing it through `intermediate_nodes`.
/// See [`OnionMessenger`] for example usage.
pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
pub fn send_onion_message(&self, intermediate_nodes: &[PublicKey], destination: Destination, message: Tlv, reply_path: Option<BlindedRoute>) -> Result<(), SendError> {
if let Destination::BlindedRoute(BlindedRoute { ref blinded_hops, .. }) = destination {
if blinded_hops.len() < 2 {
return Err(SendError::TooFewBlindedHops);
Expand All @@ -167,7 +174,7 @@ impl<Signer: Sign, K: Deref, L: Deref> OnionMessenger<Signer, K, L>
}
};
let (packet_payloads, packet_keys) = packet_payloads_and_keys(
&self.secp_ctx, intermediate_nodes, destination, reply_path, &blinding_secret)
&self.secp_ctx, intermediate_nodes, destination, message, reply_path, &blinding_secret)
.map_err(|e| SendError::Secp256k1(e))?;

let prng_seed = self.keys_manager.get_secure_random_bytes();
Expand Down Expand Up @@ -256,11 +263,11 @@ impl<Signer: Sign, K: Deref, L: Deref> OnionMessageHandler for OnionMessenger<Si
msg.onion_routing_packet.hmac, control_tlvs_ss)
{
Ok((Payload::Receive {
control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
message, control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id }), reply_path,
}, None)) => {
log_info!(self.logger,
"Received an onion message with path_id: {:02x?} and {}reply_path",
path_id, if reply_path.is_some() { "" } else { "no " });
"Received an onion message with path_id {:02x?} and {} reply_path: {:?}",
path_id, if reply_path.is_some() { "a" } else { "no" }, message);
},
Ok((Payload::Forward(ForwardControlTlvs::Unblinded(ForwardTlvs {
next_node_id, next_blinding_override
Expand Down Expand Up @@ -396,8 +403,8 @@ pub type SimpleRefOnionMessenger<'a, 'b, L> = OnionMessenger<InMemorySigner, &'a
/// Construct onion packet payloads and keys for sending an onion message along the given
/// `unblinded_path` to the given `destination`.
fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, mut reply_path:
Option<BlindedRoute>, session_priv: &SecretKey
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], destination: Destination, mut message: Tlv,
mut reply_path: Option<BlindedRoute>, session_priv: &SecretKey
) -> Result<(Vec<(Payload, [u8; 32])>, Vec<onion_utils::OnionKeys>), secp256k1::Error> {
let num_hops = unblinded_path.len() + destination.num_hops();
let mut payloads = Vec::with_capacity(num_hops);
Expand Down Expand Up @@ -440,9 +447,13 @@ fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
control_tlvs_ss));
blinded_path_idx += 1;
} else if let Some(encrypted_payload) = enc_payload_opt {
let mut msg = Tlv { tag: message.tag, value: Vec::new() };
// Used to get around the compiler's "use of moved value" error
mem::swap(&mut msg.value, &mut message.value);
payloads.push((Payload::Receive {
control_tlvs: ReceiveControlTlvs::Blinded(encrypted_payload),
reply_path: reply_path.take(),
message: msg,
}, control_tlvs_ss));
}

Expand All @@ -462,6 +473,7 @@ fn packet_payloads_and_keys<T: secp256k1::Signing + secp256k1::Verification>(
payloads.push((Payload::Receive {
control_tlvs: ReceiveControlTlvs::Unblinded(ReceiveTlvs { path_id: None, }),
reply_path: reply_path.take(),
message,
}, control_tlvs_ss));
}

Expand Down
2 changes: 1 addition & 1 deletion lightning/src/onion_message/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger};
pub use self::messenger::{Destination, OnionMessenger, SendError, SimpleArcOnionMessenger, SimpleRefOnionMessenger, Tlv};
pub(crate) use self::packet::Packet;
58 changes: 41 additions & 17 deletions lightning/src/onion_message/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,18 @@ pub(super) enum Payload {
Receive {
control_tlvs: ReceiveControlTlvs,
reply_path: Option<BlindedRoute>,
// Coming soon:
// message: Message,
message: Tlv,
}
}

// Coming soon:
// enum Message {
// InvoiceRequest(InvoiceRequest),
// Invoice(Invoice),
// InvoiceError(InvoiceError),
// CustomMessage<T>,
// }
#[derive(Clone, Debug)]
///
pub struct Tlv {
///
pub tag: u64,
///
pub value: Vec<u8>,
}

/// Forward control TLVs in their blinded and unblinded form.
pub(super) enum ForwardControlTlvs {
Expand Down Expand Up @@ -141,11 +141,13 @@ impl Writeable for (Payload, [u8; 32]) {
})
},
Payload::Receive {
control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes), reply_path
control_tlvs: ReceiveControlTlvs::Blinded(encrypted_bytes), reply_path,
message: Tlv { tag, value, },
} => {
encode_varint_length_prefixed_tlv!(w, {
(2, reply_path, option),
(4, encrypted_bytes, vec_type)
(4, encrypted_bytes, vec_type),
(*tag, value, vec_type)
})
},
Payload::Forward(ForwardControlTlvs::Unblinded(control_tlvs)) => {
Expand All @@ -156,30 +158,45 @@ impl Writeable for (Payload, [u8; 32]) {
},
Payload::Receive {
control_tlvs: ReceiveControlTlvs::Unblinded(control_tlvs), reply_path,
message: Tlv { tag, value, },
} => {
let write_adapter = ChaChaPolyWriteAdapter::new(self.1, &control_tlvs);
encode_varint_length_prefixed_tlv!(w, {
(2, reply_path, option),
(4, write_adapter, required)
(4, write_adapter, required),
(*tag, value, vec_type)
})
},
}
Ok(())
}
}

// Uses the provided secret to simultaneously decode and decrypt the control TLVs.
// Uses the provided secret to simultaneously decode and decrypt the control TLVs and data TLV.
impl ReadableArgs<SharedSecret> for Payload {
fn read<R: Read>(r: &mut R, encrypted_tlvs_ss: SharedSecret) -> Result<Self, DecodeError> {
let v: BigSize = Readable::read(r)?;
let mut rd = FixedLengthReader::new(r, v.0);
let mut reply_path: Option<BlindedRoute> = None;
let mut read_adapter: Option<ChaChaPolyReadAdapter<ControlTlvs>> = None;
let rho = onion_utils::gen_rho_from_shared_secret(&encrypted_tlvs_ss.secret_bytes());
let (mut message_type, mut message_bytes) = (None, None);
let mut decode_custom_tlv = |msg_type, mut msg_reader: &mut FixedLengthReader<&mut &mut FixedLengthReader<&mut R>>| {
if msg_type >= 64 {
// Don't allow reading more than one data TLV from an onion message.
if message_bytes.is_some() || message_type.is_some() {
return Err(DecodeError::InvalidValue)
}
message_type = Some(msg_type);
decode_tlv!(msg_reader, message_bytes, vec_type);
return Ok(true)
}
Ok(false)
};
decode_tlv_stream!(&mut rd, {
(2, reply_path, option),
(4, read_adapter, (option: LengthReadableArgs, rho))
});
(4, read_adapter, (option: LengthReadableArgs, rho)),
}, decode_custom_tlv);
rd.eat_remaining().map_err(|_| DecodeError::ShortRead)?;

match read_adapter {
Expand All @@ -188,8 +205,15 @@ impl ReadableArgs<SharedSecret> for Payload {
Ok(Payload::Forward(ForwardControlTlvs::Unblinded(tlvs)))
},
Some(ChaChaPolyReadAdapter { readable: ControlTlvs::Receive(tlvs)}) => {
Ok(Payload::Receive { control_tlvs: ReceiveControlTlvs::Unblinded(tlvs), reply_path })
},
if message_type.is_none() || message_bytes.is_none() {
return Err(DecodeError::InvalidValue)
}
Ok(Payload::Receive {
control_tlvs: ReceiveControlTlvs::Unblinded(tlvs),
reply_path,
message: Tlv { tag: message_type.unwrap(), value: message_bytes.unwrap() },
})
}
}
}
}
Expand Down
Loading

0 comments on commit a0997ed

Please sign in to comment.