Skip to content

Commit fc1921c

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 42e198c commit fc1921c

8 files changed

Lines changed: 324 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::SplicePending);
17431743
let _ = get_event!(acceptor, Event::SplicePending);
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::SplicePending);
1820+
let _ = get_event!(acceptor, Event::SplicePending);
1821+
}

lightning/src/ln/channel.rs

Lines changed: 102 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,8 +1205,7 @@ pub(super) struct SignerResumeUpdates {
12051205
pub accept_channel: Option<msgs::AcceptChannel>,
12061206
pub funding_created: Option<msgs::FundingCreated>,
12071207
pub funding_signed: Option<msgs::FundingSigned>,
1208-
pub funding_commit_sig: Option<msgs::CommitmentSigned>,
1209-
pub tx_signatures: Option<msgs::TxSignatures>,
1208+
pub funding_tx_signed: Option<FundingTxSigned>,
12101209
pub channel_ready: Option<msgs::ChannelReady>,
12111210
pub order: RAACommitmentOrder,
12121211
pub closing_signed: Option<msgs::ClosingSigned>,
@@ -1639,11 +1638,11 @@ where
16391638

16401639
#[rustfmt::skip]
16411640
pub fn signer_maybe_unblocked<L: Logger, CBP>(
1642-
&mut self, chain_hash: ChainHash, logger: &L, path_for_release_htlc: CBP
1641+
&mut self, chain_hash: ChainHash, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
16431642
) -> Result<Option<SignerResumeUpdates>, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
16441643
match &mut self.phase {
16451644
ChannelPhase::Undefined => unreachable!(),
1646-
ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(logger, path_for_release_htlc).map(|r| Some(r)),
1645+
ChannelPhase::Funded(chan) => chan.signer_maybe_unblocked(best_block_height, logger, path_for_release_htlc).map(|r| Some(r)),
16471646
ChannelPhase::UnfundedOutboundV1(chan) => {
16481647
let (open_channel, funding_created) = chan.signer_maybe_unblocked(chain_hash, logger);
16491648
Ok(Some(SignerResumeUpdates {
@@ -1653,8 +1652,7 @@ where
16531652
accept_channel: None,
16541653
funding_created,
16551654
funding_signed: None,
1656-
funding_commit_sig: None,
1657-
tx_signatures: None,
1655+
funding_tx_signed: None,
16581656
channel_ready: None,
16591657
order: chan.context.resend_order.clone(),
16601658
closing_signed: None,
@@ -1671,8 +1669,7 @@ where
16711669
accept_channel,
16721670
funding_created: None,
16731671
funding_signed: None,
1674-
funding_commit_sig: None,
1675-
tx_signatures: None,
1672+
funding_tx_signed: None,
16761673
channel_ready: None,
16771674
order: chan.context.resend_order.clone(),
16781675
closing_signed: None,
@@ -2204,36 +2201,42 @@ where
22042201
return Err(APIError::APIMisuseError { err });
22052202
};
22062203

2207-
let tx = signing_session.unsigned_tx().tx();
2208-
if funding_txid_signed != tx.compute_txid() {
2209-
return Err(APIError::APIMisuseError {
2210-
err: "Transaction was malleated prior to signing".to_owned(),
2211-
});
2212-
}
2204+
let (mut tx_signatures, mut funding_tx) = signing_session
2205+
.provide_holder_witnesses(
2206+
context.channel_id,
2207+
funding_txid_signed,
2208+
witnesses,
2209+
&context.secp_ctx,
2210+
)
2211+
.map_err(|err| APIError::APIMisuseError { err })?;
22132212

2214-
let shared_input_signature =
2215-
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2216-
let sig = context.holder_signer.sign_splice_shared_input(
2213+
debug_assert_eq!(
2214+
pending_splice.is_some(),
2215+
signing_session.unsigned_tx().shared_input_index().is_some()
2216+
);
2217+
if let Some(splice_input_index) = signing_session.unsigned_tx().shared_input_index() {
2218+
let sig = context
2219+
.holder_signer
2220+
.sign_splice_shared_input(
22172221
&funding.channel_transaction_parameters,
2218-
tx,
2222+
signing_session.unsigned_tx().tx(),
22192223
splice_input_index as usize,
22202224
&context.secp_ctx,
2221-
);
2222-
Some(sig)
2225+
)
2226+
.ok();
2227+
if let Some(sig) = sig {
2228+
(tx_signatures, funding_tx) = signing_session
2229+
.provide_holder_shared_input_signature(sig)
2230+
.map_err(|err| APIError::APIMisuseError { err })?;
22232231
} else {
2224-
None
2225-
};
2226-
debug_assert_eq!(pending_splice.is_some(), shared_input_signature.is_some());
2227-
2228-
let tx_signatures = msgs::TxSignatures {
2229-
channel_id: context.channel_id,
2230-
tx_hash: funding_txid_signed,
2231-
witnesses,
2232-
shared_input_signature,
2233-
};
2234-
let (tx_signatures, funding_tx) = signing_session
2235-
.provide_holder_witnesses(tx_signatures, &context.secp_ctx)
2236-
.map_err(|err| APIError::APIMisuseError { err })?;
2232+
log_debug!(
2233+
logger,
2234+
"Splice shared input signature not available, waiting on async signer"
2235+
);
2236+
debug_assert!(tx_signatures.is_none());
2237+
debug_assert!(funding_tx.is_none());
2238+
}
2239+
}
22372240

22382241
let logger = WithChannelContext::from(logger, &context, None);
22392242
if tx_signatures.is_some() {
@@ -9450,6 +9453,8 @@ where
94509453
}
94519454
}
94529455

9456+
let awaiting_holder_shared_input_signature =
9457+
signing_session.awaiting_holder_shared_input_signature();
94539458
let (holder_tx_signatures, funding_tx) =
94549459
signing_session.received_tx_signatures(msg).map_err(|msg| ChannelError::Warn(msg))?;
94559460

@@ -9488,6 +9493,11 @@ where
94889493
best_block_height,
94899494
&logger,
94909495
);
9496+
} else if awaiting_holder_shared_input_signature {
9497+
log_debug!(
9498+
logger,
9499+
"Waiting for funding transaction shared input signature before finalizing negotiation"
9500+
);
94919501
} else {
94929502
debug_assert!(
94939503
false,
@@ -9882,7 +9892,7 @@ where
98829892
/// blocked.
98839893
#[rustfmt::skip]
98849894
pub fn signer_maybe_unblocked<L: Logger, CBP>(
9885-
&mut self, logger: &L, path_for_release_htlc: CBP
9895+
&mut self, best_block_height: u32, logger: &L, path_for_release_htlc: CBP
98869896
) -> Result<SignerResumeUpdates, ChannelError> where CBP: Fn(u64) -> BlindedMessagePath {
98879897
if let Some((commitment_number, commitment_secret)) = self.context.signer_pending_stale_state_verification.clone() {
98889898
if let Ok(expected_point) = self
@@ -9938,16 +9948,65 @@ where
99389948
None
99399949
};
99409950

9941-
let tx_signatures = if funding_commit_sig.is_some() {
9951+
let mut shared_input_signature_unblocked = false;
9952+
{
9953+
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_mut() {
9954+
if signing_session.awaiting_holder_shared_input_signature() {
9955+
let splice_input_index = signing_session
9956+
.unsigned_tx()
9957+
.shared_input_index()
9958+
.expect("Missing shared input index while awaiting a splice signature");
9959+
log_trace!(logger, "Attempting to generate pending splice shared input signature...");
9960+
if let Ok(shared_input_signature) = self.context.holder_signer.sign_splice_shared_input(
9961+
&self.funding.channel_transaction_parameters,
9962+
signing_session.unsigned_tx().tx(),
9963+
splice_input_index as usize,
9964+
&self.context.secp_ctx,
9965+
) {
9966+
shared_input_signature_unblocked = true;
9967+
signing_session
9968+
.provide_holder_shared_input_signature(shared_input_signature)
9969+
.map_err(ChannelError::close)?;
9970+
}
9971+
}
9972+
}
9973+
}
9974+
9975+
let mut tx_signatures = None;
9976+
let mut funding_tx = None;
9977+
if funding_commit_sig.is_some() || shared_input_signature_unblocked {
99429978
if let Some(signing_session) = self.context.interactive_tx_signing_session.as_ref() {
9943-
signing_session.holder_tx_signatures().filter(|_| !self.is_awaiting_monitor_update())
9979+
if !self.is_awaiting_monitor_update() && !self.context.signer_pending_funding {
9980+
tx_signatures = signing_session.holder_tx_signatures();
9981+
funding_tx = tx_signatures.as_ref().and_then(|_| signing_session.signed_tx());
9982+
}
99449983
} else {
99459984
debug_assert!(false);
9946-
None
99479985
}
9948-
} else {
9949-
None
9950-
};
9986+
}
9987+
9988+
let mut funding_tx_signed = None;
9989+
if funding_commit_sig.is_some() || tx_signatures.is_some() || funding_tx.is_some() {
9990+
let mut resumed = FundingTxSigned {
9991+
commitment_signed: funding_commit_sig,
9992+
counterparty_initial_commitment_signed_result: None,
9993+
tx_signatures,
9994+
funding_tx: None,
9995+
splice_negotiated: None,
9996+
splice_locked: None,
9997+
};
9998+
if let Some(funding_tx) = funding_tx {
9999+
let funding_logger = WithChannelContext::from(logger, &self.context, None);
10000+
debug_assert!(resumed.tx_signatures.is_some());
10001+
self.on_tx_signatures_exchange(
10002+
&mut resumed,
10003+
funding_tx,
10004+
best_block_height,
10005+
&funding_logger,
10006+
);
10007+
}
10008+
funding_tx_signed = Some(resumed);
10009+
}
995110010

995210011
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
995310012
// funding.
@@ -10013,8 +10072,8 @@ where
1001310072
if revoke_and_ack.is_some() { "a" } else { "no" },
1001410073
self.context.resend_order,
1001510074
if funding_signed.is_some() { "a" } else { "no" },
10016-
if funding_commit_sig.is_some() { "a" } else { "no" },
10017-
if tx_signatures.is_some() { "a" } else { "no" },
10075+
if funding_tx_signed.as_ref().map(|v| v.commitment_signed.is_some()).unwrap_or(false) { "a" } else { "no" },
10076+
if funding_tx_signed.as_ref().map(|v| v.tx_signatures.is_some()).unwrap_or(false) { "a" } else { "no" },
1001810077
if channel_ready.is_some() { "a" } else { "no" },
1001910078
if closing_signed.is_some() { "a" } else { "no" },
1002010079
if signed_closing_tx.is_some() { "a" } else { "no" },
@@ -10027,8 +10086,7 @@ where
1002710086
accept_channel: None,
1002810087
funding_created: None,
1002910088
funding_signed,
10030-
funding_commit_sig,
10031-
tx_signatures,
10089+
funding_tx_signed,
1003210090
channel_ready,
1003310091
order: self.context.resend_order.clone(),
1003410092
closing_signed,

0 commit comments

Comments
 (0)