Skip to content

Add onion message blinded control tlv payload padding #1771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 148 additions & 10 deletions lightning/src/onion_message/blinded_route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey};

use chain::keysinterface::KeysInterface;
use super::utils;
use ::get_control_tlv_length;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems bad. Will check why #[macro_export] places the get_control_tlv_length in the root of the lightning crate for onion_message::utils, but not for in ln::functional_test_utils and similar.

use ln::msgs::DecodeError;
use util::chacha20poly1305rfc::ChaChaPolyWriteAdapter;
use util::ser::{Readable, VecWriter, Writeable, Writer};
use super::packet::{ControlTlvs, Padding};

use io;
use prelude::*;
Expand Down Expand Up @@ -54,10 +56,11 @@ impl BlindedRoute {
/// 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<K: KeysInterface, T: secp256k1::Signing + secp256k1::Verification>
(node_pks: &[PublicKey], keys_manager: &K, secp_ctx: &Secp256k1<T>) -> Result<Self, ()>
{
// TODO: Add dummy hops
pub fn new<K: KeysInterface, T: secp256k1::Signing + secp256k1::Verification> (
node_pks: &[PublicKey], keys_manager: &K, secp_ctx: &Secp256k1<T>,
include_next_blinding_override_padding: bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think we can get rid of this parameter, we should only need to make the blinded hops equal length for the sake of the sender not being able to tell which hop is the last

But based on the spec test vectors I can see why you added this. Asked Rusty to clarify: https://github.com/lightning/bolts/pull/759/files#r997283120

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks for asking :)!

IIUC another case where we also need to support padding for next blinding override in blinded ForwardTlvs is for "concatenation of two blinded routes", included in the route blinding test vectors bolt04/route-blinding-test.json and detailed in 04-onion-routing.md.
Though perhaps it's too early to start preparing for support of that 😅.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"concatenation of two blinded routes", included in the route blinding test vectors bolt04/route-blinding-test.json and detailed in 04-onion-routing.md.
Though perhaps it's too early to start preparing for support of that 😅.

I've always disagreed with that spec wording, bc that just means sending from a list of unblinded hops to a blinded route (i.e. send_onion_message(vec![intermed_node_1, intermed_node_2], Destination::BlindedRoute(..)) in the current codebase)

) -> Result<Self, ()> {
if node_pks.len() < 2 { return Err(()) }
let blinding_secret_bytes = keys_manager.get_secure_random_bytes();
let blinding_secret = SecretKey::from_slice(&blinding_secret_bytes[..]).expect("RNG is busted");
Expand All @@ -66,16 +69,18 @@ impl BlindedRoute {
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(|_| ())?,
blinded_hops: blinded_hops(secp_ctx, node_pks, &blinding_secret, include_next_blinding_override_padding).map_err(|_| ())?,
})
}
}

/// Construct blinded hops for the given `unblinded_path`.
fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey
secp_ctx: &Secp256k1<T>, unblinded_path: &[PublicKey], session_priv: &SecretKey,
include_next_blinding_override_padding: bool
) -> Result<Vec<BlindedHop>, secp256k1::Error> {
let mut blinded_hops = Vec::with_capacity(unblinded_path.len());
let max_length = get_control_tlv_length!(true, include_next_blinding_override_padding);

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, _| {
Expand All @@ -84,6 +89,7 @@ fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
let payload = ForwardTlvs {
next_node_id: pk,
next_blinding_override: None,
total_length: max_length,
};
blinded_hops.push(BlindedHop {
blinded_node_id: prev_blinded_node_id,
Expand All @@ -95,7 +101,7 @@ fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
})?;

if let Some((final_ss, final_blinded_node_id)) = prev_ss_and_blinded_node_id {
let final_payload = ReceiveTlvs { path_id: None };
let final_payload = ReceiveTlvs { path_id: None, total_length: max_length, };
blinded_hops.push(BlindedHop {
blinded_node_id: final_blinded_node_id,
encrypted_payload: encrypt_payload(final_payload, final_ss),
Expand Down Expand Up @@ -150,39 +156,171 @@ impl_writeable!(BlindedHop, {

/// 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`].
#[derive(Clone, Copy)]
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<PublicKey>,
/// The length the tlv should have when it's serialized, with padding included if needed.
/// Used to ensure that all control tlvs in a blinded route have the same length.
pub(super) total_length: u16,
}

/// Similar to [`ForwardTlvs`], but these TLVs are for the final node.
#[derive(Clone, Copy)]
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]>,
/// The length the tlv should have when it's serialized, with padding included if needed.
/// Used to ensure that all control tlvs in a blinded route have the same length.
pub(super) total_length: u16,
}

impl Writeable for ForwardTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// TODO: write padding
encode_tlv_stream!(writer, {
(1, Padding::new_from_tlv(ControlTlvs::Forward(*self)), option),
(4, self.next_node_id, required),
(8, self.next_blinding_override, option)
(8, self.next_blinding_override, option),
});
Ok(())
}
}

impl Writeable for ReceiveTlvs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
// TODO: write padding
encode_tlv_stream!(writer, {
(1, Padding::new_from_tlv(ControlTlvs::Receive(*self)), option),
(6, self.path_id, option),
});
Ok(())
}
}

#[cfg(test)]
mod test {
use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1};
use ::get_control_tlv_length;
use super::{ForwardTlvs, ReceiveTlvs, blinded_hops};
use util::ser::{VecWriter, Writeable};

#[test]
fn padding_is_correctly_serialized() {
let max_length = get_control_tlv_length!(true, true);

let dummy_next_node_id = PublicKey::from_slice(&hex::decode("030101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dummy_blinding_override = PublicKey::from_slice(&hex::decode("030202020202020202020202020202020202020202020202020202020202020202").unwrap()[..]).unwrap();
let dummy_path_id = [1; 32];

let no_padding_tlv = ForwardTlvs {
next_node_id: dummy_next_node_id,
next_blinding_override: Some(dummy_blinding_override),
total_length: max_length,
};

let blinding_override_padding_tlv = ForwardTlvs {
next_node_id: dummy_next_node_id,
next_blinding_override: None,
total_length: max_length,
};

let recieve_tlv_padding_tlv = ReceiveTlvs {
path_id: Some(dummy_path_id),
total_length: max_length,
};

let full_padding_tlv = ReceiveTlvs {
path_id: None,
total_length: max_length,
};

let mut w = VecWriter(Vec::new());
no_padding_tlv.write(&mut w).unwrap();
let serialized_no_padding_tlv = w.0;
// As `serialized_no_padding_tlv` is the longest tlv, no padding is expected.
// Expected data tlv is:
// 1. 4 (type) for `next_node_id`
// 2. 33 (length) for the length of a point/public key
// 3. 33 bytes of the `dummy_next_node_id`
// 4. 8 (type) for `next_blinding_override`
// 5. 33 (length) for the length of a point/public key
// 6. 33 bytes of the `dummy_blinding_override`
let expected_serialized_no_padding_tlv_payload = &hex::decode("04210301010101010101010101010101010101010101010101010101010101010101010821030202020202020202020202020202020202020202020202020202020202020202").unwrap()[..];
assert_eq!(serialized_no_padding_tlv, expected_serialized_no_padding_tlv_payload);
assert_eq!(serialized_no_padding_tlv.len(), max_length as usize);

w = VecWriter(Vec::new());
blinding_override_padding_tlv.write(&mut w).unwrap();
let serialized_blinding_override_padding_tlv = w.0;
// As `serialized_blinding_override_padding_tlv` has no `next_blinding_override`, 35 bytes
// of padding is expected (the serialized length of `next_blinding_override`).
// Expected data tlv is:
// 1. 1 (type) for padding
// 2. 33 (length) given the length of a the missing `next_blinding_override`
// 3. 33 0 bytes of padding
// 4. 4 (type) for `next_node_id`
// 5. 33 (length) for the length of a point/public key
// 6. 33 bytes of the `dummy_next_node_id`
let expected_serialized_blinding_override_padding_tlv = &hex::decode("01210000000000000000000000000000000000000000000000000000000000000000000421030101010101010101010101010101010101010101010101010101010101010101").unwrap()[..];
assert_eq!(serialized_blinding_override_padding_tlv, expected_serialized_blinding_override_padding_tlv);
assert_eq!(serialized_blinding_override_padding_tlv.len(), max_length as usize);

w = VecWriter(Vec::new());
recieve_tlv_padding_tlv.write(&mut w).unwrap();
let serialized_recieve_tlv_padding_tlv = w.0;
// As `recieve_tlv_padding_tlv` is a `ReceiveTlv` and has a `path_id`, 36 bytes of padding
// is expected, ie. 70 (value of `max_length`) - 34 (the serialized length of `path_id`).
// Expected data tlv is:
// 1. 1 (type) for padding
// 2. 34 (length) given 70 - 34
// 3. 34 0 bytes of padding
// 4. 6 (type) for `path_id`
// 5. 32 (length) for the length of a `path_id`
// 6. 32 bytes of the `path_id`
let expected_serialized_recieve_tlv_padding_tlv_payload = &hex::decode("01220000000000000000000000000000000000000000000000000000000000000000000006200101010101010101010101010101010101010101010101010101010101010101").unwrap()[..];
assert_eq!(serialized_recieve_tlv_padding_tlv, expected_serialized_recieve_tlv_padding_tlv_payload);
assert_eq!(serialized_recieve_tlv_padding_tlv.len(), max_length as usize);

w = VecWriter(Vec::new());
full_padding_tlv.write(&mut w).unwrap();
let serialized_full_padding_tlv = w.0;
// As `serialized_full_padding_tlv` is a `ReceiveTlv` with no data at alll, 70 bytes of
// padding is expected (value of `max_length`).
// Expected data tlv is:
// 1. 1 (type) for padding
// 2. 68 (length) the length of the padding minus the prefix
// 3. 68 0 bytes of padding
let expected_serialized_full_padding_tlv_payload = &hex::decode("01440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap()[..];
assert_eq!(serialized_full_padding_tlv, expected_serialized_full_padding_tlv_payload);
assert_eq!(serialized_full_padding_tlv.len(), max_length as usize);
}

#[test]
fn blinded_hops_are_same_length() {
let secp_ctx = Secp256k1::new();
let first_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&hex::decode(format!("{:02}", 41).repeat(32)).unwrap()[..]).unwrap());
let middle_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&hex::decode(format!("{:02}", 42).repeat(32)).unwrap()[..]).unwrap());
let recieve_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&hex::decode(format!("{:02}", 43).repeat(32)).unwrap()[..]).unwrap());
let session_priv = SecretKey::from_slice(&hex::decode(format!("{:02}", 3).repeat(32)).unwrap()[..]).unwrap();

let blinded_hops = blinded_hops(&secp_ctx, &[first_node_id, middle_node_id, recieve_node_id], &session_priv, false).unwrap();

// Verify that the blinded hops returned from `blinded_hops` have the same
// `encrypted_payload` length, regardless of which type of payload it is.
let mut expected_encrypted_payload_len = None;
for blinded_hop in blinded_hops {
match expected_encrypted_payload_len {
None => {
expected_encrypted_payload_len = Some(blinded_hop.encrypted_payload.len());
},
Some(expected_len) => {
assert_eq!(blinded_hop.encrypted_payload.len(), expected_len)
}
}
}
}
}
90 changes: 82 additions & 8 deletions lightning/src/onion_message/functional_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ use chain::keysinterface::{KeysInterface, Recipient};
use ln::features::InitFeatures;
use ln::msgs::{self, OnionMessageHandler};
use super::{BlindedRoute, Destination, OnionMessenger, SendError};
use super::messenger::packet_payloads_and_keys;
use super::packet::{Payload, ForwardControlTlvs, ReceiveControlTlvs};
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;

Expand Down Expand Up @@ -98,7 +100,7 @@ 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();
let blinded_route = BlindedRoute::new(&[nodes[3].get_node_pk(), nodes[4].get_node_pk()], &*nodes[4].keys_manager, &secp_ctx, true).unwrap();

nodes[0].messenger.send_onion_message(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], Destination::BlindedRoute(blinded_route), None).unwrap();
pass_along_path(&nodes, None);
Expand All @@ -109,7 +111,7 @@ 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();
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, true).unwrap();

nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), None).unwrap();
pass_along_path(&nodes, None);
Expand All @@ -133,13 +135,13 @@ fn invalid_blinded_route_error() {

// 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();
let mut blinded_route = BlindedRoute::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx, true).unwrap();
blinded_route.blinded_hops.clear();
let err = nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), 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();
let mut blinded_route = BlindedRoute::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk()], &*nodes[2].keys_manager, &secp_ctx, true).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();
Expand All @@ -152,7 +154,7 @@ fn reply_path() {
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();
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, false).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();
pass_along_path(&nodes, None);
// Make sure the last node successfully decoded the reply path.
Expand All @@ -161,8 +163,8 @@ fn reply_path() {
format!("Received an onion message with path_id: None and 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();
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, true).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, false).unwrap();

nodes[0].messenger.send_onion_message(&[], Destination::BlindedRoute(blinded_route), Some(reply_path)).unwrap();
pass_along_path(&nodes, None);
Expand All @@ -180,3 +182,75 @@ fn peer_buffer_full() {
let err = nodes[0].messenger.send_onion_message(&[], Destination::Node(nodes[1].get_node_pk()), None).unwrap_err();
assert_eq!(err, SendError::BufferFull);
}

#[test]
fn onion_message_blinded_control_tlv_payloads_are_same_length() {
let nodes = create_nodes(4);
let secp_ctx = Secp256k1::new();
let two_blinded_hops = BlindedRoute::new(&[nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx, true).unwrap();
let three_blinded_hops = BlindedRoute::new(&[nodes[1].get_node_pk(), nodes[2].get_node_pk(), nodes[3].get_node_pk()], &*nodes[3].keys_manager, &secp_ctx, true).unwrap();
let session_priv = SecretKey::from_slice(&hex::decode(format!("{:02}", 3).repeat(32)).unwrap()[..]).unwrap();

let only_unblinded_payloads = packet_payloads_and_keys(&secp_ctx, &[nodes[0].get_node_pk(), nodes[1].get_node_pk()], Destination::Node(nodes[2].get_node_pk()), None, &session_priv).unwrap().0;
let one_unblinded_and_three_blinded_payloads = packet_payloads_and_keys(&secp_ctx, &[nodes[1].get_node_pk()], Destination::BlindedRoute(three_blinded_hops), None, &session_priv).unwrap().0;
// When more that one unblinded payload exists, the blinded payloads should be the same length
// as the largest unblinded payload.
let multiple_unblinded_and_blinded_payloads = packet_payloads_and_keys(&secp_ctx, &[nodes[0].get_node_pk(), nodes[1].get_node_pk()], Destination::BlindedRoute(two_blinded_hops), None, &session_priv).unwrap().0;

// Verify that the blinded contol tlv payloads returned from `packet_payloads_and_keys` have
// the same length, and that the payload for every blinded payload matches the length of the
// largest unblinded payload length.
Comment on lines +201 to +202
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec is a little unclear in regarding this, but the test vectors seems to indicate that this should be the case.

for payloads in [only_unblinded_payloads, one_unblinded_and_three_blinded_payloads, multiple_unblinded_and_blinded_payloads].iter() {
let mut longest_tlv_length = None;

macro_rules! assign_longest_tlv_length {
($unblinded_tlv_length: expr) => {
if longest_tlv_length.map(|current_len| $unblinded_tlv_length > current_len).unwrap_or(true) {
longest_tlv_length = Some($unblinded_tlv_length);
}
};
}

macro_rules! assert_correct_tlv_length {
($tlv_length: expr) => {
match longest_tlv_length {
None => {
longest_tlv_length = Some($tlv_length);
},
Some(expected_len) => {
assert_eq!($tlv_length, expected_len);
}
}
};
}

for payload in payloads {
match &payload.0 {
Payload::Forward(control_tlvs) => {
match control_tlvs {
ForwardControlTlvs::Blinded(bytes) => {
// 16 deducted to account for the 16 byte tag of the ChaCha encryption
// in Blinded ControlTLVs
assert_correct_tlv_length!(bytes.len() as u16 - 16);
},
ForwardControlTlvs::Unblinded(tlv) => {
assign_longest_tlv_length!(tlv.total_length);
},
}
},
Payload::Receive { control_tlvs, .. } => {
match control_tlvs {
ReceiveControlTlvs::Blinded(bytes) => {
// 16 deducted to account for the 16 byte tag of the ChaCha encryption
// in Blinded ControlTLVs
assert_correct_tlv_length!(bytes.len() as u16 - 16);
},
ReceiveControlTlvs::Unblinded(tlv) => {
assign_longest_tlv_length!(tlv.total_length);
},
}
},
};
}
}
}
Loading