diff --git a/lightning/src/crypto/utils.rs b/lightning/src/crypto/utils.rs index cd4d9bfa6..b59cc6002 100644 --- a/lightning/src/crypto/utils.rs +++ b/lightning/src/crypto/utils.rs @@ -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::::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::::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] diff --git a/lightning/src/ln/blinded_payment_tests.rs b/lightning/src/ln/blinded_payment_tests.rs index aa399b9d4..415453615 100644 --- a/lightning/src/ln/blinded_payment_tests.rs +++ b/lightning/src/ln/blinded_payment_tests.rs @@ -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); diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 4ca95db11..575a1b201 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -222,6 +222,10 @@ pub enum PendingHTLCRouting { custom_tlvs: Vec<(u64, Vec)>, /// 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 where @@ -2347,14 +2350,6 @@ where best_block: RwLock, secp_ctx: Secp256k1, - /// 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>, - /// 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)> { @@ -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 = 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()), diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index de0b4c7d4..9d6da1c8f 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2587,6 +2587,7 @@ pub struct PassAlongPathArgs<'a, 'b, 'c, 'd> { pub is_probe: bool, pub custom_tlvs: Vec<(u64, Vec)>, pub payment_metadata: Option>, + pub expected_failure: Option, } 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 { @@ -2636,6 +2642,7 @@ pub fn do_pass_along_path<'a, 'b, 'c>(args: PassAlongPathArgs) -> Option 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 _ => 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()); } diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index e294dcbcf..5b8d48e1c 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -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, 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, invoice_expiry_delta_secs: u32, + current_time: u64, min_final_cltv_expiry_delta: Option +) -> Result { + 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::::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, payment_type: Method, invoice_expiry_delta_secs: u32, highest_seen_timestamp: u64, min_final_cltv_expiry_delta: Option) -> 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(payment_hash: PaymentHash, payment_data: &msgs::F } } }, + Ok(Method::SpontaneousPayment) => { + let mut hmac = HmacEngine::::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) }), } } diff --git a/lightning/src/ln/onion_payment.rs b/lightning/src/ln/onion_payment.rs index a62846f75..3da1830df 100644 --- a/lightning/src/ln/onion_payment.rs +++ b/lightning/src/ln/onion_payment.rs @@ -135,14 +135,14 @@ pub(super) fn create_recv_pending_htlc_info( ) -> Result { 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 { diff --git a/pending_changelog/3383-deprecate-old-inbounds.txt b/pending_changelog/3383-deprecate-old-inbounds.txt new file mode 100644 index 000000000..654cbcb50 --- /dev/null +++ b/pending_changelog/3383-deprecate-old-inbounds.txt @@ -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 + +