mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-02-24 06:57:53 +01:00
Merge pull request #3383 from valentinewallace/2024-09-blinded-keysend-verify
Verify blinded keysend payments
This commit is contained in:
commit
b0bd4371d9
7 changed files with 226 additions and 121 deletions
|
@ -24,7 +24,7 @@ macro_rules! hkdf_extract_expand {
|
|||
let (k1, k2, _) = hkdf_extract_expand!($salt, $ikm);
|
||||
(k1, k2)
|
||||
}};
|
||||
($salt: expr, $ikm: expr, 5) => {{
|
||||
($salt: expr, $ikm: expr, 6) => {{
|
||||
let (k1, k2, prk) = hkdf_extract_expand!($salt, $ikm);
|
||||
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
|
||||
|
@ -42,7 +42,12 @@ macro_rules! hkdf_extract_expand {
|
|||
hmac.input(&[5; 1]);
|
||||
let k5 = Hmac::from_engine(hmac).to_byte_array();
|
||||
|
||||
(k1, k2, k3, k4, k5)
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&prk[..]);
|
||||
hmac.input(&k5);
|
||||
hmac.input(&[6; 1]);
|
||||
let k6 = Hmac::from_engine(hmac).to_byte_array();
|
||||
|
||||
(k1, k2, k3, k4, k5, k6)
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -50,10 +55,10 @@ pub fn hkdf_extract_expand_twice(salt: &[u8], ikm: &[u8]) -> ([u8; 32], [u8; 32]
|
|||
hkdf_extract_expand!(salt, ikm, 2)
|
||||
}
|
||||
|
||||
pub fn hkdf_extract_expand_5x(
|
||||
pub fn hkdf_extract_expand_6x(
|
||||
salt: &[u8], ikm: &[u8],
|
||||
) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
|
||||
hkdf_extract_expand!(salt, ikm, 5)
|
||||
) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]) {
|
||||
hkdf_extract_expand!(salt, ikm, 6)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -35,6 +35,10 @@ use crate::util::config::UserConfig;
|
|||
use crate::util::ser::WithoutLength;
|
||||
use crate::util::test_utils;
|
||||
use lightning_invoice::RawBolt11Invoice;
|
||||
#[cfg(async_payments)] use {
|
||||
crate::ln::inbound_payment,
|
||||
crate::types::payment::PaymentPreimage,
|
||||
};
|
||||
|
||||
fn blinded_payment_path(
|
||||
payment_secret: PaymentSecret, intro_node_min_htlc: u64, intro_node_max_htlc: u64,
|
||||
|
@ -1209,6 +1213,7 @@ fn conditionally_round_fwd_amt() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(async_payments)]
|
||||
fn blinded_keysend() {
|
||||
let mut mpp_keysend_config = test_default_channel_config();
|
||||
mpp_keysend_config.accept_mpp_keysend = true;
|
||||
|
@ -1219,8 +1224,15 @@ fn blinded_keysend() {
|
|||
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
|
||||
let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
|
||||
|
||||
let inbound_payment_key = inbound_payment::ExpandedKey::new(
|
||||
&nodes[2].keys_manager.get_inbound_payment_key_material()
|
||||
);
|
||||
let payment_secret = inbound_payment::create_for_spontaneous_payment(
|
||||
&inbound_payment_key, None, u32::MAX, nodes[2].node.duration_since_epoch().as_secs(), None
|
||||
).unwrap();
|
||||
|
||||
let amt_msat = 5000;
|
||||
let (keysend_preimage, _, payment_secret) = get_payment_preimage_hash(&nodes[2], None, None);
|
||||
let keysend_preimage = PaymentPreimage([42; 32]);
|
||||
let route_params = get_blinded_route_parameters(amt_msat, payment_secret, 1,
|
||||
1_0000_0000,
|
||||
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(),
|
||||
|
@ -1241,6 +1253,7 @@ fn blinded_keysend() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(async_payments)]
|
||||
fn blinded_mpp_keysend() {
|
||||
let mut mpp_keysend_config = test_default_channel_config();
|
||||
mpp_keysend_config.accept_mpp_keysend = true;
|
||||
|
@ -1254,8 +1267,15 @@ fn blinded_mpp_keysend() {
|
|||
let chan_1_3 = create_announced_chan_between_nodes(&nodes, 1, 3);
|
||||
let chan_2_3 = create_announced_chan_between_nodes(&nodes, 2, 3);
|
||||
|
||||
let inbound_payment_key = inbound_payment::ExpandedKey::new(
|
||||
&nodes[3].keys_manager.get_inbound_payment_key_material()
|
||||
);
|
||||
let payment_secret = inbound_payment::create_for_spontaneous_payment(
|
||||
&inbound_payment_key, None, u32::MAX, nodes[3].node.duration_since_epoch().as_secs(), None
|
||||
).unwrap();
|
||||
|
||||
let amt_msat = 15_000_000;
|
||||
let (keysend_preimage, _, payment_secret) = get_payment_preimage_hash(&nodes[3], None, None);
|
||||
let keysend_preimage = PaymentPreimage([42; 32]);
|
||||
let route_params = {
|
||||
let pay_params = PaymentParameters::blinded(
|
||||
vec![
|
||||
|
@ -1293,6 +1313,59 @@ fn blinded_mpp_keysend() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(async_payments)]
|
||||
fn invalid_keysend_payment_secret() {
|
||||
let mut mpp_keysend_config = test_default_channel_config();
|
||||
mpp_keysend_config.accept_mpp_keysend = true;
|
||||
let chanmon_cfgs = create_chanmon_cfgs(3);
|
||||
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
|
||||
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, Some(mpp_keysend_config)]);
|
||||
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
|
||||
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
|
||||
let chan_upd_1_2 = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0).0.contents;
|
||||
|
||||
let invalid_payment_secret = PaymentSecret([42; 32]);
|
||||
let amt_msat = 5000;
|
||||
let keysend_preimage = PaymentPreimage([42; 32]);
|
||||
let route_params = get_blinded_route_parameters(
|
||||
amt_msat, invalid_payment_secret, 1, 1_0000_0000,
|
||||
nodes.iter().skip(1).map(|n| n.node.get_our_node_id()).collect(), &[&chan_upd_1_2],
|
||||
&chanmon_cfgs[2].keys_manager
|
||||
);
|
||||
|
||||
let payment_hash = nodes[0].node.send_spontaneous_payment_with_retry(Some(keysend_preimage), RecipientOnionFields::spontaneous_empty(), PaymentId(keysend_preimage.0), route_params, Retry::Attempts(0)).unwrap();
|
||||
check_added_monitors(&nodes[0], 1);
|
||||
|
||||
let expected_route: &[&[&Node]] = &[&[&nodes[1], &nodes[2]]];
|
||||
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
|
||||
assert_eq!(events.len(), 1);
|
||||
|
||||
let ev = remove_first_msg_event_to_node(&nodes[1].node.get_our_node_id(), &mut events);
|
||||
let args = PassAlongPathArgs::new(
|
||||
&nodes[0], &expected_route[0], amt_msat, payment_hash, ev.clone()
|
||||
)
|
||||
.with_payment_secret(invalid_payment_secret)
|
||||
.with_payment_preimage(keysend_preimage)
|
||||
.expect_failure(HTLCDestination::FailedPayment { payment_hash });
|
||||
do_pass_along_path(args);
|
||||
|
||||
let updates_2_1 = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
|
||||
assert_eq!(updates_2_1.update_fail_malformed_htlcs.len(), 1);
|
||||
let update_malformed = &updates_2_1.update_fail_malformed_htlcs[0];
|
||||
assert_eq!(update_malformed.sha256_of_onion, [0; 32]);
|
||||
assert_eq!(update_malformed.failure_code, INVALID_ONION_BLINDING);
|
||||
nodes[1].node.handle_update_fail_malformed_htlc(nodes[2].node.get_our_node_id(), update_malformed);
|
||||
do_commitment_signed_dance(&nodes[1], &nodes[2], &updates_2_1.commitment_signed, true, false);
|
||||
|
||||
let updates_1_0 = get_htlc_update_msgs!(nodes[1], nodes[0].node.get_our_node_id());
|
||||
assert_eq!(updates_1_0.update_fail_htlcs.len(), 1);
|
||||
nodes[0].node.handle_update_fail_htlc(nodes[1].node.get_our_node_id(), &updates_1_0.update_fail_htlcs[0]);
|
||||
do_commitment_signed_dance(&nodes[0], &nodes[1], &updates_1_0.commitment_signed, false, false);
|
||||
expect_payment_failed_conditions(&nodes[0], payment_hash, false,
|
||||
PaymentFailedConditions::new().expected_htlc_error_data(INVALID_ONION_BLINDING, &[0; 32]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_tlvs_to_blinded_path() {
|
||||
let chanmon_cfgs = create_chanmon_cfgs(2);
|
||||
|
|
|
@ -222,6 +222,10 @@ pub enum PendingHTLCRouting {
|
|||
custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
/// Set if this HTLC is the final hop in a multi-hop blinded path.
|
||||
requires_blinded_error: bool,
|
||||
/// Set if we are receiving a keysend to a blinded path, meaning we created the
|
||||
/// [`PaymentSecret`] and should verify it using our
|
||||
/// [`NodeSigner::get_inbound_payment_key_material`].
|
||||
has_recipient_created_payment_secret: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1367,6 +1371,7 @@ pub(super) const FEERATE_TRACKING_BLOCKS: usize = 144;
|
|||
///
|
||||
/// Note that this struct will be removed entirely soon, in favor of storing no inbound payment data
|
||||
/// and instead encoding it in the payment secret.
|
||||
#[derive(Debug)]
|
||||
struct PendingInboundPayment {
|
||||
/// The payment secret that the sender must use for us to accept this payment
|
||||
payment_secret: PaymentSecret,
|
||||
|
@ -2299,25 +2304,23 @@ where
|
|||
// |
|
||||
// |__`per_peer_state`
|
||||
// |
|
||||
// |__`pending_inbound_payments`
|
||||
// |
|
||||
// |__`claimable_payments`
|
||||
// |
|
||||
// |__`pending_outbound_payments` // This field's struct contains a map of pending outbounds
|
||||
// |
|
||||
// |__`peer_state`
|
||||
// |
|
||||
// |__`outpoint_to_peer`
|
||||
// |
|
||||
// |__`short_to_chan_info`
|
||||
// |
|
||||
// |__`outbound_scid_aliases`
|
||||
// |
|
||||
// |__`best_block`
|
||||
// |
|
||||
// |__`pending_events`
|
||||
// |
|
||||
// |__`pending_background_events`
|
||||
// |__`claimable_payments`
|
||||
// |
|
||||
// |__`pending_outbound_payments` // This field's struct contains a map of pending outbounds
|
||||
// |
|
||||
// |__`peer_state`
|
||||
// |
|
||||
// |__`outpoint_to_peer`
|
||||
// |
|
||||
// |__`short_to_chan_info`
|
||||
// |
|
||||
// |__`outbound_scid_aliases`
|
||||
// |
|
||||
// |__`best_block`
|
||||
// |
|
||||
// |__`pending_events`
|
||||
// |
|
||||
// |__`pending_background_events`
|
||||
//
|
||||
pub struct ChannelManager<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref>
|
||||
where
|
||||
|
@ -2347,14 +2350,6 @@ where
|
|||
best_block: RwLock<BestBlock>,
|
||||
secp_ctx: Secp256k1<secp256k1::All>,
|
||||
|
||||
/// Storage for PaymentSecrets and any requirements on future inbound payments before we will
|
||||
/// expose them to users via a PaymentClaimable event. HTLCs which do not meet the requirements
|
||||
/// here are failed when we process them as pending-forwardable-HTLCs, and entries are removed
|
||||
/// after we generate a PaymentClaimable upon receipt of all MPP parts or when they time out.
|
||||
///
|
||||
/// See `ChannelManager` struct-level documentation for lock order requirements.
|
||||
pending_inbound_payments: Mutex<HashMap<PaymentHash, PendingInboundPayment>>,
|
||||
|
||||
/// The session_priv bytes and retry metadata of outbound payments which are pending resolution.
|
||||
/// The authoritative state of these HTLCs resides either within Channels or ChannelMonitors
|
||||
/// (if the channel has been force-closed), however we track them here to prevent duplicative
|
||||
|
@ -3350,7 +3345,6 @@ where
|
|||
best_block: RwLock::new(params.best_block),
|
||||
|
||||
outbound_scid_aliases: Mutex::new(new_hash_set()),
|
||||
pending_inbound_payments: Mutex::new(new_hash_map()),
|
||||
pending_outbound_payments: OutboundPayments::new(new_hash_map()),
|
||||
forward_htlcs: Mutex::new(new_hash_map()),
|
||||
decode_update_add_htlcs: Mutex::new(new_hash_map()),
|
||||
|
@ -5842,7 +5836,10 @@ where
|
|||
}
|
||||
}) => {
|
||||
let blinded_failure = routing.blinded_failure();
|
||||
let (cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret, mut onion_fields) = match routing {
|
||||
let (
|
||||
cltv_expiry, onion_payload, payment_data, payment_context, phantom_shared_secret,
|
||||
mut onion_fields, has_recipient_created_payment_secret
|
||||
) = match routing {
|
||||
PendingHTLCRouting::Receive {
|
||||
payment_data, payment_metadata, payment_context,
|
||||
incoming_cltv_expiry, phantom_shared_secret, custom_tlvs,
|
||||
|
@ -5852,11 +5849,13 @@ where
|
|||
let onion_fields = RecipientOnionFields { payment_secret: Some(payment_data.payment_secret),
|
||||
payment_metadata, custom_tlvs };
|
||||
(incoming_cltv_expiry, OnionPayload::Invoice { _legacy_hop_data },
|
||||
Some(payment_data), payment_context, phantom_shared_secret, onion_fields)
|
||||
Some(payment_data), payment_context, phantom_shared_secret, onion_fields,
|
||||
true)
|
||||
},
|
||||
PendingHTLCRouting::ReceiveKeysend {
|
||||
payment_data, payment_preimage, payment_metadata,
|
||||
incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _
|
||||
incoming_cltv_expiry, custom_tlvs, requires_blinded_error: _,
|
||||
has_recipient_created_payment_secret,
|
||||
} => {
|
||||
let onion_fields = RecipientOnionFields {
|
||||
payment_secret: payment_data.as_ref().map(|data| data.payment_secret),
|
||||
|
@ -5864,7 +5863,7 @@ where
|
|||
custom_tlvs,
|
||||
};
|
||||
(incoming_cltv_expiry, OnionPayload::Spontaneous(payment_preimage),
|
||||
payment_data, None, None, onion_fields)
|
||||
payment_data, None, None, onion_fields, has_recipient_created_payment_secret)
|
||||
},
|
||||
_ => {
|
||||
panic!("short_channel_id == 0 should imply any pending_forward entries are of type Receive");
|
||||
|
@ -6029,66 +6028,41 @@ where
|
|||
// that we are the ultimate recipient of the given payment hash.
|
||||
// Further, we must not expose whether we have any other HTLCs
|
||||
// associated with the same payment_hash pending or not.
|
||||
let mut payment_secrets = self.pending_inbound_payments.lock().unwrap();
|
||||
match payment_secrets.entry(payment_hash) {
|
||||
hash_map::Entry::Vacant(_) => {
|
||||
match claimable_htlc.onion_payload {
|
||||
OnionPayload::Invoice { .. } => {
|
||||
let payment_data = payment_data.unwrap();
|
||||
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", &payment_hash);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
}
|
||||
};
|
||||
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 {})",
|
||||
&payment_hash, cltv_expiry, expected_min_expiry_height);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
}
|
||||
}
|
||||
let purpose = events::PaymentPurpose::from_parts(
|
||||
payment_preimage,
|
||||
payment_data.payment_secret,
|
||||
payment_context,
|
||||
);
|
||||
check_total_value!(purpose);
|
||||
},
|
||||
OnionPayload::Spontaneous(preimage) => {
|
||||
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
|
||||
check_total_value!(purpose);
|
||||
let payment_preimage = if has_recipient_created_payment_secret {
|
||||
if let Some(ref payment_data) = payment_data {
|
||||
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", &payment_hash);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
}
|
||||
};
|
||||
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 {})",
|
||||
&payment_hash, cltv_expiry, expected_min_expiry_height);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
}
|
||||
}
|
||||
},
|
||||
hash_map::Entry::Occupied(inbound_payment) => {
|
||||
if let OnionPayload::Spontaneous(_) = claimable_htlc.onion_payload {
|
||||
log_trace!(self.logger, "Failing new keysend HTLC with payment_hash {} because we already have an inbound payment with the same payment hash", &payment_hash);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
}
|
||||
payment_preimage
|
||||
} else { fail_htlc!(claimable_htlc, payment_hash); }
|
||||
} else { None };
|
||||
match claimable_htlc.onion_payload {
|
||||
OnionPayload::Invoice { .. } => {
|
||||
let payment_data = payment_data.unwrap();
|
||||
if inbound_payment.get().payment_secret != payment_data.payment_secret {
|
||||
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our expected payment secret.", &payment_hash);
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
} else if inbound_payment.get().min_value_msat.is_some() && payment_data.total_msat < inbound_payment.get().min_value_msat.unwrap() {
|
||||
log_trace!(self.logger, "Failing new HTLC with payment_hash {} as it didn't match our minimum value (had {}, needed {}).",
|
||||
&payment_hash, payment_data.total_msat, inbound_payment.get().min_value_msat.unwrap());
|
||||
fail_htlc!(claimable_htlc, payment_hash);
|
||||
} else {
|
||||
let purpose = events::PaymentPurpose::from_parts(
|
||||
inbound_payment.get().payment_preimage,
|
||||
payment_data.payment_secret,
|
||||
payment_context,
|
||||
);
|
||||
let payment_claimable_generated = check_total_value!(purpose);
|
||||
if payment_claimable_generated {
|
||||
inbound_payment.remove_entry();
|
||||
}
|
||||
}
|
||||
let purpose = events::PaymentPurpose::from_parts(
|
||||
payment_preimage,
|
||||
payment_data.payment_secret,
|
||||
payment_context,
|
||||
);
|
||||
check_total_value!(purpose);
|
||||
},
|
||||
};
|
||||
OnionPayload::Spontaneous(preimage) => {
|
||||
let purpose = events::PaymentPurpose::SpontaneousPayment(preimage);
|
||||
check_total_value!(purpose);
|
||||
}
|
||||
}
|
||||
},
|
||||
HTLCForwardInfo::FailHTLC { .. } | HTLCForwardInfo::FailMalformedHTLC { .. } => {
|
||||
panic!("Got pending fail of our own HTLC");
|
||||
|
@ -10413,10 +10387,6 @@ where
|
|||
}
|
||||
}
|
||||
max_time!(self.highest_seen_timestamp);
|
||||
let mut payment_secrets = self.pending_inbound_payments.lock().unwrap();
|
||||
payment_secrets.retain(|_, inbound_payment| {
|
||||
inbound_payment.expiry_time > header.time as u64
|
||||
});
|
||||
}
|
||||
|
||||
fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option<BlockHash>)> {
|
||||
|
@ -11783,6 +11753,7 @@ impl_writeable_tlv_based_enum!(PendingHTLCRouting,
|
|||
(3, payment_metadata, option),
|
||||
(4, payment_data, option), // Added in 0.0.116
|
||||
(5, custom_tlvs, optional_vec),
|
||||
(7, has_recipient_created_payment_secret, (default_value, false)),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -12190,7 +12161,6 @@ where
|
|||
decode_update_add_htlcs_opt = Some(decode_update_add_htlcs);
|
||||
}
|
||||
|
||||
let pending_inbound_payments = self.pending_inbound_payments.lock().unwrap();
|
||||
let claimable_payments = self.claimable_payments.lock().unwrap();
|
||||
let pending_outbound_payments = self.pending_outbound_payments.pending_outbound_payments.lock().unwrap();
|
||||
|
||||
|
@ -12262,11 +12232,10 @@ where
|
|||
(self.highest_seen_timestamp.load(Ordering::Acquire) as u32).write(writer)?;
|
||||
(self.highest_seen_timestamp.load(Ordering::Acquire) as u32).write(writer)?;
|
||||
|
||||
(pending_inbound_payments.len() as u64).write(writer)?;
|
||||
for (hash, pending_payment) in pending_inbound_payments.iter() {
|
||||
hash.write(writer)?;
|
||||
pending_payment.write(writer)?;
|
||||
}
|
||||
// LDK versions prior to 0.0.104 wrote `pending_inbound_payments` here, with deprecated support
|
||||
// for stateful inbound payments maintained until 0.0.116, after which no further inbound
|
||||
// payments could have been written here.
|
||||
(0 as u64).write(writer)?;
|
||||
|
||||
// For backwards compat, write the session privs and their total length.
|
||||
let mut num_pending_outbounds_compat: u64 = 0;
|
||||
|
@ -12784,12 +12753,13 @@ where
|
|||
let _last_node_announcement_serial: u32 = Readable::read(reader)?; // Only used < 0.0.111
|
||||
let highest_seen_timestamp: u32 = Readable::read(reader)?;
|
||||
|
||||
// The last version where a pending inbound payment may have been added was 0.0.116.
|
||||
let pending_inbound_payment_count: u64 = Readable::read(reader)?;
|
||||
let mut pending_inbound_payments: HashMap<PaymentHash, PendingInboundPayment> = hash_map_with_capacity(cmp::min(pending_inbound_payment_count as usize, MAX_ALLOC_SIZE/(3*32)));
|
||||
for _ in 0..pending_inbound_payment_count {
|
||||
if pending_inbound_payments.insert(Readable::read(reader)?, Readable::read(reader)?).is_some() {
|
||||
return Err(DecodeError::InvalidValue);
|
||||
}
|
||||
let payment_hash: PaymentHash = Readable::read(reader)?;
|
||||
let logger = WithContext::from(&args.logger, None, None, Some(payment_hash));
|
||||
let inbound: PendingInboundPayment = Readable::read(reader)?;
|
||||
log_warn!(logger, "Ignoring deprecated pending inbound payment with payment hash {}: {:?}", payment_hash, inbound);
|
||||
}
|
||||
|
||||
let pending_outbound_payments_count_compat: u64 = Readable::read(reader)?;
|
||||
|
@ -13176,16 +13146,16 @@ where
|
|||
OnionPayload::Invoice { _legacy_hop_data } => {
|
||||
if let Some(hop_data) = _legacy_hop_data {
|
||||
events::PaymentPurpose::Bolt11InvoicePayment {
|
||||
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) {
|
||||
payment_preimage:
|
||||
match inbound_payment::verify(
|
||||
payment_hash, &hop_data, 0, &expanded_inbound_key, &args.logger
|
||||
) {
|
||||
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", &payment_hash);
|
||||
return Err(DecodeError::InvalidValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
payment_secret: hop_data.payment_secret,
|
||||
}
|
||||
} else { return Err(DecodeError::InvalidValue); }
|
||||
|
@ -13305,7 +13275,6 @@ where
|
|||
best_block: RwLock::new(BestBlock::new(best_block_hash, best_block_height)),
|
||||
|
||||
inbound_payment_key: expanded_inbound_key,
|
||||
pending_inbound_payments: Mutex::new(pending_inbound_payments),
|
||||
pending_outbound_payments: pending_outbounds,
|
||||
pending_intercepted_htlcs: Mutex::new(pending_intercepted_htlcs.unwrap()),
|
||||
|
||||
|
|
|
@ -2587,6 +2587,7 @@ pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> {
|
|||
pub is_probe: bool,
|
||||
pub custom_tlvs: Vec<(u64, Vec<u8>)>,
|
||||
pub payment_metadata: Option<Vec<u8>>,
|
||||
pub expected_failure: Option<HTLCDestination>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
|
||||
|
@ -2597,7 +2598,7 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
|
|||
Self {
|
||||
origin_node, expected_path, recv_value, payment_hash, payment_secret: None, event,
|
||||
payment_claimable_expected: true, clear_recipient_events: true, expected_preimage: None,
|
||||
is_probe: false, custom_tlvs: Vec::new(), payment_metadata: None,
|
||||
is_probe: false, custom_tlvs: Vec::new(), payment_metadata: None, expected_failure: None,
|
||||
}
|
||||
}
|
||||
pub fn without_clearing_recipient_events(mut self) -> Self {
|
||||
|
@ -2629,6 +2630,11 @@ impl<'a, 'b, 'c, 'd> PassAlongPathArgs<'a, 'b, 'c, 'd> {
|
|||
self.payment_metadata = Some(payment_metadata);
|
||||
self
|
||||
}
|
||||
pub fn expect_failure(mut self, failure: HTLCDestination) -> Self {
|
||||
self.payment_claimable_expected = false;
|
||||
self.expected_failure = Some(failure);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event> {
|
||||
|
@ -2636,6 +2642,7 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event>
|
|||
origin_node, expected_path, recv_value, payment_hash: our_payment_hash,
|
||||
payment_secret: our_payment_secret, event: ev, payment_claimable_expected,
|
||||
clear_recipient_events, expected_preimage, is_probe, custom_tlvs, payment_metadata,
|
||||
expected_failure
|
||||
} = args;
|
||||
|
||||
let mut payment_event = SendEvent::from_event(ev);
|
||||
|
@ -2699,6 +2706,11 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option<Event>
|
|||
_ => panic!("Unexpected event"),
|
||||
}
|
||||
event = Some(events_2[0].clone());
|
||||
} else if let Some(ref failure) = expected_failure {
|
||||
assert_eq!(events_2.len(), 2);
|
||||
expect_htlc_handling_failed_destinations!(events_2, &[failure]);
|
||||
node.node.process_pending_htlc_forwards();
|
||||
check_added_monitors!(node, 1);
|
||||
} else {
|
||||
assert!(events_2.is_empty());
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use bitcoin::hashes::hmac::{Hmac, HmacEngine};
|
|||
use bitcoin::hashes::sha256::Hash as Sha256;
|
||||
|
||||
use crate::crypto::chacha20::ChaCha20;
|
||||
use crate::crypto::utils::hkdf_extract_expand_5x;
|
||||
use crate::crypto::utils::hkdf_extract_expand_6x;
|
||||
use crate::ln::msgs;
|
||||
use crate::ln::msgs::MAX_VALUE_MSAT;
|
||||
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
|
||||
|
@ -55,6 +55,9 @@ pub struct ExpandedKey {
|
|||
offers_base_key: [u8; 32],
|
||||
/// The key used to encrypt message metadata for BOLT 12 Offers.
|
||||
offers_encryption_key: [u8; 32],
|
||||
/// The key used to authenticate spontaneous payments' metadata as previously registered with LDK
|
||||
/// for inclusion in a blinded path.
|
||||
spontaneous_pmt_key: [u8; 32],
|
||||
}
|
||||
|
||||
impl ExpandedKey {
|
||||
|
@ -68,13 +71,15 @@ impl ExpandedKey {
|
|||
user_pmt_hash_key,
|
||||
offers_base_key,
|
||||
offers_encryption_key,
|
||||
) = hkdf_extract_expand_5x(b"LDK Inbound Payment Key Expansion", &key_material.0);
|
||||
spontaneous_pmt_key,
|
||||
) = hkdf_extract_expand_6x(b"LDK Inbound Payment Key Expansion", &key_material.0);
|
||||
Self {
|
||||
metadata_key,
|
||||
ldk_pmt_hash_key,
|
||||
user_pmt_hash_key,
|
||||
offers_base_key,
|
||||
offers_encryption_key,
|
||||
spontaneous_pmt_key,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,11 +98,13 @@ impl ExpandedKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// We currently set aside 3 bits for the `Method` in the `PaymentSecret`.
|
||||
enum Method {
|
||||
LdkPaymentHash = 0,
|
||||
UserPaymentHash = 1,
|
||||
LdkPaymentHashCustomFinalCltv = 2,
|
||||
UserPaymentHashCustomFinalCltv = 3,
|
||||
SpontaneousPayment = 4,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
|
@ -107,6 +114,7 @@ impl Method {
|
|||
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),
|
||||
bits if bits == Method::SpontaneousPayment as u8 => Ok(Method::SpontaneousPayment),
|
||||
unknown => Err(unknown),
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +194,26 @@ pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option<u64>, payment
|
|||
Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key))
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
pub(super) fn create_for_spontaneous_payment(
|
||||
keys: &ExpandedKey, min_value_msat: Option<u64>, 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, Method::SpontaneousPayment, invoice_expiry_delta_secs, current_time,
|
||||
min_final_cltv_expiry_delta
|
||||
)?;
|
||||
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&keys.spontaneous_pmt_key);
|
||||
hmac.input(&metadata_bytes);
|
||||
let hmac_bytes = Hmac::from_engine(hmac).to_byte_array();
|
||||
|
||||
let mut iv_bytes = [0 as u8; IV_LEN];
|
||||
iv_bytes.copy_from_slice(&hmac_bytes[..IV_LEN]);
|
||||
|
||||
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, min_final_cltv_expiry_delta: Option<u16>) -> Result<[u8; METADATA_LEN], ()> {
|
||||
if min_value_msat.is_some() && min_value_msat.unwrap() > MAX_VALUE_MSAT {
|
||||
|
@ -315,6 +343,14 @@ pub(super) fn verify<L: Deref>(payment_hash: PaymentHash, payment_data: &msgs::F
|
|||
}
|
||||
}
|
||||
},
|
||||
Ok(Method::SpontaneousPayment) => {
|
||||
let mut hmac = HmacEngine::<Sha256>::new(&keys.spontaneous_pmt_key);
|
||||
hmac.input(&metadata_bytes[..]);
|
||||
if !fixed_time_eq(&iv_bytes, &Hmac::from_engine(hmac).to_byte_array().split_at_mut(IV_LEN).0) {
|
||||
log_trace!(logger, "Failing async payment HTLC with sender-generated payment_hash {}: unexpected payment_secret", &payment_hash);
|
||||
return Err(())
|
||||
}
|
||||
},
|
||||
Err(unknown_bits) => {
|
||||
log_trace!(logger, "Failing HTLC with payment hash {} due to unknown payment type {}", &payment_hash, unknown_bits);
|
||||
return Err(());
|
||||
|
@ -360,6 +396,9 @@ pub(super) fn get_payment_preimage(payment_hash: PaymentHash, payment_secret: Pa
|
|||
Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => Err(APIError::APIMisuseError {
|
||||
err: "Expected payment type to be LdkPaymentHash, instead got UserPaymentHash".to_string()
|
||||
}),
|
||||
Ok(Method::SpontaneousPayment) => Err(APIError::APIMisuseError {
|
||||
err: "Can't extract payment preimage for spontaneous payments".to_string()
|
||||
}),
|
||||
Err(other) => Err(APIError::APIMisuseError { err: format!("Unknown payment type: {}", other) }),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,14 +135,14 @@ pub(super) fn create_recv_pending_htlc_info(
|
|||
) -> Result<PendingHTLCInfo, InboundHTLCErr> {
|
||||
let (
|
||||
payment_data, keysend_preimage, custom_tlvs, onion_amt_msat, onion_cltv_expiry,
|
||||
payment_metadata, payment_context, requires_blinded_error
|
||||
payment_metadata, payment_context, requires_blinded_error, has_recipient_created_payment_secret
|
||||
) = match hop_data {
|
||||
msgs::InboundOnionPayload::Receive {
|
||||
payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
|
||||
cltv_expiry_height, payment_metadata, ..
|
||||
} =>
|
||||
(payment_data, keysend_preimage, custom_tlvs, sender_intended_htlc_amt_msat,
|
||||
cltv_expiry_height, payment_metadata, None, false),
|
||||
cltv_expiry_height, payment_metadata, None, false, keysend_preimage.is_none()),
|
||||
msgs::InboundOnionPayload::BlindedReceive {
|
||||
sender_intended_htlc_amt_msat, total_msat, cltv_expiry_height, payment_secret,
|
||||
intro_node_blinding_point, payment_constraints, payment_context, keysend_preimage,
|
||||
|
@ -161,7 +161,7 @@ pub(super) fn create_recv_pending_htlc_info(
|
|||
let payment_data = msgs::FinalOnionHopData { payment_secret, total_msat };
|
||||
(Some(payment_data), keysend_preimage, custom_tlvs,
|
||||
sender_intended_htlc_amt_msat, cltv_expiry_height, None, Some(payment_context),
|
||||
intro_node_blinding_point.is_none())
|
||||
intro_node_blinding_point.is_none(), true)
|
||||
}
|
||||
msgs::InboundOnionPayload::Forward { .. } => {
|
||||
return Err(InboundHTLCErr {
|
||||
|
@ -241,6 +241,7 @@ pub(super) fn create_recv_pending_htlc_info(
|
|||
incoming_cltv_expiry: onion_cltv_expiry,
|
||||
custom_tlvs,
|
||||
requires_blinded_error,
|
||||
has_recipient_created_payment_secret,
|
||||
}
|
||||
} else if let Some(data) = payment_data {
|
||||
PendingHTLCRouting::Receive {
|
||||
|
|
6
pending_changelog/3383-deprecate-old-inbounds.txt
Normal file
6
pending_changelog/3383-deprecate-old-inbounds.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Backwards Compatibility
|
||||
* Pending inbound payments added in versions 0.0.116 or earlier using the
|
||||
`create_inbound_payment{,_for_hash}_legacy` API will be ignored on `ChannelManager`
|
||||
deserialization and fail to be received
|
||||
|
||||
|
Loading…
Add table
Reference in a new issue