diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 4343a92f148..274c0446cd9 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -14,7 +14,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; @@ -50,6 +50,8 @@ pub struct ForwardNode { /// 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 of 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`]. @@ -67,6 +69,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 of 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. @@ -78,6 +82,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), @@ -85,6 +90,21 @@ pub(crate) enum BlindedPaymentTlvs { Receive(ReceiveTlvs), } +impl BlindedPaymentTlvs { + pub(crate) fn pad_to_length(mut self, length: usize) -> Self { + let pad_length = length.checked_sub(self.serialized_length()); + debug_assert!(pad_length.is_some(), "Size of this packet should not be larger than the size of largest packet."); + let padding = Some(Padding::new(pad_length.unwrap())); + + match &mut self { + BlindedPaymentTlvs::Forward(tlvs) => tlvs.padding = padding, + BlindedPaymentTlvs::Receive(tlvs) => tlvs.padding = padding, + } + + self + } +} + /// Parameters for relaying over a given [`BlindedHop`]. /// /// [`BlindedHop`]: crate::blinded_path::BlindedHop @@ -198,6 +218,7 @@ impl Writeable for ForwardTlvs { if self.features == BlindedHopFeatures::empty() { None } else { Some(&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), @@ -210,6 +231,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) @@ -246,6 +268,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(), @@ -254,6 +277,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(), @@ -272,7 +296,14 @@ pub(super) fn blinded_hops( let tlvs = intermediate_nodes.iter().map(|node| BlindedPaymentTlvs::Forward(node.tlvs.clone())) .chain(core::iter::once(BlindedPaymentTlvs::Receive(payee_tlvs))); - utils::construct_blinded_hops(secp_ctx, pks, tlvs, session_priv) + let max_length = tlvs.clone() + .map(|tlv| tlv.serialized_length()) + .max() + .unwrap_or(0); + + let length_tlvs = tlvs.map(|tlv| tlv.pad_to_length(max_length)); + + utils::construct_blinded_hops(secp_ctx, pks, length_tlvs, session_priv) } // Advance the blinded onion payment path by one hop, so make the second hop into the new @@ -484,6 +515,7 @@ mod tests { let intermediate_nodes = vec![ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 144, @@ -500,6 +532,7 @@ mod tests { }, ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 144, @@ -515,6 +548,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, @@ -534,6 +568,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, @@ -557,6 +592,7 @@ mod tests { let intermediate_nodes = vec![ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -573,6 +609,7 @@ mod tests { }, ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -588,6 +625,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, @@ -608,6 +646,7 @@ mod tests { let intermediate_nodes = vec![ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -624,6 +663,7 @@ mod tests { }, ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -639,6 +679,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, @@ -663,6 +704,7 @@ mod tests { let intermediate_nodes = vec![ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -679,6 +721,7 @@ mod tests { }, ForwardNode { node_id: dummy_pk, tlvs: ForwardTlvs { + padding: None, short_channel_id: 0, payment_relay: PaymentRelay { cltv_expiry_delta: 0, @@ -694,6 +737,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 e15204e1766..ff8b51a259b 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -39,6 +39,7 @@ fn blinded_payment_path( intermediate_nodes.push(ForwardNode { 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, @@ -57,6 +58,7 @@ fn blinded_payment_path( }); } let payee_tlvs = ReceiveTlvs { + padding: None, payment_secret, payment_constraints: PaymentConstraints { max_cltv_expiry: u32::max_value(), @@ -104,6 +106,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(), @@ -148,6 +151,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(), @@ -1293,6 +1297,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(), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 28c40856f50..edcc286a1d6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -9345,6 +9345,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 096bcf9633c..f493807752a 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -159,6 +159,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(), diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index e7b43ded11d..b9d8c32d0f1 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -2758,7 +2758,8 @@ impl ReadableArgs<(Option, &NS)> for InboundOnionPayload w 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 + padding: _, short_channel_id, payment_relay, + payment_constraints, features })} => { if amt.is_some() || cltv_value.is_some() || total_msat.is_some() || keysend_preimage.is_some() @@ -2774,7 +2775,8 @@ impl ReadableArgs<(Option, &NS)> for InboundOnionPayload w }) }, 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 71fde86d0b6..65f53c7cddc 100644 --- a/lightning/src/routing/router.rs +++ b/lightning/src/routing/router.rs @@ -146,6 +146,7 @@ impl> + Clone, L: Deref, ES: Deref, S: Deref, }; Some(payment::ForwardNode { tlvs: ForwardTlvs { + padding: None, short_channel_id, payment_relay, payment_constraints,