BOLT12: don't allow zero-amount offers.
What changed, and why it matters
This commit tightens validation of BOLT12 payment offers by rejecting any offer whose amount is set to zero. Previously, Core Lightning accepted zero-amount offers, which could let a malicious or misconfigured party create an offer that looks valid but requests no payment. The change also updates the embedded BOLT specification reference and adds tests so the new rule is enforced consistently across normal decoding and the developer command-line tool.
Treat this as a minor security hardening patch. Ensure nodes are upgraded so they reject zero-amount offers consistently, and verify that any downstream BOLT12 parsing does not independently accept zero amounts.
Security signals we found
Input validation hardening for BOLT12 offer decoding
Rejection of zero-amount offers per updated BOLT #12 requirement
Test coverage added for zero amount cases
BOLT reference version bumped to newer commit
Evidence from the diff
The patch adds a check in offer_decode() in common/bolt12.c: if offer_amount is present and equals 0, decoding fails with ‘Offer contains a zero amount’. It also updates the pinned BOLT RFC version in Makefile, adjusts BOLT quote markers from blank to ‘…’ so new requirements are not missed, and adds test vectors for zero offer_amount with and without a currency. The devtools/bolt12-cli.c helper now flags zero amounts as invalid when printing an offer.
Changed components
common/bolt12.ccommon/test/run-bolt12-encode-test.ccommon/test/run-bolt12-format-string-test.cdevtools/bolt12-cli.cMakefile (BOLT version pin)Inspect captured patch +32 / −3
diff --git a/Makefile b/Makefile
index db14d23e..61c86087 100644
--- a/Makefile
+++ b/Makefile
@@ -33,7 +33,7 @@ CCANDIR := ccan
# Where we keep the BOLT RFCs
BOLTDIR := ../bolts/
-DEFAULT_BOLTVERSION := 7153bed9705d7493065d9b818d25b282ef0a7c5e
+DEFAULT_BOLTVERSION := 311119388a46dfa859da3d2eda0ca836cfc5f078
# Can be overridden on cmdline.
BOLTVERSION := $(DEFAULT_BOLTVERSION)
diff --git a/common/bolt12.c b/common/bolt12.c
index d4fcd005..1f2ccf1b 100644
--- a/common/bolt12.c
+++ b/common/bolt12.c
@@ -227,7 +227,17 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
}
/* BOLT #12:
- *
+ *...
+ * - if `offer_amount` is set and is not greater than zero:
+ * - MUST NOT respond to the offer.
+ */
+ if (offer->offer_amount && *offer->offer_amount == 0) {
+ *fail = tal_strdup(ctx, "Offer contains a zero amount");
+ return tal_free(offer);
+ }
+
+ /* BOLT #12:
+ *...
* - if `offer_currency` is set and `offer_amount` is not set:
* - MUST NOT respond to the offer.
*/
@@ -237,7 +247,7 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
}
/* BOLT #12:
- *
+ *...
* - if neither `offer_issuer_id` nor `offer_paths` are set:
* - MUST NOT respond to the offer.
*/
@@ -247,6 +257,7 @@ struct tlv_offer *offer_decode(const tal_t *ctx,
}
/* BOLT #12:
+ *...
* - if `num_hops` is 0 in any `blinded_path` in `offer_paths`:
* - MUST NOT respond to the offer.
*/
diff --git a/common/test/run-bolt12-encode-test.c b/common/test/run-bolt12-encode-test.c
index 43f3e5e0..9391b1a3 100644
--- a/common/test/run-bolt12-encode-test.c
+++ b/common/test/run-bolt12-encode-test.c
@@ -429,6 +429,8 @@ int main(int argc, char *argv[])
/* BOLT #12:
* - if `offer_amount` is set and `offer_description` is not set:
* - MUST NOT respond to the offer.
+ * - if `offer_amount` is set and is not greater than zero:
+ * - MUST NOT respond to the offer.
* - if `offer_currency` is set and `offer_amount` is not set:
* - MUST NOT respond to the offer.
* - if neither `offer_issuer_id` nor `offer_paths` are set:
@@ -438,8 +440,18 @@ int main(int argc, char *argv[])
print_invalid_offer(offer, "Missing offer_description and offer_amount");
offer->offer_description = tal_utf8(tmpctx, "Test vectors");
+ offer->offer_amount = tal(offer, u64);
+ *offer->offer_amount = 0;
+ print_invalid_offer(offer, "Zero offer_amount");
+ offer->offer_amount = tal_free(offer->offer_amount);
+
offer->offer_currency = tal_utf8(offer, "USD");
print_invalid_offer(offer, "Missing offer_amount with offer_currency");
+
+ offer->offer_amount = tal(offer, u64);
+ *offer->offer_amount = 0;
+ print_invalid_offer(offer, "Zero offer_amount with currency");
+ offer->offer_amount = tal_free(offer->offer_amount);
offer->offer_currency = NULL;
offer->offer_issuer_id = NULL;
diff --git a/common/test/run-bolt12-format-string-test.c b/common/test/run-bolt12-format-string-test.c
index d517a60c..10f6748b 100644
--- a/common/test/run-bolt12-format-string-test.c
+++ b/common/test/run-bolt12-format-string-test.c
@@ -128,6 +128,7 @@ int main(int argc, char *argv[])
* - SHOULD omit `offer_chains`, implying that bitcoin is only chain.
* - if a specific minimum `offer_amount` is required for successful payment:
* - MUST set `offer_amount` to the amount expected (per item).
+ * - MUST set `offer_amount` greater than zero.
* - if the currency for `offer_amount` is that of all entries in `chains`:
* - MUST specify `offer_amount` in multiples of the minimum lightning-payable unit
* (e.g. milli-satoshis for bitcoin).
diff --git a/devtools/bolt12-cli.c b/devtools/bolt12-cli.c
index a72d57a1..3a81467a 100644
--- a/devtools/bolt12-cli.c
+++ b/devtools/bolt12-cli.c
@@ -95,6 +95,7 @@ static bool print_offer_amount(const struct bitcoin_blkid *chains,
/* BOLT #12:
* - if a specific minimum `offer_amount` is required for successful payment:
* - MUST set `offer_amount` to the amount expected (per item).
+ * - MUST set `offer_amount` greater than zero.
* - if the currency for `offer_amount` is that of all entries in `chains`:
* - MUST specify `offer_amount` in multiples of the minimum lightning-payable unit
* (e.g. milli-satoshis for bitcoin).
@@ -150,6 +151,10 @@ static bool print_offer_amount(const struct bitcoin_blkid *chains,
currency);
}
+ if (amount == 0) {
+ printf(" *** INVALID zero offer_amount");
+ ok = false;
+ }
return ok;
}
Why this scored 37/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.