Skip to content

Commit cef5314

Browse files
committed
Parse experimental invoice TLV records
The BOLT12 spec defines an experimental TLV range that is allowed in offer and invoice_request messages. The remaining TLV-space is for experimental use in invoice messages. Allow this range when parsing an invoice and include it when signing one.
1 parent 3683158 commit cef5314

File tree

4 files changed

+127
-44
lines changed

4 files changed

+127
-44
lines changed

lightning/src/offers/invoice.rs

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,9 @@ impl UnsignedBolt12Invoice {
506506
record.write(&mut bytes).unwrap();
507507
}
508508

509-
let (_, _, _, invoice_tlv_stream, _, _) = contents.as_tlv_stream();
509+
let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) =
510+
contents.as_tlv_stream();
511+
510512
invoice_tlv_stream.write(&mut bytes).unwrap();
511513

512514
let mut experimental_bytes = Vec::new();
@@ -515,6 +517,8 @@ impl UnsignedBolt12Invoice {
515517
record.write(&mut experimental_bytes).unwrap();
516518
}
517519

520+
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
521+
518522
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
519523
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
520524

@@ -872,14 +876,15 @@ impl Bolt12Invoice {
872876
let (
873877
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
874878
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
879+
experimental_invoice_tlv_stream,
875880
) = self.contents.as_tlv_stream();
876881
let signature_tlv_stream = SignatureTlvStreamRef {
877882
signature: Some(&self.signature),
878883
};
879884
(
880885
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
881886
signature_tlv_stream, experimental_offer_tlv_stream,
882-
experimental_invoice_request_tlv_stream,
887+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
883888
)
884889
}
885890

@@ -1140,9 +1145,12 @@ impl InvoiceContents {
11401145
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11411146
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11421147
};
1143-
let invoice = self.fields().as_tlv_stream();
1148+
let (invoice, experimental_invoice) = self.fields().as_tlv_stream();
11441149

1145-
(payer, offer, invoice_request, invoice, experimental_offer, experimental_invoice_request)
1150+
(
1151+
payer, offer, invoice_request, invoice, experimental_offer,
1152+
experimental_invoice_request, experimental_invoice,
1153+
)
11461154
}
11471155
}
11481156

@@ -1190,24 +1198,27 @@ pub(super) fn filter_fallbacks(
11901198
}
11911199

11921200
impl InvoiceFields {
1193-
fn as_tlv_stream(&self) -> InvoiceTlvStreamRef {
1201+
fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) {
11941202
let features = {
11951203
if self.features == Bolt12InvoiceFeatures::empty() { None }
11961204
else { Some(&self.features) }
11971205
};
11981206

1199-
InvoiceTlvStreamRef {
1200-
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
1201-
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
1202-
created_at: Some(self.created_at.as_secs()),
1203-
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
1204-
payment_hash: Some(&self.payment_hash),
1205-
amount: Some(self.amount_msats),
1206-
fallbacks: self.fallbacks.as_ref(),
1207-
features,
1208-
node_id: Some(&self.signing_pubkey),
1209-
message_paths: None,
1210-
}
1207+
(
1208+
InvoiceTlvStreamRef {
1209+
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
1210+
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
1211+
created_at: Some(self.created_at.as_secs()),
1212+
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
1213+
payment_hash: Some(&self.payment_hash),
1214+
amount: Some(self.amount_msats),
1215+
fallbacks: self.fallbacks.as_ref(),
1216+
features,
1217+
node_id: Some(&self.signing_pubkey),
1218+
message_paths: None,
1219+
},
1220+
ExperimentalInvoiceTlvStreamRef {},
1221+
)
12111222
}
12121223
}
12131224

@@ -1282,6 +1293,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
12821293
(236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
12831294
});
12841295

1296+
/// Valid type range for experimental invoice TLV records.
1297+
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1298+
1299+
tlv_stream!(
1300+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
1301+
);
1302+
12851303
pub(super) type BlindedPathIter<'a> = core::iter::Map<
12861304
core::slice::Iter<'a, BlindedPaymentPath>,
12871305
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1303,7 +1321,7 @@ impl_writeable!(FallbackAddress, { version, program });
13031321

13041322
type FullInvoiceTlvStream =(
13051323
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1306-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1324+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13071325
);
13081326

13091327
type FullInvoiceTlvStreamRef<'a> = (
@@ -1314,6 +1332,7 @@ type FullInvoiceTlvStreamRef<'a> = (
13141332
SignatureTlvStreamRef<'a>,
13151333
ExperimentalOfferTlvStreamRef,
13161334
ExperimentalInvoiceRequestTlvStreamRef,
1335+
ExperimentalInvoiceTlvStreamRef,
13171336
);
13181337

13191338
impl CursorReadable for FullInvoiceTlvStream {
@@ -1325,19 +1344,20 @@ impl CursorReadable for FullInvoiceTlvStream {
13251344
let signature = CursorReadable::read(r)?;
13261345
let experimental_offer = CursorReadable::read(r)?;
13271346
let experimental_invoice_request = CursorReadable::read(r)?;
1347+
let experimental_invoice = CursorReadable::read(r)?;
13281348

13291349
Ok(
13301350
(
13311351
payer, offer, invoice_request, invoice, signature, experimental_offer,
1332-
experimental_invoice_request,
1352+
experimental_invoice_request, experimental_invoice,
13331353
)
13341354
)
13351355
}
13361356
}
13371357

13381358
type PartialInvoiceTlvStream = (
13391359
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1340-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1360+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13411361
);
13421362

13431363
type PartialInvoiceTlvStreamRef<'a> = (
@@ -1347,6 +1367,7 @@ type PartialInvoiceTlvStreamRef<'a> = (
13471367
InvoiceTlvStreamRef<'a>,
13481368
ExperimentalOfferTlvStreamRef,
13491369
ExperimentalInvoiceRequestTlvStreamRef,
1370+
ExperimentalInvoiceTlvStreamRef,
13501371
);
13511372

13521373
impl CursorReadable for PartialInvoiceTlvStream {
@@ -1357,11 +1378,12 @@ impl CursorReadable for PartialInvoiceTlvStream {
13571378
let invoice = CursorReadable::read(r)?;
13581379
let experimental_offer = CursorReadable::read(r)?;
13591380
let experimental_invoice_request = CursorReadable::read(r)?;
1381+
let experimental_invoice = CursorReadable::read(r)?;
13601382

13611383
Ok(
13621384
(
13631385
payer, offer, invoice_request, invoice, experimental_offer,
1364-
experimental_invoice_request,
1386+
experimental_invoice_request, experimental_invoice,
13651387
)
13661388
)
13671389
}
@@ -1377,11 +1399,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
13771399
SignatureTlvStream { signature },
13781400
experimental_offer_tlv_stream,
13791401
experimental_invoice_request_tlv_stream,
1402+
experimental_invoice_tlv_stream,
13801403
) = tlv_stream;
13811404
let contents = InvoiceContents::try_from(
13821405
(
13831406
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
13841407
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1408+
experimental_invoice_tlv_stream,
13851409
)
13861410
)?;
13871411

@@ -1410,6 +1434,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14101434
},
14111435
experimental_offer_tlv_stream,
14121436
experimental_invoice_request_tlv_stream,
1437+
ExperimentalInvoiceTlvStream {},
14131438
) = tlv_stream;
14141439

14151440
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1506,7 +1531,7 @@ pub(super) fn check_invoice_signing_pubkey(
15061531

15071532
#[cfg(test)]
15081533
mod tests {
1509-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1534+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15101535

15111536
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
15121537
use bitcoin::constants::ChainHash;
@@ -1702,6 +1727,7 @@ mod tests {
17021727
ExperimentalInvoiceRequestTlvStreamRef {
17031728
experimental_bar: None,
17041729
},
1730+
ExperimentalInvoiceTlvStreamRef {},
17051731
),
17061732
);
17071733

@@ -1801,6 +1827,7 @@ mod tests {
18011827
ExperimentalInvoiceRequestTlvStreamRef {
18021828
experimental_bar: None,
18031829
},
1830+
ExperimentalInvoiceTlvStreamRef {},
18041831
),
18051832
);
18061833

@@ -1997,7 +2024,7 @@ mod tests {
19972024
.relative_expiry(one_hour.as_secs() as u32)
19982025
.build().unwrap()
19992026
.sign(recipient_sign).unwrap();
2000-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2027+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20012028
#[cfg(feature = "std")]
20022029
assert!(!invoice.is_expired());
20032030
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -2013,7 +2040,7 @@ mod tests {
20132040
.relative_expiry(one_hour.as_secs() as u32 - 1)
20142041
.build().unwrap()
20152042
.sign(recipient_sign).unwrap();
2016-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2043+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20172044
#[cfg(feature = "std")]
20182045
assert!(invoice.is_expired());
20192046
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -2032,7 +2059,7 @@ mod tests {
20322059
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20332060
.build().unwrap()
20342061
.sign(recipient_sign).unwrap();
2035-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2062+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20362063
assert_eq!(invoice.amount_msats(), 1001);
20372064
assert_eq!(tlv_stream.amount, Some(1001));
20382065
}
@@ -2050,7 +2077,7 @@ mod tests {
20502077
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20512078
.build().unwrap()
20522079
.sign(recipient_sign).unwrap();
2053-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2080+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20542081
assert_eq!(invoice.amount_msats(), 2000);
20552082
assert_eq!(tlv_stream.amount, Some(2000));
20562083

@@ -2088,7 +2115,7 @@ mod tests {
20882115
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
20892116
.build().unwrap()
20902117
.sign(recipient_sign).unwrap();
2091-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2118+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20922119
assert_eq!(
20932120
invoice.fallbacks(),
20942121
vec![
@@ -2131,7 +2158,7 @@ mod tests {
21312158
.allow_mpp()
21322159
.build().unwrap()
21332160
.sign(recipient_sign).unwrap();
2134-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2161+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21352162
assert_eq!(invoice.invoice_features(), &features);
21362163
assert_eq!(tlv_stream.features, Some(&features));
21372164
}

lightning/src/offers/invoice_request.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,7 +1538,7 @@ mod tests {
15381538
let (
15391539
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
15401540
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1541-
experimental_invoice_request_tlv_stream,
1541+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15421542
) = invoice.as_tlv_stream();
15431543
invoice_request_tlv_stream.amount = Some(2000);
15441544
invoice_tlv_stream.amount = Some(2000);
@@ -1547,6 +1547,7 @@ mod tests {
15471547
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15481548
let experimental_tlv_stream = (
15491549
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1550+
experimental_invoice_tlv_stream,
15501551
);
15511552
let mut bytes = Vec::new();
15521553
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1567,7 +1568,7 @@ mod tests {
15671568
let (
15681569
mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
15691570
mut signature_tlv_stream, experimental_offer_tlv_stream,
1570-
experimental_invoice_request_tlv_stream,
1571+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15711572
) = invoice.as_tlv_stream();
15721573
let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
15731574
payer_tlv_stream.metadata = Some(&metadata);
@@ -1576,6 +1577,7 @@ mod tests {
15761577
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15771578
let experimental_tlv_stream = (
15781579
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1580+
experimental_invoice_tlv_stream,
15791581
);
15801582
let mut bytes = Vec::new();
15811583
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1625,7 +1627,7 @@ mod tests {
16251627
let (
16261628
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
16271629
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1628-
experimental_invoice_request_tlv_stream,
1630+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16291631
) = invoice.as_tlv_stream();
16301632
invoice_request_tlv_stream.amount = Some(2000);
16311633
invoice_tlv_stream.amount = Some(2000);
@@ -1634,6 +1636,7 @@ mod tests {
16341636
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16351637
let experimental_tlv_stream = (
16361638
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1639+
experimental_invoice_tlv_stream,
16371640
);
16381641
let mut bytes = Vec::new();
16391642
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1656,7 +1659,7 @@ mod tests {
16561659
let (
16571660
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
16581661
mut signature_tlv_stream, experimental_offer_tlv_stream,
1659-
experimental_invoice_request_tlv_stream,
1662+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16601663
) = invoice.as_tlv_stream();
16611664
let payer_id = pubkey(1);
16621665
invoice_request_tlv_stream.payer_id = Some(&payer_id);
@@ -1665,6 +1668,7 @@ mod tests {
16651668
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16661669
let experimental_tlv_stream = (
16671670
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1671+
experimental_invoice_tlv_stream,
16681672
);
16691673
let mut bytes = Vec::new();
16701674
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();

0 commit comments

Comments
 (0)