Assume splicing input value from channel parameters
What changed, and why it matters
This commit changes how a Bitcoin Lightning channel signer calculates the value of the old funding output when signing a splice transaction. Instead of accepting the value as a caller-provided argument, it now derives the value from the channel's own stored parameters and adds a check that the transaction input actually matches the expected funding outpoint. This removes an opportunity for a caller to pass a wrong or manipulated input value, which could otherwise cause the signer to produce an invalid or subtly harmful signature.
Treat as a defensive hardening patch. Review any custom implementations of `EcdsaChannelSigner` outside the repository to ensure they adopt the new signature and derive the splice input value from channel parameters rather than caller input. Consider whether the new assertion could introduce a denial-of-service risk if channel parameters and the splice input become temporarily inconsistent, and add tests for mismatched outpoint/value cases.
Security signals we found
API hardening: removes externally supplied `input_value` in favor of internally derived channel value
New assertion ties the splice input to the channel's recorded funding outpoint
Prevents potential mismatch between claimed input value and actual channel funding amount during splicing signature hash computation
Splicing is a newer, complex Lightning protocol feature with historically higher bug density
Evidence from the diff
The sign_splicing_funding_input method on EcdsaChannelSigner no longer takes input_value: u64. The implementation in InMemorySigner now asserts that tx.input[input_index].previous_output equals channel_parameters.funding_outpoint, then uses channel_parameters.channel_value_satoshis as the amount for the P2WSH signature hash. The dynamic signer delegate and test signer are updated to match the new signature. This hardens the API by preventing external callers from influencing the splice input amount and by tying the signed input to the channel’s known funding outpoint.
Changed components
lightning/src/sign/ecdsa.rslightning/src/sign/mod.rslightning/src/util/dyn_signer.rslightning/src/util/test_channel_signer.rsInspect captured patch +18 / −15
diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs
index f9c330b..a25d5df 100644
--- a/lightning/src/sign/ecdsa.rs
+++ b/lightning/src/sign/ecdsa.rs
@@ -247,15 +247,17 @@ pub trait EcdsaChannelSigner: ChannelSigner {
/// In splicing, the previous funding transaction output is spent as the input of
/// the new funding transaction, and is a 2-of-2 multisig.
///
+ /// `channel_parameters`: The [`ChannelTransactionParameters`] for the channel's current funding
+ /// transaction that is being spent in the splice transaction to sign. A new set of
+ /// [`ChannelTransactionParameters`] will become available for the new funding transaction.
+ ///
/// `input_index`: The index of the input within the new funding transaction `tx`,
/// spending the previous funding transaction's output
///
- /// `input_value`: The value of the previous funding transaction output.
- ///
/// This method is *not* asynchronous. If an `Err` is returned, the channel will be immediately
/// closed.
fn sign_splicing_funding_input(
&self, channel_parameters: &ChannelTransactionParameters, tx: &Transaction,
- input_index: usize, input_value: u64, secp_ctx: &Secp256k1<secp256k1::All>,
+ input_index: usize, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()>;
}
diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs
index 057a740..abdb03c 100644
--- a/lightning/src/sign/mod.rs
+++ b/lightning/src/sign/mod.rs
@@ -1755,9 +1755,17 @@ impl EcdsaChannelSigner for InMemorySigner {
fn sign_splicing_funding_input(
&self, channel_parameters: &ChannelTransactionParameters, tx: &Transaction,
- input_index: usize, input_value: u64, secp_ctx: &Secp256k1<secp256k1::All>,
+ input_index: usize, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated");
+ assert_eq!(
+ tx.input[input_index].previous_output,
+ channel_parameters
+ .funding_outpoint
+ .as_ref()
+ .expect("Funding outpoint must be known prior to signing")
+ .into_bitcoin_outpoint()
+ );
let funding_key = self.funding_key(channel_parameters.splice_parent_funding_txid);
let funding_pubkey = funding_key.public_key(secp_ctx);
@@ -1769,7 +1777,7 @@ impl EcdsaChannelSigner for InMemorySigner {
.p2wsh_signature_hash(
input_index,
&funding_redeemscript,
- Amount::from_sat(input_value),
+ Amount::from_sat(channel_parameters.channel_value_satoshis),
EcdsaSighashType::All,
)
.unwrap()[..];
diff --git a/lightning/src/util/dyn_signer.rs b/lightning/src/util/dyn_signer.rs
index fc2f632..baa4eed 100644
--- a/lightning/src/util/dyn_signer.rs
+++ b/lightning/src/util/dyn_signer.rs
@@ -160,8 +160,7 @@ delegate!(DynSigner, EcdsaChannelSigner, inner,
fn sign_holder_htlc_transaction(, htlc_tx: &Transaction, input: usize,
htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1<All>) -> Result<Signature, ()>,
fn sign_splicing_funding_input(, channel_parameters: &ChannelTransactionParameters,
- tx: &Transaction, input_index: usize, input_value: u64,
- secp_ctx: &Secp256k1<All>) -> Result<Signature, ()>
+ tx: &Transaction, input_index: usize, secp_ctx: &Secp256k1<All>) -> Result<Signature, ()>
);
delegate!(DynSigner, ChannelSigner,
diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs
index 04b2482..7e89650 100644
--- a/lightning/src/util/test_channel_signer.rs
+++ b/lightning/src/util/test_channel_signer.rs
@@ -486,15 +486,9 @@ impl EcdsaChannelSigner for TestChannelSigner {
fn sign_splicing_funding_input(
&self, channel_parameters: &ChannelTransactionParameters, tx: &Transaction,
- input_index: usize, input_value: u64, secp_ctx: &Secp256k1<secp256k1::All>,
+ input_index: usize, secp_ctx: &Secp256k1<secp256k1::All>,
) -> Result<Signature, ()> {
- self.inner.sign_splicing_funding_input(
- channel_parameters,
- tx,
- input_index,
- input_value,
- secp_ctx,
- )
+ self.inner.sign_splicing_funding_input(channel_parameters, tx, input_index, secp_ctx)
}
}
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.