Skip to content

Commit 823f566

Browse files
add ability to intercept htlcs
1 parent 4e5f74a commit 823f566

File tree

3 files changed

+194
-2
lines changed

3 files changed

+194
-2
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,24 @@ struct ClaimableHTLC {
188188
total_msat: u64,
189189
}
190190

191+
/// An identifier used to uniquely identify an intercepted htlc to LDK.
192+
/// (C-not exported) as we just use [u8; 32] directly
193+
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
194+
pub struct InterceptId(pub [u8; 32]);
195+
196+
impl Writeable for InterceptId {
197+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
198+
self.0.write(w)
199+
}
200+
}
201+
202+
impl Readable for InterceptId {
203+
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
204+
let buf: [u8; 32] = Readable::read(r)?;
205+
Ok(InterceptId(buf))
206+
}
207+
}
208+
191209
/// A payment identifier used to uniquely identify a payment to LDK.
192210
/// (C-not exported) as we just use [u8; 32] directly
193211
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)]
@@ -722,6 +740,10 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
722740
/// Locked *after* channel_state.
723741
pending_outbound_payments: Mutex<HashMap<PaymentId, PendingOutboundPayment>>,
724742

743+
/// Storage for HTLCForwardInfo's that have been intercepted and bubbled up to the user.
744+
/// We hold them here until the user tells us what we should to with them.
745+
pending_intercepted_payments: Mutex<HashMap<InterceptId, HTLCForwardInfo>>,
746+
725747
/// The set of outbound SCID aliases across all our channels, including unconfirmed channels
726748
/// and some closed channels which reached a usable state prior to being closed. This is used
727749
/// only to avoid duplicates, and is not persisted explicitly to disk, but rebuilt from the
@@ -1586,6 +1608,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
15861608
outbound_scid_aliases: Mutex::new(HashSet::new()),
15871609
pending_inbound_payments: Mutex::new(HashMap::new()),
15881610
pending_outbound_payments: Mutex::new(HashMap::new()),
1611+
pending_intercepted_payments: Mutex::new(HashMap::new()),
15891612

15901613
our_network_key: keys_manager.get_node_secret(Recipient::Node).unwrap(),
15911614
our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &keys_manager.get_node_secret(Recipient::Node).unwrap()),
@@ -3055,6 +3078,59 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30553078
Ok(())
30563079
}
30573080

3081+
/// Fails the intercepted payment indicated by intercept_id. This should really only be called in response
3082+
/// to a PaymentIntercepted event
3083+
pub fn fail_intercepted_payment(&self, intercept_id: InterceptId) {
3084+
let pending_intercept = {
3085+
let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap();
3086+
pending_intercepts.remove(&intercept_id)
3087+
};
3088+
3089+
if let Some(_payment) = pending_intercept {
3090+
// TODO: what's best way to fail this? in `process_pending_htlc_forwards` it uses that `fail_forward` macro
3091+
// awkward way could be to just use the forward_intercepted_payment code and pick a random scid
3092+
}
3093+
}
3094+
3095+
/// Attempts to forward an intercepted payment over the provided scid and with the provided amt_to_forward.
3096+
/// Should only really be called in response to a PaymentIntercepted event
3097+
pub fn forward_intercepted_payment(&self, intercept_id: InterceptId, scid: u64, amt_to_forward: u64) -> Result<(), APIError> {
3098+
let pending_intercept = {
3099+
let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap();
3100+
pending_intercepts.remove(&intercept_id)
3101+
};
3102+
3103+
match pending_intercept {
3104+
None => Err(APIError::APIMisuseError { err: "Payment with that InterceptId not found".to_string() }),
3105+
Some(payment) => {
3106+
match payment {
3107+
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info, prev_funding_outpoint } => {
3108+
3109+
let routing = match forward_info.routing {
3110+
PendingHTLCRouting::Forward { onion_packet, .. } => {
3111+
PendingHTLCRouting::Forward { onion_packet, short_channel_id: scid }
3112+
},
3113+
_ => forward_info.routing
3114+
};
3115+
3116+
let pending_htlc_info = PendingHTLCInfo {
3117+
amt_to_forward,
3118+
routing,
3119+
..forward_info
3120+
};
3121+
3122+
let mut per_source_pending_forward = vec![(prev_short_channel_id, prev_funding_outpoint, vec![(pending_htlc_info, prev_htlc_id)])];
3123+
3124+
self.forward_htlcs(&mut per_source_pending_forward);
3125+
3126+
Ok(())
3127+
},
3128+
_ => Err(APIError::APIMisuseError { err: "impossible".to_string() })
3129+
}
3130+
}
3131+
}
3132+
}
3133+
30583134
/// Processes HTLCs which are pending waiting on random forward delay.
30593135
///
30603136
/// Should only really ever be called in response to a PendingHTLCsForwardable event.
@@ -3078,7 +3154,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30783154
for forward_info in pending_forwards.drain(..) {
30793155
match forward_info {
30803156
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
3081-
routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
3157+
ref routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
30823158
prev_funding_outpoint } => {
30833159
macro_rules! fail_forward {
30843160
($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr) => {
@@ -3125,6 +3201,24 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
31253201
},
31263202
_ => panic!(),
31273203
}
3204+
} else if fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, short_chan_id) {
3205+
let intercept_id = InterceptId(Sha256::hash(&incoming_shared_secret).into_inner());
3206+
let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap();
3207+
match pending_intercepts.entry(intercept_id) {
3208+
hash_map::Entry::Vacant(entry) => {
3209+
entry.insert(forward_info);
3210+
new_events.push(events::Event::PaymentIntercepted {
3211+
short_channel_id: short_chan_id,
3212+
payment_hash,
3213+
inbound_amount_msats: 0,
3214+
expected_outbound_amount_msats: amt_to_forward,
3215+
intercept_id
3216+
});
3217+
},
3218+
hash_map::Entry::Occupied(_) => {
3219+
fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
3220+
}
3221+
}
31283222
} else {
31293223
fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
31303224
}
@@ -5494,6 +5588,21 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
54945588
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
54955589
}
54965590

5591+
/// Gets a fake short channel id for use in receiving [intercepted payments]. These fake scids
5592+
/// are used when constructing the route hints for payments intended to be intercepted.
5593+
pub fn get_intercept_scid(&self) -> u64 {
5594+
let mut channel_state = self.channel_state.lock().unwrap();
5595+
let best_block = self.best_block.read().unwrap();
5596+
loop {
5597+
let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block.height(), &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager);
5598+
// Ensure the generated scid doesn't conflict with a real channel.
5599+
match channel_state.short_to_id.entry(scid_candidate) {
5600+
hash_map::Entry::Occupied(_) => continue,
5601+
hash_map::Entry::Vacant(_) => return scid_candidate
5602+
}
5603+
}
5604+
}
5605+
54975606
/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
54985607
/// are used when constructing the phantom invoice's route hints.
54995608
///
@@ -7209,6 +7318,7 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
72097318
inbound_payment_key: expanded_inbound_key,
72107319
pending_inbound_payments: Mutex::new(pending_inbound_payments),
72117320
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
7321+
pending_intercepted_payments: Mutex::new(HashMap::new()),
72127322

72137323
outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
72147324
fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),

lightning/src/util/events.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ use core::time::Duration;
3636
use core::ops::Deref;
3737
use sync::Arc;
3838

39+
use crate::ln::channelmanager::InterceptId;
40+
3941
/// Some information provided on receipt of payment depends on whether the payment received is a
4042
/// spontaneous payment or a "conventional" lightning payment that's paying an invoice.
4143
#[derive(Clone, Debug)]
@@ -425,6 +427,26 @@ pub enum Event {
425427
/// now + 5*time_forwardable).
426428
time_forwardable: Duration,
427429
},
430+
/// Used to indicate that the short_channel_id a payment was intended to be routed over signaled
431+
/// for interception. If this payment is to be rerouted you must call [`ChannelManager::forward_intercepted_payment`] with
432+
/// the new short_channel_id and payment amount. If this payment should be failed you must call
433+
/// [`ChannelManager::fail_intercepted_payment`].
434+
///
435+
/// [`ChannelManager::forward_intercepted_payment`]: crate::ln::channelmanager::ChannelManager::forward_intercepted_payment
436+
/// [`ChannelManager::fail_intercepted_payment`]: crate::ln::channelmanager::ChannelManager::fail_intercepted_payment
437+
PaymentIntercepted {
438+
/// The scid that indicated this payment should be intercepted
439+
short_channel_id: u64,
440+
/// The payment hash used for this payment
441+
payment_hash: PaymentHash,
442+
/// How many msats are to be received on the inbound edge of this payment
443+
inbound_amount_msats: u64,
444+
/// How many msats the payer intended to route to the next node. Depending on the reason you are
445+
/// intercepting this payment, you might take a fee by forwarding less than this amount
446+
expected_outbound_amount_msats: u64,
447+
/// A id to help LDK identify which payment is being forwarded or failed
448+
intercept_id: InterceptId
449+
},
428450
/// Used to indicate that an output which you should know how to spend was confirmed on chain
429451
/// and is now spendable.
430452
/// Such an output will *not* ever be spent by rust-lightning, and are not at risk of your
@@ -684,6 +706,16 @@ impl Writeable for Event {
684706
(6, short_channel_id, option),
685707
})
686708
},
709+
&Event::PaymentIntercepted { short_channel_id, payment_hash, inbound_amount_msats, expected_outbound_amount_msats, intercept_id } => {
710+
25u8.write(writer)?;
711+
write_tlv_fields!(writer, {
712+
(0, short_channel_id, required),
713+
(2, payment_hash, required),
714+
(4, inbound_amount_msats, required),
715+
(6, expected_outbound_amount_msats, required),
716+
(8, intercept_id, required)
717+
});
718+
}
687719
// Note that, going forward, all new events must only write data inside of
688720
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
689721
// data via `write_tlv_fields`.
@@ -942,6 +974,30 @@ impl MaybeReadable for Event {
942974
};
943975
f()
944976
},
977+
25u8 => {
978+
let f = || {
979+
let mut payment_hash = PaymentHash([0; 32]);
980+
let mut intercept_id = InterceptId([0; 32]);
981+
let mut short_channel_id = 0;
982+
let mut inbound_amount_msats = 0;
983+
let mut expected_outbound_amount_msats = 0;
984+
read_tlv_fields!(reader, {
985+
(0, short_channel_id, required),
986+
(2, payment_hash, required),
987+
(4, inbound_amount_msats, required),
988+
(6, expected_outbound_amount_msats, required),
989+
(8, intercept_id, required)
990+
});
991+
Ok(Some(Event::PaymentIntercepted {
992+
payment_hash,
993+
short_channel_id,
994+
inbound_amount_msats,
995+
expected_outbound_amount_msats,
996+
intercept_id,
997+
}))
998+
};
999+
f()
1000+
},
9451001
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
9461002
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
9471003
// reads.

lightning/src/util/scid_utils.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub fn scid_from_parts(block: u64, tx_index: u64, vout_index: u64) -> Result<u64
6363
/// LDK has multiple reasons to generate fake short channel ids:
6464
/// 1) outbound SCID aliases we use for private channels
6565
/// 2) phantom node payments, to get an scid for the phantom node's phantom channel
66+
/// 3) payments intendend to be intercepted will route using a fake scid
6667
pub(crate) mod fake_scid {
6768
use bitcoin::hash_types::BlockHash;
6869
use bitcoin::hashes::hex::FromHex;
@@ -91,6 +92,7 @@ pub(crate) mod fake_scid {
9192
pub(crate) enum Namespace {
9293
Phantom,
9394
OutboundAlias,
95+
Intercept
9496
}
9597

9698
impl Namespace {
@@ -150,7 +152,7 @@ pub(crate) mod fake_scid {
150152
}
151153
}
152154

153-
/// Returns whether the given fake scid falls into the given namespace.
155+
/// Returns whether the given fake scid falls into the phantom namespace.
154156
pub fn is_valid_phantom(fake_scid_rand_bytes: &[u8; 32], scid: u64) -> bool {
155157
let block_height = scid_utils::block_from_scid(&scid);
156158
let tx_index = scid_utils::tx_index_from_scid(&scid);
@@ -159,6 +161,17 @@ pub(crate) mod fake_scid {
159161
valid_vout == scid_utils::vout_from_scid(&scid) as u8
160162
}
161163

164+
/// Returns whether the given fake scid falls into the intercept namespace.
165+
pub fn is_valid_intercept(fake_scid_rand_bytes: &[u8; 32], scid: u64) -> bool {
166+
let block_height = scid_utils::block_from_scid(&scid);
167+
let tx_index = scid_utils::tx_index_from_scid(&scid);
168+
let namespace = Namespace::Intercept;
169+
let valid_vout = namespace.get_encrypted_vout(block_height, tx_index, fake_scid_rand_bytes);
170+
valid_vout == scid_utils::vout_from_scid(&scid) as u8
171+
}
172+
173+
174+
162175
#[cfg(test)]
163176
mod tests {
164177
use bitcoin::blockdata::constants::genesis_block;
@@ -168,6 +181,8 @@ pub(crate) mod fake_scid {
168181
use util::test_utils;
169182
use sync::Arc;
170183

184+
use crate::util::scid_utils::fake_scid::is_valid_intercept;
185+
171186
#[test]
172187
fn namespace_identifier_is_within_range() {
173188
let phantom_namespace = Namespace::Phantom;
@@ -201,6 +216,17 @@ pub(crate) mod fake_scid {
201216
assert!(!is_valid_phantom(&fake_scid_rand_bytes, invalid_fake_scid));
202217
}
203218

219+
#[test]
220+
fn test_is_valid_intercept() {
221+
let namespace = Namespace::Intercept;
222+
let fake_scid_rand_bytes = [0; 32];
223+
let valid_encrypted_vout = namespace.get_encrypted_vout(0, 0, &fake_scid_rand_bytes);
224+
let valid_fake_scid = scid_utils::scid_from_parts(0, 0, valid_encrypted_vout as u64).unwrap();
225+
assert!(is_valid_intercept(&fake_scid_rand_bytes, valid_fake_scid));
226+
let invalid_fake_scid = scid_utils::scid_from_parts(0, 0, 12).unwrap();
227+
assert!(!is_valid_intercept(&fake_scid_rand_bytes, invalid_fake_scid));
228+
}
229+
204230
#[test]
205231
fn test_get_fake_scid() {
206232
let mainnet_genesis = genesis_block(Network::Bitcoin).header.block_hash();

0 commit comments

Comments
 (0)