Adjust the weight of htlc success and timeout witnesses in 0FC channels
What changed, and why it matters
This commit updates how transaction weight (a measure of data size) is estimated for Lightning channel fee bumping when handling HTLCs (conditional Bitcoin payments). It distinguishes between two newer channel types: one using 'keyed' anchors and one using 'pay-to-anchor' (P2A) outputs. The P2A variant is slightly lighter because it removes a 1-block CSV delay. The change is a correctness fix for fee estimation; if the weight were wrong, transactions could be under- or over-paying fees, potentially causing delays or stuck funds, but it is not a direct theft or key-leak vulnerability.
Review related fee-bumping paths to ensure all weight estimates are consistent with the new P2A and keyed anchor channel types; run tests covering both channel variants for HTLC claim/timeout transactions.
Security signals we found
Transaction fee/weight estimation change for on-chain settlement path
Different constants for keyed vs P2A anchor channel variants
Removal of 1 CSV lock in P2A path changes witness weight
Panic fallback if channel type is neither expected zero-fee variant
Evidence from the diff
The patch splits the previous HTLC_*_INPUT_ANCHOR_WITNESS_WEIGHT constants into separate KEYED_ANCHOR and P2A_ANCHOR versions. In bump_transaction, it selects the correct pair based on whether the channel uses anchor_zero_fee_commitments or anchors_zero_fee_htlc_tx. The P2A weights are 3 units lower than the keyed weights because P2A HTLC outputs no longer carry the 1 CSV lock. This affects fee-bumping calculations for HTLC success/timeout transactions.
Changed components
lightning/src/events/bump_transaction/mod.rslightning/src/ln/chan_utils.rsInspect captured patch +28 / −9
diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs
index a5e0874..4d6220b 100644
--- a/lightning/src/events/bump_transaction/mod.rs
+++ b/lightning/src/events/bump_transaction/mod.rs
@@ -24,7 +24,8 @@ use crate::io_extras::sink;
use crate::ln::chan_utils;
use crate::ln::chan_utils::{
shared_anchor_script_pubkey, HTLCOutputInCommitment, ANCHOR_INPUT_WITNESS_WEIGHT,
- HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT,
+ HTLC_SUCCESS_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_P2A_ANCHOR_WITNESS_WEIGHT,
+ HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT, HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT,
P2A_ANCHOR_INPUT_WITNESS_WEIGHT,
};
use crate::ln::types::ChannelId;
@@ -864,6 +865,20 @@ where
output: vec![],
};
let mut must_spend = Vec::with_capacity(htlc_descriptors.len());
+ let (htlc_success_witness_weight, htlc_timeout_witness_weight) =
+ if channel_type.supports_anchor_zero_fee_commitments() {
+ (
+ HTLC_SUCCESS_INPUT_P2A_ANCHOR_WITNESS_WEIGHT,
+ HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT,
+ )
+ } else if channel_type.supports_anchors_zero_fee_htlc_tx() {
+ (
+ HTLC_SUCCESS_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT,
+ HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT,
+ )
+ } else {
+ panic!("channel type should be either zero-fee HTLCs, or zero-fee commitments");
+ };
for htlc_descriptor in htlc_descriptors {
let htlc_input = htlc_descriptor.unsigned_tx_input();
must_spend.push(Input {
@@ -871,9 +886,9 @@ where
previous_utxo: htlc_descriptor.previous_utxo(&self.secp),
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT
+ if htlc_descriptor.preimage.is_some() {
- HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT
+ htlc_success_witness_weight
} else {
- HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
+ htlc_timeout_witness_weight
},
});
htlc_tx.input.push(htlc_input);
diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs
index 0a5e372..7c02078 100644
--- a/lightning/src/ln/chan_utils.rs
+++ b/lightning/src/ln/chan_utils.rs
@@ -95,12 +95,16 @@ pub const P2A_ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 1;
/// The maximum value of a P2A anchor.
pub const P2A_MAX_VALUE: u64 = 240;
-/// The upper bound weight of an HTLC timeout input from a commitment transaction with anchor
-/// outputs.
-pub const HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 288;
-/// The upper bound weight of an HTLC success input from a commitment transaction with anchor
-/// outputs.
-pub const HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 327;
+/// The upper bound weight of an HTLC timeout input from a commitment transaction with keyed anchor outputs.
+pub const HTLC_TIMEOUT_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT: u64 = 288;
+/// The upper bound weight of an HTLC timeout input from a commitment transaction with a p2a anchor output.
+/// Note the corresponding outputs no longer have the 1 CSV lock.
+pub const HTLC_TIMEOUT_INPUT_P2A_ANCHOR_WITNESS_WEIGHT: u64 = 285;
+/// The upper bound weight of an HTLC success input from a commitment transaction with keyed anchor outputs.
+pub const HTLC_SUCCESS_INPUT_KEYED_ANCHOR_WITNESS_WEIGHT: u64 = 327;
+/// The upper bound weight of an HTLC success input from a commitment transaction with a p2a anchor output.
+/// Note the corresponding outputs no longer have the 1 CSV lock.
+pub const HTLC_SUCCESS_INPUT_P2A_ANCHOR_WITNESS_WEIGHT: u64 = 324;
/// The size of the 2-of-2 multisig script
const MULTISIG_SCRIPT_SIZE: u64 = 1 + // OP_2
Why this scored 42/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.