Skip to content

Commit 4793c80

Browse files
committed
Add send_probe and introduce probing cookies
When we send payment probes, we generate the [`PaymentHash`] based on a probing cookie secret and a random [`PaymentId`]. This allows us to discern probes from real payments, without keeping additional state.
1 parent 22de0ae commit 4793c80

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,11 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
749749
/// [fake scids]: crate::util::scid_utils::fake_scid
750750
fake_scid_rand_bytes: [u8; 32],
751751

752+
/// When we send payment probes, we generate the [`PaymentHash`] based on this cookie secret
753+
/// and a random [`PaymentId`]. This allows us to discern probes from real payments, without
754+
/// keeping additional state.
755+
probing_cookie_secret: [u8; 32],
756+
752757
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
753758
/// value increases strictly since we don't assume access to a time source.
754759
last_node_announcement_serial: AtomicUsize,
@@ -1605,6 +1610,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
16051610
inbound_payment_key: expanded_inbound_key,
16061611
fake_scid_rand_bytes: keys_manager.get_secure_random_bytes(),
16071612

1613+
probing_cookie_secret: keys_manager.get_secure_random_bytes(),
1614+
16081615
last_node_announcement_serial: AtomicUsize::new(0),
16091616
highest_seen_timestamp: AtomicUsize::new(0),
16101617

@@ -2722,6 +2729,49 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27222729
}
27232730
}
27242731

2732+
/// Send a payment that is probing the given route for liquidity. We calculate the
2733+
/// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
2734+
/// us to easily discern them from real payments. This can be checked by calling
2735+
/// [`payment_is_probe`].
2736+
///
2737+
/// [`payment_is_probe`]: Self::payment_is_probe
2738+
pub fn send_probe_payment(&self, route: &Route) -> Result<(PaymentHash, PaymentId), PaymentSendFailure> {
2739+
let payment_id = PaymentId(self.keys_manager.get_secure_random_bytes());
2740+
2741+
let mut preimage = [0u8; 32];
2742+
preimage[..16].copy_from_slice(&self.probing_cookie_secret[..16]);
2743+
preimage[16..].copy_from_slice(&payment_id.0[..16]);
2744+
2745+
let payment_hash = PaymentHash(Sha256::hash(&preimage).into_inner());
2746+
2747+
if let Some(params) = &route.payment_params {
2748+
if params.max_path_count != 1 {
2749+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2750+
err: "Probe payments need to be sent over a single path".to_string()
2751+
}))
2752+
}
2753+
} else {
2754+
return Err(PaymentSendFailure::ParameterError(APIError::APIMisuseError {
2755+
err: "No parameters provided".to_string()
2756+
}))
2757+
}
2758+
2759+
match self.send_payment_internal(route, payment_hash, &None, None, Some(payment_id), None) {
2760+
Ok(payment_id) => Ok((payment_hash, payment_id)),
2761+
Err(e) => Err(e)
2762+
}
2763+
}
2764+
2765+
/// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
2766+
/// payment probe.
2767+
pub fn payment_is_probe(&self, payment_hash: PaymentHash, payment_id: PaymentId) -> bool {
2768+
let mut preimage = [0u8; 32];
2769+
preimage[..16].copy_from_slice(&self.probing_cookie_secret[..16]);
2770+
preimage[16..].copy_from_slice(&payment_id.0[..16]);
2771+
let target_payment_hash = PaymentHash(Sha256::hash(&preimage).into_inner());
2772+
target_payment_hash == payment_hash
2773+
}
2774+
27252775
/// Handles the generation of a funding transaction, optionally (for tests) with a function
27262776
/// which checks the correctness of the funding transaction given the associated channel.
27272777
fn funding_transaction_generated_intern<FundingOutput: Fn(&Channel<Signer>, &Transaction) -> Result<OutPoint, APIError>>(
@@ -6623,6 +6673,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
66236673
(5, self.our_network_pubkey, required),
66246674
(7, self.fake_scid_rand_bytes, required),
66256675
(9, htlc_purposes, vec_type),
6676+
(11, self.probing_cookie_secret, required),
66266677
});
66276678

66286679
Ok(())
@@ -6919,18 +6970,24 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
69196970
let mut pending_outbound_payments = None;
69206971
let mut received_network_pubkey: Option<PublicKey> = None;
69216972
let mut fake_scid_rand_bytes: Option<[u8; 32]> = None;
6973+
let mut probing_cookie_secret: Option<[u8; 32]> = None;
69226974
let mut claimable_htlc_purposes = None;
69236975
read_tlv_fields!(reader, {
69246976
(1, pending_outbound_payments_no_retry, option),
69256977
(3, pending_outbound_payments, option),
69266978
(5, received_network_pubkey, option),
69276979
(7, fake_scid_rand_bytes, option),
69286980
(9, claimable_htlc_purposes, vec_type),
6981+
(11, probing_cookie_secret, option),
69296982
});
69306983
if fake_scid_rand_bytes.is_none() {
69316984
fake_scid_rand_bytes = Some(args.keys_manager.get_secure_random_bytes());
69326985
}
69336986

6987+
if probing_cookie_secret.is_none() {
6988+
probing_cookie_secret = Some(args.keys_manager.get_secure_random_bytes());
6989+
}
6990+
69346991
if pending_outbound_payments.is_none() && pending_outbound_payments_no_retry.is_none() {
69356992
pending_outbound_payments = Some(pending_outbound_payments_compat);
69366993
} else if pending_outbound_payments.is_none() {
@@ -7136,6 +7193,8 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
71367193
outbound_scid_aliases: Mutex::new(outbound_scid_aliases),
71377194
fake_scid_rand_bytes: fake_scid_rand_bytes.unwrap(),
71387195

7196+
probing_cookie_secret: probing_cookie_secret.unwrap(),
7197+
71397198
our_network_key,
71407199
our_network_pubkey,
71417200
secp_ctx,

0 commit comments

Comments
 (0)