Skip to content

Commit 9c2a3d0

Browse files
committed
Fix amount overflow in Invoice building
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.
1 parent 32ed69a commit 9c2a3d0

File tree

1 file changed

+35
-2
lines changed

1 file changed

+35
-2
lines changed

lightning/src/offers/invoice.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ impl<'a> InvoiceBuilder<'a> {
148148
Some(amount_msats) => amount_msats,
149149
None => match invoice_request.contents.offer.amount() {
150150
Some(Amount::Bitcoin { amount_msats }) => {
151-
amount_msats * invoice_request.quantity().unwrap_or(1)
151+
amount_msats.checked_mul(invoice_request.quantity().unwrap_or(1))
152+
.ok_or(SemanticError::InvalidAmount)?
152153
},
153154
Some(Amount::Currency { .. }) => return Err(SemanticError::UnsupportedCurrency),
154155
None => return Err(SemanticError::MissingAmount),
@@ -787,7 +788,7 @@ mod tests {
787788
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
788789
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
789790
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
790-
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
791+
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
791792
use crate::offers::parse::{ParseError, SemanticError};
792793
use crate::offers::payer::PayerTlvStreamRef;
793794
use crate::offers::refund::RefundBuilder;
@@ -1177,6 +1178,38 @@ mod tests {
11771178
assert_eq!(tlv_stream.amount, Some(1001));
11781179
}
11791180

1181+
#[test]
1182+
fn builds_invoice_with_quantity_from_request() {
1183+
let invoice = OfferBuilder::new("foo".into(), recipient_pubkey())
1184+
.amount_msats(1000)
1185+
.supported_quantity(Quantity::Unbounded)
1186+
.build().unwrap()
1187+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1188+
.quantity(2).unwrap()
1189+
.build().unwrap()
1190+
.sign(payer_sign).unwrap()
1191+
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
1192+
.build().unwrap()
1193+
.sign(recipient_sign).unwrap();
1194+
let (_, _, _, tlv_stream, _) = invoice.as_tlv_stream();
1195+
assert_eq!(invoice.amount_msats(), 2000);
1196+
assert_eq!(tlv_stream.amount, Some(2000));
1197+
1198+
match OfferBuilder::new("foo".into(), recipient_pubkey())
1199+
.amount_msats(1000)
1200+
.supported_quantity(Quantity::Unbounded)
1201+
.build().unwrap()
1202+
.request_invoice(vec![1; 32], payer_pubkey()).unwrap()
1203+
.quantity(u64::max_value()).unwrap()
1204+
.build_unchecked()
1205+
.sign(payer_sign).unwrap()
1206+
.respond_with_no_std(payment_paths(), payment_hash(), now())
1207+
{
1208+
Ok(_) => panic!("expected error"),
1209+
Err(e) => assert_eq!(e, SemanticError::InvalidAmount),
1210+
}
1211+
}
1212+
11801213
#[test]
11811214
fn builds_invoice_with_fallback_address() {
11821215
let script = Script::new();

0 commit comments

Comments
 (0)