Skip to content

Commit dfa45d6

Browse files
add ability to intercept htlcs
1 parent e254912 commit dfa45d6

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 107 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
@@ -1581,6 +1603,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
15811603
outbound_scid_aliases: Mutex::new(HashSet::new()),
15821604
pending_inbound_payments: Mutex::new(HashMap::new()),
15831605
pending_outbound_payments: Mutex::new(HashMap::new()),
1606+
pending_intercepted_payments: Mutex::new(HashMap::new()),
15841607

15851608
our_network_key: keys_manager.get_node_secret(Recipient::Node).unwrap(),
15861609
our_network_pubkey: PublicKey::from_secret_key(&secp_ctx, &keys_manager.get_node_secret(Recipient::Node).unwrap()),
@@ -3011,6 +3034,55 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30113034
Ok(())
30123035
}
30133036

3037+
pub fn fail_intercepted_payment(&self, intercept_id: InterceptId) {
3038+
let pending_intercept = {
3039+
let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap();
3040+
pending_intercepts.remove(&intercept_id)
3041+
};
3042+
3043+
if let Some(_payment) = pending_intercept {
3044+
// TODO: what's best way to fail this? in `process_pending_htlc_forwards` it uses that `fail_forward` macro
3045+
// awkward way could be to just use the forward_intercepted_payment code and pick a random scid
3046+
}
3047+
}
3048+
3049+
pub fn forward_intercepted_payment(&self, intercept_id: InterceptId, scid: u64, amt_to_forward: u64) -> Result<(), APIError> {
3050+
let pending_intercept = {
3051+
let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap();
3052+
pending_intercepts.remove(&intercept_id)
3053+
};
3054+
3055+
match pending_intercept {
3056+
None => Err(APIError::APIMisuseError { err: "Payment with that InterceptId not found".to_string() }),
3057+
Some(payment) => {
3058+
match payment {
3059+
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info, prev_funding_outpoint } => {
3060+
3061+
let routing = match forward_info.routing {
3062+
PendingHTLCRouting::Forward { onion_packet, .. } => {
3063+
PendingHTLCRouting::Forward { onion_packet, short_channel_id: scid }
3064+
},
3065+
_ => forward_info.routing
3066+
};
3067+
3068+
let pending_htlc_info = PendingHTLCInfo {
3069+
amt_to_forward,
3070+
routing,
3071+
..forward_info
3072+
};
3073+
3074+
let mut per_source_pending_forward = vec![(prev_short_channel_id, prev_funding_outpoint, vec![(pending_htlc_info, prev_htlc_id)])];
3075+
3076+
self.forward_htlcs(&mut per_source_pending_forward);
3077+
3078+
Ok(())
3079+
},
3080+
_ => Err(APIError::APIMisuseError { err: "impossible".to_string() })
3081+
}
3082+
}
3083+
}
3084+
}
3085+
30143086
/// Processes HTLCs which are pending waiting on random forward delay.
30153087
///
30163088
/// Should only really ever be called in response to a PendingHTLCsForwardable event.
@@ -3034,7 +3106,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30343106
for forward_info in pending_forwards.drain(..) {
30353107
match forward_info {
30363108
HTLCForwardInfo::AddHTLC { prev_short_channel_id, prev_htlc_id, forward_info: PendingHTLCInfo {
3037-
routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
3109+
ref routing, incoming_shared_secret, payment_hash, amt_to_forward, outgoing_cltv_value },
30383110
prev_funding_outpoint } => {
30393111
macro_rules! fail_forward {
30403112
($msg: expr, $err_code: expr, $err_data: expr, $phantom_ss: expr) => {
@@ -3081,6 +3153,24 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
30813153
},
30823154
_ => panic!(),
30833155
}
3156+
} else if fake_scid::is_valid_intercept(&self.fake_scid_rand_bytes, short_chan_id) {
3157+
let intercept_id = InterceptId(Sha256::hash(&incoming_shared_secret).into_inner());
3158+
let mut pending_intercepts = self.pending_intercepted_payments.lock().unwrap();
3159+
match pending_intercepts.entry(intercept_id) {
3160+
hash_map::Entry::Vacant(entry) => {
3161+
entry.insert(forward_info);
3162+
new_events.push(events::Event::PaymentIntercepted {
3163+
short_channel_id: short_chan_id,
3164+
payment_hash,
3165+
inbound_amount_msats: 0,
3166+
expected_outbound_amount_msats: amt_to_forward,
3167+
intercept_id
3168+
});
3169+
},
3170+
hash_map::Entry::Occupied(_) => {
3171+
fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
3172+
}
3173+
}
30843174
} else {
30853175
fail_forward!(format!("Unknown short channel id {} for forward HTLC", short_chan_id), 0x4000 | 10, Vec::new(), None);
30863176
}
@@ -5432,6 +5522,21 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
54325522
inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key)
54335523
}
54345524

5525+
/// Gets a fake short channel id for use in receiving [intercepted payments]. These fake scids
5526+
/// are used when constructing the route hints for payments intended to be intercepted.
5527+
pub fn get_intercept_scid(&self) -> u64 {
5528+
let mut channel_state = self.channel_state.lock().unwrap();
5529+
let best_block = self.best_block.read().unwrap();
5530+
loop {
5531+
let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block.height(), &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager);
5532+
// Ensure the generated scid doesn't conflict with a real channel.
5533+
match channel_state.short_to_id.entry(scid_candidate) {
5534+
hash_map::Entry::Occupied(_) => continue,
5535+
hash_map::Entry::Vacant(_) => return scid_candidate
5536+
}
5537+
}
5538+
}
5539+
54355540
/// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids
54365541
/// are used when constructing the phantom invoice's route hints.
54375542
///
@@ -7140,6 +7245,7 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
71407245
inbound_payment_key: expanded_inbound_key,
71417246
pending_inbound_payments: Mutex::new(pending_inbound_payments),
71427247
pending_outbound_payments: Mutex::new(pending_outbound_payments.unwrap()),
7248+
pending_intercepted_payments: Mutex::new(HashMap::new()),
71437249

71447250
outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
71457251
fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),

lightning/src/util/events.rs

Lines changed: 19 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)]
@@ -393,6 +395,13 @@ pub enum Event {
393395
/// now + 5*time_forwardable).
394396
time_forwardable: Duration,
395397
},
398+
PaymentIntercepted {
399+
short_channel_id: u64,
400+
payment_hash: PaymentHash,
401+
inbound_amount_msats: u64,
402+
expected_outbound_amount_msats: u64,
403+
intercept_id: InterceptId
404+
},
396405
/// Used to indicate that an output which you should know how to spend was confirmed on chain
397406
/// and is now spendable.
398407
/// Such an output will *not* ever be spent by rust-lightning, and are not at risk of your
@@ -635,6 +644,16 @@ impl Writeable for Event {
635644
(4, amount_msat, required),
636645
});
637646
},
647+
&Event::PaymentIntercepted { short_channel_id, payment_hash, inbound_amount_msats, expected_outbound_amount_msats, intercept_id } => {
648+
21u8.write(writer)?;
649+
write_tlv_fields!(writer, {
650+
(0, short_channel_id, required),
651+
(2, payment_hash, required),
652+
(4, inbound_amount_msats, required),
653+
(6, expected_outbound_amount_msats, required),
654+
(8, intercept_id, required)
655+
});
656+
}
638657
// Note that, going forward, all new events must only write data inside of
639658
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
640659
// data via `write_tlv_fields`.

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)