Skip to content

Commit d10f48c

Browse files
committed
Add new payment type and metadata bytes
Adds two new payment `Method`s for identifying payments with custom `min_final_cltv_expiry_delta` as payments with LDK or user payment hashes. The `min_final_cltv_expiry_delta` value is packed into the metadata bytes of the payment secret, taking up 12 bits.
1 parent 5d1dd58 commit d10f48c

File tree

9 files changed

+249
-41
lines changed

9 files changed

+249
-41
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ fn get_payment_secret_hash(dest: &ChanMan, payment_id: &mut u8) -> Option<(Payme
333333
let mut payment_hash;
334334
for _ in 0..256 {
335335
payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).into_inner());
336-
if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600) {
336+
if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) {
337337
return Some((payment_secret, payment_hash));
338338
}
339339
*payment_id = payment_id.wrapping_add(1);

fuzz/src/full_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
599599
let payment_hash = PaymentHash(Sha256::from_engine(sha).into_inner());
600600
// Note that this may fail - our hashes may collide and we'll end up trying to
601601
// double-register the same payment_hash.
602-
let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1);
602+
let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None);
603603
},
604604
9 => {
605605
for payment in payments_received.drain(..) {

lightning-invoice/src/utils.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ where
160160
.duration_since(UNIX_EPOCH)
161161
.expect("Time must be > 1970")
162162
.as_secs(),
163+
min_final_cltv_expiry_delta,
163164
)
164165
.map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
165166
(payment_hash, payment_secret)
@@ -173,6 +174,7 @@ where
173174
.duration_since(UNIX_EPOCH)
174175
.expect("Time must be > 1970")
175176
.as_secs(),
177+
min_final_cltv_expiry_delta,
176178
)
177179
.map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
178180
};
@@ -382,7 +384,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Der
382384
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
383385
// supply.
384386
let (payment_hash, payment_secret) = channelmanager
385-
.create_inbound_payment(amt_msat, invoice_expiry_delta_secs)
387+
.create_inbound_payment(amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta)
386388
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
387389
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
388390
channelmanager, node_signer, logger, network, amt_msat, description, duration_since_epoch,
@@ -409,7 +411,8 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_
409411
L::Target: Logger,
410412
{
411413
let payment_secret = channelmanager
412-
.create_inbound_payment_for_hash(payment_hash,amt_msat, invoice_expiry_delta_secs)
414+
.create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs,
415+
min_final_cltv_expiry_delta)
413416
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
414417
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
415418
channelmanager, node_signer, logger, network, amt_msat,
@@ -440,6 +443,10 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_has
440443
let our_node_pubkey = channelmanager.get_our_node_id();
441444
let channels = channelmanager.list_channels();
442445

446+
if min_final_cltv_expiry_delta.is_some() && min_final_cltv_expiry_delta.unwrap() < MIN_FINAL_CLTV_EXPIRY_DELTA {
447+
return Err(SignOrCreationError::CreationError(CreationError::MinFinalCltvExpiryDeltaTooShort));
448+
}
449+
443450
log_trace!(logger, "Creating invoice with payment hash {}", log_bytes!(payment_hash.0));
444451

445452
let invoice = match description {
@@ -742,6 +749,22 @@ mod test {
742749
assert_eq!(events.len(), 2);
743750
}
744751

752+
#[test]
753+
fn test_create_invoice_with_custom_min_final_cltv_delta() {
754+
let chanmon_cfgs = create_chanmon_cfgs(2);
755+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
756+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
757+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
758+
let description_hash = crate::Sha256(Hash::hash("Testing description_hash".as_bytes()));
759+
let invoice = crate::utils::create_invoice_from_channelmanager_with_description_hash_and_duration_since_epoch(
760+
&nodes[1].node, nodes[1].keys_manager, nodes[1].logger, Currency::BitcoinTestnet,
761+
Some(10_000), description_hash, Duration::from_secs(1234567), 3600, None,
762+
).unwrap();
763+
assert_eq!(invoice.amount_pico_btc(), Some(100_000));
764+
assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64);
765+
assert_eq!(invoice.description(), InvoiceDescription::Hash(&crate::Sha256(Sha256::hash("Testing description_hash".as_bytes()))));
766+
}
767+
745768
#[test]
746769
fn test_create_invoice_with_description_hash() {
747770
let chanmon_cfgs = create_chanmon_cfgs(2);
@@ -1113,7 +1136,7 @@ mod test {
11131136
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001);
11141137

11151138
let payment_amt = 20_000;
1116-
let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
1139+
let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600, None).unwrap();
11171140
let route_hints = vec![
11181141
nodes[1].node.get_phantom_route_hints(),
11191142
nodes[2].node.get_phantom_route_hints(),

lightning/src/ln/channelmanager.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,7 @@ where
19181918
// final_expiry_too_soon
19191919
// We have to have some headroom to broadcast on chain if we have the preimage, so make sure
19201920
// we have at least HTLC_FAIL_BACK_BUFFER blocks to go.
1921+
//
19211922
// Also, ensure that, in the case of an unknown preimage for the received payment hash, our
19221923
// payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
19231924
// channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
@@ -3184,10 +3185,21 @@ where
31843185
let payment_preimage = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
31853186
Ok(payment_preimage) => payment_preimage,
31863187
Err(()) => {
3188+
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as payment verification failed", log_bytes!(payment_hash.0));
31873189
fail_htlc!(claimable_htlc, payment_hash);
31883190
continue
31893191
}
31903192
};
3193+
if let Some(min_final_cltv_expiry_delta) = inbound_payment::get_custom_min_final_cltv_expiry_delta(
3194+
payment_data.payment_secret, &self.inbound_payment_key) {
3195+
let expected_min_expiry_height = (self.current_best_block().height() + min_final_cltv_expiry_delta as u32) as u64;
3196+
if (cltv_expiry as u64) < expected_min_expiry_height {
3197+
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as its CLTV expiry was too soon (had {}, earliest expected {})",
3198+
log_bytes!(payment_hash.0), cltv_expiry, expected_min_expiry_height);
3199+
fail_htlc!(claimable_htlc, payment_hash);
3200+
continue;
3201+
}
3202+
}
31913203
check_total_value!(payment_data, payment_preimage);
31923204
},
31933205
OnionPayload::Spontaneous(preimage) => {
@@ -5270,12 +5282,18 @@ where
52705282
///
52715283
/// Errors if `min_value_msat` is greater than total bitcoin supply.
52725284
///
5285+
/// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
5286+
/// on versions of LDK prior to 0.0.114.
5287+
///
52735288
/// [`claim_funds`]: Self::claim_funds
52745289
/// [`PaymentClaimable`]: events::Event::PaymentClaimable
52755290
/// [`PaymentClaimable::payment_preimage`]: events::Event::PaymentClaimable::payment_preimage
52765291
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
5277-
pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<(PaymentHash, PaymentSecret), ()> {
5278-
inbound_payment::create(&self.inbound_payment_key, min_value_msat, invoice_expiry_delta_secs, &self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64)
5292+
pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32,
5293+
min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()> {
5294+
inbound_payment::create(&self.inbound_payment_key, min_value_msat, invoice_expiry_delta_secs,
5295+
&self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
5296+
min_final_cltv_expiry_delta)
52795297
}
52805298

52815299
/// Legacy version of [`create_inbound_payment`]. Use this method if you wish to share
@@ -5336,10 +5354,16 @@ where
53365354
///
53375355
/// Errors if `min_value_msat` is greater than total bitcoin supply.
53385356
///
5357+
/// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
5358+
/// on versions of LDK prior to 0.0.114.
5359+
///
53395360
/// [`create_inbound_payment`]: Self::create_inbound_payment
53405361
/// [`PaymentClaimable`]: events::Event::PaymentClaimable
5341-
pub fn create_inbound_payment_for_hash(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<PaymentSecret, ()> {
5342-
inbound_payment::create_from_hash(&self.inbound_payment_key, min_value_msat, payment_hash, invoice_expiry_delta_secs, self.highest_seen_timestamp.load(Ordering::Acquire) as u64)
5362+
pub fn create_inbound_payment_for_hash(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>,
5363+
invoice_expiry_delta_secs: u32, min_final_cltv_expiry: Option<u16>) -> Result<PaymentSecret, ()> {
5364+
inbound_payment::create_from_hash(&self.inbound_payment_key, min_value_msat, payment_hash,
5365+
invoice_expiry_delta_secs, self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
5366+
min_final_cltv_expiry)
53435367
}
53445368

53455369
/// Legacy version of [`create_inbound_payment_for_hash`]. Use this method if you wish to share
@@ -8459,7 +8483,7 @@ pub mod bench {
84598483
payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes());
84608484
payment_count += 1;
84618485
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
8462-
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200).unwrap();
8486+
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
84638487

84648488
$node_a.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
84658489
let payment_event = SendEvent::from_event($node_a.get_and_clear_pending_msg_events().pop().unwrap());

lightning/src/ln/functional_test_utils.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,17 +1437,22 @@ macro_rules! get_payment_preimage_hash {
14371437
}
14381438
};
14391439
($dest_node: expr, $min_value_msat: expr) => {
1440+
{
1441+
crate::get_payment_preimage_hash!($dest_node, $min_value_msat, None)
1442+
}
1443+
};
1444+
($dest_node: expr, $min_value_msat: expr, $min_final_cltv_expiry_delta: expr) => {
14401445
{
14411446
use bitcoin::hashes::Hash as _;
14421447
let mut payment_count = $dest_node.network_payment_count.borrow_mut();
14431448
let payment_preimage = $crate::ln::PaymentPreimage([*payment_count; 32]);
14441449
*payment_count += 1;
14451450
let payment_hash = $crate::ln::PaymentHash(
14461451
bitcoin::hashes::sha256::Hash::hash(&payment_preimage.0[..]).into_inner());
1447-
let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200).unwrap();
1452+
let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200, $min_final_cltv_expiry_delta).unwrap();
14481453
(payment_preimage, payment_hash, payment_secret)
14491454
}
1450-
}
1455+
};
14511456
}
14521457

14531458
#[macro_export]

0 commit comments

Comments
 (0)