Merge pull request #1163 from TheBlueMatt/2021-11-support-insecure-counterparty
[rust-lightning] / lightning-invoice / src / lib.rs
index bf92dba573c81b04176b0eeb5133bc7262685d2d..04ba08bd8552c34379576b59047de719ef200ae3 100644 (file)
 //!   * For parsing use `str::parse::<Invoice>(&self)` (see the docs of `impl FromStr for Invoice`)
 //!   * For constructing invoices use the `InvoiceBuilder`
 //!   * For serializing invoices use the `Display`/`ToString` traits
+pub mod payment;
 pub mod utils;
 
 extern crate bech32;
 extern crate bitcoin_hashes;
-extern crate lightning;
+#[macro_use] extern crate lightning;
 extern crate num_traits;
 extern crate secp256k1;
 
@@ -127,6 +128,7 @@ pub fn check_platform() {
 ///
 /// ```
 /// extern crate secp256k1;
+/// extern crate lightning;
 /// extern crate lightning_invoice;
 /// extern crate bitcoin_hashes;
 ///
@@ -136,6 +138,8 @@ pub fn check_platform() {
 /// use secp256k1::Secp256k1;
 /// use secp256k1::key::SecretKey;
 ///
+/// use lightning::ln::PaymentSecret;
+///
 /// use lightning_invoice::{Currency, InvoiceBuilder};
 ///
 /// # fn main() {
@@ -148,10 +152,12 @@ pub fn check_platform() {
 ///    ).unwrap();
 ///
 /// let payment_hash = sha256::Hash::from_slice(&[0; 32][..]).unwrap();
+/// let payment_secret = PaymentSecret([42u8; 32]);
 ///
 /// let invoice = InvoiceBuilder::new(Currency::Bitcoin)
 ///    .description("Coins pls!".into())
 ///    .payment_hash(payment_hash)
+///    .payment_secret(payment_secret)
 ///    .current_timestamp()
 ///    .min_final_cltv_expiry(144)
 ///    .build_signed(|hash| {
@@ -373,7 +379,8 @@ pub enum TaggedField {
 
 /// SHA-256 hash
 #[derive(Clone, Debug, Hash, Eq, PartialEq)]
-pub struct Sha256(pub sha256::Hash);
+pub struct Sha256(/// (C-not exported) as the native hash types are not currently mapped
+       pub sha256::Hash);
 
 /// Description string
 ///
@@ -634,7 +641,7 @@ impl<D: tb::Bool, H: tb::Bool, T: tb::Bool, C: tb::Bool> InvoiceBuilder<D, H, T,
        }
 }
 
-impl<S: tb::Bool> InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, S> {
+impl InvoiceBuilder<tb::True, tb::True, tb::True, tb::True, tb::True> {
        /// Builds and signs an invoice using the supplied `sign_function`. This function MAY NOT fail
        /// and MUST produce a recoverable signature valid for the given hash and if applicable also for
        /// the included payee public key.
@@ -1018,6 +1025,24 @@ impl Invoice {
                        return  Err(SemanticError::MultipleDescriptions);
                }
 
+               self.check_payment_secret()?;
+
+               Ok(())
+       }
+
+       /// Checks that there is exactly one payment secret field
+       fn check_payment_secret(&self) -> Result<(), SemanticError> {
+               // "A writer MUST include exactly one `s` field."
+               let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
+                       TaggedField::PaymentSecret(_) => true,
+                       _ => false,
+               }).count();
+               if payment_secret_count < 1 {
+                       return Err(SemanticError::NoPaymentSecret);
+               } else if payment_secret_count > 1 {
+                       return Err(SemanticError::MultiplePaymentSecrets);
+               }
+
                Ok(())
        }
 
@@ -1033,32 +1058,21 @@ impl Invoice {
 
        /// Check that feature bits are set as required
        fn check_feature_bits(&self) -> Result<(), SemanticError> {
-               // "If the payment_secret feature is set, MUST include exactly one s field."
-               let payment_secret_count = self.tagged_fields().filter(|&tf| match *tf {
-                       TaggedField::PaymentSecret(_) => true,
-                       _ => false,
-               }).count();
-               if payment_secret_count > 1 {
-                       return Err(SemanticError::MultiplePaymentSecrets);
-               }
+               self.check_payment_secret()?;
 
                // "A writer MUST set an s field if and only if the payment_secret feature is set."
-               let has_payment_secret = payment_secret_count == 1;
+               // (this requirement has been since removed, and we now require the payment secret
+               // feature bit always).
                let features = self.tagged_fields().find(|&tf| match *tf {
                        TaggedField::Features(_) => true,
                        _ => false,
                });
                match features {
-                       None if has_payment_secret => Err(SemanticError::InvalidFeatures),
-                       None => Ok(()),
+                       None => Err(SemanticError::InvalidFeatures),
                        Some(TaggedField::Features(features)) => {
                                if features.requires_unknown_bits() {
                                        Err(SemanticError::InvalidFeatures)
-                               } else if features.supports_payment_secret() && has_payment_secret {
-                                       Ok(())
-                               } else if has_payment_secret {
-                                       Err(SemanticError::InvalidFeatures)
-                               } else if features.supports_payment_secret() {
+                               } else if !features.supports_payment_secret() {
                                        Err(SemanticError::InvalidFeatures)
                                } else {
                                        Ok(())
@@ -1154,8 +1168,8 @@ impl Invoice {
        }
 
        /// Get the payment secret if one was included in the invoice
-       pub fn payment_secret(&self) -> Option<&PaymentSecret> {
-               self.signed_invoice.payment_secret()
+       pub fn payment_secret(&self) -> &PaymentSecret {
+               self.signed_invoice.payment_secret().expect("was checked by constructor")
        }
 
        /// Get the invoice features if they were included in the invoice
@@ -1175,6 +1189,19 @@ impl Invoice {
                        .unwrap_or(Duration::from_secs(DEFAULT_EXPIRY_TIME))
        }
 
+       /// Returns whether the invoice has expired.
+       pub fn is_expired(&self) -> bool {
+               Self::is_expired_from_epoch(self.timestamp(), self.expiry_time())
+       }
+
+       /// Returns whether the expiry time from the given epoch has passed.
+       pub(crate) fn is_expired_from_epoch(epoch: &SystemTime, expiry_time: Duration) -> bool {
+               match epoch.elapsed() {
+                       Ok(elapsed) => elapsed > expiry_time,
+                       Err(_) => false,
+               }
+       }
+
        /// Returns the invoice's `min_final_cltv_expiry` time, if present, otherwise
        /// [`DEFAULT_MIN_FINAL_CLTV_EXPIRY`].
        pub fn min_final_cltv_expiry(&self) -> u64 {
@@ -1196,10 +1223,10 @@ impl Invoice {
        }
 
        /// Returns a list of all routes included in the invoice as the underlying hints
-       pub fn route_hints(&self) -> Vec<&RouteHint> {
+       pub fn route_hints(&self) -> Vec<RouteHint> {
                find_all_extract!(
                        self.signed_invoice.known_tagged_fields(), TaggedField::PrivateRoute(ref x), x
-               ).map(|route| &**route).collect()
+               ).map(|route| (**route).clone()).collect()
        }
 
        /// Returns the currency for which the invoice was issued
@@ -1207,8 +1234,13 @@ impl Invoice {
                self.signed_invoice.currency()
        }
 
+       /// Returns the amount if specified in the invoice as millisatoshis.
+       pub fn amount_milli_satoshis(&self) -> Option<u64> {
+               self.signed_invoice.amount_pico_btc().map(|v| v / 10)
+       }
+
        /// Returns the amount if specified in the invoice as pico <currency>.
-       pub fn amount_pico_btc(&self) -> Option<u64> {
+       fn amount_pico_btc(&self) -> Option<u64> {
                self.signed_invoice.amount_pico_btc()
        }
 }
@@ -1412,6 +1444,10 @@ pub enum SemanticError {
        /// The invoice contains multiple descriptions and/or description hashes which isn't allowed
        MultipleDescriptions,
 
+       /// The invoice is missing the mandatory payment secret, which all modern lightning nodes
+       /// should provide.
+       NoPaymentSecret,
+
        /// The invoice contains multiple payment secrets
        MultiplePaymentSecrets,
 
@@ -1435,6 +1471,7 @@ impl Display for SemanticError {
                        SemanticError::MultiplePaymentHashes => f.write_str("The invoice has multiple payment hashes which isn't allowed"),
                        SemanticError::NoDescription => f.write_str("No description or description hash are part of the invoice"),
                        SemanticError::MultipleDescriptions => f.write_str("The invoice contains multiple descriptions and/or description hashes which isn't allowed"),
+                       SemanticError::NoPaymentSecret => f.write_str("The invoice is missing the mandatory payment secret"),
                        SemanticError::MultiplePaymentSecrets => f.write_str("The invoice contains multiple payment secrets"),
                        SemanticError::InvalidFeatures => f.write_str("The invoice's features are invalid"),
                        SemanticError::InvalidRecoveryId => f.write_str("The recovery id doesn't fit the signature/pub key"),
@@ -1651,7 +1688,7 @@ mod test {
                        let invoice = invoice_template.clone();
                        invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
                }.unwrap();
-               assert!(Invoice::from_signed(invoice).is_ok());
+               assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
 
                // No payment secret or feature bits
                let invoice = {
@@ -1659,7 +1696,7 @@ mod test {
                        invoice.data.tagged_fields.push(Features(InvoiceFeatures::empty()).into());
                        invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
                }.unwrap();
-               assert!(Invoice::from_signed(invoice).is_ok());
+               assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
 
                // Missing payment secret
                let invoice = {
@@ -1667,7 +1704,7 @@ mod test {
                        invoice.data.tagged_fields.push(Features(InvoiceFeatures::known()).into());
                        invoice.sign::<_, ()>(|hash| Ok(Secp256k1::new().sign_recoverable(hash, &private_key)))
                }.unwrap();
-               assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::InvalidFeatures));
+               assert_eq!(Invoice::from_signed(invoice), Err(SemanticError::NoPaymentSecret));
 
                // Multiple payment secrets
                let invoice = {
@@ -1753,6 +1790,7 @@ mod test {
 
                let sign_error_res = builder.clone()
                        .description("Test".into())
+                       .payment_secret(PaymentSecret([0; 32]))
                        .try_build_signed(|_| {
                                Err("ImaginaryError")
                        });
@@ -1849,6 +1887,7 @@ mod test {
                assert!(invoice.check_signature().is_ok());
                assert_eq!(invoice.tagged_fields().count(), 10);
 
+               assert_eq!(invoice.amount_milli_satoshis(), Some(123));
                assert_eq!(invoice.amount_pico_btc(), Some(1230));
                assert_eq!(invoice.currency(), Currency::BitcoinTestnet);
                assert_eq!(
@@ -1865,7 +1904,7 @@ mod test {
                        InvoiceDescription::Hash(&Sha256(sha256::Hash::from_slice(&[3;32][..]).unwrap()))
                );
                assert_eq!(invoice.payment_hash(), &sha256::Hash::from_slice(&[21;32][..]).unwrap());
-               assert_eq!(invoice.payment_secret(), Some(&PaymentSecret([42; 32])));
+               assert_eq!(invoice.payment_secret(), &PaymentSecret([42; 32]));
                assert_eq!(invoice.features(), Some(&InvoiceFeatures::known()));
 
                let raw_invoice = builder.build_raw().unwrap();
@@ -1881,6 +1920,7 @@ mod test {
                let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin)
                        .description("Test".into())
                        .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
+                       .payment_secret(PaymentSecret([0; 32]))
                        .current_timestamp()
                        .build_raw()
                        .unwrap()
@@ -1894,5 +1934,33 @@ mod test {
 
                assert_eq!(invoice.min_final_cltv_expiry(), DEFAULT_MIN_FINAL_CLTV_EXPIRY);
                assert_eq!(invoice.expiry_time(), Duration::from_secs(DEFAULT_EXPIRY_TIME));
+               assert!(!invoice.is_expired());
+       }
+
+       #[test]
+       fn test_expiration() {
+               use ::*;
+               use secp256k1::Secp256k1;
+               use secp256k1::key::SecretKey;
+
+               let timestamp = SystemTime::now()
+                       .checked_sub(Duration::from_secs(DEFAULT_EXPIRY_TIME * 2))
+                       .unwrap();
+               let signed_invoice = InvoiceBuilder::new(Currency::Bitcoin)
+                       .description("Test".into())
+                       .payment_hash(sha256::Hash::from_slice(&[0;32][..]).unwrap())
+                       .payment_secret(PaymentSecret([0; 32]))
+                       .timestamp(timestamp)
+                       .build_raw()
+                       .unwrap()
+                       .sign::<_, ()>(|hash| {
+                               let privkey = SecretKey::from_slice(&[41; 32]).unwrap();
+                               let secp_ctx = Secp256k1::new();
+                               Ok(secp_ctx.sign_recoverable(hash, &privkey))
+                       })
+                       .unwrap();
+               let invoice = Invoice::from_signed(signed_invoice).unwrap();
+
+               assert!(invoice.is_expired());
        }
 }