Skip to content

Commit 48073c5

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 a652d2d commit 48073c5

File tree

4 files changed

+136
-46
lines changed

4 files changed

+136
-46
lines changed

lightning/src/offers/invoice.rs

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,8 @@ impl UnsignedBolt12Invoice {
497497
const EXPERIMENTAL_TYPES: core::ops::Range<u64> =
498498
EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end;
499499

500-
let (_, _, _, invoice_tlv_stream, _, _) = contents.as_tlv_stream();
500+
let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) =
501+
contents.as_tlv_stream();
501502

502503
// Allocate enough space for the invoice, which will include:
503504
// - all TLV records from `invreq_bytes` except signatures,
@@ -510,6 +511,7 @@ impl UnsignedBolt12Invoice {
510511
invreq_bytes.len()
511512
+ invoice_tlv_stream.serialized_length()
512513
+ if contents.is_for_offer() { 0 } else { SIGNATURE_TLV_RECORD_SIZE }
514+
+ experimental_invoice_tlv_stream.serialized_length(),
513515
);
514516

515517
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
@@ -531,12 +533,15 @@ impl UnsignedBolt12Invoice {
531533
- experimental_tlv_stream
532534
.peek()
533535
.map_or(remaining_bytes.len(), |first_record| first_record.start)
536+
+ experimental_invoice_tlv_stream.serialized_length(),
534537
);
535538

536539
for record in experimental_tlv_stream {
537540
record.write(&mut experimental_bytes).unwrap();
538541
}
539542

543+
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
544+
540545
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
541546
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
542547

@@ -894,14 +899,15 @@ impl Bolt12Invoice {
894899
let (
895900
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
896901
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
902+
experimental_invoice_tlv_stream,
897903
) = self.contents.as_tlv_stream();
898904
let signature_tlv_stream = SignatureTlvStreamRef {
899905
signature: Some(&self.signature),
900906
};
901907
(
902908
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
903909
signature_tlv_stream, experimental_offer_tlv_stream,
904-
experimental_invoice_request_tlv_stream,
910+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
905911
)
906912
}
907913

@@ -1169,9 +1175,12 @@ impl InvoiceContents {
11691175
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11701176
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11711177
};
1172-
let invoice = self.fields().as_tlv_stream();
1178+
let (invoice, experimental_invoice) = self.fields().as_tlv_stream();
11731179

1174-
(payer, offer, invoice_request, invoice, experimental_offer, experimental_invoice_request)
1180+
(
1181+
payer, offer, invoice_request, invoice, experimental_offer,
1182+
experimental_invoice_request, experimental_invoice,
1183+
)
11751184
}
11761185
}
11771186

@@ -1219,24 +1228,27 @@ pub(super) fn filter_fallbacks(
12191228
}
12201229

12211230
impl InvoiceFields {
1222-
fn as_tlv_stream(&self) -> InvoiceTlvStreamRef {
1231+
fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) {
12231232
let features = {
12241233
if self.features == Bolt12InvoiceFeatures::empty() { None }
12251234
else { Some(&self.features) }
12261235
};
12271236

1228-
InvoiceTlvStreamRef {
1229-
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
1230-
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
1231-
created_at: Some(self.created_at.as_secs()),
1232-
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
1233-
payment_hash: Some(&self.payment_hash),
1234-
amount: Some(self.amount_msats),
1235-
fallbacks: self.fallbacks.as_ref(),
1236-
features,
1237-
node_id: Some(&self.signing_pubkey),
1238-
message_paths: None,
1239-
}
1237+
(
1238+
InvoiceTlvStreamRef {
1239+
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
1240+
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
1241+
created_at: Some(self.created_at.as_secs()),
1242+
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
1243+
payment_hash: Some(&self.payment_hash),
1244+
amount: Some(self.amount_msats),
1245+
fallbacks: self.fallbacks.as_ref(),
1246+
features,
1247+
node_id: Some(&self.signing_pubkey),
1248+
message_paths: None,
1249+
},
1250+
ExperimentalInvoiceTlvStreamRef {},
1251+
)
12401252
}
12411253
}
12421254

@@ -1311,6 +1323,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
13111323
(236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
13121324
});
13131325

1326+
/// Valid type range for experimental invoice TLV records.
1327+
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1328+
1329+
tlv_stream!(
1330+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
1331+
);
1332+
13141333
pub(super) type BlindedPathIter<'a> = core::iter::Map<
13151334
core::slice::Iter<'a, BlindedPaymentPath>,
13161335
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1332,7 +1351,7 @@ impl_writeable!(FallbackAddress, { version, program });
13321351

13331352
type FullInvoiceTlvStream =(
13341353
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1335-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1354+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13361355
);
13371356

13381357
type FullInvoiceTlvStreamRef<'a> = (
@@ -1343,6 +1362,7 @@ type FullInvoiceTlvStreamRef<'a> = (
13431362
SignatureTlvStreamRef<'a>,
13441363
ExperimentalOfferTlvStreamRef,
13451364
ExperimentalInvoiceRequestTlvStreamRef,
1365+
ExperimentalInvoiceTlvStreamRef,
13461366
);
13471367

13481368
impl CursorReadable for FullInvoiceTlvStream {
@@ -1354,19 +1374,20 @@ impl CursorReadable for FullInvoiceTlvStream {
13541374
let signature = CursorReadable::read(r)?;
13551375
let experimental_offer = CursorReadable::read(r)?;
13561376
let experimental_invoice_request = CursorReadable::read(r)?;
1377+
let experimental_invoice = CursorReadable::read(r)?;
13571378

13581379
Ok(
13591380
(
13601381
payer, offer, invoice_request, invoice, signature, experimental_offer,
1361-
experimental_invoice_request,
1382+
experimental_invoice_request, experimental_invoice,
13621383
)
13631384
)
13641385
}
13651386
}
13661387

13671388
type PartialInvoiceTlvStream = (
13681389
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1369-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1390+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13701391
);
13711392

13721393
type PartialInvoiceTlvStreamRef<'a> = (
@@ -1376,6 +1397,7 @@ type PartialInvoiceTlvStreamRef<'a> = (
13761397
InvoiceTlvStreamRef<'a>,
13771398
ExperimentalOfferTlvStreamRef,
13781399
ExperimentalInvoiceRequestTlvStreamRef,
1400+
ExperimentalInvoiceTlvStreamRef,
13791401
);
13801402

13811403
impl CursorReadable for PartialInvoiceTlvStream {
@@ -1386,11 +1408,12 @@ impl CursorReadable for PartialInvoiceTlvStream {
13861408
let invoice = CursorReadable::read(r)?;
13871409
let experimental_offer = CursorReadable::read(r)?;
13881410
let experimental_invoice_request = CursorReadable::read(r)?;
1411+
let experimental_invoice = CursorReadable::read(r)?;
13891412

13901413
Ok(
13911414
(
13921415
payer, offer, invoice_request, invoice, experimental_offer,
1393-
experimental_invoice_request,
1416+
experimental_invoice_request, experimental_invoice,
13941417
)
13951418
)
13961419
}
@@ -1406,11 +1429,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
14061429
SignatureTlvStream { signature },
14071430
experimental_offer_tlv_stream,
14081431
experimental_invoice_request_tlv_stream,
1432+
experimental_invoice_tlv_stream,
14091433
) = tlv_stream;
14101434
let contents = InvoiceContents::try_from(
14111435
(
14121436
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
14131437
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1438+
experimental_invoice_tlv_stream,
14141439
)
14151440
)?;
14161441

@@ -1439,6 +1464,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14391464
},
14401465
experimental_offer_tlv_stream,
14411466
experimental_invoice_request_tlv_stream,
1467+
ExperimentalInvoiceTlvStream {},
14421468
) = tlv_stream;
14431469

14441470
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1535,7 +1561,7 @@ pub(super) fn check_invoice_signing_pubkey(
15351561

15361562
#[cfg(test)]
15371563
mod tests {
1538-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1564+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15391565

15401566
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
15411567
use bitcoin::constants::ChainHash;
@@ -1731,6 +1757,7 @@ mod tests {
17311757
ExperimentalInvoiceRequestTlvStreamRef {
17321758
experimental_bar: None,
17331759
},
1760+
ExperimentalInvoiceTlvStreamRef {},
17341761
),
17351762
);
17361763

@@ -1830,6 +1857,7 @@ mod tests {
18301857
ExperimentalInvoiceRequestTlvStreamRef {
18311858
experimental_bar: None,
18321859
},
1860+
ExperimentalInvoiceTlvStreamRef {},
18331861
),
18341862
);
18351863

@@ -2026,7 +2054,7 @@ mod tests {
20262054
.relative_expiry(one_hour.as_secs() as u32)
20272055
.build().unwrap()
20282056
.sign(recipient_sign).unwrap();
2029-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2057+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20302058
#[cfg(feature = "std")]
20312059
assert!(!invoice.is_expired());
20322060
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -2042,7 +2070,7 @@ mod tests {
20422070
.relative_expiry(one_hour.as_secs() as u32 - 1)
20432071
.build().unwrap()
20442072
.sign(recipient_sign).unwrap();
2045-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2073+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20462074
#[cfg(feature = "std")]
20472075
assert!(invoice.is_expired());
20482076
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -2061,7 +2089,7 @@ mod tests {
20612089
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20622090
.build().unwrap()
20632091
.sign(recipient_sign).unwrap();
2064-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2092+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20652093
assert_eq!(invoice.amount_msats(), 1001);
20662094
assert_eq!(tlv_stream.amount, Some(1001));
20672095
}
@@ -2079,7 +2107,7 @@ mod tests {
20792107
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20802108
.build().unwrap()
20812109
.sign(recipient_sign).unwrap();
2082-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2110+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20832111
assert_eq!(invoice.amount_msats(), 2000);
20842112
assert_eq!(tlv_stream.amount, Some(2000));
20852113

@@ -2117,7 +2145,7 @@ mod tests {
21172145
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
21182146
.build().unwrap()
21192147
.sign(recipient_sign).unwrap();
2120-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2148+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21212149
assert_eq!(
21222150
invoice.fallbacks(),
21232151
vec![
@@ -2160,7 +2188,7 @@ mod tests {
21602188
.allow_mpp()
21612189
.build().unwrap()
21622190
.sign(recipient_sign).unwrap();
2163-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2191+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21642192
assert_eq!(invoice.invoice_features(), &features);
21652193
assert_eq!(tlv_stream.features, Some(&features));
21662194
}

lightning/src/offers/invoice_request.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,7 +1558,7 @@ mod tests {
15581558
let (
15591559
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
15601560
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1561-
experimental_invoice_request_tlv_stream,
1561+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15621562
) = invoice.as_tlv_stream();
15631563
invoice_request_tlv_stream.amount = Some(2000);
15641564
invoice_tlv_stream.amount = Some(2000);
@@ -1567,6 +1567,7 @@ mod tests {
15671567
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15681568
let experimental_tlv_stream = (
15691569
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1570+
experimental_invoice_tlv_stream,
15701571
);
15711572
let mut bytes = Vec::new();
15721573
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1587,7 +1588,7 @@ mod tests {
15871588
let (
15881589
mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
15891590
mut signature_tlv_stream, experimental_offer_tlv_stream,
1590-
experimental_invoice_request_tlv_stream,
1591+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15911592
) = invoice.as_tlv_stream();
15921593
let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
15931594
payer_tlv_stream.metadata = Some(&metadata);
@@ -1596,6 +1597,7 @@ mod tests {
15961597
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15971598
let experimental_tlv_stream = (
15981599
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1600+
experimental_invoice_tlv_stream,
15991601
);
16001602
let mut bytes = Vec::new();
16011603
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1645,7 +1647,7 @@ mod tests {
16451647
let (
16461648
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
16471649
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1648-
experimental_invoice_request_tlv_stream,
1650+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16491651
) = invoice.as_tlv_stream();
16501652
invoice_request_tlv_stream.amount = Some(2000);
16511653
invoice_tlv_stream.amount = Some(2000);
@@ -1654,6 +1656,7 @@ mod tests {
16541656
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16551657
let experimental_tlv_stream = (
16561658
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1659+
experimental_invoice_tlv_stream,
16571660
);
16581661
let mut bytes = Vec::new();
16591662
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1676,7 +1679,7 @@ mod tests {
16761679
let (
16771680
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
16781681
mut signature_tlv_stream, experimental_offer_tlv_stream,
1679-
experimental_invoice_request_tlv_stream,
1682+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16801683
) = invoice.as_tlv_stream();
16811684
let payer_id = pubkey(1);
16821685
invoice_request_tlv_stream.payer_id = Some(&payer_id);
@@ -1685,6 +1688,7 @@ mod tests {
16851688
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16861689
let experimental_tlv_stream = (
16871690
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1691+
experimental_invoice_tlv_stream,
16881692
);
16891693
let mut bytes = Vec::new();
16901694
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();

0 commit comments

Comments
 (0)