From 45750cefd877f31f367b68a595d845b50121cc76 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 27 Mar 2026 11:24:21 +0000 Subject: [PATCH 1/2] Refer to payment info as `info` in `inbound_payment` not `metadata` `payment_metadata` is a separate concept at the BOLT 11 layer (similar to payment secret, but arbitrary-sized) and at the BOLT 12 layer, so referring to payment information as "payment metadata" is confusing. Instead, use simply "payment info". --- lightning/src/ln/inbound_payment.rs | 128 +++++++++++++--------------- 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index d70a20eaf44..5acb6d1004b 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -28,10 +28,10 @@ use crate::util::logger::Logger; use crate::prelude::*; pub(crate) const IV_LEN: usize = 16; -const METADATA_LEN: usize = 16; -const METADATA_KEY_LEN: usize = 32; +const INFO_LEN: usize = 16; +const INFO_KEY_LEN: usize = 32; const AMT_MSAT_LEN: usize = 8; -// Used to shift the payment type bits to take up the top 3 bits of the metadata bytes, or to +// Used to shift the payment type bits to take up the top 3 bits of the info bytes, or to // retrieve said payment type bits. const METHOD_TYPE_OFFSET: usize = 5; @@ -40,20 +40,20 @@ const METHOD_TYPE_OFFSET: usize = 5; /// [`NodeSigner::get_expanded_key`]: crate::sign::NodeSigner::get_expanded_key #[derive(Hash, Copy, Clone, PartialEq, Eq, Debug)] pub struct ExpandedKey { - /// The key used to encrypt the bytes containing the payment metadata (i.e. the amount and + /// The key used to encrypt the bytes containing the payment info (i.e. the amount and /// expiry, included for payment verification on decryption). - metadata_key: [u8; 32], - /// The key used to authenticate an LDK-provided payment hash and metadata as previously + info_key: [u8; 32], + /// The key used to authenticate an LDK-provided payment hash and info as previously /// registered with LDK. ldk_pmt_hash_key: [u8; 32], - /// The key used to authenticate a user-provided payment hash and metadata as previously + /// The key used to authenticate a user-provided payment hash and info as previously /// registered with LDK. user_pmt_hash_key: [u8; 32], /// The base key used to derive signing keys and authenticate messages for BOLT 12 Offers. 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 + /// The key used to authenticate spontaneous payments' info as previously registered with LDK /// for inclusion in a blinded path. spontaneous_pmt_key: [u8; 32], /// The key used to authenticate phantom-node-shared blinded paths as generated by us. Note @@ -68,7 +68,7 @@ impl ExpandedKey { /// It is recommended to cache this value and not regenerate it for each new inbound payment. pub fn new(key_material: [u8; 32]) -> ExpandedKey { let ( - metadata_key, + info_key, ldk_pmt_hash_key, user_pmt_hash_key, offers_base_key, @@ -77,7 +77,7 @@ impl ExpandedKey { phantom_node_blinded_path_key, ) = hkdf_extract_expand_7x(b"LDK Inbound Payment Key Expansion", &key_material); Self { - metadata_key, + info_key, ldk_pmt_hash_key, user_pmt_hash_key, offers_base_key, @@ -128,7 +128,7 @@ impl Method { } } -fn min_final_cltv_expiry_delta_from_metadata(bytes: [u8; METADATA_LEN]) -> u16 { +fn min_final_cltv_expiry_delta_from_info(bytes: [u8; INFO_LEN]) -> u16 { let expiry_bytes = &bytes[AMT_MSAT_LEN..]; u16::from_be_bytes([expiry_bytes[0], expiry_bytes[1]]) } @@ -151,7 +151,7 @@ pub fn create( keys: &ExpandedKey, min_value_msat: Option, invoice_expiry_delta_secs: u32, entropy_source: &ES, current_time: u64, min_final_cltv_expiry_delta: Option, ) -> Result<(PaymentHash, PaymentSecret), ()> { - let metadata_bytes = construct_metadata_bytes( + let info_bytes = construct_info_bytes( min_value_msat, if min_final_cltv_expiry_delta.is_some() { Method::LdkPaymentHashCustomFinalCltv @@ -169,11 +169,11 @@ pub fn create( let mut hmac = HmacEngine::::new(&keys.ldk_pmt_hash_key); hmac.input(&iv_bytes); - hmac.input(&metadata_bytes); + hmac.input(&info_bytes); let payment_preimage_bytes = Hmac::from_engine(hmac).to_byte_array(); let ldk_pmt_hash = PaymentHash(Sha256::hash(&payment_preimage_bytes).to_byte_array()); - let payment_secret = construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key); + let payment_secret = construct_payment_secret(&iv_bytes, &info_bytes, &keys.info_key); Ok((ldk_pmt_hash, payment_secret)) } @@ -191,7 +191,7 @@ pub fn create_from_hash( keys: &ExpandedKey, min_value_msat: Option, payment_hash: PaymentHash, invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option, ) -> Result { - let metadata_bytes = construct_metadata_bytes( + let info_bytes = construct_info_bytes( min_value_msat, if min_final_cltv_expiry_delta.is_some() { Method::UserPaymentHashCustomFinalCltv @@ -204,21 +204,21 @@ pub fn create_from_hash( )?; let mut hmac = HmacEngine::::new(&keys.user_pmt_hash_key); - hmac.input(&metadata_bytes); + hmac.input(&info_bytes); hmac.input(&payment_hash.0); 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)) + Ok(construct_payment_secret(&iv_bytes, &info_bytes, &keys.info_key)) } pub(crate) 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( + let info_bytes = construct_info_bytes( min_value_msat, Method::SpontaneousPayment, invoice_expiry_delta_secs, @@ -227,13 +227,13 @@ pub(crate) fn create_for_spontaneous_payment( )?; let mut hmac = HmacEngine::::new(&keys.spontaneous_pmt_key); - hmac.input(&metadata_bytes); + hmac.input(&info_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)) + Ok(construct_payment_secret(&iv_bytes, &info_bytes, &keys.info_key)) } pub(crate) fn calculate_absolute_expiry( @@ -247,10 +247,10 @@ pub(crate) fn calculate_absolute_expiry( highest_seen_timestamp + invoice_expiry_delta_secs as u64 + 7200 } -fn construct_metadata_bytes( +fn construct_info_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], ()> { +) -> Result<[u8; INFO_LEN], ()> { if min_value_msat.is_some() && min_value_msat.unwrap() > MAX_VALUE_MSAT { return Err(()); } @@ -285,41 +285,35 @@ fn construct_metadata_bytes( expiry_bytes[1] |= bytes[1]; } - let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN]; + let mut info_bytes: [u8; INFO_LEN] = [0; INFO_LEN]; - metadata_bytes[..AMT_MSAT_LEN].copy_from_slice(&min_amt_msat_bytes); - metadata_bytes[AMT_MSAT_LEN..].copy_from_slice(&expiry_bytes); + info_bytes[..AMT_MSAT_LEN].copy_from_slice(&min_amt_msat_bytes); + info_bytes[AMT_MSAT_LEN..].copy_from_slice(&expiry_bytes); - Ok(metadata_bytes) + Ok(info_bytes) } fn construct_payment_secret( - iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METADATA_LEN], - metadata_key: &[u8; METADATA_KEY_LEN], + iv_bytes: &[u8; IV_LEN], info_bytes: &[u8; INFO_LEN], info_key: &[u8; INFO_KEY_LEN], ) -> PaymentSecret { let mut payment_secret_bytes: [u8; 32] = [0; 32]; - let (iv_slice, encrypted_metadata_slice) = payment_secret_bytes.split_at_mut(IV_LEN); + let (iv_slice, encrypted_info_slice) = payment_secret_bytes.split_at_mut(IV_LEN); iv_slice.copy_from_slice(iv_bytes); - ChaCha20::encrypt_single_block( - metadata_key, - iv_bytes, - encrypted_metadata_slice, - metadata_bytes, - ); + ChaCha20::encrypt_single_block(info_key, iv_bytes, encrypted_info_slice, info_bytes); PaymentSecret(payment_secret_bytes) } /// Check that an inbound payment's `payment_data` field is sane. /// /// LDK does not store any data for pending inbound payments. Instead, we construct our payment -/// secret (and, if supplied by LDK, our payment preimage) to include encrypted metadata about the -/// payment. +/// secret (and, if supplied by LDK, our payment preimage) to include encrypted information about +/// the payment. /// -/// For payments without a custom `min_final_cltv_expiry_delta`, the metadata is constructed as: +/// For payments without a custom `min_final_cltv_expiry_delta`, the payment info is: /// payment method (3 bits) || payment amount (8 bytes - 3 bits) || expiry (8 bytes) /// -/// For payments including a custom `min_final_cltv_expiry_delta`, the metadata is constructed as: +/// For payments including a custom `min_final_cltv_expiry_delta`, the payment info is: /// 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_expanded_key`]. @@ -332,14 +326,14 @@ fn construct_payment_secret( /// method is called, then the payment method bits mentioned above are represented internally as /// [`Method::LdkPaymentHash`]. If the latter, [`Method::UserPaymentHash`]. /// -/// For the former method, the payment preimage is constructed as an HMAC of payment metadata and -/// random bytes. Because the payment secret is also encoded with these random bytes and metadata -/// (with the metadata encrypted with a block cipher), we're able to authenticate the preimage on +/// For the former method, the payment preimage is constructed as an HMAC of payment info and +/// random bytes. Because the payment secret is also encoded with these random bytes and info +/// (with the info encrypted with a block cipher), we're able to authenticate the preimage on /// payment receipt. /// /// For the latter, the payment secret instead contains an HMAC of the user-provided payment hash -/// and payment metadata (encrypted with a block cipher), allowing us to authenticate the payment -/// hash and metadata on payment receipt. +/// and payment info (encrypted with a block cipher), allowing us to authenticate the payment +/// hash and info on payment receipt. /// /// See [`ExpandedKey`] docs for more info on the individual keys used. /// @@ -350,14 +344,13 @@ pub(super) fn verify( payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData, highest_seen_timestamp: u64, keys: &ExpandedKey, logger: &L, ) -> Result<(Option, Option), ()> { - let (iv_bytes, metadata_bytes) = decrypt_metadata(payment_data.payment_secret, keys); + let (iv_bytes, info_bytes) = decrypt_info(payment_data.payment_secret, keys); - let payment_type_res = - Method::from_bits((metadata_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET); + let payment_type_res = Method::from_bits((info_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..]); + let mut expiry_bytes = [0; INFO_LEN - AMT_MSAT_LEN]; + amt_msat_bytes.copy_from_slice(&info_bytes[..AMT_MSAT_LEN]); + expiry_bytes.copy_from_slice(&info_bytes[AMT_MSAT_LEN..]); // Zero out the bits reserved to indicate the payment type. amt_msat_bytes[0] &= 0b00011111; let mut min_final_cltv_expiry_delta = None; @@ -368,7 +361,7 @@ pub(super) fn verify( match payment_type_res { Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => { let mut hmac = HmacEngine::::new(&keys.user_pmt_hash_key); - hmac.input(&metadata_bytes[..]); + hmac.input(&info_bytes[..]); hmac.input(&payment_hash.0); if !fixed_time_eq( &iv_bytes, @@ -383,7 +376,7 @@ pub(super) fn verify( } }, Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => { - match derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys) { + match derive_ldk_payment_preimage(payment_hash, &iv_bytes, &info_bytes, keys) { Ok(preimage) => payment_preimage = Some(preimage), Err(bad_preimage_bytes) => { log_trace!( @@ -398,7 +391,7 @@ pub(super) fn verify( }, Ok(Method::SpontaneousPayment) => { let mut hmac = HmacEngine::::new(&keys.spontaneous_pmt_key); - hmac.input(&metadata_bytes[..]); + hmac.input(&info_bytes[..]); if !fixed_time_eq( &iv_bytes, &Hmac::from_engine(hmac).to_byte_array().split_at_mut(IV_LEN).0, @@ -420,8 +413,7 @@ pub(super) fn verify( 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)); + min_final_cltv_expiry_delta = Some(min_final_cltv_expiry_delta_from_info(info_bytes)); // Zero out first two bytes of expiry reserved for `min_final_cltv_expiry_delta`. expiry_bytes[0] &= 0; expiry_bytes[1] &= 0; @@ -448,11 +440,11 @@ pub(super) fn verify( pub(super) fn get_payment_preimage( payment_hash: PaymentHash, payment_secret: PaymentSecret, keys: &ExpandedKey, ) -> Result { - let (iv_bytes, metadata_bytes) = decrypt_metadata(payment_secret, keys); + let (iv_bytes, info_bytes) = decrypt_info(payment_secret, keys); - match Method::from_bits((metadata_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET) { + match Method::from_bits((info_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET) { Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => { - derive_ldk_payment_preimage(payment_hash, &iv_bytes, &metadata_bytes, keys).map_err( + derive_ldk_payment_preimage(payment_hash, &iv_bytes, &info_bytes, keys).map_err( |bad_preimage_bytes| APIError::APIMisuseError { err: format!( "Payment hash {} did not match decoded preimage {}", @@ -477,33 +469,33 @@ pub(super) fn get_payment_preimage( } } -fn decrypt_metadata( +fn decrypt_info( payment_secret: PaymentSecret, keys: &ExpandedKey, -) -> ([u8; IV_LEN], [u8; METADATA_LEN]) { +) -> ([u8; IV_LEN], [u8; INFO_LEN]) { let mut iv_bytes = [0; IV_LEN]; - let (iv_slice, encrypted_metadata_bytes) = payment_secret.0.split_at(IV_LEN); + let (iv_slice, encrypted_info_bytes) = payment_secret.0.split_at(IV_LEN); iv_bytes.copy_from_slice(iv_slice); - let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN]; + let mut info_bytes: [u8; INFO_LEN] = [0; INFO_LEN]; ChaCha20::encrypt_single_block( - &keys.metadata_key, + &keys.info_key, &iv_bytes, - &mut metadata_bytes, - encrypted_metadata_bytes, + &mut info_bytes, + encrypted_info_bytes, ); - (iv_bytes, metadata_bytes) + (iv_bytes, info_bytes) } // Errors if the payment preimage doesn't match `payment_hash`. Returns the bad preimage bytes in // this case. fn derive_ldk_payment_preimage( - payment_hash: PaymentHash, iv_bytes: &[u8; IV_LEN], metadata_bytes: &[u8; METADATA_LEN], + payment_hash: PaymentHash, iv_bytes: &[u8; IV_LEN], info_bytes: &[u8; INFO_LEN], keys: &ExpandedKey, ) -> Result { let mut hmac = HmacEngine::::new(&keys.ldk_pmt_hash_key); hmac.input(iv_bytes); - hmac.input(metadata_bytes); + hmac.input(info_bytes); let decoded_payment_preimage = Hmac::from_engine(hmac).to_byte_array(); if !fixed_time_eq(&payment_hash.0, &Sha256::hash(&decoded_payment_preimage).to_byte_array()) { return Err(decoded_payment_preimage); From d9e8f0f39fe2e02f0d470ab7a4cb45fc43b1c38d Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 31 Mar 2026 18:00:00 +0000 Subject: [PATCH 2/2] Commit to payment_metadata in inbound payment HMAC When payment_metadata is set in a BOLT 11 invoice, users expect to receive it back as-is in the payment onion. In order to ensure it isn't tampered with, they presumably will add an HMAC, or worse, not add one and forget that it can be tampered with. Instead, here we include it in the HMAC computation for the payment secret. This ensures that the sender must relay the correct metadata for the payment to be accepted by the receiver, binding the metadata to the payment cryptographically. The metadata is only included in the HMAC when present, so existing payments without metadata continue to verify correctly. However, this does break receiving payments with metadata today. On an upgrade this seems acceptable to me given we have seen almost no use of payment metadata in practice. Co-Authored-By: Claude Opus 4.6 (1M context) --- fuzz/src/chanmon_consistency.rs | 2 +- fuzz/src/full_stack.rs | 2 +- .../tests/lsps2_integration_tests.rs | 2 +- lightning/src/ln/bolt11_payment_tests.rs | 4 +- lightning/src/ln/channelmanager.rs | 44 +++++++++++---- lightning/src/ln/functional_test_utils.rs | 1 + lightning/src/ln/functional_tests.rs | 38 +++++++------ lightning/src/ln/inbound_payment.rs | 53 ++++++++++++++----- lightning/src/ln/invoice_utils.rs | 9 ++-- .../src/ln/max_payment_path_len_tests.rs | 40 +++++++++++--- lightning/src/ln/payment_tests.rs | 30 +++++++---- pending_changelog/matt-commit-to-metadata.txt | 6 +++ 12 files changed, 171 insertions(+), 60 deletions(-) create mode 100644 pending_changelog/matt-commit-to-metadata.txt diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs index f20f93c789c..39ddbf393b0 100644 --- a/fuzz/src/chanmon_consistency.rs +++ b/fuzz/src/chanmon_consistency.rs @@ -572,7 +572,7 @@ fn get_payment_secret_hash(dest: &ChanMan, payment_ctr: &mut u64) -> (PaymentSec *payment_ctr += 1; let payment_hash = PaymentHash(Sha256::hash(&[*payment_ctr as u8]).to_byte_array()); let payment_secret = dest - .create_inbound_payment_for_hash(payment_hash, None, 3600, None) + .create_inbound_payment_for_hash(payment_hash, None, 3600, None, None) .expect("create_inbound_payment_for_hash failed"); (payment_secret, payment_hash) } diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs index c1d7982e5e4..35894c8d895 100644 --- a/fuzz/src/full_stack.rs +++ b/fuzz/src/full_stack.rs @@ -841,7 +841,7 @@ pub fn do_test(mut data: &[u8], logger: &Arc PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array()); // 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, None); + let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None, None); }, 9 => { for payment in payments_received.drain(..) { diff --git a/lightning-liquidity/tests/lsps2_integration_tests.rs b/lightning-liquidity/tests/lsps2_integration_tests.rs index fbff2eae4cd..92e6b33ebb6 100644 --- a/lightning-liquidity/tests/lsps2_integration_tests.rs +++ b/lightning-liquidity/tests/lsps2_integration_tests.rs @@ -122,7 +122,7 @@ fn create_jit_invoice( let min_final_cltv_expiry_delta = MIN_FINAL_CLTV_EXPIRY_DELTA + 2; let (payment_hash, payment_secret) = node .node - .create_inbound_payment(None, expiry_secs, Some(min_final_cltv_expiry_delta)) + .create_inbound_payment(None, expiry_secs, Some(min_final_cltv_expiry_delta), None) .map_err(|e| { log_error!(node.logger, "Failed to register inbound payment: {:?}", e); })?; diff --git a/lightning/src/ln/bolt11_payment_tests.rs b/lightning/src/ln/bolt11_payment_tests.rs index 8c2ac155ce7..733e26d0f1b 100644 --- a/lightning/src/ln/bolt11_payment_tests.rs +++ b/lightning/src/ln/bolt11_payment_tests.rs @@ -31,7 +31,7 @@ fn payment_metadata_end_to_end_for_invoice_with_amount() { let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42]; let (payment_hash, payment_secret) = - nodes[1].node.create_inbound_payment(None, 7200, None).unwrap(); + nodes[1].node.create_inbound_payment(None, 7200, None, Some(&payment_metadata)).unwrap(); let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let invoice = InvoiceBuilder::new(Currency::Bitcoin) @@ -98,7 +98,7 @@ fn payment_metadata_end_to_end_for_invoice_with_no_amount() { let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42]; let (payment_hash, payment_secret) = - nodes[1].node.create_inbound_payment(None, 7200, None).unwrap(); + nodes[1].node.create_inbound_payment(None, 7200, None, Some(&payment_metadata)).unwrap(); let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let invoice = InvoiceBuilder::new(Currency::Bitcoin) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 660f61f0f57..47626e60cd5 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -8481,6 +8481,7 @@ impl< let verify_res = inbound_payment::verify( payment_hash, &payment_data, + onion_fields.payment_metadata.as_deref(), self.highest_seen_timestamp.load(Ordering::Acquire) as u64, &self.inbound_payment_key, &self.logger, @@ -14123,7 +14124,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ ) -> Result> { let Bolt11InvoiceParameters { amount_msats, description, invoice_expiry_delta_secs, min_final_cltv_expiry_delta, - payment_hash, + payment_hash, payment_metadata, } = params; let currency = @@ -14156,6 +14157,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ payment_hash, amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32), min_final_cltv_expiry_delta, + payment_metadata.as_deref(), ) .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; (payment_hash, payment_secret) @@ -14165,6 +14167,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ .create_inbound_payment( amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32), min_final_cltv_expiry_delta, + payment_metadata.as_deref(), ) .map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))? }, @@ -14203,7 +14206,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ invoice = invoice.private_route(hint); } - let raw_invoice = invoice.build_raw().map_err(|e| SignOrCreationError::CreationError(e))?; + let raw_invoice = if let Some(payment_metadata) = payment_metadata { + invoice.payment_metadata(payment_metadata).build_raw() + } else { + invoice.build_raw() + }.map_err(|e| SignOrCreationError::CreationError(e))?; let signature = self.node_signer.sign_invoice(&raw_invoice, Recipient::Node); raw_invoice @@ -14282,6 +14289,14 @@ pub struct Bolt11InvoiceParameters { /// involving another protocol where the payment hash is also involved outside the scope of /// lightning. pub payment_hash: Option, + + /// The `payment_metadata` to include in the invoice. This is provided back to us in the payment + /// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via + /// [`Event::PaymentClaimable::onion_fields`]. + /// + /// Note that because it is exposed to the sender in the invoice you should consider encrypting + /// it. It is committed to, however, so cannot be modified by the sender. + pub payment_metadata: Option>, } impl Default for Bolt11InvoiceParameters { @@ -14292,6 +14307,7 @@ impl Default for Bolt11InvoiceParameters { invoice_expiry_delta_secs: None, min_final_cltv_expiry_delta: None, payment_hash: None, + payment_metadata: None, } } } @@ -14776,7 +14792,7 @@ impl< refund, self.list_usable_channels(), |amount_msats, relative_expiry| { - self.create_inbound_payment(Some(amount_msats), relative_expiry, None) + self.create_inbound_payment(Some(amount_msats), relative_expiry, None, None) .map_err(|()| Bolt12SemanticError::InvalidAmount) }, )?; @@ -14885,7 +14901,7 @@ impl< /// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash pub fn create_inbound_payment( &self, min_value_msat: Option, invoice_expiry_delta_secs: u32, - min_final_cltv_expiry_delta: Option, + min_final_cltv_expiry_delta: Option, payment_metadata: Option<&[u8]>, ) -> Result<(PaymentHash, PaymentSecret), ()> { inbound_payment::create( &self.inbound_payment_key, @@ -14894,6 +14910,7 @@ impl< &self.entropy_source, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, min_final_cltv_expiry_delta, + payment_metadata, ) } @@ -14946,6 +14963,7 @@ impl< pub fn create_inbound_payment_for_hash( &self, payment_hash: PaymentHash, min_value_msat: Option, invoice_expiry_delta_secs: u32, min_final_cltv_expiry: Option, + payment_metadata: Option<&[u8]>, ) -> Result { inbound_payment::create_from_hash( &self.inbound_payment_key, @@ -14954,6 +14972,7 @@ impl< invoice_expiry_delta_secs, self.highest_seen_timestamp.load(Ordering::Acquire) as u64, min_final_cltv_expiry, + payment_metadata, ) } @@ -14963,9 +14982,15 @@ impl< /// [`create_inbound_payment`]: Self::create_inbound_payment pub fn get_payment_preimage( &self, payment_hash: PaymentHash, payment_secret: PaymentSecret, + payment_metadata: Option<&[u8]>, ) -> Result { let expanded_key = &self.inbound_payment_key; - inbound_payment::get_payment_preimage(payment_hash, payment_secret, expanded_key) + inbound_payment::get_payment_preimage( + payment_hash, + payment_secret, + payment_metadata, + expanded_key, + ) } /// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively @@ -17017,7 +17042,8 @@ impl< self.create_inbound_payment( Some(amount_msats), relative_expiry, - None + None, + None, ).map_err(|_| Bolt12SemanticError::InvalidAmount) }; @@ -21275,7 +21301,7 @@ mod tests { // payment verification fails as expected. let mut bad_payment_hash = payment_hash.clone(); bad_payment_hash.0[0] += 1; - match inbound_payment::verify(bad_payment_hash, &payment_data, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger) { + match inbound_payment::verify(bad_payment_hash, &payment_data, None, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger) { Ok(_) => panic!("Unexpected ok"), Err(()) => { nodes[0].logger.assert_log_contains("lightning::ln::inbound_payment", "Failing HTLC with user-generated payment_hash", 1); @@ -21283,7 +21309,7 @@ mod tests { } // Check that using the original payment hash succeeds. - assert!(inbound_payment::verify(payment_hash, &payment_data, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger).is_ok()); + assert!(inbound_payment::verify(payment_hash, &payment_data, None, nodes[0].node.highest_seen_timestamp.load(Ordering::Acquire) as u64, &nodes[0].node.inbound_payment_key, &nodes[0].logger).is_ok()); } fn check_not_connected_to_peer_error( @@ -21956,7 +21982,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[..]).to_byte_array()); - let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap(); + let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None, None).unwrap(); $node_a.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret, 10_000), PaymentId(payment_hash.0), diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 84cdf785da5..3e5b7e3296f 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -2818,6 +2818,7 @@ pub fn get_payment_preimage_hash( min_value_msat, 7200, min_final_cltv_expiry_delta, + None, ) .unwrap(); (payment_preimage, payment_hash, payment_secret) diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 7ed46922d8a..894497b927d 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -293,8 +293,10 @@ pub fn test_duplicate_htlc_different_direction_onchain() { let (payment_preimage, payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1]], 900_000); let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[0], payment_value_msats); - let node_a_payment_secret = - nodes[0].node.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap(); + let node_a_payment_secret = nodes[0] + .node + .create_inbound_payment_for_hash(payment_hash, None, 7200, None, None) + .unwrap(); send_along_route_with_secret( &nodes[1], route, @@ -4156,8 +4158,10 @@ pub fn test_duplicate_payment_hash_one_failure_one_success() { let (our_payment_preimage, dup_payment_hash, ..) = route_payment(&nodes[0], &[&nodes[1], &nodes[2], &nodes[3]], 900_000); - let payment_secret = - nodes[4].node.create_inbound_payment_for_hash(dup_payment_hash, None, 7200, None).unwrap(); + let payment_secret = nodes[4] + .node + .create_inbound_payment_for_hash(dup_payment_hash, None, 7200, None, None) + .unwrap(); let payment_params = PaymentParameters::from_node_id(node_e_id, TEST_FINAL_CLTV) .with_bolt11_features(nodes[4].node.bolt11_invoice_features()) .unwrap(); @@ -4424,13 +4428,13 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno // 2nd HTLC (not added - smaller than dust limit + HTLC tx fee): let path_5: &[&[_]] = &[&[&nodes[2], &nodes[3], &nodes[5]]]; let payment_secret = - nodes[5].node.create_inbound_payment_for_hash(hash_1, None, 7200, None).unwrap(); + nodes[5].node.create_inbound_payment_for_hash(hash_1, None, 7200, None, None).unwrap(); let route = route_to_5.clone(); send_along_route_with_secret(&nodes[1], route, path_5, dust_limit_msat, hash_1, payment_secret); // 3rd HTLC (not added - smaller than dust limit + HTLC tx fee): let payment_secret = - nodes[5].node.create_inbound_payment_for_hash(hash_2, None, 7200, None).unwrap(); + nodes[5].node.create_inbound_payment_for_hash(hash_2, None, 7200, None, None).unwrap(); let route = route_to_5; send_along_route_with_secret(&nodes[1], route, path_5, dust_limit_msat, hash_2, payment_secret); @@ -4443,12 +4447,12 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno // 6th HTLC: let payment_secret = - nodes[5].node.create_inbound_payment_for_hash(hash_3, None, 7200, None).unwrap(); + nodes[5].node.create_inbound_payment_for_hash(hash_3, None, 7200, None, None).unwrap(); send_along_route_with_secret(&nodes[1], route.clone(), path_5, 1000000, hash_3, payment_secret); // 7th HTLC: let payment_secret = - nodes[5].node.create_inbound_payment_for_hash(hash_4, None, 7200, None).unwrap(); + nodes[5].node.create_inbound_payment_for_hash(hash_4, None, 7200, None, None).unwrap(); send_along_route_with_secret(&nodes[1], route, path_5, 1000000, hash_4, payment_secret); // 8th HTLC: @@ -4457,7 +4461,7 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno // 9th HTLC (not added - smaller than dust limit + HTLC tx fee): let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], dust_limit_msat); let payment_secret = - nodes[5].node.create_inbound_payment_for_hash(hash_5, None, 7200, None).unwrap(); + nodes[5].node.create_inbound_payment_for_hash(hash_5, None, 7200, None, None).unwrap(); send_along_route_with_secret(&nodes[1], route, path_5, dust_limit_msat, hash_5, payment_secret); // 10th HTLC (not added - smaller than dust limit + HTLC tx fee): @@ -4466,7 +4470,7 @@ fn do_test_fail_backwards_unrevoked_remote_announce(deliver_last_raa: bool, anno // 11th HTLC: let (route, _, _, _) = get_route_and_payment_hash!(nodes[1], nodes[5], 1000000); let payment_secret = - nodes[5].node.create_inbound_payment_for_hash(hash_6, None, 7200, None).unwrap(); + nodes[5].node.create_inbound_payment_for_hash(hash_6, None, 7200, None, None).unwrap(); send_along_route_with_secret(&nodes[1], route, path_5, 1000000, hash_6, payment_secret); // Double-check that six of the new HTLC were added @@ -6061,7 +6065,7 @@ pub fn test_check_htlc_underpaying() { let (_, our_payment_hash, _) = get_payment_preimage_hash(&nodes[0], None, None); let our_payment_secret = nodes[1] .node - .create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200, None) + .create_inbound_payment_for_hash(our_payment_hash, Some(100_000), 7200, None, None) .unwrap(); let onion = RecipientOnionFields::secret_only(our_payment_secret, route.get_total_amount()); let id = PaymentId(our_payment_hash.0); @@ -7229,7 +7233,7 @@ pub fn test_preimage_storage() { { let (payment_hash, payment_secret) = - nodes[1].node.create_inbound_payment(Some(100_000), 7200, None).unwrap(); + nodes[1].node.create_inbound_payment(Some(100_000), 7200, None, None).unwrap(); let (route, _, _, _) = get_route_and_payment_hash!(nodes[0], nodes[1], 100_000); let onion = RecipientOnionFields::secret_only(payment_secret, 100_000); let id = PaymentId(payment_hash.0); @@ -7274,7 +7278,7 @@ pub fn test_bad_secret_hash() { let random_hash = PaymentHash([42; 32]); let random_secret = PaymentSecret([43; 32]); let (our_payment_hash, our_payment_secret) = - nodes[1].node.create_inbound_payment(Some(100_000), 2, None).unwrap(); + nodes[1].node.create_inbound_payment(Some(100_000), 2, None, 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 @@ -9482,9 +9486,13 @@ fn do_payment_with_custom_min_final_cltv_expiry(valid_delta: bool, use_user_hash } else { let (hash, payment_secret) = nodes[1] .node - .create_inbound_payment(Some(recv_value), 7200, Some(min_cltv_expiry_delta)) + .create_inbound_payment(Some(recv_value), 7200, Some(min_cltv_expiry_delta), None) .unwrap(); - (hash, nodes[1].node.get_payment_preimage(hash, payment_secret).unwrap(), payment_secret) + ( + hash, + nodes[1].node.get_payment_preimage(hash, payment_secret, None).unwrap(), + payment_secret, + ) }; let route = get_route!(nodes[0], payment_parameters, recv_value).unwrap(); let onion = RecipientOnionFields::secret_only(payment_secret, recv_value); diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 5acb6d1004b..ec5ac92c20e 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -150,6 +150,7 @@ fn min_final_cltv_expiry_delta_from_info(bytes: [u8; INFO_LEN]) -> u16 { pub fn create( keys: &ExpandedKey, min_value_msat: Option, invoice_expiry_delta_secs: u32, entropy_source: &ES, current_time: u64, min_final_cltv_expiry_delta: Option, + payment_metadata: Option<&[u8]>, ) -> Result<(PaymentHash, PaymentSecret), ()> { let info_bytes = construct_info_bytes( min_value_msat, @@ -170,6 +171,9 @@ pub fn create( let mut hmac = HmacEngine::::new(&keys.ldk_pmt_hash_key); hmac.input(&iv_bytes); hmac.input(&info_bytes); + if let Some(metadata) = payment_metadata { + hmac.input(metadata); + } let payment_preimage_bytes = Hmac::from_engine(hmac).to_byte_array(); let ldk_pmt_hash = PaymentHash(Sha256::hash(&payment_preimage_bytes).to_byte_array()); @@ -190,6 +194,7 @@ pub fn create( pub fn create_from_hash( keys: &ExpandedKey, min_value_msat: Option, payment_hash: PaymentHash, invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option, + payment_metadata: Option<&[u8]>, ) -> Result { let info_bytes = construct_info_bytes( min_value_msat, @@ -206,6 +211,9 @@ pub fn create_from_hash( let mut hmac = HmacEngine::::new(&keys.user_pmt_hash_key); hmac.input(&info_bytes); hmac.input(&payment_hash.0); + if let Some(metadata) = payment_metadata { + hmac.input(metadata); + } let hmac_bytes = Hmac::from_engine(hmac).to_byte_array(); let mut iv_bytes = [0 as u8; IV_LEN]; @@ -341,8 +349,9 @@ fn construct_payment_secret( /// [`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( - payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData, highest_seen_timestamp: u64, - keys: &ExpandedKey, logger: &L, + payment_hash: PaymentHash, payment_data: &msgs::FinalOnionHopData, + payment_metadata: Option<&[u8]>, highest_seen_timestamp: u64, keys: &ExpandedKey, + logger: &L, ) -> Result<(Option, Option), ()> { let (iv_bytes, info_bytes) = decrypt_info(payment_data.payment_secret, keys); @@ -363,6 +372,9 @@ pub(super) fn verify( let mut hmac = HmacEngine::::new(&keys.user_pmt_hash_key); hmac.input(&info_bytes[..]); hmac.input(&payment_hash.0); + if let Some(metadata) = payment_metadata { + hmac.input(metadata); + } if !fixed_time_eq( &iv_bytes, &Hmac::from_engine(hmac).to_byte_array().split_at_mut(IV_LEN).0, @@ -376,7 +388,13 @@ pub(super) fn verify( } }, Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => { - match derive_ldk_payment_preimage(payment_hash, &iv_bytes, &info_bytes, keys) { + match derive_ldk_payment_preimage( + payment_hash, + &iv_bytes, + &info_bytes, + payment_metadata, + keys, + ) { Ok(preimage) => payment_preimage = Some(preimage), Err(bad_preimage_bytes) => { log_trace!( @@ -438,21 +456,27 @@ pub(super) fn verify( } pub(super) fn get_payment_preimage( - payment_hash: PaymentHash, payment_secret: PaymentSecret, keys: &ExpandedKey, + payment_hash: PaymentHash, payment_secret: PaymentSecret, payment_metadata: Option<&[u8]>, + keys: &ExpandedKey, ) -> Result { let (iv_bytes, info_bytes) = decrypt_info(payment_secret, keys); match Method::from_bits((info_bytes[0] & 0b1110_0000) >> METHOD_TYPE_OFFSET) { Ok(Method::LdkPaymentHash) | Ok(Method::LdkPaymentHashCustomFinalCltv) => { - derive_ldk_payment_preimage(payment_hash, &iv_bytes, &info_bytes, keys).map_err( - |bad_preimage_bytes| APIError::APIMisuseError { - err: format!( - "Payment hash {} did not match decoded preimage {}", - &payment_hash, - log_bytes!(bad_preimage_bytes) - ), - }, + derive_ldk_payment_preimage( + payment_hash, + &iv_bytes, + &info_bytes, + payment_metadata, + keys, ) + .map_err(|bad_preimage_bytes| APIError::APIMisuseError { + err: format!( + "Payment hash {} did not match decoded preimage {}", + &payment_hash, + log_bytes!(bad_preimage_bytes) + ), + }) }, Ok(Method::UserPaymentHash) | Ok(Method::UserPaymentHashCustomFinalCltv) => { Err(APIError::APIMisuseError { @@ -491,11 +515,14 @@ fn decrypt_info( // this case. fn derive_ldk_payment_preimage( payment_hash: PaymentHash, iv_bytes: &[u8; IV_LEN], info_bytes: &[u8; INFO_LEN], - keys: &ExpandedKey, + payment_metadata: Option<&[u8]>, keys: &ExpandedKey, ) -> Result { let mut hmac = HmacEngine::::new(&keys.ldk_pmt_hash_key); hmac.input(iv_bytes); hmac.input(info_bytes); + if let Some(metadata) = payment_metadata { + hmac.input(metadata); + } let decoded_payment_preimage = Hmac::from_engine(hmac).to_byte_array(); if !fixed_time_eq(&payment_hash.0, &Sha256::hash(&decoded_payment_preimage).to_byte_array()) { return Err(decoded_payment_preimage); diff --git a/lightning/src/ln/invoice_utils.rs b/lightning/src/ln/invoice_utils.rs index 63ad110bba0..564203bf524 100644 --- a/lightning/src/ln/invoice_utils.rs +++ b/lightning/src/ln/invoice_utils.rs @@ -191,6 +191,7 @@ fn _create_phantom_invoice( invoice_expiry_delta_secs, duration_since_epoch.as_secs(), min_final_cltv_expiry_delta, + None, ) .map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; (payment_hash, payment_secret) @@ -202,6 +203,7 @@ fn _create_phantom_invoice( &entropy_source, duration_since_epoch.as_secs(), min_final_cltv_expiry_delta, + None, ) .map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))? }; @@ -670,7 +672,8 @@ mod test { let (payment_hash, payment_secret) = (invoice.payment_hash(), *invoice.payment_secret()); - let preimage = nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); + let preimage = + nodes[1].node.get_payment_preimage(payment_hash, payment_secret, None).unwrap(); // Invoice SCIDs should always use inbound SCID aliases over the real channel ID, if one is // available. @@ -1255,7 +1258,7 @@ mod test { let payment_preimage = if user_generated_pmt_hash { user_payment_preimage } else { - nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap() + nodes[1].node.get_payment_preimage(payment_hash, payment_secret, None).unwrap() }; assert_eq!(invoice.min_final_cltv_expiry_delta(), MIN_FINAL_CLTV_EXPIRY_DELTA as u64); @@ -1363,7 +1366,7 @@ mod test { let payment_amt = 20_000; let (payment_hash, _payment_secret) = - nodes[1].node.create_inbound_payment(Some(payment_amt), 3600, None).unwrap(); + nodes[1].node.create_inbound_payment(Some(payment_amt), 3600, None, None).unwrap(); let route_hints = vec![nodes[1].node.get_phantom_route_hints(), nodes[2].node.get_phantom_route_hints()]; diff --git a/lightning/src/ln/max_payment_path_len_tests.rs b/lightning/src/ln/max_payment_path_len_tests.rs index 45640d3486d..2ede955de7b 100644 --- a/lightning/src/ln/max_payment_path_len_tests.rs +++ b/lightning/src/ln/max_payment_path_len_tests.rs @@ -32,7 +32,7 @@ use crate::routing::router::{ }; use crate::sign::NodeSigner; use crate::types::features::BlindedHopFeatures; -use crate::types::payment::PaymentSecret; +use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::util::errors::APIError; use crate::util::ser::Writeable; use crate::util::test_utils; @@ -80,9 +80,29 @@ fn large_payment_metadata() { - final_payload_len_without_metadata; let mut payment_metadata = vec![42; max_metadata_len]; + let mut counter = 42; + macro_rules! get_payment_hash { + ($node: expr, $metadata: expr) => { { + let payment_preimage = PaymentPreimage([counter; 32]); + counter += 1; + let payment_hash: PaymentHash = payment_preimage.into(); + let payment_secret = $node + .node + .create_inbound_payment_for_hash( + payment_hash, + Some(amt_msat), + 7200, + None, + Some($metadata), + ) + .unwrap(); + (payment_hash, payment_preimage, payment_secret) + } }; + } + // Check that the maximum-size metadata is sendable. - let (mut route_0_1, payment_hash, payment_preimage, payment_secret) = - get_route_and_payment_hash!(&nodes[0], &nodes[1], amt_msat); + let (payment_hash, payment_preimage, payment_secret) = get_payment_hash!(nodes[1], &payment_metadata); + let (mut route_0_1, ..) = get_route_and_payment_hash!(&nodes[0], &nodes[1], amt_msat); let mut max_sized_onion = RecipientOnionFields { payment_secret: Some(payment_secret), payment_metadata: Some(payment_metadata.clone()), @@ -112,14 +132,17 @@ fn large_payment_metadata() { // Check that the payment parameter for max path length will prevent us from routing past our // next-hop peer given the payment_metadata size. - let (mut route_0_2, payment_hash_2, payment_preimage_2, payment_secret_2) = - get_route_and_payment_hash!(&nodes[0], &nodes[2], amt_msat); + + let (payment_hash_2, payment_preimage_2, payment_secret_2) = + get_payment_hash!(nodes[2], &max_sized_onion.payment_metadata.as_ref().unwrap()); + let (mut route_0_2, ..) = get_route_and_payment_hash!(&nodes[0], &nodes[2], amt_msat); let mut route_params_0_2 = route_0_2.route_params.clone().unwrap(); route_params_0_2.payment_params.max_path_length = 1; nodes[0].router.expect_find_route_query(route_params_0_2); + max_sized_onion.payment_secret = Some(payment_secret_2); let id = PaymentId(payment_hash_2.0); - let route_params = route_0_2.route_params.clone().unwrap(); + let mut route_params = route_0_2.route_params.clone().unwrap(); let err = nodes[0] .node .send_payment(payment_hash_2, max_sized_onion.clone(), id, route_params, Retry::Attempts(0)) @@ -130,6 +153,9 @@ fn large_payment_metadata() { let mut too_large_onion = max_sized_onion.clone(); too_large_onion.payment_metadata.as_mut().map(|mut md| md.push(42)); too_large_onion.total_mpp_amount_msat = MIN_FINAL_VALUE_ESTIMATE_WITH_OVERPAY; + let (payment_hash_2, payment_preimage_2, payment_secret_2) = + get_payment_hash!(nodes[2], &too_large_onion.payment_metadata.as_ref().unwrap()); + too_large_onion.payment_secret = Some(payment_secret_2); // First confirm we'll fail to create the onion packet directly. let secp_ctx = Secp256k1::signing_only(); @@ -164,6 +190,8 @@ fn large_payment_metadata() { // If we remove enough payment_metadata bytes to allow for 2 hops, we're now able to send to // nodes[2]. let two_hop_metadata = vec![42; max_metadata_len - INTERMED_PAYLOAD_LEN_ESTIMATE]; + let (payment_hash_2, payment_preimage_2, payment_secret_2) = + get_payment_hash!(nodes[2], &two_hop_metadata); let mut onion_allowing_2_hops = RecipientOnionFields { payment_secret: Some(payment_secret_2), payment_metadata: Some(two_hop_metadata.clone()), diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index be52459a872..d7bef35ed06 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -1506,7 +1506,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, None).unwrap(); + nodes[1].node.create_inbound_payment(Some(amt_msat), expiry_secs, None, None).unwrap(); let payment_params = PaymentParameters::from_node_id(node_b_id, TEST_FINAL_CLTV) .with_bolt11_features(nodes[1].node.bolt11_invoice_features()) @@ -1519,7 +1519,8 @@ fn get_ldk_payment_preimage() { check_added_monitors(&nodes[0], 1); // Make sure to use `get_payment_preimage` - let preimage = Some(nodes[1].node.get_payment_preimage(payment_hash, payment_secret).unwrap()); + let preimage = + Some(nodes[1].node.get_payment_preimage(payment_hash, payment_secret, None).unwrap()); let mut events = nodes[0].node.get_and_clear_pending_msg_events(); assert_eq!(events.len(), 1); let event = events.pop().unwrap(); @@ -2240,7 +2241,7 @@ fn do_test_intercepted_payment(test: InterceptTest) { let route = get_route(&nodes[0], &route_params).unwrap(); let (hash, payment_secret) = - nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap(); + nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None, None).unwrap(); let onion = RecipientOnionFields::secret_only(payment_secret, amt_msat); let id = PaymentId(hash.0); nodes[0].node.send_payment_with_route(route.clone(), hash, onion, id).unwrap(); @@ -2349,7 +2350,8 @@ fn do_test_intercepted_payment(test: InterceptTest) { do_commitment_signed_dance(&nodes[2], &nodes[1], commitment, false, true); expect_and_process_pending_htlcs(&nodes[2], false); - let preimage = Some(nodes[2].node.get_payment_preimage(hash, payment_secret).unwrap()); + let preimage = + Some(nodes[2].node.get_payment_preimage(hash, payment_secret, None).unwrap()); expect_payment_claimable!(&nodes[2], hash, payment_secret, amt_msat, preimage, node_c_id); let path: &[&[_]] = &[&[&nodes[1], &nodes[2]]]; @@ -2475,7 +2477,7 @@ fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) { .unwrap(); let route_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat); let (payment_hash, payment_secret) = - nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None).unwrap(); + nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60, None, None).unwrap(); let onion = RecipientOnionFields::secret_only(payment_secret, amt_msat); let id = PaymentId(payment_hash.0); @@ -2531,7 +2533,7 @@ fn do_accept_underpaying_htlcs_config(num_mpp_parts: usize) { // Claim the payment and check that the skimmed fee is as expected. let payment_preimage = - nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); + nodes[2].node.get_payment_preimage(payment_hash, payment_secret, None).unwrap(); let events = nodes[2].node.get_and_clear_pending_events(); assert_eq!(events.len(), 1); match events[0] { @@ -4807,10 +4809,20 @@ fn do_test_payment_metadata_consistency(do_reload: bool, do_modify: bool) { // Pay more than half of each channel's max, requiring MPP let amt_msat = 750_000_000; - let (payment_preimage, payment_hash, payment_secret) = - get_payment_preimage_hash(&nodes[3], Some(amt_msat), None); - let payment_id = PaymentId(payment_hash.0); let payment_metadata = vec![44, 49, 52, 142]; + let payment_preimage = PaymentPreimage([42; 32]); + let payment_hash: PaymentHash = payment_preimage.into(); + let payment_secret = nodes[3] + .node + .create_inbound_payment_for_hash( + payment_hash, + Some(amt_msat), + 7200, + None, + Some(&payment_metadata), + ) + .unwrap(); + let payment_id = PaymentId(payment_hash.0); let payment_params = PaymentParameters::from_node_id(node_d_id, TEST_FINAL_CLTV) .with_bolt11_features(nodes[1].node.bolt11_invoice_features()) diff --git a/pending_changelog/matt-commit-to-metadata.txt b/pending_changelog/matt-commit-to-metadata.txt new file mode 100644 index 00000000000..400af0fe952 --- /dev/null +++ b/pending_changelog/matt-commit-to-metadata.txt @@ -0,0 +1,6 @@ +# Backwards compat + * Payment metadata is now comitted to in the HMAC used to build payment secrets. + As such, any existing BOLT 11 invoices issued with payment metadata will be + implicitly invalidated on upgrade and any BOLT 11 invoices issued with payment + metadata will be invalidated on downgrade. If this is problematic for you + please reach out.