Skip to content

Commit 9524cc6

Browse files
committed
Parse experimental offer TLV records
The BOLT12 spec defines an experimental TLV range that are allowed in offer messages. Allow this range when parsing an offer and include those bytes in any invoice requests. Also include those bytes when computing an OfferId and verifying that an InvoiceRequest is for a valid Offer.
1 parent 7f21788 commit 9524cc6

File tree

6 files changed

+309
-173
lines changed

6 files changed

+309
-173
lines changed

lightning/src/offers/invoice.rs

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ use crate::offers::invoice_macros::invoice_builder_methods_test;
124124
use crate::offers::invoice_request::{EXPERIMENTAL_INVOICE_REQUEST_TYPES, INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
125125
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self, SIGNATURE_TLV_RECORD_SIZE};
126126
use crate::offers::nonce::Nonce;
127-
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
127+
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
128128
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
129129
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
130130
use crate::offers::refund::{IV_BYTES_WITH_METADATA as REFUND_IV_BYTES_WITH_METADATA, IV_BYTES_WITHOUT_METADATA as REFUND_IV_BYTES_WITHOUT_METADATA, Refund, RefundContents};
@@ -497,7 +497,7 @@ 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, _) = contents.as_tlv_stream();
501501

502502
// Allocate enough space for the invoice, which will include:
503503
// - all TLV records from `invreq_bytes` except signatures,
@@ -891,13 +891,17 @@ impl Bolt12Invoice {
891891
}
892892

893893
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
894-
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
895-
self.contents.as_tlv_stream();
894+
let (
895+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
896+
experimental_offer_tlv_stream,
897+
) = self.contents.as_tlv_stream();
896898
let signature_tlv_stream = SignatureTlvStreamRef {
897899
signature: Some(&self.signature),
898900
};
899-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
900-
signature_tlv_stream)
901+
(
902+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
903+
signature_tlv_stream, experimental_offer_tlv_stream,
904+
)
901905
}
902906

903907
pub(crate) fn is_for_refund_without_paths(&self) -> bool {
@@ -1135,7 +1139,7 @@ impl InvoiceContents {
11351139

11361140
fn verify<T: secp256k1::Signing>(
11371141
&self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
1138-
secp_ctx: &Secp256k1<T>
1142+
secp_ctx: &Secp256k1<T>,
11391143
) -> Result<PaymentId, ()> {
11401144
const EXPERIMENTAL_TYPES: core::ops::Range<u64> =
11411145
EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end;
@@ -1158,13 +1162,13 @@ impl InvoiceContents {
11581162
}
11591163

11601164
fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
1161-
let (payer, offer, invoice_request) = match self {
1165+
let (payer, offer, invoice_request, experimental_offer) = match self {
11621166
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11631167
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11641168
};
11651169
let invoice = self.fields().as_tlv_stream();
11661170

1167-
(payer, offer, invoice_request, invoice)
1171+
(payer, offer, invoice_request, invoice, experimental_offer)
11681172
}
11691173
}
11701174

@@ -1323,15 +1327,18 @@ pub(super) struct FallbackAddress {
13231327

13241328
impl_writeable!(FallbackAddress, { version, program });
13251329

1326-
type FullInvoiceTlvStream =
1327-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
1330+
type FullInvoiceTlvStream =(
1331+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1332+
ExperimentalOfferTlvStream,
1333+
);
13281334

13291335
type FullInvoiceTlvStreamRef<'a> = (
13301336
PayerTlvStreamRef<'a>,
13311337
OfferTlvStreamRef<'a>,
13321338
InvoiceRequestTlvStreamRef<'a>,
13331339
InvoiceTlvStreamRef<'a>,
13341340
SignatureTlvStreamRef<'a>,
1341+
ExperimentalOfferTlvStreamRef,
13351342
);
13361343

13371344
impl CursorReadable for FullInvoiceTlvStream {
@@ -1341,19 +1348,23 @@ impl CursorReadable for FullInvoiceTlvStream {
13411348
let invoice_request = CursorReadable::read(r)?;
13421349
let invoice = CursorReadable::read(r)?;
13431350
let signature = CursorReadable::read(r)?;
1351+
let experimental_offer = CursorReadable::read(r)?;
13441352

1345-
Ok((payer, offer, invoice_request, invoice, signature))
1353+
Ok((payer, offer, invoice_request, invoice, signature, experimental_offer))
13461354
}
13471355
}
13481356

1349-
type PartialInvoiceTlvStream =
1350-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
1357+
type PartialInvoiceTlvStream = (
1358+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1359+
ExperimentalOfferTlvStream,
1360+
);
13511361

13521362
type PartialInvoiceTlvStreamRef<'a> = (
13531363
PayerTlvStreamRef<'a>,
13541364
OfferTlvStreamRef<'a>,
13551365
InvoiceRequestTlvStreamRef<'a>,
13561366
InvoiceTlvStreamRef<'a>,
1367+
ExperimentalOfferTlvStreamRef,
13571368
);
13581369

13591370
impl CursorReadable for PartialInvoiceTlvStream {
@@ -1362,8 +1373,9 @@ impl CursorReadable for PartialInvoiceTlvStream {
13621373
let offer = CursorReadable::read(r)?;
13631374
let invoice_request = CursorReadable::read(r)?;
13641375
let invoice = CursorReadable::read(r)?;
1376+
let experimental_offer = CursorReadable::read(r)?;
13651377

1366-
Ok((payer, offer, invoice_request, invoice))
1378+
Ok((payer, offer, invoice_request, invoice, experimental_offer))
13671379
}
13681380
}
13691381

@@ -1375,9 +1387,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
13751387
let (
13761388
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
13771389
SignatureTlvStream { signature },
1390+
experimental_offer_tlv_stream,
13781391
) = tlv_stream;
13791392
let contents = InvoiceContents::try_from(
1380-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
1393+
(
1394+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1395+
experimental_offer_tlv_stream,
1396+
)
13811397
)?;
13821398

13831399
let signature = signature.ok_or(
@@ -1403,6 +1419,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14031419
paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks,
14041420
features, node_id, message_paths,
14051421
},
1422+
experimental_offer_tlv_stream,
14061423
) = tlv_stream;
14071424

14081425
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1435,12 +1452,18 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14351452

14361453
if offer_tlv_stream.issuer_id.is_none() && offer_tlv_stream.paths.is_none() {
14371454
let refund = RefundContents::try_from(
1438-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1455+
(
1456+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1457+
experimental_offer_tlv_stream,
1458+
)
14391459
)?;
14401460
Ok(InvoiceContents::ForRefund { refund, fields })
14411461
} else {
14421462
let invoice_request = InvoiceRequestContents::try_from(
1443-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1463+
(
1464+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1465+
experimental_offer_tlv_stream,
1466+
)
14441467
)?;
14451468
Ok(InvoiceContents::ForOffer { invoice_request, fields })
14461469
}
@@ -1515,7 +1538,7 @@ mod tests {
15151538
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
15161539
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
15171540
use crate::offers::nonce::Nonce;
1518-
use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
1541+
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
15191542
use crate::prelude::*;
15201543
#[cfg(not(c_bindings))]
15211544
use {
@@ -1683,6 +1706,7 @@ mod tests {
16831706
message_paths: None,
16841707
},
16851708
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1709+
ExperimentalOfferTlvStreamRef {},
16861710
),
16871711
);
16881712

@@ -1776,6 +1800,7 @@ mod tests {
17761800
message_paths: None,
17771801
},
17781802
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1803+
ExperimentalOfferTlvStreamRef {},
17791804
),
17801805
);
17811806

@@ -1969,7 +1994,7 @@ mod tests {
19691994
.relative_expiry(one_hour.as_secs() as u32)
19701995
.build().unwrap()
19711996
.sign(recipient_sign).unwrap();
1972-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
1997+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19731998
#[cfg(feature = "std")]
19741999
assert!(!invoice.is_expired());
19752000
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -1985,7 +2010,7 @@ mod tests {
19852010
.relative_expiry(one_hour.as_secs() as u32 - 1)
19862011
.build().unwrap()
19872012
.sign(recipient_sign).unwrap();
1988-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2013+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19892014
#[cfg(feature = "std")]
19902015
assert!(invoice.is_expired());
19912016
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -2004,7 +2029,7 @@ mod tests {
20042029
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20052030
.build().unwrap()
20062031
.sign(recipient_sign).unwrap();
2007-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2032+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20082033
assert_eq!(invoice.amount_msats(), 1001);
20092034
assert_eq!(tlv_stream.amount, Some(1001));
20102035
}
@@ -2022,7 +2047,7 @@ mod tests {
20222047
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20232048
.build().unwrap()
20242049
.sign(recipient_sign).unwrap();
2025-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2050+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20262051
assert_eq!(invoice.amount_msats(), 2000);
20272052
assert_eq!(tlv_stream.amount, Some(2000));
20282053

@@ -2060,7 +2085,7 @@ mod tests {
20602085
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
20612086
.build().unwrap()
20622087
.sign(recipient_sign).unwrap();
2063-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2088+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20642089
assert_eq!(
20652090
invoice.fallbacks(),
20662091
vec![
@@ -2103,7 +2128,7 @@ mod tests {
21032128
.allow_mpp()
21042129
.build().unwrap()
21052130
.sign(recipient_sign).unwrap();
2106-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2131+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
21072132
assert_eq!(invoice.invoice_features(), &features);
21082133
assert_eq!(tlv_stream.features, Some(&features));
21092134
}

0 commit comments

Comments
 (0)