Fix amount overflow in Invoice building
authorJeffrey Czyz <jkczyz@gmail.com>
Thu, 16 Feb 2023 02:17:18 +0000 (20:17 -0600)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 24 Feb 2023 00:25:50 +0000 (18:25 -0600)
An overflow can occur when multiplying the offer amount by the requested
quantity when no amount is given in the request. Return an error instead
of overflowing.

lightning/src/offers/invoice.rs

index 7a3438b6410f8ff73fe7fd5f47bc28479aef1d39..49c03a443475cb0c0c4f6ed556f5a242cda7d40b 100644 (file)
@@ -148,7 +148,8 @@ impl<'a> InvoiceBuilder<'a> {
                        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)
+                                       amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
+                                               .ok_or(SemanticError::InvalidAmount)?
                                },
                                Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
                                None => return Err(SemanticError::MissingAmount),
@@ -787,7 +788,7 @@ mod tests {
        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::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
        use crate::offers::parse::{ParseError, SemanticError};
        use crate::offers::payer::PayerTlvStreamRef;
        use crate::offers::refund::RefundBuilder;
@@ -1177,6 +1178,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, SemanticError::InvalidAmount),
+               }
+       }
+
        #[test]
        fn builds_invoice_with_fallback_address() {
                let script = Script::new();