Skip to content

Commit e01e731

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 38e9457 commit e01e731

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,
@@ -901,13 +901,17 @@ impl Bolt12Invoice {
901901
}
902902

903903
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
904-
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
905-
self.contents.as_tlv_stream();
904+
let (
905+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
906+
experimental_offer_tlv_stream,
907+
) = self.contents.as_tlv_stream();
906908
let signature_tlv_stream = SignatureTlvStreamRef {
907909
signature: Some(&self.signature),
908910
};
909-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
910-
signature_tlv_stream)
911+
(
912+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
913+
signature_tlv_stream, experimental_offer_tlv_stream,
914+
)
911915
}
912916

913917
pub(crate) fn is_for_refund_without_paths(&self) -> bool {
@@ -1145,7 +1149,7 @@ impl InvoiceContents {
11451149

11461150
fn verify<T: secp256k1::Signing>(
11471151
&self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
1148-
secp_ctx: &Secp256k1<T>
1152+
secp_ctx: &Secp256k1<T>,
11491153
) -> Result<PaymentId, ()> {
11501154
const EXPERIMENTAL_TYPES: core::ops::Range<u64> =
11511155
EXPERIMENTAL_OFFER_TYPES.start..EXPERIMENTAL_INVOICE_REQUEST_TYPES.end;
@@ -1168,13 +1172,13 @@ impl InvoiceContents {
11681172
}
11691173

11701174
fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
1171-
let (payer, offer, invoice_request) = match self {
1175+
let (payer, offer, invoice_request, experimental_offer) = match self {
11721176
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11731177
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11741178
};
11751179
let invoice = self.fields().as_tlv_stream();
11761180

1177-
(payer, offer, invoice_request, invoice)
1181+
(payer, offer, invoice_request, invoice, experimental_offer)
11781182
}
11791183
}
11801184

@@ -1333,15 +1337,18 @@ pub(super) struct FallbackAddress {
13331337

13341338
impl_writeable!(FallbackAddress, { version, program });
13351339

1336-
type FullInvoiceTlvStream =
1337-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
1340+
type FullInvoiceTlvStream =(
1341+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1342+
ExperimentalOfferTlvStream,
1343+
);
13381344

13391345
type FullInvoiceTlvStreamRef<'a> = (
13401346
PayerTlvStreamRef<'a>,
13411347
OfferTlvStreamRef<'a>,
13421348
InvoiceRequestTlvStreamRef<'a>,
13431349
InvoiceTlvStreamRef<'a>,
13441350
SignatureTlvStreamRef<'a>,
1351+
ExperimentalOfferTlvStreamRef,
13451352
);
13461353

13471354
impl CursorReadable for FullInvoiceTlvStream {
@@ -1351,19 +1358,23 @@ impl CursorReadable for FullInvoiceTlvStream {
13511358
let invoice_request = CursorReadable::read(r)?;
13521359
let invoice = CursorReadable::read(r)?;
13531360
let signature = CursorReadable::read(r)?;
1361+
let experimental_offer = CursorReadable::read(r)?;
13541362

1355-
Ok((payer, offer, invoice_request, invoice, signature))
1363+
Ok((payer, offer, invoice_request, invoice, signature, experimental_offer))
13561364
}
13571365
}
13581366

1359-
type PartialInvoiceTlvStream =
1360-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
1367+
type PartialInvoiceTlvStream = (
1368+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1369+
ExperimentalOfferTlvStream,
1370+
);
13611371

13621372
type PartialInvoiceTlvStreamRef<'a> = (
13631373
PayerTlvStreamRef<'a>,
13641374
OfferTlvStreamRef<'a>,
13651375
InvoiceRequestTlvStreamRef<'a>,
13661376
InvoiceTlvStreamRef<'a>,
1377+
ExperimentalOfferTlvStreamRef,
13671378
);
13681379

13691380
impl CursorReadable for PartialInvoiceTlvStream {
@@ -1372,8 +1383,9 @@ impl CursorReadable for PartialInvoiceTlvStream {
13721383
let offer = CursorReadable::read(r)?;
13731384
let invoice_request = CursorReadable::read(r)?;
13741385
let invoice = CursorReadable::read(r)?;
1386+
let experimental_offer = CursorReadable::read(r)?;
13751387

1376-
Ok((payer, offer, invoice_request, invoice))
1388+
Ok((payer, offer, invoice_request, invoice, experimental_offer))
13771389
}
13781390
}
13791391

@@ -1385,9 +1397,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
13851397
let (
13861398
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
13871399
SignatureTlvStream { signature },
1400+
experimental_offer_tlv_stream,
13881401
) = tlv_stream;
13891402
let contents = InvoiceContents::try_from(
1390-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
1403+
(
1404+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1405+
experimental_offer_tlv_stream,
1406+
)
13911407
)?;
13921408

13931409
let signature = signature.ok_or(
@@ -1413,6 +1429,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14131429
paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks,
14141430
features, node_id, message_paths,
14151431
},
1432+
experimental_offer_tlv_stream,
14161433
) = tlv_stream;
14171434

14181435
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1445,12 +1462,18 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14451462

14461463
if offer_tlv_stream.issuer_id.is_none() && offer_tlv_stream.paths.is_none() {
14471464
let refund = RefundContents::try_from(
1448-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1465+
(
1466+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1467+
experimental_offer_tlv_stream,
1468+
)
14491469
)?;
14501470
Ok(InvoiceContents::ForRefund { refund, fields })
14511471
} else {
14521472
let invoice_request = InvoiceRequestContents::try_from(
1453-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1473+
(
1474+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1475+
experimental_offer_tlv_stream,
1476+
)
14541477
)?;
14551478
Ok(InvoiceContents::ForOffer { invoice_request, fields })
14561479
}
@@ -1525,7 +1548,7 @@ mod tests {
15251548
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
15261549
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
15271550
use crate::offers::nonce::Nonce;
1528-
use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
1551+
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
15291552
use crate::prelude::*;
15301553
#[cfg(not(c_bindings))]
15311554
use {
@@ -1693,6 +1716,7 @@ mod tests {
16931716
message_paths: None,
16941717
},
16951718
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1719+
ExperimentalOfferTlvStreamRef {},
16961720
),
16971721
);
16981722

@@ -1786,6 +1810,7 @@ mod tests {
17861810
message_paths: None,
17871811
},
17881812
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1813+
ExperimentalOfferTlvStreamRef {},
17891814
),
17901815
);
17911816

@@ -1979,7 +2004,7 @@ mod tests {
19792004
.relative_expiry(one_hour.as_secs() as u32)
19802005
.build().unwrap()
19812006
.sign(recipient_sign).unwrap();
1982-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2007+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19832008
#[cfg(feature = "std")]
19842009
assert!(!invoice.is_expired());
19852010
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -1995,7 +2020,7 @@ mod tests {
19952020
.relative_expiry(one_hour.as_secs() as u32 - 1)
19962021
.build().unwrap()
19972022
.sign(recipient_sign).unwrap();
1998-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2023+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19992024
#[cfg(feature = "std")]
20002025
assert!(invoice.is_expired());
20012026
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -2014,7 +2039,7 @@ mod tests {
20142039
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20152040
.build().unwrap()
20162041
.sign(recipient_sign).unwrap();
2017-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2042+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20182043
assert_eq!(invoice.amount_msats(), 1001);
20192044
assert_eq!(tlv_stream.amount, Some(1001));
20202045
}
@@ -2032,7 +2057,7 @@ mod tests {
20322057
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20332058
.build().unwrap()
20342059
.sign(recipient_sign).unwrap();
2035-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2060+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20362061
assert_eq!(invoice.amount_msats(), 2000);
20372062
assert_eq!(tlv_stream.amount, Some(2000));
20382063

@@ -2070,7 +2095,7 @@ mod tests {
20702095
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
20712096
.build().unwrap()
20722097
.sign(recipient_sign).unwrap();
2073-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2098+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20742099
assert_eq!(
20752100
invoice.fallbacks(),
20762101
vec![
@@ -2113,7 +2138,7 @@ mod tests {
21132138
.allow_mpp()
21142139
.build().unwrap()
21152140
.sign(recipient_sign).unwrap();
2116-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2141+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
21172142
assert_eq!(invoice.invoice_features(), &features);
21182143
assert_eq!(tlv_stream.features, Some(&features));
21192144
}

0 commit comments

Comments
 (0)