Skip to content

Commit d794030

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 d794030

8 files changed

Lines changed: 349 additions & 96 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: 112 additions & 57 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,
@@ -2188,9 +2185,7 @@ where
21882185
.unwrap_or(false));
21892186
}
21902187

2191-
if signing_session.has_holder_tx_signatures() {
2192-
// Our `tx_signatures` either should've been the first time we processed them,
2193-
// or we're waiting for our counterparty to send theirs first.
2188+
if signing_session.has_holder_tx_witnesses() {
21942189
return Ok(FundingTxSigned {
21952190
commitment_signed: None,
21962191
counterparty_initial_commitment_signed_result: None,
@@ -2219,36 +2214,42 @@ where
22192214
return Err(APIError::APIMisuseError { err });
22202215
};
22212216

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-
}
2217+
let (mut tx_signatures, mut funding_tx) = signing_session
2218+
.provide_holder_witnesses(
2219+
context.channel_id,
2220+
funding_txid_signed,
2221+
witnesses,
2222+
&context.secp_ctx,
2223+
)
2224+
.map_err(|err| APIError::APIMisuseError { err })?;
22282225

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(
2226+
debug_assert_eq!(
2227+
pending_splice.is_some(),
2228+
signing_session.unsigned_tx().shared_input_index().is_some()
2229+
);
2230+
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2231+
let sig = context
2232+
.holder_signer
2233+
.sign_splice_shared_input(
22322234
&funding.channel_transaction_parameters,
2233-
tx,
2235+
signing_session.unsigned_tx().tx(),
22342236
splice_input_index as usize,
22352237
&context.secp_ctx,
2236-
);
2237-
Some(sig)
2238+
)
2239+
.ok();
2240+
if let Some(sig) = sig {
2241+
(tx_signatures, funding_tx) = signing_session
2242+
.provide_holder_shared_input_signature(sig)
2243+
.map_err(|err| APIError::APIMisuseError { err })?;
22382244
} 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 })?;
2245+
log_debug!(
2246+
logger,
2247+
"Splice shared input signature not available, waiting on async signer"
2248+
);
2249+
debug_assert!(tx_signatures.is_none());
2250+
debug_assert!(funding_tx.is_none());
2251+
}
2252+
}
22522253

22532254
let logger = WithChannelContext::from(logger, &context, None);
22542255
if tx_signatures.is_some() {
@@ -2380,18 +2381,17 @@ where
23802381
// which must always come after the initial commitment signed is sent.
23812382
.unwrap_or(true);
23822383
let res = if has_negotiated_pending_splice && !session_received_commitment_signed {
2383-
let has_holder_tx_signatures = funded_channel
2384+
let has_holder_tx_witnesses = funded_channel
23842385
.context
23852386
.interactive_tx_signing_session
23862387
.as_ref()
2387-
.map(|session| session.has_holder_tx_signatures())
2388+
.map(|session| session.has_holder_tx_witnesses())
23882389
.unwrap_or(false);
23892390

23902391
// We delay processing this until the user manually approves the splice via
2391-
// [`Channel::funding_transaction_signed`], as otherwise, there would be a
2392-
// [`ChannelMonitorUpdateStep::RenegotiatedFunding`] committed that we would
2393-
// need to undo if they no longer wish to proceed.
2394-
if has_holder_tx_signatures {
2392+
// [`Channel::funding_transaction_signed`], as otherwise, it would prevent the
2393+
// user from canceling their contribution if they no longer wish to proceed.
2394+
if has_holder_tx_witnesses {
23952395
funded_channel
23962396
.splice_initial_commitment_signed(msg, fee_estimator, logger)
23972397
.map(|monitor_update_opt| (None, monitor_update_opt))
@@ -5135,7 +5135,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
51355135
ChannelState::FundingNegotiated(_) => self
51365136
.interactive_tx_signing_session
51375137
.as_ref()
5138-
.map(|signing_session| signing_session.has_holder_tx_signatures())
5138+
.map(|signing_session| signing_session.has_holder_tx_witnesses())
51395139
.unwrap_or(false),
51405140
ChannelState::AwaitingChannelReady(flags) => !flags.is_waiting_for_batch(),
51415141
_ => true,
@@ -7847,7 +7847,7 @@ where
78477847
.interactive_tx_signing_session
78487848
.as_ref()
78497849
.map(|signing_session| {
7850-
signing_session.has_holder_tx_signatures()
7850+
signing_session.has_holder_tx_witnesses()
78517851
|| signing_session.has_received_tx_signatures()
78527852
})
78537853
.unwrap_or(false);
@@ -9514,6 +9514,8 @@ where
95149514
}
95159515
}
95169516

9517+
let awaiting_holder_shared_input_signature =
9518+
signing_session.awaiting_holder_shared_input_signature();
95179519
let (holder_tx_signatures, funding_tx) =
95189520
signing_session.received_tx_signatures(msg).map_err(|msg| ChannelError::Warn(msg))?;
95199521

@@ -9552,6 +9554,11 @@ where
95529554
best_block_height,
95539555
&logger,
95549556
);
9557+
} else if awaiting_holder_shared_input_signature {
9558+
log_debug!(
9559+
logger,
9560+
"Waiting for funding transaction shared input signature before finalizing negotiation"
9561+
);
95559562
} else {
95569563
debug_assert!(
95579564
false,
@@ -9946,7 +9953,7 @@ where
99469953
/// blocked.
99479954
#[rustfmt::skip]
99489955
pub fn signer_maybe_unblocked<L: Logger, CBP>(
9949-
&mut self, logger: &L, path_for_release_htlc: CBP
9956+
&mut self, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
99509957
) -> Result<SignerResumeUpdates, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
99519958
if let Some((commitment_number, commitment_secret)) = self.context.signer_pending_stale_state_verification.clone() {
99529959
if let Ok(expected_point) = self
@@ -10002,16 +10009,65 @@ where
1000210009
None
1000310010
};
1000410011

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

1001610072
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
1001710073
// funding.
@@ -10077,8 +10133,8 @@ where
1007710133
if revoke_and_ack.is_some() { "a" } else { "no" },
1007810134
self.context.resend_order,
1007910135
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" },
10136+
if funding_tx_signed.as_ref().map(|v| v.commitment_signed.is_some()).unwrap_or(false) { "a" } else { "no" },
10137+
if funding_tx_signed.as_ref().map(|v| v.tx_signatures.is_some()).unwrap_or(false) { "a" } else { "no" },
1008210138
if channel_ready.is_some() { "a" } else { "no" },
1008310139
if closing_signed.is_some() { "a" } else { "no" },
1008410140
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -10091,8 +10147,7 @@ where
1009110147
accept_channel: None,
1009210148
funding_created: None,
1009310149
funding_signed,
10094-
funding_commit_sig,
10095-
tx_signatures,
10150+
funding_tx_signed,
1009610151
channel_ready,
1009710152
order: self.context.resend_order.clone(),
1009810153
closing_signed,
@@ -10442,7 +10497,7 @@ where
1044210497
} else {
1044310498
tx_signatures = Some(holder_tx_signatures);
1044410499
}
10445-
} else if !session.has_holder_tx_signatures() {
10500+
} else if !session.has_holder_tx_witnesses() {
1044610501
log_debug!(logger, "Waiting for funding transaction signatures to be provided");
1044710502
}
1044810503
} else {
@@ -10878,7 +10933,7 @@ where
1087810933
matches!(self.context.channel_state, ChannelState::NegotiatingFunding(_));
1087910934
if matches!(self.context.channel_state, ChannelState::FundingNegotiated(_)) {
1088010935
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
10881-
if !signing_session.has_holder_tx_signatures() {
10936+
if !signing_session.has_holder_tx_witnesses() {
1088210937
// If we're a V1 channel or we haven't yet sent our `tx_signatures` for a dual
1088310938
// funded channel, the funding tx couldn't be broadcasted yet, so just short-circuit
1088410939
// the shutdown logic.

0 commit comments

Comments
 (0)