Skip to content

Commit 25cd9e0

Browse files
committed
Support async signing of splice shared input
While user signatures may be provided whenever ready at the user's discretion when handling a `FundingTransactionReadyForSigning` event, it does not cover the user's signature for the 2-of-2 multisig input in a splice. This signature is obtained via the `EcdsaChannelSigner`, which did not support providing it asynchronously. Since the splice shared input signature is part of the `tx_signatures` message, we're not allowed to send the message until it's complete. This results in us needing to explicitly handle the signature exchange logic when the signer unblocks the shared input signature.
1 parent b780a85 commit 25cd9e0

8 files changed

Lines changed: 334 additions & 78 deletions

File tree

lightning/src/ln/async_signer_tests.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,3 +1742,80 @@ fn test_async_splice_initial_commit_sig_waits_for_monitor_before_tx_signatures()
17421742
let _ = get_event!(initiator, Event::SpliceNegotiated);
17431743
let _ = get_event!(acceptor, Event::SpliceNegotiated);
17441744
}
1745+
1746+
#[test]
1747+
fn test_async_splice_shared_input_signature_released_on_unblock() {
1748+
// Test that we can provide the signature of a splice's shared input asynchronously, and check
1749+
// that the holding cell is freed after exiting quiescence due to exchanging `tx_signatures`.
1750+
let chanmon_cfgs = create_chanmon_cfgs(2);
1751+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1752+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1753+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1754+
1755+
let channel_id = create_announced_chan_between_nodes(&nodes, 0, 1).2;
1756+
1757+
let (initiator, acceptor) = (&nodes[0], &nodes[1]);
1758+
let initiator_node_id = initiator.node.get_our_node_id();
1759+
let acceptor_node_id = acceptor.node.get_our_node_id();
1760+
1761+
initiator.disable_channel_signer_op(
1762+
&acceptor_node_id,
1763+
&channel_id,
1764+
SignerOp::SignSpliceSharedInput,
1765+
);
1766+
1767+
let outputs = vec![TxOut {
1768+
value: Amount::from_sat(1_000),
1769+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
1770+
}];
1771+
let contribution = initiate_splice_out(initiator, acceptor, channel_id, outputs).unwrap();
1772+
negotiate_splice_tx(initiator, acceptor, channel_id, contribution);
1773+
1774+
let event = get_event!(initiator, Event::FundingTransactionReadyForSigning);
1775+
if let Event::FundingTransactionReadyForSigning { unsigned_transaction, .. } = event {
1776+
let partially_signed_tx = initiator.wallet_source.sign_tx(unsigned_transaction).unwrap();
1777+
initiator
1778+
.node
1779+
.funding_transaction_signed(&channel_id, &acceptor_node_id, partially_signed_tx)
1780+
.unwrap();
1781+
}
1782+
1783+
let initiator_commit_sig = get_htlc_update_msgs(initiator, &acceptor_node_id);
1784+
acceptor
1785+
.node
1786+
.handle_commitment_signed(initiator_node_id, &initiator_commit_sig.commitment_signed[0]);
1787+
check_added_monitors(acceptor, 1);
1788+
1789+
let acceptor_msg_events = acceptor.node.get_and_clear_pending_msg_events();
1790+
assert_eq!(acceptor_msg_events.len(), 2, "{acceptor_msg_events:?}");
1791+
for msg_event in &acceptor_msg_events {
1792+
match msg_event {
1793+
MessageSendEvent::UpdateHTLCs { updates, .. } => {
1794+
initiator
1795+
.node
1796+
.handle_commitment_signed(acceptor_node_id, &updates.commitment_signed[0]);
1797+
check_added_monitors(initiator, 1);
1798+
},
1799+
MessageSendEvent::SendTxSignatures { msg, .. } => {
1800+
initiator.node.handle_tx_signatures(acceptor_node_id, msg);
1801+
},
1802+
_ => panic!("Unexpected event"),
1803+
}
1804+
}
1805+
1806+
assert!(initiator.node.get_and_clear_pending_msg_events().is_empty());
1807+
1808+
initiator.enable_channel_signer_op(
1809+
&acceptor_node_id,
1810+
&channel_id,
1811+
SignerOp::SignSpliceSharedInput,
1812+
);
1813+
initiator.node.signer_unblocked(None);
1814+
1815+
let tx_signatures =
1816+
get_event_msg!(initiator, MessageSendEvent::SendTxSignatures, acceptor_node_id);
1817+
acceptor.node.handle_tx_signatures(initiator_node_id, &tx_signatures);
1818+
1819+
let _ = get_event!(initiator, Event::SpliceNegotiated);
1820+
let _ = get_event!(acceptor, Event::SpliceNegotiated);
1821+
}

lightning/src/ln/channel.rs

Lines changed: 102 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,8 +1220,7 @@ pub(super) struct SignerResumeUpdates {
12201220
pub accept_channel: Option<msgs::AcceptChannel>,
12211221
pub funding_created: Option<msgs::FundingCreated>,
12221222
pub funding_signed: Option<msgs::FundingSigned>,
1223-
pub funding_commit_sig: Option<msgs::CommitmentSigned>,
1224-
pub tx_signatures: Option<msgs::TxSignatures>,
1223+
pub funding_tx_signed: Option<FundingTxSigned>,
12251224
pub channel_ready: Option<msgs::ChannelReady>,
12261225
pub order: RAACommitmentOrder,
12271226
pub closing_signed: Option<msgs::ClosingSigned>,
@@ -1654,11 +1653,11 @@ where
16541653

16551654
#[rustfmt::skip]
16561655
pub fn signer_maybe_unblocked<L: Logger, CBP>(
1657-
&mut self, chain_hash: ChainHash, logger: &L, path_for_release_htlc: CBP
1656+
&mut self, chain_hash: ChainHash, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
16581657
) -> Result<Option<SignerResumeUpdates>, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
16591658
match &mut self.phase {
16601659
ChannelPhase::Undefined => unreachable!(),
1661-
ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(logger, path_for_release_htlc).map(|r| Some(r)),
1660+
ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(best_block_height, logger, path_for_release_htlc).map(|r| Some(r)),
16621661
ChannelPhase::UnfundedOutboundV1(chan) => {
16631662
let (open_channel, funding_created) = chan.signer_maybe_unblocked(chain_hash, logger);
16641663
Ok(Some(SignerResumeUpdates {
@@ -1668,8 +1667,7 @@ where
16681667
accept_channel: None,
16691668
funding_created,
16701669
funding_signed: None,
1671-
funding_commit_sig: None,
1672-
tx_signatures: None,
1670+
funding_tx_signed: None,
16731671
channel_ready: None,
16741672
order: chan.context.resend_order.clone(),
16751673
closing_signed: None,
@@ -1686,8 +1684,7 @@ where
16861684
accept_channel,
16871685
funding_created: None,
16881686
funding_signed: None,
1689-
funding_commit_sig: None,
1690-
tx_signatures: None,
1687+
funding_tx_signed: None,
16911688
channel_ready: None,
16921689
order: chan.context.resend_order.clone(),
16931690
closing_signed: None,
@@ -2219,36 +2216,42 @@ where
22192216
return Err(APIError::APIMisuseError { err });
22202217
};
22212218

2222-
let tx = signing_session.unsigned_tx().tx();
2223-
if funding_txid_signed != tx.compute_txid() {
2224-
return Err(APIError::APIMisuseError {
2225-
err: "Transaction was malleated prior to signing".to_owned(),
2226-
});
2227-
}
2219+
let (mut tx_signatures, mut funding_tx) = signing_session
2220+
.provide_holder_witnesses(
2221+
context.channel_id,
2222+
funding_txid_signed,
2223+
witnesses,
2224+
&context.secp_ctx,
2225+
)
2226+
.map_err(|err| APIError::APIMisuseError { err })?;
22282227

2229-
let shared_input_signature =
2230-
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2231-
let sig = context.holder_signer.sign_splice_shared_input(
2228+
debug_assert_eq!(
2229+
pending_splice.is_some(),
2230+
signing_session.unsigned_tx().shared_input_index().is_some()
2231+
);
2232+
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2233+
let sig = context
2234+
.holder_signer
2235+
.sign_splice_shared_input(
22322236
&funding.channel_transaction_parameters,
2233-
tx,
2237+
signing_session.unsigned_tx().tx(),
22342238
splice_input_index as usize,
22352239
&context.secp_ctx,
2236-
);
2237-
Some(sig)
2240+
)
2241+
.ok();
2242+
if let Some(sig) = sig {
2243+
(tx_signatures, funding_tx) = signing_session
2244+
.provide_holder_shared_input_signature(sig)
2245+
.map_err(|err| APIError::APIMisuseError { err })?;
22382246
} else {
2239-
None
2240-
};
2241-
debug_assert_eq!(pending_splice.is_some(), shared_input_signature.is_some());
2242-
2243-
let tx_signatures = msgs::TxSignatures {
2244-
channel_id: context.channel_id,
2245-
tx_hash: funding_txid_signed,
2246-
witnesses,
2247-
shared_input_signature,
2248-
};
2249-
let (tx_signatures, funding_tx) = signing_session
2250-
.provide_holder_witnesses(tx_signatures, &context.secp_ctx)
2251-
.map_err(|err| APIError::APIMisuseError { err })?;
2247+
log_debug!(
2248+
logger,
2249+
"Splice shared input signature not available, waiting on async signer"
2250+
);
2251+
debug_assert!(tx_signatures.is_none());
2252+
debug_assert!(funding_tx.is_none());
2253+
}
2254+
}
22522255

22532256
let logger = WithChannelContext::from(logger, &context, None);
22542257
if tx_signatures.is_some() {
@@ -9514,6 +9517,8 @@ where
95149517
}
95159518
}
95169519

9520+
let awaiting_holder_shared_input_signature =
9521+
signing_session.awaiting_holder_shared_input_signature();
95179522
let (holder_tx_signatures, funding_tx) =
95189523
signing_session.received_tx_signatures(msg).map_err(|msg| ChannelError::Warn(msg))?;
95199524

@@ -9552,6 +9557,11 @@ where
95529557
best_block_height,
95539558
&logger,
95549559
);
9560+
} else if awaiting_holder_shared_input_signature {
9561+
log_debug!(
9562+
logger,
9563+
"Waiting for funding transaction shared input signature before finalizing negotiation"
9564+
);
95559565
} else {
95569566
debug_assert!(
95579567
false,
@@ -9946,7 +9956,7 @@ where
99469956
/// blocked.
99479957
#[rustfmt::skip]
99489958
pub fn signer_maybe_unblocked<L: Logger, CBP>(
9949-
&mut self, logger: &L, path_for_release_htlc: CBP
9959+
&mut self, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
99509960
) -> Result<SignerResumeUpdates, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
99519961
if let Some((commitment_number, commitment_secret)) = self.context.signer_pending_stale_state_verification.clone() {
99529962
if let Ok(expected_point) = self
@@ -10002,16 +10012,65 @@ where
1000210012
None
1000310013
};
1000410014

10005-
let tx_signatures = if funding_commit_sig.is_some() {
10015+
let mut shared_input_signature_unblocked = false;
10016+
{
10017+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_mut() {
10018+
if signing_session.awaiting_holder_shared_input_signature() {
10019+
let splice_input_index = signing_session
10020+
.unsigned_tx()
10021+
.shared_input_index()
10022+
.expect("Missing shared input index while awaiting a splice signature");
10023+
log_trace!(logger, "Attempting to generate pending splice shared input signature...");
10024+
if let Ok(shared_input_signature) = self.context.holder_signer.sign_splice_shared_input(
10025+
&self.funding.channel_transaction_parameters,
10026+
signing_session.unsigned_tx().tx(),
10027+
splice_input_index as usize,
10028+
&self.context.secp_ctx,
10029+
) {
10030+
shared_input_signature_unblocked = true;
10031+
signing_session
10032+
.provide_holder_shared_input_signature(shared_input_signature)
10033+
.map_err(ChannelError::close)?;
10034+
}
10035+
}
10036+
}
10037+
}
10038+
10039+
let mut tx_signatures = None;
10040+
let mut funding_tx = None;
10041+
if funding_commit_sig.is_some() || shared_input_signature_unblocked {
1000610042
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
10007-
signing_session.holder_tx_signatures().filter(|_| !self.is_awaiting_monitor_update())
10043+
if !self.is_awaiting_monitor_update() && !self.context.signer_pending_funding {
10044+
tx_signatures = signing_session.holder_tx_signatures();
10045+
funding_tx = tx_signatures.as_ref().and_then(|_| signing_session.signed_tx());
10046+
}
1000810047
} else {
1000910048
debug_assert!(false);
10010-
None
1001110049
}
10012-
} else {
10013-
None
10014-
};
10050+
}
10051+
10052+
let mut funding_tx_signed = None;
10053+
if funding_commit_sig.is_some() || tx_signatures.is_some() || funding_tx.is_some() {
10054+
let mut resumed = FundingTxSigned {
10055+
commitment_signed: funding_commit_sig,
10056+
counterparty_initial_commitment_signed_result: None,
10057+
tx_signatures,
10058+
funding_tx: None,
10059+
splice_negotiated: None,
10060+
splice_locked: None,
10061+
};
10062+
if let Some(funding_tx) = funding_tx {
10063+
let funding_logger = WithChannelContext::from(logger, &self.context, None);
10064+
debug_assert!(resumed.tx_signatures.is_some());
10065+
self.on_tx_signatures_exchange(
10066+
&mut resumed,
10067+
funding_tx,
10068+
best_block_height,
10069+
&funding_logger,
10070+
);
10071+
}
10072+
funding_tx_signed = Some(resumed);
10073+
}
1001510074

1001610075
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
1001710076
// funding.
@@ -10077,8 +10136,8 @@ where
1007710136
if revoke_and_ack.is_some() { "a" } else { "no" },
1007810137
self.context.resend_order,
1007910138
if funding_signed.is_some() { "a" } else { "no" },
10080-
if funding_commit_sig.is_some() { "a" } else { "no" },
10081-
if tx_signatures.is_some() { "a" } else { "no" },
10139+
if funding_tx_signed.as_ref().map(|v| v.commitment_signed.is_some()).unwrap_or(false) { "a" } else { "no" },
10140+
if funding_tx_signed.as_ref().map(|v| v.tx_signatures.is_some()).unwrap_or(false) { "a" } else { "no" },
1008210141
if channel_ready.is_some() { "a" } else { "no" },
1008310142
if closing_signed.is_some() { "a" } else { "no" },
1008410143
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -10091,8 +10150,7 @@ where
1009110150
accept_channel: None,
1009210151
funding_created: None,
1009310152
funding_signed,
10094-
funding_commit_sig,
10095-
tx_signatures,
10153+
funding_tx_signed,
1009610154
channel_ready,
1009710155
order: self.context.resend_order.clone(),
1009810156
closing_signed,

0 commit comments

Comments
 (0)