common: enforce the use of a "primary" tag in coin_mvt tags.
What changed, and why it matters
This commit adds internal safety checks to ensure that financial movement records in Core Lightning always start with a main category tag, with any extra tags treated as secondary. It does not change user-facing behavior in production builds, but it helps prevent bookkeeping mistakes that could misclassify transactions.
Treat as a hardening/defensive commit. Review whether the assertions are sufficient and whether any existing call sites pass tag arrays that violate the new invariant. No immediate security patch is indicated, but verify bookkeeper accounting correctness under the new rules.
Security signals we found
Defensive invariant enforcement for coin movement tagging
Reliance by bookkeeper plugin on first tag being primary
Use of assertions means runtime enforcement depends on build configuration
No direct exploit path visible in diff
Evidence from the diff
The patch introduces mvt_tag_is_primary() to classify each coin movement tag as primary or secondary, and adds assertions in new_tag_arr(), new_channel_coin_mvt(), and new_chain_coin_mvt() to enforce that tag arrays have a primary tag first and only non-primary tags afterward. These assertions are active in debug builds; in release builds they are typically compiled out. The stated motivation is that the bookkeeper plugin relies on the first tag being the primary one.
Changed components
common/coin_mvt.cbookkeeper plugin (consumer of coin_movement notifications)Inspect captured patch +66 / −0
diff --git a/common/coin_mvt.c b/common/coin_mvt.c
index 5dc7843b..8f4b240a 100644
--- a/common/coin_mvt.c
+++ b/common/coin_mvt.c
@@ -36,6 +36,61 @@ static const char *mvt_tags[] = {
"splice",
};
+static bool mvt_tag_is_primary(enum mvt_tag tag)
+{
+ switch (tag) {
+ case MVT_DEPOSIT:
+ return true;
+ case MVT_WITHDRAWAL:
+ return true;
+ case MVT_PENALTY:
+ return true;
+ case MVT_INVOICE:
+ return true;
+ case MVT_ROUTED:
+ return true;
+ case MVT_PUSHED:
+ return true;
+ case MVT_CHANNEL_OPEN:
+ return true;
+ case MVT_CHANNEL_CLOSE:
+ return true;
+ case MVT_CHANNEL_TO_US:
+ return true;
+ case MVT_HTLC_TIMEOUT:
+ return true;
+ case MVT_HTLC_FULFILL:
+ return true;
+ case MVT_HTLC_TX:
+ return true;
+ case MVT_TO_WALLET:
+ return true;
+ case MVT_ANCHOR:
+ return true;
+ case MVT_TO_THEM:
+ return true;
+ case MVT_PENALIZED:
+ return true;
+ case MVT_STOLEN:
+ return true;
+ case MVT_TO_MINER:
+ return true;
+ case MVT_OPENER:
+ return false;
+ case MVT_LEASE_FEE:
+ return true;
+ case MVT_LEASED:
+ return false;
+ case MVT_STEALABLE:
+ return false;
+ case MVT_CHANNEL_PROPOSED:
+ return true;
+ case MVT_SPLICE:
+ return false;
+ }
+ abort();
+}
+
const char *mvt_tag_str(enum mvt_tag tag)
{
return mvt_tags[tag];
@@ -44,10 +99,19 @@ const char *mvt_tag_str(enum mvt_tag tag)
enum mvt_tag *new_tag_arr(const tal_t *ctx, enum mvt_tag tag)
{
enum mvt_tag *tags = tal_arr(ctx, enum mvt_tag, 1);
+ assert(mvt_tag_is_primary(tag));
tags[0] = tag;
return tags;
}
+static void check_tags(const enum mvt_tag *tags)
+{
+ assert(tal_count(tags) > 0);
+ assert(mvt_tag_is_primary(tags[0]));
+ for (size_t i = 1; i < tal_count(tags); i++)
+ assert(!mvt_tag_is_primary(tags[i]));
+}
+
void set_mvt_account_id(struct mvt_account_id *acct_id,
const struct channel *channel,
const char *account_name TAKES)
@@ -97,6 +161,7 @@ struct channel_coin_mvt *new_channel_coin_mvt(const tal_t *ctx,
pg->group_id = *group_id;
}
+ check_tags(tags);
mvt->tags = tal_dup_talarr(mvt, enum mvt_tag, tags);
mvt->fees = fees;
@@ -142,6 +207,7 @@ static struct chain_coin_mvt *new_chain_coin_mvt(const tal_t *ctx,
mvt->payment_hash = tal_dup_or_null(mvt, struct sha256, payment_hash);
mvt->blockheight = blockheight;
+ check_tags(tags);
mvt->tags = tal_dup_talarr(mvt, enum mvt_tag, tags);
mvt->output_val = output_val;
Why this scored 24/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.