Merge pull request #3383 from valentinewallace/2024-09-blinded-keysend-verify

Verify blinded keysend payments
This commit is contained in:
Matt Corallo 2024-11-12 15:00:46 +00:00 committed by GitHub
commit b0bd4371d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 226 additions and 121 deletions

View file

@ -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]

View file

@ -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);

View file

@ -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()),

View file

@ -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());
}

View file

@ -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) }),
}
}

View file

@ -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 {

View 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