splice: Add field to DB to track if we’ve sent sigs
What changed, and why it matters
This commit adds a new bookkeeping flag, 'i_sent_sigs', to track whether a splice signature has already been sent. It changes how the node decides that question: previously it inspected the active PSBT, but now it records the fact explicitly in memory, wire messages, and the wallet database. The change is described as a refactor to support generating splice signatures earlier. There is no direct evidence in the commit of a security vulnerability, but any mismatch in this flag could in principle cause a node to send signatures twice, omit them, or resume a splice incorrectly after a restart.
Treat as a normal correctness/state-integrity change. Review follow-up commits that actually set i_sent_sigs to true and consume it during reconnection or retransmission, because the security-relevant behavior depends on how that flag is used. No immediate patch or incident response is indicated by this commit alone.
Security signals we found
State-machine flag added to prevent duplicate or missing signature events during splice negotiation
Database migration added to persist signature-sent state across restarts
Wire protocol and struct changes extend trusted internal IPC only, not a public network protocol
No input validation, bounds, or cryptographic changes visible in the diff
No explicit bug fix, CVE reference, or security disclosure in commit message
Evidence from the diff
The patch threads a new boolean ‘i_sent_sigs’ through channeld and lightningd. It extends the channeld_add_inflight and channeld_update_inflight wire messages, the inflight serialization helpers, the channel_inflight struct, and the channel_funding_inflights database table (with a migration). All existing call sites initialize the flag to false and update it via the wire message handlers. The stated reason is that the previous heuristic—checking whether the local signature was present in the active PSBT—is no longer valid because signatures are now generated earlier.
Changed components
channeld/channeld.cchanneld/channeld_wire.csvchanneld/inflight.cchanneld/inflight.hlightningd/channel.clightningd/channel.hlightningd/channel_control.clightningd/dual_open_control.cwallet/db.cwallet/wallet.cInspect captured patch +56 / −18
diff --git a/channeld/channeld.c b/channeld/channeld.c
index f00b2b1..1f8efde 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -3560,7 +3560,8 @@ static void resume_splice_negotiation(struct peer *peer,
msg = towire_channeld_update_inflight(NULL, current_psbt,
their_commit->tx,
&their_commit->commit_signature,
- inflight->locked_scid);
+ inflight->locked_scid,
+ inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
}
@@ -3631,7 +3632,8 @@ static void resume_splice_negotiation(struct peer *peer,
&& send_signature) {
msg = towire_channeld_update_inflight(NULL, current_psbt,
NULL, NULL,
- inflight->locked_scid);
+ inflight->locked_scid,
+ inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
msg = towire_channeld_splice_sending_sigs(tmpctx, &final_txid);
@@ -3829,7 +3831,8 @@ static void resume_splice_negotiation(struct peer *peer,
/* We let core validate our peer's signatures are correct. */
msg = towire_channeld_update_inflight(NULL, current_psbt, NULL,
NULL,
- inflight->locked_scid);
+ inflight->locked_scid,
+ inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
}
@@ -4094,7 +4097,8 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg)
peer->splicing->accepter_relative,
ictx->current_psbt,
false,
- peer->splicing->force_sign_first);
+ peer->splicing->force_sign_first,
+ false);
master_wait_sync_reply(tmpctx, peer, take(msg),
WIRE_CHANNELD_GOT_INFLIGHT);
@@ -4112,6 +4116,7 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg)
new_inflight->i_am_initiator = false;
new_inflight->force_sign_first = peer->splicing->force_sign_first;
new_inflight->locked_scid = NULL;
+ new_inflight->i_sent_sigs = false;
current_push_val = relative_splice_balance_fundee(peer, our_role,ictx->current_psbt,
outpoint.n, splice_funding_index);
@@ -4344,7 +4349,8 @@ static void splice_initiator_user_finalized(struct peer *peer)
peer->splicing->opener_relative,
ictx->current_psbt,
true,
- peer->splicing->force_sign_first);
+ peer->splicing->force_sign_first,
+ false);
master_wait_sync_reply(tmpctx, peer, take(outmsg),
WIRE_CHANNELD_GOT_INFLIGHT);
@@ -4361,6 +4367,7 @@ static void splice_initiator_user_finalized(struct peer *peer)
new_inflight->i_am_initiator = true;
new_inflight->force_sign_first = peer->splicing->force_sign_first;
new_inflight->locked_scid = NULL;
+ new_inflight->i_sent_sigs = false;
audit_psbt(ictx->current_psbt, ictx->current_psbt);
@@ -4392,7 +4399,8 @@ static void splice_initiator_user_finalized(struct peer *peer)
outmsg = towire_channeld_update_inflight(NULL, new_inflight->psbt,
their_commit->tx,
&their_commit->commit_signature,
- new_inflight->locked_scid);
+ new_inflight->locked_scid,
+ new_inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(outmsg));
sign_first = do_i_sign_first(peer, new_inflight->psbt, our_role,
@@ -4586,7 +4594,8 @@ static void splice_initiator_user_signed(struct peer *peer, const u8 *inmsg)
outmsg = towire_channeld_update_inflight(NULL, inflight->psbt,
inflight->last_tx,
&inflight->last_sig,
- inflight->locked_scid);
+ inflight->locked_scid,
+ inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(outmsg));
@@ -5900,7 +5909,8 @@ static void handle_funding_depth(struct peer *peer, const u8 *msg)
inflight->psbt,
NULL,
NULL,
- inflight->locked_scid);
+ inflight->locked_scid,
+ inflight->i_sent_sigs);
wire_sync_write(MASTER_FD, take(msg));
inflight_match = inflight;
}
diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv
index 43ee7af..3abacb9 100644
--- a/channeld/channeld_wire.csv
+++ b/channeld/channeld_wire.csv
@@ -262,6 +262,7 @@ msgdata,channeld_add_inflight,splice_amount,s64,
msgdata,channeld_add_inflight,psbt,wally_psbt,
msgdata,channeld_add_inflight,i_am_initiator,bool,
msgdata,channeld_add_inflight,force_sign_first,bool,
+msgdata,channeld_add_inflight,i_sent_sigs,bool,
# master->channeld: Inflight saved successfully
msgtype,channeld_got_inflight,7217
@@ -272,6 +273,7 @@ msgdata,channeld_update_inflight,psbt,wally_psbt,
msgdata,channeld_update_inflight,last_tx,?bitcoin_tx,
msgdata,channeld_update_inflight,last_sig,?bitcoin_signature,
msgdata,channeld_update_inflight,locked_scid,?short_channel_id,
+msgdata,channeld_update_inflight,i_sent_sigs,bool,
# channeld->master: A funding error has occured
msgtype,channeld_splice_funding_error,7220
diff --git a/channeld/inflight.c b/channeld/inflight.c
index 88e7ac6..db84e58 100644
--- a/channeld/inflight.c
+++ b/channeld/inflight.c
@@ -34,6 +34,7 @@ struct inflight *fromwire_inflight(const tal_t *ctx, const u8 **cursor, size_t *
else {
inflight->locked_scid = NULL;
}
+ inflight->i_sent_sigs = fromwire_bool(cursor, max);
return inflight;
}
@@ -56,4 +57,5 @@ void towire_inflight(u8 **pptr, const struct inflight *inflight)
towire_u8(pptr, inflight->locked_scid ? 1 : 0);
if (inflight->locked_scid)
towire_short_channel_id(pptr, *inflight->locked_scid);
+ towire_bool(pptr, inflight->i_sent_sigs);
}
diff --git a/channeld/inflight.h b/channeld/inflight.h
index 73d9d1a..7143e0a 100644
--- a/channeld/inflight.h
+++ b/channeld/inflight.h
@@ -20,6 +20,7 @@ struct inflight {
bool i_am_initiator;
bool force_sign_first;
struct short_channel_id *locked_scid;
+ bool i_sent_sigs;
};
struct inflight *fromwire_inflight(const tal_t *ctx, const u8 **cursor, size_t *max);
diff --git a/lightningd/channel.c b/lightningd/channel.c
index 346cb1c..40cd6cb 100644
--- a/lightningd/channel.c
+++ b/lightningd/channel.c
@@ -172,7 +172,8 @@ new_inflight(struct channel *channel,
const struct amount_sat lease_amt,
s64 splice_amnt,
bool i_am_initiator,
- bool force_sign_first)
+ bool force_sign_first,
+ bool i_sent_sigs)
{
struct channel_inflight *inflight
= tal(channel, struct channel_inflight);
@@ -208,6 +209,7 @@ new_inflight(struct channel *channel,
inflight->i_am_initiator = i_am_initiator;
inflight->force_sign_first = force_sign_first;
inflight->locked_scid = NULL;
+ inflight->i_sent_sigs = i_sent_sigs;
inflight->splice_locked_memonly = false;
list_add_tail(&channel->inflights, &inflight->list);
diff --git a/lightningd/channel.h b/lightningd/channel.h
index 509c93f..4dfcf0a 100644
--- a/lightningd/channel.h
+++ b/lightningd/channel.h
@@ -86,6 +86,9 @@ struct channel_inflight {
* peer through reconnect flows. */
struct short_channel_id *locked_scid;
+ /* Have I sent my peer my sigs? */
+ bool i_sent_sigs;
+
/* Note: This field is not stored in the database.
*
* After splice_locked, we need a way to stop the chain watchers from
@@ -456,7 +459,8 @@ struct channel_inflight *new_inflight(struct channel *channel,
const struct amount_sat lease_amt,
s64 splice_amnt,
bool i_am_initiator,
- bool force_sign_first);
+ bool force_sign_first,
+ bool i_sent_sigs);
struct channel_state_change *new_channel_state_change(const tal_t *ctx,
struct timeabs timestamp,
diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c
index 337f8f2..1470ddd 100644
--- a/lightningd/channel_control.c
+++ b/lightningd/channel_control.c
@@ -842,7 +842,7 @@ static void handle_add_inflight(struct lightningd *ld,
s64 splice_amnt;
struct wally_psbt *psbt;
struct channel_inflight *inflight;
- bool i_am_initiator, force_sign_first;
+ bool i_am_initiator, force_sign_first, i_sent_sigs;
if (!fromwire_channeld_add_inflight(tmpctx,
msg,
@@ -854,7 +854,8 @@ static void handle_add_inflight(struct lightningd *ld,
&splice_amnt,
&psbt,
&i_am_initiator,
- &force_sign_first)) {
+ &force_sign_first,
+ &i_sent_sigs)) {
channel_internal_error(channel,
"bad channel_add_inflight %s",
tal_hex(channel, msg));
@@ -877,7 +878,8 @@ static void handle_add_inflight(struct lightningd *ld,
AMOUNT_SAT(0),
splice_amnt,
i_am_initiator,
- force_sign_first);
+ force_sign_first,
+ i_sent_sigs);
log_debug(channel->log, "lightningd adding inflight with txid %s",
fmt_bitcoin_txid(tmpctx,
@@ -898,9 +900,11 @@ static void handle_update_inflight(struct lightningd *ld,
struct bitcoin_tx *last_tx;
struct bitcoin_signature *last_sig;
struct short_channel_id *locked_scid;
+ bool i_sent_sigs;
if (!fromwire_channeld_update_inflight(tmpctx, msg, &psbt, &last_tx,
- &last_sig, &locked_scid)) {
+ &last_sig, &locked_scid,
+ &i_sent_sigs)) {
channel_internal_error(channel,
"bad channel_add_inflight %s",
tal_hex(channel, msg));
@@ -929,6 +933,7 @@ static void handle_update_inflight(struct lightningd *ld,
inflight->last_sig = *last_sig;
inflight->locked_scid = tal_steal(inflight, locked_scid);
+ inflight->i_sent_sigs = i_sent_sigs;
tal_wally_start();
if (wally_psbt_combine(inflight->funding_psbt, psbt) != WALLY_OK) {
@@ -1823,6 +1828,7 @@ bool peer_start_channeld(struct channel *channel,
infcopy->i_am_initiator = inflight->i_am_initiator;
infcopy->force_sign_first = inflight->force_sign_first;
infcopy->locked_scid = tal_dup_or_null(infcopy, struct short_channel_id, inflight->locked_scid);
+ infcopy->i_sent_sigs = inflight->i_sent_sigs;
tal_wally_start();
wally_psbt_clone_alloc(inflight->funding_psbt, 0, &infcopy->psbt);
diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c
index 4560f9f..7835dcf 100644
--- a/lightningd/dual_open_control.c
+++ b/lightningd/dual_open_control.c
@@ -1300,6 +1300,7 @@ wallet_update_channel(struct lightningd *ld,
lease_amt,
0,
false,
+ false,
false);
wallet_inflight_add(ld->wallet, inflight);
@@ -1538,6 +1539,7 @@ wallet_commit_channel(struct lightningd *ld,
lease_amt,
0,
false,
+ false,
false);
wallet_inflight_add(ld->wallet, inflight);
return inflight;
diff --git a/wallet/db.c b/wallet/db.c
index 93707eb..17f0d33 100644
--- a/wallet/db.c
+++ b/wallet/db.c
@@ -1042,6 +1042,7 @@ static struct migration dbmigrations[] = {
{NULL, NULL}, /* Old, incorrect channel_htlcs_wait_indexes migration */
{SQL("ALTER TABLE channel_funding_inflights ADD locked_scid BIGINT DEFAULT 0;"), NULL},
{NULL, migrate_initialize_channel_htlcs_wait_indexes_and_fixup_forwards},
+ {SQL("ALTER TABLE channel_funding_inflights ADD i_sent_sigs INTEGER DEFAULT 0"), NULL},
};
/**
diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c
index 7c18ded..a855293 100644
--- a/wallet/test/run-db.c
+++ b/wallet/test/run-db.c
@@ -272,7 +272,8 @@ struct channel_inflight *new_inflight(struct channel *channel UNNEEDED,
const struct amount_sat lease_amt UNNEEDED,
s64 splice_amnt UNNEEDED,
bool i_am_initiator UNNEEDED,
- bool force_sign_first UNNEEDED)
+ bool force_sign_first UNNEEDED,
+ bool i_sent_sigs UNNEEDED)
{ fprintf(stderr, "new_inflight called!\n"); abort(); }
/* Generated stub for new_logger */
struct logger *new_logger(const tal_t *ctx UNNEEDED, struct log_book *record UNNEEDED,
diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c
index 5787f64..3084da4 100644
--- a/wallet/test/run-wallet.c
+++ b/wallet/test/run-wallet.c
@@ -2080,6 +2080,7 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx)
AMOUNT_SAT(1111),
0,
false,
+ false,
false);
inflight->splice_locked_memonly = true;
inflight->locked_scid = tal(inflight, struct short_channel_id);
@@ -2110,6 +2111,7 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx)
AMOUNT_SAT(0),
0,
false,
+ false,
false);
inflight->splice_locked_memonly = false;
inflight->locked_scid = NULL;
diff --git a/wallet/wallet.c b/wallet/wallet.c
index 75f3b6f..090ba7f 100644
--- a/wallet/wallet.c
+++ b/wallet/wallet.c
@@ -1415,8 +1415,9 @@ void wallet_inflight_add(struct wallet *w, struct channel_inflight *inflight)
", force_sign_first"
", remote_funding"
", locked_scid"
+ ", i_sent_sigs"
") VALUES ("
- "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
+ "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
db_bind_u64(stmt, inflight->channel->dbid);
db_bind_txid(stmt, &inflight->funding->outpoint.txid);
@@ -1463,6 +1464,7 @@ void wallet_inflight_add(struct wallet *w, struct channel_inflight *inflight)
db_bind_short_channel_id(stmt, *inflight->locked_scid);
else
db_bind_null(stmt);
+ db_bind_int(stmt, inflight->i_sent_sigs);
db_exec_prepared_v2(stmt);
assert(!stmt->error);
@@ -1567,7 +1569,7 @@ wallet_stmt2inflight(struct wallet *w, struct db_stmt *stmt,
struct bitcoin_tx *last_tx;
struct channel_inflight *inflight;
s64 splice_amnt;
- bool i_am_initiator, force_sign_first;
+ bool i_am_initiator, force_sign_first, i_sent_sigs;
secp256k1_ecdsa_signature *lease_commit_sig;
u32 lease_blockheight_start;
@@ -1611,6 +1613,7 @@ wallet_stmt2inflight(struct wallet *w, struct db_stmt *stmt,
splice_amnt = db_col_s64(stmt, "splice_amnt");
i_am_initiator = db_col_int(stmt, "i_am_initiator");
force_sign_first = db_col_int(stmt, "force_sign_first");
+ i_sent_sigs = db_col_int(stmt, "i_sent_sigs");
inflight = new_inflight(chan, remote_funding, &funding,
db_col_int(stmt, "funding_feerate"),
@@ -1626,7 +1629,8 @@ wallet_stmt2inflight(struct wallet *w, struct db_stmt *stmt,
lease_amt,
splice_amnt,
i_am_initiator,
- force_sign_first);
+ force_sign_first,
+ i_sent_sigs);
inflight->locked_scid = db_col_optional_scid(inflight, stmt, "locked_scid");
@@ -1682,6 +1686,7 @@ static bool wallet_channel_load_inflights(struct wallet *w,
", force_sign_first"
", remote_funding"
", locked_scid"
+ ", i_sent_sigs"
" FROM channel_funding_inflights"
" WHERE channel_id = ?"
" ORDER BY funding_feerate"));
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.