Document nonzero anchors in features module.
[rust-lightning] / lightning / src / offers / offer.rs
index a5935c87b8ab0482144a4df710a77343943d6ef8..9c8489778b08eaee1e444fe3f2584345c5224582 100644 (file)
@@ -27,7 +27,7 @@
 //! use lightning::offers::parse::ParseError;
 //! use lightning::util::ser::{Readable, Writeable};
 //!
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! # #[cfg(feature = "std")]
 //! # use std::time::SystemTime;
 //! #
 
 use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{PublicKey, Secp256k1, self};
+use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, self};
 use core::convert::TryFrom;
 use core::num::NonZeroU64;
 use core::ops::Deref;
 use core::str::FromStr;
 use core::time::Duration;
-use crate::chain::keysinterface::EntropySource;
+use crate::sign::EntropySource;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::features::OfferFeatures;
 use crate::ln::inbound_payment::{ExpandedKey, IV_LEN, Nonce};
 use crate::ln::msgs::MAX_VALUE_MSAT;
-use crate::offers::invoice_request::InvoiceRequestBuilder;
+use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
+use crate::offers::merkle::TlvStream;
 use crate::offers::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
-use crate::offers::signer::{Metadata, MetadataMaterial};
-use crate::onion_message::BlindedPath;
+use crate::offers::signer::{Metadata, MetadataMaterial, self};
 use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
 
@@ -91,12 +92,14 @@ use crate::prelude::*;
 #[cfg(feature = "std")]
 use std::time::SystemTime;
 
-const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
+pub(super) const IV_BYTES: &[u8; IV_LEN] = b"LDK Offer ~~~~~~";
 
 /// Builds an [`Offer`] for the "offer to be paid" flow.
 ///
 /// See [module-level documentation] for usage.
 ///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+///
 /// [module-level documentation]: self
 pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> {
        offer: OfferContents,
@@ -105,12 +108,18 @@ pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> {
 }
 
 /// Indicates how [`Offer::metadata`] may be set.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
 pub trait MetadataStrategy {}
 
 /// [`Offer::metadata`] may be explicitly set or left empty.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
 pub struct ExplicitMetadata {}
 
 /// [`Offer::metadata`] will be derived.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
 pub struct DerivedMetadata {}
 
 impl MetadataStrategy for ExplicitMetadata {}
@@ -149,10 +158,11 @@ impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
        /// recipient privacy by using a different signing pubkey for each offer. Otherwise, the
        /// provided `node_id` is used for the signing pubkey.
        ///
-       /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used to
-       /// verify that an [`InvoiceRequest`] was produced for the offer given an [`ExpandedKey`].
+       /// Also, sets the metadata when [`OfferBuilder::build`] is called such that it can be used by
+       /// [`InvoiceRequest::verify`] to determine if the request was produced for the offer given an
+       /// [`ExpandedKey`].
        ///
-       /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+       /// [`InvoiceRequest::verify`]: crate::offers::invoice_request::InvoiceRequest::verify
        /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
        pub fn deriving_signing_pubkey<ES: Deref>(
                description: String, node_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES,
@@ -382,7 +392,7 @@ impl Offer {
        /// A complete description of the purpose of the payment. Intended to be displayed to the user
        /// but with the caveat that it has not been verified in any way.
        pub fn description(&self) -> PrintableString {
-               PrintableString(&self.contents.description)
+               self.contents.description()
        }
 
        /// Features pertaining to the offer.
@@ -437,8 +447,57 @@ impl Offer {
                self.contents.signing_pubkey()
        }
 
-       /// Creates an [`InvoiceRequest`] for the offer with the given `metadata` and `payer_id`, which
-       /// will be reflected in the `Invoice` response.
+       /// Similar to [`Offer::request_invoice`] except it:
+       /// - derives the [`InvoiceRequest::payer_id`] such that a different key can be used for each
+       ///   request, and
+       /// - sets the [`InvoiceRequest::metadata`] when [`InvoiceRequestBuilder::build`] is called such
+       ///   that it can be used by [`Invoice::verify`] to determine if the invoice was requested using
+       ///   a base [`ExpandedKey`] from which the payer id was derived.
+       ///
+       /// Useful to protect the sender's privacy.
+       ///
+       /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+       ///
+       /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
+       /// [`InvoiceRequest::metadata`]: crate::offers::invoice_request::InvoiceRequest::metadata
+       /// [`Invoice::verify`]: crate::offers::invoice::Invoice::verify
+       /// [`ExpandedKey`]: crate::ln::inbound_payment::ExpandedKey
+       pub fn request_invoice_deriving_payer_id<'a, 'b, ES: Deref, T: secp256k1::Signing>(
+               &'a self, expanded_key: &ExpandedKey, entropy_source: ES, secp_ctx: &'b Secp256k1<T>
+       ) -> Result<InvoiceRequestBuilder<'a, 'b, DerivedPayerId, T>, SemanticError>
+       where
+               ES::Target: EntropySource,
+       {
+               if self.features().requires_unknown_bits() {
+                       return Err(SemanticError::UnknownRequiredFeatures);
+               }
+
+               Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
+       }
+
+       /// Similar to [`Offer::request_invoice_deriving_payer_id`] except uses `payer_id` for the
+       /// [`InvoiceRequest::payer_id`] instead of deriving a different key for each request.
+       ///
+       /// Useful for recurring payments using the same `payer_id` with different invoices.
+       ///
+       /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+       ///
+       /// [`InvoiceRequest::payer_id`]: crate::offers::invoice_request::InvoiceRequest::payer_id
+       pub fn request_invoice_deriving_metadata<ES: Deref>(
+               &self, payer_id: PublicKey, expanded_key: &ExpandedKey, entropy_source: ES
+       ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError>
+       where
+               ES::Target: EntropySource,
+       {
+               if self.features().requires_unknown_bits() {
+                       return Err(SemanticError::UnknownRequiredFeatures);
+               }
+
+               Ok(InvoiceRequestBuilder::deriving_metadata(self, payer_id, expanded_key, entropy_source))
+       }
+
+       /// Creates an [`InvoiceRequestBuilder`] for the offer with the given `metadata` and `payer_id`,
+       /// which will be reflected in the `Invoice` response.
        ///
        /// The `metadata` is useful for including information about the derivation of `payer_id` such
        /// that invoice response handling can be stateless. Also serves as payer-provided entropy while
@@ -449,10 +508,12 @@ impl Offer {
        ///
        /// Errors if the offer contains unknown required features.
        ///
+       /// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+       ///
        /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
        pub fn request_invoice(
                &self, metadata: Vec<u8>, payer_id: PublicKey
-       ) -> Result<InvoiceRequestBuilder, SemanticError> {
+       ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, SemanticError> {
                if self.features().requires_unknown_bits() {
                        return Err(SemanticError::UnknownRequiredFeatures);
                }
@@ -489,6 +550,10 @@ impl OfferContents {
                self.metadata.as_ref().and_then(|metadata| metadata.as_bytes())
        }
 
+       pub fn description(&self) -> PrintableString {
+               PrintableString(&self.description)
+       }
+
        #[cfg(feature = "std")]
        pub(super) fn is_expired(&self) -> bool {
                match self.absolute_expiry {
@@ -566,6 +631,27 @@ impl OfferContents {
                self.signing_pubkey
        }
 
+       /// Verifies that the offer metadata was produced from the offer in the TLV stream.
+       pub(super) fn verify<T: secp256k1::Signing>(
+               &self, bytes: &[u8], key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> Result<Option<KeyPair>, ()> {
+               match self.metadata() {
+                       Some(metadata) => {
+                               let tlv_stream = TlvStream::new(bytes).range(OFFER_TYPES).filter(|record| {
+                                       match record.r#type {
+                                               OFFER_METADATA_TYPE => false,
+                                               OFFER_NODE_ID_TYPE => !self.metadata.as_ref().unwrap().derives_keys(),
+                                               _ => true,
+                                       }
+                               });
+                               signer::verify_metadata(
+                                       metadata, key, IV_BYTES, self.signing_pubkey(), tlv_stream, secp_ctx
+                               )
+                       },
+                       None => Err(()),
+               }
+       }
+
        pub(super) fn as_tlv_stream(&self) -> OfferTlvStreamRef {
                let (currency, amount) = match &self.amount {
                        None => (None, None),
@@ -653,9 +739,18 @@ impl Quantity {
        }
 }
 
-tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
+/// Valid type range for offer TLV records.
+pub(super) const OFFER_TYPES: core::ops::Range<u64> = 1..80;
+
+/// TLV record type for [`Offer::metadata`].
+const OFFER_METADATA_TYPE: u64 = 4;
+
+/// TLV record type for [`Offer::signing_pubkey`].
+const OFFER_NODE_ID_TYPE: u64 = 22;
+
+tlv_stream!(OfferTlvStream, OfferTlvStreamRef, OFFER_TYPES, {
        (2, chains: (Vec<ChainHash>, WithoutLength)),
-       (4, metadata: (Vec<u8>, WithoutLength)),
+       (OFFER_METADATA_TYPE, metadata: (Vec<u8>, WithoutLength)),
        (6, currency: CurrencyCode),
        (8, amount: (u64, HighZeroBytesDroppedBigSize)),
        (10, description: (String, WithoutLength)),
@@ -664,7 +759,7 @@ tlv_stream!(OfferTlvStream, OfferTlvStreamRef, 1..80, {
        (16, paths: (Vec<BlindedPath>, WithoutLength)),
        (18, issuer: (String, WithoutLength)),
        (20, quantity_max: (u64, HighZeroBytesDroppedBigSize)),
-       (22, node_id: PublicKey),
+       (OFFER_NODE_ID_TYPE, node_id: PublicKey),
 });
 
 impl Bech32Encode for Offer {
@@ -751,14 +846,17 @@ mod tests {
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
+       use bitcoin::secp256k1::Secp256k1;
        use core::convert::TryFrom;
        use core::num::NonZeroU64;
        use core::time::Duration;
+       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::sign::KeyMaterial;
        use crate::ln::features::OfferFeatures;
+       use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::{DecodeError, MAX_VALUE_MSAT};
        use crate::offers::parse::{ParseError, SemanticError};
        use crate::offers::test_utils::*;
-       use crate::onion_message::{BlindedHop, BlindedPath};
        use crate::util::ser::{BigSize, Writeable};
        use crate::util::string::PrintableString;
 
@@ -865,6 +963,110 @@ mod tests {
                assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![43; 32]));
        }
 
+       #[test]
+       fn builds_offer_with_metadata_derived() {
+               let desc = "foo".to_string();
+               let node_id = recipient_pubkey();
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let secp_ctx = Secp256k1::new();
+
+               let offer = OfferBuilder
+                       ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+                       .amount_msats(1000)
+                       .build().unwrap();
+               assert_eq!(offer.signing_pubkey(), node_id);
+
+               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+
+               // Fails verification with altered offer field
+               let mut tlv_stream = offer.as_tlv_stream();
+               tlv_stream.amount = Some(100);
+
+               let mut encoded_offer = Vec::new();
+               tlv_stream.write(&mut encoded_offer).unwrap();
+
+               let invoice_request = Offer::try_from(encoded_offer).unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
+
+               // Fails verification with altered metadata
+               let mut tlv_stream = offer.as_tlv_stream();
+               let metadata = tlv_stream.metadata.unwrap().iter().copied().rev().collect();
+               tlv_stream.metadata = Some(&metadata);
+
+               let mut encoded_offer = Vec::new();
+               tlv_stream.write(&mut encoded_offer).unwrap();
+
+               let invoice_request = Offer::try_from(encoded_offer).unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
+       }
+
+       #[test]
+       fn builds_offer_with_derived_signing_pubkey() {
+               let desc = "foo".to_string();
+               let node_id = recipient_pubkey();
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let secp_ctx = Secp256k1::new();
+
+               let blinded_path = BlindedPath {
+                       introduction_node_id: pubkey(40),
+                       blinding_point: pubkey(41),
+                       blinded_hops: vec![
+                               BlindedHop { blinded_node_id: pubkey(42), encrypted_payload: vec![0; 43] },
+                               BlindedHop { blinded_node_id: node_id, encrypted_payload: vec![0; 44] },
+                       ],
+               };
+
+               let offer = OfferBuilder
+                       ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+                       .amount_msats(1000)
+                       .path(blinded_path)
+                       .build().unwrap();
+               assert_ne!(offer.signing_pubkey(), node_id);
+
+               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_ok());
+
+               // Fails verification with altered offer field
+               let mut tlv_stream = offer.as_tlv_stream();
+               tlv_stream.amount = Some(100);
+
+               let mut encoded_offer = Vec::new();
+               tlv_stream.write(&mut encoded_offer).unwrap();
+
+               let invoice_request = Offer::try_from(encoded_offer).unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
+
+               // Fails verification with altered signing pubkey
+               let mut tlv_stream = offer.as_tlv_stream();
+               let signing_pubkey = pubkey(1);
+               tlv_stream.node_id = Some(&signing_pubkey);
+
+               let mut encoded_offer = Vec::new();
+               tlv_stream.write(&mut encoded_offer).unwrap();
+
+               let invoice_request = Offer::try_from(encoded_offer).unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+               assert!(invoice_request.verify(&expanded_key, &secp_ctx).is_err());
+       }
+
        #[test]
        fn builds_offer_with_amount() {
                let bitcoin_amount = Amount::Bitcoin { amount_msats: 1000 };
@@ -1252,30 +1454,23 @@ mod bech32_tests {
        use bitcoin::bech32;
        use crate::ln::msgs::DecodeError;
 
-       // TODO: Remove once test vectors are updated.
-       #[ignore]
        #[test]
        fn encodes_offer_as_bech32_without_checksum() {
-               let encoded_offer = "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy";
+               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
                let offer = dbg!(encoded_offer.parse::<Offer>().unwrap());
                let reencoded_offer = offer.to_string();
                dbg!(reencoded_offer.parse::<Offer>().unwrap());
                assert_eq!(reencoded_offer, encoded_offer);
        }
 
-       // TODO: Remove once test vectors are updated.
-       #[ignore]
        #[test]
        fn parses_bech32_encoded_offers() {
                let offers = [
                        // BOLT 12 test vectors
-                       "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
-                       "l+no1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
-                       "l+no1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
-                       "lno1qcp4256ypqpq+86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn0+0fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0+sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qs+y",
-                       "lno1qcp4256ypqpq+ 86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn0+  0fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0+\nsqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43l+\r\nastpwuh73k29qs+\r  y",
-                       // Two blinded paths
-                       "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0yg06qg2qdd7t628sgykwj5kuc837qmlv9m9gr7sq8ap6erfgacv26nhp8zzcqgzhdvttlk22pw8fmwqqrvzst792mj35ypylj886ljkcmug03wg6heqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6muh550qsfva9fdes0ruph7ctk2s8aqq06r4jxj3msc448wzwy9sqs9w6ckhlv55zuwnkuqqxc9qhu24h9rggzflyw04l9d3hcslzu340jqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
+                       "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+                       "l+no1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+                       "lno1pqps7sjqpgt+yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+5k7msjzfpy7nz5yqcn+ygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+5xvxg",
+                       "lno1pqps7sjqpgt+ yzm3qv4uxzmtsd3jjqer9wd3hy6tsw3+  5k7msjzfpy7nz5yqcn+\nygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd+\r\n 5xvxg",
                ];
                for encoded_offer in &offers {
                        if let Err(e) = encoded_offer.parse::<Offer>() {
@@ -1288,11 +1483,11 @@ mod bech32_tests {
        fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
                let offers = [
                        // BOLT 12 test vectors
-                       "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy+",
-                       "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy+ ",
-                       "+lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
-                       "+ lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
-                       "ln++o1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy",
+                       "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+",
+                       "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg+ ",
+                       "+lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+                       "+ lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
+                       "ln++o1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg",
                ];
                for encoded_offer in &offers {
                        match encoded_offer.parse::<Offer>() {
@@ -1305,7 +1500,7 @@ mod bech32_tests {
 
        #[test]
        fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
-               let encoded_offer = "lni1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsy";
+               let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
                match encoded_offer.parse::<Offer>() {
                        Ok(_) => panic!("Valid offer: {}", encoded_offer),
                        Err(e) => assert_eq!(e, ParseError::InvalidBech32Hrp),
@@ -1314,7 +1509,7 @@ mod bech32_tests {
 
        #[test]
        fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
-               let encoded_offer = "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qso";
+               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
                match encoded_offer.parse::<Offer>() {
                        Ok(_) => panic!("Valid offer: {}", encoded_offer),
                        Err(e) => assert_eq!(e, ParseError::Bech32(bech32::Error::InvalidChar('o'))),
@@ -1323,7 +1518,7 @@ mod bech32_tests {
 
        #[test]
        fn fails_parsing_bech32_encoded_offer_with_invalid_tlv_data() {
-               let encoded_offer = "lno1qcp4256ypqpq86q2pucnq42ngssx2an9wfujqerp0y2pqun4wd68jtn00fkxzcnn9ehhyec6qgqsz83qfwdpl28qqmc78ymlvhmxcsywdk5wrjnj36jryg488qwlrnzyjczlqsp9nyu4phcg6dqhlhzgxagfu7zh3d9re0sqp9ts2yfugvnnm9gxkcnnnkdpa084a6t520h5zhkxsdnghvpukvd43lastpwuh73k29qsyqqqqq";
+               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
                match encoded_offer.parse::<Offer>() {
                        Ok(_) => panic!("Valid offer: {}", encoded_offer),
                        Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),