Skip to content

Commit cd48683

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 2b1a625 commit cd48683

File tree

7 files changed

+387
-218
lines changed

7 files changed

+387
-218
lines changed

lightning/src/offers/invoice.rs

Lines changed: 80 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ use crate::ln::inbound_payment::{ExpandedKey, IV_LEN};
118118
use crate::ln::msgs::DecodeError;
119119
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
120120
use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
121-
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, WithoutSignatures, self};
121+
use crate::offers::merkle::{SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
122122
use crate::offers::nonce::Nonce;
123-
use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
123+
use crate::offers::offer::{Amount, EXPERIMENTAL_OFFER_TYPES, ExperimentalOfferTlvStream, ExperimentalOfferTlvStreamRef, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef, Quantity};
124124
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
125125
use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
126126
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};
@@ -483,17 +483,26 @@ where
483483

484484
impl UnsignedBolt12Invoice {
485485
fn new(invreq_bytes: &[u8], contents: InvoiceContents) -> Self {
486+
const NON_EXPERIMENTAL_TYPES: core::ops::Range<u64> = 0..INVOICE_REQUEST_TYPES.end;
487+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> = EXPERIMENTAL_OFFER_TYPES;
488+
489+
let mut bytes = Vec::new();
490+
486491
// Use the invoice_request bytes instead of the invoice_request TLV stream as the latter may
487492
// have contained unknown TLV records, which are not stored in `InvoiceRequestContents` or
488493
// `RefundContents`.
489-
let (_, _, _, invoice_tlv_stream) = contents.as_tlv_stream();
490-
let invoice_request_bytes = WithoutSignatures(invreq_bytes);
491-
let unsigned_tlv_stream = (invoice_request_bytes, invoice_tlv_stream);
494+
for record in TlvStream::new(invreq_bytes).range(NON_EXPERIMENTAL_TYPES) {
495+
record.write(&mut bytes).unwrap();
496+
}
492497

493-
let mut bytes = Vec::new();
494-
unsigned_tlv_stream.write(&mut bytes).unwrap();
498+
let (_, _, _, invoice_tlv_stream, _) = contents.as_tlv_stream();
499+
invoice_tlv_stream.write(&mut bytes).unwrap();
495500

496-
let experimental_bytes = Vec::new();
501+
let mut experimental_bytes = Vec::new();
502+
503+
for record in TlvStream::new(invreq_bytes).range(EXPERIMENTAL_TYPES) {
504+
record.write(&mut experimental_bytes).unwrap();
505+
}
497506

498507
let tlv_stream = TlvStream::new(&bytes).chain(TlvStream::new(&experimental_bytes));
499508
let tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
@@ -826,7 +835,7 @@ impl Bolt12Invoice {
826835
(&refund.payer.0, REFUND_IV_BYTES_WITH_METADATA)
827836
},
828837
};
829-
self.contents.verify(TlvStream::new(&self.bytes), metadata, key, iv_bytes, secp_ctx)
838+
self.contents.verify(&self.bytes, metadata, key, iv_bytes, secp_ctx)
830839
}
831840

832841
/// Verifies that the invoice was for a request or refund created using the given key by
@@ -840,21 +849,26 @@ impl Bolt12Invoice {
840849
InvoiceContents::ForOffer { .. } => INVOICE_REQUEST_IV_BYTES,
841850
InvoiceContents::ForRefund { .. } => REFUND_IV_BYTES_WITHOUT_METADATA,
842851
};
843-
self.contents.verify(TlvStream::new(&self.bytes), &metadata, key, iv_bytes, secp_ctx)
852+
self.contents
853+
.verify(&self.bytes, &metadata, key, iv_bytes, secp_ctx)
844854
.and_then(|extracted_payment_id| (payment_id == extracted_payment_id)
845855
.then(|| payment_id)
846856
.ok_or(())
847857
)
848858
}
849859

850860
pub(crate) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
851-
let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
852-
self.contents.as_tlv_stream();
861+
let (
862+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
863+
experimental_offer_tlv_stream,
864+
) = self.contents.as_tlv_stream();
853865
let signature_tlv_stream = SignatureTlvStreamRef {
854866
signature: Some(&self.signature),
855867
};
856-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
857-
signature_tlv_stream)
868+
(
869+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
870+
signature_tlv_stream, experimental_offer_tlv_stream,
871+
)
858872
}
859873

860874
pub(crate) fn is_for_refund_without_paths(&self) -> bool {
@@ -1084,18 +1098,21 @@ impl InvoiceContents {
10841098
}
10851099

10861100
fn verify<T: secp256k1::Signing>(
1087-
&self, tlv_stream: TlvStream<'_>, metadata: &Metadata, key: &ExpandedKey,
1088-
iv_bytes: &[u8; IV_LEN], secp_ctx: &Secp256k1<T>
1101+
&self, bytes: &[u8], metadata: &Metadata, key: &ExpandedKey, iv_bytes: &[u8; IV_LEN],
1102+
secp_ctx: &Secp256k1<T>,
10891103
) -> Result<PaymentId, ()> {
1090-
let offer_records = tlv_stream.clone().range(OFFER_TYPES);
1091-
let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
1104+
const EXPERIMENTAL_TYPES: core::ops::Range<u64> = EXPERIMENTAL_OFFER_TYPES;
1105+
1106+
let offer_records = TlvStream::new(bytes).range(OFFER_TYPES);
1107+
let invreq_records = TlvStream::new(bytes).range(INVOICE_REQUEST_TYPES).filter(|record| {
10921108
match record.r#type {
10931109
PAYER_METADATA_TYPE => false, // Should be outside range
10941110
INVOICE_REQUEST_PAYER_ID_TYPE => !metadata.derives_payer_keys(),
10951111
_ => true,
10961112
}
10971113
});
1098-
let tlv_stream = offer_records.chain(invreq_records);
1114+
let experimental_records = TlvStream::new(bytes).range(EXPERIMENTAL_TYPES);
1115+
let tlv_stream = offer_records.chain(invreq_records).chain(experimental_records);
10991116

11001117
let signing_pubkey = self.payer_signing_pubkey();
11011118
signer::verify_payer_metadata(
@@ -1104,13 +1121,13 @@ impl InvoiceContents {
11041121
}
11051122

11061123
fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
1107-
let (payer, offer, invoice_request) = match self {
1124+
let (payer, offer, invoice_request, experimental_offer) = match self {
11081125
InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
11091126
InvoiceContents::ForRefund { refund, .. } => refund.as_tlv_stream(),
11101127
};
11111128
let invoice = self.fields().as_tlv_stream();
11121129

1113-
(payer, offer, invoice_request, invoice)
1130+
(payer, offer, invoice_request, invoice, experimental_offer)
11141131
}
11151132
}
11161133

@@ -1213,9 +1230,13 @@ impl TryFrom<Vec<u8>> for UnsignedBolt12Invoice {
12131230
let ParsedMessage { mut bytes, tlv_stream } = invoice;
12141231
let (
12151232
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1233+
experimental_offer_tlv_stream,
12161234
) = tlv_stream;
12171235
let contents = InvoiceContents::try_from(
1218-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
1236+
(
1237+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1238+
experimental_offer_tlv_stream,
1239+
)
12191240
)?;
12201241

12211242
let tagged_hash = TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &bytes);
@@ -1314,15 +1335,18 @@ pub(super) struct FallbackAddress {
13141335

13151336
impl_writeable!(FallbackAddress, { version, program });
13161337

1317-
type FullInvoiceTlvStream =
1318-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream);
1338+
type FullInvoiceTlvStream =(
1339+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream, SignatureTlvStream,
1340+
ExperimentalOfferTlvStream,
1341+
);
13191342

13201343
type FullInvoiceTlvStreamRef<'a> = (
13211344
PayerTlvStreamRef<'a>,
13221345
OfferTlvStreamRef<'a>,
13231346
InvoiceRequestTlvStreamRef<'a>,
13241347
InvoiceTlvStreamRef<'a>,
13251348
SignatureTlvStreamRef<'a>,
1349+
ExperimentalOfferTlvStreamRef,
13261350
);
13271351

13281352
impl SeekReadable for FullInvoiceTlvStream {
@@ -1332,19 +1356,23 @@ impl SeekReadable for FullInvoiceTlvStream {
13321356
let invoice_request = SeekReadable::read(r)?;
13331357
let invoice = SeekReadable::read(r)?;
13341358
let signature = SeekReadable::read(r)?;
1359+
let experimental_offer = SeekReadable::read(r)?;
13351360

1336-
Ok((payer, offer, invoice_request, invoice, signature))
1361+
Ok((payer, offer, invoice_request, invoice, signature, experimental_offer))
13371362
}
13381363
}
13391364

1340-
type PartialInvoiceTlvStream =
1341-
(PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream);
1365+
type PartialInvoiceTlvStream = (
1366+
PayerTlvStream, OfferTlvStream, InvoiceRequestTlvStream, InvoiceTlvStream,
1367+
ExperimentalOfferTlvStream,
1368+
);
13421369

13431370
type PartialInvoiceTlvStreamRef<'a> = (
13441371
PayerTlvStreamRef<'a>,
13451372
OfferTlvStreamRef<'a>,
13461373
InvoiceRequestTlvStreamRef<'a>,
13471374
InvoiceTlvStreamRef<'a>,
1375+
ExperimentalOfferTlvStreamRef,
13481376
);
13491377

13501378
impl SeekReadable for PartialInvoiceTlvStream {
@@ -1353,8 +1381,9 @@ impl SeekReadable for PartialInvoiceTlvStream {
13531381
let offer = SeekReadable::read(r)?;
13541382
let invoice_request = SeekReadable::read(r)?;
13551383
let invoice = SeekReadable::read(r)?;
1384+
let experimental_offer = SeekReadable::read(r)?;
13561385

1357-
Ok((payer, offer, invoice_request, invoice))
1386+
Ok((payer, offer, invoice_request, invoice, experimental_offer))
13581387
}
13591388
}
13601389

@@ -1366,9 +1395,13 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
13661395
let (
13671396
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
13681397
SignatureTlvStream { signature },
1398+
experimental_offer_tlv_stream,
13691399
) = tlv_stream;
13701400
let contents = InvoiceContents::try_from(
1371-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream)
1401+
(
1402+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream,
1403+
experimental_offer_tlv_stream,
1404+
)
13721405
)?;
13731406

13741407
let signature = signature.ok_or(
@@ -1394,6 +1427,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
13941427
paths, blindedpay, created_at, relative_expiry, payment_hash, amount, fallbacks,
13951428
features, node_id, message_paths,
13961429
},
1430+
experimental_offer_tlv_stream,
13971431
) = tlv_stream;
13981432

13991433
if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1426,12 +1460,18 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
14261460

14271461
if offer_tlv_stream.issuer_id.is_none() && offer_tlv_stream.paths.is_none() {
14281462
let refund = RefundContents::try_from(
1429-
(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+
)
14301467
)?;
14311468
Ok(InvoiceContents::ForRefund { refund, fields })
14321469
} else {
14331470
let invoice_request = InvoiceRequestContents::try_from(
1434-
(payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream)
1471+
(
1472+
payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream,
1473+
experimental_offer_tlv_stream,
1474+
)
14351475
)?;
14361476
Ok(InvoiceContents::ForOffer { invoice_request, fields })
14371477
}
@@ -1500,7 +1540,7 @@ mod tests {
15001540
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
15011541
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
15021542
use crate::offers::nonce::Nonce;
1503-
use crate::offers::offer::{Amount, OfferTlvStreamRef, Quantity};
1543+
use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
15041544
use crate::prelude::*;
15051545
#[cfg(not(c_bindings))]
15061546
use {
@@ -1668,6 +1708,7 @@ mod tests {
16681708
message_paths: None,
16691709
},
16701710
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1711+
ExperimentalOfferTlvStreamRef {},
16711712
),
16721713
);
16731714

@@ -1761,6 +1802,7 @@ mod tests {
17611802
message_paths: None,
17621803
},
17631804
SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
1805+
ExperimentalOfferTlvStreamRef {},
17641806
),
17651807
);
17661808

@@ -1956,7 +1998,7 @@ mod tests {
19561998
.relative_expiry(one_hour.as_secs() as u32)
19571999
.build().unwrap()
19582000
.sign(recipient_sign).unwrap();
1959-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2001+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19602002
#[cfg(feature = "std")]
19612003
assert!(!invoice.is_expired());
19622004
assert_eq!(invoice.relative_expiry(), one_hour);
@@ -1972,7 +2014,7 @@ mod tests {
19722014
.relative_expiry(one_hour.as_secs() as u32 - 1)
19732015
.build().unwrap()
19742016
.sign(recipient_sign).unwrap();
1975-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2017+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19762018
#[cfg(feature = "std")]
19772019
assert!(invoice.is_expired());
19782020
assert_eq!(invoice.relative_expiry(), one_hour - Duration::from_secs(1));
@@ -1991,7 +2033,7 @@ mod tests {
19912033
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
19922034
.build().unwrap()
19932035
.sign(recipient_sign).unwrap();
1994-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2036+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
19952037
assert_eq!(invoice.amount_msats(), 1001);
19962038
assert_eq!(tlv_stream.amount, Some(1001));
19972039
}
@@ -2009,7 +2051,7 @@ mod tests {
20092051
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
20102052
.build().unwrap()
20112053
.sign(recipient_sign).unwrap();
2012-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2054+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20132055
assert_eq!(invoice.amount_msats(), 2000);
20142056
assert_eq!(tlv_stream.amount, Some(2000));
20152057

@@ -2047,7 +2089,7 @@ mod tests {
20472089
.fallback_v1_p2tr_tweaked(&tweaked_pubkey)
20482090
.build().unwrap()
20492091
.sign(recipient_sign).unwrap();
2050-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2092+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20512093
assert_eq!(
20522094
invoice.fallbacks(),
20532095
vec![
@@ -2090,7 +2132,7 @@ mod tests {
20902132
.allow_mpp()
20912133
.build().unwrap()
20922134
.sign(recipient_sign).unwrap();
2093-
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
2135+
let (_, _, _, tlv_stream, _, _) = invoice.as_tlv_stream();
20942136
assert_eq!(invoice.invoice_features(), &features);
20952137
assert_eq!(tlv_stream.features, Some(&features));
20962138
}

0 commit comments

Comments
 (0)