reestablish: Send announcement sigs when asked
What changed, and why it matters
This commit changes how Core Lightning handles reconnection with another Lightning node. It adds a way for the peer to ask for 'announcement signatures' again after reconnecting, and makes the node resend them when asked. This is part of a protocol update for splicing. It is not a traditional security fix; it is a protocol compliance/robustness change that could help prevent channel announcement issues after reconnections or splices.
Treat as a normal protocol update. Review that retransmit_flags parsing only honors the defined bit and that signature retransmission cannot be abused to spam or trigger state confusion. No urgent action required absent additional context.
Security signals we found
Protocol feature addition (BOLTs splice PR 1160)
Resends cryptographic signatures upon explicit peer request
Changes one-shot signature-sending guard to allow retransmission on request
No input validation beyond bit mask on retransmit_flags
No changelog/security note from vendor
Evidence from the diff
The commit implements a new TLV field in channel_reestablish (my_current_funding_locked with retransmit_flags bit 0) that lets a peer request retransmission of announcement_signatures. channeld parses this bit, forwards a boolean to lightningd via an updated channeld_reestablished wire message, and channel_gossip.c conditionally calls send_channel_announce_sigs() when requested. The existing ‘sent_sigs’ guard is split into a one-shot wrapper (send_channel_announce_sigs_once) and a direct function so re-requests can bypass the one-shot check. This is tied to BOLTs PR 1160 (splice spec).
Changed components
channeld/channeld.cchanneld/channeld_wire.csvlightningd/channel_control.clightningd/channel_gossip.clightningd/channel_gossip.htests/plugins/channeld_fakenet.cwallet/test/run-wallet.cInspect captured patch +65 / −19
diff --git a/channeld/channeld.c b/channeld/channeld.c
index aa092f5..4e18a93 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -5542,6 +5542,7 @@ static void peer_reconnect(struct peer *peer,
const struct secret *last_remote_per_commit_secret)
{
struct channel_id channel_id;
+ bool announcement_sigs_requested;
/* Note: BOLT #2 uses these names! */
u64 next_commitment_number, next_revocation_number;
bool retransmit_revoke_and_ack, retransmit_commitment_signed;
@@ -6036,6 +6037,24 @@ static void peer_reconnect(struct peer *peer,
if (retransmit_revoke_and_ack && peer->last_was_revoke)
resend_revoke(peer);
+ /* BOLT-??? #2
+ * 1. type: 5 (`my_current_funding_locked`)
+ * 2. data:
+ * * [`sha256`:`my_current_funding_locked_txid`]
+ * * [`byte`:`retransmit_flags`]
+ *
+ * The `retransmit_flags` bitfield is used to let our peer know which messages
+ * we expect them to retransmit after the reconnection:
+ *
+ * | Bit Position | Name |
+ * | ------------- | --------------------------|
+ * | 0 | `announcement_signatures` |
+ */
+ announcement_sigs_requested = false;
+ if (recv_tlvs && recv_tlvs->my_current_funding_locked
+ && recv_tlvs->my_current_funding_locked->retransmit_flags & 1)
+ announcement_sigs_requested = true;
+
/* BOLT #2:
*
* - upon reconnection:
@@ -6048,7 +6067,7 @@ static void peer_reconnect(struct peer *peer,
tal_free(send_tlvs);
/* We've reestablished! */
- wire_sync_write(MASTER_FD, take(towire_channeld_reestablished(NULL)));
+ wire_sync_write(MASTER_FD, take(towire_channeld_reestablished(NULL, announcement_sigs_requested)));
/* Corner case: we didn't send shutdown before because update_add_htlc
* pending, but now they're cleared by restart, and we're actually
diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv
index be5a5c6..3e9a717 100644
--- a/channeld/channeld_wire.csv
+++ b/channeld/channeld_wire.csv
@@ -78,6 +78,7 @@ msgdata,channeld_init,scid_alias,short_channel_id,
# channeld->lightningd: successfully negotated reestablishment.
msgtype,channeld_reestablished,1101
+msgdata,channeld_reestablished,announcement_sigs_requested,bool,
# master->channeld funding hit new depth(funding locked if >= lock depth)
# short_channel_id != NULL once we have 3+ confirmations
diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c
index 917f8af..2e7799e 100644
--- a/lightningd/channel_control.c
+++ b/lightningd/channel_control.c
@@ -1311,6 +1311,17 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
wallet_channel_save(ld->wallet, channel);
}
+static void peer_channeld_reestablished(struct channel *channel, const u8* msg)
+{
+ bool announcement_sigs_requested;
+ if (!fromwire_channeld_reestablished(msg, &announcement_sigs_requested))
+ channel_internal_error(channel,
+ "bad channeld_reestablished %s",
+ tal_hex(channel, msg));
+
+ channel_gossip_channel_reestablished(channel, announcement_sigs_requested);
+}
+
void channel_fallen_behind(struct channel *channel)
{
channel->has_future_per_commitment_point = true;
@@ -1565,7 +1576,7 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds)
peer_got_shutdown(sd->channel, msg);
break;
case WIRE_CHANNELD_REESTABLISHED:
- channel_gossip_channel_reestablished(sd->channel);
+ peer_channeld_reestablished(sd->channel, msg);
break;
case WIRE_CHANNELD_SHUTDOWN_COMPLETE:
/* We expect 1 fd. */
@@ -1923,7 +1934,7 @@ bool peer_start_channeld(struct channel *channel,
/* "Reestablished" if we've just opened. */
if (!reconnected)
- channel_gossip_channel_reestablished(channel);
+ channel_gossip_channel_reestablished(channel, false);
/* FIXME: DTODO: Use a pointer to a txid instead of zero'ing one out. */
memset(&txid, 0, sizeof(txid));
diff --git a/lightningd/channel_gossip.c b/lightningd/channel_gossip.c
index 0120781..fcd31c7 100644
--- a/lightningd/channel_gossip.c
+++ b/lightningd/channel_gossip.c
@@ -687,25 +687,22 @@ static void stash_remote_announce_sigs(struct channel *channel,
* - MUST NOT send the `announcement_signatures` message.
*/
-static void send_channel_announce_sigs(struct channel *channel)
+static bool send_channel_announce_sigs(struct channel *channel)
{
/* First 2 + 256 byte are the signatures and msg type, skip them */
const size_t offset = 258;
struct lightningd *ld = channel->peer->ld;
struct sha256_double hash;
- secp256k1_ecdsa_signature local_node_sig, local_bitcoin_sig;
struct channel_gossip *cg = channel->channel_gossip;
+ secp256k1_ecdsa_signature local_node_sig, local_bitcoin_sig;
const u8 *ca, *msg;
/* Wait until we've exchanged reestablish messages */
if (!channel->reestablished) {
log_debug(channel->log, "channel_gossip: not sending channel_announcement_sigs until reestablished");
- return;
+ return false;
}
- if (cg->sent_sigs)
- return;
-
ca = create_channel_announcement(tmpctx, channel, *channel->scid,
NULL, NULL, NULL, NULL);
@@ -722,20 +719,30 @@ static void send_channel_announce_sigs(struct channel *channel)
if (!check_signed_hash(&hash, &local_node_sig, &ld->our_pubkey)) {
channel_internal_error(channel,
"HSM returned an invalid node signature");
- return;
+ return false;
}
if (!check_signed_hash(&hash, &local_bitcoin_sig, &channel->local_funding_pubkey)) {
channel_internal_error(channel,
"HSM returned an invalid bitcoin signature");
- return;
+ return false;
}
msg = towire_announcement_signatures(NULL,
&channel->cid, *channel->scid,
&local_node_sig, &local_bitcoin_sig);
msg_to_peer(channel->peer, take(msg));
- cg->sent_sigs = true;
+ return cg->sent_sigs = true;
+}
+
+static bool send_channel_announce_sigs_once(struct channel *channel)
+{
+ struct channel_gossip *cg = channel->channel_gossip;
+
+ if (cg->sent_sigs)
+ return false;
+
+ return send_channel_announce_sigs(channel);
}
/* Sends channel_announcement */
@@ -829,7 +836,7 @@ static void set_gossip_state(struct channel *channel,
case CGOSSIP_ANNOUNCED:
/* In case this snuck up on us (fast confirmations),
* make sure we sent sigs */
- send_channel_announce_sigs(channel);
+ send_channel_announce_sigs_once(channel);
/* BOLT #7:
* A recipient node:
@@ -897,7 +904,7 @@ static void update_gossip_state(struct channel *channel)
return;
case CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS:
case CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH:
- send_channel_announce_sigs(channel);
+ send_channel_announce_sigs_once(channel);
/* fall thru */
case CGOSSIP_WAITING_FOR_SCID:
case CGOSSIP_PRIVATE:
@@ -1005,7 +1012,7 @@ void channel_gossip_got_announcement_sigs(struct channel *channel,
send_our_sigs:
/* This only works once, so we won't spam them. */
- send_channel_announce_sigs(channel);
+ send_channel_announce_sigs_once(channel);
}
/* Short channel id changed (splice, or reorg). */
@@ -1198,7 +1205,8 @@ static void channel_reestablished_stable(struct channel *channel)
}
/* Peer has connected and successfully reestablished channel. */
-void channel_gossip_channel_reestablished(struct channel *channel)
+void channel_gossip_channel_reestablished(struct channel *channel,
+ bool announcement_sigs_requested)
{
channel->reestablished = true;
tal_free(channel->stable_conn_timer);
@@ -1216,6 +1224,9 @@ void channel_gossip_channel_reestablished(struct channel *channel)
/* We can re-xmit sigs once per reconnect */
channel->channel_gossip->sent_sigs = false;
+ if (announcement_sigs_requested)
+ send_channel_announce_sigs(channel);
+
/* BOLT #7:
* - Upon reconnection (once the above timing requirements have
* been met):
@@ -1236,7 +1247,7 @@ void channel_gossip_channel_reestablished(struct channel *channel)
check_channel_gossip(channel);
return;
case CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS:
- send_channel_announce_sigs(channel);
+ send_channel_announce_sigs_once(channel);
/* fall thru */
case CGOSSIP_PRIVATE:
case CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH:
diff --git a/lightningd/channel_gossip.h b/lightningd/channel_gossip.h
index 6b8e4e2..b6fae02 100644
--- a/lightningd/channel_gossip.h
+++ b/lightningd/channel_gossip.h
@@ -43,7 +43,8 @@ void channel_gossip_update_from_gossipd(struct channel *channel,
void channel_gossip_init_done(struct lightningd *ld);
/* Peer has connected and successfully reestablished channel. */
-void channel_gossip_channel_reestablished(struct channel *channel);
+void channel_gossip_channel_reestablished(struct channel *channel,
+ bool announcement_sigs_requested);
/* Peer has disconnected */
void channel_gossip_channel_disconnect(struct channel *channel);
diff --git a/tests/plugins/channeld_fakenet.c b/tests/plugins/channeld_fakenet.c
index 81002e1..1d3cf61 100644
--- a/tests/plugins/channeld_fakenet.c
+++ b/tests/plugins/channeld_fakenet.c
@@ -1163,7 +1163,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg)
channel->htlcs = htlc_map;
status_debug("Created channel");
- daemon_conn_send(info->dc, take(towire_channeld_reestablished(NULL)));
+ daemon_conn_send(info->dc, take(towire_channeld_reestablished(NULL, false)));
return channel;
}
diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c
index 339a218..9336451 100644
--- a/wallet/test/run-wallet.c
+++ b/wallet/test/run-wallet.c
@@ -380,6 +380,9 @@ bool fromwire_onchaind_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNE
bool fromwire_openingd_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNEEDED)
{ fprintf(stderr, "fromwire_openingd_dev_memleak_reply called!\n"); abort(); }
/* Generated stub for get_network_blockheight */
+bool fromwire_channeld_reestablished(const void *p UNNEEDED, bool *leak UNNEEDED)
+{ fprintf(stderr, "fromwire_channeld_reestablished called!\n"); abort(); }
+/* Generated stub for get_network_blockheight */
u32 get_network_blockheight(const struct chain_topology *topo UNNEEDED)
{ fprintf(stderr, "get_network_blockheight called!\n"); abort(); }
/* Generated stub for hash_cid */
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.