Drop `RevokedOutput::is_counterparty_balance_on_anchors`
What changed, and why it matters
This commit removes an internal flag used when claiming funds from a revoked Lightning channel. The flag had become unused after a prior change, and removing it simplifies the code. The change intentionally breaks backward compatibility with older versions (before 0.1) to avoid ambiguous behavior with newer anchor channel types. It is a cleanup/refactoring change with a deliberate compatibility trade-off, not a fix for an active exploit.
Review downgrade policy and release notes to ensure users are aware that downgrade to LDK 0.0.115 and earlier is no longer supported for persisted `RevokedOutput` state. No immediate security patch is required, but operators should avoid mixed-version clusters with pre-0.1 nodes after upgrading.
Security signals we found
Backward-compatibility break explicitly acknowledged in commit message
Serialization change writes legacy sentinel value to influence older versions' aggregation behavior
Removes dead code tied to anchor channel revocation handling
No patch of an active vulnerability; change is defensive cleanup
Evidence from the diff
The commit drops the is_counterparty_balance_on_anchors field from RevokedOutput in LDK’s on-chain package handling. This boolean previously distinguished revoked claims from anchor channels for aggregation purposes. Since commit 0fe90c6f7c3325935b18dbc809be14afd8fe067f, aggregation decisions are based on pinnability rather than this coarse flag, making the field dead code. Serialization now always writes a legacy Some(()) value for field 14 so that older LDK versions (pre-0.1) conservatively refuse to aggregate these claims. The commit explicitly breaks downgrade to LDK 0.0.115 and earlier.
Changed components
lightning/src/chain/channelmonitor.rslightning/src/chain/package.rsLDK RevokedOutput serialization formatDowngrade compatibility to LDK 0.0.115 and earlierInspect captured patch +13 / −18
diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs
index 51a179c..882f7b5 100644
--- a/lightning/src/chain/channelmonitor.rs
+++ b/lightning/src/chain/channelmonitor.rs
@@ -4656,7 +4656,6 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
if outp.script_pubkey == revokeable_p2wsh {
let revk_outp = RevokedOutput::build(
per_commitment_point, per_commitment_key, outp.value,
- funding_spent.channel_type_features().supports_anchors_zero_fee_htlc_tx(),
funding_spent.channel_parameters.clone(), height,
);
let justice_package = PackageTemplate::build_package(
@@ -4869,9 +4868,8 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
if input.previous_output.txid == *commitment_txid && input.witness.len() == 5 && tx.output.get(idx).is_some() {
log_error!(logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}", htlc_txid, idx);
let revk_outp = RevokedOutput::build(
- per_commitment_point, per_commitment_key, tx.output[idx].value, false,
- funding_spent.channel_parameters.clone(),
- height,
+ per_commitment_point, per_commitment_key, tx.output[idx].value,
+ self.funding.channel_parameters.clone(), height,
);
let justice_package = PackageTemplate::build_package(
htlc_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp),
diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs
index b67bb1c..7b5d047 100644
--- a/lightning/src/chain/package.rs
+++ b/lightning/src/chain/package.rs
@@ -144,7 +144,6 @@ pub(crate) struct RevokedOutput {
weight: u64,
amount: Amount,
on_counterparty_tx_csv: u16,
- is_counterparty_balance_on_anchors: Option<()>,
channel_parameters: Option<ChannelTransactionParameters>,
// Added in LDK 0.1.4/0.2 and always set since.
outpoint_confirmation_height: Option<u32>,
@@ -154,7 +153,7 @@ impl RevokedOutput {
#[rustfmt::skip]
pub(crate) fn build(
per_commitment_point: PublicKey, per_commitment_key: SecretKey, amount: Amount,
- is_counterparty_balance_on_anchors: bool, channel_parameters: ChannelTransactionParameters,
+ channel_parameters: ChannelTransactionParameters,
outpoint_confirmation_height: u32,
) -> Self {
let directed_params = channel_parameters.as_counterparty_broadcastable();
@@ -170,7 +169,6 @@ impl RevokedOutput {
weight: WEIGHT_REVOKED_OUTPUT,
amount,
on_counterparty_tx_csv,
- is_counterparty_balance_on_anchors: if is_counterparty_balance_on_anchors { Some(()) } else { None },
channel_parameters: Some(channel_parameters),
outpoint_confirmation_height: Some(outpoint_confirmation_height),
}
@@ -186,7 +184,9 @@ impl_writeable_tlv_based!(RevokedOutput, {
(8, weight, required),
(10, amount, required),
(12, on_counterparty_tx_csv, required),
- (14, is_counterparty_balance_on_anchors, option),
+ // Unused since 0.1, this setting causes downgrades to before 0.1 to refuse to
+ // aggregate `RevokedOutput` claims, which is the more conservative stance.
+ (14, is_counterparty_balance_on_anchors, (legacy, (), |_| Some(()))),
(15, channel_parameters, (option: ReadableArgs, None)), // Added in 0.2.
});
@@ -1821,16 +1821,13 @@ mod tests {
#[rustfmt::skip]
macro_rules! dumb_revk_output {
- ($is_counterparty_balance_on_anchors: expr) => {
+ () => {
{
let secp_ctx = Secp256k1::new();
let dumb_scalar = SecretKey::from_slice(&<Vec<u8>>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar);
let channel_parameters = ChannelTransactionParameters::test_dummy(0);
- PackageSolvingData::RevokedOutput(RevokedOutput::build(
- dumb_point, dumb_scalar, Amount::ZERO, $is_counterparty_balance_on_anchors,
- channel_parameters, 0,
- ))
+ PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, dumb_scalar, Amount::ZERO, channel_parameters, 0))
}
}
}
@@ -2132,9 +2129,9 @@ mod tests {
#[test]
#[rustfmt::skip]
fn test_package_split_malleable() {
- let revk_outp_one = dumb_revk_output!(false);
- let revk_outp_two = dumb_revk_output!(false);
- let revk_outp_three = dumb_revk_output!(false);
+ let revk_outp_one = dumb_revk_output!();
+ let revk_outp_two = dumb_revk_output!();
+ let revk_outp_three = dumb_revk_output!();
let mut package_one = PackageTemplate::build_package(fake_txid(1), 0, revk_outp_one, 1100);
let package_two = PackageTemplate::build_package(fake_txid(1), 1, revk_outp_two, 1100);
@@ -2166,7 +2163,7 @@ mod tests {
#[test]
fn test_package_timer() {
- let revk_outp = dumb_revk_output!(false);
+ let revk_outp = dumb_revk_output!();
let mut package = PackageTemplate::build_package(fake_txid(1), 0, revk_outp, 1000);
assert_eq!(package.timer(), 0);
@@ -2190,7 +2187,7 @@ mod tests {
let weight_sans_output = (4 + 4 + 1 + 36 + 4 + 1 + 1 + 8 + 1) * WITNESS_SCALE_FACTOR as u64 + 2;
{
- let revk_outp = dumb_revk_output!(false);
+ let revk_outp = dumb_revk_output!();
let package = PackageTemplate::build_package(fake_txid(1), 0, revk_outp, 0);
assert_eq!(package.package_weight(&ScriptBuf::new()), weight_sans_output + WEIGHT_REVOKED_OUTPUT);
}
Why this scored 32/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.