Move bech32 parsing tests to the parse module
[rust-lightning] / lightning / src / offers / offer.rs
index e0bc63e8b2b8109e0b9243a1a32ecfb0a5a3d073..64e413679b672c3c34c8d9c4d33e85e965a12a6d 100644 (file)
@@ -13,6 +13,8 @@
 //! 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.
 //!
+//! # Example
+//!
 //! ```
 //! extern crate bitcoin;
 //! extern crate core;
 //! # Ok(())
 //! # }
 //! ```
+//!
+//! # Note
+//!
+//! If constructing an [`Offer`] for use with a [`ChannelManager`], use
+//! [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+//!
+//! [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+//! [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
 
 use bitcoin::blockdata::constants::ChainHash;
 use bitcoin::network::constants::Network;
@@ -132,6 +142,14 @@ impl<'a> OfferBuilder<'a, ExplicitMetadata, secp256k1::SignOnly> {
        /// while the offer is valid.
        ///
        /// Use a different pubkey per offer to avoid correlating offers.
+       ///
+       /// # Note
+       ///
+       /// If constructing an [`Offer`] for use with a [`ChannelManager`], use
+       /// [`ChannelManager::create_offer_builder`] instead of [`OfferBuilder::new`].
+       ///
+       /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
+       /// [`ChannelManager::create_offer_builder`]: crate::ln::channelmanager::ChannelManager::create_offer_builder
        pub fn new(description: String, signing_pubkey: PublicKey) -> Self {
                OfferBuilder {
                        offer: OfferContents {
@@ -191,9 +209,18 @@ impl<'a, M: MetadataStrategy, T: secp256k1::Signing> OfferBuilder<'a, M, T> {
        /// See [`Offer::chains`] on how this relates to the payment currency.
        ///
        /// Successive calls to this method will add another chain hash.
-       pub fn chain(mut self, network: Network) -> Self {
+       pub fn chain(self, network: Network) -> Self {
+               self.chain_hash(ChainHash::using_genesis_block(network))
+       }
+
+       /// Adds the [`ChainHash`] to [`Offer::chains`]. If not called, the chain hash of
+       /// [`Network::Bitcoin`] is assumed to be the only one supported.
+       ///
+       /// See [`Offer::chains`] on how this relates to the payment currency.
+       ///
+       /// Successive calls to this method will add another chain hash.
+       pub(crate) fn chain_hash(mut self, chain: ChainHash) -> Self {
                let chains = self.offer.chains.get_or_insert_with(Vec::new);
-               let chain = ChainHash::using_genesis_block(network);
                if !chains.contains(&chain) {
                        chains.push(chain);
                }
@@ -366,7 +393,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
        /// 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<$crate::bitcoin::blockdata::constants::ChainHash> {
+       pub fn chains(&$self) -> Vec<bitcoin::blockdata::constants::ChainHash> {
                $contents.chains()
        }
 
@@ -418,7 +445,7 @@ macro_rules! offer_accessors { ($self: ident, $contents: expr) => {
        }
 
        /// The public key used by the recipient to sign invoices.
-       pub fn signing_pubkey(&$self) -> $crate::bitcoin::secp256k1::PublicKey {
+       pub fn signing_pubkey(&$self) -> bitcoin::secp256k1::PublicKey {
                $contents.signing_pubkey()
        }
 } }
@@ -582,13 +609,16 @@ impl OfferContents {
 
        #[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,
-               }
+               SystemTime::UNIX_EPOCH
+                       .elapsed()
+                       .map(|duration_since_epoch| self.is_expired_no_std(duration_since_epoch))
+                       .unwrap_or(false)
+       }
+
+       pub(super) fn is_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+               self.absolute_expiry
+                       .map(|absolute_expiry| duration_since_epoch > absolute_expiry)
+                       .unwrap_or(false)
        }
 
        pub fn issuer(&self) -> Option<PrintableString> {
@@ -1479,81 +1509,3 @@ mod tests {
                }
        }
 }
-
-#[cfg(test)]
-mod bech32_tests {
-       use super::{Bolt12ParseError, Offer};
-       use bitcoin::bech32;
-       use crate::ln::msgs::DecodeError;
-
-       #[test]
-       fn encodes_offer_as_bech32_without_checksum() {
-               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);
-       }
-
-       #[test]
-       fn parses_bech32_encoded_offers() {
-               let offers = [
-                       // BOLT 12 test vectors
-                       "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>() {
-                               panic!("Invalid offer ({:?}): {}", e, encoded_offer);
-                       }
-               }
-       }
-
-       #[test]
-       fn fails_parsing_bech32_encoded_offers_with_invalid_continuations() {
-               let offers = [
-                       // BOLT 12 test vectors
-                       "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, Bolt12ParseError::InvalidContinuation),
-                       }
-               }
-
-       }
-
-       #[test]
-       fn fails_parsing_bech32_encoded_offer_with_invalid_hrp() {
-               let encoded_offer = "lni1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg";
-               match encoded_offer.parse::<Offer>() {
-                       Ok(_) => panic!("Valid offer: {}", encoded_offer),
-                       Err(e) => assert_eq!(e, Bolt12ParseError::InvalidBech32Hrp),
-               }
-       }
-
-       #[test]
-       fn fails_parsing_bech32_encoded_offer_with_invalid_bech32_data() {
-               let encoded_offer = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxo";
-               match encoded_offer.parse::<Offer>() {
-                       Ok(_) => panic!("Valid offer: {}", encoded_offer),
-                       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 = "lno1pqps7sjqpgtyzm3qv4uxzmtsd3jjqer9wd3hy6tsw35k7msjzfpy7nz5yqcnygrfdej82um5wf5k2uckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxgqqqqq";
-               match encoded_offer.parse::<Offer>() {
-                       Ok(_) => panic!("Valid offer: {}", encoded_offer),
-                       Err(e) => assert_eq!(e, Bolt12ParseError::Decode(DecodeError::InvalidValue)),
-               }
-       }
-}