Skip to content

Commit bbdd873

Browse files
committed
Add parsing tests for unknown BOLT12 TLVs
1 parent 605952c commit bbdd873

File tree

5 files changed

+327
-19
lines changed

5 files changed

+327
-19
lines changed

lightning/src/offers/invoice.rs

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,10 @@ impl TryFrom<Vec<u8>> for Bolt12Invoice {
12341234
}
12351235
}
12361236

1237-
tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
1237+
/// Valid type range for invoice TLV records.
1238+
pub(super) const INVOICE_TYPES: core::ops::Range<u64> = 160..240;
1239+
1240+
tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, INVOICE_TYPES, {
12381241
(160, paths: (Vec<BlindedPath>, WithoutLength, Iterable<'a, BlindedPathIter<'a>, BlindedPath>)),
12391242
(162, blindedpay: (Vec<BlindedPayInfo>, WithoutLength, Iterable<'a, BlindedPayInfoIter<'a>, BlindedPayInfo>)),
12401243
(164, created_at: (u64, HighZeroBytesDroppedBigSize)),
@@ -1245,7 +1248,7 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
12451248
(174, features: (Bolt12InvoiceFeatures, WithoutLength)),
12461249
(176, node_id: PublicKey),
12471250
// Only present in `StaticInvoice`s.
1248-
(238, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
1251+
(236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
12491252
});
12501253

12511254
pub(super) type BlindedPathIter<'a> = core::iter::Map<
@@ -1437,7 +1440,7 @@ pub(super) fn check_invoice_signing_pubkey(
14371440

14381441
#[cfg(test)]
14391442
mod tests {
1440-
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
1443+
use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
14411444

14421445
use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
14431446
use bitcoin::constants::ChainHash;
@@ -2494,7 +2497,78 @@ mod tests {
24942497
}
24952498

24962499
#[test]
2497-
fn fails_parsing_invoice_with_extra_tlv_records() {
2500+
fn parses_invoice_with_unknown_tlv_records() {
2501+
const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
2502+
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
2503+
2504+
let secp_ctx = Secp256k1::new();
2505+
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
2506+
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
2507+
.amount_msats(1000)
2508+
.build().unwrap()
2509+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2510+
.build().unwrap()
2511+
.sign(payer_sign).unwrap()
2512+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2513+
.build().unwrap();
2514+
2515+
BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
2516+
BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
2517+
[42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
2518+
2519+
unsigned_invoice.tagged_hash =
2520+
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
2521+
2522+
let invoice = unsigned_invoice
2523+
.sign(|message: &UnsignedBolt12Invoice|
2524+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2525+
)
2526+
.unwrap();
2527+
2528+
let mut encoded_invoice = Vec::new();
2529+
invoice.write(&mut encoded_invoice).unwrap();
2530+
2531+
match Bolt12Invoice::try_from(encoded_invoice.clone()) {
2532+
Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
2533+
Err(e) => panic!("error parsing invoice: {:?}", e),
2534+
}
2535+
2536+
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_TYPES.end - 2;
2537+
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
2538+
2539+
let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
2540+
.amount_msats(1000)
2541+
.build().unwrap()
2542+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
2543+
.build().unwrap()
2544+
.sign(payer_sign).unwrap()
2545+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
2546+
.build().unwrap();
2547+
2548+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
2549+
BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
2550+
[42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
2551+
2552+
unsigned_invoice.tagged_hash =
2553+
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
2554+
2555+
let invoice = unsigned_invoice
2556+
.sign(|message: &UnsignedBolt12Invoice|
2557+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2558+
)
2559+
.unwrap();
2560+
2561+
let mut encoded_invoice = Vec::new();
2562+
invoice.write(&mut encoded_invoice).unwrap();
2563+
2564+
match Bolt12Invoice::try_from(encoded_invoice) {
2565+
Ok(_) => panic!("expected error"),
2566+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2567+
}
2568+
}
2569+
2570+
#[test]
2571+
fn fails_parsing_invoice_with_out_of_range_tlv_records() {
24982572
let invoice = OfferBuilder::new(recipient_pubkey())
24992573
.amount_msats(1000)
25002574
.build().unwrap()

lightning/src/offers/invoice_request.rs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ impl Readable for InvoiceRequestFields {
12421242

12431243
#[cfg(test)]
12441244
mod tests {
1245-
use super::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
1245+
use super::{INVOICE_REQUEST_TYPES, InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
12461246

12471247
use bitcoin::constants::ChainHash;
12481248
use bitcoin::network::Network;
@@ -2294,7 +2294,72 @@ mod tests {
22942294
}
22952295

22962296
#[test]
2297-
fn fails_parsing_invoice_request_with_extra_tlv_records() {
2297+
fn parses_invoice_request_with_unknown_tlv_records() {
2298+
const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
2299+
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
2300+
2301+
let secp_ctx = Secp256k1::new();
2302+
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
2303+
let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
2304+
.amount_msats(1000)
2305+
.build().unwrap()
2306+
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
2307+
.build().unwrap();
2308+
2309+
BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
2310+
BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
2311+
[42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
2312+
2313+
unsigned_invoice_request.tagged_hash =
2314+
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
2315+
2316+
let invoice_request = unsigned_invoice_request
2317+
.sign(|message: &UnsignedInvoiceRequest|
2318+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2319+
)
2320+
.unwrap();
2321+
2322+
let mut encoded_invoice_request = Vec::new();
2323+
invoice_request.write(&mut encoded_invoice_request).unwrap();
2324+
2325+
match InvoiceRequest::try_from(encoded_invoice_request.clone()) {
2326+
Ok(invoice_request) => assert_eq!(invoice_request.bytes, encoded_invoice_request),
2327+
Err(e) => panic!("error parsing invoice_request: {:?}", e),
2328+
}
2329+
2330+
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
2331+
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
2332+
2333+
let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
2334+
.amount_msats(1000)
2335+
.build().unwrap()
2336+
.request_invoice(vec![1; 32], keys.public_key()).unwrap()
2337+
.build().unwrap();
2338+
2339+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
2340+
BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
2341+
[42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
2342+
2343+
unsigned_invoice_request.tagged_hash =
2344+
TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
2345+
2346+
let invoice_request = unsigned_invoice_request
2347+
.sign(|message: &UnsignedInvoiceRequest|
2348+
Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
2349+
)
2350+
.unwrap();
2351+
2352+
let mut encoded_invoice_request = Vec::new();
2353+
invoice_request.write(&mut encoded_invoice_request).unwrap();
2354+
2355+
match InvoiceRequest::try_from(encoded_invoice_request) {
2356+
Ok(_) => panic!("expected error"),
2357+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
2358+
}
2359+
}
2360+
2361+
#[test]
2362+
fn fails_parsing_invoice_request_with_out_of_range_tlv_records() {
22982363
let secp_ctx = Secp256k1::new();
22992364
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
23002365
let invoice_request = OfferBuilder::new(keys.public_key())

lightning/src/offers/offer.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ impl core::fmt::Display for Offer {
11731173

11741174
#[cfg(test)]
11751175
mod tests {
1176-
use super::{Amount, Offer, OfferTlvStreamRef, Quantity};
1176+
use super::{Amount, OFFER_TYPES, Offer, OfferTlvStreamRef, Quantity};
11771177
#[cfg(not(c_bindings))]
11781178
use {
11791179
super::OfferBuilder,
@@ -1860,12 +1860,47 @@ mod tests {
18601860
}
18611861

18621862
#[test]
1863-
fn fails_parsing_offer_with_extra_tlv_records() {
1863+
fn parses_offer_with_unknown_tlv_records() {
1864+
const UNKNOWN_ODD_TYPE: u64 = OFFER_TYPES.end - 1;
1865+
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
1866+
1867+
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
1868+
1869+
let mut encoded_offer = Vec::new();
1870+
offer.write(&mut encoded_offer).unwrap();
1871+
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_offer).unwrap();
1872+
BigSize(32).write(&mut encoded_offer).unwrap();
1873+
[42u8; 32].write(&mut encoded_offer).unwrap();
1874+
1875+
match Offer::try_from(encoded_offer.clone()) {
1876+
Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
1877+
Err(e) => panic!("error parsing offer: {:?}", e),
1878+
}
1879+
1880+
const UNKNOWN_EVEN_TYPE: u64 = OFFER_TYPES.end - 2;
1881+
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
1882+
1883+
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
1884+
1885+
let mut encoded_offer = Vec::new();
1886+
offer.write(&mut encoded_offer).unwrap();
1887+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_offer).unwrap();
1888+
BigSize(32).write(&mut encoded_offer).unwrap();
1889+
[42u8; 32].write(&mut encoded_offer).unwrap();
1890+
1891+
match Offer::try_from(encoded_offer) {
1892+
Ok(_) => panic!("expected error"),
1893+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
1894+
}
1895+
}
1896+
1897+
#[test]
1898+
fn fails_parsing_offer_with_out_of_range_tlv_records() {
18641899
let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
18651900

18661901
let mut encoded_offer = Vec::new();
18671902
offer.write(&mut encoded_offer).unwrap();
1868-
BigSize(80).write(&mut encoded_offer).unwrap();
1903+
BigSize(OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
18691904
BigSize(32).write(&mut encoded_offer).unwrap();
18701905
[42u8; 32].write(&mut encoded_offer).unwrap();
18711906

lightning/src/offers/refund.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ mod tests {
944944
use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
945945
use crate::ln::inbound_payment::ExpandedKey;
946946
use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
947-
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
947+
use crate::offers::invoice_request::{INVOICE_REQUEST_TYPES, InvoiceRequestTlvStreamRef};
948948
use crate::offers::nonce::Nonce;
949949
use crate::offers::offer::OfferTlvStreamRef;
950950
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
@@ -1522,7 +1522,44 @@ mod tests {
15221522
}
15231523

15241524
#[test]
1525-
fn fails_parsing_refund_with_extra_tlv_records() {
1525+
fn parses_refund_with_unknown_tlv_records() {
1526+
const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
1527+
assert!(UNKNOWN_ODD_TYPE % 2 == 1);
1528+
1529+
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
1530+
.build().unwrap();
1531+
1532+
let mut encoded_refund = Vec::new();
1533+
refund.write(&mut encoded_refund).unwrap();
1534+
BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_refund).unwrap();
1535+
BigSize(32).write(&mut encoded_refund).unwrap();
1536+
[42u8; 32].write(&mut encoded_refund).unwrap();
1537+
1538+
match Refund::try_from(encoded_refund.clone()) {
1539+
Ok(refund) => assert_eq!(refund.bytes, encoded_refund),
1540+
Err(e) => panic!("error parsing refund: {:?}", e),
1541+
}
1542+
1543+
const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
1544+
assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
1545+
1546+
let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
1547+
.build().unwrap();
1548+
1549+
let mut encoded_refund = Vec::new();
1550+
refund.write(&mut encoded_refund).unwrap();
1551+
BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_refund).unwrap();
1552+
BigSize(32).write(&mut encoded_refund).unwrap();
1553+
[42u8; 32].write(&mut encoded_refund).unwrap();
1554+
1555+
match Refund::try_from(encoded_refund) {
1556+
Ok(_) => panic!("expected error"),
1557+
Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
1558+
}
1559+
}
1560+
1561+
#[test]
1562+
fn fails_parsing_refund_with_out_of_range_tlv_records() {
15261563
let secp_ctx = Secp256k1::new();
15271564
let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
15281565
let refund = RefundBuilder::new(vec![1; 32], keys.public_key(), 1000).unwrap()

0 commit comments

Comments
 (0)