common: hoist amount+currency parsing into common code.
What changed, and why it matters
This commit moves existing code that parses payment amounts (Bitcoin millisatoshis, 'any' amount, or fiat currency amounts) from one plugin file into a shared library so it can be reused by a future feature. It is a code refactoring with no obvious security bug introduced or fixed. The parsing logic is essentially unchanged, just relocated and slightly tightened.
No immediate action required. Treat as routine refactoring. If reviewing further, verify that the new str_to_u64-based parsing rejects the same malformed inputs as the previous json_to_u64-based parsing and that overflow checks cover all arithmetic paths.
Security signals we found
Refactoring of amount-parsing code into a shared helper
Addition of explicit u64 overflow guards (mul_overflows_u64, add_overflows_u64) in the new common code
No change to wire protocol or RPC interface semantics
No mention of vulnerability, CVE, bug fix, or security issue in commit message
Evidence from the diff
The change hoists amount/currency parsing from plugins/offers_offer.c into common/iso4217.c as parse_currency_amount(). The new helper accepts a raw buffer+length instead of a JSON token, uses str_to_u64 instead of json_to_u64, and adds explicit u64 overflow checks for multiplication and addition. The offers plugin now calls this common helper. The behavior for valid and invalid inputs appears equivalent to the previous inline implementation.
Changed components
common/iso4217.ccommon/iso4217.hplugins/offers_offer.cInspect captured patch +112 / −72
diff --git a/common/iso4217.c b/common/iso4217.c
index a164e91c..85f5898e 100644
--- a/common/iso4217.c
+++ b/common/iso4217.c
@@ -1,7 +1,11 @@
#include "config.h"
#include <ccan/array_size/array_size.h>
#include <ccan/mem/mem.h>
+#include <ccan/tal/str/str.h>
+#include <common/amount.h>
#include <common/iso4217.h>
+#include <common/overflows.h>
+#include <common/utils.h>
/* Wikipedia leads me to: https://www.currency-iso.org/en/home/tables/table-a1.html
@@ -202,3 +206,86 @@ const struct iso4217_name_and_divisor *find_iso4217(const utf8 *prefix,
}
return NULL;
}
+
+static bool msat_or_any(const tal_t *ctx,
+ const char *buf,
+ size_t buflen,
+ u64 **amount)
+{
+ struct amount_msat msat;
+
+ if (memeqstr(buf, buflen, "any")) {
+ *amount = NULL;
+ return true;
+ }
+
+ if (!parse_amount_msat(&msat, buf, buflen))
+ return false;
+
+ *amount = tal_dup(ctx, u64, &msat.millisatoshis); /* Raw: parsing */
+ return true;
+}
+
+const char *parse_currency_amount(const tal_t *ctx,
+ const char *buf,
+ size_t buflen,
+ const struct iso4217_name_and_divisor **isocode,
+ u64 **amount)
+{
+ const char *dot;
+ size_t wholelen;
+ u64 cents;
+ u64 total;
+
+ if (msat_or_any(ctx, buf, buflen, amount)) {
+ *isocode = NULL;
+ return NULL;
+ }
+
+ /* BOLT #12:
+ *
+ * - MUST specify `offer_currency` `iso4217` as an ISO 4217 three-letter code.
+ * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4217
+ * exponent (e.g. USD cents).
+ */
+ if (buflen < ISO4217_NAMELEN)
+ return tal_fmt(ctx, "Not a number, and too short for currency");
+
+ *isocode = find_iso4217(buf + buflen - ISO4217_NAMELEN, ISO4217_NAMELEN);
+ if (!*isocode)
+ return tal_fmt(ctx, "Unknown currency suffix %.*s",
+ ISO4217_NAMELEN,
+ buf + buflen - ISO4217_NAMELEN);
+
+ buflen -= ISO4217_NAMELEN;
+ dot = memchr(buf, '.', buflen);
+ if (!dot) {
+ wholelen = buflen;
+ cents = 0;
+ } else {
+ const char *afterdot = dot + 1;
+ size_t partlen = buf + buflen - afterdot;
+ wholelen = dot - buf;
+ if (partlen != (*isocode)->minor_unit)
+ return tal_fmt(ctx, "Currency %s requires %u minor units",
+ (*isocode)->name, (*isocode)->minor_unit);
+ if (!str_to_u64(afterdot, partlen, ¢s))
+ return tal_fmt(ctx, "Bad minor units number");
+ }
+
+ if (!str_to_u64(buf, wholelen, &total))
+ return tal_fmt(ctx, "Not a valid number");
+
+ for (size_t i = 0; i < (*isocode)->minor_unit; i++) {
+ if (mul_overflows_u64(total, 10))
+ return tal_fmt(ctx, "excessively large value");
+ total *= 10;
+ }
+
+ if (add_overflows_u64(total, cents))
+ return tal_fmt(ctx, "excessively large value");
+
+ total += cents;
+ *amount = tal_dup(ctx, u64, &total);
+ return NULL;
+}
diff --git a/common/iso4217.h b/common/iso4217.h
index e57c5baa..a77a6d9f 100644
--- a/common/iso4217.h
+++ b/common/iso4217.h
@@ -18,4 +18,17 @@ struct iso4217_name_and_divisor {
const struct iso4217_name_and_divisor *find_iso4217(const utf8 *prefix,
size_t len);
+
+/**
+ * parse_currency_amount - convert msat amount, any, or currency amount.
+ *
+ * Returns error message or NULL. On success:
+ * if *iso4217 == NULL: currency is BTC. If *amount == NULL, "any".
+ * if *iso4217 != NULL: *amount is amount in cents in that currency, never NULL.
+ */
+const char *parse_currency_amount(const tal_t *ctx,
+ const char *buf,
+ size_t buflen,
+ const struct iso4217_name_and_divisor **iso4217,
+ u64 **amount);
#endif /* LIGHTNING_COMMON_ISO4217_H */
diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c
index 59570e26..77c3cf9b 100644
--- a/plugins/offers_offer.c
+++ b/plugins/offers_offer.c
@@ -13,87 +13,27 @@
#include <plugins/offers.h>
#include <plugins/offers_offer.h>
-static bool msat_or_any(const char *buffer,
- const jsmntok_t *tok,
- struct tlv_offer *offer)
-{
- struct amount_msat msat;
- if (json_tok_streq(buffer, tok, "any"))
- return true;
-
- if (!parse_amount_msat(&msat,
- buffer + tok->start, tok->end - tok->start))
- return false;
-
- offer->offer_amount = tal_dup(offer, u64,
- &msat.millisatoshis); /* Raw: other currencies */
- return true;
-}
-
static struct command_result *param_amount(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct tlv_offer *offer)
{
+ const char *err;
const struct iso4217_name_and_divisor *isocode;
- jsmntok_t number, whole, frac;
- u64 cents;
-
- if (msat_or_any(buffer, tok, offer))
- return NULL;
- offer->offer_amount = tal(offer, u64);
-
- /* BOLT #12:
- *
- * - MUST specify `offer_currency` `iso4217` as an ISO 4217 three-letter code.
- * - MUST specify `offer_amount` in the currency unit adjusted by the ISO 4217
- * exponent (e.g. USD cents).
- */
- if (tok->end - tok->start < ISO4217_NAMELEN)
- return command_fail_badparam(cmd, name, buffer, tok,
- "should be 'any', msatoshis or <amount>[.<amount>]<ISO-4217>");
-
- isocode = find_iso4217(buffer + tok->end - ISO4217_NAMELEN, ISO4217_NAMELEN);
- if (!isocode)
- return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
- "Unknown currency suffix %.*s",
- ISO4217_NAMELEN,
- buffer + tok->end - ISO4217_NAMELEN);
-
- offer->offer_currency
- = tal_dup_arr(offer, utf8, isocode->name, ISO4217_NAMELEN, 0);
-
- number = *tok;
- number.end -= ISO4217_NAMELEN;
- if (!split_tok(buffer, &number, '.', &whole, &frac)) {
- whole = number;
- cents = 0;
- } else {
- if (frac.end - frac.start != isocode->minor_unit)
- return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
- "Currency %s requires %u minor units",
- isocode->name, isocode->minor_unit);
- if (!json_to_u64(buffer, &frac, ¢s))
- return command_fail_badparam(cmd, name, buffer,
- &number,
- "Bad minor units");
+ err = parse_currency_amount(offer,
+ buffer + tok->start,
+ tok->end - tok->start,
+ &isocode,
+ &offer->offer_amount);
+ if (err)
+ return command_fail_badparam(cmd, name, buffer, tok, err);
+
+ if (isocode) {
+ offer->offer_currency
+ = tal_dup_arr(offer, utf8, isocode->name, ISO4217_NAMELEN, 0);
}
-
- if (!json_to_u64(buffer, &whole, offer->offer_amount))
- return command_fail_badparam(cmd, name, buffer, tok,
- "should be 'any', msatoshis or <ISO-4217><amount>[.<amount>]");
-
- for (size_t i = 0; i < isocode->minor_unit; i++) {
- if (mul_overflows_u64(*offer->offer_amount, 10))
- return command_fail_badparam(cmd, name, buffer,
- &whole,
- "excessively large value");
- *offer->offer_amount *= 10;
- }
-
- *offer->offer_amount += cents;
return NULL;
}
Why this scored 18/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.