mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-15 15:39:09 +01:00
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 first 2 bytes of the expiry timestamp in the payment secret metadata.
This commit is contained in:
parent
1d72e87e78
commit
5b53670172
9 changed files with 210 additions and 46 deletions
|
@ -340,7 +340,7 @@ fn get_payment_secret_hash(dest: &ChanMan, payment_id: &mut u8) -> Option<(Payme
|
|||
let mut payment_hash;
|
||||
for _ in 0..256 {
|
||||
payment_hash = PaymentHash(Sha256::hash(&[*payment_id; 1]).into_inner());
|
||||
if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600) {
|
||||
if let Ok(payment_secret) = dest.create_inbound_payment_for_hash(payment_hash, None, 3600, None) {
|
||||
return Some((payment_secret, payment_hash));
|
||||
}
|
||||
*payment_id = payment_id.wrapping_add(1);
|
||||
|
|
|
@ -605,7 +605,7 @@ pub fn do_test(data: &[u8], logger: &Arc<dyn Logger>) {
|
|||
let payment_hash = PaymentHash(Sha256::from_engine(sha).into_inner());
|
||||
// Note that this may fail - our hashes may collide and we'll end up trying to
|
||||
// double-register the same payment_hash.
|
||||
let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1);
|
||||
let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None);
|
||||
},
|
||||
9 => {
|
||||
for payment in payments_received.drain(..) {
|
||||
|
|
|
@ -166,6 +166,7 @@ where
|
|||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time must be > 1970")
|
||||
.as_secs(),
|
||||
min_final_cltv_expiry_delta,
|
||||
)
|
||||
.map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
|
||||
(payment_hash, payment_secret)
|
||||
|
@ -179,6 +180,7 @@ where
|
|||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time must be > 1970")
|
||||
.as_secs(),
|
||||
min_final_cltv_expiry_delta,
|
||||
)
|
||||
.map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
|
||||
};
|
||||
|
@ -397,7 +399,7 @@ fn _create_invoice_from_channelmanager_and_duration_since_epoch<M: Deref, T: Der
|
|||
// `create_inbound_payment` only returns an error if the amount is greater than the total bitcoin
|
||||
// supply.
|
||||
let (payment_hash, payment_secret) = channelmanager
|
||||
.create_inbound_payment(amt_msat, invoice_expiry_delta_secs)
|
||||
.create_inbound_payment(amt_msat, invoice_expiry_delta_secs, min_final_cltv_expiry_delta)
|
||||
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
|
||||
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
|
||||
channelmanager, node_signer, logger, network, amt_msat, description, duration_since_epoch,
|
||||
|
@ -424,7 +426,8 @@ pub fn create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_
|
|||
L::Target: Logger,
|
||||
{
|
||||
let payment_secret = channelmanager
|
||||
.create_inbound_payment_for_hash(payment_hash,amt_msat, invoice_expiry_delta_secs)
|
||||
.create_inbound_payment_for_hash(payment_hash, amt_msat, invoice_expiry_delta_secs,
|
||||
min_final_cltv_expiry_delta)
|
||||
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
|
||||
_create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash(
|
||||
channelmanager, node_signer, logger, network, amt_msat,
|
||||
|
@ -1170,7 +1173,7 @@ mod test {
|
|||
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 2, 100000, 10001);
|
||||
|
||||
let payment_amt = 20_000;
|
||||
let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600).unwrap();
|
||||
let (payment_hash, _payment_secret) = nodes[1].node.create_inbound_payment(Some(payment_amt), 3600, None).unwrap();
|
||||
let route_hints = vec![
|
||||
nodes[1].node.get_phantom_route_hints(),
|
||||
nodes[2].node.get_phantom_route_hints(),
|
||||
|
|
|
@ -1914,6 +1914,7 @@ where
|
|||
// final_expiry_too_soon
|
||||
// We have to have some headroom to broadcast on chain if we have the preimage, so make sure
|
||||
// we have at least HTLC_FAIL_BACK_BUFFER blocks to go.
|
||||
//
|
||||
// Also, ensure that, in the case of an unknown preimage for the received payment hash, our
|
||||
// payment logic has enough time to fail the HTLC backward before our onchain logic triggers a
|
||||
// channel closure (see HTLC_FAIL_BACK_BUFFER rationale).
|
||||
|
@ -3181,13 +3182,23 @@ where
|
|||
match claimable_htlc.onion_payload {
|
||||
OnionPayload::Invoice { .. } => {
|
||||
let payment_data = payment_data.unwrap();
|
||||
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) {
|
||||
Ok(payment_preimage) => payment_preimage,
|
||||
let (payment_preimage, min_final_cltv_expiry_delta) = match inbound_payment::verify(payment_hash, &payment_data, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger) {
|
||||
Ok(result) => result,
|
||||
Err(()) => {
|
||||
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as payment verification failed", log_bytes!(payment_hash.0));
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
continue
|
||||
}
|
||||
};
|
||||
if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
|
||||
let expected_min_expiry_height = (self.current_best_block().height() + min_final_cltv_expiry_delta as u32) as u64;
|
||||
if (cltv_expiry as u64) < expected_min_expiry_height {
|
||||
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as its CLTV expiry was too soon (had {}, earliest expected {})",
|
||||
log_bytes!(payment_hash.0), cltv_expiry, expected_min_expiry_height);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
check_total_value!(payment_data, payment_preimage);
|
||||
},
|
||||
OnionPayload::Spontaneous(preimage) => {
|
||||
|
@ -5273,12 +5284,18 @@ where
|
|||
///
|
||||
/// Errors if `min_value_msat` is greater than total bitcoin supply.
|
||||
///
|
||||
/// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
|
||||
/// on versions of LDK prior to 0.0.114.
|
||||
///
|
||||
/// [`claim_funds`]: Self::claim_funds
|
||||
/// [`PaymentClaimable`]: events::Event::PaymentClaimable
|
||||
/// [`PaymentClaimable::payment_preimage`]: events::Event::PaymentClaimable::payment_preimage
|
||||
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
|
||||
pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<(PaymentHash, PaymentSecret), ()> {
|
||||
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)
|
||||
pub fn create_inbound_payment(&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32,
|
||||
min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()> {
|
||||
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,
|
||||
min_final_cltv_expiry_delta)
|
||||
}
|
||||
|
||||
/// Legacy version of [`create_inbound_payment`]. Use this method if you wish to share
|
||||
|
@ -5339,10 +5356,16 @@ where
|
|||
///
|
||||
/// Errors if `min_value_msat` is greater than total bitcoin supply.
|
||||
///
|
||||
/// If `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
|
||||
/// on versions of LDK prior to 0.0.114.
|
||||
///
|
||||
/// [`create_inbound_payment`]: Self::create_inbound_payment
|
||||
/// [`PaymentClaimable`]: events::Event::PaymentClaimable
|
||||
pub fn create_inbound_payment_for_hash(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32) -> Result<PaymentSecret, ()> {
|
||||
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)
|
||||
pub fn create_inbound_payment_for_hash(&self, payment_hash: PaymentHash, min_value_msat: Option<u64>,
|
||||
invoice_expiry_delta_secs: u32, min_final_cltv_expiry: Option<u16>) -> Result<PaymentSecret, ()> {
|
||||
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,
|
||||
min_final_cltv_expiry)
|
||||
}
|
||||
|
||||
/// Legacy version of [`create_inbound_payment_for_hash`]. Use this method if you wish to share
|
||||
|
@ -7369,7 +7392,7 @@ where
|
|||
payment_preimage: match pending_inbound_payments.get(&payment_hash) {
|
||||
Some(inbound_payment) => inbound_payment.payment_preimage,
|
||||
None => match inbound_payment::verify(payment_hash, &hop_data, 0, &expanded_inbound_key, &args.logger) {
|
||||
Ok(payment_preimage) => payment_preimage,
|
||||
Ok((payment_preimage, _)) => payment_preimage,
|
||||
Err(()) => {
|
||||
log_error!(args.logger, "Failed to read claimable payment data for HTLC with payment hash {} - was not a pending inbound payment and didn't match our payment key", log_bytes!(payment_hash.0));
|
||||
return Err(DecodeError::InvalidValue);
|
||||
|
@ -8505,7 +8528,7 @@ pub mod bench {
|
|||
payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes());
|
||||
payment_count += 1;
|
||||
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
|
||||
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200).unwrap();
|
||||
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
|
||||
|
||||
$node_a.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
|
||||
let payment_event = SendEvent::from_event($node_a.get_and_clear_pending_msg_events().pop().unwrap());
|
||||
|
|
|
@ -1447,6 +1447,11 @@ macro_rules! get_payment_preimage_hash {
|
|||
}
|
||||
};
|
||||
($dest_node: expr, $min_value_msat: expr) => {
|
||||
{
|
||||
crate::get_payment_preimage_hash!($dest_node, $min_value_msat, None)
|
||||
}
|
||||
};
|
||||
($dest_node: expr, $min_value_msat: expr, $min_final_cltv_expiry_delta: expr) => {
|
||||
{
|
||||
use bitcoin::hashes::Hash as _;
|
||||
let mut payment_count = $dest_node.network_payment_count.borrow_mut();
|
||||
|
@ -1454,10 +1459,10 @@ macro_rules! get_payment_preimage_hash {
|
|||
*payment_count += 1;
|
||||
let payment_hash = $crate::ln::PaymentHash(
|
||||
bitcoin::hashes::sha256::Hash::hash(&payment_preimage.0[..]).into_inner());
|
||||
let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200).unwrap();
|
||||
let payment_secret = $dest_node.node.create_inbound_payment_for_hash(payment_hash, $min_value_msat, 7200, $min_final_cltv_expiry_delta).unwrap();
|
||||
(payment_preimage, payment_hash, payment_secret)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
|
|
@ -1246,7 +1246,7 @@ fn test_duplicate_htlc_different_direction_onchain() {
|
|||
let (payment_preimage, payment_hash, _) = route_payment(&nodes[0], &vec!(&nodes[1])[..], 900_000);
|
||||
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], 800_000);
|
||||
let node_a_payment_secret = nodes[0].node.create_inbound_payment_for_hash(payment_hash, None, 7200).unwrap();
|
||||
let node_a_payment_secret = nodes[0].node.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[0]]], 800_000, payment_hash, node_a_payment_secret);
|
||||
|
||||
// Provide preimage to node 0 by claiming payment
|
||||
|
@ -4712,7 +4712,7 @@ fn test_duplicate_payment_hash_one_failure_one_success() {
|
|||
|
||||
let (our_payment_preimage, duplicate_payment_hash, _) = route_payment(&nodes[0], &[&nodes[1], &nodes[2]], 900_000);
|
||||
|
||||
let payment_secret = nodes[3].node.create_inbound_payment_for_hash(duplicate_payment_hash, None, 7200).unwrap();
|
||||
let payment_secret = nodes[3].node.create_inbound_payment_for_hash(duplicate_payment_hash, None, 7200, None).unwrap();
|
||||
// We reduce the final CLTV here by a somewhat arbitrary constant to keep it under the one-byte
|
||||
// script push size limit so that the below script length checks match
|
||||
// ACCEPTED_HTLC_SCRIPT_WEIGHT.
|
||||
|
@ -4925,30 +4925,30 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno
|
|||
let (_, payment_hash_2, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], ds_dust_limit*1000);
|
||||
// 2nd HTLC:
|
||||
send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_1, nodes[5].node.create_inbound_payment_for_hash(payment_hash_1, None, 7200).unwrap()); // not added < dust limit + HTLC tx fee
|
||||
send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_1, nodes[5].node.create_inbound_payment_for_hash(payment_hash_1, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
|
||||
// 3rd HTLC:
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_2, nodes[5].node.create_inbound_payment_for_hash(payment_hash_2, None, 7200).unwrap()); // not added < dust limit + HTLC tx fee
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_2, nodes[5].node.create_inbound_payment_for_hash(payment_hash_2, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
|
||||
// 4th HTLC:
|
||||
let (_, payment_hash_3, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
|
||||
// 5th HTLC:
|
||||
let (_, payment_hash_4, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000);
|
||||
// 6th HTLC:
|
||||
send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_3, nodes[5].node.create_inbound_payment_for_hash(payment_hash_3, None, 7200).unwrap());
|
||||
send_along_route_with_secret(&nodes[1], route.clone(), &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_3, nodes[5].node.create_inbound_payment_for_hash(payment_hash_3, None, 7200, None).unwrap());
|
||||
// 7th HTLC:
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_4, nodes[5].node.create_inbound_payment_for_hash(payment_hash_4, None, 7200).unwrap());
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_4, nodes[5].node.create_inbound_payment_for_hash(payment_hash_4, None, 7200, None).unwrap());
|
||||
|
||||
// 8th HTLC:
|
||||
let (_, payment_hash_5, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], 1000000);
|
||||
// 9th HTLC:
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], ds_dust_limit*1000);
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_5, nodes[5].node.create_inbound_payment_for_hash(payment_hash_5, None, 7200).unwrap()); // not added < dust limit + HTLC tx fee
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], ds_dust_limit*1000, payment_hash_5, nodes[5].node.create_inbound_payment_for_hash(payment_hash_5, None, 7200, None).unwrap()); // not added < dust limit + HTLC tx fee
|
||||
|
||||
// 10th HTLC:
|
||||
let (_, payment_hash_6, _) = route_payment(&nodes[0], &[&nodes[2], &nodes[3], &nodes[4]], ds_dust_limit*1000); // not added < dust limit + HTLC tx fee
|
||||
// 11th HTLC:
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000);
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_6, nodes[5].node.create_inbound_payment_for_hash(payment_hash_6, None, 7200).unwrap());
|
||||
send_along_route_with_secret(&nodes[1], route, &[&[&nodes[2], &nodes[3], &nodes[5]]], 1000000, payment_hash_6, nodes[5].node.create_inbound_payment_for_hash(payment_hash_6, None, 7200, None).unwrap());
|
||||
|
||||
// Double-check that six of the new HTLC were added
|
||||
// We now have six HTLCs pending over the dust limit and six HTLCs under the dust limit (ie,
|
||||
|
@ -6922,7 +6922,7 @@ fn test_check_htlc_underpaying() {
|
|||
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id()).with_features(nodes[1].node.invoice_features());
|
||||
let route = get_route(&nodes[0].node.get_our_node_id(), &payment_params, &nodes[0].network_graph.read_only(), None, 10_000, TEST_FINAL_CLTV, nodes[0].logger, &scorer, &random_seed_bytes).unwrap();
|
||||
let (_, our_payment_hash, _) = get_payment_preimage_hash!(nodes[0]);
|
||||
let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200).unwrap();
|
||||
let our_payment_secret = nodes[1].node.create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200, None).unwrap();
|
||||
nodes[0].node.send_payment(&route, our_payment_hash, &Some(our_payment_secret), PaymentId(our_payment_hash.0)).unwrap();
|
||||
check_added_monitors!(nodes[0], 1);
|
||||
|
||||
|
@ -7917,7 +7917,7 @@ fn test_preimage_storage() {
|
|||
create_announced_chan_between_nodes(&nodes, 0, 1).0.contents.short_channel_id;
|
||||
|
||||
{
|
||||
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 7200).unwrap();
|
||||
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 7200, None).unwrap();
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
|
||||
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
|
||||
check_added_monitors!(nodes[0], 1);
|
||||
|
@ -8023,7 +8023,7 @@ fn test_bad_secret_hash() {
|
|||
|
||||
let random_payment_hash = PaymentHash([42; 32]);
|
||||
let random_payment_secret = PaymentSecret([43; 32]);
|
||||
let (our_payment_hash, our_payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 2).unwrap();
|
||||
let (our_payment_hash, our_payment_secret) = nodes[1].node.create_inbound_payment(Some(100_000), 2, None).unwrap();
|
||||
let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000);
|
||||
|
||||
// All the below cases should end up being handled exactly identically, so we macro the
|
||||
|
@ -9582,3 +9582,60 @@ fn accept_busted_but_better_fee() {
|
|||
_ => panic!("Unexpected event"),
|
||||
};
|
||||
}
|
||||
|
||||
fn do_payment_with_custom_min_final_cltv_expiry(valid_delta: bool, use_user_hash: bool) {
|
||||
let mut chanmon_cfgs = create_chanmon_cfgs(2);
|
||||
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
|
||||
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
|
||||
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
|
||||
let min_final_cltv_expiry_delta = 120;
|
||||
let final_cltv_expiry_delta = if valid_delta { min_final_cltv_expiry_delta + 2 } else {
|
||||
min_final_cltv_expiry_delta - 2 };
|
||||
let recv_value = 100_000;
|
||||
|
||||
create_chan_between_nodes(&nodes[0], &nodes[1]);
|
||||
|
||||
let payment_parameters = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id());
|
||||
let (payment_hash, payment_preimage, payment_secret) = if use_user_hash {
|
||||
let (payment_preimage, payment_hash, payment_secret) = get_payment_preimage_hash!(nodes[1],
|
||||
Some(recv_value), Some(min_final_cltv_expiry_delta));
|
||||
(payment_hash, payment_preimage, payment_secret)
|
||||
} else {
|
||||
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(recv_value), 7200, Some(min_final_cltv_expiry_delta)).unwrap();
|
||||
(payment_hash, nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap(), payment_secret)
|
||||
};
|
||||
let route = get_route!(nodes[0], payment_parameters, recv_value, final_cltv_expiry_delta as u32).unwrap();
|
||||
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
|
||||
check_added_monitors!(nodes[0], 1);
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
let mut payment_event = SendEvent::from_event(events.pop().unwrap());
|
||||
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
|
||||
commitment_signed_dance!(nodes[1], nodes[0], payment_event.commitment_msg, false);
|
||||
expect_pending_htlcs_forwardable!(nodes[1]);
|
||||
|
||||
if valid_delta {
|
||||
expect_payment_claimable!(nodes[1], payment_hash, payment_secret, recv_value, if use_user_hash {
|
||||
None } else { Some(payment_preimage) }, nodes[1].node.get_our_node_id());
|
||||
|
||||
claim_payment(&nodes[0], &vec!(&nodes[1])[..], payment_preimage);
|
||||
} else {
|
||||
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(nodes[1], vec![HTLCDestination::FailedPayment { payment_hash }]);
|
||||
|
||||
check_added_monitors!(nodes[1], 1);
|
||||
|
||||
let fail_updates = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
|
||||
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &fail_updates.update_fail_htlcs[0]);
|
||||
commitment_signed_dance!(nodes[0], nodes[1], fail_updates.commitment_signed, false, true);
|
||||
|
||||
expect_payment_failed!(nodes[0], payment_hash, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_payment_with_custom_min_cltv_expiry_delta() {
|
||||
do_payment_with_custom_min_final_cltv_expiry(false, false);
|
||||
do_payment_with_custom_min_final_cltv_expiry(false, true);
|
||||
do_payment_with_custom_min_final_cltv_expiry(true, false);
|
||||
do_payment_with_custom_min_final_cltv_expiry(true, true);
|
||||
}
|
||||
|
|
|
@ -68,6 +68,8 @@ impl ExpandedKey {
|
|||
enum Method {
|
||||
LdkPaymentHash = 0,
|
||||
UserPaymentHash = 1,
|
||||
LdkPaymentHashCustomFinalCltv = 2,
|
||||
UserPaymentHashCustomFinalCltv = 3,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
|
@ -75,11 +77,18 @@ impl Method {
|
|||
match bits {
|
||||
bits if bits == Method::LdkPaymentHash as u8 => Ok(Method::LdkPaymentHash),
|
||||
bits if bits == Method::UserPaymentHash as u8 => Ok(Method::UserPaymentHash),
|
||||
bits if bits == Method::LdkPaymentHashCustomFinalCltv as u8 => Ok(Method::LdkPaymentHashCustomFinalCltv),
|
||||
bits if bits == Method::UserPaymentHashCustomFinalCltv as u8 => Ok(Method::UserPaymentHashCustomFinalCltv),
|
||||
unknown => Err(unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn min_final_cltv_expiry_delta_from_metadata(bytes: [u8; METADATA_LEN]) -> u16 {
|
||||
let expiry_bytes = &bytes[AMT_MSAT_LEN..];
|
||||
u16::from_be_bytes([expiry_bytes[0], expiry_bytes[1]])
|
||||
}
|
||||
|
||||
/// Equivalent to [`crate::ln::channelmanager::ChannelManager::create_inbound_payment`], but no
|
||||
/// `ChannelManager` is required. Useful for generating invoices for [phantom node payments] without
|
||||
/// a `ChannelManager`.
|
||||
|
@ -90,12 +99,21 @@ impl Method {
|
|||
///
|
||||
/// `current_time` is a Unix timestamp representing the current time.
|
||||
///
|
||||
/// Note that if `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
|
||||
/// on versions of LDK prior to 0.0.114.
|
||||
///
|
||||
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
|
||||
/// [`NodeSigner::get_inbound_payment_key_material`]: crate::chain::keysinterface::NodeSigner::get_inbound_payment_key_material
|
||||
pub fn create<ES: Deref>(keys: &ExpandedKey, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32, entropy_source: &ES, current_time: u64) -> Result<(PaymentHash, PaymentSecret), ()>
|
||||
pub fn create<ES: Deref>(keys: &ExpandedKey, min_value_msat: Option<u64>,
|
||||
invoice_expiry_delta_secs: u32, entropy_source: &ES, current_time: u64,
|
||||
min_final_cltv_expiry_delta: Option<u16>) -> Result<(PaymentHash, PaymentSecret), ()>
|
||||
where ES::Target: EntropySource
|
||||
{
|
||||
let metadata_bytes = construct_metadata_bytes(min_value_msat, Method::LdkPaymentHash, invoice_expiry_delta_secs, current_time)?;
|
||||
let metadata_bytes = construct_metadata_bytes(min_value_msat, if min_final_cltv_expiry_delta.is_some() {
|
||||
Method::LdkPaymentHashCustomFinalCltv
|
||||
} else {
|
||||
Method::LdkPaymentHash
|
||||
}, invoice_expiry_delta_secs, current_time, min_final_cltv_expiry_delta)?;
|
||||
|
||||
let mut iv_bytes = [0 as u8; IV_LEN];
|
||||
let rand_bytes = entropy_source.get_secure_random_bytes();
|
||||
|
@ -117,9 +135,17 @@ pub fn create<ES: Deref>(keys: &ExpandedKey, min_value_msat: Option<u64>, invoic
|
|||
///
|
||||
/// See [`create`] for information on the `keys` and `current_time` parameters.
|
||||
///
|
||||
/// Note that if `min_final_cltv_expiry_delta` is set to some value, then the payment will not be receivable
|
||||
/// on versions of LDK prior to 0.0.114.
|
||||
///
|
||||
/// [phantom node payments]: crate::chain::keysinterface::PhantomKeysManager
|
||||
pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option<u64>, payment_hash: PaymentHash, invoice_expiry_delta_secs: u32, current_time: u64) -> Result<PaymentSecret, ()> {
|
||||
let metadata_bytes = construct_metadata_bytes(min_value_msat, Method::UserPaymentHash, invoice_expiry_delta_secs, current_time)?;
|
||||
pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option<u64>, payment_hash: PaymentHash,
|
||||
invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option<u16>) -> Result<PaymentSecret, ()> {
|
||||
let metadata_bytes = construct_metadata_bytes(min_value_msat, if min_final_cltv_expiry_delta.is_some() {
|
||||
Method::UserPaymentHashCustomFinalCltv
|
||||
} else {
|
||||
Method::UserPaymentHash
|
||||
}, invoice_expiry_delta_secs, current_time, min_final_cltv_expiry_delta)?;
|
||||
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&keys.user_pmt_hash_key);
|
||||
hmac.input(&metadata_bytes);
|
||||
|
@ -132,7 +158,8 @@ pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option<u64>, payment
|
|||
Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key))
|
||||
}
|
||||
|
||||
fn construct_metadata_bytes(min_value_msat: Option<u64>, payment_type: Method, invoice_expiry_delta_secs: u32, highest_seen_timestamp: u64) -> Result<[u8; METADATA_LEN], ()> {
|
||||
fn construct_metadata_bytes(min_value_msat: Option<u64>, payment_type: Method,
|
||||
invoice_expiry_delta_secs: u32, highest_seen_timestamp: u64, min_final_cltv_expiry_delta: Option<u16>) -> Result<[u8; METADATA_LEN], ()> {
|
||||
if min_value_msat.is_some() && min_value_msat.unwrap() > MAX_VALUE_MSAT {
|
||||
return Err(());
|
||||
}
|
||||
|
@ -148,9 +175,27 @@ fn construct_metadata_bytes(min_value_msat: Option<u64>, payment_type: Method, i
|
|||
// than two hours in the future. Thus, we add two hours here as a buffer to ensure we
|
||||
// absolutely never fail a payment too early.
|
||||
// Note that we assume that received blocks have reasonably up-to-date timestamps.
|
||||
let expiry_bytes = (highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200).to_be_bytes();
|
||||
let expiry_timestamp = highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200;
|
||||
let mut expiry_bytes = expiry_timestamp.to_be_bytes();
|
||||
|
||||
// `min_value_msat` should fit in (64 bits - 3 payment type bits =) 61 bits as an unsigned integer.
|
||||
// This should leave us with a maximum value greater than the 21M BTC supply cap anyway.
|
||||
if min_value_msat.is_some() && min_value_msat.unwrap() > ((1u64 << 61) - 1) { return Err(()); }
|
||||
|
||||
// `expiry_timestamp` should fit in (64 bits - 2 delta bytes =) 48 bits as an unsigned integer.
|
||||
// Bitcoin's block header timestamps are actually `u32`s, so we're technically already limited to
|
||||
// the much smaller maximum timestamp of `u32::MAX` for now, but we check the u64 `expiry_timestamp`
|
||||
// for future-proofing.
|
||||
if min_final_cltv_expiry_delta.is_some() && expiry_timestamp > ((1u64 << 48) - 1) { return Err(()); }
|
||||
|
||||
if let Some(min_final_cltv_expiry_delta) = min_final_cltv_expiry_delta {
|
||||
let bytes = min_final_cltv_expiry_delta.to_be_bytes();
|
||||
expiry_bytes[0] |= bytes[0];
|
||||
expiry_bytes[1] |= bytes[1];
|
||||
}
|
||||
|
||||
let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
|
||||
|
||||
metadata_bytes[..AMT_MSAT_LEN].copy_from_slice(&min_amt_msat_bytes);
|
||||
metadata_bytes[AMT_MSAT_LEN..].copy_from_slice(&expiry_bytes);
|
||||
|
||||
|
@ -175,9 +220,13 @@ fn construct_payment_secret(iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METAD
|
|||
/// secret (and, if supplied by LDK, our payment preimage) to include encrypted metadata about the
|
||||
/// payment.
|
||||
///
|
||||
/// The metadata is constructed as:
|
||||
/// For payments without a custom `min_final_cltv_expiry_delta`, the metadata is constructed as:
|
||||
/// payment method (3 bits) || payment amount (8 bytes - 3 bits) || expiry (8 bytes)
|
||||
/// and encrypted using a key derived from [`NodeSigner::get_inbound_payment_key_material`].
|
||||
///
|
||||
/// For payments including a custom `min_final_cltv_expiry_delta`, the metadata is constructed as:
|
||||
/// payment method (3 bits) || payment amount (8 bytes - 3 bits) || min_final_cltv_expiry_delta (2 bytes) || expiry (6 bytes)
|
||||
///
|
||||
/// In both cases the result is then encrypted using a key derived from [`NodeSigner::get_inbound_payment_key_material`].
|
||||
///
|
||||
/// Then on payment receipt, we verify in this method that the payment preimage and payment secret
|
||||
/// match what was constructed.
|
||||
|
@ -201,24 +250,27 @@ fn construct_payment_secret(iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METAD
|
|||
/// [`NodeSigner::get_inbound_payment_key_material`]: crate::chain::keysinterface::NodeSigner::get_inbound_payment_key_material
|
||||
/// [`create_inbound_payment`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment
|
||||
/// [`create_inbound_payment_for_hash`]: crate::ln::channelmanager::ChannelManager::create_inbound_payment_for_hash
|
||||
pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData, highest_seen_timestamp: u64, keys: &ExpandedKey, logger: &L) -> Result<Option<PaymentPreimage>, ()>
|
||||
pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData,
|
||||
highest_seen_timestamp: u64, keys: &ExpandedKey, logger: &L) -> Result<
|
||||
(Option<PaymentPreimage>, Option<u16>), ()>
|
||||
where L::Target: Logger
|
||||
{
|
||||
let (iv_bytes, metadata_bytes) = decrypt_metadata(payment_data.payment_secret, keys);
|
||||
|
||||
let payment_type_res = Method::from_bits((metadata_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET);
|
||||
let mut amt_msat_bytes = [0; AMT_MSAT_LEN];
|
||||
let mut expiry_bytes = [0; METADATA_LEN - AMT_MSAT_LEN];
|
||||
amt_msat_bytes.copy_from_slice(&metadata_bytes[..AMT_MSAT_LEN]);
|
||||
expiry_bytes.copy_from_slice(&metadata_bytes[AMT_MSAT_LEN..]);
|
||||
// Zero out the bits reserved to indicate the payment type.
|
||||
amt_msat_bytes[0] &= 0b00011111;
|
||||
let min_amt_msat: u64 = u64::from_be_bytes(amt_msat_bytes.into());
|
||||
let expiry = u64::from_be_bytes(metadata_bytes[AMT_MSAT_LEN..].try_into().unwrap());
|
||||
let mut min_final_cltv_expiry_delta = None;
|
||||
|
||||
// Make sure to check to check the HMAC before doing the other checks below, to mitigate timing
|
||||
// attacks.
|
||||
// Make sure to check the HMAC before doing the other checks below, to mitigate timing attacks.
|
||||
let mut payment_preimage = None;
|
||||
|
||||
match payment_type_res {
|
||||
Ok(Method::UserPaymentHash) => {
|
||||
Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => {
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&keys.user_pmt_hash_key);
|
||||
hmac.input(&metadata_bytes[..]);
|
||||
hmac.input(&payment_hash.0);
|
||||
|
@ -227,7 +279,7 @@ pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::F
|
|||
return Err(())
|
||||
}
|
||||
},
|
||||
Ok(Method::LdkPaymentHash) => {
|
||||
Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
|
||||
match derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys) {
|
||||
Ok(preimage) => payment_preimage = Some(preimage),
|
||||
Err(bad_preimage_bytes) => {
|
||||
|
@ -242,6 +294,19 @@ pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::F
|
|||
}
|
||||
}
|
||||
|
||||
match payment_type_res {
|
||||
Ok(Method::UserPaymentHashCustomFinalCltv) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
|
||||
min_final_cltv_expiry_delta = Some(min_final_cltv_expiry_delta_from_metadata(metadata_bytes));
|
||||
// Zero out first two bytes of expiry reserved for `min_final_cltv_expiry_delta`.
|
||||
expiry_bytes[0] &= 0;
|
||||
expiry_bytes[1] &= 0;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let min_amt_msat: u64 = u64::from_be_bytes(amt_msat_bytes.into());
|
||||
let expiry = u64::from_be_bytes(expiry_bytes.try_into().unwrap());
|
||||
|
||||
if payment_data.total_msat < min_amt_msat {
|
||||
log_trace!(logger, "Failing HTLC with payment_hash {} due to total_msat {} being less than the minimum amount of {} msat", log_bytes!(payment_hash.0), payment_data.total_msat, min_amt_msat);
|
||||
return Err(())
|
||||
|
@ -252,20 +317,20 @@ pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::F
|
|||
return Err(())
|
||||
}
|
||||
|
||||
Ok(payment_preimage)
|
||||
Ok((payment_preimage, min_final_cltv_expiry_delta))
|
||||
}
|
||||
|
||||
pub(super) fn get_payment_preimage(payment_hash: PaymentHash, payment_secret: PaymentSecret, keys: &ExpandedKey) -> Result<PaymentPreimage, APIError> {
|
||||
let (iv_bytes, metadata_bytes) = decrypt_metadata(payment_secret, keys);
|
||||
|
||||
match Method::from_bits((metadata_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET) {
|
||||
Ok(Method::LdkPaymentHash) => {
|
||||
Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => {
|
||||
derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys)
|
||||
.map_err(|bad_preimage_bytes| APIError::APIMisuseError {
|
||||
err: format!("Payment hash {} did not match decoded preimage {}", log_bytes!(payment_hash.0), log_bytes!(bad_preimage_bytes))
|
||||
})
|
||||
},
|
||||
Ok(Method::UserPaymentHash) => Err(APIError::APIMisuseError {
|
||||
Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => Err(APIError::APIMisuseError {
|
||||
err: "Expected payment type to be LdkPaymentHash, instead got UserPaymentHash".to_string()
|
||||
}),
|
||||
Err(other) => Err(APIError::APIMisuseError { err: format!("Unknown payment type: {}", other) }),
|
||||
|
|
|
@ -910,7 +910,7 @@ fn get_ldk_payment_preimage() {
|
|||
|
||||
let amt_msat = 60_000;
|
||||
let expiry_secs = 60 * 60;
|
||||
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs).unwrap();
|
||||
let (payment_hash, payment_secret) = nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs, None).unwrap();
|
||||
|
||||
let payment_params = PaymentParameters::from_node_id(nodes[1].node.get_our_node_id())
|
||||
.with_features(nodes[1].node.invoice_features());
|
||||
|
@ -1444,7 +1444,7 @@ fn do_test_intercepted_payment(test: InterceptTest) {
|
|||
route_params.final_cltv_expiry_delta, nodes[0].logger, &scorer, &random_seed_bytes
|
||||
).unwrap();
|
||||
|
||||
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60).unwrap();
|
||||
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap();
|
||||
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
|
||||
let payment_event = {
|
||||
{
|
||||
|
|
11
pending_changelog/1878.txt
Normal file
11
pending_changelog/1878.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
## API Updates
|
||||
- The functions `inbound_payment::{create, create_from_hash}` and
|
||||
`channelmanager::{create_inbound_payment, create_inbound_payment_for_hash}` now accept a
|
||||
`min_final_cltv_expiry_delta` argument. This encodes the `min_final_cltv_expiry_delta` in the
|
||||
payment secret metadata bytes to be validated on payment receipt.
|
||||
|
||||
## Backwards Compatibility
|
||||
- If `min_final_cltv_expiry_delta` set for any of `inbound_payment::{create, create_from_hash}` or
|
||||
`channelmanager::{create_inbound_payment, create_inbound_payment_for_hash}` then the payment will
|
||||
not be receivable on versions of LDK prior to 0.0.114.
|
||||
|
Loading…
Add table
Reference in a new issue