Macro-ize Offer accessors for reuse
[rust-lightning] / lightning / src / offers / offer.rs
index 6451d9431a188f3b15440c1d687435e7068f7240..1044ac9606d5acbe07e736204a97632c08b6758f 100644 (file)
@@ -13,7 +13,7 @@
 //! published as a QR code to be scanned by a customer. The customer uses the offer to request an
 //! invoice from the merchant to be paid.
 //!
-//! ```ignore
+//! ```
 //! extern crate bitcoin;
 //! extern crate core;
 //! extern crate lightning;
 //!
 //! use bitcoin::secp256k1::{KeyPair, PublicKey, Secp256k1, SecretKey};
 //! use lightning::offers::offer::{Offer, OfferBuilder, Quantity};
-//! use lightning::offers::parse::ParseError;
+//! use lightning::offers::parse::Bolt12ParseError;
 //! use lightning::util::ser::{Readable, Writeable};
 //!
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! # #[cfg(feature = "std")]
 //! # use std::time::SystemTime;
 //! #
@@ -35,7 +35,7 @@
 //! # fn create_another_blinded_path() -> BlindedPath { unimplemented!() }
 //! #
 //! # #[cfg(feature = "std")]
-//! # fn build() -> Result<(), ParseError> {
+//! # fn build() -> Result<(), Bolt12ParseError> {
 //! let secp_ctx = Secp256k1::new();
 //! let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
 //! let pubkey = PublicKey::from(keys);
 
 use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::PublicKey;
+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::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::parse::{Bech32Encode, ParseError, ParsedMessage, SemanticError};
-use crate::onion_message::BlindedPath;
+use crate::offers::invoice_request::{DerivedPayerId, ExplicitPayerId, InvoiceRequestBuilder};
+use crate::offers::merkle::TlvStream;
+use crate::offers::parse::{Bech32Encode, Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
+use crate::offers::signer::{Metadata, MetadataMaterial, self};
 use crate::util::ser::{HighZeroBytesDroppedBigSize, WithoutLength, Writeable, Writer};
 use crate::util::string::PrintableString;
 
@@ -87,30 +92,98 @@ use crate::prelude::*;
 #[cfg(feature = "std")]
 use std::time::SystemTime;
 
+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 {
+pub struct OfferBuilder<'a, M: MetadataStrategy, T: secp256k1::Signing> {
        offer: OfferContents,
+       metadata_strategy: core::marker::PhantomData<M>,
+       secp_ctx: Option<&'a Secp256k1<T>>,
 }
 
-impl OfferBuilder {
+/// 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 {}
+impl MetadataStrategy for DerivedMetadata {}
+
+impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
        /// Creates a new builder for an offer setting the [`Offer::description`] and using the
        /// [`Offer::signing_pubkey`] for signing invoices. The associated secret key must be remembered
        /// while the offer is valid.
        ///
        /// Use a different pubkey per offer to avoid correlating offers.
        pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
-               let offer = OfferContents {
-                       chains: None, metadata: None, amount: None, description,
-                       features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
-                       supported_quantity: Quantity::one(), signing_pubkey,
-               };
-               OfferBuilder { offer }
+               OfferBuilder {
+                       offer: OfferContents {
+                               chains: None, metadata: None, amount: None, description,
+                               features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
+                               supported_quantity: Quantity::One, signing_pubkey,
+                       },
+                       metadata_strategy: core::marker::PhantomData,
+                       secp_ctx: None,
+               }
+       }
+
+       /// Sets the [`Offer::metadata`] to the given bytes.
+       ///
+       /// Successive calls to this method will override the previous setting.
+       pub fn metadata(mut self, metadata: Vec<u8>) -> Result<Self, Bolt12SemanticError> {
+               self.offer.metadata = Some(Metadata::Bytes(metadata));
+               Ok(self)
+       }
+}
+
+impl<'a, T: secp256k1::Signing> OfferBuilder<'a, DerivedMetadata, T> {
+       /// Similar to [`OfferBuilder::new`] except, if [`OfferBuilder::path`] is called, the signing
+       /// pubkey is derived from the given [`ExpandedKey`] and [`EntropySource`]. This provides
+       /// 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 by
+       /// [`InvoiceRequest::verify`] to determine if the request was produced for the offer given an
+       /// [`ExpandedKey`].
+       ///
+       /// [`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,
+               secp_ctx: &'a Secp256k1<T>
+       ) -> Self where ES::Target: EntropySource {
+               let nonce = Nonce::from_entropy_source(entropy_source);
+               let derivation_material = MetadataMaterial::new(nonce, expanded_key, IV_BYTES);
+               let metadata = Metadata::DerivedSigningPubkey(derivation_material);
+               OfferBuilder {
+                       offer: OfferContents {
+                               chains: None, metadata: Some(metadata), amount: None, description,
+                               features: OfferFeatures::empty(), absolute_expiry: None, issuer: None, paths: None,
+                               supported_quantity: Quantity::One, signing_pubkey: node_id,
+                       },
+                       metadata_strategy: core::marker::PhantomData,
+                       secp_ctx: Some(secp_ctx),
+               }
        }
+}
 
+impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
        /// Adds the chain hash of the given [`Network`] to [`Offer::chains`]. If not called,
        /// the chain hash of [`Network::Bitcoin`] is assumed to be the only one supported.
        ///
@@ -127,14 +200,6 @@ impl OfferBuilder {
                self
        }
 
-       /// Sets the [`Offer::metadata`].
-       ///
-       /// Successive calls to this method will override the previous setting.
-       pub fn metadata(mut self, metadata: Vec<u8>) -> Self {
-               self.offer.metadata = Some(metadata);
-               self
-       }
-
        /// Sets the [`Offer::amount`] as an [`Amount::Bitcoin`].
        ///
        /// Successive calls to this method will override the previous setting.
@@ -178,7 +243,7 @@ impl OfferBuilder {
        }
 
        /// Sets the quantity of items for [`Offer::supported_quantity`]. If not called, defaults to
-       /// [`Quantity::one`].
+       /// [`Quantity::One`].
        ///
        /// Successive calls to this method will override the previous setting.
        pub fn supported_quantity(mut self, quantity: Quantity) -> Self {
@@ -187,14 +252,14 @@ impl OfferBuilder {
        }
 
        /// Builds an [`Offer`] from the builder's settings.
-       pub fn build(mut self) -> Result<Offer, SemanticError> {
+       pub fn build(mut self) -> Result<Offer, Bolt12SemanticError> {
                match self.offer.amount {
                        Some(Amount::Bitcoin { amount_msats }) => {
                                if amount_msats > MAX_VALUE_MSAT {
-                                       return Err(SemanticError::InvalidAmount);
+                                       return Err(Bolt12SemanticError::InvalidAmount);
                                }
                        },
-                       Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+                       Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
                        None => {},
                }
 
@@ -204,36 +269,58 @@ impl OfferBuilder {
                        }
                }
 
+               Ok(self.build_without_checks())
+       }
+
+       fn build_without_checks(mut self) -> Offer {
+               // Create the metadata for stateless verification of an InvoiceRequest.
+               if let Some(mut metadata) = self.offer.metadata.take() {
+                       if metadata.has_derivation_material() {
+                               if self.offer.paths.is_none() {
+                                       metadata = metadata.without_keys();
+                               }
+
+                               let mut tlv_stream = self.offer.as_tlv_stream();
+                               debug_assert_eq!(tlv_stream.metadata, None);
+                               tlv_stream.metadata = None;
+                               if metadata.derives_keys() {
+                                       tlv_stream.node_id = None;
+                               }
+
+                               let (derived_metadata, keys) = metadata.derive_from(tlv_stream, self.secp_ctx);
+                               metadata = derived_metadata;
+                               if let Some(keys) = keys {
+                                       self.offer.signing_pubkey = keys.public_key();
+                               }
+                       }
+
+                       self.offer.metadata = Some(metadata);
+               }
+
                let mut bytes = Vec::new();
                self.offer.write(&mut bytes).unwrap();
 
-               Ok(Offer {
-                       bytes,
-                       contents: self.offer,
-               })
+               Offer { bytes, contents: self.offer }
        }
 }
 
 #[cfg(test)]
-impl OfferBuilder {
+impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
        fn features_unchecked(mut self, features: OfferFeatures) -> Self {
                self.offer.features = features;
                self
        }
 
        pub(super) fn build_unchecked(self) -> Offer {
-               let mut bytes = Vec::new();
-               self.offer.write(&mut bytes).unwrap();
-
-               Offer { bytes, contents: self.offer }
+               self.build_without_checks()
        }
 }
 
 /// An `Offer` is a potentially long-lived proposal for payment of a good or service.
 ///
 /// An offer is a precursor to an [`InvoiceRequest`]. A merchant publishes an offer from which a
-/// customer may request an `Invoice` for a specific quantity and using an amount sufficient to
-/// cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
+/// customer may request an [`Bolt12Invoice`] for a specific quantity and using an amount sufficient
+/// to cover that quantity (i.e., at least `quantity * amount`). See [`Offer::amount`].
 ///
 /// Offers may be denominated in currency other than bitcoin but are ultimately paid using the
 /// latter.
@@ -241,7 +328,9 @@ impl OfferBuilder {
 /// Through the use of [`BlindedPath`]s, offers provide recipient privacy.
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
 #[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 pub struct Offer {
        // The serialized offer. Needed when creating an `InvoiceRequest` if the offer contains unknown
        // fields.
@@ -249,13 +338,16 @@ pub struct Offer {
        pub(super) contents: OfferContents,
 }
 
-/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or an `Invoice`.
+/// The contents of an [`Offer`], which may be shared with an [`InvoiceRequest`] or a
+/// [`Bolt12Invoice`].
 ///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
+/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
 #[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 pub(super) struct OfferContents {
        chains: Option<Vec<ChainHash>>,
-       metadata: Option<Vec<u8>>,
+       metadata: Option<Metadata>,
        amount: Option<Amount>,
        description: String,
        features: OfferFeatures,
@@ -266,83 +358,86 @@ pub(super) struct OfferContents {
        signing_pubkey: PublicKey,
 }
 
-impl Offer {
+macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
        // TODO: Return a slice once ChainHash has constants.
        // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1283
        // - https://github.com/rust-bitcoin/rust-bitcoin/pull/1286
        /// The chains that may be used when paying a requested invoice (e.g., bitcoin mainnet).
        /// Payments must be denominated in units of the minimal lightning-payable unit (e.g., msats)
        /// for the selected chain.
-       pub fn chains(&self) -> Vec<ChainHash> {
-               self.contents.chains()
-       }
-
-       pub(super) fn implied_chain(&self) -> ChainHash {
-               self.contents.implied_chain()
-       }
-
-       /// Returns whether the given chain is supported by the offer.
-       pub fn supports_chain(&self, chain: ChainHash) -> bool {
-               self.contents.supports_chain(chain)
+       pub fn chains(&$self) -> Vec<$crate::bitcoin::blockdata::constants::ChainHash> {
+               $contents.chains()
        }
 
        // TODO: Link to corresponding method in `InvoiceRequest`.
        /// Opaque bytes set by the originator. Useful for authentication and validating fields since it
        /// is reflected in `invoice_request` messages along with all the other fields from the `offer`.
-       pub fn metadata(&self) -> Option<&Vec<u8>> {
-               self.contents.metadata.as_ref()
+       pub fn metadata(&$self) -> Option<&Vec<u8>> {
+               $contents.metadata()
        }
 
        /// The minimum amount required for a successful payment of a single item.
-       pub fn amount(&self) -> Option<&Amount> {
-               self.contents.amount()
+       pub fn amount(&$self) -> Option<&$crate::offers::offer::Amount> {
+               $contents.amount()
        }
 
        /// 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)
+       pub fn description(&$self) -> $crate::util::string::PrintableString {
+               $contents.description()
        }
 
        /// Features pertaining to the offer.
-       pub fn features(&self) -> &OfferFeatures {
-               &self.contents.features
+       pub fn offer_features(&$self) -> &$crate::ln::features::OfferFeatures {
+               &$contents.features()
        }
 
        /// Duration since the Unix epoch when an invoice should no longer be requested.
        ///
        /// If `None`, the offer does not expire.
-       pub fn absolute_expiry(&self) -> Option<Duration> {
-               self.contents.absolute_expiry
-       }
-
-       /// Whether the offer has expired.
-       #[cfg(feature = "std")]
-       pub fn is_expired(&self) -> bool {
-               match self.absolute_expiry() {
-                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
-                               Ok(elapsed) => elapsed > seconds_from_epoch,
-                               Err(_) => false,
-                       },
-                       None => false,
-               }
+       pub fn absolute_expiry(&$self) -> Option<core::time::Duration> {
+               $contents.absolute_expiry()
        }
 
        /// The issuer of the offer, possibly beginning with `user@domain` or `domain`. Intended to be
        /// displayed to the user but with the caveat that it has not been verified in any way.
-       pub fn issuer(&self) -> Option<PrintableString> {
-               self.contents.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+       pub fn issuer(&$self) -> Option<$crate::util::string::PrintableString> {
+               $contents.issuer()
        }
 
        /// Paths to the recipient originating from publicly reachable nodes. Blinded paths provide
        /// recipient privacy by obfuscating its node id.
-       pub fn paths(&self) -> &[BlindedPath] {
-               self.contents.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
+       pub fn paths(&$self) -> &[$crate::blinded_path::BlindedPath] {
+               $contents.paths()
        }
 
        /// The quantity of items supported.
-       pub fn supported_quantity(&self) -> Quantity {
-               self.contents.supported_quantity()
+       pub fn supported_quantity(&$self) -> $crate::offers::offer::Quantity {
+               $contents.supported_quantity()
+       }
+
+       /// The public key used by the recipient to sign invoices.
+       pub fn signing_pubkey(&$self) -> $crate::bitcoin::secp256k1::PublicKey {
+               $contents.signing_pubkey()
+       }
+} }
+
+impl Offer {
+       offer_accessors!(self, self.contents);
+
+       pub(super) fn implied_chain(&self) -> ChainHash {
+               self.contents.implied_chain()
+       }
+
+       /// Returns whether the given chain is supported by the offer.
+       pub fn supports_chain(&self, chain: ChainHash) -> bool {
+               self.contents.supports_chain(chain)
+       }
+
+       /// Whether the offer has expired.
+       #[cfg(feature = "std")]
+       pub fn is_expired(&self) -> bool {
+               self.contents.is_expired()
        }
 
        /// Returns whether the given quantity is valid for the offer.
@@ -357,13 +452,57 @@ impl Offer {
                self.contents.expects_quantity()
        }
 
-       /// The public key used by the recipient to sign invoices.
-       pub fn signing_pubkey(&self) -> PublicKey {
-               self.contents.signing_pubkey
+       /// 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 [`Bolt12Invoice::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
+       /// [`Bolt12Invoice::verify`]: crate::offers::invoice::Bolt12Invoice::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>, Bolt12SemanticError>
+       where
+               ES::Target: EntropySource,
+       {
+               if self.offer_features().requires_unknown_bits() {
+                       return Err(Bolt12SemanticError::UnknownRequiredFeatures);
+               }
+
+               Ok(InvoiceRequestBuilder::deriving_payer_id(self, expanded_key, entropy_source, secp_ctx))
        }
 
-       /// 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_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>, Bolt12SemanticError>
+       where
+               ES::Target: EntropySource,
+       {
+               if self.offer_features().requires_unknown_bits() {
+                       return Err(Bolt12SemanticError::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 `Bolt12Invoice` 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
@@ -374,12 +513,14 @@ 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<InvoiceRequestBuilderSemanticError> {
-               if self.features().requires_unknown_bits() {
-                       return Err(SemanticError::UnknownRequiredFeatures);
+       ) -> Result<InvoiceRequestBuilder<ExplicitPayerId, secp256k1::SignOnly>, Bolt12SemanticError> {
+               if self.offer_features().requires_unknown_bits() {
+                       return Err(Bolt12SemanticError::UnknownRequiredFeatures);
                }
 
                Ok(InvoiceRequestBuilder::new(self, metadata, payer_id))
@@ -410,29 +551,65 @@ impl OfferContents {
                self.chains().contains(&chain)
        }
 
+       pub fn metadata(&self) -> Option<&Vec<u8>> {
+               self.metadata.as_ref().and_then(|metadata| metadata.as_bytes())
+       }
+
        pub fn amount(&self) -> Option<&Amount> {
                self.amount.as_ref()
        }
 
+       pub fn description(&self) -> PrintableString {
+               PrintableString(&self.description)
+       }
+
+       pub fn features(&self) -> &OfferFeatures {
+               &self.features
+       }
+
+       pub fn absolute_expiry(&self) -> Option<Duration> {
+               self.absolute_expiry
+       }
+
+       #[cfg(feature = "std")]
+       pub(super) fn is_expired(&self) -> bool {
+               match self.absolute_expiry {
+                       Some(seconds_from_epoch) => match SystemTime::UNIX_EPOCH.elapsed() {
+                               Ok(elapsed) => elapsed > seconds_from_epoch,
+                               Err(_) => false,
+                       },
+                       None => false,
+               }
+       }
+
+       pub fn issuer(&self) -> Option<PrintableString> {
+               self.issuer.as_ref().map(|issuer| PrintableString(issuer.as_str()))
+       }
+
+       pub fn paths(&self) -> &[BlindedPath] {
+               self.paths.as_ref().map(|paths| paths.as_slice()).unwrap_or(&[])
+       }
+
        pub(super) fn check_amount_msats_for_quantity(
                &self, amount_msats: Option<u64>, quantity: Option<u64>
-       ) -> Result<(), SemanticError> {
+       ) -> Result<(), Bolt12SemanticError> {
                let offer_amount_msats = match self.amount {
                        None => 0,
                        Some(Amount::Bitcoin { amount_msats }) => amount_msats,
-                       Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
+                       Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency),
                };
 
                if !self.expects_quantity() || quantity.is_some() {
-                       let expected_amount_msats = offer_amount_msats * quantity.unwrap_or(1);
+                       let expected_amount_msats = offer_amount_msats.checked_mul(quantity.unwrap_or(1))
+                               .ok_or(Bolt12SemanticError::InvalidAmount)?;
                        let amount_msats = amount_msats.unwrap_or(expected_amount_msats);
 
                        if amount_msats < expected_amount_msats {
-                               return Err(SemanticError::InsufficientAmount);
+                               return Err(Bolt12SemanticError::InsufficientAmount);
                        }
 
                        if amount_msats > MAX_VALUE_MSAT {
-                               return Err(SemanticError::InvalidAmount);
+                               return Err(Bolt12SemanticError::InvalidAmount);
                        }
                }
 
@@ -443,13 +620,13 @@ impl OfferContents {
                self.supported_quantity
        }
 
-       pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), SemanticError> {
+       pub(super) fn check_quantity(&self, quantity: Option<u64>) -> Result<(), Bolt12SemanticError> {
                let expects_quantity = self.expects_quantity();
                match quantity {
-                       None if expects_quantity => Err(SemanticError::MissingQuantity),
-                       Some(_) if !expects_quantity => Err(SemanticError::UnexpectedQuantity),
+                       None if expects_quantity => Err(Bolt12SemanticError::MissingQuantity),
+                       Some(_) if !expects_quantity => Err(Bolt12SemanticError::UnexpectedQuantity),
                        Some(quantity) if !self.is_valid_quantity(quantity) => {
-                               Err(SemanticError::InvalidQuantity)
+                               Err(Bolt12SemanticError::InvalidQuantity)
                        },
                        _ => Ok(()),
                }
@@ -457,19 +634,42 @@ impl OfferContents {
 
        fn is_valid_quantity(&self, quantity: u64) -> bool {
                match self.supported_quantity {
-                       Quantity::Bounded(n) => {
-                               let n = n.get();
-                               if n == 1 { false }
-                               else { quantity > 0 && quantity <= n }
-                       },
+                       Quantity::Bounded(n) => quantity <= n.get(),
                        Quantity::Unbounded => quantity > 0,
+                       Quantity::One => quantity == 1,
                }
        }
 
        fn expects_quantity(&self) -> bool {
                match self.supported_quantity {
-                       Quantity::Bounded(n) => n.get() != 1,
+                       Quantity::Bounded(_) => true,
                        Quantity::Unbounded => true,
+                       Quantity::One => false,
+               }
+       }
+
+       pub(super) fn signing_pubkey(&self) -> PublicKey {
+               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(()),
                }
        }
 
@@ -488,7 +688,7 @@ impl OfferContents {
 
                OfferTlvStreamRef {
                        chains: self.chains.as_ref(),
-                       metadata: self.metadata.as_ref(),
+                       metadata: self.metadata(),
                        currency,
                        amount,
                        description: Some(&self.description),
@@ -538,41 +738,49 @@ pub type CurrencyCode = [u8; 3];
 /// Quantity of items supported by an [`Offer`].
 #[derive(Clone, Copy, Debug, PartialEq)]
 pub enum Quantity {
-       /// Up to a specific number of items (inclusive).
+       /// Up to a specific number of items (inclusive). Use when more than one item can be requested
+       /// but is limited (e.g., because of per customer or inventory limits).
+       ///
+       /// May be used with `NonZeroU64::new(1)` but prefer to use [`Quantity::One`] if only one item
+       /// is supported.
        Bounded(NonZeroU64),
-       /// One or more items.
+       /// One or more items. Use when more than one item can be requested without any limit.
        Unbounded,
+       /// Only one item. Use when only a single item can be requested.
+       One,
 }
 
 impl Quantity {
-       /// The default quantity of one.
-       pub fn one() -> Self {
-               Quantity::Bounded(NonZeroU64::new(1).unwrap())
-       }
-
        fn to_tlv_record(&self) -> Option<u64> {
                match self {
-                       Quantity::Bounded(n) => {
-                               let n = n.get();
-                               if n == 1 { None } else { Some(n) }
-                       },
+                       Quantity::Bounded(n) => Some(n.get()),
                        Quantity::Unbounded => Some(0),
+                       Quantity::One => None,
                }
        }
 }
 
-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)),
-       (12, features: OfferFeatures),
+       (12, features: (OfferFeatures, WithoutLength)),
        (14, absolute_expiry: (u64, HighZeroBytesDroppedBigSize)),
        (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 {
@@ -580,7 +788,7 @@ impl Bech32Encode for Offer {
 }
 
 impl FromStr for Offer {
-       type Err = ParseError;
+       type Err = Bolt12ParseError;
 
        fn from_str(s: &str) -> Result<Self, <Self as FromStr>::Err> {
                Self::from_bech32_str(s)
@@ -588,7 +796,7 @@ impl FromStr for Offer {
 }
 
 impl TryFrom<Vec<u8>> for Offer {
-       type Error = ParseError;
+       type Error = Bolt12ParseError;
 
        fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
                let offer = ParsedMessage::<OfferTlvStream>::try_from(bytes)?;
@@ -599,7 +807,7 @@ impl TryFrom<Vec<u8>> for Offer {
 }
 
 impl TryFrom<OfferTlvStream> for OfferContents {
-       type Error = SemanticError;
+       type Error = Bolt12SemanticError;
 
        fn try_from(tlv_stream: OfferTlvStream) -> Result<Self, Self::Error> {
                let OfferTlvStream {
@@ -607,18 +815,20 @@ impl TryFrom<OfferTlvStream> for OfferContents {
                        issuer, quantity_max, node_id,
                } = tlv_stream;
 
+               let metadata = metadata.map(|metadata| Metadata::Bytes(metadata));
+
                let amount = match (currency, amount) {
                        (None, None) => None,
                        (None, Some(amount_msats)) if amount_msats > MAX_VALUE_MSAT => {
-                               return Err(SemanticError::InvalidAmount);
+                               return Err(Bolt12SemanticError::InvalidAmount);
                        },
                        (None, Some(amount_msats)) => Some(Amount::Bitcoin { amount_msats }),
-                       (Some(_), None) => return Err(SemanticError::MissingAmount),
+                       (Some(_), None) => return Err(Bolt12SemanticError::MissingAmount),
                        (Some(iso4217_code), Some(amount)) => Some(Amount::Currency { iso4217_code, amount }),
                };
 
                let description = match description {
-                       None => return Err(SemanticError::MissingDescription),
+                       None => return Err(Bolt12SemanticError::MissingDescription),
                        Some(description) => description,
                };
 
@@ -628,14 +838,13 @@ impl TryFrom<OfferTlvStream> for OfferContents {
                        .map(|seconds_from_epoch| Duration::from_secs(seconds_from_epoch));
 
                let supported_quantity = match quantity_max {
-                       None => Quantity::one(),
+                       None => Quantity::One,
                        Some(0) => Quantity::Unbounded,
-                       Some(1) => return Err(SemanticError::InvalidQuantity),
                        Some(n) => Quantity::Bounded(NonZeroU64::new(n).unwrap()),
                };
 
                let signing_pubkey = match node_id {
-                       None => return Err(SemanticError::MissingSigningPubkey),
+                       None => return Err(Bolt12SemanticError::MissingSigningPubkey),
                        Some(node_id) => node_id,
                };
 
@@ -658,26 +867,20 @@ mod tests {
 
        use bitcoin::blockdata::constants::ChainHash;
        use bitcoin::network::constants::Network;
-       use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey};
+       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::onion_message::{BlindedHop, BlindedPath};
+       use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
+       use crate::offers::test_utils::*;
        use crate::util::ser::{BigSize, Writeable};
        use crate::util::string::PrintableString;
 
-       fn pubkey(byte: u8) -> PublicKey {
-               let secp_ctx = Secp256k1::new();
-               PublicKey::from_secret_key(&secp_ctx, &privkey(byte))
-       }
-
-       fn privkey(byte: u8) -> SecretKey {
-               SecretKey::from_slice(&[byte; 32]).unwrap()
-       }
-
        #[test]
        fn builds_offer_with_defaults() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42)).build().unwrap();
@@ -691,13 +894,13 @@ mod tests {
                assert_eq!(offer.metadata(), None);
                assert_eq!(offer.amount(), None);
                assert_eq!(offer.description(), PrintableString("foo"));
-               assert_eq!(offer.features(), &OfferFeatures::empty());
+               assert_eq!(offer.offer_features(), &OfferFeatures::empty());
                assert_eq!(offer.absolute_expiry(), None);
                #[cfg(feature = "std")]
                assert!(!offer.is_expired());
                assert_eq!(offer.paths(), &[]);
                assert_eq!(offer.issuer(), None);
-               assert_eq!(offer.supported_quantity(), Quantity::one());
+               assert_eq!(offer.supported_quantity(), Quantity::One);
                assert_eq!(offer.signing_pubkey(), pubkey(42));
 
                assert_eq!(
@@ -766,21 +969,125 @@ mod tests {
        #[test]
        fn builds_offer_with_metadata() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
-                       .metadata(vec![42; 32])
+                       .metadata(vec![42; 32]).unwrap()
                        .build()
                        .unwrap();
                assert_eq!(offer.metadata(), Some(&vec![42; 32]));
                assert_eq!(offer.as_tlv_stream().metadata, Some(&vec![42; 32]));
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
-                       .metadata(vec![42; 32])
-                       .metadata(vec![43; 32])
+                       .metadata(vec![42; 32]).unwrap()
+                       .metadata(vec![43; 32]).unwrap()
                        .build()
                        .unwrap();
                assert_eq!(offer.metadata(), Some(&vec![43; 32]));
                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 };
@@ -803,7 +1110,7 @@ mod tests {
                assert_eq!(tlv_stream.currency, Some(b"USD"));
                match builder.build() {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, SemanticError::UnsupportedCurrency),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedCurrency),
                }
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -818,7 +1125,7 @@ mod tests {
                let invalid_amount = Amount::Bitcoin { amount_msats: MAX_VALUE_MSAT + 1 };
                match OfferBuilder::new("foo".into(), pubkey(42)).amount(invalid_amount).build() {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
                }
        }
 
@@ -828,7 +1135,7 @@ mod tests {
                        .features_unchecked(OfferFeatures::unknown())
                        .build()
                        .unwrap();
-               assert_eq!(offer.features(), &OfferFeatures::unknown());
+               assert_eq!(offer.offer_features(), &OfferFeatures::unknown());
                assert_eq!(offer.as_tlv_stream().features, Some(&OfferFeatures::unknown()));
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -836,7 +1143,7 @@ mod tests {
                        .features_unchecked(OfferFeatures::empty())
                        .build()
                        .unwrap();
-               assert_eq!(offer.features(), &OfferFeatures::empty());
+               assert_eq!(offer.offer_features(), &OfferFeatures::empty());
                assert_eq!(offer.as_tlv_stream().features, None);
        }
 
@@ -919,14 +1226,15 @@ mod tests {
 
        #[test]
        fn builds_offer_with_supported_quantity() {
+               let one = NonZeroU64::new(1).unwrap();
                let ten = NonZeroU64::new(10).unwrap();
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
-                       .supported_quantity(Quantity::one())
+                       .supported_quantity(Quantity::One)
                        .build()
                        .unwrap();
                let tlv_stream = offer.as_tlv_stream();
-               assert_eq!(offer.supported_quantity(), Quantity::one());
+               assert_eq!(offer.supported_quantity(), Quantity::One);
                assert_eq!(tlv_stream.quantity_max, None);
 
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
@@ -945,13 +1253,21 @@ mod tests {
                assert_eq!(offer.supported_quantity(), Quantity::Bounded(ten));
                assert_eq!(tlv_stream.quantity_max, Some(10));
 
+               let offer = OfferBuilder::new("foo".into(), pubkey(42))
+                       .supported_quantity(Quantity::Bounded(one))
+                       .build()
+                       .unwrap();
+               let tlv_stream = offer.as_tlv_stream();
+               assert_eq!(offer.supported_quantity(), Quantity::Bounded(one));
+               assert_eq!(tlv_stream.quantity_max, Some(1));
+
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
                        .supported_quantity(Quantity::Bounded(ten))
-                       .supported_quantity(Quantity::one())
+                       .supported_quantity(Quantity::One)
                        .build()
                        .unwrap();
                let tlv_stream = offer.as_tlv_stream();
-               assert_eq!(offer.supported_quantity(), Quantity::one());
+               assert_eq!(offer.supported_quantity(), Quantity::One);
                assert_eq!(tlv_stream.quantity_max, None);
        }
 
@@ -963,7 +1279,7 @@ mod tests {
                        .request_invoice(vec![1; 32], pubkey(43))
                {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, SemanticError::UnknownRequiredFeatures),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::UnknownRequiredFeatures),
                }
        }
 
@@ -1009,7 +1325,7 @@ mod tests {
 
                match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
                }
 
                let mut tlv_stream = offer.as_tlv_stream();
@@ -1021,7 +1337,7 @@ mod tests {
 
                match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidAmount)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
                }
        }
 
@@ -1041,7 +1357,7 @@ mod tests {
                match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingDescription));
+                               assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingDescription));
                        },
                }
        }
@@ -1083,7 +1399,7 @@ mod tests {
        #[test]
        fn parses_offer_with_quantity() {
                let offer = OfferBuilder::new("foo".into(), pubkey(42))
-                       .supported_quantity(Quantity::one())
+                       .supported_quantity(Quantity::One)
                        .build()
                        .unwrap();
                if let Err(e) = offer.to_string().parse::<Offer>() {
@@ -1106,17 +1422,12 @@ mod tests {
                        panic!("error parsing offer: {:?}", e);
                }
 
-               let mut tlv_stream = offer.as_tlv_stream();
-               tlv_stream.quantity_max = Some(1);
-
-               let mut encoded_offer = Vec::new();
-               tlv_stream.write(&mut encoded_offer).unwrap();
-
-               match Offer::try_from(encoded_offer) {
-                       Ok(_) => panic!("expected error"),
-                       Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidQuantity));
-                       },
+               let offer = OfferBuilder::new("foo".into(), pubkey(42))
+                       .supported_quantity(Quantity::Bounded(NonZeroU64::new(1).unwrap()))
+                       .build()
+                       .unwrap();
+               if let Err(e) = offer.to_string().parse::<Offer>() {
+                       panic!("error parsing offer: {:?}", e);
                }
        }
 
@@ -1136,7 +1447,7 @@ mod tests {
                match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+                               assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
                        },
                }
        }
@@ -1153,41 +1464,34 @@ mod tests {
 
                match Offer::try_from(encoded_offer) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
                }
        }
 }
 
 #[cfg(test)]
 mod bech32_tests {
-       use super::{Offer, ParseError};
+       use super::{Bolt12ParseError, Offer};
        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>() {
@@ -1200,16 +1504,16 @@ 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>() {
                                Ok(_) => panic!("Valid offer: {}", encoded_offer),
-                               Err(e) => assert_eq!(e, ParseError::InvalidContinuation),
+                               Err(e) => assert_eq!(e, Bolt12ParseError::InvalidContinuation),
                        }
                }
 
@@ -1217,28 +1521,28 @@ 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),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
                }
        }
 
        #[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'))),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Bech32(bech32::Error::InvalidChar('o'))),
                }
        }
 
        #[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)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
                }
        }
 }