Skip to content

Commit f408b17

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 783c280 commit f408b17

8 files changed

Lines changed: 350 additions & 97 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: 113 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,8 +1249,7 @@ pub(super) struct SignerResumeUpdates {
12491249
pub accept_channel: Option<msgs::AcceptChannel>,
12501250
pub funding_created: Option<msgs::FundingCreated>,
12511251
pub funding_signed: Option<msgs::FundingSigned>,
1252-
pub funding_commit_sig: Option<msgs::CommitmentSigned>,
1253-
pub tx_signatures: Option<msgs::TxSignatures>,
1252+
pub funding_tx_signed: Option<FundingTxSigned>,
12541253
pub channel_ready: Option<msgs::ChannelReady>,
12551254
pub order: RAACommitmentOrder,
12561255
pub closing_signed: Option<msgs::ClosingSigned>,
@@ -1683,11 +1682,11 @@ where
16831682

16841683
#[rustfmt::skip]
16851684
pub fn signer_maybe_unblocked<L: Logger, CBP>(
1686-
&mut self, chain_hash: ChainHash, logger: &L, path_for_release_htlc: CBP
1685+
&mut self, chain_hash: ChainHash, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
16871686
) -> Result<Option<SignerResumeUpdates>, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
16881687
match &mut self.phase {
16891688
ChannelPhase::Undefined => unreachable!(),
1690-
ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(logger, path_for_release_htlc).map(|r| Some(r)),
1689+
ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(best_block_height, logger, path_for_release_htlc).map(|r| Some(r)),
16911690
ChannelPhase::UnfundedOutboundV1(chan) => {
16921691
let (open_channel, funding_created) = chan.signer_maybe_unblocked(chain_hash, logger);
16931692
Ok(Some(SignerResumeUpdates {
@@ -1697,8 +1696,7 @@ where
16971696
accept_channel: None,
16981697
funding_created,
16991698
funding_signed: None,
1700-
funding_commit_sig: None,
1701-
tx_signatures: None,
1699+
funding_tx_signed: None,
17021700
channel_ready: None,
17031701
order: chan.context.resend_order.clone(),
17041702
closing_signed: None,
@@ -1715,8 +1713,7 @@ where
17151713
accept_channel,
17161714
funding_created: None,
17171715
funding_signed: None,
1718-
funding_commit_sig: None,
1719-
tx_signatures: None,
1716+
funding_tx_signed: None,
17201717
channel_ready: None,
17211718
order: chan.context.resend_order.clone(),
17221719
closing_signed: None,
@@ -2217,9 +2214,7 @@ where
22172214
.unwrap_or(false));
22182215
}
22192216

2220-
if signing_session.has_holder_tx_signatures() {
2221-
// Our `tx_signatures` either should've been the first time we processed them,
2222-
// or we're waiting for our counterparty to send theirs first.
2217+
if signing_session.has_holder_witnesses() {
22232218
return Ok(FundingTxSigned {
22242219
commitment_signed: None,
22252220
counterparty_initial_commitment_signed_result: None,
@@ -2248,36 +2243,42 @@ where
22482243
return Err(APIError::APIMisuseError { err });
22492244
};
22502245

2251-
let tx = signing_session.unsigned_tx().tx();
2252-
if funding_txid_signed != tx.compute_txid() {
2253-
return Err(APIError::APIMisuseError {
2254-
err: "Transaction was malleated prior to signing".to_owned(),
2255-
});
2256-
}
2246+
let (mut tx_signatures, mut funding_tx) = signing_session
2247+
.provide_holder_witnesses(
2248+
context.channel_id,
2249+
funding_txid_signed,
2250+
witnesses,
2251+
&context.secp_ctx,
2252+
)
2253+
.map_err(|err| APIError::APIMisuseError { err })?;
22572254

2258-
let shared_input_signature =
2259-
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2260-
let sig = context.holder_signer.sign_splice_shared_input(
2255+
debug_assert_eq!(
2256+
pending_splice.is_some(),
2257+
signing_session.unsigned_tx().shared_input_index().is_some()
2258+
);
2259+
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2260+
let sig = context
2261+
.holder_signer
2262+
.sign_splice_shared_input(
22612263
&funding.channel_transaction_parameters,
2262-
tx,
2264+
signing_session.unsigned_tx().tx(),
22632265
splice_input_index as usize,
22642266
&context.secp_ctx,
2265-
);
2266-
Some(sig)
2267+
)
2268+
.ok();
2269+
if let Some(sig) = sig {
2270+
(tx_signatures, funding_tx) = signing_session
2271+
.provide_holder_shared_input_signature(sig)
2272+
.map_err(|err| APIError::APIMisuseError { err })?;
22672273
} else {
2268-
None
2269-
};
2270-
debug_assert_eq!(pending_splice.is_some(), shared_input_signature.is_some());
2271-
2272-
let tx_signatures = msgs::TxSignatures {
2273-
channel_id: context.channel_id,
2274-
tx_hash: funding_txid_signed,
2275-
witnesses,
2276-
shared_input_signature,
2277-
};
2278-
let (tx_signatures, funding_tx) = signing_session
2279-
.provide_holder_witnesses(tx_signatures, &context.secp_ctx)
2280-
.map_err(|err| APIError::APIMisuseError { err })?;
2274+
log_debug!(
2275+
logger,
2276+
"Splice shared input signature not available, waiting on async signer"
2277+
);
2278+
debug_assert!(tx_signatures.is_none());
2279+
debug_assert!(funding_tx.is_none());
2280+
}
2281+
}
22812282

22822283
let logger = WithChannelContext::from(logger, &context, None);
22832284
if tx_signatures.is_some() {
@@ -2409,18 +2410,17 @@ where
24092410
// which must always come after the initial commitment signed is sent.
24102411
.unwrap_or(true);
24112412
let res = if has_negotiated_pending_splice && !session_received_commitment_signed {
2412-
let has_holder_tx_signatures = funded_channel
2413+
let has_holder_witnesses = funded_channel
24132414
.context
24142415
.interactive_tx_signing_session
24152416
.as_ref()
2416-
.map(|session| session.has_holder_tx_signatures())
2417+
.map(|session| session.has_holder_witnesses())
24172418
.unwrap_or(false);
24182419

24192420
// We delay processing this until the user manually approves the splice via
2420-
// [`Channel::funding_transaction_signed`], as otherwise, there would be a
2421-
// [`ChannelMonitorUpdateStep::RenegotiatedFunding`] committed that we would
2422-
// need to undo if they no longer wish to proceed.
2423-
if has_holder_tx_signatures {
2421+
// [`Channel::funding_transaction_signed`], as otherwise, it would prevent the
2422+
// user from canceling their contribution if they no longer wish to proceed.
2423+
if has_holder_witnesses {
24242424
funded_channel
24252425
.splice_initial_commitment_signed(msg, fee_estimator, logger)
24262426
.map(|monitor_update_opt| (None, monitor_update_opt))
@@ -5179,7 +5179,7 @@ impl<SP: SignerProvider> ChannelContext<SP> {
51795179
ChannelState::FundingNegotiated(_) => self
51805180
.interactive_tx_signing_session
51815181
.as_ref()
5182-
.map(|signing_session| signing_session.has_holder_tx_signatures())
5182+
.map(|signing_session| signing_session.has_holder_witnesses())
51835183
.unwrap_or(false),
51845184
ChannelState::AwaitingChannelReady(flags) => !flags.is_waiting_for_batch(),
51855185
_ => true,
@@ -7910,7 +7910,7 @@ where
79107910
.interactive_tx_signing_session
79117911
.as_ref()
79127912
.map(|signing_session| {
7913-
signing_session.has_holder_tx_signatures()
7913+
signing_session.has_holder_witnesses()
79147914
|| signing_session.has_received_tx_signatures()
79157915
})
79167916
.unwrap_or(false);
@@ -9584,6 +9584,8 @@ where
95849584
}
95859585
}
95869586

9587+
let awaiting_holder_shared_input_signature =
9588+
signing_session.awaiting_holder_shared_input_signature();
95879589
let (holder_tx_signatures, funding_tx) =
95889590
signing_session.received_tx_signatures(msg).map_err(|msg| ChannelError::Warn(msg))?;
95899591

@@ -9622,6 +9624,11 @@ where
96229624
best_block_height,
96239625
&logger,
96249626
);
9627+
} else if awaiting_holder_shared_input_signature {
9628+
log_debug!(
9629+
logger,
9630+
"Waiting for funding transaction shared input signature before finalizing negotiation"
9631+
);
96259632
} else {
96269633
debug_assert!(
96279634
false,
@@ -10016,7 +10023,7 @@ where
1001610023
/// blocked.
1001710024
#[rustfmt::skip]
1001810025
pub fn signer_maybe_unblocked<L: Logger, CBP>(
10019-
&mut self, logger: &L, path_for_release_htlc: CBP
10026+
&mut self, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
1002010027
) -> Result<SignerResumeUpdates, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
1002110028
if let Some((commitment_number, commitment_secret)) = self.context.signer_pending_stale_state_verification.clone() {
1002210029
if let Ok(expected_point) = self
@@ -10072,16 +10079,65 @@ where
1007210079
None
1007310080
};
1007410081

10075-
let tx_signatures = if funding_commit_sig.is_some() {
10082+
let mut shared_input_signature_unblocked = false;
10083+
{
10084+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_mut() {
10085+
if signing_session.awaiting_holder_shared_input_signature() {
10086+
let splice_input_index = signing_session
10087+
.unsigned_tx()
10088+
.shared_input_index()
10089+
.expect("Missing shared input index while awaiting a splice signature");
10090+
log_trace!(logger, "Attempting to generate pending splice shared input signature...");
10091+
if let Ok(shared_input_signature) = self.context.holder_signer.sign_splice_shared_input(
10092+
&self.funding.channel_transaction_parameters,
10093+
signing_session.unsigned_tx().tx(),
10094+
splice_input_index as usize,
10095+
&self.context.secp_ctx,
10096+
) {
10097+
shared_input_signature_unblocked = true;
10098+
signing_session
10099+
.provide_holder_shared_input_signature(shared_input_signature)
10100+
.map_err(ChannelError::close)?;
10101+
}
10102+
}
10103+
}
10104+
}
10105+
10106+
let mut tx_signatures = None;
10107+
let mut funding_tx = None;
10108+
if funding_commit_sig.is_some() || shared_input_signature_unblocked {
1007610109
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
10077-
signing_session.holder_tx_signatures().filter(|_| !self.is_awaiting_monitor_update())
10110+
if !self.is_awaiting_monitor_update() && !self.context.signer_pending_funding {
10111+
tx_signatures = signing_session.holder_tx_signatures();
10112+
funding_tx = tx_signatures.as_ref().and_then(|_| signing_session.signed_tx());
10113+
}
1007810114
} else {
1007910115
debug_assert!(false);
10080-
None
1008110116
}
10082-
} else {
10083-
None
10084-
};
10117+
}
10118+
10119+
let mut funding_tx_signed = None;
10120+
if funding_commit_sig.is_some() || tx_signatures.is_some() || funding_tx.is_some() {
10121+
let mut resumed = FundingTxSigned {
10122+
commitment_signed: funding_commit_sig,
10123+
counterparty_initial_commitment_signed_result: None,
10124+
tx_signatures,
10125+
funding_tx: None,
10126+
splice_negotiated: None,
10127+
splice_locked: None,
10128+
};
10129+
if let Some(funding_tx) = funding_tx {
10130+
let funding_logger = WithChannelContext::from(logger, &self.context, None);
10131+
debug_assert!(resumed.tx_signatures.is_some());
10132+
self.on_tx_signatures_exchange(
10133+
&mut resumed,
10134+
funding_tx,
10135+
best_block_height,
10136+
&funding_logger,
10137+
);
10138+
}
10139+
funding_tx_signed = Some(resumed);
10140+
}
1008510141

1008610142
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
1008710143
// funding.
@@ -10147,8 +10203,8 @@ where
1014710203
if revoke_and_ack.is_some() { "a" } else { "no" },
1014810204
self.context.resend_order,
1014910205
if funding_signed.is_some() { "a" } else { "no" },
10150-
if funding_commit_sig.is_some() { "a" } else { "no" },
10151-
if tx_signatures.is_some() { "a" } else { "no" },
10206+
if funding_tx_signed.as_ref().map(|v| v.commitment_signed.is_some()).unwrap_or(false) { "a" } else { "no" },
10207+
if funding_tx_signed.as_ref().map(|v| v.tx_signatures.is_some()).unwrap_or(false) { "a" } else { "no" },
1015210208
if channel_ready.is_some() { "a" } else { "no" },
1015310209
if closing_signed.is_some() { "a" } else { "no" },
1015410210
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -10161,8 +10217,7 @@ where
1016110217
accept_channel: None,
1016210218
funding_created: None,
1016310219
funding_signed,
10164-
funding_commit_sig,
10165-
tx_signatures,
10220+
funding_tx_signed,
1016610221
channel_ready,
1016710222
order: self.context.resend_order.clone(),
1016810223
closing_signed,
@@ -10512,7 +10567,7 @@ where
1051210567
} else {
1051310568
tx_signatures = Some(holder_tx_signatures);
1051410569
}
10515-
} else if !session.has_holder_tx_signatures() {
10570+
} else if !session.has_holder_witnesses() {
1051610571
log_debug!(logger, "Waiting for funding transaction signatures to be provided");
1051710572
}
1051810573
} else {
@@ -10948,7 +11003,7 @@ where
1094811003
matches!(self.context.channel_state, ChannelState::NegotiatingFunding(_));
1094911004
if matches!(self.context.channel_state, ChannelState::FundingNegotiated(_)) {
1095011005
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
10951-
if !signing_session.has_holder_tx_signatures() {
11006+
if !signing_session.has_holder_witnesses() {
1095211007
// If we're a V1 channel or we haven't yet sent our `tx_signatures` for a dual
1095311008
// funded channel, the funding tx couldn't be broadcasted yet, so just short-circuit
1095411009
// the shutdown logic.
@@ -12919,7 +12974,7 @@ where
1291912974
.interactive_tx_signing_session
1292012975
.as_ref()
1292112976
.expect("We have a pending splice awaiting signatures")
12922-
.has_holder_tx_signatures();
12977+
.has_holder_witnesses();
1292312978
if already_signed {
1292412979
return Err(APIError::APIMisuseError {
1292512980
err: format!(

0 commit comments

Comments
 (0)