Use `SINGLE|ACP` sighash on signatures from peer in 0FC channels
What changed, and why it matters
This commit fixes how a Lightning node validates signatures from its channel peer for a newer type of channel (0FC, or 'zero-fee commitments'). Previously, the code expected the older SIGHASH_ALL signature type for these channels, which would cause the node to reject valid peer signatures or fail to build valid transactions. The fix makes the code use SIGHASH_SINGLE|SIGHASH_ANYONECANPAY for 0FC channels, matching the protocol design. A mismatch here could prevent correct operation or, in the worst case, be exploited to make a node accept an invalid transaction state, though the commit itself is small and defensive.
Review related 0FC channel specification and test vectors to confirm SIGHASH_SINGLE|SIGHASH_ANYONECANPAY is the intended sighash for all HTLC signatures in zero-fee commitment channels. Add regression tests covering signature validation and HTLC claim transaction construction for 0FC channels. Consider whether any deployed nodes need to resync or revalidate prior 0FC channel states.
Security signals we found
Signature hash type mismatch between channel variants
Peer-provided signature handling changed for newer channel type
Defensive alignment of sighash policy across signer implementations
No explicit vulnerability language in commit message
Evidence from the diff
The change extends the existing conditional that selects EcdsaSighashType::SinglePlusAnyoneCanPay (SIGHASH_SINGLE|SIGHASH_ANYONECANPAY) to also apply when the channel type supports anchor_zero_fee_commitments (0FC), not only anchors_zero_fee_htlc_tx. This affects HTLC input witness construction, HTLC signature validation in Channel, and signing in InMemorySigner and TestChannelSigner. The prior code would have used SIGHASH_ALL for 0FC channels, which is inconsistent with the expected signature hash type for these channels and could lead to signature verification failures or incorrect transaction assembly.
Changed components
lightning/src/ln/chan_utils.rslightning/src/ln/channel.rslightning/src/sign/mod.rslightning/src/util/test_channel_signer.rsInspect captured patch +12 / −4
diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs
index cb01318..be724a2 100644
--- a/lightning/src/ln/chan_utils.rs
+++ b/lightning/src/ln/chan_utils.rs
@@ -869,7 +869,9 @@ pub fn build_htlc_input_witness(
local_sig: &Signature, remote_sig: &Signature, preimage: &Option<PaymentPreimage>,
redeem_script: &Script, channel_type_features: &ChannelTypeFeatures,
) -> Witness {
- let remote_sighash_type = if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ let remote_sighash_type = if channel_type_features.supports_anchors_zero_fee_htlc_tx()
+ || channel_type_features.supports_anchor_zero_fee_commitments()
+ {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 6d8c310..96fec49 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -4671,7 +4671,9 @@ where
let htlc_redeemscript =
chan_utils::get_htlc_redeemscript(&htlc, funding.get_channel_type(), &holder_keys);
- let htlc_sighashtype = if funding.get_channel_type().supports_anchors_zero_fee_htlc_tx()
+ let channel_type = funding.get_channel_type();
+ let htlc_sighashtype = if channel_type.supports_anchors_zero_fee_htlc_tx()
+ || channel_type.supports_anchor_zero_fee_commitments()
{
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 6d6b819..f9db5ff 100644
--- a/lightning/src/sign/mod.rs
+++ b/lightning/src/sign/mod.rs
@@ -1472,7 +1472,9 @@ impl EcdsaChannelSigner for InMemorySigner {
&keys.revocation_key,
);
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, chan_type, &keys);
- let htlc_sighashtype = if chan_type.supports_anchors_zero_fee_htlc_tx() {
+ let htlc_sighashtype = if chan_type.supports_anchors_zero_fee_htlc_tx()
+ || chan_type.supports_anchor_zero_fee_commitments()
+ {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs
index 0b472b9..4d9bf24 100644
--- a/lightning/src/util/test_channel_signer.rs
+++ b/lightning/src/util/test_channel_signer.rs
@@ -386,7 +386,9 @@ impl EcdsaChannelSigner for TestChannelSigner {
let channel_parameters =
&htlc_descriptor.channel_derivation_parameters.transaction_parameters;
let channel_type_features = &channel_parameters.channel_type_features;
- let sighash_type = if channel_type_features.supports_anchors_zero_fee_htlc_tx() {
+ let sighash_type = if channel_type_features.supports_anchors_zero_fee_htlc_tx()
+ || channel_type_features.supports_anchor_zero_fee_commitments()
+ {
EcdsaSighashType::SinglePlusAnyoneCanPay
} else {
EcdsaSighashType::All
Why this scored 57/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.