Use `FundingScope` spent when signing watchtower justice transactions
What changed, and why it matters
This patch fixes a bug in how a Lightning node signs 'justice' transactions—on-chain penalty transactions that reclaim funds when a counterparty tries to cheat. Before the fix, the code always used the current funding channel parameters when signing, but with splicing there can be multiple funding scopes. If the wrong parameters were used, the justice transaction signature could be invalid, potentially preventing the node from successfully penalizing a cheating counterparty in a spliced channel. The fix looks up the correct funding scope based on which commitment transaction is being claimed.
Review and merge this patch if not already applied. Test justice transaction signing across splice operations where the counterparty commitment number exists in both old and new funding scopes. Verify that update_persisted_channel applies monitor state before this function is called, as the comment assumes.
Security signals we found
Invalid signature generation for revoked commitment justice transactions in spliced channels
Potential failure to claim revoked counterparty outputs after a splice
Channel parameter mismatch between current funding scope and historical commitment transaction
Splicing-specific state handling bug
Evidence from the diff
In ChannelMonitorImpl::sign_justice_revoked_output, the code previously always used self.funding.channel_parameters when calling sign_justice_revoked_output. With splicing, multiple counterparty commitment transactions can exist for the same commitment number across different FundingScope instances (self.funding and self.pending_funding). The patch now resolves the correct FundingScope by matching the justice_tx input’s previous_output.txid against counterparty_claimable_outpoints in each funding scope, and returns Err if no match is found. This ensures the signer receives the channel_parameters corresponding to the actual commitment transaction being revoked.
Changed components
lightning/src/chain/channelmonitor.rsChannelMonitorImpl::sign_justice_revoked_outputEcdsaChannelSigner::sign_justice_revoked_outputFundingScopesplicing logicInspect captured patch +15 / −1
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index 15bd651..38eed18 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -2125,6 +2125,10 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitor<Signer> {
/// to the commitment transaction being revoked, this will return a signed transaction, but
/// the signature will not be valid.
///
+ /// Note that due to splicing, this can also return an `Err` when the counterparty commitment
+ /// this transaction is attempting to claim is no longer valid because the corresponding funding
+ /// transaction was spliced.
+ ///
/// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_justice_revoked_output
/// [`Persist`]: crate::chain::chainmonitor::Persist
#[rustfmt::skip]
@@ -4285,7 +4289,17 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey,
self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key);
- let channel_parameters = &self.funding.channel_parameters;
+ let commitment_txid = &justice_tx.input[input_idx].previous_output.txid;
+ // Since there may be multiple counterparty commitment transactions for the same commitment
+ // number due to splicing, we have to locate the matching `FundingScope::channel_parameters`
+ // to provide the signer. Since this is intended to be called during
+ // `Persist::update_persisted_channel`, the monitor should have already had the update
+ // applied.
+ let channel_parameters = core::iter::once(&self.funding)
+ .chain(&self.pending_funding)
+ .find(|funding| funding.counterparty_claimable_outpoints.contains_key(commitment_txid))
+ .map(|funding| &funding.channel_parameters)
+ .ok_or(())?;
let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output(
&channel_parameters, &justice_tx, input_idx, value, &per_commitment_key,
&self.onchain_tx_handler.secp_ctx,
Why this scored 56/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.