Merge pull request #2727 from TheBlueMatt/2023-11-better-bolt11-utils
[rust-lightning] / lightning / src / offers / invoice.rs
index 06215e2d48615ce9784c6e95f534a8eeb7739be9..5bd5a95d8e627a3e69964db540fbffa554bbddf4 100644 (file)
@@ -129,7 +129,7 @@ use crate::prelude::*;
 #[cfg(feature = "std")]
 use std::time::SystemTime;
 
-const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
+pub(crate) const DEFAULT_RELATIVE_EXPIRY: Duration = Duration::from_secs(7200);
 
 /// Tag for the hash function used when signing a [`Bolt12Invoice`]'s merkle root.
 pub const SIGNATURE_TAG: &'static str = concat!("lightning", "invoice", "signature");
@@ -174,7 +174,7 @@ impl<'a> InvoiceBuilder<'a, ExplicitSigningPubkey> {
                invoice_request: &'a InvoiceRequest, payment_paths: Vec<(BlindedPayInfo, BlindedPath)>,
                created_at: Duration, payment_hash: PaymentHash
        ) -> Result<Self, Bolt12SemanticError> {
-               let amount_msats = Self::check_amount_msats(invoice_request)?;
+               let amount_msats = Self::amount_msats(invoice_request)?;
                let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
@@ -207,7 +207,7 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
                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 amount_msats = Self::amount_msats(invoice_request)?;
                let signing_pubkey = invoice_request.contents.inner.offer.signing_pubkey();
                let contents = InvoiceContents::ForOffer {
                        invoice_request: invoice_request.contents.clone(),
@@ -237,7 +237,9 @@ impl<'a> InvoiceBuilder<'a, DerivedSigningPubkey> {
 }
 
 impl<'a, S: SigningPubkeyStrategy> InvoiceBuilder<'a, S> {
-       fn check_amount_msats(invoice_request: &InvoiceRequest) -> Result<u64, Bolt12SemanticError> {
+       pub(crate) fn 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() {
@@ -339,6 +341,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 +363,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;
@@ -425,6 +439,7 @@ impl UnsignedBolt12Invoice {
                        bytes: self.bytes,
                        contents: self.contents,
                        signature,
+                       tagged_hash: self.tagged_hash,
                })
        }
 }
@@ -449,6 +464,7 @@ pub struct Bolt12Invoice {
        bytes: Vec<u8>,
        contents: InvoiceContents,
        signature: Signature,
+       tagged_hash: TaggedHash,
 }
 
 /// The contents of an [`Bolt12Invoice`] for responding to either an [`Offer`] or a [`Refund`].
@@ -693,7 +709,7 @@ impl Bolt12Invoice {
 
        /// 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()
+               self.tagged_hash.as_digest().as_ref().clone()
        }
 
        /// Verifies that the invoice was for a request or refund created using the given key. Returns
@@ -727,6 +743,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, .. } =>
@@ -1188,11 +1214,11 @@ impl TryFrom<ParsedMessage<FullInvoiceTlvStream>> for Bolt12Invoice {
                        None => return Err(Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::MissingSignature)),
                        Some(signature) => signature,
                };
-               let message = TaggedHash::new(SIGNATURE_TAG, &bytes);
+               let tagged_hash = TaggedHash::new(SIGNATURE_TAG, &bytes);
                let pubkey = contents.fields().signing_pubkey;
-               merkle::verify_signature(&signature, message, pubkey)?;
+               merkle::verify_signature(&signature, &tagged_hash, pubkey)?;
 
-               Ok(Bolt12Invoice { bytes, contents, signature })
+               Ok(Bolt12Invoice { bytes, contents, signature, tagged_hash })
        }
 }
 
@@ -1407,7 +1433,7 @@ mod tests {
                assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
 
                let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
-               assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok());
+               assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok());
 
                let digest = Message::from_slice(&invoice.signable_hash()).unwrap();
                let pubkey = recipient_pubkey().into();
@@ -1504,7 +1530,7 @@ mod tests {
                assert_eq!(invoice.signing_pubkey(), recipient_pubkey());
 
                let message = TaggedHash::new(SIGNATURE_TAG, &invoice.bytes);
-               assert!(merkle::verify_signature(&invoice.signature, message, recipient_pubkey()).is_ok());
+               assert!(merkle::verify_signature(&invoice.signature, &message, recipient_pubkey()).is_ok());
 
                assert_eq!(
                        invoice.as_tlv_stream(),