Address custom HTLC TLV fixups
[rust-lightning] / lightning / src / offers / invoice.rs
index 66642dec7b2d30a6dc04643bcc19bea0c49838e8..c3d4500aaebbb0b8f0c2d6abdb4f4179bfad3cf0 100644 (file)
@@ -9,14 +9,14 @@
 
 //! Data structures and encoding for `invoice` messages.
 //!
-//! An [`Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid" flow or
-//! from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment then sends
-//! the invoice to the intended payer, who will then pay it.
+//! A [`Bolt12Invoice`] can be built from a parsed [`InvoiceRequest`] for the "offer to be paid"
+//! flow or from a [`Refund`] as an "offer for money" flow. The expected recipient of the payment
+//! then sends the invoice to the intended payer, who will then pay it.
 //!
 //! The payment recipient must include a [`PaymentHash`], so as to reveal the preimage upon payment
 //! receipt, and one or more [`BlindedPath`]s for the payer to use when sending the payment.
 //!
-//! ```ignore
+//! ```
 //! extern crate bitcoin;
 //! extern crate lightning;
 //!
 //!
 //! # use lightning::ln::PaymentHash;
 //! # use lightning::offers::invoice::BlindedPayInfo;
-//! # use lightning::onion_message::BlindedPath;
+//! # use lightning::blinded_path::BlindedPath;
 //! #
-//! # fn create_payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> { unimplemented!() }
+//! # fn create_payment_paths() -> Vec<(BlindedPayInfo, BlindedPath)> { unimplemented!() }
 //! # fn create_payment_hash() -> PaymentHash { unimplemented!() }
 //! #
-//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse_invoice_request(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::Bolt12ParseError> {
 //! let payment_paths = create_payment_paths();
 //! let payment_hash = create_payment_hash();
 //! let secp_ctx = Secp256k1::new();
@@ -62,7 +62,7 @@
 //! # Ok(())
 //! # }
 //!
-//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::ParseError> {
+//! # fn parse_refund(bytes: Vec<u8>) -> Result<(), lightning::offers::parse::Bolt12ParseError> {
 //! # let payment_paths = create_payment_paths();
 //! # let payment_hash = create_payment_hash();
 //! # let secp_ctx = Secp256k1::new();
@@ -97,24 +97,27 @@ use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::hash_types::{WPubkeyHash, WScriptHash};
 use bitcoin::hashes::Hash;
 use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::{Message, PublicKey};
+use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, self};
 use bitcoin::secp256k1::schnorr::Signature;
 use bitcoin::util::address::{Address, Payload, WitnessVersion};
 use bitcoin::util::schnorr::TweakedPublicKey;
-use core::convert::TryFrom;
+use core::convert::{Infallible, TryFrom};
 use core::time::Duration;
 use crate::io;
+use crate::blinded_path::BlindedPath;
 use crate::ln::PaymentHash;
 use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
+use crate::ln::inbound_payment::ExpandedKey;
 use crate::ln::msgs::DecodeError;
-use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
-use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, WithoutSignatures, self};
-use crate::offers::offer::{Amount, OfferTlvStream, OfferTlvStreamRef};
-use crate::offers::parse::{ParseError, ParsedMessage, SemanticError};
-use crate::offers::payer::{PayerTlvStream, PayerTlvStreamRef};
-use crate::offers::refund::{Refund, RefundContents};
-use crate::onion_message::BlindedPath;
+use crate::offers::invoice_request::{INVOICE_REQUEST_PAYER_ID_TYPE, INVOICE_REQUEST_TYPES, IV_BYTES as INVOICE_REQUEST_IV_BYTES, InvoiceRequest, InvoiceRequestContents, InvoiceRequestTlvStream, InvoiceRequestTlvStreamRef};
+use crate::offers::merkle::{SignError, SignatureTlvStream, SignatureTlvStreamRef, TlvStream, WithoutSignatures, self};
+use crate::offers::offer::{Amount, OFFER_TYPES, OfferTlvStream, OfferTlvStreamRef};
+use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
+use crate::offers::payer::{PAYER_METADATA_TYPE, PayerTlvStream, PayerTlvStreamRef};
+use crate::offers::refund::{IV_BYTES as REFUND_IV_BYTES, Refund, RefundContents};
+use crate::offers::signer;
 use crate::util::ser::{HighZeroBytesDroppedBigSize, Iterable, SeekReadable, WithoutLength, Writeable, Writer};
+use crate::util::string::PrintableString;
 
 use crate::prelude::*;
 
@@ -123,76 +126,154 @@ use std::time::SystemTime;
 
 const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
 
-const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
+pub(super) const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
 
-/// Builds an [`Invoice`] from either:
+/// Builds a [`Bolt12Invoice`] from either:
 /// - an [`InvoiceRequest`] for the "offer to be paid" flow or
 /// - a [`Refund`] for the "offer for money" flow.
 ///
 /// See [module-level documentation] for usage.
 ///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+///
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
 /// [`Refund`]: crate::offers::refund::Refund
 /// [module-level documentation]: self
-pub struct InvoiceBuilder<'a> {
+pub struct InvoiceBuilder<'a, S: SigningPubkeyStrategy> {
        invreq_bytes: &'a Vec<u8>,
        invoice: InvoiceContents,
+       keys: Option<KeyPair>,
+       signing_pubkey_strategy: core::marker::PhantomData<S>,
 }
 
-impl<'a> InvoiceBuilder<'a> {
+/// Indicates how [`Bolt12Invoice::signing_pubkey`] was set.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+pub trait SigningPubkeyStrategy {}
+
+/// [`Bolt12Invoice::signing_pubkey`] was explicitly set.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+pub struct ExplicitSigningPubkey {}
+
+/// [`Bolt12Invoice::signing_pubkey`] was derived.
+///
+/// This is not exported to bindings users as builder patterns don't map outside of move semantics.
+pub struct DerivedSigningPubkey {}
+
+impl SigningPubkeyStrategy for ExplicitSigningPubkey {}
+impl SigningPubkeyStrategy for DerivedSigningPubkey {}
+
+impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
        pub(super) fn for_offer(
-               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash
-       ) -> Result<Self, SemanticError> {
-               let amount_msats = match invoice_request.amount_msats() {
-                       Some(amount_msats) => amount_msats,
-                       None => match invoice_request.contents.offer.amount() {
-                               Some(Amount::Bitcoin { amount_msats }) => {
-                                       amount_msats * invoice_request.quantity().unwrap_or(1)
-                               },
-                               Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
-                               None => return Err(SemanticError::MissingAmount),
-                       },
-               };
-
+       ) -> Result<Self, Bolt12SemanticError> {
+               let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
-                       fields: InvoiceFields {
-                               payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
-                               fallbacks: None, features: Bolt12InvoiceFeatures::empty(),
-                               signing_pubkey: invoice_request.contents.offer.signing_pubkey(),
-                       },
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
                };
 
-               Self::new(&invoice_request.bytes, contents)
+               Self::new(&invoice_request.bytes, contents, None)
        }
 
        pub(super) fn for_refund(
-               refund: &'a Refund, payment_paths: Vec<(BlindedPath, BlindedPayInfo)>, created_at: Duration,
+               refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
                payment_hash: PaymentHash, signing_pubkey: PublicKey
-       ) -> Result<Self, SemanticError> {
+       ) -> Result<Self, Bolt12SemanticError> {
+               let amount_msats = refund.amount_msats();
                let contents = InvoiceContents::ForRefund {
                        refund: refund.contents.clone(),
-                       fields: InvoiceFields {
-                               payment_paths, created_at, relative_expiry: None, payment_hash,
-                               amount_msats: refund.amount_msats(), fallbacks: None,
-                               features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
-                       },
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
                };
 
-               Self::new(&refund.bytes, contents)
+               Self::new(&refund.bytes, contents, None)
        }
+}
 
-       fn new(invreq_bytes: &'a Vec<u8>, contents: InvoiceContents) -> Result<Self, SemanticError> {
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+       pub(super) fn for_offer_using_keys(
+               invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
+               created_at: Duration, payment_hash: PaymentHash, keys: KeyPair
+       ) -> Result<Self, Bolt12SemanticError> {
+               let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
+               let contents = InvoiceContents::ForOffer {
+                       invoice_request: invoice_request.contents.clone(),
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
+               };
+
+               Self::new(&invoice_request.bytes, contents, Some(keys))
+       }
+
+       pub(super) fn for_refund_using_keys(
+               refund: &'a Refund, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
+               payment_hash: PaymentHash, keys: KeyPair,
+       ) -> Result<Self, Bolt12SemanticError> {
+               let amount_msats = refund.amount_msats();
+               let signing_pubkey = keys.public_key();
+               let contents = InvoiceContents::ForRefund {
+                       refund: refund.contents.clone(),
+                       fields: Self::fields(
+                               payment_paths, created_at, payment_hash, amount_msats, signing_pubkey
+                       ),
+               };
+
+               Self::new(&refund.bytes, contents, Some(keys))
+       }
+}
+
+impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
+       fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
+               match invoice_request.amount_msats() {
+                       Some(amount_msats) => Ok(amount_msats),
+                       None => match invoice_request.contents.inner.offer.amount() {
+                               Some(Amount::Bitcoin { amount_msats }) => {
+                                       amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
+                                               .ok_or(Bolt12SemanticError::InvalidAmount)
+                               },
+                               Some(Amount::Currency { .. }) => Err(Bolt12SemanticError::UnsupportedCurrency),
+                               None => Err(Bolt12SemanticError::MissingAmount),
+                       },
+               }
+       }
+
+       fn fields(
+               payment_paths: Vec<(BlindedPayInfo, BlindedPath)>, created_at: Duration,
+               payment_hash: PaymentHash, amount_msats: u64, signing_pubkey: PublicKey
+       ) -> InvoiceFields {
+               InvoiceFields {
+                       payment_paths, created_at, relative_expiry: None, payment_hash, amount_msats,
+                       fallbacks: None, features: Bolt12InvoiceFeatures::empty(), signing_pubkey,
+               }
+       }
+
+       fn new(
+               invreq_bytes: &'a Vec<u8>, contents: InvoiceContents, keys: Option<KeyPair>
+       ) -> Result<Self, Bolt12SemanticError> {
                if contents.fields().payment_paths.is_empty() {
-                       return Err(SemanticError::MissingPaths);
+                       return Err(Bolt12SemanticError::MissingPaths);
                }
 
-               Ok(Self { invreq_bytes, invoice: contents })
+               Ok(Self {
+                       invreq_bytes,
+                       invoice: contents,
+                       keys,
+                       signing_pubkey_strategy: core::marker::PhantomData,
+               })
        }
 
-       /// Sets the [`Invoice::relative_expiry`] as seconds since [`Invoice::created_at`]. Any expiry
-       /// that has already passed is valid and can be checked for using [`Invoice::is_expired`].
+       /// Sets the [`Bolt12Invoice::relative_expiry`] as seconds since [`Bolt12Invoice::created_at`].
+       /// Any expiry that has already passed is valid and can be checked for using
+       /// [`Bolt12Invoice::is_expired`].
        ///
        /// Successive calls to this method will override the previous setting.
        pub fn relative_expiry(mut self, relative_expiry_secs: u32) -> Self {
@@ -201,7 +282,7 @@ impl<'a> InvoiceBuilder<'a> {
                self
        }
 
-       /// Adds a P2WSH address to [`Invoice::fallbacks`].
+       /// Adds a P2WSH address to [`Bolt12Invoice::fallbacks`].
        ///
        /// Successive calls to this method will add another address. Caller is responsible for not
        /// adding duplicate addresses and only calling if capable of receiving to P2WSH addresses.
@@ -214,7 +295,7 @@ impl<'a> InvoiceBuilder<'a> {
                self
        }
 
-       /// Adds a P2WPKH address to [`Invoice::fallbacks`].
+       /// Adds a P2WPKH address to [`Bolt12Invoice::fallbacks`].
        ///
        /// Successive calls to this method will add another address. Caller is responsible for not
        /// adding duplicate addresses and only calling if capable of receiving to P2WPKH addresses.
@@ -227,7 +308,7 @@ impl<'a> InvoiceBuilder<'a> {
                self
        }
 
-       /// Adds a P2TR address to [`Invoice::fallbacks`].
+       /// Adds a P2TR address to [`Bolt12Invoice::fallbacks`].
        ///
        /// Successive calls to this method will add another address. Caller is responsible for not
        /// adding duplicate addresses and only calling if capable of receiving to P2TR addresses.
@@ -240,35 +321,66 @@ impl<'a> InvoiceBuilder<'a> {
                self
        }
 
-       /// Sets [`Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
+       /// Sets [`Bolt12Invoice::features`] to indicate MPP may be used. Otherwise, MPP is disallowed.
        pub fn allow_mpp(mut self) -> Self {
                self.invoice.fields_mut().features.set_basic_mpp_optional();
                self
        }
+}
 
-       /// Builds an unsigned [`Invoice`] after checking for valid semantics. It can be signed by
-       /// [`UnsignedInvoice::sign`].
-       pub fn build(self) -> Result<UnsignedInvoice<'a>, SemanticError> {
+impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
+       /// Builds an unsigned [`Bolt12Invoice`] after checking for valid semantics. It can be signed by
+       /// [`UnsignedBolt12Invoice::sign`].
+       pub fn build(self) -> Result<UnsignedBolt12Invoice<'a>, Bolt12SemanticError> {
                #[cfg(feature = "std")] {
                        if self.invoice.is_offer_or_refund_expired() {
-                               return Err(SemanticError::AlreadyExpired);
+                               return Err(Bolt12SemanticError::AlreadyExpired);
                        }
                }
 
-               let InvoiceBuilder { invreq_bytes, invoice } = self;
-               Ok(UnsignedInvoice { invreq_bytes, invoice })
+               let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
+               Ok(UnsignedBolt12Invoice { invreq_bytes, invoice })
        }
 }
 
-/// A semantically valid [`Invoice`] that hasn't been signed.
-pub struct UnsignedInvoice<'a> {
+impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
+       /// Builds a signed [`Bolt12Invoice`] after checking for valid semantics.
+       pub fn build_and_sign<T: secp256k1::Signing>(
+               self, secp_ctx: &Secp256k1<T>
+       ) -> Result<Bolt12Invoice, Bolt12SemanticError> {
+               #[cfg(feature = "std")] {
+                       if self.invoice.is_offer_or_refund_expired() {
+                               return Err(Bolt12SemanticError::AlreadyExpired);
+                       }
+               }
+
+               let InvoiceBuilder { invreq_bytes, invoice, keys, .. } = self;
+               let unsigned_invoice = UnsignedBolt12Invoice { invreq_bytes, invoice };
+
+               let keys = keys.unwrap();
+               let invoice = unsigned_invoice
+                       .sign::<_, Infallible>(|digest| Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys)))
+                       .unwrap();
+               Ok(invoice)
+       }
+}
+
+/// A semantically valid [`Bolt12Invoice`] that hasn't been signed.
+pub struct UnsignedBolt12Invoice<'a> {
        invreq_bytes: &'a Vec<u8>,
        invoice: InvoiceContents,
 }
 
-impl<'a> UnsignedInvoice<'a> {
+impl<'a> UnsignedBolt12Invoice<'a> {
+       /// The public key corresponding to the key needed to sign the invoice.
+       pub fn signing_pubkey(&self) -> PublicKey {
+               self.invoice.fields().signing_pubkey
+       }
+
        /// Signs the invoice using the given function.
-       pub fn sign<F, E>(self, sign: F) -> Result<Invoice, SignError<E>>
+       ///
+       /// This is not exported to bindings users as functions aren't currently mapped.
+       pub fn sign<F, E>(self, sign: F) -> Result<Bolt12Invoice, SignError<E>>
        where
                F: FnOnce(&Message) -> Result<Signature, E>
        {
@@ -291,7 +403,7 @@ impl<'a> UnsignedInvoice<'a> {
                };
                signature_tlv_stream.write(&mut bytes).unwrap();
 
-               Ok(Invoice {
+               Ok(Bolt12Invoice {
                        bytes,
                        contents: self.invoice,
                        signature,
@@ -299,7 +411,7 @@ impl<'a> UnsignedInvoice<'a> {
        }
 }
 
-/// An `Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
+/// A `Bolt12Invoice` is a payment request, typically corresponding to an [`Offer`] or a [`Refund`].
 ///
 /// An invoice may be sent in response to an [`InvoiceRequest`] in the case of an offer or sent
 /// directly after scanning a refund. It includes all the information needed to pay a recipient.
@@ -307,25 +419,29 @@ impl<'a> UnsignedInvoice<'a> {
 /// [`Offer`]: crate::offers::offer::Offer
 /// [`Refund`]: crate::offers::refund::Refund
 /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest
-pub struct Invoice {
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
+pub struct Bolt12Invoice {
        bytes: Vec<u8>,
        contents: InvoiceContents,
        signature: Signature,
 }
 
-/// The contents of an [`Invoice`] for responding to either an [`Offer`] or a [`Refund`].
+/// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
 ///
 /// [`Offer`]: crate::offers::offer::Offer
 /// [`Refund`]: crate::offers::refund::Refund
+#[derive(Clone, Debug)]
+#[cfg_attr(test, derive(PartialEq))]
 enum InvoiceContents {
-       /// Contents for an [`Invoice`] corresponding to an [`Offer`].
+       /// Contents for an [`Bolt12Invoice`] corresponding to an [`Offer`].
        ///
        /// [`Offer`]: crate::offers::offer::Offer
        ForOffer {
                invoice_request: InvoiceRequestContents,
                fields: InvoiceFields,
        },
-       /// Contents for an [`Invoice`] corresponding to a [`Refund`].
+       /// Contents for an [`Bolt12Invoice`] corresponding to a [`Refund`].
        ///
        /// [`Refund`]: crate::offers::refund::Refund
        ForRefund {
@@ -335,8 +451,9 @@ enum InvoiceContents {
 }
 
 /// Invoice-specific fields for an `invoice` message.
+#[derive(Clone, Debug, PartialEq)]
 struct InvoiceFields {
-       payment_paths: Vec<(BlindedPath, BlindedPayInfo)>,
+       payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
        created_at: Duration,
        relative_expiry: Option<Duration>,
        payment_hash: PaymentHash,
@@ -346,13 +463,22 @@ struct InvoiceFields {
        signing_pubkey: PublicKey,
 }
 
-impl Invoice {
+impl Bolt12Invoice {
+       /// A complete description of the purpose of the originating offer or refund. 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 {
+               self.contents.description()
+       }
+
        /// Paths to the recipient originating from publicly reachable nodes, including information
        /// needed for routing payments across them.
        ///
        /// Blinded paths provide recipient privacy by obfuscating its node id. Note, however, that this
-       /// privacy is lost if a public node id is used for [`Invoice::signing_pubkey`].
-       pub fn payment_paths(&self) -> &[(BlindedPath, BlindedPayInfo)] {
+       /// privacy is lost if a public node id is used for [`Bolt12Invoice::signing_pubkey`].
+       ///
+       /// This is not exported to bindings users as slices with non-reference types cannot be ABI
+       /// matched in another language.
+       pub fn payment_paths(&self) -> &[(BlindedPayInfo, BlindedPath)] {
                &self.contents.fields().payment_paths[..]
        }
 
@@ -361,8 +487,8 @@ impl Invoice {
                self.contents.fields().created_at
        }
 
-       /// Duration since [`Invoice::created_at`] when the invoice has expired and therefore should no
-       /// longer be paid.
+       /// Duration since [`Bolt12Invoice::created_at`] when the invoice has expired and therefore
+       /// should no longer be paid.
        pub fn relative_expiry(&self) -> Duration {
                self.contents.fields().relative_expiry.unwrap_or(DEFAULT_RELATIVE_EXPIRY)
        }
@@ -450,18 +576,30 @@ impl Invoice {
                &self.contents.fields().features
        }
 
-       /// The public key used to sign invoices.
+       /// The public key corresponding to the key used to sign the invoice.
        pub fn signing_pubkey(&self) -> PublicKey {
                self.contents.fields().signing_pubkey
        }
 
-       /// Signature of the invoice using [`Invoice::signing_pubkey`].
+       /// Signature of the invoice verified using [`Bolt12Invoice::signing_pubkey`].
        pub fn signature(&self) -> Signature {
                self.signature
        }
 
+       /// Hash that was used for signing the invoice.
+       pub fn signable_hash(&self) -> [u8; 32] {
+               merkle::message_digest(SIGNATURE_TAG, &self.bytes).as_ref().clone()
+       }
+
+       /// Verifies that the invoice was for a request or refund created using the given key.
+       pub fn verify<T: secp256k1::Signing>(
+               &self, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> bool {
+               self.contents.verify(TlvStream::new(&self.bytes), key, secp_ctx)
+       }
+
        #[cfg(test)]
-       fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
+       pub(super) fn as_tlv_stream(&self) -> FullInvoiceTlvStreamRef {
                let (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream, invoice_tlv_stream) =
                        self.contents.as_tlv_stream();
                let signature_tlv_stream = SignatureTlvStreamRef {
@@ -477,7 +615,8 @@ impl InvoiceContents {
        #[cfg(feature = "std")]
        fn is_offer_or_refund_expired(&self) -> bool {
                match self {
-                       InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.offer.is_expired(),
+                       InvoiceContents::ForOffer { invoice_request, .. } =>
+                               invoice_request.inner.offer.is_expired(),
                        InvoiceContents::ForRefund { refund, .. } => refund.is_expired(),
                }
        }
@@ -489,6 +628,15 @@ impl InvoiceContents {
                }
        }
 
+       fn description(&self) -> PrintableString {
+               match self {
+                       InvoiceContents::ForOffer { invoice_request, .. } => {
+                               invoice_request.inner.offer.description()
+                       },
+                       InvoiceContents::ForRefund { refund, .. } => refund.description(),
+               }
+       }
+
        fn fields(&self) -> &InvoiceFields {
                match self {
                        InvoiceContents::ForOffer { fields, .. } => fields,
@@ -503,6 +651,41 @@ impl InvoiceContents {
                }
        }
 
+       fn verify<T: secp256k1::Signing>(
+               &self, tlv_stream: TlvStream<'_>, key: &ExpandedKey, secp_ctx: &Secp256k1<T>
+       ) -> bool {
+               let offer_records = tlv_stream.clone().range(OFFER_TYPES);
+               let invreq_records = tlv_stream.range(INVOICE_REQUEST_TYPES).filter(|record| {
+                       match record.r#type {
+                               PAYER_METADATA_TYPE => false, // Should be outside range
+                               INVOICE_REQUEST_PAYER_ID_TYPE => !self.derives_keys(),
+                               _ => true,
+                       }
+               });
+               let tlv_stream = offer_records.chain(invreq_records);
+
+               let (metadata, payer_id, iv_bytes) = match self {
+                       InvoiceContents::ForOffer { invoice_request, .. } => {
+                               (invoice_request.metadata(), invoice_request.payer_id(), INVOICE_REQUEST_IV_BYTES)
+                       },
+                       InvoiceContents::ForRefund { refund, .. } => {
+                               (refund.metadata(), refund.payer_id(), REFUND_IV_BYTES)
+                       },
+               };
+
+               match signer::verify_metadata(metadata, key, iv_bytes, payer_id, tlv_stream, secp_ctx) {
+                       Ok(_) => true,
+                       Err(()) => false,
+               }
+       }
+
+       fn derives_keys(&self) -> bool {
+               match self {
+                       InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.derives_keys(),
+                       InvoiceContents::ForRefund { refund, .. } => refund.derives_keys(),
+               }
+       }
+
        fn as_tlv_stream(&self) -> PartialInvoiceTlvStreamRef {
                let (payer, offer, invoice_request) = match self {
                        InvoiceContents::ForOffer { invoice_request, .. } => invoice_request.as_tlv_stream(),
@@ -522,8 +705,8 @@ impl InvoiceFields {
                };
 
                InvoiceTlvStreamRef {
-                       paths: Some(Iterable(self.payment_paths.iter().map(|(path, _)| path))),
-                       blindedpay: Some(Iterable(self.payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                       paths: Some(Iterable(self.payment_paths.iter().map(|(_, path)| path))),
+                       blindedpay: Some(Iterable(self.payment_paths.iter().map(|(payinfo, _)| payinfo))),
                        created_at: Some(self.created_at.as_secs()),
                        relative_expiry: self.relative_expiry.map(|duration| duration.as_secs() as u32),
                        payment_hash: Some(&self.payment_hash),
@@ -535,7 +718,7 @@ impl InvoiceFields {
        }
 }
 
-impl Writeable for Invoice {
+impl Writeable for Bolt12Invoice {
        fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
                WithoutLength(&self.bytes).write(writer)
        }
@@ -547,12 +730,12 @@ impl Writeable for InvoiceContents {
        }
 }
 
-impl TryFrom<Vec<u8>> for Invoice {
-       type Error = ParseError;
+impl TryFrom<Vec<u8>> for Bolt12Invoice {
+       type Error = Bolt12ParseError;
 
        fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
                let parsed_invoice = ParsedMessage::<FullInvoiceTlvStream>::try_from(bytes)?;
-               Invoice::try_from(parsed_invoice)
+               Bolt12Invoice::try_from(parsed_invoice)
        }
 }
 
@@ -569,24 +752,42 @@ tlv_stream!(InvoiceTlvStream, InvoiceTlvStreamRef, 160..240, {
 });
 
 type BlindedPathIter<'a> = core::iter::Map<
-       core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
-       for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPath,
+       core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+       for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPath,
 >;
 
 type BlindedPayInfoIter<'a> = core::iter::Map<
-       core::slice::Iter<'a, (BlindedPath, BlindedPayInfo)>,
-       for<'r> fn(&'r (BlindedPath, BlindedPayInfo)) -> &'r BlindedPayInfo,
+       core::slice::Iter<'a, (BlindedPayInfo, BlindedPath)>,
+       for<'r> fn(&'r (BlindedPayInfo, BlindedPath)) -> &'r BlindedPayInfo,
 >;
 
 /// Information needed to route a payment across a [`BlindedPath`].
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
 pub struct BlindedPayInfo {
-       fee_base_msat: u32,
-       fee_proportional_millionths: u32,
-       cltv_expiry_delta: u16,
-       htlc_minimum_msat: u64,
-       htlc_maximum_msat: u64,
-       features: BlindedHopFeatures,
+       /// Base fee charged (in millisatoshi) for the entire blinded path.
+       pub fee_base_msat: u32,
+
+       /// Liquidity fee charged (in millionths of the amount transferred) for the entire blinded path
+       /// (i.e., 10,000 is 1%).
+       pub fee_proportional_millionths: u32,
+
+       /// Number of blocks subtracted from an incoming HTLC's `cltv_expiry` for the entire blinded
+       /// path.
+       pub cltv_expiry_delta: u16,
+
+       /// The minimum HTLC value (in millisatoshi) that is acceptable to all channel peers on the
+       /// blinded path from the introduction node to the recipient, accounting for any fees, i.e., as
+       /// seen by the recipient.
+       pub htlc_minimum_msat: u64,
+
+       /// The maximum HTLC value (in millisatoshi) that is acceptable to all channel peers on the
+       /// blinded path from the introduction node to the recipient, accounting for any fees, i.e., as
+       /// seen by the recipient.
+       pub htlc_maximum_msat: u64,
+
+       /// Features set in `encrypted_data_tlv` for the `encrypted_recipient_data` TLV record in an
+       /// onion payload.
+       pub features: BlindedHopFeatures,
 }
 
 impl_writeable!(BlindedPayInfo, {
@@ -641,8 +842,8 @@ type PartialInvoiceTlvStreamRef<'a> = (
        InvoiceTlvStreamRef<'a>,
 );
 
-impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Invoice {
-       type Error = ParseError;
+impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
+       type Error = Bolt12ParseError;
 
        fn try_from(invoice: ParsedMessage<FullInvoiceTlvStream>) -> Result<Self, Self::Error> {
                let ParsedMessage { bytes, tlv_stream } = invoice;
@@ -655,18 +856,18 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Invoice {
                )?;
 
                let signature = match signature {
-                       None => return Err(ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+                       None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
                        Some(signature) => signature,
                };
                let pubkey = contents.fields().signing_pubkey;
                merkle::verify_signature(&signature, SIGNATURE_TAG, &bytes, pubkey)?;
 
-               Ok(Invoice { bytes, contents, signature })
+               Ok(Bolt12Invoice { bytes, contents, signature })
        }
 }
 
 impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
-       type Error = SemanticError;
+       type Error = Bolt12SemanticError;
 
        fn try_from(tlv_stream: PartialInvoiceTlvStream) -> Result<Self, Self::Error> {
                let (
@@ -679,20 +880,20 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                        },
                ) = tlv_stream;
 
-               let payment_paths = match (paths, blindedpay) {
-                       (None, _) => return Err(SemanticError::MissingPaths),
-                       (_, None) => return Err(SemanticError::InvalidPayInfo),
-                       (Some(paths), _) if paths.is_empty() => return Err(SemanticError::MissingPaths),
-                       (Some(paths), Some(blindedpay)) if paths.len() != blindedpay.len() => {
-                               return Err(SemanticError::InvalidPayInfo);
+               let payment_paths = match (blindedpay, paths) {
+                       (_, None) => return Err(Bolt12SemanticError::MissingPaths),
+                       (None, _) => return Err(Bolt12SemanticError::InvalidPayInfo),
+                       (_, Some(paths)) if paths.is_empty() => return Err(Bolt12SemanticError::MissingPaths),
+                       (Some(blindedpay), Some(paths)) if paths.len() != blindedpay.len() => {
+                               return Err(Bolt12SemanticError::InvalidPayInfo);
                        },
-                       (Some(paths), Some(blindedpay)) => {
-                               paths.into_iter().zip(blindedpay.into_iter()).collect::<Vec<_>>()
+                       (Some(blindedpay), Some(paths)) => {
+                               blindedpay.into_iter().zip(paths.into_iter()).collect::<Vec<_>>()
                        },
                };
 
                let created_at = match created_at {
-                       None => return Err(SemanticError::MissingCreationTime),
+                       None => return Err(Bolt12SemanticError::MissingCreationTime),
                        Some(timestamp) => Duration::from_secs(timestamp),
                };
 
@@ -701,19 +902,19 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                        .map(Duration::from_secs);
 
                let payment_hash = match payment_hash {
-                       None => return Err(SemanticError::MissingPaymentHash),
+                       None => return Err(Bolt12SemanticError::MissingPaymentHash),
                        Some(payment_hash) => payment_hash,
                };
 
                let amount_msats = match amount {
-                       None => return Err(SemanticError::MissingAmount),
+                       None => return Err(Bolt12SemanticError::MissingAmount),
                        Some(amount) => amount,
                };
 
                let features = features.unwrap_or_else(Bolt12InvoiceFeatures::empty);
 
                let signing_pubkey = match node_id {
-                       None => return Err(SemanticError::MissingSigningPubkey),
+                       None => return Err(Bolt12SemanticError::MissingSigningPubkey),
                        Some(node_id) => node_id,
                };
 
@@ -725,7 +926,7 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
                match offer_tlv_stream.node_id {
                        Some(expected_signing_pubkey) => {
                                if fields.signing_pubkey != expected_signing_pubkey {
-                                       return Err(SemanticError::InvalidSigningPubkey);
+                                       return Err(Bolt12SemanticError::InvalidSigningPubkey);
                                }
 
                                let invoice_request = InvoiceRequestContents::try_from(
@@ -745,67 +946,30 @@ impl TryFrom<PartialInvoiceTlvStream> for InvoiceContents {
 
 #[cfg(test)]
 mod tests {
-       use super::{DEFAULT_RELATIVE_EXPIRY, BlindedPayInfo, FallbackAddress, FullInvoiceTlvStreamRef, Invoice, InvoiceTlvStreamRef, SIGNATURE_TAG};
+       use super::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, FallbackAddress, FullInvoiceTlvStreamRef, InvoiceTlvStreamRef, SIGNATURE_TAG};
 
        use bitcoin::blockdata::script::Script;
        use bitcoin::hashes::Hash;
        use bitcoin::network::constants::Network;
-       use bitcoin::secp256k1::{KeyPair, Message, PublicKey, Secp256k1, SecretKey, XOnlyPublicKey, self};
-       use bitcoin::secp256k1::schnorr::Signature;
+       use bitcoin::secp256k1::{Message, Secp256k1, XOnlyPublicKey, self};
        use bitcoin::util::address::{Address, Payload, WitnessVersion};
        use bitcoin::util::schnorr::TweakedPublicKey;
-       use core::convert::{Infallible, TryFrom};
+       use core::convert::TryFrom;
        use core::time::Duration;
-       use crate::ln::PaymentHash;
+       use crate::blinded_path::{BlindedHop, BlindedPath};
+       use crate::sign::KeyMaterial;
+       use crate::ln::features::Bolt12InvoiceFeatures;
+       use crate::ln::inbound_payment::ExpandedKey;
        use crate::ln::msgs::DecodeError;
-       use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
        use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
        use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
-       use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
-       use crate::offers::parse::{ParseError, SemanticError};
+       use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
+       use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError};
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::refund::RefundBuilder;
-       use crate::onion_message::{BlindedHop, BlindedPath};
+       use crate::offers::test_utils::*;
        use crate::util::ser::{BigSize, Iterable, Writeable};
-
-       fn payer_keys() -> KeyPair {
-               let secp_ctx = Secp256k1::new();
-               KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap())
-       }
-
-       fn payer_sign(digest: &Message) -> Result<Signature, Infallible> {
-               let secp_ctx = Secp256k1::new();
-               let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap());
-               Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
-       }
-
-       fn payer_pubkey() -> PublicKey {
-               payer_keys().public_key()
-       }
-
-       fn recipient_keys() -> KeyPair {
-               let secp_ctx = Secp256k1::new();
-               KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap())
-       }
-
-       fn recipient_sign(digest: &Message) -> Result<Signature, Infallible> {
-               let secp_ctx = Secp256k1::new();
-               let keys = KeyPair::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[43; 32]).unwrap());
-               Ok(secp_ctx.sign_schnorr_no_aux_rand(digest, &keys))
-       }
-
-       fn recipient_pubkey() -> PublicKey {
-               recipient_keys().public_key()
-       }
-
-       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()
-       }
+       use crate::util::string::PrintableString;
 
        trait ToBytes {
                fn to_bytes(&self) -> Vec<u8>;
@@ -823,58 +987,6 @@ mod tests {
                }
        }
 
-       fn payment_paths() -> Vec<(BlindedPath, BlindedPayInfo)> {
-               let paths = vec![
-                       BlindedPath {
-                               introduction_node_id: pubkey(40),
-                               blinding_point: pubkey(41),
-                               blinded_hops: vec![
-                                       BlindedHop { blinded_node_id: pubkey(43), encrypted_payload: vec![0; 43] },
-                                       BlindedHop { blinded_node_id: pubkey(44), encrypted_payload: vec![0; 44] },
-                               ],
-                       },
-                       BlindedPath {
-                               introduction_node_id: pubkey(40),
-                               blinding_point: pubkey(41),
-                               blinded_hops: vec![
-                                       BlindedHop { blinded_node_id: pubkey(45), encrypted_payload: vec![0; 45] },
-                                       BlindedHop { blinded_node_id: pubkey(46), encrypted_payload: vec![0; 46] },
-                               ],
-                       },
-               ];
-
-               let payinfo = vec![
-                       BlindedPayInfo {
-                               fee_base_msat: 1,
-                               fee_proportional_millionths: 1_000,
-                               cltv_expiry_delta: 42,
-                               htlc_minimum_msat: 100,
-                               htlc_maximum_msat: 1_000_000_000_000,
-                               features: BlindedHopFeatures::empty(),
-                       },
-                       BlindedPayInfo {
-                               fee_base_msat: 1,
-                               fee_proportional_millionths: 1_000,
-                               cltv_expiry_delta: 42,
-                               htlc_minimum_msat: 100,
-                               htlc_maximum_msat: 1_000_000_000_000,
-                               features: BlindedHopFeatures::empty(),
-                       },
-               ];
-
-               paths.into_iter().zip(payinfo.into_iter()).collect()
-       }
-
-       fn payment_hash() -> PaymentHash {
-               PaymentHash([42; 32])
-       }
-
-       fn now() -> Duration {
-               std::time::SystemTime::now()
-                       .duration_since(std::time::SystemTime::UNIX_EPOCH)
-                       .expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH")
-       }
-
        #[test]
        fn builds_invoice_for_offer_with_defaults() {
                let payment_paths = payment_paths();
@@ -894,6 +1006,7 @@ mod tests {
                invoice.write(&mut buffer).unwrap();
 
                assert_eq!(invoice.bytes, buffer.as_slice());
+               assert_eq!(invoice.description(), PrintableString("foo"));
                assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
                assert_eq!(invoice.created_at(), now);
                assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -910,6 +1023,11 @@ mod tests {
                        ).is_ok()
                );
 
+               let digest = Message::from_slice(&invoice.signable_hash()).unwrap();
+               let pubkey = recipient_pubkey().into();
+               let secp_ctx = Secp256k1::verification_only();
+               assert!(secp_ctx.verify_schnorr(&invoice.signature, &digest, &pubkey).is_ok());
+
                assert_eq!(
                        invoice.as_tlv_stream(),
                        (
@@ -936,8 +1054,8 @@ mod tests {
                                        payer_note: None,
                                },
                                InvoiceTlvStreamRef {
-                                       paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
-                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                                       paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
                                        created_at: Some(now.as_secs()),
                                        relative_expiry: None,
                                        payment_hash: Some(&payment_hash),
@@ -950,7 +1068,7 @@ mod tests {
                        ),
                );
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
        }
@@ -971,6 +1089,7 @@ mod tests {
                invoice.write(&mut buffer).unwrap();
 
                assert_eq!(invoice.bytes, buffer.as_slice());
+               assert_eq!(invoice.description(), PrintableString("foo"));
                assert_eq!(invoice.payment_paths(), payment_paths.as_slice());
                assert_eq!(invoice.created_at(), now);
                assert_eq!(invoice.relative_expiry(), DEFAULT_RELATIVE_EXPIRY);
@@ -1013,8 +1132,8 @@ mod tests {
                                        payer_note: None,
                                },
                                InvoiceTlvStreamRef {
-                                       paths: Some(Iterable(payment_paths.iter().map(|(path, _)| path))),
-                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo))),
+                                       paths: Some(Iterable(payment_paths.iter().map(|(_, path)| path))),
+                                       blindedpay: Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo))),
                                        created_at: Some(now.as_secs()),
                                        relative_expiry: None,
                                        payment_hash: Some(&payment_hash),
@@ -1027,11 +1146,47 @@ mod tests {
                        ),
                );
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
        }
 
+       #[cfg(feature = "std")]
+       #[test]
+       fn builds_invoice_from_offer_with_expiration() {
+               let future_expiry = Duration::from_secs(u64::max_value());
+               let past_expiry = Duration::from_secs(0);
+
+               if let Err(e) = OfferBuilder::new("foo".into(), recipient_pubkey())
+                       .amount_msats(1000)
+                       .absolute_expiry(future_expiry)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap()
+                       .respond_with(payment_paths(), payment_hash())
+                       .unwrap()
+                       .build()
+               {
+                       panic!("error building invoice: {:?}", e);
+               }
+
+               match OfferBuilder::new("foo".into(), recipient_pubkey())
+                       .amount_msats(1000)
+                       .absolute_expiry(past_expiry)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build_unchecked()
+                       .sign(payer_sign).unwrap()
+                       .respond_with(payment_paths(), payment_hash())
+                       .unwrap()
+                       .build()
+               {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
+               }
+       }
+
        #[cfg(feature = "std")]
        #[test]
        fn builds_invoice_from_refund_with_expiration() {
@@ -1056,7 +1211,88 @@ mod tests {
                        .build()
                {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, SemanticError::AlreadyExpired),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::AlreadyExpired),
+               }
+       }
+
+       #[test]
+       fn builds_invoice_from_offer_using_derived_keys() {
+               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();
+               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+
+               if let Err(e) = invoice_request
+                       .verify_and_respond_using_derived_keys_no_std(
+                               payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+                       )
+                       .unwrap()
+                       .build_and_sign(&secp_ctx)
+               {
+                       panic!("error building invoice: {:?}", e);
+               }
+
+               let expanded_key = ExpandedKey::new(&KeyMaterial([41; 32]));
+               match invoice_request.verify_and_respond_using_derived_keys_no_std(
+                       payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+               ) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
+               }
+
+               let desc = "foo".to_string();
+               let offer = OfferBuilder
+                       ::deriving_signing_pubkey(desc, node_id, &expanded_key, &entropy, &secp_ctx)
+                       .amount_msats(1000)
+                       .build().unwrap();
+               let invoice_request = offer.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap();
+
+               match invoice_request.verify_and_respond_using_derived_keys_no_std(
+                       payment_paths(), payment_hash(), now(), &expanded_key, &secp_ctx
+               ) {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidMetadata),
+               }
+       }
+
+       #[test]
+       fn builds_invoice_from_refund_using_derived_keys() {
+               let expanded_key = ExpandedKey::new(&KeyMaterial([42; 32]));
+               let entropy = FixedEntropy {};
+               let secp_ctx = Secp256k1::new();
+
+               let refund = RefundBuilder::new("foo".into(), vec![1; 32], payer_pubkey(), 1000).unwrap()
+                       .build().unwrap();
+
+               if let Err(e) = refund
+                       .respond_using_derived_keys_no_std(
+                               payment_paths(), payment_hash(), now(), &expanded_key, &entropy
+                       )
+                       .unwrap()
+                       .build_and_sign(&secp_ctx)
+               {
+                       panic!("error building invoice: {:?}", e);
                }
        }
 
@@ -1115,6 +1351,38 @@ mod tests {
                assert_eq!(tlv_stream.amount, Some(1001));
        }
 
+       #[test]
+       fn builds_invoice_with_quantity_from_request() {
+               let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
+                       .amount_msats(1000)
+                       .supported_quantity(Quantity::Unbounded)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .quantity(2).unwrap()
+                       .build().unwrap()
+                       .sign(payer_sign).unwrap()
+                       .respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
+                       .build().unwrap()
+                       .sign(recipient_sign).unwrap();
+               let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
+               assert_eq!(invoice.amount_msats(), 2000);
+               assert_eq!(tlv_stream.amount, Some(2000));
+
+               match OfferBuilder::new("foo".into(), recipient_pubkey())
+                       .amount_msats(1000)
+                       .supported_quantity(Quantity::Unbounded)
+                       .build().unwrap()
+                       .request_invoice(vec![1; 32], payer_pubkey()).unwrap()
+                       .quantity(u64::max_value()).unwrap()
+                       .build_unchecked()
+                       .sign(payer_sign).unwrap()
+                       .respond_with_no_std(payment_paths(), payment_hash(), now())
+               {
+                       Ok(_) => panic!("expected error"),
+                       Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount),
+               }
+       }
+
        #[test]
        fn builds_invoice_with_fallback_address() {
                let script = Script::new();
@@ -1228,43 +1496,43 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
 
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.paths = None;
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)),
                }
 
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.blindedpay = None;
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidPayInfo)),
                }
 
                let empty_payment_paths = vec![];
                let mut tlv_stream = invoice.as_tlv_stream();
-               tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(path, _)| path)));
+               tlv_stream.3.paths = Some(Iterable(empty_payment_paths.iter().map(|(_, path)| path)));
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaths)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaths)),
                }
 
                let mut payment_paths = payment_paths();
                payment_paths.pop();
                let mut tlv_stream = invoice.as_tlv_stream();
-               tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(_, payinfo)| payinfo)));
+               tlv_stream.3.blindedpay = Some(Iterable(payment_paths.iter().map(|(payinfo, _)| payinfo)));
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidPayInfo)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidPayInfo)),
                }
        }
 
@@ -1283,17 +1551,17 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
 
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.created_at = None;
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingCreationTime));
+                               assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingCreationTime));
                        },
                }
        }
@@ -1314,7 +1582,7 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               match Invoice::try_from(buffer) {
+               match Bolt12Invoice::try_from(buffer) {
                        Ok(invoice) => assert_eq!(invoice.relative_expiry(), Duration::from_secs(3600)),
                        Err(e) => panic!("error parsing invoice: {:?}", e),
                }
@@ -1335,17 +1603,17 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
 
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.payment_hash = None;
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingPaymentHash));
+                               assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingPaymentHash));
                        },
                }
        }
@@ -1365,16 +1633,16 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
 
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.amount = None;
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingAmount)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingAmount)),
                }
        }
 
@@ -1394,7 +1662,7 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               match Invoice::try_from(buffer) {
+               match Bolt12Invoice::try_from(buffer) {
                        Ok(invoice) => {
                                let mut features = Bolt12InvoiceFeatures::empty();
                                features.set_basic_mpp_optional();
@@ -1426,7 +1694,7 @@ mod tests {
                        .build().unwrap();
 
                // Only standard addresses will be included.
-               let mut fallbacks = unsigned_invoice.invoice.fields_mut().fallbacks.as_mut().unwrap();
+               let fallbacks = unsigned_invoice.invoice.fields_mut().fallbacks.as_mut().unwrap();
                // Non-standard addresses
                fallbacks.push(FallbackAddress { version: 1, program: vec![0u8; 41] });
                fallbacks.push(FallbackAddress { version: 2, program: vec![0u8; 1] });
@@ -1439,7 +1707,7 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               match Invoice::try_from(buffer) {
+               match Bolt12Invoice::try_from(buffer) {
                        Ok(invoice) => {
                                assert_eq!(
                                        invoice.fallbacks(),
@@ -1483,17 +1751,17 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               if let Err(e) = Invoice::try_from(buffer) {
+               if let Err(e) = Bolt12Invoice::try_from(buffer) {
                        panic!("error parsing invoice: {:?}", e);
                }
 
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.node_id = None;
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSigningPubkey));
+                               assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSigningPubkey));
                        },
                }
 
@@ -1501,10 +1769,10 @@ mod tests {
                let mut tlv_stream = invoice.as_tlv_stream();
                tlv_stream.3.node_id = Some(&invalid_pubkey);
 
-               match Invoice::try_from(tlv_stream.to_bytes()) {
+               match Bolt12Invoice::try_from(tlv_stream.to_bytes()) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSemantics(SemanticError::InvalidSigningPubkey));
+                               assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::InvalidSigningPubkey));
                        },
                }
        }
@@ -1523,9 +1791,9 @@ mod tests {
                        .invoice
                        .write(&mut buffer).unwrap();
 
-               match Invoice::try_from(buffer) {
+               match Bolt12Invoice::try_from(buffer) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::InvalidSemantics(SemanticError::MissingSignature)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
                }
        }
 
@@ -1546,10 +1814,10 @@ mod tests {
                let mut buffer = Vec::new();
                invoice.write(&mut buffer).unwrap();
 
-               match Invoice::try_from(buffer) {
+               match Bolt12Invoice::try_from(buffer) {
                        Ok(_) => panic!("expected error"),
                        Err(e) => {
-                               assert_eq!(e, ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
+                               assert_eq!(e, Bolt12ParseError::InvalidSignature(secp256k1::Error::InvalidSignature));
                        },
                }
        }
@@ -1572,9 +1840,9 @@ mod tests {
                BigSize(32).write(&mut encoded_invoice).unwrap();
                [42u8; 32].write(&mut encoded_invoice).unwrap();
 
-               match Invoice::try_from(encoded_invoice) {
+               match Bolt12Invoice::try_from(encoded_invoice) {
                        Ok(_) => panic!("expected error"),
-                       Err(e) => assert_eq!(e, ParseError::Decode(DecodeError::InvalidValue)),
+                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
                }
        }
 }