diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index f234ad9f91f..b2395ce3e1c 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -114,6 +114,7 @@ pub(super) struct PendingHTLCInfo { payment_hash: PaymentHash, pub(super) amt_to_forward: u64, pub(super) outgoing_cltv_value: u32, + pub(super) amt_incoming: Option } #[derive(Clone)] // See Channel::revoke_and_ack for why, tl;dr: Rust bug @@ -129,20 +130,23 @@ pub(super) enum PendingHTLCStatus { Fail(HTLCFailureMsg), } -pub(super) enum HTLCForwardInfo { - AddHTLC { - forward_info: PendingHTLCInfo, +#[derive(Clone)] +pub(super) struct PendingAddHTLCInfo { + pub(super) forward_info: PendingHTLCInfo, + + // These fields are produced in `forward_htlcs()` and consumed in + // `process_pending_htlc_forwards()` for constructing the + // `HTLCSource::PreviousHopData` for failed and forwarded + // HTLCs. + // + // Note that this may be an outbound SCID alias for the associated channel. + prev_short_channel_id: u64, + prev_htlc_id: u64, + prev_funding_outpoint: OutPoint, +} - // These fields are produced in `forward_htlcs()` and consumed in - // `process_pending_htlc_forwards()` for constructing the - // `HTLCSource::PreviousHopData` for failed and forwarded - // HTLCs. - // - // Note that this may be an outbound SCID alias for the associated channel. - prev_short_channel_id: u64, - prev_htlc_id: u64, - prev_funding_outpoint: OutPoint, - }, +pub(super) enum HTLCForwardInfo { + AddHTLC(PendingAddHTLCInfo), FailHTLC { htlc_id: u64, err_packet: msgs::OnionErrorPacket, @@ -186,6 +190,24 @@ struct ClaimableHTLC { total_msat: u64, } +/// An identifier used to uniquely identify an intercepted htlc to LDK. +/// (C-not exported) as we just use [u8; 32] directly +#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)] +pub struct InterceptId(pub [u8; 32]); + +impl Writeable for InterceptId { + fn write(&self, w: &mut W) -> Result<(), io::Error> { + self.0.write(w) + } +} + +impl Readable for InterceptId { + fn read(r: &mut R) -> Result { + let buf: [u8; 32] = Readable::read(r)?; + Ok(InterceptId(buf)) + } +} + /// A payment identifier used to uniquely identify a payment to LDK. /// (C-not exported) as we just use [u8; 32] directly #[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)] @@ -761,6 +783,9 @@ pub struct ChannelManager>>, #[cfg(not(test))] forward_htlcs: Mutex>>, + /// Storage for PendingInterceptedHTLC's that have been intercepted and bubbled up to the user. + /// We hold them here until the user tells us what we should to with them. + pending_intercepted_payments: Mutex>, /// The set of outbound SCID aliases across all our channels, including unconfirmed channels /// and some closed channels which reached a usable state prior to being closed. This is used @@ -1634,6 +1659,7 @@ impl ChannelMana pending_outbound_payments: Mutex::new(HashMap::new()), forward_htlcs: Mutex::new(HashMap::new()), id_to_peer: Mutex::new(HashMap::new()), + pending_intercepted_payments: Mutex::new(HashMap::new()), our_network_key: keys_manager.get_node_secret(Recipient::Node).unwrap(), our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &keys_manager.get_node_secret(Recipient::Node).unwrap()), @@ -2163,6 +2189,7 @@ impl ChannelMana payment_hash, incoming_shared_secret: shared_secret, amt_to_forward: amt_msat, + amt_incoming: Some(amt_msat), outgoing_cltv_value: hop_data.outgoing_cltv_value, }) } @@ -2260,6 +2287,7 @@ impl ChannelMana incoming_shared_secret: shared_secret, amt_to_forward: next_hop_data.amt_to_forward, outgoing_cltv_value: next_hop_data.outgoing_cltv_value, + amt_incoming: Some(msg.amount_msat) }) } }; @@ -3030,6 +3058,66 @@ impl ChannelMana Ok(()) } + /// Fails the intercepted payment indicated by intercept_id. This should really only be called in response + /// to a PaymentIntercepted event + pub fn fail_intercepted_payment(&self, intercept_id: InterceptId) { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); + + let pending_intercept = { + let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap(); + pending_intercepts.remove(&intercept_id) + }; + + if let Some(payment) = pending_intercept { + if let PendingHTLCRouting::Forward { short_channel_id, .. } = payment.forward_info.routing { + let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { + short_channel_id: payment.prev_short_channel_id, + outpoint: payment.prev_funding_outpoint, + htlc_id: payment.prev_htlc_id, + incoming_packet_shared_secret: payment.forward_info.incoming_shared_secret, + phantom_shared_secret: None, + }); + + let failure_reason = HTLCFailReason::Reason { failure_code: 0x4000 | 10, data: Vec::new() }; + let destination = HTLCDestination::UnknownNextHop { requested_forward_scid: short_channel_id }; + self.fail_htlc_backwards_internal(htlc_source, &payment.forward_info.payment_hash, failure_reason, destination); + } + } + } + + /// Attempts to forward an intercepted payment over the provided scid and with the provided amt_to_forward. + /// Should only really be called in response to a PaymentIntercepted event + pub fn forward_intercepted_payment(&self, intercept_id: InterceptId, scid: u64, amt_to_forward: u64) -> Result<(), APIError> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); + + let pending_intercept = { + let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap(); + pending_intercepts.remove(&intercept_id) + }; + + match pending_intercept { + None => Err(APIError::APIMisuseError { err: "Payment with that InterceptId not found".to_string() }), + Some(payment) => { + let routing = match payment.forward_info.routing { + PendingHTLCRouting::Forward { onion_packet, .. } => { + PendingHTLCRouting::Forward { onion_packet, short_channel_id: scid } + }, + _ => payment.forward_info.routing + }; + + let pending_htlc_info = PendingHTLCInfo { + amt_to_forward, + routing, + ..payment.forward_info + }; + + let mut per_source_pending_forward = vec![(payment.prev_short_channel_id, payment.prev_funding_outpoint, vec![(pending_htlc_info, payment.prev_htlc_id)])]; + self.forward_htlcs(&mut per_source_pending_forward); + Ok(()) + } + } + } + /// Processes HTLCs which are pending waiting on random forward delay. /// /// Should only really ever be called in response to a PendingHTLCsForwardable event. @@ -3054,9 +3142,9 @@ impl ChannelMana None => { for forward_info in pending_forwards.drain(..) { match forward_info { - HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo { - routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value }, - prev_funding_outpoint } => { + HTLCForwardInfo::AddHTLC(pending_add_htlc_info) => { + let PendingAddHTLCInfo { prev_short_channel_id, prev_htlc_id, forward_info, prev_funding_outpoint } = pending_add_htlc_info.clone(); + macro_rules! failure_handler { ($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr, $next_hop_unknown: expr) => { log_info!(self.logger, "Failed to accept/forward incoming HTLC: {}", $msg); @@ -3065,17 +3153,17 @@ impl ChannelMana short_channel_id: prev_short_channel_id, outpoint: prev_funding_outpoint, htlc_id: prev_htlc_id, - incoming_packet_shared_secret: incoming_shared_secret, + incoming_packet_shared_secret: forward_info.incoming_shared_secret, phantom_shared_secret: $phantom_ss, }); let reason = if $next_hop_unknown { HTLCDestination::UnknownNextHop { requested_forward_scid: short_chan_id } } else { - HTLCDestination::FailedPayment{ payment_hash } + HTLCDestination::FailedPayment{ payment_hash: forward_info.payment_hash } }; - failed_forwards.push((htlc_source, payment_hash, + failed_forwards.push((htlc_source, forward_info.payment_hash, HTLCFailReason::Reason { failure_code: $err_code, data: $err_data }, reason )); @@ -3096,11 +3184,11 @@ impl ChannelMana } } } - if let PendingHTLCRouting::Forward { onion_packet, .. } = routing { + if let PendingHTLCRouting::Forward { ref onion_packet, .. } = forward_info.routing { let phantom_secret_res = self.keys_manager.get_node_secret(Recipient::PhantomNode); if phantom_secret_res.is_ok() && fake_scid::is_valid_phantom(&self.fake_scid_rand_bytes, short_chan_id) { let phantom_shared_secret = SharedSecret::new(&onion_packet.public_key.unwrap(), &phantom_secret_res.unwrap()).secret_bytes(); - let next_hop = match onion_utils::decode_next_payment_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, payment_hash) { + let next_hop = match onion_utils::decode_next_payment_hop(phantom_shared_secret, &onion_packet.hop_data, onion_packet.hmac, forward_info.payment_hash) { Ok(res) => res, Err(onion_utils::OnionDecodeErr::Malformed { err_msg, err_code }) => { let sha256_of_onion = Sha256::hash(&onion_packet.hop_data).into_inner(); @@ -3116,13 +3204,31 @@ impl ChannelMana }; match next_hop { onion_utils::Hop::Receive(hop_data) => { - match self.construct_recv_pending_htlc_info(hop_data, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value, Some(phantom_shared_secret)) { + match self.construct_recv_pending_htlc_info(hop_data, forward_info.incoming_shared_secret, forward_info.payment_hash, forward_info.amt_to_forward, forward_info.outgoing_cltv_value, Some(phantom_shared_secret)) { Ok(info) => phantom_receives.push((prev_short_channel_id, prev_funding_outpoint, vec![(info, prev_htlc_id)])), Err(ReceiveError { err_code, err_data, msg }) => failed_payment!(msg, err_code, err_data, Some(phantom_shared_secret)) } }, _ => panic!(), } + } else if forward_info.amt_incoming.is_some() && fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, short_chan_id) { + let intercept_id = InterceptId(Sha256::hash(&forward_info.incoming_shared_secret).into_inner()); + let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap(); + match pending_intercepts.entry(intercept_id) { + hash_map::Entry::Vacant(entry) => { + entry.insert(pending_add_htlc_info); + new_events.push(events::Event::PaymentIntercepted { + short_channel_id: short_chan_id, + payment_hash: forward_info.payment_hash, + inbound_amount_msats: forward_info.amt_incoming.unwrap(), + expected_outbound_amount_msats: forward_info.amt_to_forward, + intercept_id + }); + }, + hash_map::Entry::Occupied(_) => { + fail_forward!(format!("Detected duplicate intercepted payment over short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None); + } + } } else { fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None); } @@ -3146,11 +3252,12 @@ impl ChannelMana let mut fail_htlc_msgs = Vec::new(); for forward_info in pending_forwards.drain(..) { match forward_info { - HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo { - routing: PendingHTLCRouting::Forward { - onion_packet, .. - }, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value }, - prev_funding_outpoint } => { + HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo { + routing: PendingHTLCRouting::Forward { + onion_packet, .. + }, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value, .. }, + prev_funding_outpoint + }) => { log_trace!(self.logger, "Adding HTLC from short id {} with payment_hash {} to channel with short id {} after delay", prev_short_channel_id, log_bytes!(payment_hash.0), short_chan_id); let htlc_source = HTLCSource::PreviousHopData(HTLCPreviousHopData { short_channel_id: prev_short_channel_id, @@ -3272,9 +3379,9 @@ impl ChannelMana } else { for forward_info in pending_forwards.drain(..) { match forward_info { - HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo { - routing, incoming_shared_secret, payment_hash, amt_to_forward, .. }, - prev_funding_outpoint } => { + HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo { + routing, incoming_shared_secret, payment_hash, amt_to_forward, .. + }, prev_funding_outpoint, .. }) => { let (cltv_expiry, onion_payload, payment_data, phantom_shared_secret) = match routing { PendingHTLCRouting::Receive { payment_data, incoming_cltv_expiry, phantom_shared_secret } => { let _legacy_hop_data = Some(payment_data.clone()); @@ -4939,12 +5046,12 @@ impl ChannelMana PendingHTLCRouting::ReceiveKeysend { .. } => 0, }) { hash_map::Entry::Occupied(mut entry) => { - entry.get_mut().push(HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_funding_outpoint, - prev_htlc_id, forward_info }); + entry.get_mut().push(HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, prev_funding_outpoint, + prev_htlc_id, forward_info })); }, hash_map::Entry::Vacant(entry) => { - entry.insert(vec!(HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_funding_outpoint, - prev_htlc_id, forward_info })); + entry.insert(vec!(HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { prev_short_channel_id, prev_funding_outpoint, + prev_htlc_id, forward_info }))); } } } @@ -5521,6 +5628,21 @@ impl ChannelMana inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } + /// Gets a fake short channel id for use in receiving [intercepted payments]. These fake scids + /// are used when constructing the route hints for payments intended to be intercepted. + pub fn get_intercept_scid(&self) -> u64 { + let mut channel_state = self.channel_state.lock().unwrap(); + let best_block = self.best_block.read().unwrap(); + loop { + let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block.height(), &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager); + // Ensure the generated scid doesn't conflict with a real channel. + match channel_state.short_to_chan_info.entry(scid_candidate) { + hash_map::Entry::Occupied(_) => continue, + hash_map::Entry::Vacant(_) => return scid_candidate + } + } + } + /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// @@ -6324,7 +6446,8 @@ impl_writeable_tlv_based!(PendingHTLCInfo, { (2, incoming_shared_secret, required), (4, payment_hash, required), (6, amt_to_forward, required), - (8, outgoing_cltv_value, required) + (8, outgoing_cltv_value, required), + (9, amt_incoming, option), }); @@ -6546,18 +6669,21 @@ impl_writeable_tlv_based_enum!(HTLCFailReason, }, ;); +impl_writeable_tlv_based!(PendingAddHTLCInfo, { + (0, forward_info, required), + (2, prev_short_channel_id, required), + (4, prev_htlc_id, required), + (6, prev_funding_outpoint, required) +}); + + impl_writeable_tlv_based_enum!(HTLCForwardInfo, - (0, AddHTLC) => { - (0, forward_info, required), - (2, prev_short_channel_id, required), - (4, prev_htlc_id, required), - (6, prev_funding_outpoint, required), - }, (1, FailHTLC) => { (0, htlc_id, required), (2, err_packet, required), - }, -;); + }; + (0, AddHTLC) +); impl_writeable_tlv_based!(PendingInboundPayment, { (0, payment_secret, required), @@ -6567,6 +6693,7 @@ impl_writeable_tlv_based!(PendingInboundPayment, { (8, min_value_msat, required), }); + impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment, (0, Legacy) => { (0, session_privs, required), @@ -6661,6 +6788,8 @@ impl Writeable f let pending_inbound_payments = self.pending_inbound_payments.lock().unwrap(); let pending_outbound_payments = self.pending_outbound_payments.lock().unwrap(); + let pending_intercepted_payments = self.pending_intercepted_payments.lock().unwrap(); + let events = self.pending_events.lock().unwrap(); (events.len() as u64).write(writer)?; for event in events.iter() { @@ -6732,6 +6861,12 @@ impl Writeable f (11, self.probing_cookie_secret, required), }); + (pending_intercepted_payments.len() as u64).write(writer)?; + for (intercept_id, pending_intercepted_payment) in pending_intercepted_payments.iter() { + intercept_id.write(writer)?; + pending_intercepted_payment.write(writer)?; + } + Ok(()) } } @@ -7032,7 +7167,7 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> let mut fake_scid_rand_bytes: Option<[u8; 32]> = None; let mut probing_cookie_secret: Option<[u8; 32]> = None; let mut claimable_htlc_purposes = None; - read_tlv_fields!(reader, { + read_tlv_fields!(reader.by_ref(), { (1, pending_outbound_payments_no_retry, option), (3, pending_outbound_payments, option), (5, received_network_pubkey, option), @@ -7040,6 +7175,15 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> (9, claimable_htlc_purposes, vec_type), (11, probing_cookie_secret, option), }); + + let pending_intercepted_payment_count: u64 = Readable::read(reader)?; + let mut pending_intercepted_payments: HashMap = HashMap::with_capacity(cmp::min(pending_intercepted_payment_count as usize, MAX_ALLOC_SIZE/(3*32))); + for _ in 0..pending_intercepted_payment_count { + if pending_intercepted_payments.insert(Readable::read(reader)?, Readable::read(reader)?).is_some() { + return Err(DecodeError::InvalidValue); + } + } + if fake_scid_rand_bytes.is_none() { fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes()); } @@ -7250,6 +7394,7 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> inbound_payment_key: expanded_inbound_key, pending_inbound_payments: Mutex::new(pending_inbound_payments), pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()), + pending_intercepted_payments: Mutex::new(pending_intercepted_payments), forward_htlcs: Mutex::new(forward_htlcs), outbound_scid_aliases: Mutex::new(outbound_scid_aliases), diff --git a/lightning/src/ln/onion_route_tests.rs b/lightning/src/ln/onion_route_tests.rs index 76d6723466b..2a934059ed1 100644 --- a/lightning/src/ln/onion_route_tests.rs +++ b/lightning/src/ln/onion_route_tests.rs @@ -44,6 +44,8 @@ use core::default::Default; use ln::functional_test_utils::*; +use super::channelmanager::PendingAddHTLCInfo; + fn run_onion_failure_test(_name: &str, test_case: u8, nodes: &Vec, route: &Route, payment_hash: &PaymentHash, payment_secret: &PaymentSecret, callback_msg: F1, callback_node: F2, expected_retryable: bool, expected_error_code: Option, expected_channel_update: Option, expected_short_channel_id: Option) where F1: for <'a> FnMut(&'a mut msgs::UpdateAddHTLC), F2: FnMut(), @@ -543,7 +545,7 @@ fn test_onion_failure() { for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { - &mut HTLCForwardInfo::AddHTLC { ref mut forward_info, .. } => + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => forward_info.outgoing_cltv_value += 1, _ => {}, } @@ -556,7 +558,7 @@ fn test_onion_failure() { for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { - &mut HTLCForwardInfo::AddHTLC { ref mut forward_info, .. } => + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { ref mut forward_info, .. }) => forward_info.amt_to_forward -= 1, _ => {}, } @@ -1024,12 +1026,12 @@ fn test_phantom_onion_hmac_failure() { let mut forward_htlcs = nodes[1].node.forward_htlcs.lock().unwrap(); let mut pending_forward = forward_htlcs.get_mut(&phantom_scid).unwrap(); match pending_forward[0] { - HTLCForwardInfo::AddHTLC { + HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { forward_info: PendingHTLCInfo { routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. }, .. }, .. - } => { + }) => { onion_packet.hmac[onion_packet.hmac.len() - 1] ^= 1; Sha256::hash(&onion_packet.hop_data).into_inner().to_vec() }, @@ -1084,12 +1086,12 @@ fn test_phantom_invalid_onion_payload() { for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { - &mut HTLCForwardInfo::AddHTLC { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { forward_info: PendingHTLCInfo { routing: PendingHTLCRouting::Forward { ref mut onion_packet, .. }, .. }, .. - } => { + }) => { // Construct the onion payloads for the entire route and an invalid amount. let height = nodes[0].best_block_info().1; let session_priv = SecretKey::from_slice(&session_priv).unwrap(); @@ -1155,9 +1157,9 @@ fn test_phantom_final_incorrect_cltv_expiry() { for (_, pending_forwards) in nodes[1].node.forward_htlcs.lock().unwrap().iter_mut() { for f in pending_forwards.iter_mut() { match f { - &mut HTLCForwardInfo::AddHTLC { + &mut HTLCForwardInfo::AddHTLC(PendingAddHTLCInfo { forward_info: PendingHTLCInfo { ref mut outgoing_cltv_value, .. }, .. - } => { + }) => { *outgoing_cltv_value += 1; }, _ => panic!("Unexpected forward"), diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index 8ddd762e970..6691b4acd80 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -36,6 +36,8 @@ use core::time::Duration; use core::ops::Deref; use sync::Arc; +use crate::ln::channelmanager::InterceptId; + /// Some information provided on receipt of payment depends on whether the payment received is a /// spontaneous payment or a "conventional" lightning payment that's paying an invoice. #[derive(Clone, Debug)] @@ -469,6 +471,26 @@ pub enum Event { /// now + 5*time_forwardable). time_forwardable: Duration, }, + /// Used to indicate that the short_channel_id a payment was intended to be routed over signaled + /// for interception. If this payment is to be rerouted you must call [`ChannelManager::forward_intercepted_payment`] with + /// the new short_channel_id and payment amount. If this payment should be failed you must call + /// [`ChannelManager::fail_intercepted_payment`]. + /// + /// [`ChannelManager::forward_intercepted_payment`]: crate::ln::channelmanager::ChannelManager::forward_intercepted_payment + /// [`ChannelManager::fail_intercepted_payment`]: crate::ln::channelmanager::ChannelManager::fail_intercepted_payment + PaymentIntercepted { + /// The scid that indicated this payment should be intercepted + short_channel_id: u64, + /// The payment hash used for this payment + payment_hash: PaymentHash, + /// How many msats are to be received on the inbound edge of this payment + inbound_amount_msats: u64, + /// How many msats the payer intended to route to the next node. Depending on the reason you are + /// intercepting this payment, you might take a fee by forwarding less than this amount + expected_outbound_amount_msats: u64, + /// A id to help LDK identify which payment is being forwarded or failed + intercept_id: InterceptId + }, /// Used to indicate that an output which you should know how to spend was confirmed on chain /// and is now spendable. /// Such an output will *not* ever be spent by rust-lightning, and are not at risk of your @@ -753,6 +775,16 @@ impl Writeable for Event { (2, failed_next_destination, required), }) }, + &Event::PaymentIntercepted { short_channel_id, payment_hash, inbound_amount_msats, expected_outbound_amount_msats, intercept_id } => { + 27u8.write(writer)?; + write_tlv_fields!(writer, { + (0, short_channel_id, required), + (2, payment_hash, required), + (4, inbound_amount_msats, required), + (6, expected_outbound_amount_msats, required), + (8, intercept_id, required) + }); + } // Note that, going forward, all new events must only write data inside of // `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write // data via `write_tlv_fields`. @@ -1033,6 +1065,27 @@ impl MaybeReadable for Event { }; f() }, + 27u8 => { + let mut payment_hash = PaymentHash([0; 32]); + let mut intercept_id = InterceptId([0; 32]); + let mut short_channel_id = 0; + let mut inbound_amount_msats = 0; + let mut expected_outbound_amount_msats = 0; + read_tlv_fields!(reader, { + (0, short_channel_id, required), + (2, payment_hash, required), + (4, inbound_amount_msats, required), + (6, expected_outbound_amount_msats, required), + (8, intercept_id, required) + }); + Ok(Some(Event::PaymentIntercepted { + payment_hash, + short_channel_id, + inbound_amount_msats, + expected_outbound_amount_msats, + intercept_id, + })) + }, // Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue. // Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt // reads. diff --git a/lightning/src/util/scid_utils.rs b/lightning/src/util/scid_utils.rs index 676c303bfa8..d8abe48076c 100644 --- a/lightning/src/util/scid_utils.rs +++ b/lightning/src/util/scid_utils.rs @@ -63,6 +63,7 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result bool { let block_height = scid_utils::block_from_scid(&scid); let tx_index = scid_utils::tx_index_from_scid(&scid); @@ -159,6 +161,17 @@ pub(crate) mod fake_scid { valid_vout == scid_utils::vout_from_scid(&scid) as u8 } + /// Returns whether the given fake scid falls into the intercept namespace. + pub fn is_valid_intercept(fake_scid_rand_bytes: &[u8; 32], scid: u64) -> bool { + let block_height = scid_utils::block_from_scid(&scid); + let tx_index = scid_utils::tx_index_from_scid(&scid); + let namespace = Namespace::Intercept; + let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes); + valid_vout == scid_utils::vout_from_scid(&scid) as u8 + } + + + #[cfg(test)] mod tests { use bitcoin::blockdata::constants::genesis_block; @@ -168,6 +181,8 @@ pub(crate) mod fake_scid { use util::test_utils; use sync::Arc; +use crate::util::scid_utils::fake_scid::is_valid_intercept; + #[test] fn namespace_identifier_is_within_range() { let phantom_namespace = Namespace::Phantom; @@ -201,6 +216,17 @@ pub(crate) mod fake_scid { assert!(!is_valid_phantom(&fake_scid_rand_bytes, invalid_fake_scid)); } + #[test] + fn test_is_valid_intercept() { + let namespace = Namespace::Intercept; + let fake_scid_rand_bytes = [0; 32]; + let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes); + let valid_fake_scid = scid_utils::scid_from_parts(0, 0, valid_encrypted_vout as u64).unwrap(); + assert!(is_valid_intercept(&fake_scid_rand_bytes, valid_fake_scid)); + let invalid_fake_scid = scid_utils::scid_from_parts(0, 0, 12).unwrap(); + assert!(!is_valid_intercept(&fake_scid_rand_bytes, invalid_fake_scid)); + } + #[test] fn test_get_fake_scid() { let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();