openingd/dualopend: don't allow peer not to send channel_type.
What changed, and why it matters
This change makes Core Lightning stricter during Lightning channel opening: it now refuses to open a channel if the other peer does not include a 'channel_type' field. Previously, Core Lightning would guess a default channel type when the peer omitted it. The commit author frames this as a simplification, but it also removes a fallback path that could have led to mismatched assumptions between nodes about what kind of channel they were creating.
Treat as a hardening/protocol-compliance change rather than an active vulnerability. Review whether any deployed peers still omit channel_type and would now fail channel opens. Monitor release notes and upstream discussion for any security advisory or CVE assignment.
Security signals we found
Removal of default/fallback channel type selection
New mandatory validation requiring peer to send channel_type
Potential downgrade or ambiguity risk from previous defaulting behavior
No explicit security framing by the vendor in commit message
Evidence from the diff
The patch removes fallback logic in openingd and dualopend that constructed a default channel_type when the peer did not send one in open_channel, open_channel2, or accept_channel2 messages. It now calls negotiation_failed() instead. It also removes the intuit_scid_alias_type() compatibility heuristic that depended on whether the peer sent channel_type. The change aligns with BOLT requirements that make channel_type compulsory once the feature is negotiated, but the commit message does not explicitly describe it as a security fix.
Changed components
openingd/dualopend.copeningd/openingd.cLightning channel opening protocol handlingInspect captured patch +58 / −66
diff --git a/openingd/dualopend.c b/openingd/dualopend.c
index b3e687fd..616df5dd 100644
--- a/openingd/dualopend.c
+++ b/openingd/dualopend.c
@@ -2434,30 +2434,30 @@ static void accepter_start(struct state *state, const u8 *oc2_msg)
* - if `type` is not suitable.
* - if `type` includes `option_zeroconf` and it does not trust the sender to open an unconfirmed channel.
*/
- if (open_tlv->channel_type) {
- state->channel_type =
- channel_type_accept(state,
- open_tlv->channel_type,
- state->our_features);
- if (!state->channel_type) {
- if (state->dev_accept_any_channel_type) {
- status_unusual("dev-any-channel-type: accepting %s",
- fmt_featurebits(tmpctx,
- open_tlv->channel_type));
- state->channel_type = channel_type_from(state, open_tlv->channel_type);
- } else {
- negotiation_failed(state,
- "Did not support channel_type [%s]",
- fmt_featurebits(tmpctx,
- open_tlv->channel_type));
- return;
- }
+ if (!open_tlv->channel_type) {
+ negotiation_failed(state,
+ "open_channel2 missing channel_type");
+ return;
+ }
+
+ state->channel_type =
+ channel_type_accept(state,
+ open_tlv->channel_type,
+ state->our_features);
+ if (!state->channel_type) {
+ if (state->dev_accept_any_channel_type) {
+ status_unusual("dev-any-channel-type: accepting %s",
+ fmt_featurebits(tmpctx,
+ open_tlv->channel_type));
+ state->channel_type = channel_type_from(state, open_tlv->channel_type);
+ } else {
+ negotiation_failed(state,
+ "Did not support channel_type [%s]",
+ fmt_featurebits(tmpctx,
+ open_tlv->channel_type));
+ return;
}
- } else
- state->channel_type
- = default_channel_type(state,
- state->our_features,
- state->their_features);
+ }
/* Since anchor outputs are optional, we
* only support liquidity ads if those are enabled. */
@@ -3164,9 +3164,13 @@ static void opener_start(struct state *state, u8 *msg)
* `open_channel`, and they are not equal types:
* - MUST fail the channel.
*/
- if (a_tlv->channel_type
- && !featurebits_eq(a_tlv->channel_type,
- state->channel_type->features)) {
+ if (!a_tlv->channel_type) {
+ negotiation_failed(state,
+ "Missing channel_type in accept_channel2");
+ return;
+ }
+ if (!featurebits_eq(a_tlv->channel_type,
+ state->channel_type->features)) {
negotiation_failed(state,
"Return unoffered channel_type: %s",
fmt_featurebits(tmpctx,
diff --git a/openingd/openingd.c b/openingd/openingd.c
index 601fcab2..f4361770 100644
--- a/openingd/openingd.c
+++ b/openingd/openingd.c
@@ -270,17 +270,12 @@ static void set_remote_upfront_shutdown(struct state *state,
/* Since we can't send OPT_SCID_ALIAS due to compat issues, intuit whether
* we really actually want it anyway, we just can't say that. */
-static bool intuit_scid_alias_type(struct state *state, u8 channel_flags,
- bool peer_sent_channel_type)
+static bool intuit_scid_alias_type(struct state *state, u8 channel_flags)
{
/* Don't need to intuit if actually set */
if (channel_type_has(state->channel_type, OPT_SCID_ALIAS))
return false;
- /* Old clients didn't send channel_type at all */
- if (!peer_sent_channel_type)
- return false;
-
/* Modern peer: no intuit hacks necessary. */
if (channel_type_has(state->channel_type, OPT_ANCHORS_ZERO_FEE_HTLC_TX))
return false;
@@ -446,14 +441,13 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
* `open_channel`, and they are not equal types:
* - MUST fail the channel.
*/
+ if (!accept_tlvs->channel_type) {
+ negotiation_failed(state,
+ "accept_channel without a channel_type");
+ }
/* Simple case: caller specified, don't allow any variants */
if (ctype) {
- if (!accept_tlvs->channel_type) {
- negotiation_failed(state,
- "They replied without a channel_type");
- return NULL;
- }
if (!featurebits_eq(accept_tlvs->channel_type, state->channel_type->features)) {
negotiation_failed(state,
"Return unoffered channel_type: %s",
@@ -461,7 +455,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
accept_tlvs->channel_type));
return NULL;
}
- } else if (accept_tlvs->channel_type) {
+ } else {
/* Except that v23.05 could set OPT_SCID_ALIAS in reply! */
struct channel_type *atype;
@@ -587,8 +581,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
tal_hex(tmpctx, funding_output_script));
/* Backwards/cross compat hack */
- if (!ctype && intuit_scid_alias_type(state, channel_flags,
- accept_tlvs->channel_type != NULL)) {
+ if (!ctype && intuit_scid_alias_type(state, channel_flags)) {
channel_type_set_scid_alias(state->channel_type);
}
@@ -902,7 +895,6 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
struct tlv_accept_channel_tlvs *accept_tlvs;
struct tlv_open_channel_tlvs *open_tlvs;
struct amount_sat *reserve;
- bool open_channel_had_channel_type;
/* BOLT #2:
*
@@ -943,30 +935,27 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
* - if `type` is not suitable.
* - if `type` includes `option_zeroconf` and it does not trust the sender to open an unconfirmed channel.
*/
- if (open_tlvs->channel_type) {
- open_channel_had_channel_type = true;
- state->channel_type = channel_type_accept(
- state, open_tlvs->channel_type, state->our_features);
- if (!state->channel_type) {
- if (state->dev_accept_any_channel_type) {
- status_unusual("dev-any-channel-type: accepting %s",
- fmt_featurebits(tmpctx,
- open_tlvs->channel_type));
- state->channel_type = channel_type_from(state, open_tlvs->channel_type);
- } else {
- negotiation_failed(state,
- "Did not support channel_type [%s]",
- fmt_featurebits(tmpctx,
- open_tlvs->channel_type));
- return NULL;
- }
+ /* option_channel_type is compulsory. */
+ if (!open_tlvs->channel_type) {
+ negotiation_failed(state,
+ "Did not set channel_type in open_channel message");
+ }
+
+ state->channel_type = channel_type_accept(
+ state, open_tlvs->channel_type, state->our_features);
+ if (!state->channel_type) {
+ if (state->dev_accept_any_channel_type) {
+ status_unusual("dev-any-channel-type: accepting %s",
+ fmt_featurebits(tmpctx,
+ open_tlvs->channel_type));
+ state->channel_type = channel_type_from(state, open_tlvs->channel_type);
+ } else {
+ negotiation_failed(state,
+ "Did not support channel_type [%s]",
+ fmt_featurebits(tmpctx,
+ open_tlvs->channel_type));
+ return NULL;
}
- } else {
- open_channel_had_channel_type = false;
- state->channel_type
- = default_channel_type(state,
- state->our_features,
- state->their_features);
}
/* BOLT #2:
@@ -1191,8 +1180,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
fmt_channel_id(msg, &id_in));
/* Backwards/cross compat hack */
- if (intuit_scid_alias_type(state, channel_flags,
- open_channel_had_channel_type)) {
+ if (intuit_scid_alias_type(state, channel_flags)) {
channel_type_set_scid_alias(state->channel_type);
}
Why this scored 44/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.