Add funding redeem script to `ChannelDetails` and `ChannelPending` event
What changed, and why it matters
This commit adds a new piece of public information—the funding redeem script—to two existing data structures that describe Lightning channels. It does not change how funds are secured, how transactions are signed, or how peers communicate. It simply lets downstream wallet software reconstruct the channel's funding transaction output so it can calculate fees more accurately when splicing funds into a channel. There is no indication this fixes a vulnerability or introduces a new attack path.
No security action required. Treat as a normal API enhancement. Reviewers may want to confirm that the new `get_funding_output()` helper is not used in any security-critical path where a stale value during splicing could cause incorrect behavior, but the commit itself does not introduce such usage.
Security signals we found
No change to signing, key generation, transaction validation, or peer protocol logic
New field is optional and backward-compatible via TLV serialization
Helper only converts already-known public data into a `TxOut`
Commit message frames change as a wallet convenience, not as a security fix
Evidence from the diff
The patch exposes funding_redeem_script in ChannelDetails and in the ChannelPending event, and adds a helper ChannelDetails::get_funding_output() that builds the corresponding TxOut. Serialization is updated with a new optional TLV field (tag 47 for ChannelDetails, tag 9 for ChannelPending). The redeem script is derived from the existing holder and counterparty funding pubkeys via make_funding_redeemscript_opt(). Test and fuzz fixtures are updated to populate the new field. No cryptographic, network, or consensus logic is changed.
Changed components
lightning/src/ln/channel_state.rs (ChannelDetails struct and serialization)lightning/src/events/mod.rs (ChannelPending event and serialization)lightning/src/ln/chan_utils.rs (funding redeem script helper)lightning/src/ln/channelmanager.rs (event emission macro)lightning/src/routing/router.rs and fuzz/src/router.rs (test/bench fixtures)Inspect captured patch +61 / −5
diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs
index a4fb00e..af29a02 100644
--- a/fuzz/src/router.rs
+++ b/fuzz/src/router.rs
@@ -229,6 +229,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
index: 0,
}),
+ funding_redeem_script: None,
channel_type: None,
short_channel_id: Some(scid),
inbound_scid_alias: None,
diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs
index 9f7e4c5..3c52016 100644
--- a/lightning/src/events/mod.rs
+++ b/lightning/src/events/mod.rs
@@ -1413,6 +1413,10 @@ pub enum Event {
///
/// Will be `None` for channels created prior to LDK version 0.0.122.
channel_type: Option<ChannelTypeFeatures>,
+ /// The witness script that is used to lock the channel's funding output to commitment transactions.
+ ///
+ /// This field will be `None` for objects serialized with LDK versions prior to 0.2.0.
+ funding_redeem_script: Option<ScriptBuf>,
},
/// Used to indicate that a channel with the given `channel_id` is ready to be used. This event
/// is emitted when
@@ -2234,6 +2238,7 @@ impl Writeable for Event {
ref counterparty_node_id,
ref funding_txo,
ref channel_type,
+ ref funding_redeem_script,
} => {
31u8.write(writer)?;
write_tlv_fields!(writer, {
@@ -2243,6 +2248,7 @@ impl Writeable for Event {
(4, former_temporary_channel_id, required),
(6, counterparty_node_id, required),
(8, funding_txo, required),
+ (9, funding_redeem_script, option),
});
},
&Event::ConnectionNeeded { .. } => {
@@ -2815,6 +2821,7 @@ impl MaybeReadable for Event {
let mut counterparty_node_id = RequiredWrapper(None);
let mut funding_txo = RequiredWrapper(None);
let mut channel_type = None;
+ let mut funding_redeem_script = None;
read_tlv_fields!(reader, {
(0, channel_id, required),
(1, channel_type, option),
@@ -2822,6 +2829,7 @@ impl MaybeReadable for Event {
(4, former_temporary_channel_id, required),
(6, counterparty_node_id, required),
(8, funding_txo, required),
+ (9, funding_redeem_script, option),
});
Ok(Some(Event::ChannelPending {
@@ -2831,6 +2839,7 @@ impl MaybeReadable for Event {
counterparty_node_id: counterparty_node_id.0.unwrap(),
funding_txo: funding_txo.0.unwrap(),
channel_type,
+ funding_redeem_script,
}))
};
f()
diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs
index e70d935..431fdd2 100644
--- a/lightning/src/ln/chan_utils.rs
+++ b/lightning/src/ln/chan_utils.rs
@@ -1121,12 +1121,17 @@ impl ChannelTransactionParameters {
}
}
- #[rustfmt::skip]
pub(crate) fn make_funding_redeemscript(&self) -> ScriptBuf {
- make_funding_redeemscript(
- &self.holder_pubkeys.funding_pubkey,
- &self.counterparty_parameters.as_ref().unwrap().pubkeys.funding_pubkey
- )
+ self.make_funding_redeemscript_opt().unwrap()
+ }
+
+ pub(crate) fn make_funding_redeemscript_opt(&self) -> Option<ScriptBuf> {
+ self.counterparty_parameters.as_ref().map(|p| {
+ make_funding_redeemscript(
+ &self.holder_pubkeys.funding_pubkey,
+ &p.pubkeys.funding_pubkey,
+ )
+ })
}
/// Returns the counterparty's pubkeys.
diff --git a/lightning/src/ln/channel_state.rs b/lightning/src/ln/channel_state.rs
index c28b468..81a7cb4 100644
--- a/lightning/src/ln/channel_state.rs
+++ b/lightning/src/ln/channel_state.rs
@@ -450,6 +450,10 @@ pub struct ChannelDetails {
///
/// This field is empty for objects serialized with LDK versions prior to 0.0.122.
pub pending_outbound_htlcs: Vec<OutboundHTLCDetails>,
+ /// The witness script that is used to lock the channel's funding output to commitment transactions.
+ ///
+ /// This field will be `None` for objects serialized with LDK versions prior to 0.2.0.
+ pub funding_redeem_script: Option<bitcoin::ScriptBuf>,
}
impl ChannelDetails {
@@ -475,6 +479,21 @@ impl ChannelDetails {
self.short_channel_id.or(self.outbound_scid_alias)
}
+ /// Gets the funding output for this channel, if available.
+ ///
+ /// During a splice, the funding output will change and this value will be updated
+ /// after the splice transaction has reached sufficient confirmations and we've
+ /// exchanged `splice_locked` messages.
+ pub fn get_funding_output(&self) -> Option<bitcoin::TxOut> {
+ match self.funding_redeem_script.as_ref() {
+ None => None,
+ Some(redeem_script) => Some(bitcoin::TxOut {
+ value: bitcoin::Amount::from_sat(self.channel_value_satoshis),
+ script_pubkey: redeem_script.to_p2wsh(),
+ }),
+ }
+ }
+
pub(super) fn from_channel<SP: Deref, F: Deref>(
channel: &Channel<SP>, best_block_height: u32, latest_features: InitFeatures,
fee_estimator: &LowerBoundedFeeEstimator<F>,
@@ -509,6 +528,9 @@ impl ChannelDetails {
outbound_htlc_maximum_msat: context.get_counterparty_htlc_maximum_msat(funding),
},
funding_txo: funding.get_funding_txo(),
+ funding_redeem_script: funding
+ .channel_transaction_parameters
+ .make_funding_redeemscript_opt(),
// Note that accept_channel (or open_channel) is always the first message, so
// `have_received_message` indicates that type negotiation has completed.
channel_type: if context.have_received_message() {
@@ -583,6 +605,7 @@ impl_writeable_tlv_based!(ChannelDetails, {
(41, channel_shutdown_state, option),
(43, pending_inbound_htlcs, optional_vec),
(45, pending_outbound_htlcs, optional_vec),
+ (47, funding_redeem_script, option),
(_unused, user_channel_id, (static_value,
_user_channel_id_low.unwrap_or(0) as u128 | ((_user_channel_id_high.unwrap_or(0) as u128) << 64)
)),
@@ -627,6 +650,7 @@ mod tests {
use crate::{
chain::transaction::OutPoint,
ln::{
+ chan_utils::make_funding_redeemscript,
channel_state::{
InboundHTLCDetails, InboundHTLCStateDetails, OutboundHTLCDetails,
OutboundHTLCStateDetails,
@@ -658,6 +682,10 @@ mod tests {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
index: 1,
}),
+ funding_redeem_script: Some(make_funding_redeemscript(
+ &PublicKey::from_slice(&[2; 33]).unwrap(),
+ &PublicKey::from_slice(&[2; 33]).unwrap(),
+ )),
channel_type: None,
short_channel_id: None,
outbound_scid_alias: None,
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 0a13d23..23b68dd 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -3303,6 +3303,8 @@ macro_rules! emit_channel_pending_event {
($locked_events: expr, $channel: expr) => {
if $channel.context.should_emit_channel_pending_event() {
let funding_txo = $channel.funding.get_funding_txo().unwrap();
+ let funding_redeem_script =
+ Some($channel.funding.channel_transaction_parameters.make_funding_redeemscript());
$locked_events.push_back((
events::Event::ChannelPending {
channel_id: $channel.context.channel_id(),
@@ -3311,6 +3313,7 @@ macro_rules! emit_channel_pending_event {
user_channel_id: $channel.context.get_user_id(),
funding_txo: funding_txo.into_bitcoin_outpoint(),
channel_type: Some($channel.funding.get_channel_type().clone()),
+ funding_redeem_script,
},
None,
));
diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs
index 371c523..c06e517 100644
--- a/lightning/src/routing/router.rs
+++ b/lightning/src/routing/router.rs
@@ -3926,6 +3926,7 @@ mod tests {
use crate::blinded_path::BlindedHop;
use crate::chain::transaction::OutPoint;
use crate::crypto::chacha20::ChaCha20;
+ use crate::ln::chan_utils::make_funding_redeemscript;
use crate::ln::channel_state::{ChannelCounterparty, ChannelDetails, ChannelShutdownState};
use crate::ln::channelmanager;
use crate::ln::msgs::{UnsignedChannelUpdate, MAX_VALUE_MSAT};
@@ -3984,6 +3985,10 @@ mod tests {
outbound_htlc_maximum_msat: None,
},
funding_txo: Some(OutPoint { txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0 }),
+ funding_redeem_script: Some(make_funding_redeemscript(
+ &PublicKey::from_slice(&[2; 33]).unwrap(),
+ &PublicKey::from_slice(&[2; 33]).unwrap(),
+ )),
channel_type: None,
short_channel_id,
outbound_scid_alias: None,
@@ -9384,6 +9389,7 @@ pub(crate) mod bench_utils {
use std::io::Read;
use crate::chain::transaction::OutPoint;
+ use crate::ln::chan_utils::make_funding_redeemscript;
use crate::ln::channel_state::{ChannelCounterparty, ChannelShutdownState};
use crate::ln::channelmanager;
use crate::ln::types::ChannelId;
@@ -9479,6 +9485,10 @@ pub(crate) mod bench_utils {
funding_txo: Some(OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(), index: 0
}),
+ funding_redeem_script: Some(make_funding_redeemscript(
+ &PublicKey::from_slice(&[2; 33]).unwrap(),
+ &PublicKey::from_slice(&[2; 33]).unwrap(),
+ )),
channel_type: None,
short_channel_id: Some(1),
inbound_scid_alias: None,
Why this scored 19/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.