Fix incorrect weight estimation for splice shared input
What changed, and why it matters
This commit fixes a bug in how the Lightning Dev Kit estimates the transaction weight (and thus the required transaction fee) when splicing a channel. Previously, the code estimated the weight of the shared splice input using a generic function based on the previous funding output type, which could produce an incorrect value. The fix explicitly builds the weight from the base input fields, an empty script signature, and the expected multisig witness. If the estimate was too low, the resulting transaction could have paid an insufficient fee and failed to confirm or propagate; if too high, funds could be overpaid as fees. The included test changes show the corrected fee math changes the expected change output amount.
Review the fixed weight formula against all supported channel types and complete the TODO for Taproot/single-sig channel types. Run the updated splicing tests and consider adding edge-case tests for minimum-fee splice transactions to ensure the estimate is never below the actual weight.
Security signals we found
Incorrect transaction weight estimation can lead to underpayment or overpayment of miner fees
Shared splice input uses a fixed 2-of-2 multisig witness weight assumption
Fee miscalculation could cause a splice transaction to be rejected by the network or to lock up funds temporarily
TODO indicates weight model is incomplete for non-multisig channel types such as Taproot
Evidence from the diff
In rust-lightning’s interactive transaction construction for splicing, InputOwned::Shared’s estimate_input_weight previously called estimate_input_weight(&shared.prev_output), which did not correctly model the shared funding input’s actual witness structure. The patch replaces it with an explicit sum of BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT, matching the structure of a 2-of-2 multisig funding input. The same explicit components are added in calculate_change_output_value when is_splice is true. A TODO notes this still needs adjustment for different channel types (e.g., Taproot). Tests are updated to reflect the corrected change output value (13979 sats instead of 14146) and a larger input amount to keep the scenario valid.
Changed components
lightning/src/ln/interactivetxs.rslightning/src/ln/splicing_tests.rsInputOwned::Shared weight estimationcalculate_change_output_value fee computationInspect captured patch +8 / −3
diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs
index 5212773..563a894 100644
--- a/lightning/src/ln/interactivetxs.rs
+++ b/lightning/src/ln/interactivetxs.rs
@@ -1639,7 +1639,10 @@ impl InputOwned {
fn estimate_input_weight(&self) -> Weight {
match self {
InputOwned::Single(single) => estimate_input_weight(&single.prev_output),
- InputOwned::Shared(shared) => estimate_input_weight(&shared.prev_output),
+ // TODO(taproot): Needs to consider different weights based on channel type
+ InputOwned::Shared(_) => Weight::from_wu(
+ BASE_INPUT_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT,
+ ),
}
}
@@ -2156,6 +2159,8 @@ pub(super) fn calculate_change_output_value(
weight = weight.saturating_add(TX_COMMON_FIELDS_WEIGHT);
if is_splice {
// TODO(taproot): Needs to consider different weights based on channel type
+ weight = weight.saturating_add(BASE_INPUT_WEIGHT);
+ weight = weight.saturating_add(EMPTY_SCRIPT_SIG_WEIGHT);
weight = weight.saturating_add(FUNDING_TRANSACTION_WITNESS_WEIGHT);
}
}
@@ -3147,7 +3152,7 @@ mod tests {
// Provide and expect a shared input
do_test_interactive_tx_constructor(TestSession {
description: "Provide and expect a shared input",
- inputs_a: generate_inputs(&[TestOutput::P2WPKH(50_000)]),
+ inputs_a: generate_inputs(&[TestOutput::P2WPKH(100_000)]),
a_shared_input: Some(generate_shared_input(&prev_funding_tx_1, 0, 60_000)),
shared_output_a: generate_funding_txout(108_000, 108_000),
outputs_a: vec![],
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index bec2f4c..e822b8f 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -235,7 +235,7 @@ fn test_v1_splice_in() {
assert_eq!(tx_add_output_msg.sats, post_splice_channel_value);
} else {
assert!(tx_add_output_msg.script.is_p2wpkh());
- assert_eq!(tx_add_output_msg.sats, 14146); // extra_splice_funding_input_sats - splice_in_sats
+ assert_eq!(tx_add_output_msg.sats, 13979); // extra_splice_funding_input_sats - splice_in_sats
}
let _res = acceptor_node
Why this scored 59/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.