]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Add parsing tests for unknown BOLT12 TLVs
authorJeffrey Czyz <jkczyz@gmail.com>
Fri, 9 Aug 2024 00:11:27 +0000 (19:11 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 29 Oct 2024 23:31:43 +0000 (18:31 -0500)
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/offer.rs
lightning/src/offers/refund.rs
lightning/src/offers/static_invoice.rs

index 648c0fba65149dcaa7c3a1d03a88da7aeb223d05..6cb37ab5339a8901c52eb81d7346022f546b3c2a 100644 (file)
@@ -1234,7 +1234,10 @@ impl TryFrom<Vec<u8>> for Bolt12Invoice {
        }
 }
 
-tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
+/// Valid type range for invoice TLV records.
+pub(super) const INVOICE_TYPES: core::ops::Range<u64> = 160..240;
+
+tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, INVOICE_TYPES, {
        (160, paths: (Vec<BlindedPath>, WithoutLength, Iterable<'a, BlindedPathIter<'a>, BlindedPath>)),
        (162, blindedpay: (Vec<BlindedPayInfo>, WithoutLength, Iterable<'a, BlindedPayInfoIter<'a>, BlindedPayInfo>)),
        (164, created_at: (u64, HighZeroBytesDroppedBigSize)),
@@ -1245,7 +1248,7 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
        (174, features: (Bolt12InvoiceFeatures, WithoutLength)),
        (176, node_id: PublicKey),
        // Only present in `StaticInvoice`s.
-       (238, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
+       (236, message_paths: (Vec<BlindedMessagePath>, WithoutLength)),
 });
 
 pub(super) type BlindedPathIter<'a> = core::iter::Map<
@@ -1437,7 +1440,7 @@ pub(super) fn check_invoice_signing_pubkey(
 
 #[cfg(test)]
 mod tests {
-       use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
+       use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
 
        use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
        use bitcoin::constants::ChainHash;
@@ -2494,7 +2497,78 @@ mod tests {
        }
 
        #[test]
-       fn fails_parsing_invoice_with_extra_tlv_records() {
+       fn parses_invoice_with_unknown_tlv_records() {
+               const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
+               assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+               let secp_ctx = Secp256k1::new();
+               let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+               let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
+                       .amount_msats(1000)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap()
+                       .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+                       .build().unwrap();
+
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
+               BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+               [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+               unsigned_invoice.tagged_hash =
+                       TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+               let invoice = unsigned_invoice
+                       .sign(|message: &UnsignedBolt12Invoice|
+                               Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
+                       .unwrap();
+
+               let mut encoded_invoice = Vec::new();
+               invoice.write(&mut encoded_invoice).unwrap();
+
+               match Bolt12Invoice::try_from(encoded_invoice.clone()) {
+                       Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
+                       Err(e) => panic!("error parsing invoice: {:?}", e),
+               }
+
+               const UNKNOWN_EVEN_TYPE: u64 = INVOICE_TYPES.end - 2;
+               assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+               let mut unsigned_invoice = OfferBuilder::new(keys.public_key())
+                       .amount_msats(1000)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap()
+                       .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+                       .build().unwrap();
+
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
+               BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+               [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+               unsigned_invoice.tagged_hash =
+                       TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+               let invoice = unsigned_invoice
+                       .sign(|message: &UnsignedBolt12Invoice|
+                               Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
+                       .unwrap();
+
+               let mut encoded_invoice = Vec::new();
+               invoice.write(&mut encoded_invoice).unwrap();
+
+               match Bolt12Invoice::try_from(encoded_invoice) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_invoice_with_out_of_range_tlv_records() {
                let invoice = OfferBuilder::new(recipient_pubkey())
                        .amount_msats(1000)
                        .build().unwrap()
index c6c9da82a4eb19d5974e5929cdef9721bbee33f9..e33ccb61606dd61489c98035058ff6ab7ee7abbc 100644 (file)
@@ -1242,7 +1242,7 @@ impl Readable for InvoiceRequestFields {
 
 #[cfg(test)]
 mod tests {
-       use super::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
+       use super::{INVOICE_REQUEST_TYPES, InvoiceRequest, InvoiceRequestFields, InvoiceRequestTlvStreamRef, PAYER_NOTE_LIMIT, SIGNATURE_TAG, UnsignedInvoiceRequest};
 
        use bitcoin::constants::ChainHash;
        use bitcoin::network::Network;
@@ -2294,7 +2294,72 @@ mod tests {
        }
 
        #[test]
-       fn fails_parsing_invoice_request_with_extra_tlv_records() {
+       fn parses_invoice_request_with_unknown_tlv_records() {
+               const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
+               assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+               let secp_ctx = Secp256k1::new();
+               let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+               let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
+                       .amount_msats(1000)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], keys.public_key()).unwrap()
+                       .build().unwrap();
+
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
+               BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
+               [42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
+
+               unsigned_invoice_request.tagged_hash =
+                       TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
+
+               let invoice_request = unsigned_invoice_request
+                       .sign(|message: &UnsignedInvoiceRequest|
+                               Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
+                       .unwrap();
+
+               let mut encoded_invoice_request = Vec::new();
+               invoice_request.write(&mut encoded_invoice_request).unwrap();
+
+               match InvoiceRequest::try_from(encoded_invoice_request.clone()) {
+                       Ok(invoice_request) => assert_eq!(invoice_request.bytes, encoded_invoice_request),
+                       Err(e) => panic!("error parsing invoice_request: {:?}", e),
+               }
+
+               const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
+               assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+               let mut unsigned_invoice_request = OfferBuilder::new(keys.public_key())
+                       .amount_msats(1000)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], keys.public_key()).unwrap()
+                       .build().unwrap();
+
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice_request.bytes).unwrap();
+               BigSize(32).write(&mut unsigned_invoice_request.bytes).unwrap();
+               [42u8; 32].write(&mut unsigned_invoice_request.bytes).unwrap();
+
+               unsigned_invoice_request.tagged_hash =
+                       TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice_request.bytes);
+
+               let invoice_request = unsigned_invoice_request
+                       .sign(|message: &UnsignedInvoiceRequest|
+                               Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       )
+                       .unwrap();
+
+               let mut encoded_invoice_request = Vec::new();
+               invoice_request.write(&mut encoded_invoice_request).unwrap();
+
+               match InvoiceRequest::try_from(encoded_invoice_request) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_invoice_request_with_out_of_range_tlv_records() {
                let secp_ctx = Secp256k1::new();
                let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
                let invoice_request = OfferBuilder::new(keys.public_key())
index 8501b8d4651f503a4f16ab307e2d33c763652090..546d42b88487afde98ed5e40a6d447865fc58046 100644 (file)
@@ -1173,7 +1173,7 @@ impl core::fmt::Display for Offer {
 
 #[cfg(test)]
 mod tests {
-       use super::{Amount, Offer, OfferTlvStreamRef, Quantity};
+       use super::{Amount, OFFER_TYPES, Offer, OfferTlvStreamRef, Quantity};
        #[cfg(not(c_bindings))]
        use {
                super::OfferBuilder,
@@ -1860,12 +1860,47 @@ mod tests {
        }
 
        #[test]
-       fn fails_parsing_offer_with_extra_tlv_records() {
+       fn parses_offer_with_unknown_tlv_records() {
+               const UNKNOWN_ODD_TYPE: u64 = OFFER_TYPES.end - 1;
+               assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+               let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
+
+               let mut encoded_offer = Vec::new();
+               offer.write(&mut encoded_offer).unwrap();
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_offer).unwrap();
+               BigSize(32).write(&mut encoded_offer).unwrap();
+               [42u8; 32].write(&mut encoded_offer).unwrap();
+
+               match Offer::try_from(encoded_offer.clone()) {
+                       Ok(offer) => assert_eq!(offer.bytes, encoded_offer),
+                       Err(e) => panic!("error parsing offer: {:?}", e),
+               }
+
+               const UNKNOWN_EVEN_TYPE: u64 = OFFER_TYPES.end - 2;
+               assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+               let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
+
+               let mut encoded_offer = Vec::new();
+               offer.write(&mut encoded_offer).unwrap();
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_offer).unwrap();
+               BigSize(32).write(&mut encoded_offer).unwrap();
+               [42u8; 32].write(&mut encoded_offer).unwrap();
+
+               match Offer::try_from(encoded_offer) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_offer_with_out_of_range_tlv_records() {
                let offer = OfferBuilder::new(pubkey(42)).build().unwrap();
 
                let mut encoded_offer = Vec::new();
                offer.write(&mut encoded_offer).unwrap();
-               BigSize(80).write(&mut encoded_offer).unwrap();
+               BigSize(OFFER_TYPES.end).write(&mut encoded_offer).unwrap();
                BigSize(32).write(&mut encoded_offer).unwrap();
                [42u8; 32].write(&mut encoded_offer).unwrap();
 
index 482c3b6884c4c922370f8bea5424e6d0403bf70a..860de534ce03312fa0ff56f1302f761cefc39549 100644 (file)
@@ -944,7 +944,7 @@ mod tests {
        use crate::ln::features::{InvoiceRequestFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
-       use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
+       use crate::offers::invoice_request::{INVOICE_REQUEST_TYPES, InvoiceRequestTlvStreamRef};
        use crate::offers::nonce::Nonce;
        use crate::offers::offer::OfferTlvStreamRef;
        use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
@@ -1522,7 +1522,44 @@ mod tests {
        }
 
        #[test]
-       fn fails_parsing_refund_with_extra_tlv_records() {
+       fn parses_refund_with_unknown_tlv_records() {
+               const UNKNOWN_ODD_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 1;
+               assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+               let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+
+               let mut encoded_refund = Vec::new();
+               refund.write(&mut encoded_refund).unwrap();
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_refund).unwrap();
+               BigSize(32).write(&mut encoded_refund).unwrap();
+               [42u8; 32].write(&mut encoded_refund).unwrap();
+
+               match Refund::try_from(encoded_refund.clone()) {
+                       Ok(refund) => assert_eq!(refund.bytes, encoded_refund),
+                       Err(e) => panic!("error parsing refund: {:?}", e),
+               }
+
+               const UNKNOWN_EVEN_TYPE: u64 = INVOICE_REQUEST_TYPES.end - 2;
+               assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+               let refund = RefundBuilder::new(vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+
+               let mut encoded_refund = Vec::new();
+               refund.write(&mut encoded_refund).unwrap();
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut encoded_refund).unwrap();
+               BigSize(32).write(&mut encoded_refund).unwrap();
+               [42u8; 32].write(&mut encoded_refund).unwrap();
+
+               match Refund::try_from(encoded_refund) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_refund_with_out_of_range_tlv_records() {
                let secp_ctx = Secp256k1::new();
                let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
                let refund = RefundBuilder::new(vec![1; 32], keys.public_key(), 1000).unwrap()
index bf88bd9446cc6d3b45956363508e9f123b8dcc9c..dd3fb8b150b894a377f2fc56aaaab363533d15d5 100644 (file)
@@ -130,10 +130,9 @@ impl<'a> StaticInvoiceBuilder<'a> {
                Ok(Self { offer_bytes: &offer.bytes, invoice, keys })
        }
 
-       /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
-       pub fn build_and_sign<T: secp256k1::Signing>(
-               self, secp_ctx: &Secp256k1<T>,
-       ) -> Result<StaticInvoice, Bolt12SemanticError> {
+       /// Builds an [`UnsignedStaticInvoice`] after checking for valid semantics, returning it along with
+       /// the [`Keypair`] needed to sign it.
+       pub fn build(self) -> Result<(UnsignedStaticInvoice, Keypair), Bolt12SemanticError> {
                #[cfg(feature = "std")]
                {
                        if self.invoice.is_offer_expired() {
@@ -149,7 +148,14 @@ impl<'a> StaticInvoiceBuilder<'a> {
                }
 
                let Self { offer_bytes, invoice, keys } = self;
-               let unsigned_invoice = UnsignedStaticInvoice::new(&offer_bytes, invoice);
+               Ok((UnsignedStaticInvoice::new(&offer_bytes, invoice), keys))
+       }
+
+       /// Builds a signed [`StaticInvoice`] after checking for valid semantics.
+       pub fn build_and_sign<T: secp256k1::Signing>(
+               self, secp_ctx: &Secp256k1<T>,
+       ) -> Result<StaticInvoice, Bolt12SemanticError> {
+               let (unsigned_invoice, keys) = self.build()?;
                let invoice = unsigned_invoice
                        .sign(|message: &UnsignedStaticInvoice| {
                                Ok(secp_ctx.sign_schnorr_no_aux_rand(message.tagged_hash.as_digest(), &keys))
@@ -606,14 +612,15 @@ mod tests {
        use crate::ln::features::{Bolt12InvoiceFeatures, OfferFeatures};
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::DecodeError;
-       use crate::offers::invoice::InvoiceTlvStreamRef;
+       use crate::offers::invoice::{InvoiceTlvStreamRef, INVOICE_TYPES};
        use crate::offers::merkle;
        use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
        use crate::offers::nonce::Nonce;
        use crate::offers::offer::{Offer, OfferBuilder, OfferTlvStreamRef, Quantity};
        use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
        use crate::offers::static_invoice::{
-               StaticInvoice, StaticInvoiceBuilder, DEFAULT_RELATIVE_EXPIRY, SIGNATURE_TAG,
+               StaticInvoice, StaticInvoiceBuilder, UnsignedStaticInvoice, DEFAULT_RELATIVE_EXPIRY,
+               SIGNATURE_TAG,
        };
        use crate::offers::test_utils::*;
        use crate::sign::KeyMaterial;
@@ -1185,7 +1192,97 @@ mod tests {
        }
 
        #[test]
-       fn fails_parsing_invoice_with_extra_tlv_records() {
+       fn parses_invoice_with_unknown_tlv_records() {
+               let node_id = recipient_pubkey();
+               let payment_paths = payment_paths();
+               let now = now();
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let nonce = Nonce::from_entropy_source(&entropy);
+               let secp_ctx = Secp256k1::new();
+
+               let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
+                       .path(blinded_path())
+                       .build()
+                       .unwrap();
+
+               const UNKNOWN_ODD_TYPE: u64 = INVOICE_TYPES.end - 1;
+               assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+               let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
+                       &offer,
+                       payment_paths.clone(),
+                       vec![blinded_path()],
+                       now,
+                       &expanded_key,
+                       nonce,
+                       &secp_ctx,
+               )
+               .unwrap()
+               .build()
+               .unwrap();
+
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
+               BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+               [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+               unsigned_invoice.tagged_hash =
+                       TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+               let invoice = unsigned_invoice
+                       .sign(|message: &UnsignedStaticInvoice| {
+                               Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       })
+                       .unwrap();
+
+               let mut encoded_invoice = Vec::new();
+               invoice.write(&mut encoded_invoice).unwrap();
+
+               match StaticInvoice::try_from(encoded_invoice.clone()) {
+                       Ok(invoice) => assert_eq!(invoice.bytes, encoded_invoice),
+                       Err(e) => panic!("error parsing invoice: {:?}", e),
+               }
+
+               const UNKNOWN_EVEN_TYPE: u64 = INVOICE_TYPES.end - 2;
+               assert!(UNKNOWN_EVEN_TYPE % 2 == 0);
+
+               let (mut unsigned_invoice, keys) = StaticInvoiceBuilder::for_offer_using_derived_keys(
+                       &offer,
+                       payment_paths.clone(),
+                       vec![blinded_path()],
+                       now,
+                       &expanded_key,
+                       nonce,
+                       &secp_ctx,
+               )
+               .unwrap()
+               .build()
+               .unwrap();
+
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut unsigned_invoice.bytes).unwrap();
+               BigSize(32).write(&mut unsigned_invoice.bytes).unwrap();
+               [42u8; 32].write(&mut unsigned_invoice.bytes).unwrap();
+
+               unsigned_invoice.tagged_hash =
+                       TaggedHash::from_valid_tlv_stream_bytes(SIGNATURE_TAG, &unsigned_invoice.bytes);
+
+               let invoice = unsigned_invoice
+                       .sign(|message: &UnsignedStaticInvoice| {
+                               Ok(secp_ctx.sign_schnorr_no_aux_rand(message.as_ref().as_digest(), &keys))
+                       })
+                       .unwrap();
+
+               let mut encoded_invoice = Vec::new();
+               invoice.write(&mut encoded_invoice).unwrap();
+
+               match StaticInvoice::try_from(encoded_invoice) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::UnknownRequiredFeature)),
+               }
+       }
+
+       #[test]
+       fn fails_parsing_invoice_with_out_of_range_tlv_records() {
                let invoice = invoice();
                let mut encoded_invoice = Vec::new();
                invoice.write(&mut encoded_invoice).unwrap();