Skip to content

Commit 8e279f7

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 22d30e6 commit 8e279f7

File tree

4 files changed

+134
-46
lines changed

4 files changed

+134
-46
lines changed

lightning/src/offers/invoice.rs

Lines changed: 55 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,14 @@ 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();
540544
debug_assert_eq!(experimental_bytes.len(), experimental_bytes.capacity());
541545

542546
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
@@ -904,14 +908,15 @@ impl Bolt12Invoice {
904908
let (
905909
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
906910
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
911+
experimental_invoice_tlv_stream,
907912
) = self.contents.as_tlv_stream();
908913
let signature_tlv_stream = SignatureTlvStreamRef {
909914
signature: Some(&self.signature),
910915
};
911916
(
912917
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
913918
signature_tlv_stream, experimental_offer_tlv_stream,
914-
experimental_invoice_request_tlv_stream,
919+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
915920
)
916921
}
917922

@@ -1179,9 +1184,12 @@ impl InvoiceContents {
11791184
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11801185
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11811186
};
1182-
let invoice = self.fields().as_tlv_stream();
1187+
let (invoice, experimental_invoice) = self.fields().as_tlv_stream();
11831188

1184-
(payer, offer, invoice_request, invoice, experimental_offer, experimental_invoice_request)
1189+
(
1190+
payer, offer, invoice_request, invoice, experimental_offer,
1191+
experimental_invoice_request, experimental_invoice,
1192+
)
11851193
}
11861194
}
11871195

@@ -1229,24 +1237,27 @@ pub(super) fn filter_fallbacks(
12291237
}
12301238

12311239
impl InvoiceFields {
1232-
fn as_tlv_stream(&self) -> InvoiceTlvStreamRef {
1240+
fn as_tlv_stream(&self) -> (InvoiceTlvStreamRef, ExperimentalInvoiceTlvStreamRef) {
12331241
let features = {
12341242
if self.features == Bolt12InvoiceFeatures::empty() { None }
12351243
else { Some(&self.features) }
12361244
};
12371245

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-
}
1246+
(
1247+
InvoiceTlvStreamRef {
1248+
paths: Some(Iterable(self.payment_paths.iter().map(|path| path.inner_blinded_path()))),
1249+
blindedpay: Some(Iterable(self.payment_paths.iter().map(|path| &path.payinfo))),
1250+
created_at: Some(self.created_at.as_secs()),
1251+
relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
1252+
payment_hash: Some(&self.payment_hash),
1253+
amount: Some(self.amount_msats),
1254+
fallbacks: self.fallbacks.as_ref(),
1255+
features,
1256+
node_id: Some(&self.signing_pubkey),
1257+
message_paths: None,
1258+
},
1259+
ExperimentalInvoiceTlvStreamRef {},
1260+
)
12501261
}
12511262
}
12521263

@@ -1321,6 +1332,13 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
13211332
(236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
13221333
});
13231334

1335+
/// Valid type range for experimental invoice TLV records.
1336+
const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
1337+
1338+
tlv_stream!(
1339+
ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
1340+
);
1341+
13241342
pub(super) type BlindedPathIter<'a> = core::iter::Map<
13251343
core::slice::Iter<'a, BlindedPaymentPath>,
13261344
for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1342,7 +1360,7 @@ impl_writeable!(FallbackAddress, { version, program });
13421360

13431361
type FullInvoiceTlvStream =(
13441362
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1345-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1363+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13461364
);
13471365

13481366
type FullInvoiceTlvStreamRef<'a> = (
@@ -1353,6 +1371,7 @@ type FullInvoiceTlvStreamRef<'a> = (
13531371
SignatureTlvStreamRef<'a>,
13541372
ExperimentalOfferTlvStreamRef,
13551373
ExperimentalInvoiceRequestTlvStreamRef,
1374+
ExperimentalInvoiceTlvStreamRef,
13561375
);
13571376

13581377
impl CursorReadable for FullInvoiceTlvStream {
@@ -1364,19 +1383,20 @@ impl CursorReadable for FullInvoiceTlvStream {
13641383
let signature = CursorReadable::read(r)?;
13651384
let experimental_offer = CursorReadable::read(r)?;
13661385
let experimental_invoice_request = CursorReadable::read(r)?;
1386+
let experimental_invoice = CursorReadable::read(r)?;
13671387

13681388
Ok(
13691389
(
13701390
payer, offer, invoice_request, invoice, signature, experimental_offer,
1371-
experimental_invoice_request,
1391+
experimental_invoice_request, experimental_invoice,
13721392
)
13731393
)
13741394
}
13751395
}
13761396

13771397
type PartialInvoiceTlvStream = (
13781398
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1379-
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream,
1399+
ExperimentalOfferTlvStream, ExperimentalInvoiceRequestTlvStream, ExperimentalInvoiceTlvStream,
13801400
);
13811401

13821402
type PartialInvoiceTlvStreamRef<'a> = (
@@ -1386,6 +1406,7 @@ type PartialInvoiceTlvStreamRef<'a> = (
13861406
InvoiceTlvStreamRef<'a>,
13871407
ExperimentalOfferTlvStreamRef,
13881408
ExperimentalInvoiceRequestTlvStreamRef,
1409+
ExperimentalInvoiceTlvStreamRef,
13891410
);
13901411

13911412
impl CursorReadable for PartialInvoiceTlvStream {
@@ -1396,11 +1417,12 @@ impl CursorReadable for PartialInvoiceTlvStream {
13961417
let invoice = CursorReadable::read(r)?;
13971418
let experimental_offer = CursorReadable::read(r)?;
13981419
let experimental_invoice_request = CursorReadable::read(r)?;
1420+
let experimental_invoice = CursorReadable::read(r)?;
13991421

14001422
Ok(
14011423
(
14021424
payer, offer, invoice_request, invoice, experimental_offer,
1403-
experimental_invoice_request,
1425+
experimental_invoice_request, experimental_invoice,
14041426
)
14051427
)
14061428
}
@@ -1416,11 +1438,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
14161438
SignatureTlvStream { signature },
14171439
experimental_offer_tlv_stream,
14181440
experimental_invoice_request_tlv_stream,
1441+
experimental_invoice_tlv_stream,
14191442
) = tlv_stream;
14201443
let contents = InvoiceContents::try_from(
14211444
(
14221445
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
14231446
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1447+
experimental_invoice_tlv_stream,
14241448
)
14251449
)?;
14261450

@@ -1449,6 +1473,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14491473
},
14501474
experimental_offer_tlv_stream,
14511475
experimental_invoice_request_tlv_stream,
1476+
ExperimentalInvoiceTlvStream {},
14521477
) = tlv_stream;
14531478

14541479
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1545,7 +1570,7 @@ pub(super) fn check_invoice_signing_pubkey(
15451570

15461571
#[cfg(test)]
15471572
mod tests {
1548-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1573+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
15491574

15501575
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
15511576
use bitcoin::constants::ChainHash;
@@ -1741,6 +1766,7 @@ mod tests {
17411766
ExperimentalInvoiceRequestTlvStreamRef {
17421767
experimental_bar: None,
17431768
},
1769+
ExperimentalInvoiceTlvStreamRef {},
17441770
),
17451771
);
17461772

@@ -1840,6 +1866,7 @@ mod tests {
18401866
ExperimentalInvoiceRequestTlvStreamRef {
18411867
experimental_bar: None,
18421868
},
1869+
ExperimentalInvoiceTlvStreamRef {},
18431870
),
18441871
);
18451872

@@ -2036,7 +2063,7 @@ mod tests {
20362063
.relative_expiry(one_hour.as_secs() as u32)
20372064
.build().unwrap()
20382065
.sign(recipient_sign).unwrap();
2039-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2066+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20402067
#[cfg(feature = "std")]
20412068
assert!(!invoice.is_expired());
20422069
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -2052,7 +2079,7 @@ mod tests {
20522079
.relative_expiry(one_hour.as_secs() as u32 - 1)
20532080
.build().unwrap()
20542081
.sign(recipient_sign).unwrap();
2055-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2082+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20562083
#[cfg(feature = "std")]
20572084
assert!(invoice.is_expired());
20582085
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -2071,7 +2098,7 @@ mod tests {
20712098
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20722099
.build().unwrap()
20732100
.sign(recipient_sign).unwrap();
2074-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2101+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20752102
assert_eq!(invoice.amount_msats(), 1001);
20762103
assert_eq!(tlv_stream.amount, Some(1001));
20772104
}
@@ -2089,7 +2116,7 @@ mod tests {
20892116
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20902117
.build().unwrap()
20912118
.sign(recipient_sign).unwrap();
2092-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2119+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
20932120
assert_eq!(invoice.amount_msats(), 2000);
20942121
assert_eq!(tlv_stream.amount, Some(2000));
20952122

@@ -2127,7 +2154,7 @@ mod tests {
21272154
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
21282155
.build().unwrap()
21292156
.sign(recipient_sign).unwrap();
2130-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2157+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21312158
assert_eq!(
21322159
invoice.fallbacks(),
21332160
vec![
@@ -2170,7 +2197,7 @@ mod tests {
21702197
.allow_mpp()
21712198
.build().unwrap()
21722199
.sign(recipient_sign).unwrap();
2173-
let (_, _, _, tlv_stream, _, _, _) = invoice.as_tlv_stream();
2200+
let (_, _, _, tlv_stream, _, _, _, _) = invoice.as_tlv_stream();
21742201
assert_eq!(invoice.invoice_features(), &features);
21752202
assert_eq!(tlv_stream.features, Some(&features));
21762203
}

lightning/src/offers/invoice_request.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,7 +1565,7 @@ mod tests {
15651565
let (
15661566
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
15671567
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1568-
experimental_invoice_request_tlv_stream,
1568+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15691569
) = invoice.as_tlv_stream();
15701570
invoice_request_tlv_stream.amount = Some(2000);
15711571
invoice_tlv_stream.amount = Some(2000);
@@ -1574,6 +1574,7 @@ mod tests {
15741574
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
15751575
let experimental_tlv_stream = (
15761576
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1577+
experimental_invoice_tlv_stream,
15771578
);
15781579
let mut bytes = Vec::new();
15791580
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1594,7 +1595,7 @@ mod tests {
15941595
let (
15951596
mut payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
15961597
mut signature_tlv_stream, experimental_offer_tlv_stream,
1597-
experimental_invoice_request_tlv_stream,
1598+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
15981599
) = invoice.as_tlv_stream();
15991600
let metadata = payer_tlv_stream.metadata.unwrap().iter().copied().rev().collect();
16001601
payer_tlv_stream.metadata = Some(&metadata);
@@ -1603,6 +1604,7 @@ mod tests {
16031604
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16041605
let experimental_tlv_stream = (
16051606
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1607+
experimental_invoice_tlv_stream,
16061608
);
16071609
let mut bytes = Vec::new();
16081610
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1652,7 +1654,7 @@ mod tests {
16521654
let (
16531655
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream,
16541656
mut invoice_tlv_stream, mut signature_tlv_stream, experimental_offer_tlv_stream,
1655-
experimental_invoice_request_tlv_stream,
1657+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16561658
) = invoice.as_tlv_stream();
16571659
invoice_request_tlv_stream.amount = Some(2000);
16581660
invoice_tlv_stream.amount = Some(2000);
@@ -1661,6 +1663,7 @@ mod tests {
16611663
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16621664
let experimental_tlv_stream = (
16631665
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1666+
experimental_invoice_tlv_stream,
16641667
);
16651668
let mut bytes = Vec::new();
16661669
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();
@@ -1683,7 +1686,7 @@ mod tests {
16831686
let (
16841687
payer_tlv_stream, offer_tlv_stream, mut invoice_request_tlv_stream, invoice_tlv_stream,
16851688
mut signature_tlv_stream, experimental_offer_tlv_stream,
1686-
experimental_invoice_request_tlv_stream,
1689+
experimental_invoice_request_tlv_stream, experimental_invoice_tlv_stream,
16871690
) = invoice.as_tlv_stream();
16881691
let payer_id = pubkey(1);
16891692
invoice_request_tlv_stream.payer_id = Some(&payer_id);
@@ -1692,6 +1695,7 @@ mod tests {
16921695
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream);
16931696
let experimental_tlv_stream = (
16941697
experimental_offer_tlv_stream, experimental_invoice_request_tlv_stream,
1698+
experimental_invoice_tlv_stream,
16951699
);
16961700
let mut bytes = Vec::new();
16971701
(&tlv_stream, &experimental_tlv_stream).write(&mut bytes).unwrap();

0 commit comments

Comments
 (0)