]> git.bitcoin.ninja Git - rust-lightning/commitdiff
Add parsing tests for experimental invoice TLVs
authorJeffrey Czyz <jkczyz@gmail.com>
Tue, 13 Aug 2024 22:32:28 +0000 (17:32 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Tue, 5 Nov 2024 00:00:24 +0000 (18:00 -0600)
lightning/src/offers/invoice.rs
lightning/src/offers/invoice_macros.rs
lightning/src/offers/invoice_request.rs
lightning/src/offers/refund.rs
lightning/src/offers/static_invoice.rs

index 08c4c6a78b730a06aff3cb9cd9596337212da903..ab5ff78984c874dba60deed58229e6bbe784d6e5 100644 (file)
@@ -363,6 +363,8 @@ macro_rules! invoice_builder_methods { (
                InvoiceFields {
                        payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
                        fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
+                       #[cfg(test)]
+                       experimental_baz: None,
                }
        }
 
@@ -666,6 +668,8 @@ struct InvoiceFields {
        fallbacks: Option<Vec<FallbackAddress>>,
        features: Bolt12InvoiceFeatures,
        signing_pubkey: PublicKey,
+       #[cfg(test)]
+       experimental_baz: Option<u64>,
 }
 
 macro_rules! invoice_accessors { ($self: ident, $contents: expr) => {
@@ -1256,7 +1260,10 @@ impl InvoiceFields {
                                node_id: Some(&self.signing_pubkey),
                                message_paths: None,
                        },
-                       ExperimentalInvoiceTlvStreamRef {},
+                       ExperimentalInvoiceTlvStreamRef {
+                               #[cfg(test)]
+                               experimental_baz: self.experimental_baz,
+                       },
                )
        }
 }
@@ -1333,12 +1340,20 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef<'a>, INVOICE_TYPES, {
 });
 
 /// Valid type range for experimental invoice TLV records.
-const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
+pub(super) const EXPERIMENTAL_INVOICE_TYPES: core::ops::RangeFrom<u64> = 3_000_000_000..;
 
+#[cfg(not(test))]
 tlv_stream!(
        ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {}
 );
 
+#[cfg(test)]
+tlv_stream!(
+       ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES, {
+               (3_999_999_999, experimental_baz: (u64, HighZeroBytesDroppedBigSize)),
+       }
+);
+
 pub(super) type BlindedPathIter<'a> = core::iter::Map<
        core::slice::Iter<'a, BlindedPaymentPath>,
        for<'r> fn(&'r BlindedPaymentPath) -> &'r BlindedPath,
@@ -1473,7 +1488,10 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                        },
                        experimental_offer_tlv_stream,
                        experimental_invoice_request_tlv_stream,
-                       ExperimentalInvoiceTlvStream {},
+                       ExperimentalInvoiceTlvStream {
+                               #[cfg(test)]
+                               experimental_baz,
+                       },
                ) = tlv_stream;
 
                if message_paths.is_some() { return Err(Bolt12SemanticError::UnexpectedPaths) }
@@ -1500,6 +1518,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                let fields = InvoiceFields {
                        payment_paths, created_at, relative_expiry, payment_hash, amount_msats, fallbacks,
                        features, signing_pubkey,
+                       #[cfg(test)]
+                       experimental_baz,
                };
 
                check_invoice_signing_pubkey(&fields.signing_pubkey, &offer_tlv_stream)?;
@@ -1570,7 +1590,7 @@ pub(super) fn check_invoice_signing_pubkey(
 
 #[cfg(test)]
 mod tests {
-       use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
+       use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, EXPERIMENTAL_INVOICE_TYPES, ExperimentalInvoiceTlvStreamRef, FallbackAddress, FullInvoiceTlvStreamRef, INVOICE_TYPES, InvoiceTlvStreamRef, SIGNATURE_TAG, UnsignedBolt12Invoice};
 
        use bitcoin::{CompressedPublicKey, WitnessProgram, WitnessVersion};
        use bitcoin::constants::ChainHash;
@@ -1590,7 +1610,7 @@ mod tests {
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::DecodeError;
        use crate::offers::invoice_request::{ExperimentalInvoiceRequestTlvStreamRef, InvoiceRequestTlvStreamRef};
-       use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, self};
+       use crate::offers::merkle::{SignError, SignatureTlvStreamRef, TaggedHash, TlvStream, self};
        use crate::offers::nonce::Nonce;
        use crate::offers::offer::{Amount, ExperimentalOfferTlvStreamRef, OfferTlvStreamRef, Quantity};
        use crate::prelude::*;
@@ -1766,7 +1786,9 @@ mod tests {
                                ExperimentalInvoiceRequestTlvStreamRef {
                                        experimental_bar: None,
                                },
-                               ExperimentalInvoiceTlvStreamRef {},
+                               ExperimentalInvoiceTlvStreamRef {
+                                       experimental_baz: None,
+                               },
                        ),
                );
 
@@ -1866,7 +1888,9 @@ mod tests {
                                ExperimentalInvoiceRequestTlvStreamRef {
                                        experimental_bar: None,
                                },
-                               ExperimentalInvoiceTlvStreamRef {},
+                               ExperimentalInvoiceTlvStreamRef {
+                                       experimental_baz: None,
+                               },
                        ),
                );
 
@@ -2724,6 +2748,135 @@ mod tests {
                }
        }
 
+       #[test]
+       fn parses_invoice_with_experimental_tlv_records() {
+               let secp_ctx = Secp256k1::new();
+               let keys = Keypair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
+               let 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()
+                       .experimental_baz(42)
+                       .build().unwrap()
+                       .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();
+
+               assert!(Bolt12Invoice::try_from(encoded_invoice).is_ok());
+
+               const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 1;
+               assert!(UNKNOWN_ODD_TYPE % 2 == 1);
+
+               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();
+
+               let mut unknown_bytes = Vec::new();
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
+               BigSize(32).write(&mut unknown_bytes).unwrap();
+               [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+               unsigned_invoice.bytes.reserve_exact(
+                       unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+               );
+               unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+               let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
+                       .chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
+               unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
+
+               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 = EXPERIMENTAL_INVOICE_TYPES.start;
+               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();
+
+               let mut unknown_bytes = Vec::new();
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
+               BigSize(32).write(&mut unknown_bytes).unwrap();
+               [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+               unsigned_invoice.bytes.reserve_exact(
+                       unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+               );
+               unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+               let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
+                       .chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
+               unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
+
+               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)),
+               }
+
+               let 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()
+                       .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();
+
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
+               BigSize(32).write(&mut encoded_invoice).unwrap();
+               [42u8; 32].write(&mut encoded_invoice).unwrap();
+
+               match Bolt12Invoice::try_from(encoded_invoice) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)),
+               }
+       }
+
        #[test]
        fn fails_parsing_invoice_with_out_of_range_tlv_records() {
                let invoice = OfferBuilder::new(recipient_pubkey())
index 579ecd2d20a02b17394a18b15afaf40870c70fa4..4a540c1604668204d99cc399260fdc464e0e3b17 100644 (file)
@@ -95,6 +95,11 @@ macro_rules! invoice_builder_methods_test { (
                $return_value
        }
 
+       #[cfg_attr(c_bindings, allow(dead_code))]
+       pub(super) fn experimental_baz($($self_mut)* $self: $self_type, experimental_baz: u64) -> $return_type {
+               $invoice_fields.experimental_baz = Some(experimental_baz);
+               $return_value
+       }
 } }
 
 macro_rules! invoice_accessors_common { ($self: ident, $contents: expr, $invoice_type: ty) => {
index 55fe9af1102aa813ab5faaf6200c33491fabe7fe..3c369feacebf5456bcedb31b8e94e7ed890ffcc2 100644 (file)
@@ -1551,6 +1551,7 @@ mod tests {
 
                let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
                        .unwrap()
+                       .experimental_baz(42)
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
                match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@@ -1643,6 +1644,7 @@ mod tests {
 
                let invoice = invoice_request.respond_with_no_std(payment_paths(), payment_hash(), now())
                        .unwrap()
+                       .experimental_baz(42)
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
                assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
index fd0f0916f762745ecf8384b068165c58503efd35..e538ab77f45ad70aed1455ae35c9ba5a89f0fb0b 100644 (file)
@@ -1110,6 +1110,7 @@ mod tests {
                let invoice = refund
                        .respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
                        .unwrap()
+                       .experimental_baz(42)
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
                match invoice.verify_using_metadata(&expanded_key, &secp_ctx) {
@@ -1178,6 +1179,7 @@ mod tests {
                let invoice = refund
                        .respond_with_no_std(payment_paths(), payment_hash(), recipient_pubkey(), now())
                        .unwrap()
+                       .experimental_baz(42)
                        .build().unwrap()
                        .sign(recipient_sign).unwrap();
                assert!(invoice.verify_using_metadata(&expanded_key, &secp_ctx).is_err());
index 56ef40aab73ef5125e3a7390d5578866872aa0cc..b3618ae5d989045213339ff96de8a63c665ffcd9 100644 (file)
@@ -20,6 +20,8 @@ use crate::offers::invoice::{
        ExperimentalInvoiceTlvStream, ExperimentalInvoiceTlvStreamRef, FallbackAddress,
        InvoiceTlvStream, InvoiceTlvStreamRef,
 };
+#[cfg(test)]
+use crate::offers::invoice_macros::invoice_builder_methods_test;
 use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
 use crate::offers::invoice_request::InvoiceRequest;
 use crate::offers::merkle::{
@@ -82,6 +84,8 @@ struct InvoiceContents {
        features: Bolt12InvoiceFeatures,
        signing_pubkey: PublicKey,
        message_paths: Vec<BlindedMessagePath>,
+       #[cfg(test)]
+       experimental_baz: Option<u64>,
 }
 
 /// Builds a [`StaticInvoice`] from an [`Offer`].
@@ -168,6 +172,9 @@ impl<'a> StaticInvoiceBuilder<'a> {
        }
 
        invoice_builder_methods_common!(self, Self, self.invoice, Self, self, StaticInvoice, mut);
+
+       #[cfg(test)]
+       invoice_builder_methods_test!(self, Self, self.invoice, Self, self, mut);
 }
 
 /// A semantically valid [`StaticInvoice`] that hasn't been signed.
@@ -426,6 +433,8 @@ impl InvoiceContents {
                        fallbacks: None,
                        features: Bolt12InvoiceFeatures::empty(),
                        signing_pubkey,
+                       #[cfg(test)]
+                       experimental_baz: None,
                }
        }
 
@@ -451,7 +460,10 @@ impl InvoiceContents {
                        payment_hash: None,
                };
 
-               let experimental_invoice = ExperimentalInvoiceTlvStreamRef {};
+               let experimental_invoice = ExperimentalInvoiceTlvStreamRef {
+                       #[cfg(test)]
+                       experimental_baz: self.experimental_baz,
+               };
 
                let (offer, experimental_offer) = self.offer.as_tlv_stream();
 
@@ -636,7 +648,10 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                                amount,
                        },
                        experimental_offer_tlv_stream,
-                       ExperimentalInvoiceTlvStream {},
+                       ExperimentalInvoiceTlvStream {
+                               #[cfg(test)]
+                               experimental_baz,
+                       },
                ) = tlv_stream;
 
                if payment_hash.is_some() {
@@ -677,6 +692,8 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                        fallbacks,
                        features,
                        signing_pubkey,
+                       #[cfg(test)]
+                       experimental_baz,
                })
        }
 }
@@ -689,10 +706,11 @@ mod tests {
        use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::DecodeError;
        use crate::offers::invoice::{
-               ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, INVOICE_TYPES,
+               ExperimentalInvoiceTlvStreamRef, InvoiceTlvStreamRef, EXPERIMENTAL_INVOICE_TYPES,
+               INVOICE_TYPES,
        };
        use crate::offers::merkle;
-       use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash};
+       use crate::offers::merkle::{SignatureTlvStreamRef, TaggedHash, TlvStream};
        use crate::offers::nonce::Nonce;
        use crate::offers::offer::{
                ExperimentalOfferTlvStreamRef, Offer, OfferBuilder, OfferTlvStreamRef, Quantity,
@@ -881,7 +899,7 @@ mod tests {
                                },
                                SignatureTlvStreamRef { signature: Some(&invoice.signature()) },
                                ExperimentalOfferTlvStreamRef { experimental_foo: None },
-                               ExperimentalInvoiceTlvStreamRef {},
+                               ExperimentalInvoiceTlvStreamRef { experimental_baz: None },
                        )
                );
 
@@ -1439,6 +1457,162 @@ mod tests {
                }
        }
 
+       #[test]
+       fn parses_invoice_with_experimental_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();
+
+               let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
+                       &offer,
+                       payment_paths.clone(),
+                       vec![blinded_path()],
+                       now,
+                       &expanded_key,
+                       nonce,
+                       &secp_ctx,
+               )
+               .unwrap()
+               .experimental_baz(42)
+               .build_and_sign(&secp_ctx)
+               .unwrap();
+
+               let mut encoded_invoice = Vec::new();
+               invoice.write(&mut encoded_invoice).unwrap();
+
+               assert!(StaticInvoice::try_from(encoded_invoice).is_ok());
+
+               const UNKNOWN_ODD_TYPE: u64 = EXPERIMENTAL_INVOICE_TYPES.start + 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();
+
+               let mut unknown_bytes = Vec::new();
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut unknown_bytes).unwrap();
+               BigSize(32).write(&mut unknown_bytes).unwrap();
+               [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+               unsigned_invoice.bytes.reserve_exact(
+                       unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+               );
+               unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+               let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
+                       .chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
+               unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
+
+               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 = EXPERIMENTAL_INVOICE_TYPES.start;
+               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();
+
+               let mut unknown_bytes = Vec::new();
+               BigSize(UNKNOWN_EVEN_TYPE).write(&mut unknown_bytes).unwrap();
+               BigSize(32).write(&mut unknown_bytes).unwrap();
+               [42u8; 32].write(&mut unknown_bytes).unwrap();
+
+               unsigned_invoice.bytes.reserve_exact(
+                       unsigned_invoice.bytes.capacity() - unsigned_invoice.bytes.len() + unknown_bytes.len(),
+               );
+               unsigned_invoice.experimental_bytes.extend_from_slice(&unknown_bytes);
+
+               let tlv_stream = TlvStream::new(&unsigned_invoice.bytes)
+                       .chain(TlvStream::new(&unsigned_invoice.experimental_bytes));
+               unsigned_invoice.tagged_hash = TaggedHash::from_tlv_stream(SIGNATURE_TAG, tlv_stream);
+
+               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)),
+               }
+
+               let offer = OfferBuilder::deriving_signing_pubkey(node_id, &expanded_key, nonce, &secp_ctx)
+                       .path(blinded_path())
+                       .build()
+                       .unwrap();
+
+               let invoice = StaticInvoiceBuilder::for_offer_using_derived_keys(
+                       &offer,
+                       payment_paths.clone(),
+                       vec![blinded_path()],
+                       now,
+                       &expanded_key,
+                       nonce,
+                       &secp_ctx,
+               )
+               .unwrap()
+               .build_and_sign(&secp_ctx)
+               .unwrap();
+
+               let mut encoded_invoice = Vec::new();
+               invoice.write(&mut encoded_invoice).unwrap();
+
+               BigSize(UNKNOWN_ODD_TYPE).write(&mut encoded_invoice).unwrap();
+               BigSize(32).write(&mut encoded_invoice).unwrap();
+               [42u8; 32].write(&mut encoded_invoice).unwrap();
+
+               match StaticInvoice::try_from(encoded_invoice) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(
+                               e,
+                               Bolt12ParseError::InvalidSignature(secp256k1::Error::IncorrectSignature)
+                       ),
+               }
+       }
+
        #[test]
        fn fails_parsing_invoice_with_out_of_range_tlv_records() {
                let invoice = invoice();