What changed, and why it matters
This commit tightens validation of Lightning Network 'offers' (a way for a merchant to publish a payment request). Previously, an offer could include an amount field set to zero, which the protocol designers consider invalid and confusing. The change makes Eclair reject such offers. The main risk is that an attacker or buggy peer could send a zero-amount offer and, before this patch, Eclair might have accepted or processed it in an unintended way. The commit itself is a straightforward validation fix and does not show an active exploit.
Treat as a low-severity hardening patch. Review whether zero-amount offers could have caused incorrect routing, accounting, or user-facing behavior in prior versions, but no urgent response is indicated by the diff alone.
Security signals we found
Input validation hardening for BOLT12 offer_amount
Reference to upstream BOLT specification change (bolts PR 1316)
New negative test vectors for zero offer_amount with and without currency
Evidence from the diff
In OfferTypes.scala, the validate() method now returns ForbiddenTlv(UInt64(10)) when OfferAmount is present with amount == 0. Two negative test vectors were added to offers-test.json. The change aligns with BOLT12 PR 1316, which clarifies that offer_amount should be omitted if any amount is acceptable, not set to zero. The patch is purely additive validation; no logic for handling accepted offers is changed.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scalaBOLT12 offer parsing/validationInspect captured patch +52 / −0
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala
index 3963518..d1717a1 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferTypes.scala
@@ -314,6 +314,7 @@ object OfferTypes {
def validate(records: TlvStream[OfferTlv]): Either[InvalidTlvPayload, Offer] = {
if (records.get[OfferDescription].isEmpty && records.get[OfferAmount].nonEmpty) return Left(MissingRequiredTlv(UInt64(10)))
+ if (records.get[OfferAmount].exists(_.amount == 0)) return Left(ForbiddenTlv(UInt64(10)))
if (records.get[OfferNodeId].isEmpty && records.get[OfferPaths].isEmpty) return Left(MissingRequiredTlv(UInt64(22)))
if (records.get[OfferCurrency].nonEmpty && records.get[OfferAmount].isEmpty) return Left(MissingRequiredTlv(UInt64(8)))
if (records.unknown.exists(!isOfferTlv(_))) return Left(ForbiddenTlv(records.unknown.find(!isOfferTlv(_)).get.tag))
diff --git a/eclair-core/src/test/resources/offers-test.json b/eclair-core/src/test/resources/offers-test.json
index 33e0f62..f3bdf55 100644
--- a/eclair-core/src/test/resources/offers-test.json
+++ b/eclair-core/src/test/resources/offers-test.json
@@ -583,6 +583,57 @@
"valid": false,
"bolt12": "lno1qcp4256ypgx9getnwss8vetrw3hhyuckyypwa3eyt44h6txtxquqh7lz5djge4afgfjn7k4rgrkuag0jsd5xvxg"
},
+ {
+ "description": "Invalid: zero offer_amount",
+ "valid": false,
+ "bolt12": "lno1pqqq5qqkyyp4he0fg7pqje62jmnq78cr0ashv4q06qql58tyd9rhp3t2wuyugtq",
+ "field info": "offer_amount is 0",
+ "fields": [
+ {
+ "type": 8,
+ "length": 0,
+ "hex": ""
+ },
+ {
+ "type": 10,
+ "length": 0,
+ "hex": ""
+ },
+ {
+ "type": 22,
+ "length": 33,
+ "hex": "035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c"
+ }
+ ]
+ },
+ {
+ "description": "Invalid: zero offer_amount with currency",
+ "valid": false,
+ "bolt12": "lno1qcp4256ypqqq5qqkyyp4he0fg7pqje62jmnq78cr0ashv4q06qql58tyd9rhp3t2wuyugtq",
+ "field info": "offer_amount is 0, offer_currency is USD",
+ "fields": [
+ {
+ "type": 6,
+ "length": 3,
+ "hex": "555344"
+ },
+ {
+ "type": 8,
+ "length": 0,
+ "hex": ""
+ },
+ {
+ "type": 10,
+ "length": 0,
+ "hex": ""
+ },
+ {
+ "type": 22,
+ "length": 33,
+ "hex": "035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c"
+ }
+ ]
+ },
{
"description": "Missing offer_issuer_id and no offer_path",
"valid": false,
Why this scored 28/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.