diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 12b2f6d7b62..d2ca2f89720 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -12,7 +12,7 @@ use bitcoin::secp256k1::{self, PublicKey, Secp256k1, SecretKey}; use crate::blinded_path::{BlindedHop, BlindedPath, IntroductionNode, NodeIdLookUp}; -use crate::blinded_path::utils; +use crate::blinded_path::utils::{self, Padding}; use crate::crypto::streams::ChaChaPolyReadAdapter; use crate::io; use crate::io::Cursor; @@ -90,7 +90,7 @@ impl BlindedPaymentPath { // be in relation to a specific channel. let htlc_maximum_msat = u64::max_value(); Self::new( - &[], payee_node_id, payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, + &mut [], payee_node_id, payee_tlvs, htlc_maximum_msat, min_final_cltv_expiry_delta, entropy_source, secp_ctx ) } @@ -103,7 +103,7 @@ impl BlindedPaymentPath { /// * any unknown features are required in the provided [`ForwardTlvs`] // TODO: make all payloads the same size with padding + add dummy hops pub fn new( - intermediate_nodes: &[PaymentForwardNode], payee_node_id: PublicKey, + intermediate_nodes: &mut [PaymentForwardNode], payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, htlc_maximum_msat: u64, min_final_cltv_expiry_delta: u16, entropy_source: ES, secp_ctx: &Secp256k1, ) -> Result where ES::Target: EntropySource { @@ -234,6 +234,8 @@ pub struct PaymentForwardNode { /// Data to construct a [`BlindedHop`] for forwarding a payment. #[derive(Clone, Debug)] pub struct ForwardTlvs { + /// The padding data used to make all packets of a blinded path the same size + pub padding: Option, /// The short channel id this payment should be forwarded out over. pub short_channel_id: u64, /// Payment parameters for relaying over [`Self::short_channel_id`]. @@ -254,6 +256,8 @@ pub struct ForwardTlvs { /// may not be valid if received by another lightning implementation. #[derive(Clone, Debug)] pub struct ReceiveTlvs { + /// The padding data used to make all packets of a blinded path the same size + pub padding: Option, /// Used to authenticate the sender of a payment to the receiver and tie MPP HTLCs together. pub payment_secret: PaymentSecret, /// Constraints for the receiver of this payment. @@ -265,6 +269,7 @@ pub struct ReceiveTlvs { /// Data to construct a [`BlindedHop`] for sending a payment over. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop +#[derive(Clone)] pub(crate) enum BlindedPaymentTlvs { /// This blinded payment data is for a forwarding node. Forward(ForwardTlvs), @@ -284,6 +289,28 @@ enum BlindedPaymentTlvsMut<'a> { Receive(&'a mut ReceiveTlvs), } +impl<'a> BlindedPaymentTlvsMut<'a> { + pub(crate) fn pad_to_length(mut self, length: usize) -> Self { + let padding = match length.checked_sub(self.serialized_length()) { + Some(length) => Some(Padding::new(length)), + None => { + debug_assert!( + false, + "Size of this packet should not be larger than the size of largest packet." + ); + None + } + }; + + match self { + BlindedPaymentTlvsMut::Forward(ref mut tlvs) => tlvs.padding = padding, + BlindedPaymentTlvsMut::Receive(ref mut tlvs) => tlvs.padding = padding, + } + + self + } +} + /// Parameters for relaying over a given [`BlindedHop`]. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop @@ -396,6 +423,7 @@ impl Writeable for ForwardTlvs { if self.features == BlindedHopFeatures::empty() { None } else { Some(WithoutLength(&self.features)) }; encode_tlv_stream!(w, { + (1, self.padding, option), (2, self.short_channel_id, required), (10, self.payment_relay, required), (12, self.payment_constraints, required), @@ -408,6 +436,7 @@ impl Writeable for ForwardTlvs { impl Writeable for ReceiveTlvs { fn write(&self, w: &mut W) -> Result<(), io::Error> { encode_tlv_stream!(w, { + (1, self.padding, option), (12, self.payment_constraints, required), (65536, self.payment_secret, required), (65537, self.payment_context, required) @@ -418,7 +447,6 @@ impl Writeable for ReceiveTlvs { impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { fn write(&self, w: &mut W) -> Result<(), io::Error> { - // TODO: write padding match self { Self::Forward(tlvs) => tlvs.write(w)?, Self::Receive(tlvs) => tlvs.write(w)?, @@ -429,7 +457,6 @@ impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { impl<'a> Writeable for BlindedPaymentTlvsMut<'a> { fn write(&self, w: &mut W) -> Result<(), io::Error> { - // TODO: write padding match self { Self::Forward(tlvs) => tlvs.write(w)?, Self::Receive(tlvs) => tlvs.write(w)?, @@ -457,6 +484,7 @@ impl Readable for BlindedPaymentTlvs { return Err(DecodeError::InvalidValue) } Ok(BlindedPaymentTlvs::Forward(ForwardTlvs { + padding: None, short_channel_id, payment_relay: payment_relay.ok_or(DecodeError::InvalidValue)?, payment_constraints: payment_constraints.0.unwrap(), @@ -466,6 +494,7 @@ impl Readable for BlindedPaymentTlvs { } else { if payment_relay.is_some() || features.is_some() { return Err(DecodeError::InvalidValue) } Ok(BlindedPaymentTlvs::Receive(ReceiveTlvs { + padding: None, payment_secret: payment_secret.ok_or(DecodeError::InvalidValue)?, payment_constraints: payment_constraints.0.unwrap(), payment_context: payment_context.0.unwrap(), @@ -476,15 +505,20 @@ impl Readable for BlindedPaymentTlvs { /// Construct blinded payment hops for the given `intermediate_nodes` and payee info. pub(super) fn blinded_hops( - secp_ctx: &Secp256k1, intermediate_nodes: &[PaymentForwardNode], - payee_node_id: PublicKey, payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, + secp_ctx: &Secp256k1, intermediate_nodes: &mut [PaymentForwardNode], + payee_node_id: PublicKey, mut payee_tlvs: ReceiveTlvs, session_priv: &SecretKey, ) -> Result, secp256k1::Error> { - let pks = intermediate_nodes.iter().map(|node| node.node_id) - .chain(core::iter::once(payee_node_id)); - let tlvs = intermediate_nodes.iter().map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) - .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))); - - let path = pks.zip(tlvs); + let max_length = intermediate_nodes.iter().map(|node| BlindedPaymentTlvsRef::Forward(&node.tlvs)) + .chain(core::iter::once(BlindedPaymentTlvsRef::Receive(&payee_tlvs))) + .map(|tlv| tlv.serialized_length()) + .max() + .unwrap_or(0); + + let path = intermediate_nodes + .iter_mut() + .map(|node| (node.node_id, BlindedPaymentTlvsMut::Forward(&mut node.tlvs))) + .chain(core::iter::once((payee_node_id, BlindedPaymentTlvsMut::Receive(&mut payee_tlvs)))) + .map(|(pubkey, tlvs)| (pubkey, tlvs.pad_to_length(max_length))); utils::construct_blinded_hops(secp_ctx, path, session_priv) } @@ -661,6 +695,7 @@ mod tests { let intermediate_nodes = vec![PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 144, @@ -678,6 +713,7 @@ mod tests { }, PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 144, @@ -694,6 +730,7 @@ mod tests { htlc_maximum_msat: u64::max_value(), }]; let recv_tlvs = ReceiveTlvs { + padding: None, payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, @@ -713,6 +750,7 @@ mod tests { #[test] fn compute_payinfo_1_hop() { let recv_tlvs = ReceiveTlvs { + padding: None, payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, @@ -736,6 +774,7 @@ mod tests { let intermediate_nodes = vec![PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -753,6 +792,7 @@ mod tests { }, PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -769,6 +809,7 @@ mod tests { htlc_maximum_msat: u64::max_value() }]; let recv_tlvs = ReceiveTlvs { + padding: None, payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, @@ -789,6 +830,7 @@ mod tests { let intermediate_nodes = vec![PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -806,6 +848,7 @@ mod tests { }, PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -822,6 +865,7 @@ mod tests { htlc_maximum_msat: u64::max_value() }]; let recv_tlvs = ReceiveTlvs { + padding: None, payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, @@ -846,6 +890,7 @@ mod tests { let intermediate_nodes = vec![PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -863,6 +908,7 @@ mod tests { }, PaymentForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -879,6 +925,7 @@ mod tests { htlc_maximum_msat: 10_000 }]; let recv_tlvs = ReceiveTlvs { + padding: None, payment_secret: PaymentSecret([0; 32]), payment_constraints: PaymentConstraints { max_cltv_expiry: 0, diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index d099e439ae5..f63905ac7f3 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -47,6 +47,7 @@ fn blinded_payment_path( intermediate_nodes.push(PaymentForwardNode { node_id: *node_id, tlvs: ForwardTlvs { + padding: None, short_channel_id: chan_upd.short_channel_id, payment_relay: PaymentRelay { cltv_expiry_delta: chan_upd.cltv_expiry_delta, @@ -66,6 +67,7 @@ fn blinded_payment_path( }); } let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -76,7 +78,7 @@ fn blinded_payment_path( }; let mut secp_ctx = Secp256k1::new(); BlindedPaymentPath::new( - &intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs, + &mut intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs, intro_node_max_htlc_opt.unwrap_or_else(|| channel_upds.last().unwrap().htlc_maximum_msat), TEST_FINAL_CLTV as u16, keys_manager, &secp_ctx ).unwrap() @@ -113,6 +115,7 @@ fn do_one_hop_blinded_path(success: bool) { let amt_msat = 5000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -122,7 +125,7 @@ fn do_one_hop_blinded_path(success: bool) { }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &mut [], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); @@ -157,6 +160,7 @@ fn mpp_to_one_hop_blinded_path() { let amt_msat = 15_000_000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -165,7 +169,7 @@ fn mpp_to_one_hop_blinded_path() { payment_context: PaymentContext::unknown(), }; let blinded_path = BlindedPaymentPath::new( - &[], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &mut [], nodes[3].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[3].keys_manager, &secp_ctx ).unwrap(); @@ -298,7 +302,7 @@ fn do_forward_checks_failure(check: ForwardCheckFail, intro_fails: bool) { let mut route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1, 1_0000_0000, nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2, &chan_upd_2_3], &chanmon_cfgs[3].keys_manager); - route_params.payment_params.max_path_length = 18; + route_params.payment_params.max_path_length = 17; let route = get_route(&nodes[0], &route_params).unwrap(); node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); @@ -1303,6 +1307,7 @@ fn custom_tlvs_to_blinded_path() { let amt_msat = 5000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[1], Some(amt_msat), None); let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -1312,7 +1317,7 @@ fn custom_tlvs_to_blinded_path() { }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &mut [], nodes[1].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[1].keys_manager, &secp_ctx ).unwrap(); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 7e13d56667f..58438389df9 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9680,6 +9680,7 @@ where let max_cltv_expiry = self.best_block.read().unwrap().height + CLTV_FAR_FAR_AWAY + LATENCY_GRACE_PERIOD_BLOCKS; let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry, diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 380fe21984b..99a18ed086b 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -158,6 +158,7 @@ fn one_hop_blinded_path_with_custom_tlv() { let amt_msat = 100_000; let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -167,7 +168,7 @@ fn one_hop_blinded_path_with_custom_tlv() { }; let mut secp_ctx = Secp256k1::new(); let blinded_path = BlindedPaymentPath::new( - &[], nodes[2].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, + &mut [], nodes[2].node.get_our_node_id(), payee_tlvs, u64::MAX, TEST_FINAL_CLTV as u16, &chanmon_cfgs[2].keys_manager, &secp_ctx ).unwrap(); let route_params = RouteParameters::from_payment_params_and_value( diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index be5ecb27ae0..140cc956cc8 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2822,7 +2822,8 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); match ChaChaPolyReadAdapter::read(&mut reader, rho)? { ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs { - short_channel_id, payment_relay, payment_constraints, features, next_blinding_override + padding: _, short_channel_id, payment_relay, + payment_constraints, features, next_blinding_override })} => { if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || keysend_preimage.is_some() @@ -2839,7 +2840,8 @@ impl ReadableArgs<(Option, NS)> for InboundOnionPayload wh }) }, ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs { - payment_secret, payment_constraints, payment_context + padding: _, payment_secret, payment_constraints, + payment_context })} => { if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } Ok(Self::BlindedReceive { diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs index b0a3f34a676..9bf34234609 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -150,6 +150,7 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size }; Some(PaymentForwardNode { tlvs: ForwardTlvs { + padding: None, short_channel_id, payment_relay, payment_constraints, @@ -162,7 +163,7 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size }) .map(|forward_node| { BlindedPaymentPath::new( - &[forward_node], recipient, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, + &mut [forward_node], recipient, tlvs.clone(), u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx ) }) @@ -174,7 +175,7 @@ impl>, L: Deref, ES: Deref, S: Deref, SP: Size _ => { if network_graph.nodes().contains_key(&NodeId::from_pubkey(&recipient)) { BlindedPaymentPath::new( - &[], recipient, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, + &mut [], recipient, tlvs, u64::MAX, MIN_FINAL_CLTV_EXPIRY_DELTA, &*self.entropy_source, secp_ctx ).map(|path| vec![path]) } else {