Skip to content

Commit 927c477

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 1d477ea commit 927c477

File tree

7 files changed

+380
-212
lines changed

7 files changed

+380
-212
lines changed

lightning/src/offers/invoice.rs

Lines changed: 74 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_me
122122
#[cfg(test)]
123123
use crate::offers::invoice_macros::invoice_builder_methods_test;
124124
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
125-
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
125+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
126126
use crate::offers::nonce::Nonce;
127-
use crate::offers::offer::{Amount, 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};
@@ -492,17 +492,26 @@ where
492492

493493
impl UnsignedBolt12Invoice {
494494
fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
495+
const NON_EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..INVOICE_REQUEST_TYPES.end;
496+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> = EXPERIMENTAL_OFFER_TYPES;
497+
498+
let mut bytes = Vec::new();
499+
495500
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
496501
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
497502
// `RefundContents`.
498-
let (_, _, _, invoice_tlv_stream) = contents.as_tlv_stream();
499-
let invoice_request_bytes = WithoutSignatures(invreq_bytes);
500-
let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
503+
for record in TlvStream::new(invreq_bytes).range(NON_EXPERIMENTAL_TYPES) {
504+
record.write(&mut bytes).unwrap();
505+
}
501506

502-
let mut bytes = Vec::new();
503-
unsigned_tlv_stream.write(&mut bytes).unwrap();
507+
let (_, _, _, invoice_tlv_stream, _) = contents.as_tlv_stream();
508+
invoice_tlv_stream.write(&mut bytes).unwrap();
504509

505-
let experimental_bytes = Vec::new();
510+
let mut experimental_bytes = Vec::new();
511+
512+
for record in TlvStream::new(invreq_bytes).range(EXPERIMENTAL_TYPES) {
513+
record.write(&mut experimental_bytes).unwrap();
514+
}
506515

507516
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
508517
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
@@ -858,13 +867,17 @@ impl Bolt12Invoice {
858867
}
859868

860869
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
861-
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
862-
self.contents.as_tlv_stream();
870+
let (
871+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
872+
experimental_offer_tlv_stream,
873+
) = self.contents.as_tlv_stream();
863874
let signature_tlv_stream = SignatureTlvStreamRef {
864875
signature: Some(&self.signature),
865876
};
866-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
867-
signature_tlv_stream)
877+
(
878+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
879+
signature_tlv_stream, experimental_offer_tlv_stream,
880+
)
868881
}
869882

870883
pub(crate) fn is_for_refund_without_paths(&self) -> bool {
@@ -1095,8 +1108,10 @@ impl InvoiceContents {
10951108

10961109
fn verify<T: secp256k1::Signing>(
10971110
&self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
1098-
secp_ctx: &Secp256k1<T>
1111+
secp_ctx: &Secp256k1<T>,
10991112
) -> Result<PaymentId, ()> {
1113+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> = EXPERIMENTAL_OFFER_TYPES;
1114+
11001115
let offer_records = TlvStream::new(bytes).range(OFFER_TYPES);
11011116
let invreq_records = TlvStream::new(bytes).range(INVOICE_REQUEST_TYPES).filter(|record| {
11021117
match record.r#type {
@@ -1105,7 +1120,8 @@ impl InvoiceContents {
11051120
_ => true,
11061121
}
11071122
});
1108-
let tlv_stream = offer_records.chain(invreq_records);
1123+
let experimental_records = TlvStream::new(bytes).range(EXPERIMENTAL_TYPES);
1124+
let tlv_stream = offer_records.chain(invreq_records).chain(experimental_records);
11091125

11101126
let signing_pubkey = self.payer_signing_pubkey();
11111127
signer::verify_payer_metadata(
@@ -1114,13 +1130,13 @@ impl InvoiceContents {
11141130
}
11151131

11161132
fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
1117-
let (payer, offer, invoice_request) = match self {
1133+
let (payer, offer, invoice_request, experimental_offer) = match self {
11181134
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11191135
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11201136
};
11211137
let invoice = self.fields().as_tlv_stream();
11221138

1123-
(payer, offer, invoice_request, invoice)
1139+
(payer, offer, invoice_request, invoice, experimental_offer)
11241140
}
11251141
}
11261142

@@ -1222,9 +1238,13 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12221238
let ParsedMessage { mut bytes, tlv_stream } = invoice;
12231239
let (
12241240
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1241+
experimental_offer_tlv_stream,
12251242
) = tlv_stream;
12261243
let contents = InvoiceContents::try_from(
1227-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
1244+
(
1245+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1246+
experimental_offer_tlv_stream,
1247+
)
12281248
)?;
12291249

12301250
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
@@ -1285,15 +1305,18 @@ pub(super) struct FallbackAddress {
12851305

12861306
impl_writeable!(FallbackAddress, { version, program });
12871307

1288-
type FullInvoiceTlvStream =
1289-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
1308+
type FullInvoiceTlvStream =(
1309+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1310+
ExperimentalOfferTlvStream,
1311+
);
12901312

12911313
type FullInvoiceTlvStreamRef<'a> = (
12921314
PayerTlvStreamRef<'a>,
12931315
OfferTlvStreamRef<'a>,
12941316
InvoiceRequestTlvStreamRef<'a>,
12951317
InvoiceTlvStreamRef<'a>,
12961318
SignatureTlvStreamRef<'a>,
1319+
ExperimentalOfferTlvStreamRef,
12971320
);
12981321

12991322
impl CursorReadable for FullInvoiceTlvStream {
@@ -1303,19 +1326,23 @@ impl CursorReadable for FullInvoiceTlvStream {
13031326
let invoice_request = CursorReadable::read(r)?;
13041327
let invoice = CursorReadable::read(r)?;
13051328
let signature = CursorReadable::read(r)?;
1329+
let experimental_offer = CursorReadable::read(r)?;
13061330

1307-
Ok((payer, offer, invoice_request, invoice, signature))
1331+
Ok((payer, offer, invoice_request, invoice, signature, experimental_offer))
13081332
}
13091333
}
13101334

1311-
type PartialInvoiceTlvStream =
1312-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
1335+
type PartialInvoiceTlvStream = (
1336+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1337+
ExperimentalOfferTlvStream,
1338+
);
13131339

13141340
type PartialInvoiceTlvStreamRef<'a> = (
13151341
PayerTlvStreamRef<'a>,
13161342
OfferTlvStreamRef<'a>,
13171343
InvoiceRequestTlvStreamRef<'a>,
13181344
InvoiceTlvStreamRef<'a>,
1345+
ExperimentalOfferTlvStreamRef,
13191346
);
13201347

13211348
impl CursorReadable for PartialInvoiceTlvStream {
@@ -1324,8 +1351,9 @@ impl CursorReadable for PartialInvoiceTlvStream {
13241351
let offer = CursorReadable::read(r)?;
13251352
let invoice_request = CursorReadable::read(r)?;
13261353
let invoice = CursorReadable::read(r)?;
1354+
let experimental_offer = CursorReadable::read(r)?;
13271355

1328-
Ok((payer, offer, invoice_request, invoice))
1356+
Ok((payer, offer, invoice_request, invoice, experimental_offer))
13291357
}
13301358
}
13311359

@@ -1337,9 +1365,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
13371365
let (
13381366
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
13391367
SignatureTlvStream { signature },
1368+
experimental_offer_tlv_stream,
13401369
) = tlv_stream;
13411370
let contents = InvoiceContents::try_from(
1342-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
1371+
(
1372+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1373+
experimental_offer_tlv_stream,
1374+
)
13431375
)?;
13441376

13451377
let signature = signature.ok_or(
@@ -1365,6 +1397,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
13651397
paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks,
13661398
features, node_id, message_paths,
13671399
},
1400+
experimental_offer_tlv_stream,
13681401
) = tlv_stream;
13691402

13701403
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1397,12 +1430,18 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
13971430

13981431
if offer_tlv_stream.issuer_id.is_none() && offer_tlv_stream.paths.is_none() {
13991432
let refund = RefundContents::try_from(
1400-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1433+
(
1434+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1435+
experimental_offer_tlv_stream,
1436+
)
14011437
)?;
14021438
Ok(InvoiceContents::ForRefund { refund, fields })
14031439
} else {
14041440
let invoice_request = InvoiceRequestContents::try_from(
1405-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1441+
(
1442+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1443+
experimental_offer_tlv_stream,
1444+
)
14061445
)?;
14071446
Ok(InvoiceContents::ForOffer { invoice_request, fields })
14081447
}
@@ -1477,7 +1516,7 @@ mod tests {
14771516
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
14781517
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
14791518
use crate::offers::nonce::Nonce;
1480-
use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
1519+
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
14811520
use crate::prelude::*;
14821521
#[cfg(not(c_bindings))]
14831522
use {
@@ -1645,6 +1684,7 @@ mod tests {
16451684
message_paths: None,
16461685
},
16471686
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1687+
ExperimentalOfferTlvStreamRef {},
16481688
),
16491689
);
16501690

@@ -1738,6 +1778,7 @@ mod tests {
17381778
message_paths: None,
17391779
},
17401780
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1781+
ExperimentalOfferTlvStreamRef {},
17411782
),
17421783
);
17431784

@@ -1931,7 +1972,7 @@ mod tests {
19311972
.relative_expiry(one_hour.as_secs() as u32)
19321973
.build().unwrap()
19331974
.sign(recipient_sign).unwrap();
1934-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
1975+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19351976
#[cfg(feature = "std")]
19361977
assert!(!invoice.is_expired());
19371978
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -1947,7 +1988,7 @@ mod tests {
19471988
.relative_expiry(one_hour.as_secs() as u32 - 1)
19481989
.build().unwrap()
19491990
.sign(recipient_sign).unwrap();
1950-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
1991+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19511992
#[cfg(feature = "std")]
19521993
assert!(invoice.is_expired());
19531994
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -1966,7 +2007,7 @@ mod tests {
19662007
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
19672008
.build().unwrap()
19682009
.sign(recipient_sign).unwrap();
1969-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2010+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19702011
assert_eq!(invoice.amount_msats(), 1001);
19712012
assert_eq!(tlv_stream.amount, Some(1001));
19722013
}
@@ -1984,7 +2025,7 @@ mod tests {
19842025
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
19852026
.build().unwrap()
19862027
.sign(recipient_sign).unwrap();
1987-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2028+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19882029
assert_eq!(invoice.amount_msats(), 2000);
19892030
assert_eq!(tlv_stream.amount, Some(2000));
19902031

@@ -2022,7 +2063,7 @@ mod tests {
20222063
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
20232064
.build().unwrap()
20242065
.sign(recipient_sign).unwrap();
2025-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2066+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20262067
assert_eq!(
20272068
invoice.fallbacks(),
20282069
vec![
@@ -2065,7 +2106,7 @@ mod tests {
20652106
.allow_mpp()
20662107
.build().unwrap()
20672108
.sign(recipient_sign).unwrap();
2068-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2109+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20692110
assert_eq!(invoice.invoice_features(), &features);
20702111
assert_eq!(tlv_stream.features, Some(&features));
20712112
}

0 commit comments

Comments
 (0)