Reject quantity of 0 for offers with bounded quantity
What changed, and why it matters
This commit fixes a validation bug in rust-lightning's BOLT12 offer handling. An offer that says 'buy up to N items' was accidentally accepting requests to buy 0 items, which is meaningless and could let someone request an invoice for nothing. The fix now rejects zero-quantity requests for bounded-quantity offers, matching the intended behavior.
Review whether any other quantity-related edge cases (e.g., overflow, quantity == 0 with other quantity types) are handled consistently. No immediate emergency action is indicated, but users processing BOLT12 offers should update to include this validation fix.
Security signals we found
Input validation bug in BOLT12 offer quantity parsing
Zero-value / zero-quantity invoice request accepted when it should be rejected
Semantic validation fix with regression test
Evidence from the diff
In OfferContents::is_valid_quantity, the Quantity::Bounded(n) arm previously only checked quantity <= n.get(). It now also requires quantity > 0. A regression test was added in invoice_request.rs verifying that a Quantity::Bounded(10) offer rejects quantity(0) with Bolt12SemanticError::InvalidQuantity.
Changed components
lightning/src/offers/offer.rslightning/src/offers/invoice_request.rsBOLT12 offer/invoice request quantity validationInspect captured patch +14 / −1
diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs
index 7805882..2b4379e 100644
--- a/lightning/src/offers/invoice_request.rs
+++ b/lightning/src/offers/invoice_request.rs
@@ -2221,6 +2221,19 @@ mod tests {
Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidQuantity),
}
+ match OfferBuilder::new(recipient_pubkey())
+ .amount_msats(1000)
+ .supported_quantity(Quantity::Bounded(ten))
+ .build()
+ .unwrap()
+ .request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
+ .unwrap()
+ .quantity(0)
+ {
+ Ok(_) => panic!("expected error"),
+ Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidQuantity),
+ }
+
let invoice_request = OfferBuilder::new(recipient_pubkey())
.amount_msats(1000)
.supported_quantity(Quantity::Unbounded)
diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs
index b270345..8bafb00 100644
--- a/lightning/src/offers/offer.rs
+++ b/lightning/src/offers/offer.rs
@@ -975,7 +975,7 @@ impl OfferContents {
fn is_valid_quantity(&self, quantity: u64) -> bool {
match self.supported_quantity {
- Quantity::Bounded(n) => quantity <= n.get(),
+ Quantity::Bounded(n) => quantity > 0 && quantity <= n.get(),
Quantity::Unbounded => quantity > 0,
Quantity::One => quantity == 1,
}
Why this scored 41/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.