Check offer expiry when building invoice in no-std
authorJeffrey Czyz <jkczyz@gmail.com>
Thu, 19 Oct 2023 22:49:13 +0000 (17:49 -0500)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 20 Oct 2023 14:49:57 +0000 (09:49 -0500)
Building an invoice will fail if the underlying offer or refund has
already expired. The check was skipped in no-std since there is no
system clock. However, the invoice creation time can be used instead.
This prevents responding to an invoice request if the offer has already
expired.

lightning/src/offers/invoice.rs
lightning/src/offers/offer.rs
lightning/src/offers/refund.rs

index 908d2d4bee6d2f6b9091ae0d62ce7e650cbfe754..1165cfd39a50dd7dcd9d01f883c62663d7f2e882 100644 (file)
@@ -339,6 +339,12 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
                        }
                }
 
+               #[cfg(not(feature = "std"))] {
+                       if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
+                               return Err(Bolt12SemanticError::AlreadyExpired);
+                       }
+               }
+
                let InvoiceBuilder { invreq_bytes, invoice, .. } = self;
                Ok(UnsignedBolt12Invoice::new(invreq_bytes, invoice))
        }
@@ -355,6 +361,12 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
                        }
                }
 
+               #[cfg(not(feature = "std"))] {
+                       if self.invoice.is_offer_or_refund_expired_no_std(self.invoice.created_at()) {
+                               return Err(Bolt12SemanticError::AlreadyExpired);
+                       }
+               }
+
                let InvoiceBuilder {
                        invreq_bytes, invoice, signing_pubkey_strategy: DerivedSigningPubkey(keys)
                } = self;
@@ -727,6 +739,16 @@ impl InvoiceContents {
                }
        }
 
+       #[cfg(not(feature = "std"))]
+       fn is_offer_or_refund_expired_no_std(&self, duration_since_epoch: Duration) -> bool {
+               match self {
+                       InvoiceContents::ForOffer { invoice_request, .. } =>
+                               invoice_request.inner.offer.is_expired_no_std(duration_since_epoch),
+                       InvoiceContents::ForRefund { refund, .. } =>
+                               refund.is_expired_no_std(duration_since_epoch),
+               }
+       }
+
        fn offer_chains(&self) -> Option<Vec<ChainHash>> {
                match self {
                        InvoiceContents::ForOffer { invoice_request, .. } =>
index ab95d5b192f6936be0701aefb876089a62054906..8e0d46261483769864dde69296a6447ff3cfcbad 100644 (file)
@@ -609,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> {
index ecafb2bb5c7b3f3cb4973016ba117cef9ed638ae..0a95f72511821f33c7b58214e1dcbe4cf2a5647b 100644 (file)
@@ -540,13 +540,16 @@ impl RefundContents {
 
        #[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> {