Skip to content

Commit 1ab220b

Browse files
authored
Merge pull request #4528 from TheBlueMatt/2026-03-commit-to-metadata
Commit to payment_metadata in inbound payment HMAC
2 parents 783c280 + 44828f7 commit 1ab220b

14 files changed

Lines changed: 259 additions & 142 deletions

fuzz/src/chanmon_consistency.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1370,7 +1370,7 @@ impl PaymentTracker {
13701370
payment_preimage.0[0..8].copy_from_slice(&self.payment_ctr.to_be_bytes());
13711371
let hash = PaymentHash(Sha256::hash(&payment_preimage.0).to_byte_array());
13721372
let secret = dest
1373-
.create_inbound_payment_for_hash(hash, None, 3600, None)
1373+
.create_inbound_payment_for_hash(hash, None, 3600, None, None)
13741374
.expect("create_inbound_payment_for_hash failed");
13751375
assert!(self.payment_preimages.insert(hash, payment_preimage).is_none());
13761376
let mut id = PaymentId([0; 32]);

fuzz/src/full_stack.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -837,11 +837,10 @@ pub fn do_test(mut data: &[u8], logger: &Arc<dyn Logger + MaybeSend + MaybeSync>
837837
},
838838
16 => {
839839
let payment_preimage = PaymentPreimage(keys_manager.get_secure_random_bytes());
840-
let payment_hash =
841-
PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array());
840+
let hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array());
842841
// Note that this may fail - our hashes may collide and we'll end up trying to
843842
// double-register the same payment_hash.
844-
let _ = channelmanager.create_inbound_payment_for_hash(payment_hash, None, 1, None);
843+
let _ = channelmanager.create_inbound_payment_for_hash(hash, None, 1, None, None);
845844
},
846845
9 => {
847846
for payment in payments_received.drain(..) {

lightning-invoice/src/lib.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -880,11 +880,10 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool>
880880
{
881881
/// Sets the payment metadata.
882882
///
883-
/// By default features are set to *optionally* allow the sender to include the payment metadata.
884-
/// If you wish to require that the sender include the metadata (and fail to parse the invoice if
885-
/// they don't support payment metadata fields), you need to call
886-
/// [`InvoiceBuilder::require_payment_metadata`] after this.
887-
pub fn payment_metadata(
883+
/// This marks the payment metadata as optional, allowing a legacy sender that doesn't
884+
/// understand payment metadata to ignore it. Note that LDK by default commits to the payment
885+
/// metadata in its payment secret, implicitly making it required.
886+
pub fn optional_payment_metadata(
888887
mut self, payment_metadata: Vec<u8>,
889888
) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
890889
self.tagged_fields.push(TaggedField::PaymentMetadata(payment_metadata));
@@ -902,20 +901,23 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool>
902901
}
903902
self.set_flags()
904903
}
905-
}
906904

907-
impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool, S: tb::Bool>
908-
InvoiceBuilder<D, H, T, C, S, tb::True>
909-
{
910-
/// Sets forwarding of payment metadata as required. A reader of the invoice which does not
911-
/// support sending payment metadata will fail to read the invoice.
912-
pub fn require_payment_metadata(mut self) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
913-
for field in self.tagged_fields.iter_mut() {
905+
/// Sets the payment metadata.
906+
///
907+
/// By default features are set to *require* the sender to include the payment metadata.
908+
/// If you wish to support legacy senders that ignore the metadata, you can call
909+
/// [`InvoiceBuilder::optional_payment_metadata`] instead. Note that LDK by default commits to
910+
/// the payment metadata in its payment secret, implicitly making it required.
911+
pub fn payment_metadata(
912+
self, payment_metadata: Vec<u8>,
913+
) -> InvoiceBuilder<D, H, T, C, S, tb::True> {
914+
let mut res = self.optional_payment_metadata(payment_metadata);
915+
for field in res.tagged_fields.iter_mut() {
914916
if let TaggedField::Features(f) = field {
915917
f.set_payment_metadata_required();
916918
}
917919
}
918-
self
920+
res
919921
}
920922
}
921923

lightning-invoice/tests/ser_de.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,6 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> {
418418
))
419419
.description("payment metadata inside".to_owned())
420420
.payment_metadata(<Vec<u8>>::from_hex("01fafaf0").unwrap())
421-
.require_payment_metadata()
422421
.payee_pub_key(PublicKey::from_slice(&<Vec<u8>>::from_hex(
423422
"03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad"
424423
).unwrap()).unwrap())
@@ -450,7 +449,6 @@ fn get_test_tuples() -> Vec<(String, SignedRawBolt11Invoice, bool, bool)> {
450449
))
451450
.description("payment metadata inside".to_owned())
452451
.payment_metadata(<Vec<u8>>::from_hex("01fafaf0").unwrap())
453-
.require_payment_metadata()
454452
.payment_secret(PaymentSecret([0x11; 32]))
455453
.build_raw()
456454
.unwrap()

lightning-liquidity/tests/lsps2_integration_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn create_jit_invoice(
122122
let min_final_cltv_expiry_delta = MIN_FINAL_CLTV_EXPIRY_DELTA + 2;
123123
let (payment_hash, payment_secret) = node
124124
.node
125-
.create_inbound_payment(None, expiry_secs, Some(min_final_cltv_expiry_delta))
125+
.create_inbound_payment(None, expiry_secs, Some(min_final_cltv_expiry_delta), None)
126126
.map_err(|e| {
127127
log_error!(node.logger, "Failed to register inbound payment: {:?}", e);
128128
})?;

lightning/src/ln/bolt11_payment_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn payment_metadata_end_to_end_for_invoice_with_amount() {
3131
let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42];
3232

3333
let (payment_hash, payment_secret) =
34-
nodes[1].node.create_inbound_payment(None, 7200, None).unwrap();
34+
nodes[1].node.create_inbound_payment(None, 7200, None, Some(&payment_metadata)).unwrap();
3535

3636
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
3737
let invoice = InvoiceBuilder::new(Currency::Bitcoin)
@@ -98,7 +98,7 @@ fn payment_metadata_end_to_end_for_invoice_with_no_amount() {
9898
let payment_metadata = vec![42, 43, 44, 45, 46, 47, 48, 49, 42];
9999

100100
let (payment_hash, payment_secret) =
101-
nodes[1].node.create_inbound_payment(None, 7200, None).unwrap();
101+
nodes[1].node.create_inbound_payment(None, 7200, None, Some(&payment_metadata)).unwrap();
102102

103103
let timestamp = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap();
104104
let invoice = InvoiceBuilder::new(Currency::Bitcoin)

lightning/src/ln/channelmanager.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8594,6 +8594,7 @@ impl<
85948594
let verify_res = inbound_payment::verify(
85958595
payment_hash,
85968596
&payment_data,
8597+
onion_fields.payment_metadata.as_deref(),
85978598
self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
85988599
&self.inbound_payment_key,
85998600
&self.logger,
@@ -14255,7 +14256,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1425514256
) -> Result<Bolt11Invoice, SignOrCreationError<()>> {
1425614257
let Bolt11InvoiceParameters {
1425714258
amount_msats, description, invoice_expiry_delta_secs, min_final_cltv_expiry_delta,
14258-
payment_hash,
14259+
payment_hash, payment_metadata,
1425914260
} = params;
1426014261

1426114262
let currency =
@@ -14288,6 +14289,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1428814289
payment_hash, amount_msats,
1428914290
invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
1429014291
min_final_cltv_expiry_delta,
14292+
payment_metadata.as_deref(),
1429114293
)
1429214294
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?;
1429314295
(payment_hash, payment_secret)
@@ -14297,6 +14299,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1429714299
.create_inbound_payment(
1429814300
amount_msats, invoice_expiry_delta_secs.unwrap_or(DEFAULT_EXPIRY_TIME as u32),
1429914301
min_final_cltv_expiry_delta,
14302+
payment_metadata.as_deref(),
1430014303
)
1430114304
.map_err(|()| SignOrCreationError::CreationError(CreationError::InvalidAmount))?
1430214305
},
@@ -14335,7 +14338,11 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1433514338
invoice = invoice.private_route(hint);
1433614339
}
1433714340

14338-
let raw_invoice = invoice.build_raw().map_err(|e| SignOrCreationError::CreationError(e))?;
14341+
let raw_invoice = if let Some(payment_metadata) = payment_metadata {
14342+
invoice.payment_metadata(payment_metadata).build_raw()
14343+
} else {
14344+
invoice.build_raw()
14345+
}.map_err(|e| SignOrCreationError::CreationError(e))?;
1433914346
let signature = self.node_signer.sign_invoice(&raw_invoice, Recipient::Node);
1434014347

1434114348
raw_invoice
@@ -14414,6 +14421,14 @@ pub struct Bolt11InvoiceParameters {
1441414421
/// involving another protocol where the payment hash is also involved outside the scope of
1441514422
/// lightning.
1441614423
pub payment_hash: Option<PaymentHash>,
14424+
14425+
/// The `payment_metadata` to include in the invoice. This is provided back to us in the payment
14426+
/// onion by the sender, available as [`RecipientOnionFields::payment_metadata`] via
14427+
/// [`Event::PaymentClaimable::onion_fields`].
14428+
///
14429+
/// Note that because it is exposed to the sender in the invoice you should consider encrypting
14430+
/// it. It is committed to, however, so cannot be modified by the sender.
14431+
pub payment_metadata: Option<Vec<u8>>,
1441714432
}
1441814433

1441914434
impl Default for Bolt11InvoiceParameters {
@@ -14424,6 +14439,7 @@ impl Default for Bolt11InvoiceParameters {
1442414439
invoice_expiry_delta_secs: None,
1442514440
min_final_cltv_expiry_delta: None,
1442614441
payment_hash: None,
14442+
payment_metadata: None,
1442714443
}
1442814444
}
1442914445
}
@@ -14915,7 +14931,7 @@ impl<
1491514931
refund,
1491614932
self.list_usable_channels(),
1491714933
|amount_msats, relative_expiry| {
14918-
self.create_inbound_payment(Some(amount_msats), relative_expiry, None)
14934+
self.create_inbound_payment(Some(amount_msats), relative_expiry, None, None)
1491914935
.map_err(|()| Bolt12SemanticError::InvalidAmount)
1492014936
},
1492114937
)?;
@@ -14958,7 +14974,7 @@ impl<
1495814974
/// [`create_inbound_payment_for_hash`]: Self::create_inbound_payment_for_hash
1495914975
pub fn create_inbound_payment(
1496014976
&self, min_value_msat: Option<u64>, invoice_expiry_delta_secs: u32,
14961-
min_final_cltv_expiry_delta: Option<u16>,
14977+
min_final_cltv_expiry_delta: Option<u16>, payment_metadata: Option<&[u8]>,
1496214978
) -> Result<(PaymentHash, PaymentSecret), ()> {
1496314979
inbound_payment::create(
1496414980
&self.inbound_payment_key,
@@ -14967,6 +14983,7 @@ impl<
1496714983
&self.entropy_source,
1496814984
self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
1496914985
min_final_cltv_expiry_delta,
14986+
payment_metadata,
1497014987
)
1497114988
}
1497214989

@@ -14986,6 +15003,9 @@ impl<
1498615003
/// before a [`PaymentClaimable`] event will be generated, ensuring that we do not provide the
1498715004
/// sender "proof-of-payment" unless they have paid the required amount.
1498815005
///
15006+
/// The returned secret commits to the `payment_metadata` and thus the invoice's metadata must
15007+
/// match what is provided here.
15008+
///
1498915009
/// `invoice_expiry_delta_secs` describes the number of seconds that the invoice is valid for
1499015010
/// in excess of the current time. This should roughly match the expiry time set in the invoice.
1499115011
/// After this many seconds, we will remove the inbound payment, resulting in any attempts to
@@ -15019,6 +15039,7 @@ impl<
1501915039
pub fn create_inbound_payment_for_hash(
1502015040
&self, payment_hash: PaymentHash, min_value_msat: Option<u64>,
1502115041
invoice_expiry_delta_secs: u32, min_final_cltv_expiry: Option<u16>,
15042+
payment_metadata: Option<&[u8]>,
1502215043
) -> Result<PaymentSecret, ()> {
1502315044
inbound_payment::create_from_hash(
1502415045
&self.inbound_payment_key,
@@ -15027,18 +15048,25 @@ impl<
1502715048
invoice_expiry_delta_secs,
1502815049
self.highest_seen_timestamp.load(Ordering::Acquire) as u64,
1502915050
min_final_cltv_expiry,
15051+
payment_metadata,
1503015052
)
1503115053
}
1503215054

15033-
/// Gets an LDK-generated payment preimage from a payment hash and payment secret that were
15055+
/// Gets an LDK-generated payment preimage from a payment hash, metadata and secret that were
1503415056
/// previously returned from [`create_inbound_payment`].
1503515057
///
1503615058
/// [`create_inbound_payment`]: Self::create_inbound_payment
1503715059
pub fn get_payment_preimage(
1503815060
&self, payment_hash: PaymentHash, payment_secret: PaymentSecret,
15061+
payment_metadata: Option<&[u8]>,
1503915062
) -> Result<PaymentPreimage, APIError> {
1504015063
let expanded_key = &self.inbound_payment_key;
15041-
inbound_payment::get_payment_preimage(payment_hash, payment_secret, expanded_key)
15064+
inbound_payment::get_payment_preimage(
15065+
payment_hash,
15066+
payment_secret,
15067+
payment_metadata,
15068+
expanded_key,
15069+
)
1504215070
}
1504315071

1504415072
/// [`BlindedMessagePath`]s for an async recipient to communicate with this node and interactively
@@ -17107,7 +17135,8 @@ impl<
1710717135
self.create_inbound_payment(
1710817136
Some(amount_msats),
1710917137
relative_expiry,
17110-
None
17138+
None,
17139+
None,
1711117140
).map_err(|_| Bolt12SemanticError::InvalidAmount)
1711217141
};
1711317142

@@ -21319,15 +21348,15 @@ mod tests {
2131921348
// payment verification fails as expected.
2132021349
let mut bad_payment_hash = payment_hash.clone();
2132121350
bad_payment_hash.0[0] += 1;
21322-
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) {
21351+
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) {
2132321352
Ok(_) => panic!("Unexpected ok"),
2132421353
Err(()) => {
2132521354
nodes[0].logger.assert_log_contains("lightning::ln::inbound_payment", "Failing HTLC with user-generated payment_hash", 1);
2132621355
}
2132721356
}
2132821357

2132921358
// Check that using the original payment hash succeeds.
21330-
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());
21359+
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());
2133121360
}
2133221361

2133321362
fn check_not_connected_to_peer_error<T>(
@@ -22000,7 +22029,7 @@ pub mod bench {
2200022029
payment_preimage.0[0..8].copy_from_slice(&payment_count.to_le_bytes());
2200122030
payment_count += 1;
2200222031
let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).to_byte_array());
22003-
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None).unwrap();
22032+
let payment_secret = $node_b.create_inbound_payment_for_hash(payment_hash, None, 7200, None, None).unwrap();
2200422033

2200522034
$node_a.send_payment(payment_hash, RecipientOnionFields::secret_only(payment_secret, 10_000),
2200622035
PaymentId(payment_hash.0),

lightning/src/ln/functional_test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2807,6 +2807,7 @@ pub fn get_payment_preimage_hash(
28072807
min_value_msat,
28082808
7200,
28092809
min_final_cltv_expiry_delta,
2810+
None,
28102811
)
28112812
.unwrap();
28122813
(payment_preimage, payment_hash, payment_secret)

0 commit comments

Comments
 (0)