Skip to content

Commit 89eafe3

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 e6d4868 commit 89eafe3

File tree

4 files changed

+129
-44
lines changed

4 files changed

+129
-44
lines changed

lightning/src/offers/invoice.rs

+57-28
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,9 @@ impl UnsignedBolt12Invoice {
496496
record.write(&mut bytes).unwrap();
497497
}
498498

499-
let (_, _, _, invoice_tlv_stream, _, _) = contents.as_tlv_stream();
499+
let (_, _, _, invoice_tlv_stream, _, _, experimental_invoice_tlv_stream) =
500+
contents.as_tlv_stream();
501+
500502
invoice_tlv_stream.write(&mut bytes).unwrap();
501503

502504
let mut experimental_bytes = Vec::new();
@@ -505,6 +507,8 @@ impl UnsignedBolt12Invoice {
505507
record.write(&mut experimental_bytes).unwrap();
506508
}
507509

510+
experimental_invoice_tlv_stream.write(&mut experimental_bytes).unwrap();
511+
508512
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
509513
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
510514

@@ -862,14 +866,15 @@ impl Bolt12Invoice {
862866
let (
863867
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
864868
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
869+
experimental_invoice_tlv_stream,
865870
) = self.contents.as_tlv_stream();
866871
let signature_tlv_stream = SignatureTlvStreamRef {
867872
signature: Some(&self.signature),
868873
};
869874
(
870875
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
871876
signature_tlv_stream, experimental_offer_tlv_stream,
872-
experimental_invoice_request_tlv_stream,
877+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
873878
)
874879
}
875880

@@ -1130,9 +1135,12 @@ impl InvoiceContents {
11301135
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11311136
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11321137
};
1133-
let invoice = self.fields().as_tlv_stream();
1138+
let (invoice, experimental_invoice) = self.fields().as_tlv_stream();
11341139

1135-
(payer, offer, invoice_request, invoice, experimental_offer, experimental_invoice_request)
1140+
(
1141+
payer, offer, invoice_request, invoice, experimental_offer,
1142+
experimental_invoice_request, experimental_invoice,
1143+
)
11361144
}
11371145
}
11381146

@@ -1181,24 +1189,27 @@ pub(super) fn filter_fallbacks(
11811189
}
11821190

11831191
impl InvoiceFields {
1184-
fn as_tlv_stream(&self) -> InvoiceTlvStreamRef {
1192+
fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) {
11851193
let features = {
11861194
if self.features == Bolt12InvoiceFeatures::empty() { None }
11871195
else { Some(&self.features) }
11881196
};
11891197

1190-
InvoiceTlvStreamRef {
1191-
paths: Some(Iterable(self.payment_paths.iter().map(|(_, path)| path))),
1192-
blindedpay: Some(Iterable(self.payment_paths.iter().map(|(payinfo, _)| payinfo))),
1193-
created_at: Some(self.created_at.as_secs()),
1194-
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
1195-
payment_hash: Some(&self.payment_hash),
1196-
amount: Some(self.amount_msats),
1197-
fallbacks: self.fallbacks.as_ref(),
1198-
features,
1199-
node_id: Some(&self.signing_pubkey),
1200-
message_paths: None,
1201-
}
1198+
(
1199+
InvoiceTlvStreamRef {
1200+
paths: Some(Iterable(self.payment_paths.iter().map(|(_, path)| path))),
1201+
blindedpay: Some(Iterable(self.payment_paths.iter().map(|(payinfo, _)| 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+
},
1211+
ExperimentalInvoiceTlvStreamRef {},
1212+
)
12021213
}
12031214
}
12041215

@@ -1236,11 +1247,13 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12361247
let (
12371248
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
12381249
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1250+
experimental_invoice_tlv_stream,
12391251
) = tlv_stream;
12401252
let contents = InvoiceContents::try_from(
12411253
(
12421254
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
12431255
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1256+
experimental_invoice_tlv_stream,
12441257
)
12451258
)?;
12461259

@@ -1283,6 +1296,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
12831296
(236, message_paths: (Vec<BlindedPath>, WithoutLength)),
12841297
});
12851298

1299+
/// Valid type range for experimental invoice TLV records.
1300+
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1301+
1302+
tlv_stream!(
1303+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
1304+
);
1305+
12861306
pub(super) type BlindedPathIter<'a> = core::iter::Map<
12871307
core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
12881308
for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPath,
@@ -1342,7 +1362,7 @@ impl_writeable!(FallbackAddress, { version, program });
13421362

13431363
type FullInvoiceTlvStream =(
13441364
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1345-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1365+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13461366
);
13471367

13481368
type FullInvoiceTlvStreamRef<'a> = (
@@ -1353,6 +1373,7 @@ type FullInvoiceTlvStreamRef<'a> = (
13531373
SignatureTlvStreamRef<'a>,
13541374
ExperimentalOfferTlvStreamRef,
13551375
ExperimentalInvoiceRequestTlvStreamRef,
1376+
ExperimentalInvoiceTlvStreamRef,
13561377
);
13571378

13581379
impl SeekReadable for FullInvoiceTlvStream {
@@ -1364,19 +1385,20 @@ impl SeekReadable for FullInvoiceTlvStream {
13641385
let signature = SeekReadable::read(r)?;
13651386
let experimental_offer = SeekReadable::read(r)?;
13661387
let experimental_invoice_request = SeekReadable::read(r)?;
1388+
let experimental_invoice = SeekReadable::read(r)?;
13671389

13681390
Ok(
13691391
(
13701392
payer, offer, invoice_request, invoice, signature, experimental_offer,
1371-
experimental_invoice_request,
1393+
experimental_invoice_request, experimental_invoice,
13721394
)
13731395
)
13741396
}
13751397
}
13761398

13771399
type PartialInvoiceTlvStream = (
13781400
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1379-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1401+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13801402
);
13811403

13821404
type PartialInvoiceTlvStreamRef<'a> = (
@@ -1386,6 +1408,7 @@ type PartialInvoiceTlvStreamRef<'a> = (
13861408
InvoiceTlvStreamRef<'a>,
13871409
ExperimentalOfferTlvStreamRef,
13881410
ExperimentalInvoiceRequestTlvStreamRef,
1411+
ExperimentalInvoiceTlvStreamRef,
13891412
);
13901413

13911414
impl SeekReadable for PartialInvoiceTlvStream {
@@ -1396,11 +1419,12 @@ impl SeekReadable for PartialInvoiceTlvStream {
13961419
let invoice = SeekReadable::read(r)?;
13971420
let experimental_offer = SeekReadable::read(r)?;
13981421
let experimental_invoice_request = SeekReadable::read(r)?;
1422+
let experimental_invoice= SeekReadable::read(r)?;
13991423

14001424
Ok(
14011425
(
14021426
payer, offer, invoice_request, invoice, experimental_offer,
1403-
experimental_invoice_request,
1427+
experimental_invoice_request, experimental_invoice,
14041428
)
14051429
)
14061430
}
@@ -1416,11 +1440,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
14161440
SignatureTlvStream { signature },
14171441
experimental_offer_tlv_stream,
14181442
experimental_invoice_request_tlv_stream,
1443+
experimental_invoice_tlv_stream,
14191444
) = tlv_stream;
14201445
let contents = InvoiceContents::try_from(
14211446
(
14221447
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
14231448
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1449+
experimental_invoice_tlv_stream,
14241450
)
14251451
)?;
14261452

@@ -1449,6 +1475,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14491475
},
14501476
experimental_offer_tlv_stream,
14511477
experimental_invoice_request_tlv_stream,
1478+
ExperimentalInvoiceTlvStream {},
14521479
) = tlv_stream;
14531480

14541481
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1540,7 +1567,7 @@ pub(super) fn check_invoice_signing_pubkey(
15401567

15411568
#[cfg(test)]
15421569
mod tests {
1543-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1570+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15441571

15451572
use bitcoin::{WitnessProgram, WitnessVersion};
15461573
use bitcoin::blockdata::constants::ChainHash;
@@ -1735,6 +1762,7 @@ mod tests {
17351762
ExperimentalInvoiceRequestTlvStreamRef {
17361763
experimental_bar: None,
17371764
},
1765+
ExperimentalInvoiceTlvStreamRef {},
17381766
),
17391767
);
17401768

@@ -1834,6 +1862,7 @@ mod tests {
18341862
ExperimentalInvoiceRequestTlvStreamRef {
18351863
experimental_bar: None,
18361864
},
1865+
ExperimentalInvoiceTlvStreamRef {},
18371866
),
18381867
);
18391868

@@ -2032,7 +2061,7 @@ mod tests {
20322061
.relative_expiry(one_hour.as_secs() as u32)
20332062
.build().unwrap()
20342063
.sign(recipient_sign).unwrap();
2035-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2064+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20362065
#[cfg(feature = "std")]
20372066
assert!(!invoice.is_expired());
20382067
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -2048,7 +2077,7 @@ mod tests {
20482077
.relative_expiry(one_hour.as_secs() as u32 - 1)
20492078
.build().unwrap()
20502079
.sign(recipient_sign).unwrap();
2051-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2080+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20522081
#[cfg(feature = "std")]
20532082
assert!(invoice.is_expired());
20542083
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -2067,7 +2096,7 @@ mod tests {
20672096
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20682097
.build().unwrap()
20692098
.sign(recipient_sign).unwrap();
2070-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2099+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20712100
assert_eq!(invoice.amount_msats(), 1001);
20722101
assert_eq!(tlv_stream.amount, Some(1001));
20732102
}
@@ -2085,7 +2114,7 @@ mod tests {
20852114
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20862115
.build().unwrap()
20872116
.sign(recipient_sign).unwrap();
2088-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2117+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20892118
assert_eq!(invoice.amount_msats(), 2000);
20902119
assert_eq!(tlv_stream.amount, Some(2000));
20912120

@@ -2123,7 +2152,7 @@ mod tests {
21232152
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
21242153
.build().unwrap()
21252154
.sign(recipient_sign).unwrap();
2126-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2155+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21272156
assert_eq!(
21282157
invoice.fallbacks(),
21292158
vec![
@@ -2166,7 +2195,7 @@ mod tests {
21662195
.allow_mpp()
21672196
.build().unwrap()
21682197
.sign(recipient_sign).unwrap();
2169-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2198+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21702199
assert_eq!(invoice.invoice_features(), &features);
21712200
assert_eq!(tlv_stream.features, Some(&features));
21722201
}

lightning/src/offers/invoice_request.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1527,7 +1527,7 @@ mod tests {
15271527
let (
15281528
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
15291529
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1530-
experimental_invoice_request_tlv_stream,
1530+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15311531
) = invoice.as_tlv_stream();
15321532
invoice_request_tlv_stream.amount = Some(2000);
15331533
invoice_tlv_stream.amount = Some(2000);
@@ -1536,6 +1536,7 @@ mod tests {
15361536
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15371537
let experimental_tlv_stream = (
15381538
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1539+
experimental_invoice_tlv_stream,
15391540
);
15401541
let mut bytes = Vec::new();
15411542
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1556,7 +1557,7 @@ mod tests {
15561557
let (
15571558
mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
15581559
mut signature_tlv_stream, experimental_offer_tlv_stream,
1559-
experimental_invoice_request_tlv_stream,
1560+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15601561
) = invoice.as_tlv_stream();
15611562
let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
15621563
payer_tlv_stream.metadata = Some(&metadata);
@@ -1565,6 +1566,7 @@ mod tests {
15651566
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15661567
let experimental_tlv_stream = (
15671568
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1569+
experimental_invoice_tlv_stream,
15681570
);
15691571
let mut bytes = Vec::new();
15701572
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1614,7 +1616,7 @@ mod tests {
16141616
let (
16151617
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
16161618
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1617-
experimental_invoice_request_tlv_stream,
1619+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16181620
) = invoice.as_tlv_stream();
16191621
invoice_request_tlv_stream.amount = Some(2000);
16201622
invoice_tlv_stream.amount = Some(2000);
@@ -1623,6 +1625,7 @@ mod tests {
16231625
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16241626
let experimental_tlv_stream = (
16251627
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1628+
experimental_invoice_tlv_stream,
16261629
);
16271630
let mut bytes = Vec::new();
16281631
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1645,7 +1648,7 @@ mod tests {
16451648
let (
16461649
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
16471650
mut signature_tlv_stream, experimental_offer_tlv_stream,
1648-
experimental_invoice_request_tlv_stream,
1651+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16491652
) = invoice.as_tlv_stream();
16501653
let payer_id = pubkey(1);
16511654
invoice_request_tlv_stream.payer_id = Some(&payer_id);
@@ -1654,6 +1657,7 @@ mod tests {
16541657
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16551658
let experimental_tlv_stream = (
16561659
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1660+
experimental_invoice_tlv_stream,
16571661
);
16581662
let mut bytes = Vec::new();
16591663
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();

0 commit comments

Comments
 (0)