Skip to content

Commit 2fd0cbb

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 2035ff3 commit 2fd0cbb

File tree

1 file changed

+35
-2
lines changed

1 file changed

+35
-2
lines changed

lightning/src/offers/invoice.rs

Lines changed: 35 additions & 2 deletions
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),
@@ -782,7 +783,7 @@ mod tests {
782783
use crate::ln::features::{BlindedHopFeatures, Bolt12InvoiceFeatures};
783784
use crate::offers::invoice_request::InvoiceRequestTlvStreamRef;
784785
use crate::offers::merkle::{SignError, SignatureTlvStreamRef, self};
785-
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef};
786+
use crate::offers::offer::{OfferBuilder, OfferTlvStreamRef, Quantity};
786787
use crate::offers::parse::{ParseError, SemanticError};
787788
use crate::offers::payer::PayerTlvStreamRef;
788789
use crate::offers::refund::RefundBuilder;
@@ -1172,6 +1173,38 @@ mod tests {
11721173
assert_eq!(tlv_stream.amount, Some(1001));
11731174
}
11741175

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

0 commit comments

Comments
 (0)