Skip to content

Commit 58d4ba9

Browse files
committed
Introduce FundingTransactionReadyForSignatures event
The `FundingTransactionReadyForSignatures` event requests witnesses from the client for their contributed inputs to an interactively constructed transaction. The client calls `ChannelManager::funding_transaction_signed` to provide the witnesses to LDK.
1 parent 0fe51c5 commit 58d4ba9

File tree

4 files changed

+192
-29
lines changed

4 files changed

+192
-29
lines changed

lightning/src/events/mod.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,55 @@ pub enum Event {
15821582
/// onion messages.
15831583
peer_node_id: PublicKey,
15841584
},
1585+
/// Indicates that a funding transaction constructed via interactive transaction construction for a
1586+
/// channel is ready to be signed by the client. This event will only be triggered
1587+
/// if at least one input was contributed by the holder and needs to be signed.
1588+
///
1589+
/// The transaction contains all inputs provided by both parties along with the channel's funding
1590+
/// output and a change output if applicable.
1591+
///
1592+
/// No part of the transaction should be changed before signing as the content of the transaction
1593+
/// has already been negotiated with the counterparty.
1594+
///
1595+
/// Each signature MUST use the SIGHASH_ALL flag to avoid invalidation of the initial commitment and
1596+
/// hence possible loss of funds.
1597+
///
1598+
/// After signing, call [`ChannelManager::funding_transaction_signed`] with the (partially) signed
1599+
/// funding transaction.
1600+
///
1601+
/// Generated in [`ChannelManager`] message handling.
1602+
///
1603+
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager
1604+
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
1605+
FundingTransactionReadyForSigning {
1606+
/// The channel_id of the channel which you'll need to pass back into
1607+
/// [`ChannelManager::funding_transaction_signed`].
1608+
///
1609+
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
1610+
channel_id: ChannelId,
1611+
/// The counterparty's node_id, which you'll need to pass back into
1612+
/// [`ChannelManager::funding_transaction_signed`].
1613+
///
1614+
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
1615+
counterparty_node_id: PublicKey,
1616+
// TODO(dual_funding): Enable links when methods are implemented
1617+
/// The `user_channel_id` value passed in to `ChannelManager::create_dual_funded_channel` for outbound
1618+
/// channels, or to [`ChannelManager::accept_inbound_channel`] or `ChannelManager::accept_inbound_channel_with_contribution`
1619+
/// for inbound channels if [`UserConfig::manually_accept_inbound_channels`] config flag is set to true.
1620+
/// Otherwise `user_channel_id` will be randomized for an inbound channel.
1621+
/// This may be zero for objects serialized with LDK versions prior to 0.0.113.
1622+
///
1623+
/// [`ChannelManager::accept_inbound_channel`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel
1624+
/// [`UserConfig::manually_accept_inbound_channels`]: crate::util::config::UserConfig::manually_accept_inbound_channels
1625+
// [`ChannelManager::create_dual_funded_channel`]: crate::ln::channelmanager::ChannelManager::create_dual_funded_channel
1626+
// [`ChannelManager::accept_inbound_channel_with_contribution`]: crate::ln::channelmanager::ChannelManager::accept_inbound_channel_with_contribution
1627+
user_channel_id: u128,
1628+
/// The unsigned transaction to be signed and passed back to
1629+
/// [`ChannelManager::funding_transaction_signed`].
1630+
///
1631+
/// [`ChannelManager::funding_transaction_signed`]: crate::ln::channelmanager::ChannelManager::funding_transaction_signed
1632+
unsigned_transaction: Transaction,
1633+
},
15851634
}
15861635

15871636
impl Writeable for Event {
@@ -2012,6 +2061,13 @@ impl Writeable for Event {
20122061
(8, former_temporary_channel_id, required),
20132062
});
20142063
},
2064+
&Event::FundingTransactionReadyForSigning { .. } => {
2065+
45u8.write(writer)?;
2066+
// We never write out FundingTransactionReadyForSigning events as, upon disconnection, peers
2067+
// drop any V2-established/spliced channels which have not yet exchanged the initial `commitment_signed`.
2068+
// We only exhange the initial `commitment_signed` after the client calls
2069+
// `ChannelManager::funding_transaction_signed` and ALWAYS before we send a `tx_signatures`
2070+
},
20152071
// Note that, going forward, all new events must only write data inside of
20162072
// `write_tlv_fields`. Versions 0.0.101+ will ignore odd-numbered events that write
20172073
// data via `write_tlv_fields`.
@@ -2583,6 +2639,10 @@ impl MaybeReadable for Event {
25832639
former_temporary_channel_id: former_temporary_channel_id.0.unwrap(),
25842640
}))
25852641
},
2642+
45u8 => {
2643+
// Value 45 is used for `Event::FundingTransactionReadyForSigning`.
2644+
Ok(None)
2645+
},
25862646
// Versions prior to 0.0.100 did not ignore odd types, instead returning InvalidValue.
25872647
// Version 0.0.100 failed to properly ignore odd types, possibly resulting in corrupt
25882648
// reads.

lightning/src/ln/channel.rs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bitcoin::constants::ChainHash;
1414
use bitcoin::script::{Builder, Script, ScriptBuf, WScriptHash};
1515
use bitcoin::sighash::EcdsaSighashType;
1616
use bitcoin::transaction::{Transaction, TxIn, TxOut};
17-
use bitcoin::Weight;
17+
use bitcoin::{Weight, Witness};
1818

1919
use bitcoin::hash_types::{BlockHash, Txid};
2020
use bitcoin::hashes::sha256::Hash as Sha256;
@@ -2967,7 +2967,7 @@ where
29672967
},
29682968
};
29692969

2970-
let funding_ready_for_sig_event = if signing_session.local_inputs_count() == 0 {
2970+
let funding_ready_for_sig_event_opt = if signing_session.local_inputs_count() == 0 {
29712971
debug_assert_eq!(our_funding_satoshis, 0);
29722972
if signing_session.provide_holder_witnesses(self.context.channel_id, Vec::new()).is_err() {
29732973
debug_assert!(
@@ -2981,28 +2981,12 @@ where
29812981
}
29822982
None
29832983
} else {
2984-
// TODO(dual_funding): Send event for signing if we've contributed funds.
2985-
// Inform the user that SIGHASH_ALL must be used for all signatures when contributing
2986-
// inputs/signatures.
2987-
// Also warn the user that we don't do anything to prevent the counterparty from
2988-
// providing non-standard witnesses which will prevent the funding transaction from
2989-
// confirming. This warning must appear in doc comments wherever the user is contributing
2990-
// funds, whether they are initiator or acceptor.
2991-
//
2992-
// The following warning can be used when the APIs allowing contributing inputs become available:
2993-
// <div class="warning">
2994-
// WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which
2995-
// will prevent the funding transaction from being relayed on the bitcoin network and hence being
2996-
// confirmed.
2997-
// </div>
2998-
debug_assert!(
2999-
false,
3000-
"We don't support users providing inputs but somehow we had more than zero inputs",
3001-
);
3002-
return Err(ChannelError::Close((
3003-
"V2 channel rejected due to sender error".into(),
3004-
ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }
3005-
)));
2984+
Some(Event::FundingTransactionReadyForSigning {
2985+
channel_id: self.context.channel_id,
2986+
counterparty_node_id: self.context.counterparty_node_id,
2987+
user_channel_id: self.context.user_id,
2988+
unsigned_transaction: signing_session.unsigned_tx().build_unsigned_tx(),
2989+
})
30062990
};
30072991

30082992
let mut channel_state = ChannelState::FundingNegotiated(FundingNegotiatedFlags::new());
@@ -3013,7 +2997,7 @@ where
30132997
self.interactive_tx_constructor.take();
30142998
self.interactive_tx_signing_session = Some(signing_session);
30152999

3016-
Ok((commitment_signed, funding_ready_for_sig_event))
3000+
Ok((commitment_signed, funding_ready_for_sig_event_opt))
30173001
}
30183002
}
30193003

@@ -7640,6 +7624,45 @@ where
76407624
}
76417625
}
76427626

7627+
fn verify_interactive_tx_signatures(&mut self, _witnesses: &Vec<Witness>) {
7628+
if let Some(ref mut _signing_session) = self.interactive_tx_signing_session {
7629+
// Check that sighash_all was used:
7630+
// TODO(dual_funding): Check sig for sighash
7631+
}
7632+
}
7633+
7634+
pub fn funding_transaction_signed<L: Deref>(
7635+
&mut self, witnesses: Vec<Witness>, logger: &L,
7636+
) -> Result<Option<msgs::TxSignatures>, APIError>
7637+
where
7638+
L::Target: Logger,
7639+
{
7640+
self.verify_interactive_tx_signatures(&witnesses);
7641+
if let Some(ref mut signing_session) = self.interactive_tx_signing_session {
7642+
let logger = WithChannelContext::from(logger, &self.context, None);
7643+
if let Some(holder_tx_signatures) = signing_session
7644+
.provide_holder_witnesses(self.context.channel_id, witnesses)
7645+
.map_err(|err| APIError::APIMisuseError { err })?
7646+
{
7647+
if self.is_awaiting_initial_mon_persist() {
7648+
log_debug!(logger, "Not sending tx_signatures: a monitor update is in progress. Setting monitor_pending_tx_signatures.");
7649+
self.context.monitor_pending_tx_signatures = Some(holder_tx_signatures);
7650+
return Ok(None);
7651+
}
7652+
return Ok(Some(holder_tx_signatures));
7653+
} else {
7654+
return Ok(None);
7655+
}
7656+
} else {
7657+
return Err(APIError::APIMisuseError {
7658+
err: format!(
7659+
"Channel with id {} not expecting funding signatures",
7660+
self.context.channel_id
7661+
),
7662+
});
7663+
}
7664+
}
7665+
76437666
#[rustfmt::skip]
76447667
pub fn tx_signatures<L: Deref>(&mut self, msg: &msgs::TxSignatures, logger: &L) -> Result<(Option<Transaction>, Option<msgs::TxSignatures>), ChannelError>
76457668
where L::Target: Logger

lightning/src/ln/channelmanager.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5885,6 +5885,77 @@ where
58855885
result
58865886
}
58875887

5888+
/// Handles a signed funding transaction generated by interactive transaction construction and
5889+
/// provided by the client.
5890+
///
5891+
/// Do NOT broadcast the funding transaction yourself. When we have safely received our
5892+
/// counterparty's signature(s) the funding transaction will automatically be broadcast via the
5893+
/// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed.
5894+
///
5895+
/// SIGHASH_ALL MUST be used for all signatures when providing signatures.
5896+
///
5897+
/// <div class="warning">
5898+
/// WARNING: LDK makes no attempt to prevent the counterparty from using non-standard inputs which
5899+
/// will prevent the funding transaction from being relayed on the bitcoin network and hence being
5900+
/// confirmed.
5901+
/// </div>
5902+
pub fn funding_transaction_signed(
5903+
&self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, transaction: Transaction,
5904+
) -> Result<(), APIError> {
5905+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
5906+
let witnesses: Vec<_> = transaction
5907+
.input
5908+
.into_iter()
5909+
.filter_map(|input| if input.witness.is_empty() { None } else { Some(input.witness) })
5910+
.collect();
5911+
5912+
let per_peer_state = self.per_peer_state.read().unwrap();
5913+
let peer_state_mutex = per_peer_state.get(counterparty_node_id).ok_or_else(|| {
5914+
APIError::ChannelUnavailable {
5915+
err: format!(
5916+
"Can't find a peer matching the passed counterparty node_id {}",
5917+
counterparty_node_id
5918+
),
5919+
}
5920+
})?;
5921+
5922+
let mut peer_state_lock = peer_state_mutex.lock().unwrap();
5923+
let peer_state = &mut *peer_state_lock;
5924+
5925+
match peer_state.channel_by_id.get_mut(channel_id) {
5926+
Some(channel) => match channel.as_funded_mut() {
5927+
Some(chan) => {
5928+
if let Some(tx_signatures) =
5929+
chan.funding_transaction_signed(witnesses, &self.logger)?
5930+
{
5931+
peer_state.pending_msg_events.push(MessageSendEvent::SendTxSignatures {
5932+
node_id: *counterparty_node_id,
5933+
msg: tx_signatures,
5934+
});
5935+
}
5936+
},
5937+
None => {
5938+
return Err(APIError::APIMisuseError {
5939+
err: format!(
5940+
"Channel with id {} not expecting funding signatures",
5941+
channel_id
5942+
),
5943+
})
5944+
},
5945+
},
5946+
None => {
5947+
return Err(APIError::ChannelUnavailable {
5948+
err: format!(
5949+
"Channel with id {} not found for the passed counterparty node_id {}",
5950+
channel_id, counterparty_node_id
5951+
),
5952+
})
5953+
},
5954+
}
5955+
5956+
Ok(())
5957+
}
5958+
58885959
/// Atomically applies partial updates to the [`ChannelConfig`] of the given channels.
58895960
///
58905961
/// Once the updates are applied, each eligible channel (advertised with a known short channel

lightning/src/ln/interactivetxs.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,14 @@ impl InteractiveTxSigningSession {
396396
/// unsigned transaction.
397397
pub fn provide_holder_witnesses(
398398
&mut self, channel_id: ChannelId, witnesses: Vec<Witness>,
399-
) -> Result<(), ()> {
400-
if self.local_inputs_count() != witnesses.len() {
401-
return Err(());
399+
) -> Result<Option<TxSignatures>, String> {
400+
let local_inputs_count = self.local_inputs_count();
401+
if local_inputs_count != witnesses.len() {
402+
return Err(format!(
403+
"Provided witness count of {} does not match required count for {} inputs",
404+
witnesses.len(),
405+
local_inputs_count
406+
));
402407
}
403408

404409
self.unsigned_tx.add_local_witnesses(witnesses.clone());
@@ -409,7 +414,11 @@ impl InteractiveTxSigningSession {
409414
shared_input_signature: None,
410415
});
411416

412-
Ok(())
417+
if self.holder_sends_tx_signatures_first && self.has_received_commitment_signed {
418+
Ok(self.holder_tx_signatures.clone())
419+
} else {
420+
Ok(None)
421+
}
413422
}
414423

415424
pub fn remote_inputs_count(&self) -> usize {

0 commit comments

Comments
 (0)