From 1e12bdfb3ba47d77d37bf863ab2c1ac7a78596e1 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 23 Mar 2023 18:56:11 -0400 Subject: [PATCH 01/22] Parse blinding point in UpdateAddHTLC A blinding point is provided in update_add_htlc messages if we are relaying or receiving a payment within a blinded path, to decrypt the onion routing packet and the recipient-provided encrypted payload within. Will be used in upcoming commits. --- lightning/src/ln/channel.rs | 2 ++ lightning/src/ln/functional_tests.rs | 5 +++++ lightning/src/ln/msgs.rs | 5 +++++ lightning/src/ln/onion_payment.rs | 1 + 4 files changed, 13 insertions(+) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2862e360e33..4650a9c143a 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -4076,6 +4076,7 @@ impl Channel where cltv_expiry: htlc.cltv_expiry, onion_routing_packet: (**onion_packet).clone(), skimmed_fee_msat: htlc.skimmed_fee_msat, + blinding_point: None, }); } } @@ -5617,6 +5618,7 @@ impl Channel where cltv_expiry, onion_routing_packet, skimmed_fee_msat, + blinding_point: None, }; self.context.next_holder_htlc_id += 1; diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index cc00d93b17a..8df252586a3 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -1415,6 +1415,7 @@ fn test_fee_spike_violation_fails_htlc() { cltv_expiry: htlc_cltv, onion_routing_packet: onion_packet, skimmed_fee_msat: None, + blinding_point: None, }; nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg); @@ -1611,6 +1612,7 @@ fn test_chan_reserve_violation_inbound_htlc_outbound_channel() { cltv_expiry: htlc_cltv, onion_routing_packet: onion_packet, skimmed_fee_msat: None, + blinding_point: None, }; nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &msg); @@ -1789,6 +1791,7 @@ fn test_chan_reserve_violation_inbound_htlc_inbound_chan() { cltv_expiry: htlc_cltv, onion_routing_packet: onion_packet, skimmed_fee_msat: None, + blinding_point: None, }; nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &msg); @@ -3510,6 +3513,7 @@ fn fail_backward_pending_htlc_upon_channel_failure() { cltv_expiry, onion_routing_packet, skimmed_fee_msat: None, + blinding_point: None, }; nodes[0].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add_htlc); } @@ -6481,6 +6485,7 @@ fn test_update_add_htlc_bolt2_receiver_check_max_htlc_limit() { cltv_expiry: htlc_cltv, onion_routing_packet: onion_packet.clone(), skimmed_fee_msat: None, + blinding_point: None, }; for i in 0..50 { diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index b2825df4a9c..349cc5c728a 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -682,6 +682,9 @@ pub struct UpdateAddHTLC { pub skimmed_fee_msat: Option, /// The onion routing packet with encrypted data for the next hop. pub onion_routing_packet: OnionPacket, + /// Provided if we are relaying or receiving a payment within a blinded path, to decrypt the onion + /// routing packet and the recipient-provided encrypted payload within. + pub blinding_point: Option, } /// An onion message to be sent to or received from a peer. @@ -2212,6 +2215,7 @@ impl_writeable_msg!(UpdateAddHTLC, { cltv_expiry, onion_routing_packet, }, { + (0, blinding_point, option), (65537, skimmed_fee_msat, option) }); @@ -3757,6 +3761,7 @@ mod tests { cltv_expiry: 821716, onion_routing_packet, skimmed_fee_msat: None, + blinding_point: None, }; let encoded_value = update_add_htlc.encode(); let target_value = >::from_hex("020202020202020202020202020202020202020202020202020202020202020200083a840000034d32144668701144760101010101010101010101010101010101010101010101010101010101010101000c89d4ff031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202").unwrap(); diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index d191c470373..ca7c0dff20c 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -449,6 +449,7 @@ mod tests { payment_hash, onion_routing_packet, skimmed_fee_msat: None, + blinding_point: None, } } From 7f765a39bcf1464fab92d19b6ecd6f38e296a30e Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 12:59:42 -0400 Subject: [PATCH 02/22] onion_utils: extract decrypting faiure packet into method Will be used in the next commit to parse onion errors from blinded paths in tests only. --- lightning/src/ln/onion_utils.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 31f2f7827bc..08fe7a73ae6 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -433,11 +433,22 @@ pub(crate) struct DecodedOnionFailure { pub(crate) onion_error_data: Option>, } +/// Note that we always decrypt `packet` in-place here even if the deserialization into +/// [`msgs::DecodedOnionErrorPacket`] ultimately fails. +fn decrypt_onion_error_packet( + packet: &mut Vec, shared_secret: SharedSecret +) -> Result { + let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref()); + let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); + chacha.process_in_place(packet); + msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(packet)) +} + /// Process failure we got back from upstream on a payment we sent (implying htlc_source is an /// OutboundRoute). #[inline] pub(super) fn process_onion_failure( - secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec + secp_ctx: &Secp256k1, logger: &L, htlc_source: &HTLCSource, mut encrypted_packet: Vec ) -> DecodedOnionFailure where L::Target: Logger { let (path, session_priv, first_hop_htlc_msat) = if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat, .. @@ -504,15 +515,7 @@ pub(super) fn process_onion_failure( let amt_to_forward = htlc_msat - route_hop.fee_msat; htlc_msat = amt_to_forward; - let ammag = gen_ammag_from_shared_secret(shared_secret.as_ref()); - - let mut decryption_tmp = Vec::with_capacity(packet_decrypted.len()); - decryption_tmp.resize(packet_decrypted.len(), 0); - let mut chacha = ChaCha20::new(&ammag, &[0u8; 8]); - chacha.process(&packet_decrypted, &mut decryption_tmp[..]); - packet_decrypted = decryption_tmp; - - let err_packet = match msgs::DecodedOnionErrorPacket::read(&mut Cursor::new(&packet_decrypted)) { + let err_packet = match decrypt_onion_error_packet(&mut encrypted_packet, shared_secret) { Ok(p) => p, Err(_) => return }; From b70525df28118b835c9ebe77bb1a835b7fff58e1 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 13:09:48 -0400 Subject: [PATCH 03/22] Parse blinded onion errors in tests only. So we can make sure they're encoded properly. --- lightning/src/ln/onion_utils.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 08fe7a73ae6..605080c7b92 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -502,8 +502,21 @@ pub(super) fn process_onion_failure( Some(hop) => hop, None => { // The failing hop is within a multi-hop blinded path. - error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding - error_packet_ret = Some(vec![0; 32]); + #[cfg(not(test))] { + error_code_ret = Some(BADONION | PERM | 24); // invalid_onion_blinding + error_packet_ret = Some(vec![0; 32]); + } + #[cfg(test)] { + // Actually parse the onion error data in tests so we can check that blinded hops fail + // back correctly. + let err_packet = decrypt_onion_error_packet( + &mut encrypted_packet, shared_secret + ).unwrap(); + error_code_ret = + Some(u16::from_be_bytes(err_packet.failuremsg.get(0..2).unwrap().try_into().unwrap())); + error_packet_ret = Some(err_packet.failuremsg[2..].to_vec()); + } + res = Some(FailureLearnings { network_update: None, short_channel_id: None, payment_failed_permanently: false }); From 1596116fa4b87452414e2b2035fd4212408468f4 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 12 Jul 2023 21:36:11 -0400 Subject: [PATCH 04/22] Persist outbound blinding points in Channel A blinding point is provided in update_add_htlc messages if we are relaying or receiving a payment within a blinded path, to decrypt the onion routing packet and the recipient-provided encrypted payload within. Will be used in upcoming commits. --- lightning/src/ln/channel.rs | 43 ++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 4650a9c143a..125499e14ef 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -230,6 +230,7 @@ struct OutboundHTLCOutput { payment_hash: PaymentHash, state: OutboundHTLCState, source: HTLCSource, + blinding_point: Option, skimmed_fee_msat: Option, } @@ -244,6 +245,7 @@ enum HTLCUpdateAwaitingACK { onion_routing_packet: msgs::OnionPacket, // The extra fee we're skimming off the top of this HTLC. skimmed_fee_msat: Option, + blinding_point: Option, }, ClaimHTLC { payment_preimage: PaymentPreimage, @@ -5596,6 +5598,7 @@ impl Channel where source, onion_routing_packet, skimmed_fee_msat, + blinding_point: None, }); return Ok(None); } @@ -5607,6 +5610,7 @@ impl Channel where cltv_expiry, state: OutboundHTLCState::LocalAnnounced(Box::new(onion_routing_packet.clone())), source, + blinding_point: None, skimmed_fee_msat, }); @@ -7091,6 +7095,7 @@ impl Writeable for Channel where SP::Target: SignerProvider { let mut preimages: Vec<&Option> = vec![]; let mut pending_outbound_skimmed_fees: Vec> = Vec::new(); + let mut pending_outbound_blinding_points: Vec> = Vec::new(); (self.context.pending_outbound_htlcs.len() as u64).write(writer)?; for (idx, htlc) in self.context.pending_outbound_htlcs.iter().enumerate() { @@ -7137,15 +7142,17 @@ impl Writeable for Channel where SP::Target: SignerProvider { } else if !pending_outbound_skimmed_fees.is_empty() { pending_outbound_skimmed_fees.push(None); } + pending_outbound_blinding_points.push(htlc.blinding_point); } let mut holding_cell_skimmed_fees: Vec> = Vec::new(); + let mut holding_cell_blinding_points: Vec> = Vec::new(); (self.context.holding_cell_htlc_updates.len() as u64).write(writer)?; for (idx, update) in self.context.holding_cell_htlc_updates.iter().enumerate() { match update { &HTLCUpdateAwaitingACK::AddHTLC { ref amount_msat, ref cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet, - skimmed_fee_msat, + blinding_point, skimmed_fee_msat, } => { 0u8.write(writer)?; amount_msat.write(writer)?; @@ -7160,6 +7167,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { } holding_cell_skimmed_fees.push(Some(skimmed_fee)); } else if !holding_cell_skimmed_fees.is_empty() { holding_cell_skimmed_fees.push(None); } + + holding_cell_blinding_points.push(blinding_point); }, &HTLCUpdateAwaitingACK::ClaimHTLC { ref payment_preimage, ref htlc_id } => { 1u8.write(writer)?; @@ -7329,6 +7338,8 @@ impl Writeable for Channel where SP::Target: SignerProvider { (35, pending_outbound_skimmed_fees, optional_vec), (37, holding_cell_skimmed_fees, optional_vec), (38, self.context.is_batch_funding, option), + (39, pending_outbound_blinding_points, optional_vec), + (41, holding_cell_blinding_points, optional_vec), }); Ok(()) @@ -7440,6 +7451,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch _ => return Err(DecodeError::InvalidValue), }, skimmed_fee_msat: None, + blinding_point: None, }); } @@ -7454,6 +7466,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch source: Readable::read(reader)?, onion_routing_packet: Readable::read(reader)?, skimmed_fee_msat: None, + blinding_point: None, }, 1 => HTLCUpdateAwaitingACK::ClaimHTLC { payment_preimage: Readable::read(reader)?, @@ -7614,6 +7627,9 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch let mut is_batch_funding: Option<()> = None; + let mut pending_outbound_blinding_points_opt: Option>> = None; + let mut holding_cell_blinding_points_opt: Option>> = None; + read_tlv_fields!(reader, { (0, announcement_sigs, option), (1, minimum_depth, option), @@ -7640,6 +7656,8 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch (35, pending_outbound_skimmed_fees_opt, optional_vec), (37, holding_cell_skimmed_fees_opt, optional_vec), (38, is_batch_funding, option), + (39, pending_outbound_blinding_points_opt, optional_vec), + (41, holding_cell_blinding_points_opt, optional_vec), }); let (channel_keys_id, holder_signer) = if let Some(channel_keys_id) = channel_keys_id { @@ -7716,6 +7734,24 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch // We expect all skimmed fees to be consumed above if iter.next().is_some() { return Err(DecodeError::InvalidValue) } } + if let Some(blinding_pts) = pending_outbound_blinding_points_opt { + let mut iter = blinding_pts.into_iter(); + for htlc in pending_outbound_htlcs.iter_mut() { + htlc.blinding_point = iter.next().ok_or(DecodeError::InvalidValue)?; + } + // We expect all blinding points to be consumed above + if iter.next().is_some() { return Err(DecodeError::InvalidValue) } + } + if let Some(blinding_pts) = holding_cell_blinding_points_opt { + let mut iter = blinding_pts.into_iter(); + for htlc in holding_cell_htlc_updates.iter_mut() { + if let HTLCUpdateAwaitingACK::AddHTLC { ref mut blinding_point, .. } = htlc { + *blinding_point = iter.next().ok_or(DecodeError::InvalidValue)?; + } + } + // We expect all blinding points to be consumed above + if iter.next().is_some() { return Err(DecodeError::InvalidValue) } + } Ok(Channel { context: ChannelContext { @@ -8055,6 +8091,7 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; payment_id: PaymentId([42; 32]), }, skimmed_fee_msat: None, + blinding_point: None, }); // Make sure when Node A calculates their local commitment transaction, none of the HTLCs pass @@ -8629,6 +8666,7 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; state: OutboundHTLCState::Committed, source: HTLCSource::dummy(), skimmed_fee_msat: None, + blinding_point: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0202020202020202020202020202020202020202020202020202020202020202").unwrap()).to_byte_array(); out @@ -8642,6 +8680,7 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; state: OutboundHTLCState::Committed, source: HTLCSource::dummy(), skimmed_fee_msat: None, + blinding_point: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0303030303030303030303030303030303030303030303030303030303030303").unwrap()).to_byte_array(); out @@ -9053,6 +9092,7 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; state: OutboundHTLCState::Committed, source: HTLCSource::dummy(), skimmed_fee_msat: None, + blinding_point: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array(); out @@ -9066,6 +9106,7 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; state: OutboundHTLCState::Committed, source: HTLCSource::dummy(), skimmed_fee_msat: None, + blinding_point: None, }; out.payment_hash.0 = Sha256::hash(&>::from_hex("0505050505050505050505050505050505050505050505050505050505050505").unwrap()).to_byte_array(); out From b64523780b2f08f8cea7f29b660841310dba7a70 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 17:26:18 -0400 Subject: [PATCH 05/22] Store whether a forwarded HTLC is blinded in PendingHTLCRouting We need to store the inbound blinding point in PendingHTLCRouting in order to calculate the outbound blinding point. The new BlindedForward struct will be augmented when we add support for forwarding as a non-intro node. --- lightning/src/ln/channelmanager.rs | 23 +++++++++++++++++++++-- lightning/src/ln/onion_payment.rs | 3 ++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 694d7734dba..9941ad6583c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -119,6 +119,8 @@ pub enum PendingHTLCRouting { /// The SCID from the onion that we should forward to. This could be a real SCID or a fake one /// generated using `get_fake_scid` from the scid_utils::fake_scid module. short_channel_id: u64, // This should be NonZero eventually when we bump MSRV + /// Set if this HTLC is being forwarded within a blinded path. + blinded: Option, }, /// An HTLC paid to an invoice (supposedly) generated by us. /// At this point, we have not checked that the invoice being paid was actually generated by us, @@ -155,6 +157,16 @@ pub enum PendingHTLCRouting { }, } +/// Information used to forward or fail this HTLC that is being forwarded within a blinded path. +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct BlindedForward { + /// The `blinding_point` that was set in the inbound [`msgs::UpdateAddHTLC`], or in the inbound + /// onion payload if we're the introduction node. Useful for calculating the next hop's + /// [`msgs::UpdateAddHTLC::blinding_point`]. + pub inbound_blinding_point: PublicKey, + // Another field will be added here when we support forwarding as a non-intro node. +} + /// Full details of an incoming HTLC, including routing info. #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug pub struct PendingHTLCInfo { @@ -4013,8 +4025,10 @@ where })?; let routing = match payment.forward_info.routing { - PendingHTLCRouting::Forward { onion_packet, .. } => { - PendingHTLCRouting::Forward { onion_packet, short_channel_id: next_hop_scid } + PendingHTLCRouting::Forward { onion_packet, blinded, .. } => { + PendingHTLCRouting::Forward { + onion_packet, blinded, short_channel_id: next_hop_scid + } }, _ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted }; @@ -9143,9 +9157,14 @@ impl_writeable_tlv_based!(PhantomRouteHints, { (6, real_node_pubkey, required), }); +impl_writeable_tlv_based!(BlindedForward, { + (0, inbound_blinding_point, required), +}); + impl_writeable_tlv_based_enum!(PendingHTLCRouting, (0, Forward) => { (0, onion_packet, required), + (1, blinded, option), (2, short_channel_id, required), }, (1, Receive) => { diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index ca7c0dff20c..c10cdc9ab10 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -56,6 +56,7 @@ pub(super) fn create_fwd_pending_htlc_info( routing: PendingHTLCRouting::Forward { onion_packet: outgoing_packet, short_channel_id, + blinded: None, }, payment_hash: msg.payment_hash, incoming_shared_secret: shared_secret, @@ -414,7 +415,7 @@ mod tests { .map_err(|e| e.msg).unwrap(); let next_onion = match peeled.routing { - PendingHTLCRouting::Forward { onion_packet, short_channel_id: _ } => { + PendingHTLCRouting::Forward { onion_packet, .. } => { onion_packet }, _ => panic!("expected a forwarded onion"), From ae15ba84620f40ed5c80870d254d5eda341f1aba Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 17:35:22 -0400 Subject: [PATCH 06/22] Persist whether an HTLC is blinded in HTLCPreviousHopData. Useful so we know to fail blinded intro node HTLCs back with an invalid_onion_blinding error per BOLT 4. Another variant will be added to the new Blinded enum when we support receiving/forwarding as a non-intro node. --- lightning/src/ln/channelmanager.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9941ad6583c..e3fe74d1a07 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -225,6 +225,13 @@ pub(super) enum HTLCForwardInfo { }, } +// Used for failing blinded HTLCs backwards correctly. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +enum BlindedFailure { + FromIntroductionNode, + // Another variant will be added here for non-intro nodes. +} + /// Tracks the inbound corresponding to an outbound HTLC #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub(crate) struct HTLCPreviousHopData { @@ -234,6 +241,7 @@ pub(crate) struct HTLCPreviousHopData { htlc_id: u64, incoming_packet_shared_secret: [u8; 32], phantom_shared_secret: Option<[u8; 32]>, + blinded_failure: Option, // This field is consumed by `claim_funds_from_hop()` when updating a force-closed backwards // channel with a preimage provided by the forward channel. @@ -4072,6 +4080,7 @@ where htlc_id: payment.prev_htlc_id, incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, phantom_shared_secret: None, + blinded_failure: None, }); let failure_reason = HTLCFailReason::from_failure_code(0x4000 | 10); @@ -4120,6 +4129,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret: $phantom_ss, + blinded_failure: None, }); let reason = if $next_hop_unknown { @@ -4236,6 +4246,7 @@ where incoming_packet_shared_secret: incoming_shared_secret, // Phantom payments are only PendingHTLCRouting::Receive. phantom_shared_secret: None, + blinded_failure: None, }); if let Err(e) = chan.queue_add_htlc(outgoing_amt_msat, payment_hash, outgoing_cltv_value, htlc_source.clone(), @@ -4319,6 +4330,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret, + blinded_failure: None, }, // We differentiate the received value from the sender intended value // if possible so that we don't prematurely mark MPP payments complete @@ -4349,6 +4361,7 @@ where htlc_id: $htlc.prev_hop.htlc_id, incoming_packet_shared_secret: $htlc.prev_hop.incoming_packet_shared_secret, phantom_shared_secret, + blinded_failure: None, }), payment_hash, HTLCFailReason::reason(0x4000 | 15, htlc_msat_height_data), HTLCDestination::FailedPayment { payment_hash: $payment_hash }, @@ -6598,6 +6611,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: forward_info.incoming_shared_secret, phantom_shared_secret: None, + blinded_failure: None, }); failed_intercept_forwards.push((htlc_source, forward_info.payment_hash, @@ -8194,6 +8208,7 @@ where incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret, phantom_shared_secret: None, outpoint: htlc.prev_funding_outpoint, + blinded_failure: None, }); let requested_forward_scid /* intercept scid */ = match htlc.forward_info.routing { @@ -9266,10 +9281,15 @@ impl_writeable_tlv_based_enum!(PendingHTLCStatus, ; (1, Fail), ); +impl_writeable_tlv_based_enum!(BlindedFailure, + (0, FromIntroductionNode) => {}, ; +); + impl_writeable_tlv_based!(HTLCPreviousHopData, { (0, short_channel_id, required), (1, phantom_shared_secret, option), (2, outpoint, required), + (3, blinded_failure, option), (4, htlc_id, required), (6, incoming_packet_shared_secret, required), (7, user_channel_id, option), From 21ae9fdd69bf1b72601c6c264a1c06c804909fb0 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 18:18:28 -0400 Subject: [PATCH 07/22] Set HTLCPreviousHopData::blinded on intro node forward. Useful so we know to fail back blinded HTLCs where we are the intro node with the invalid_onion_blinding error per BOLT 4. We don't set this field for blinded received HTLCs because we don't support receiving to multi-hop blinded paths yet, and there's no point in setting it for HTLCs received to 1-hop blinded paths because per the spec they should fail back using an unblinded error code. --- lightning/src/ln/channelmanager.rs | 31 ++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index e3fe74d1a07..9861729d778 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -167,6 +167,18 @@ pub struct BlindedForward { // Another field will be added here when we support forwarding as a non-intro node. } +impl PendingHTLCRouting { + // Used to override the onion failure code and data if the HTLC is blinded. + fn blinded_failure(&self) -> Option { + // TODO: needs update when we support receiving to multi-hop blinded paths + if let Self::Forward { blinded: Some(_), .. } = self { + Some(BlindedFailure::FromIntroductionNode) + } else { + None + } + } +} + /// Full details of an incoming HTLC, including routing info. #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug pub struct PendingHTLCInfo { @@ -4080,7 +4092,7 @@ where htlc_id: payment.prev_htlc_id, incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, phantom_shared_secret: None, - blinded_failure: None, + blinded_failure: payment.forward_info.routing.blinded_failure(), }); let failure_reason = HTLCFailReason::from_failure_code(0x4000 | 10); @@ -4129,7 +4141,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret: $phantom_ss, - blinded_failure: None, + blinded_failure: routing.blinded_failure(), }); let reason = if $next_hop_unknown { @@ -4159,7 +4171,7 @@ where } } } - if let PendingHTLCRouting::Forward { onion_packet, .. } = routing { + if let PendingHTLCRouting::Forward { ref onion_packet, .. } = routing { let phantom_pubkey_res = self.node_signer.get_node_id(Recipient::PhantomNode); if phantom_pubkey_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id, &self.chain_hash) { let phantom_shared_secret = self.node_signer.ecdh(Recipient::PhantomNode, &onion_packet.public_key.unwrap(), None).unwrap().secret_bytes(); @@ -4234,7 +4246,9 @@ where prev_short_channel_id, prev_htlc_id, prev_funding_outpoint, prev_user_channel_id, forward_info: PendingHTLCInfo { incoming_shared_secret, payment_hash, outgoing_amt_msat, outgoing_cltv_value, - routing: PendingHTLCRouting::Forward { onion_packet, .. }, skimmed_fee_msat, .. + routing: PendingHTLCRouting::Forward { + onion_packet, blinded, .. + }, skimmed_fee_msat, .. }, }) => { log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, &payment_hash, short_chan_id); @@ -4246,7 +4260,7 @@ where incoming_packet_shared_secret: incoming_shared_secret, // Phantom payments are only PendingHTLCRouting::Receive. phantom_shared_secret: None, - blinded_failure: None, + blinded_failure: blinded.map(|_| BlindedFailure::FromIntroductionNode), }); if let Err(e) = chan.queue_add_htlc(outgoing_amt_msat, payment_hash, outgoing_cltv_value, htlc_source.clone(), @@ -4301,6 +4315,7 @@ where skimmed_fee_msat, .. } }) => { + let blinded_failure = routing.blinded_failure(); let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret, mut onion_fields) = match routing { PendingHTLCRouting::Receive { payment_data, payment_metadata, incoming_cltv_expiry, phantom_shared_secret, custom_tlvs } => { let _legacy_hop_data = Some(payment_data.clone()); @@ -4330,7 +4345,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: incoming_shared_secret, phantom_shared_secret, - blinded_failure: None, + blinded_failure, }, // We differentiate the received value from the sender intended value // if possible so that we don't prematurely mark MPP payments complete @@ -6611,7 +6626,7 @@ where htlc_id: prev_htlc_id, incoming_packet_shared_secret: forward_info.incoming_shared_secret, phantom_shared_secret: None, - blinded_failure: None, + blinded_failure: forward_info.routing.blinded_failure(), }); failed_intercept_forwards.push((htlc_source, forward_info.payment_hash, @@ -8208,7 +8223,7 @@ where incoming_packet_shared_secret: htlc.forward_info.incoming_shared_secret, phantom_shared_secret: None, outpoint: htlc.prev_funding_outpoint, - blinded_failure: None, + blinded_failure: htlc.forward_info.routing.blinded_failure(), }); let requested_forward_scid /* intercept scid */ = match htlc.forward_info.routing { From a2b2fb0ceb9ac8815d5bc902668e54c29284380d Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 14:18:18 -0400 Subject: [PATCH 08/22] Parameterize Channel's htlc forward method by outbound blinding point Used in the next commit to set the update_add blinding point on HTLC forward. --- lightning/src/ln/channel.rs | 26 ++++++++++++++------------ lightning/src/ln/channelmanager.rs | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 125499e14ef..46209a0c402 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3358,11 +3358,12 @@ impl Channel where match &htlc_update { &HTLCUpdateAwaitingACK::AddHTLC { amount_msat, cltv_expiry, ref payment_hash, ref source, ref onion_routing_packet, - skimmed_fee_msat, .. + skimmed_fee_msat, blinding_point, .. } => { - match self.send_htlc(amount_msat, *payment_hash, cltv_expiry, source.clone(), - onion_routing_packet.clone(), false, skimmed_fee_msat, fee_estimator, logger) - { + match self.send_htlc( + amount_msat, *payment_hash, cltv_expiry, source.clone(), onion_routing_packet.clone(), + false, skimmed_fee_msat, blinding_point, fee_estimator, logger + ) { Ok(_) => update_add_count += 1, Err(e) => { match e { @@ -4078,7 +4079,7 @@ impl Channel where cltv_expiry: htlc.cltv_expiry, onion_routing_packet: (**onion_packet).clone(), skimmed_fee_msat: htlc.skimmed_fee_msat, - blinding_point: None, + blinding_point: htlc.blinding_point, }); } } @@ -5507,13 +5508,13 @@ impl Channel where pub fn queue_add_htlc( &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, skimmed_fee_msat: Option, - fee_estimator: &LowerBoundedFeeEstimator, logger: &L + blinding_point: Option, fee_estimator: &LowerBoundedFeeEstimator, logger: &L ) -> Result<(), ChannelError> where F::Target: FeeEstimator, L::Target: Logger { self .send_htlc(amount_msat, payment_hash, cltv_expiry, source, onion_routing_packet, true, - skimmed_fee_msat, fee_estimator, logger) + skimmed_fee_msat, blinding_point, fee_estimator, logger) .map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?")) .map_err(|err| { if let ChannelError::Ignore(_) = err { /* fine */ } @@ -5541,7 +5542,8 @@ impl Channel where fn send_htlc( &mut self, amount_msat: u64, payment_hash: PaymentHash, cltv_expiry: u32, source: HTLCSource, onion_routing_packet: msgs::OnionPacket, mut force_holding_cell: bool, - skimmed_fee_msat: Option, fee_estimator: &LowerBoundedFeeEstimator, logger: &L + skimmed_fee_msat: Option, blinding_point: Option, + fee_estimator: &LowerBoundedFeeEstimator, logger: &L ) -> Result, ChannelError> where F::Target: FeeEstimator, L::Target: Logger { @@ -5598,7 +5600,7 @@ impl Channel where source, onion_routing_packet, skimmed_fee_msat, - blinding_point: None, + blinding_point, }); return Ok(None); } @@ -5610,7 +5612,7 @@ impl Channel where cltv_expiry, state: OutboundHTLCState::LocalAnnounced(Box::new(onion_routing_packet.clone())), source, - blinding_point: None, + blinding_point, skimmed_fee_msat, }); @@ -5622,7 +5624,7 @@ impl Channel where cltv_expiry, onion_routing_packet, skimmed_fee_msat, - blinding_point: None, + blinding_point, }; self.context.next_holder_htlc_id += 1; @@ -5785,7 +5787,7 @@ impl Channel where where F::Target: FeeEstimator, L::Target: Logger { let send_res = self.send_htlc(amount_msat, payment_hash, cltv_expiry, source, - onion_routing_packet, false, skimmed_fee_msat, fee_estimator, logger); + onion_routing_packet, false, skimmed_fee_msat, None, fee_estimator, logger); if let Err(e) = &send_res { if let ChannelError::Ignore(_) = e {} else { debug_assert!(false, "Sending cannot trigger channel failure"); } } match send_res? { Some(_) => { diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 9861729d778..b3994e97763 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -4264,7 +4264,7 @@ where }); if let Err(e) = chan.queue_add_htlc(outgoing_amt_msat, payment_hash, outgoing_cltv_value, htlc_source.clone(), - onion_packet, skimmed_fee_msat, &self.fee_estimator, + onion_packet, skimmed_fee_msat, None, &self.fee_estimator, &self.logger) { if let ChannelError::Ignore(msg) = e { From 50c850fdd08eab678558c70c7c9d6c369fe918e8 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 14:24:10 -0400 Subject: [PATCH 09/22] Set update_add blinding point on HTLC forward Used by the next hop to decode their blinded onion payload. --- lightning/src/ln/channelmanager.rs | 12 ++++++++++-- lightning/src/ln/onion_utils.rs | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index b3994e97763..de126084215 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -53,7 +53,7 @@ use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParame use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundOnionErr, NextPacketDetails}; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::HTLCFailReason; +use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; @@ -4262,9 +4262,17 @@ where phantom_shared_secret: None, blinded_failure: blinded.map(|_| BlindedFailure::FromIntroductionNode), }); + let next_blinding_point = blinded.and_then(|b| { + let encrypted_tlvs_ss = self.node_signer.ecdh( + Recipient::Node, &b.inbound_blinding_point, None + ).unwrap().secret_bytes(); + onion_utils::next_hop_pubkey( + &self.secp_ctx, b.inbound_blinding_point, &encrypted_tlvs_ss + ).ok() + }); if let Err(e) = chan.queue_add_htlc(outgoing_amt_msat, payment_hash, outgoing_cltv_value, htlc_source.clone(), - onion_packet, skimmed_fee_msat, None, &self.fee_estimator, + onion_packet, skimmed_fee_msat, next_blinding_point, &self.fee_estimator, &self.logger) { if let ChannelError::Ignore(msg) = e { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 605080c7b92..88f0efe9aa8 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -242,6 +242,8 @@ pub(super) fn build_onion_payloads(path: &Path, total_msat: u64, mut recipient_o /// the hops can be of variable length. pub(crate) const ONION_DATA_LEN: usize = 20*65; +pub(super) const INVALID_ONION_BLINDING: u16 = 0x8000 | 0x4000 | 24; + #[inline] fn shift_slice_right(arr: &mut [u8], amt: usize) { for i in (amt..arr.len()).rev() { From 1a7254c1784557cb69f551ac35401a0d7da7598a Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 14:28:45 -0400 Subject: [PATCH 10/22] Parse blinded forward-as-intro onion payloads Previously, we only parsed blinded receive payloads. --- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/msgs.rs | 30 +++++++++++++++++++++++++++--- lightning/src/ln/onion_payment.rs | 15 ++++++++++++++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index de126084215..d56d1349231 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -53,7 +53,7 @@ use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParame use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundOnionErr, NextPacketDetails}; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; +use crate::ln::onion_utils::HTLCFailReason; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs index 349cc5c728a..2d871b354a2 100644 --- a/lightning/src/ln/msgs.rs +++ b/lightning/src/ln/msgs.rs @@ -31,7 +31,7 @@ use bitcoin::{secp256k1, Witness}; use bitcoin::blockdata::script::ScriptBuf; use bitcoin::hash_types::Txid; -use crate::blinded_path::payment::ReceiveTlvs; +use crate::blinded_path::payment::{BlindedPaymentTlvs, ForwardTlvs, ReceiveTlvs}; use crate::ln::{ChannelId, PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; use crate::ln::onion_utils; @@ -1666,9 +1666,10 @@ pub trait OnionMessageHandler { mod fuzzy_internal_msgs { use bitcoin::secp256k1::PublicKey; - use crate::blinded_path::payment::PaymentConstraints; + use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay}; use crate::prelude::*; use crate::ln::{PaymentPreimage, PaymentSecret}; + use crate::ln::features::BlindedHopFeatures; // These types aren't intended to be pub, but are exposed for direct fuzzing (as we deserialize // them from untrusted input): @@ -1695,6 +1696,13 @@ mod fuzzy_internal_msgs { amt_msat: u64, outgoing_cltv_value: u32, }, + BlindedForward { + short_channel_id: u64, + payment_relay: PaymentRelay, + payment_constraints: PaymentConstraints, + features: BlindedHopFeatures, + intro_node_blinding_point: PublicKey, + }, BlindedReceive { amt_msat: u64, total_msat: u64, @@ -2354,7 +2362,23 @@ impl ReadableArgs<&NS> for InboundOnionPayload where NS::Target: Node let mut s = Cursor::new(&enc_tlvs); let mut reader = FixedLengthReader::new(&mut s, enc_tlvs.len() as u64); match ChaChaPolyReadAdapter::read(&mut reader, rho)? { - ChaChaPolyReadAdapter { readable: ReceiveTlvs { payment_secret, payment_constraints }} => { + ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Forward(ForwardTlvs { + short_channel_id, payment_relay, payment_constraints, features + })} => { + if amt.is_some() || cltv_value.is_some() || total_msat.is_some() { + return Err(DecodeError::InvalidValue) + } + Ok(Self::BlindedForward { + short_channel_id, + payment_relay, + payment_constraints, + features, + intro_node_blinding_point: blinding_point, + }) + }, + ChaChaPolyReadAdapter { readable: BlindedPaymentTlvs::Receive(ReceiveTlvs { + payment_secret, payment_constraints + })} => { if total_msat.unwrap_or(0) > MAX_VALUE_MSAT { return Err(DecodeError::InvalidValue) } Ok(Self::BlindedReceive { amt_msat: amt.ok_or(DecodeError::InvalidValue)?, diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index c10cdc9ab10..3c30a4458f5 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -11,7 +11,7 @@ use crate::ln::PaymentHash; use crate::ln::channelmanager::{CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting}; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::HTLCFailReason; +use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; use crate::sign::{NodeSigner, Recipient}; use crate::util::logger::Logger; @@ -44,6 +44,7 @@ pub(super) fn create_fwd_pending_htlc_info( let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data { msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => (short_channel_id, amt_to_forward, outgoing_cltv_value), + msgs::InboundOnionPayload::BlindedForward { .. } => todo!(), msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } => return Err(InboundOnionErr { msg: "Final Node OnionHopData provided for us as an intermediary node", @@ -90,6 +91,13 @@ pub(super) fn create_recv_pending_htlc_info( msg: "Got non final data with an HMAC of 0", }) }, + msgs::InboundOnionPayload::BlindedForward { .. } => { + return Err(InboundOnionErr { + err_code: INVALID_ONION_BLINDING, + err_data: vec![0; 32], + msg: "Got blinded non final data with an HMAC of 0", + }) + } }; // final_incorrect_cltv_expiry if outgoing_cltv_value > cltv_expiry { @@ -327,6 +335,11 @@ where outgoing_amt_msat: amt_to_forward, outgoing_cltv_value } }, + onion_utils::Hop::Forward { + next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, .. + } => { + todo!() + }, onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)), onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } | onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::BlindedReceive { .. }, .. } => From 47d34c3668061eaa84adffcf91d0c3d7ad4162c7 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Thu, 26 Oct 2023 14:37:13 -0400 Subject: [PATCH 11/22] Support forwarding blinded HTLCs as intro node. Error handling will be completed in upcoming commits. --- lightning/src/blinded_path/payment.rs | 2 +- lightning/src/ln/onion_payment.rs | 67 ++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 4edfb7d8de0..3e475893153 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -187,7 +187,7 @@ pub(super) fn blinded_hops( } /// `None` if underflow occurs. -fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option { +pub(crate) fn amt_to_forward_msat(inbound_amt_msat: u64, payment_relay: &PaymentRelay) -> Option { let inbound_amt = inbound_amt_msat as u128; let base = payment_relay.fee_base_msat as u128; let prop = payment_relay.fee_proportional_millionths as u128; diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index 3c30a4458f5..ca15a37c072 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -6,9 +6,12 @@ use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::{self, Secp256k1, PublicKey}; +use crate::blinded_path; +use crate::blinded_path::payment::{PaymentConstraints, PaymentRelay}; use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS}; use crate::ln::PaymentHash; -use crate::ln::channelmanager::{CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting}; +use crate::ln::channelmanager::{BlindedForward, CLTV_FAR_FAR_AWAY, HTLCFailureMsg, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting}; +use crate::ln::features::BlindedHopFeatures; use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; @@ -28,6 +31,23 @@ pub struct InboundOnionErr { pub msg: &'static str, } +fn check_blinded_forward( + inbound_amt_msat: u64, inbound_cltv_expiry: u32, payment_relay: &PaymentRelay, + payment_constraints: &PaymentConstraints, features: &BlindedHopFeatures +) -> Result<(u64, u32), ()> { + let amt_to_forward = blinded_path::payment::amt_to_forward_msat( + inbound_amt_msat, payment_relay + ).ok_or(())?; + let outgoing_cltv_value = inbound_cltv_expiry.checked_sub( + payment_relay.cltv_expiry_delta as u32 + ).ok_or(())?; + if inbound_amt_msat < payment_constraints.htlc_minimum_msat || + outgoing_cltv_value > payment_constraints.max_cltv_expiry + { return Err(()) } + if features.requires_unknown_bits_from(&BlindedHopFeatures::empty()) { return Err(()) } + Ok((amt_to_forward, outgoing_cltv_value)) +} + pub(super) fn create_fwd_pending_htlc_info( msg: &msgs::UpdateAddHTLC, hop_data: msgs::InboundOnionPayload, hop_hmac: [u8; 32], new_packet_bytes: [u8; onion_utils::ONION_DATA_LEN], shared_secret: [u8; 32], @@ -41,10 +61,27 @@ pub(super) fn create_fwd_pending_htlc_info( hmac: hop_hmac, }; - let (short_channel_id, amt_to_forward, outgoing_cltv_value) = match hop_data { + let ( + short_channel_id, amt_to_forward, outgoing_cltv_value, inbound_blinding_point + ) = match hop_data { msgs::InboundOnionPayload::Forward { short_channel_id, amt_to_forward, outgoing_cltv_value } => - (short_channel_id, amt_to_forward, outgoing_cltv_value), - msgs::InboundOnionPayload::BlindedForward { .. } => todo!(), + (short_channel_id, amt_to_forward, outgoing_cltv_value, None), + msgs::InboundOnionPayload::BlindedForward { + short_channel_id, payment_relay, payment_constraints, intro_node_blinding_point, features, + } => { + let (amt_to_forward, outgoing_cltv_value) = check_blinded_forward( + msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features + ).map_err(|()| { + // We should be returning malformed here if `msg.blinding_point` is set, but this is + // unreachable right now since we checked it in `decode_update_add_htlc_onion`. + InboundOnionErr { + msg: "Underflow calculating outbound amount or cltv value for blinded forward", + err_code: INVALID_ONION_BLINDING, + err_data: vec![0; 32], + } + })?; + (short_channel_id, amt_to_forward, outgoing_cltv_value, Some(intro_node_blinding_point)) + }, msgs::InboundOnionPayload::Receive { .. } | msgs::InboundOnionPayload::BlindedReceive { .. } => return Err(InboundOnionErr { msg: "Final Node OnionHopData provided for us as an intermediary node", @@ -57,7 +94,7 @@ pub(super) fn create_fwd_pending_htlc_info( routing: PendingHTLCRouting::Forward { onion_packet: outgoing_packet, short_channel_id, - blinded: None, + blinded: inbound_blinding_point.map(|bp| BlindedForward { inbound_blinding_point: bp }), }, payment_hash: msg.payment_hash, incoming_shared_secret: shared_secret, @@ -336,9 +373,25 @@ where } }, onion_utils::Hop::Forward { - next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, .. + next_hop_data: msgs::InboundOnionPayload::BlindedForward { + short_channel_id, ref payment_relay, ref payment_constraints, ref features, .. + }, .. } => { - todo!() + let (amt_to_forward, outgoing_cltv_value) = match check_blinded_forward( + msg.amount_msat, msg.cltv_expiry, &payment_relay, &payment_constraints, &features + ) { + Ok((amt, cltv)) => (amt, cltv), + Err(()) => { + return_err!("Underflow calculating outbound amount or cltv value for blinded forward", + INVALID_ONION_BLINDING, &[0; 32]); + } + }; + let next_packet_pubkey = onion_utils::next_hop_pubkey(&secp_ctx, + msg.onion_routing_packet.public_key.unwrap(), &shared_secret); + NextPacketDetails { + next_packet_pubkey, outgoing_scid: short_channel_id, outgoing_amt_msat: amt_to_forward, + outgoing_cltv_value + } }, onion_utils::Hop::Receive { .. } => return Ok((next_hop, shared_secret, None)), onion_utils::Hop::Forward { next_hop_data: msgs::InboundOnionPayload::Receive { .. }, .. } | From d2222c8224923feb90c1b0efd3ef920d56f342f9 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Wed, 25 Oct 2023 17:44:51 -0400 Subject: [PATCH 12/22] Remove now-unused Readable impl for ReceiveTlvs --- lightning/src/blinded_path/payment.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lightning/src/blinded_path/payment.rs b/lightning/src/blinded_path/payment.rs index 3e475893153..7b604fbdcb1 100644 --- a/lightning/src/blinded_path/payment.rs +++ b/lightning/src/blinded_path/payment.rs @@ -118,21 +118,6 @@ impl Writeable for ReceiveTlvs { } } -// This will be removed once we support forwarding blinded HTLCs, because we'll always read a -// `BlindedPaymentTlvs` instead. -impl Readable for ReceiveTlvs { - fn read(r: &mut R) -> Result { - _init_and_read_tlv_stream!(r, { - (12, payment_constraints, required), - (65536, payment_secret, required), - }); - Ok(Self { - payment_secret: payment_secret.0.unwrap(), - payment_constraints: payment_constraints.0.unwrap() - }) - } -} - impl<'a> Writeable for BlindedPaymentTlvsRef<'a> { fn write(&self, w: &mut W) -> Result<(), io::Error> { // TODO: write padding From 918f09c5291815bf4feaf0f754a5544e426bcfa8 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 15 Sep 2023 18:10:22 -0400 Subject: [PATCH 13/22] Test blinded forward failure to calculate outbound cltv expiry Intro node failure only. --- lightning/src/ln/blinded_payment_tests.rs | 71 ++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index e8b6bfd679a..56bf629acb8 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -9,11 +9,14 @@ use bitcoin::secp256k1::Secp256k1; use crate::blinded_path::BlindedPath; -use crate::blinded_path::payment::{PaymentConstraints, ReceiveTlvs}; +use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::events::MessageSendEventsProvider; use crate::ln::channelmanager; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; +use crate::ln::features::BlindedHopFeatures; use crate::ln::functional_test_utils::*; +use crate::ln::msgs::ChannelMessageHandler; +use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::Retry; use crate::prelude::*; use crate::routing::router::{PaymentParameters, RouteParameters}; @@ -109,3 +112,69 @@ fn mpp_to_one_hop_blinded_path() { Some(payment_secret), ev.clone(), true, None); claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage); } + +#[test] +fn forward_checks_failure() { + // Ensure we'll fail backwards properly if a forwarding check fails on initial update_add + // receipt. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let intermediate_nodes = vec![ForwardNode { + node_id: nodes[1].node.get_our_node_id(), + tlvs: ForwardTlvs { + short_channel_id: chan_upd_1_2.short_channel_id, + payment_relay: PaymentRelay { + cltv_expiry_delta: chan_upd_1_2.cltv_expiry_delta, + fee_proportional_millionths: chan_upd_1_2.fee_proportional_millionths, + fee_base_msat: chan_upd_1_2.fee_base_msat, + }, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat, + }, + features: BlindedHopFeatures::empty(), + }, + htlc_maximum_msat: chan_upd_1_2.htlc_maximum_msat, + }]; + let payee_tlvs = ReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat, + }, + }; + let mut secp_ctx = Secp256k1::new(); + let blinded_path = BlindedPath::new_for_payment( + &intermediate_nodes[..], nodes[2].node.get_our_node_id(), payee_tlvs, + chan_upd_1_2.htlc_maximum_msat, &chanmon_cfgs[2].keys_manager, &secp_ctx + ).unwrap(); + + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::blinded(vec![blinded_path]), amt_msat); + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut payment_event = SendEvent::from_event(ev); + + let mut update_add = &mut payment_event.msgs[0]; + update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); + check_added_monitors!(nodes[1], 0); + do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, true, true); + + let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); +} From c8adb54b71f259805f519c33771dae8de6155a63 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 15 Sep 2023 19:47:25 -0400 Subject: [PATCH 14/22] Test blinded forwarding payload encoded as receive error case --- lightning/src/ln/blinded_payment_tests.rs | 39 +++++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 56bf629acb8..e20464cb964 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bitcoin::secp256k1::Secp256k1; +use bitcoin::secp256k1::{Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::events::MessageSendEventsProvider; @@ -16,6 +16,7 @@ use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::features::BlindedHopFeatures; use crate::ln::functional_test_utils::*; use crate::ln::msgs::ChannelMessageHandler; +use crate::ln::onion_utils; use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::outbound_payment::Retry; use crate::prelude::*; @@ -113,14 +114,29 @@ fn mpp_to_one_hop_blinded_path() { claim_payment_along_route(&nodes[0], expected_route, false, payment_preimage); } +enum ForwardCheckFail { + // Fail a check on the inbound onion payload. In this case, we underflow when calculating the + // outgoing cltv_expiry. + InboundOnionCheck, + // The forwarding node's payload is encoded as a receive, i.e. the next hop HMAC is [0; 32]. + ForwardPayloadEncodedAsReceive, +} + #[test] fn forward_checks_failure() { + do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck); + do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive); +} + +fn do_forward_checks_failure(check: ForwardCheckFail) { // Ensure we'll fail backwards properly if a forwarding check fails on initial update_add // receipt. let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); - let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + // We need the session priv to construct a bogus onion packet later. + *nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([3; 32]); create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; @@ -158,6 +174,8 @@ fn forward_checks_failure() { let route_params = RouteParameters::from_payment_params_and_value( PaymentParameters::blinded(vec![blinded_path]), amt_msat); + let route = get_route(&nodes[0], &route_params).unwrap(); + node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); check_added_monitors(&nodes[0], 1); @@ -167,7 +185,22 @@ fn forward_checks_failure() { let mut payment_event = SendEvent::from_event(ev); let mut update_add = &mut payment_event.msgs[0]; - update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow + match check { + ForwardCheckFail::InboundOnionCheck => { + update_add.cltv_expiry = 10; // causes outbound CLTV expiry to underflow + }, + ForwardCheckFail::ForwardPayloadEncodedAsReceive => { + let session_priv = SecretKey::from_slice(&[3; 32]).unwrap(); + let onion_keys = onion_utils::construct_onion_keys(&Secp256k1::new(), &route.paths[0], &session_priv).unwrap(); + let cur_height = nodes[0].best_block_info().1; + let (mut onion_payloads, ..) = onion_utils::build_onion_payloads( + &route.paths[0], amt_msat, RecipientOnionFields::spontaneous_empty(), cur_height, &None).unwrap(); + // Remove the receive payload so the blinded forward payload is encoded as a final payload + // (i.e. next_hop_hmac == [0; 32]) + onion_payloads.pop(); + update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); + }, + } nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); check_added_monitors!(nodes[1], 0); do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, true, true); From 67d24633aeb02ed2288171bd1cee5af815a770f7 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Fri, 15 Sep 2023 18:21:28 -0400 Subject: [PATCH 15/22] Correctly fail back on outbound channel check for blinded HTLC Forwarding intro nodes should always fail with 0x8000|0x4000|24. --- lightning/src/ln/blinded_payment_tests.rs | 7 +++++++ lightning/src/ln/channelmanager.rs | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index e20464cb964..e26da792209 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -120,12 +120,15 @@ enum ForwardCheckFail { InboundOnionCheck, // The forwarding node's payload is encoded as a receive, i.e. the next hop HMAC is [0; 32]. ForwardPayloadEncodedAsReceive, + // Fail a check on the outbound channel. In this case, our next-hop peer is offline. + OutboundChannelCheck, } #[test] fn forward_checks_failure() { do_forward_checks_failure(ForwardCheckFail::InboundOnionCheck); do_forward_checks_failure(ForwardCheckFail::ForwardPayloadEncodedAsReceive); + do_forward_checks_failure(ForwardCheckFail::OutboundChannelCheck); } fn do_forward_checks_failure(check: ForwardCheckFail) { @@ -200,6 +203,10 @@ fn do_forward_checks_failure(check: ForwardCheckFail) { onion_payloads.pop(); update_add.onion_routing_packet = onion_utils::construct_onion_packet(onion_payloads, onion_keys, [0; 32], &payment_hash).unwrap(); }, + ForwardCheckFail::OutboundChannelCheck => { + // The intro node will see that the next-hop peer is disconnected and fail the HTLC backwards. + nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id()); + }, } nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); check_added_monitors!(nodes[1], 0); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d56d1349231..5a6694fe5f4 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -53,7 +53,7 @@ use crate::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringFeeParame use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htlc_info, create_fwd_pending_htlc_info, decode_incoming_update_add_htlc_onion, InboundOnionErr, NextPacketDetails}; use crate::ln::msgs; use crate::ln::onion_utils; -use crate::ln::onion_utils::HTLCFailReason; +use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; @@ -2977,14 +2977,24 @@ where msg, &self.node_signer, &self.logger, &self.secp_ctx )?; + let is_blinded = match next_hop { + onion_utils::Hop::Forward { + next_hop_data: msgs::InboundOnionPayload::BlindedForward { .. }, .. + } => true, + _ => false, // TODO: update this when we support receiving to multi-hop blinded paths + }; + macro_rules! return_err { ($msg: expr, $err_code: expr, $data: expr) => { { log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg); + let (err_code, err_data) = if is_blinded { + (INVALID_ONION_BLINDING, &[0; 32][..]) + } else { ($err_code, $data) }; return Err(HTLCFailureMsg::Relay(msgs::UpdateFailHTLC { channel_id: msg.channel_id, htlc_id: msg.htlc_id, - reason: HTLCFailReason::reason($err_code, $data.to_vec()) + reason: HTLCFailReason::reason(err_code, err_data.to_vec()) .get_encrypted_failure_packet(&shared_secret, &None), })); } From 8c0c3a37db96d6c328e3fd248a255f16e0eef819 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 2 Oct 2023 18:23:48 -0400 Subject: [PATCH 16/22] Extract blinded route param creation into test util --- lightning/src/ln/blinded_payment_tests.rs | 81 ++++++++++++++--------- 1 file changed, 49 insertions(+), 32 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index e26da792209..c0756782afa 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -7,14 +7,16 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bitcoin::secp256k1::{Secp256k1, SecretKey}; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; use crate::events::MessageSendEventsProvider; +use crate::ln::PaymentSecret; use crate::ln::channelmanager; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; use crate::ln::features::BlindedHopFeatures; use crate::ln::functional_test_utils::*; +use crate::ln::msgs; use crate::ln::msgs::ChannelMessageHandler; use crate::ln::onion_utils; use crate::ln::onion_utils::INVALID_ONION_BLINDING; @@ -22,6 +24,49 @@ use crate::ln::outbound_payment::Retry; use crate::prelude::*; use crate::routing::router::{PaymentParameters, RouteParameters}; use crate::util::config::UserConfig; +use crate::util::test_utils; + +pub fn get_blinded_route_parameters( + amt_msat: u64, payment_secret: PaymentSecret, node_ids: Vec, + channel_upds: &[&msgs::UnsignedChannelUpdate], keys_manager: &test_utils::TestKeysInterface +) -> RouteParameters { + let mut intermediate_nodes = Vec::new(); + for (node_id, chan_upd) in node_ids.iter().zip(channel_upds) { + intermediate_nodes.push(ForwardNode { + node_id: *node_id, + tlvs: ForwardTlvs { + short_channel_id: chan_upd.short_channel_id, + payment_relay: PaymentRelay { + cltv_expiry_delta: chan_upd.cltv_expiry_delta, + fee_proportional_millionths: chan_upd.fee_proportional_millionths, + fee_base_msat: chan_upd.fee_base_msat, + }, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: chan_upd.htlc_minimum_msat, + }, + features: BlindedHopFeatures::empty(), + }, + htlc_maximum_msat: chan_upd.htlc_maximum_msat, + }); + } + let payee_tlvs = ReceiveTlvs { + payment_secret, + payment_constraints: PaymentConstraints { + max_cltv_expiry: u32::max_value(), + htlc_minimum_msat: channel_upds.last().unwrap().htlc_minimum_msat, + }, + }; + let mut secp_ctx = Secp256k1::new(); + let blinded_path = BlindedPath::new_for_payment( + &intermediate_nodes[..], *node_ids.last().unwrap(), payee_tlvs, + channel_upds.last().unwrap().htlc_maximum_msat, keys_manager, &secp_ctx + ).unwrap(); + + RouteParameters::from_payment_params_and_value( + PaymentParameters::blinded(vec![blinded_path]), amt_msat + ) +} #[test] fn one_hop_blinded_path() { @@ -145,38 +190,10 @@ fn do_forward_checks_failure(check: ForwardCheckFail) { let amt_msat = 5000; let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); - let intermediate_nodes = vec![ForwardNode { - node_id: nodes[1].node.get_our_node_id(), - tlvs: ForwardTlvs { - short_channel_id: chan_upd_1_2.short_channel_id, - payment_relay: PaymentRelay { - cltv_expiry_delta: chan_upd_1_2.cltv_expiry_delta, - fee_proportional_millionths: chan_upd_1_2.fee_proportional_millionths, - fee_base_msat: chan_upd_1_2.fee_base_msat, - }, - payment_constraints: PaymentConstraints { - max_cltv_expiry: u32::max_value(), - htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat, - }, - features: BlindedHopFeatures::empty(), - }, - htlc_maximum_msat: chan_upd_1_2.htlc_maximum_msat, - }]; - let payee_tlvs = ReceiveTlvs { - payment_secret, - payment_constraints: PaymentConstraints { - max_cltv_expiry: u32::max_value(), - htlc_minimum_msat: chan_upd_1_2.htlc_minimum_msat, - }, - }; - let mut secp_ctx = Secp256k1::new(); - let blinded_path = BlindedPath::new_for_payment( - &intermediate_nodes[..], nodes[2].node.get_our_node_id(), payee_tlvs, - chan_upd_1_2.htlc_maximum_msat, &chanmon_cfgs[2].keys_manager, &secp_ctx - ).unwrap(); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager); - let route_params = RouteParameters::from_payment_params_and_value( - PaymentParameters::blinded(vec![blinded_path]), amt_msat); let route = get_route(&nodes[0], &route_params).unwrap(); node_cfgs[0].router.expect_find_route(route_params.clone(), Ok(route.clone())); nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); From 09cf4847bd2b21c6ffe0a92d5b6a19306d77be13 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 2 Oct 2023 14:13:25 -1000 Subject: [PATCH 17/22] Correctly fail back blinded inbound fwd HTLCs when adding to a Channel As the intro node. --- lightning/src/ln/channelmanager.rs | 8 ++++++-- lightning/src/ln/shutdown_tests.rs | 32 +++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 5a6694fe5f4..aa44a63e590 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -6441,8 +6441,12 @@ where // but if we've sent a shutdown and they haven't acknowledged it yet, we just // want to reject the new HTLC and fail it backwards instead of forwarding. match pending_forward_info { - PendingHTLCStatus::Forward(PendingHTLCInfo { ref incoming_shared_secret, .. }) => { - let reason = if (error_code & 0x1000) != 0 { + PendingHTLCStatus::Forward(PendingHTLCInfo { + ref incoming_shared_secret, ref routing, .. + }) => { + let reason = if routing.blinded_failure().is_some() { + HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]) + } else if (error_code & 0x1000) != 0 { let (real_code, error_data) = self.get_htlc_inbound_temp_fail_err_and_data(error_code, chan); HTLCFailReason::reason(real_code, error_data) } else { diff --git a/lightning/src/ln/shutdown_tests.rs b/lightning/src/ln/shutdown_tests.rs index bc7c013771f..308211d0b37 100644 --- a/lightning/src/ln/shutdown_tests.rs +++ b/lightning/src/ln/shutdown_tests.rs @@ -13,10 +13,11 @@ use crate::sign::{EntropySource, SignerProvider}; use crate::chain::ChannelMonitorUpdateStatus; use crate::chain::transaction::OutPoint; use crate::events::{MessageSendEvent, HTLCDestination, MessageSendEventsProvider, ClosureReason}; -use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields, ChannelShutdownState, ChannelDetails}; +use crate::ln::channelmanager::{self, PaymentSendFailure, PaymentId, RecipientOnionFields, Retry, ChannelShutdownState, ChannelDetails}; use crate::routing::router::{PaymentParameters, get_route, RouteParameters}; use crate::ln::msgs; use crate::ln::msgs::{ChannelMessageHandler, ErrorAction}; +use crate::ln::onion_utils::INVALID_ONION_BLINDING; use crate::ln::script::ShutdownScript; use crate::util::test_utils; use crate::util::test_utils::OnGetShutdownScriptpubkey; @@ -401,6 +402,11 @@ fn updates_shutdown_wait() { #[test] fn htlc_fail_async_shutdown() { + do_htlc_fail_async_shutdown(true); + do_htlc_fail_async_shutdown(false); +} + +fn do_htlc_fail_async_shutdown(blinded_recipient: bool) { // Test HTLCs fail if shutdown starts even if messages are delivered out-of-order let chanmon_cfgs = create_chanmon_cfgs(3); let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); @@ -409,9 +415,20 @@ fn htlc_fail_async_shutdown() { let chan_1 = create_announced_chan_between_nodes(&nodes, 0, 1); let chan_2 = create_announced_chan_between_nodes(&nodes, 1, 2); - let (route, our_payment_hash, _, our_payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 100000); - nodes[0].node.send_payment_with_route(&route, our_payment_hash, - RecipientOnionFields::secret_only(our_payment_secret), PaymentId(our_payment_hash.0)).unwrap(); + let amt_msat = 100000; + let (_, our_payment_hash, our_payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let route_params = if blinded_recipient { + crate::ln::blinded_payment_tests::get_blinded_route_parameters( + amt_msat, our_payment_secret, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_2.0.contents], + &chanmon_cfgs[2].keys_manager) + } else { + RouteParameters::from_payment_params_and_value( + PaymentParameters::from_node_id(nodes[2].node.get_our_node_id(), TEST_FINAL_CLTV), amt_msat) + }; + nodes[0].node.send_payment(our_payment_hash, + RecipientOnionFields::secret_only(our_payment_secret), + PaymentId(our_payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); check_added_monitors!(nodes[0], 1); let updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id()); assert_eq!(updates.update_add_htlcs.len(), 1); @@ -441,7 +458,12 @@ fn htlc_fail_async_shutdown() { nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates_2.update_fail_htlcs[0]); commitment_signed_dance!(nodes[0], nodes[1], updates_2.commitment_signed, false, true); - expect_payment_failed_with_update!(nodes[0], our_payment_hash, false, chan_2.0.contents.short_channel_id, true); + if blinded_recipient { + expect_payment_failed_conditions(&nodes[0], our_payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); + } else { + expect_payment_failed_with_update!(nodes[0], our_payment_hash, false, chan_2.0.contents.short_channel_id, true); + } let msg_events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(msg_events.len(), 1); From b767d379c118228994706ba4ef589aee91c87bbd Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sun, 8 Oct 2023 16:10:17 -1000 Subject: [PATCH 18/22] Correctly fail back downstream-failed blinded HTLCs as intro --- lightning/src/ln/blinded_payment_tests.rs | 57 +++++++++++++++++++++++ lightning/src/ln/channelmanager.rs | 20 ++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index c0756782afa..2eacdc839d0 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -235,3 +235,60 @@ fn do_forward_checks_failure(check: ForwardCheckFail) { expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); } + +#[test] +fn failed_backwards_to_intro_node() { + // Ensure the intro node will error backwards properly even if the downstream node did not blind + // their error. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager); + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut payment_event = SendEvent::from_event(ev); + + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); + check_added_monitors!(nodes[1], 0); + do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, false); + expect_pending_htlcs_forwardable!(nodes[1]); + check_added_monitors!(&nodes[1], 1); + + let mut events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[2].node.get_our_node_id(), &mut events); + let mut payment_event = SendEvent::from_event(ev); + + // Ensure the final node fails to handle the HTLC. + payment_event.msgs[0].onion_routing_packet.hop_data[0] ^= 1; + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); + check_added_monitors!(nodes[2], 0); + do_commitment_signed_dance(&nodes[2], &nodes[1], &payment_event.commitment_msg, true, true); + nodes[2].node.process_pending_htlc_forwards(); + + let mut updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id()); + let mut update_malformed = &mut updates.update_fail_malformed_htlcs[0]; + // Ensure the final hop does not correctly blind their error. + update_malformed.sha256_of_onion = [1; 32]; + nodes[1].node.handle_update_fail_malformed_htlc(&nodes[2].node.get_our_node_id(), update_malformed); + do_commitment_signed_dance(&nodes[1], &nodes[2], &updates.commitment_signed, true, false); + + let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); +} diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index aa44a63e590..4247d57cbb6 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -5158,9 +5158,23 @@ where &self.pending_events, &self.logger) { self.push_pending_forwards_ev(); } }, - HTLCSource::PreviousHopData(HTLCPreviousHopData { ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, ref phantom_shared_secret, ref outpoint, .. }) => { - log_trace!(self.logger, "Failing HTLC with payment_hash {} backwards from us with {:?}", &payment_hash, onion_error); - let err_packet = onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret); + HTLCSource::PreviousHopData(HTLCPreviousHopData { + ref short_channel_id, ref htlc_id, ref incoming_packet_shared_secret, + ref phantom_shared_secret, ref outpoint, ref blinded_failure, .. + }) => { + log_trace!(self.logger, "Failing {}HTLC with payment_hash {} backwards from us: {:?}", + if blinded_failure.is_some() { "blinded " } else { "" }, &payment_hash, onion_error); + let err_packet = match blinded_failure { + Some(BlindedFailure::FromIntroductionNode) => { + let blinded_onion_error = HTLCFailReason::reason(INVALID_ONION_BLINDING, vec![0; 32]); + blinded_onion_error.get_encrypted_failure_packet( + incoming_packet_shared_secret, phantom_shared_secret + ) + }, + None => { + onion_error.get_encrypted_failure_packet(incoming_packet_shared_secret, phantom_shared_secret) + } + }; let mut push_forward_ev = false; let mut forward_htlcs = self.forward_htlcs.lock().unwrap(); From 0a4587020ec7eb81d59038ffe137c6b93b30ba49 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 31 Oct 2023 13:27:50 -0400 Subject: [PATCH 19/22] Test intro node blinded HTLC failing in process_pending_htlc_fwds. --- lightning/src/ln/blinded_payment_tests.rs | 84 ++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 2eacdc839d0..48a8ad02ccc 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -10,7 +10,7 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use crate::blinded_path::BlindedPath; use crate::blinded_path::payment::{ForwardNode, ForwardTlvs, PaymentConstraints, PaymentRelay, ReceiveTlvs}; -use crate::events::MessageSendEventsProvider; +use crate::events::{HTLCDestination, MessageSendEventsProvider}; use crate::ln::PaymentSecret; use crate::ln::channelmanager; use crate::ln::channelmanager::{PaymentId, RecipientOnionFields}; @@ -292,3 +292,85 @@ fn failed_backwards_to_intro_node() { expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); } + +enum ProcessPendingHTLCsCheck { + FwdPeerDisconnected, + FwdChannelClosed, +} + +#[test] +fn forward_fail_in_process_pending_htlc_fwds() { + do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdPeerDisconnected); + do_forward_fail_in_process_pending_htlc_fwds(ProcessPendingHTLCsCheck::FwdChannelClosed); +} +fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck) { + // Ensure the intro node will error backwards properly if the HTLC fails in + // process_pending_htlc_forwards. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]); + let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let (chan_upd_1_2, channel_id) = { + let chan = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0); + (chan.0.contents, chan.2) + }; + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2], + &chanmon_cfgs[2].keys_manager); + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events); + let mut payment_event = SendEvent::from_event(ev); + + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); + check_added_monitors!(nodes[1], 0); + do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, false); + + match check { + ProcessPendingHTLCsCheck::FwdPeerDisconnected => { + // Disconnect the next-hop peer so when we go to forward in process_pending_htlc_forwards, the + // intro node will error backwards. + nodes[1].node.peer_disconnected(&nodes[2].node.get_our_node_id()); + expect_pending_htlcs_forwardable!(nodes[1]); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], + vec![HTLCDestination::NextHopChannel { node_id: Some(nodes[2].node.get_our_node_id()), channel_id }]); + }, + ProcessPendingHTLCsCheck::FwdChannelClosed => { + // Force close the next-hop channel so when we go to forward in process_pending_htlc_forwards, + // the intro node will error backwards. + nodes[1].node.force_close_broadcasting_latest_txn(&channel_id, &nodes[2].node.get_our_node_id()).unwrap(); + let events = nodes[1].node.get_and_clear_pending_events(); + match events[0] { + crate::events::Event::PendingHTLCsForwardable { .. } => {}, + _ => panic!("Unexpected event {:?}", events), + }; + match events[1] { + crate::events::Event::ChannelClosed { .. } => {}, + _ => panic!("Unexpected event {:?}", events), + } + + nodes[1].node.process_pending_htlc_forwards(); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], + vec![HTLCDestination::UnknownNextHop { requested_forward_scid: chan_upd_1_2.short_channel_id }]); + check_closed_broadcast(&nodes[1], 1, true); + check_added_monitors!(nodes[1], 1); + nodes[1].node.process_pending_htlc_forwards(); + }, + } + + let mut updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]); + check_added_monitors!(nodes[1], 1); + do_commitment_signed_dance(&nodes[0], &nodes[1], &updates.commitment_signed, false, false); + + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); +} From 4d43ccdf0f8fa36de2beb13f4b75947098d71d0d Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 31 Oct 2023 13:28:15 -0400 Subject: [PATCH 20/22] Test intro node failing blinded intercept HTLC. --- lightning/src/ln/blinded_payment_tests.rs | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index 48a8ad02ccc..635057deab2 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -374,3 +374,61 @@ fn do_forward_fail_in_process_pending_htlc_fwds(check: ProcessPendingHTLCsCheck) expect_payment_failed_conditions(&nodes[0], payment_hash, false, PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); } + +#[test] +fn blinded_intercept_payment() { + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let mut intercept_forwards_config = test_default_channel_config(); + intercept_forwards_config.accept_intercept_htlcs = true; + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_forwards_config), None]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0); + let chan_upd = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents; + + let amt_msat = 5000; + let (_, payment_hash, payment_secret) = get_payment_preimage_hash(&nodes[2], Some(amt_msat), None); + let intercept_scid = nodes[1].node.get_intercept_scid(); + let mut intercept_chan_upd = chan_upd; + intercept_chan_upd.short_channel_id = intercept_scid; + let route_params = get_blinded_route_parameters(amt_msat, payment_secret, + nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&intercept_chan_upd], + &chanmon_cfgs[2].keys_manager); + + nodes[0].node.send_payment(payment_hash, RecipientOnionFields::spontaneous_empty(), + PaymentId(payment_hash.0), route_params, Retry::Attempts(0)).unwrap(); + check_added_monitors(&nodes[0], 1); + let payment_event = { + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + SendEvent::from_event(events.remove(0)) + }; + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); + commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true); + + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + let intercept_id = match events[0] { + crate::events::Event::HTLCIntercepted { + intercept_id, payment_hash: pmt_hash, + requested_next_hop_scid: short_channel_id, .. + } => { + assert_eq!(pmt_hash, payment_hash); + assert_eq!(short_channel_id, intercept_scid); + intercept_id + }, + _ => panic!() + }; + + nodes[1].node.fail_intercepted_htlc(intercept_id).unwrap(); + expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(nodes[1], vec![HTLCDestination::UnknownNextHop { requested_forward_scid: intercept_scid }]); + nodes[1].node.process_pending_htlc_forwards(); + let update_fail = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id()); + check_added_monitors!(&nodes[1], 1); + assert!(update_fail.update_fail_htlcs.len() == 1); + let fail_msg = update_fail.update_fail_htlcs[0].clone(); + nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_msg); + commitment_signed_dance!(nodes[0], nodes[1], update_fail.commitment_signed, false); + expect_payment_failed_conditions(&nodes[0], payment_hash, false, + PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32])); +} From e510e3c2d9ec719ebe35248a544e4510b116ddfa Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Tue, 7 Nov 2023 14:47:26 -0500 Subject: [PATCH 21/22] Add release note for blinded HTLC backwards compat. --- pending_changelog/route-blinding-intro-node.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pending_changelog/route-blinding-intro-node.txt diff --git a/pending_changelog/route-blinding-intro-node.txt b/pending_changelog/route-blinding-intro-node.txt new file mode 100644 index 00000000000..3f31d3760e6 --- /dev/null +++ b/pending_changelog/route-blinding-intro-node.txt @@ -0,0 +1,4 @@ +## Backwards Compat + +* Forwarding a blinded HTLC and subsequently downgrading to an LDK version prior to 0.0.119 may + result in a forwarding failure or an HTLC being failed backwards with an unblinded error. From 6af786af6b17c15ddec727b6eda306ace55d2e29 Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Mon, 13 Nov 2023 15:52:18 -0500 Subject: [PATCH 22/22] Test blinding point serialization in Channel. --- lightning/src/ln/channel.rs | 106 ++++++++++++++++++++++++++++++-- lightning/src/ln/onion_utils.rs | 2 + 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 46209a0c402..001c4d8c963 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -166,6 +166,7 @@ struct InboundHTLCOutput { state: InboundHTLCState, } +#[cfg_attr(test, derive(Clone, Debug, PartialEq))] enum OutboundHTLCState { /// Added by us and included in a commitment_signed (if we were AwaitingRemoteRevoke when we /// created it we would have put it in the holding cell instead). When they next revoke_and_ack @@ -199,6 +200,7 @@ enum OutboundHTLCState { } #[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] enum OutboundHTLCOutcome { /// LDK version 0.0.105+ will always fill in the preimage here. Success(Option), @@ -223,6 +225,7 @@ impl<'a> Into> for &'a OutboundHTLCOutcome { } } +#[cfg_attr(test, derive(Clone, Debug, PartialEq))] struct OutboundHTLCOutput { htlc_id: u64, amount_msat: u64, @@ -235,6 +238,7 @@ struct OutboundHTLCOutput { } /// See AwaitingRemoteRevoke ChannelState for more info +#[cfg_attr(test, derive(Clone, Debug, PartialEq))] enum HTLCUpdateAwaitingACK { AddHTLC { // TODO: Time out if we're getting close to cltv_expiry // always outbound @@ -7889,13 +7893,14 @@ mod tests { use bitcoin::blockdata::transaction::{Transaction, TxOut}; use bitcoin::blockdata::opcodes; use bitcoin::network::constants::Network; - use crate::ln::PaymentHash; + use crate::ln::{PaymentHash, PaymentPreimage}; use crate::ln::channel_keys::{RevocationKey, RevocationBasepoint}; -use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; + use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; use crate::ln::channel::InitFeatures; - use crate::ln::channel::{ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, commit_tx_fee_msat}; + use crate::ln::channel::{Channel, ChannelState, InboundHTLCOutput, OutboundV1Channel, InboundV1Channel, OutboundHTLCOutput, InboundHTLCState, OutboundHTLCState, HTLCCandidate, HTLCInitiator, HTLCUpdateAwaitingACK, commit_tx_fee_msat}; use crate::ln::channel::{MAX_FUNDING_SATOSHIS_NO_WUMBO, TOTAL_BITCOIN_SUPPLY_SATOSHIS, MIN_THEIR_CHAN_RESERVE_SATOSHIS}; - use crate::ln::features::ChannelTypeFeatures; + use crate::ln::features::{ChannelFeatures, ChannelTypeFeatures, NodeFeatures}; + use crate::ln::msgs; use crate::ln::msgs::{ChannelUpdate, DecodeError, UnsignedChannelUpdate, MAX_VALUE_MSAT}; use crate::ln::script::ShutdownScript; use crate::ln::chan_utils::{self, htlc_success_tx_weight, htlc_timeout_tx_weight}; @@ -7903,9 +7908,10 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; use crate::chain::chaininterface::{FeeEstimator, LowerBoundedFeeEstimator, ConfirmationTarget}; use crate::sign::{ChannelSigner, InMemorySigner, EntropySource, SignerProvider}; use crate::chain::transaction::OutPoint; - use crate::routing::router::Path; + use crate::routing::router::{Path, RouteHop}; use crate::util::config::UserConfig; use crate::util::errors::APIError; + use crate::util::ser::{ReadableArgs, Writeable}; use crate::util::test_utils; use crate::util::test_utils::{OnGetShutdownScriptpubkey, TestKeysInterface}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; @@ -8419,6 +8425,96 @@ use crate::ln::channelmanager::{self, HTLCSource, PaymentId}; assert!(!node_a_chan.channel_update(&update).unwrap()); } + #[test] + fn blinding_point_ser() { + // Ensure that channel blinding points are (de)serialized properly. + let feeest = LowerBoundedFeeEstimator::new(&TestFeeEstimator{fee_est: 15000}); + let secp_ctx = Secp256k1::new(); + let seed = [42; 32]; + let network = Network::Testnet; + let keys_provider = test_utils::TestKeysInterface::new(&seed, network); + + let node_b_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); + let config = UserConfig::default(); + let features = channelmanager::provided_init_features(&config); + let outbound_chan = OutboundV1Channel::<&TestKeysInterface>::new(&feeest, &&keys_provider, &&keys_provider, node_b_node_id, &features, 10000000, 100000, 42, &config, 0, 42, None).unwrap(); + let mut chan = Channel { context: outbound_chan.context }; + + let dummy_htlc_source = HTLCSource::OutboundRoute { + path: Path { + hops: vec![RouteHop { + pubkey: test_utils::pubkey(2), channel_features: ChannelFeatures::empty(), + node_features: NodeFeatures::empty(), short_channel_id: 0, fee_msat: 0, + cltv_expiry_delta: 0, maybe_announced_channel: false, + }], + blinded_tail: None + }, + session_priv: test_utils::privkey(42), + first_hop_htlc_msat: 0, + payment_id: PaymentId([42; 32]), + }; + let dummy_outbound_output = OutboundHTLCOutput { + htlc_id: 0, + amount_msat: 0, + payment_hash: PaymentHash([43; 32]), + cltv_expiry: 0, + state: OutboundHTLCState::Committed, + source: dummy_htlc_source.clone(), + skimmed_fee_msat: None, + blinding_point: None, + }; + let mut pending_outbound_htlcs = vec![dummy_outbound_output.clone(); 10]; + for (idx, htlc) in pending_outbound_htlcs.iter_mut().enumerate() { + if idx % 2 == 0 { + htlc.blinding_point = Some(test_utils::pubkey(42 + idx as u8)); + } + } + chan.context.pending_outbound_htlcs = pending_outbound_htlcs.clone(); + + let dummy_holding_cell_add_htlc = HTLCUpdateAwaitingACK::AddHTLC { + amount_msat: 0, + cltv_expiry: 0, + payment_hash: PaymentHash([43; 32]), + source: dummy_htlc_source.clone(), + onion_routing_packet: msgs::OnionPacket { + version: 0, + public_key: Ok(test_utils::pubkey(1)), + hop_data: [0; 20*65], + hmac: [0; 32] + }, + skimmed_fee_msat: None, + blinding_point: None, + }; + let dummy_holding_cell_claim_htlc = HTLCUpdateAwaitingACK::ClaimHTLC { + payment_preimage: PaymentPreimage([42; 32]), + htlc_id: 0, + }; + let mut holding_cell_htlc_updates = Vec::with_capacity(10); + for i in 0..10 { + if i % 3 == 0 { + holding_cell_htlc_updates.push(dummy_holding_cell_add_htlc.clone()); + } else if i % 3 == 1 { + holding_cell_htlc_updates.push(dummy_holding_cell_claim_htlc.clone()); + } else { + let mut dummy_add = dummy_holding_cell_add_htlc.clone(); + if let HTLCUpdateAwaitingACK::AddHTLC { ref mut blinding_point, .. } = &mut dummy_add { + *blinding_point = Some(test_utils::pubkey(42 + i)); + } else { panic!() } + holding_cell_htlc_updates.push(dummy_add); + } + } + chan.context.holding_cell_htlc_updates = holding_cell_htlc_updates.clone(); + + // Encode and decode the channel and ensure that the HTLCs within are the same. + let encoded_chan = chan.encode(); + let mut s = crate::io::Cursor::new(&encoded_chan); + let mut reader = crate::util::ser::FixedLengthReader::new(&mut s, encoded_chan.len() as u64); + let features = channelmanager::provided_channel_type_features(&config); + let decoded_chan = Channel::read(&mut reader, (&&keys_provider, &&keys_provider, 0, &features)).unwrap(); + assert_eq!(decoded_chan.context.pending_outbound_htlcs, pending_outbound_htlcs); + assert_eq!(decoded_chan.context.holding_cell_htlc_updates, holding_cell_htlc_updates); + } + #[cfg(feature = "_test_vectors")] #[test] fn outbound_commitment_test() { diff --git a/lightning/src/ln/onion_utils.rs b/lightning/src/ln/onion_utils.rs index 88f0efe9aa8..051e78c46ee 100644 --- a/lightning/src/ln/onion_utils.rs +++ b/lightning/src/ln/onion_utils.rs @@ -740,9 +740,11 @@ pub(super) fn process_onion_failure( } #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug +#[cfg_attr(test, derive(PartialEq))] pub(super) struct HTLCFailReason(HTLCFailReasonRepr); #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug +#[cfg_attr(test, derive(PartialEq))] enum HTLCFailReasonRepr { LightningError { err: msgs::OnionErrorPacket,