diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index bf065d80eae..fda8b9f7181 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -9,12 +9,13 @@ use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; -use bitcoin::script::{Script, ScriptBuf, Builder}; -use bitcoin::transaction::{Transaction, TxIn, TxOut}; +use bitcoin::script::{Script, ScriptBuf, Builder, WScriptHash}; +use bitcoin::transaction::{Transaction, TxIn}; use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::consensus::encode; use bitcoin::absolute::LockTime; +use bitcoin::witness::Witness; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; @@ -29,9 +30,9 @@ use bitcoin::secp256k1; use crate::ln::types::{ChannelId, PaymentPreimage, PaymentHash}; use crate::ln::features::{ChannelTypeFeatures, InitFeatures}; use crate::ln::interactivetxs::{ - estimate_input_weight, get_output_weight, HandleTxCompleteResult, - InteractiveTxConstructor, InteractiveTxConstructorArgs, InteractiveTxMessageSend, - InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT, + ConstructedTransaction, estimate_input_weight, get_output_weight, + HandleTxCompleteResult, InteractiveTxConstructor, InteractiveTxConstructorArgs, + InteractiveTxSigningSession, InteractiveTxMessageSendResult, TX_COMMON_FIELDS_WEIGHT, }; use crate::ln::msgs; use crate::ln::msgs::{ClosingSigned, ClosingSignedFeeRange, DecodeError}; @@ -54,7 +55,7 @@ use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Channel use crate::chain::transaction::{OutPoint, TransactionData}; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::{EntropySource, ChannelSigner, SignerProvider, NodeSigner, Recipient}; -use crate::events::ClosureReason; +use crate::events::{ClosureReason, Event}; use crate::routing::gossip::NodeId; use crate::util::ser::{Readable, ReadableArgs, TransactionU16LenLimited, Writeable, Writer}; use crate::util::logger::{Logger, Record, WithContext}; @@ -1559,6 +1560,63 @@ pub(super) trait InteractivelyFunded where SP::Target: SignerProvider }), }) } + + fn internal_funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let our_funding_satoshis = self.dual_funding_context().our_funding_satoshis; + + let mut output_index = None; + let expected_spk = self.context().get_funding_redeemscript().to_p2wsh(); + for (idx, outp) in signing_session.unsigned_tx.outputs().enumerate() { + if outp.script_pubkey() == &expected_spk && outp.value() == self.context().get_value_satoshis() { + if output_index.is_some() { + return Err(ChannelError::Close( + ( + "Multiple outputs matched the expected script and value".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + output_index = Some(idx as u16); + } + } + let outpoint = if let Some(output_index) = output_index { + OutPoint { txid: signing_session.unsigned_tx.txid(), index: output_index } + } else { + return Err(ChannelError::Close( + ( + "No output matched the funding script_pubkey".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + }; + self.context_mut().channel_transaction_parameters.funding_outpoint = Some(outpoint); + { + let channel_context = self.context_mut(); + channel_context.holder_signer.as_mut().provide_channel_parameters(&channel_context.channel_transaction_parameters); + } + + let commitment_signed = get_initial_commitment_signed(self.context_mut(), &signing_session.unsigned_tx, logger); + let commitment_signed = match commitment_signed { + Ok(commitment_signed) => commitment_signed, + Err(err) => return Err(ChannelError::Close((err.to_string(), ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }))), + }; + + let funding_ready_for_sig_event = None; + if our_funding_satoshis == 0 { + signing_session.provide_holder_witnesses(self.context().channel_id, Vec::new()); + } else { + // TODO(dual_funding): Send event for signing if we've contributed funds. + } + + // Clear the interactive transaction constructor + self.interactive_tx_constructor_mut().take(); + self.context_mut().channel_state = ChannelState::FundingNegotiated; + + Ok((commitment_signed, funding_ready_for_sig_event)) + } } impl InteractivelyFunded for OutboundV2Channel where SP::Target: SignerProvider { @@ -1597,7 +1655,7 @@ impl InteractivelyFunded for InboundV2Channel where SP::Targe } } -impl ChannelContext where SP::Target: SignerProvider { +impl ChannelContext where SP::Target: SignerProvider { fn new_for_inbound_channel<'a, ES: Deref, F: Deref, L: Deref>( fee_estimator: &'a LowerBoundedFeeEstimator, entropy_source: &'a ES, @@ -2937,7 +2995,7 @@ impl ChannelContext where SP::Target: SignerProvider { } /// Gets the redeemscript for the funding transaction output (ie the funding transaction output - /// pays to get_funding_redeemscript().to_v0_p2wsh()). + /// pays to get_funding_redeemscript().to_p2wsh()). /// Panics if called before accept_channel/InboundV1Channel::new pub fn get_funding_redeemscript(&self) -> ScriptBuf { make_funding_redeemscript(&self.get_holder_pubkeys().funding_pubkey, self.counterparty_funding_pubkey()) @@ -3725,14 +3783,13 @@ impl ChannelContext where SP::Target: SignerProvider { Ok(()) } - // Interactive transaction construction - - pub fn tx_signatures(&self, msg: &msgs::TxSignatures) -> Result { - todo!(); - } - - pub fn tx_abort(&self, msg: &msgs::TxAbort) -> Result { - todo!(); + /// Panics if the commitment tx numbers have advanced from their initial number. + fn assert_no_commitment_advancement(&self) { + if self.commitment_secrets.get_min_seen_secret() != (1 << 48) || + self.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + self.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); + } } } @@ -3793,8 +3850,7 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos pub(super) fn calculate_our_funding_satoshis( is_initiator: bool, funding_inputs: &[(TxIn, TransactionU16LenLimited)], - funding_outputs: &[TxOut], funding_feerate_sat_per_1000_weight: u32, - holder_dust_limit_satoshis: u64, + funding_feerate_sat_per_1000_weight: u32, holder_dust_limit_satoshis: u64, ) -> Result { let mut total_input_satoshis = 0u64; let mut our_contributed_weight = 0u64; @@ -3809,13 +3865,18 @@ pub(super) fn calculate_our_funding_satoshis( input.1.as_transaction().compute_txid(), input.0.previous_output.vout, idx) }); } } - our_contributed_weight = our_contributed_weight.saturating_add(funding_outputs.iter().fold(0u64, |weight, txout| { - weight.saturating_add(get_output_weight(&txout.script_pubkey).to_wu()) - })); // If we are the initiator, we must pay for weight of all common fields in the funding transaction. if is_initiator { - our_contributed_weight = our_contributed_weight.saturating_add(TX_COMMON_FIELDS_WEIGHT); + our_contributed_weight = our_contributed_weight + .saturating_add(TX_COMMON_FIELDS_WEIGHT) + // The weight of a P2WSH output to be added later. + // + // NOTE: The witness script hash given here is irrelevant as it's a fixed size and we just want + // to calculate the contributed weight, so we use an all-zero hash. + .saturating_add(get_output_weight(&ScriptBuf::new_p2wsh( + &WScriptHash::from_raw_hash(Hash::all_zeros()) + )).to_wu()) } let funding_satoshis = total_input_satoshis @@ -3832,7 +3893,7 @@ pub(super) struct DualFundingChannelContext { /// The amount in satoshis we will be contributing to the channel. pub our_funding_satoshis: u64, /// The amount in satoshis our counterparty will be contributing to the channel. - pub their_funding_satoshis: u64, + pub their_funding_satoshis: Option, /// The funding transaction locktime suggested by the initiator. If set by us, it is always set /// to the current block height to align incentives against fee-sniping. pub funding_tx_locktime: LockTime, @@ -3852,7 +3913,8 @@ pub(super) struct Channel where SP::Target: SignerProvider { pub context: ChannelContext, pub dual_funding_channel_context: Option, /// The current interactive transaction construction session under negotiation. - interactive_tx_constructor: Option, + pub interactive_tx_constructor: Option, + pub interactive_tx_signing_session: Option, } #[cfg(any(test, fuzzing))] @@ -4623,6 +4685,105 @@ impl Channel where Ok(()) } + pub fn commitment_signed_initial_v2( + &mut self, msg: &msgs::CommitmentSigned, best_block: BestBlock, signer_provider: &SP, logger: &L + ) -> Result::EcdsaSigner>, ChannelError> + where L::Target: Logger + { + if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { + return Err(ChannelError::Close( + ( + "Received initial commitment_signed before funding transaction constructed!".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + self.context.assert_no_commitment_advancement(); + + let funding_script = self.context.get_funding_redeemscript(); + + let counterparty_keys = self.context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust(); + let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction(); + + log_trace!(logger, "Initial counterparty tx for channel {} is: txid {} tx {}", + &self.context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + + let holder_signer = self.context.build_holder_transaction_keys(); + let initial_commitment_tx = self.context.build_commitment_transaction( + self.context.holder_commitment_point.transaction_number(), &holder_signer, true, false, logger + ).tx; + { + let trusted_tx = initial_commitment_tx.trust(); + let initial_commitment_bitcoin_tx = trusted_tx.built_transaction(); + let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); + // They sign our commitment transaction, allowing us to broadcast the tx if we wish. + if self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.get_counterparty_pubkeys().funding_pubkey).is_err() { + return Err(ChannelError::Close( + ( + "Invalid funding_signed signature from peer".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + } + + let holder_commitment_tx = HolderCommitmentTransaction::new( + initial_commitment_tx, + msg.signature, + Vec::new(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()) + .map_err(|_| ChannelError::Close( + ( + "Failed to validate our commitment".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + )))?; + + let funding_redeemscript = self.context.get_funding_redeemscript(); + let funding_txo = self.context.get_funding_txo().unwrap(); + let funding_txo_script = funding_redeemscript.to_p2wsh(); + let obscure_factor = get_commitment_transaction_number_obscure_factor(&self.context.get_holder_pubkeys().payment_point, &self.context.get_counterparty_pubkeys().payment_point, self.context.is_outbound()); + let shutdown_script = self.context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); + let mut monitor_signer = signer_provider.derive_channel_signer(self.context.channel_value_satoshis, self.context.channel_keys_id); + monitor_signer.provide_channel_parameters(&self.context.channel_transaction_parameters); + let channel_monitor = ChannelMonitor::new(self.context.secp_ctx.clone(), monitor_signer, + shutdown_script, self.context.get_holder_selected_contest_delay(), + &self.context.destination_script, (funding_txo, funding_txo_script), + &self.context.channel_transaction_parameters, self.context.is_outbound(), + funding_redeemscript.clone(), self.context.channel_value_satoshis, + obscure_factor, + holder_commitment_tx, best_block, self.context.counterparty_node_id, self.context.channel_id()); + + channel_monitor.provide_initial_counterparty_commitment_tx( + counterparty_initial_bitcoin_tx.txid, Vec::new(), + self.context.cur_counterparty_commitment_transaction_number, + self.context.counterparty_cur_commitment_point.unwrap(), + counterparty_initial_commitment_tx.feerate_per_kw(), + counterparty_initial_commitment_tx.to_broadcaster_value_sat(), + counterparty_initial_commitment_tx.to_countersignatory_value_sat(), logger); + + assert!(!self.context.channel_state.is_monitor_update_in_progress()); // We have no had any monitor(s) yet to fail update! + if self.context.holder_commitment_point.advance(&self.context.holder_signer, &self.context.secp_ctx, logger).is_err() { + // We only fail to advance our commitment point/number if we're currently + // waiting for our signer to unblock and provide a commitment point. + // If we err here by the time we receive the initial commitment_signed, something has gone wrong. + debug_assert!(false, "We should be ready to advance our commitment point by the time we receive initial commitment_signed"); + return Err(ChannelError::close("Failed to advance holder commitment point".to_owned())); + } + self.context.cur_counterparty_commitment_transaction_number -= 1; + + log_info!(logger, "Received initial commitment_signed from peer for channel {}", &self.context.channel_id()); + + let need_channel_ready = self.check_get_channel_ready(0, logger).is_some(); + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + self.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); + + Ok(channel_monitor) + } + pub fn commitment_signed(&mut self, msg: &msgs::CommitmentSigned, logger: &L) -> Result, ChannelError> where L::Target: Logger { @@ -5288,6 +5449,65 @@ impl Channel where } } + pub fn verify_interactive_tx_signatures(&mut self, _witnesses: &Vec) { + if let Some(ref mut _signing_session) = self.interactive_tx_signing_session { + // Check that sighash_all was used: + // TODO(dual_funding): Check sig for sighash + } + } + + pub fn tx_signatures(&mut self, msg: &msgs::TxSignatures) -> Result<(Option, Option), ChannelError> { + if let Some(ref mut signing_session) = self.interactive_tx_signing_session { + if msg.witnesses.len() != signing_session.remote_inputs_count() { + return Err(ChannelError::Close( + ( + "Witness count did not match contributed input count".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + for witness in &msg.witnesses { + if witness.is_empty() { + return Err(ChannelError::Close( + ( + "Unexpected empty witness in tx_signatures received".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + // TODO(dual_funding): Check all sigs are SIGHASH_ALL. + + // TODO(dual_funding): I don't see how we're going to be able to ensure witness-standardness + // for spending. Doesn't seem to be anything in rust-bitcoin. + } + + if msg.tx_hash != signing_session.unsigned_tx.txid() { + return Err(ChannelError::Close( + ( + "The txid for the transaction does not match".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + + let (tx_signatures_opt, funding_tx_opt) = signing_session.received_tx_signatures(msg.clone()); + if funding_tx_opt.is_some() { + self.context.channel_state = ChannelState::AwaitingChannelReady(AwaitingChannelReadyFlags::new()); + } + self.context.funding_transaction = funding_tx_opt.clone(); + + // Clear out the signing session + self.interactive_tx_signing_session = None; + + Ok((tx_signatures_opt, funding_tx_opt)) + } else { + return Err(ChannelError::Close( + ( + "Unexpected tx_signatures. No funding transaction awaiting signatures".to_string(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))); + } + } + /// Queues up an outbound update fee by placing it in the holding cell. You should call /// [`Self::maybe_free_holding_cell_htlcs`] in order to actually generate and send the /// commitment update. @@ -7872,11 +8092,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { ) { panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement(); self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); self.context.holder_signer.as_mut().provide_channel_parameters(&self.context.channel_transaction_parameters); @@ -7982,7 +8198,8 @@ impl OutboundV1Channel where SP::Target: SignerProvider { &mut self, msg: &msgs::AcceptChannel, default_limits: &ChannelHandshakeLimits, their_features: &InitFeatures ) -> Result<(), ChannelError> { - self.context.do_accept_channel_checks(default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) + self.context.do_accept_channel_checks( + default_limits, their_features, &msg.common_fields, msg.channel_reserve_satoshis) } /// Handles a funding_signed message from the remote end. @@ -7999,11 +8216,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { if !matches!(self.context.channel_state, ChannelState::FundingNegotiated) { return Err((self, ChannelError::close("Received funding_signed in strange state!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement(); let funding_script = self.context.get_funding_redeemscript(); @@ -8085,6 +8298,7 @@ impl OutboundV1Channel where SP::Target: SignerProvider { context: self.context, dual_funding_channel_context: None, interactive_tx_constructor: None, + interactive_tx_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); @@ -8301,11 +8515,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { // channel. return Err((self, ChannelError::close("Received funding_created after we got the channel!".to_owned()))); } - if self.context.commitment_secrets.get_min_seen_secret() != (1 << 48) || - self.context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || - self.context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { - panic!("Should not have advanced channel commitment tx numbers prior to funding_created"); - } + self.context.assert_no_commitment_advancement(); let funding_txo = OutPoint { txid: msg.funding_txid, index: msg.funding_output_index }; self.context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); @@ -8383,6 +8593,7 @@ impl InboundV1Channel where SP::Target: SignerProvider { context: self.context, dual_funding_channel_context: None, interactive_tx_constructor: None, + interactive_tx_signing_session: None, }; let need_channel_ready = channel.check_get_channel_ready(0, logger).is_some(); channel.monitor_updating_paused(false, false, need_channel_ready, Vec::new(), Vec::new(), Vec::new()); @@ -8451,7 +8662,7 @@ impl OutboundV2Channel where SP::Target: SignerProvider { unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: 0, + their_funding_satoshis: None, funding_tx_locktime, funding_feerate_sat_per_1000_weight, our_funding_inputs: funding_inputs, @@ -8523,6 +8734,33 @@ impl OutboundV2Channel where SP::Target: SignerProvider { require_confirmed_inputs: None, } } + + pub fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let (commitment_signed, funding_ready_for_sig_event) = match self.internal_funding_tx_constructed( + signing_session, logger, + ) { + Ok(res) => res, + Err(err) => return Err(err), + }; + + Ok((commitment_signed, funding_ready_for_sig_event)) + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + interactive_tx_constructor: None, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // A not-yet-funded inbound (from counterparty) channel using V2 channel establishment. @@ -8539,7 +8777,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { /// Assumes chain_hash has already been checked and corresponds with what we expect! pub fn new( fee_estimator: &LowerBoundedFeeEstimator, entropy_source: &ES, signer_provider: &SP, - counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, our_supported_features: &ChannelTypeFeatures, their_features: &InitFeatures, msg: &msgs::OpenChannelV2, funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig, current_chain_height: u32, logger: &L, @@ -8549,7 +8787,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { L::Target: Logger, { let funding_satoshis = calculate_our_funding_satoshis( - false, &funding_inputs, &[], msg.funding_feerate_sat_per_1000_weight, + false, &funding_inputs, msg.funding_feerate_sat_per_1000_weight, msg.common_fields.dust_limit_satoshis ).map_err(|_| ChannelError::Close( ( @@ -8609,7 +8847,7 @@ impl InboundV2Channel where SP::Target: SignerProvider { unfunded_context: UnfundedChannelContext { unfunded_channel_age_ticks: 0 }, dual_funding_context: DualFundingChannelContext { our_funding_satoshis: funding_satoshis, - their_funding_satoshis: msg.common_fields.funding_satoshis, + their_funding_satoshis: Some(msg.common_fields.funding_satoshis), funding_tx_locktime: LockTime::from_consensus(msg.locktime), funding_feerate_sat_per_1000_weight: msg.funding_feerate_sat_per_1000_weight, our_funding_inputs: funding_inputs, @@ -8620,6 +8858,8 @@ impl InboundV2Channel where SP::Target: SignerProvider { match InteractiveTxConstructor::new( InteractiveTxConstructorArgs { entropy_source, + holder_node_id, + counterparty_node_id, channel_id: channel.context.channel_id, feerate_sat_per_kw: channel.dual_funding_context.funding_feerate_sat_per_1000_weight, funding_tx_locktime: channel.dual_funding_context.funding_tx_locktime, @@ -8713,6 +8953,33 @@ impl InboundV2Channel where SP::Target: SignerProvider { pub fn get_accept_channel_v2_message(&self) -> msgs::AcceptChannelV2 { self.generate_accept_channel_v2_message() } + + pub fn funding_tx_constructed( + &mut self, signing_session: &mut InteractiveTxSigningSession, logger: &L + ) -> Result<(msgs::CommitmentSigned, Option), ChannelError> + where + L::Target: Logger + { + let (commitment_signed, funding_ready_for_sig_event) = match self.internal_funding_tx_constructed( + signing_session, logger, + ) { + Ok(res) => res, + Err(err) => return Err(err), + }; + + Ok((commitment_signed, funding_ready_for_sig_event)) + } + + pub fn into_channel(self, signing_session: InteractiveTxSigningSession) -> Result, ChannelError>{ + let channel = Channel { + context: self.context, + dual_funding_channel_context: Some(self.dual_funding_context), + interactive_tx_constructor: None, + interactive_tx_signing_session: Some(signing_session), + }; + + Ok(channel) + } } // Unfunded channel utilities @@ -8740,6 +9007,85 @@ fn get_initial_channel_type(config: &UserConfig, their_features: &InitFeatures) ret } +fn get_initial_counterparty_commitment_signature( + context: &mut ChannelContext, logger: &L +) -> Result +where + SP::Target: SignerProvider, + L::Target: Logger +{ + let counterparty_keys = context.build_remote_transaction_keys(); + let counterparty_initial_commitment_tx = context.build_commitment_transaction( + context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx; + match context.holder_signer { + // TODO (taproot|arik): move match into calling method for Taproot + ChannelSignerType::Ecdsa(ref ecdsa) => { + Ok(ecdsa.sign_counterparty_commitment(&counterparty_initial_commitment_tx, Vec::new(), Vec::new(), &context.secp_ctx) + .map_err(|_| ChannelError::Close( + ( + "Failed to get signatures for new commitment_signed".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + )))?.0) + }, + // TODO (taproot|arik) + #[cfg(taproot)] + _ => todo!(), + } +} + +fn get_initial_commitment_signed( + context: &mut ChannelContext, transaction: &ConstructedTransaction, logger: &L +) -> Result +where + SP::Target: SignerProvider, + L::Target: Logger +{ + if !matches!( + context.channel_state, ChannelState::NegotiatingFunding(flags) + if flags == (NegotiatingFundingFlags::OUR_INIT_SENT | NegotiatingFundingFlags::THEIR_INIT_SENT)) { + panic!("Tried to get a funding_created messsage at a time other than immediately after initial handshake completion (or tried to get funding_created twice)"); + } + if context.commitment_secrets.get_min_seen_secret() != (1 << 48) || + context.cur_counterparty_commitment_transaction_number != INITIAL_COMMITMENT_NUMBER || + context.holder_commitment_point.transaction_number() != INITIAL_COMMITMENT_NUMBER { + panic!("Should not have advanced channel commitment tx numbers prior to initial commitment_signed"); + } + + let funding_redeemscript = context.get_funding_redeemscript().to_p2wsh(); + let funding_outpoint_index = transaction.outputs().enumerate().find_map( + |(idx, output)| { + if output.tx_out().script_pubkey == funding_redeemscript { Some(idx as u16) } else { None } + }).expect("funding transaction contains funding output"); + let funding_txo = OutPoint { txid: transaction.txid(), index: funding_outpoint_index }; + context.channel_transaction_parameters.funding_outpoint = Some(funding_txo); + context.holder_signer.as_mut().provide_channel_parameters(&context.channel_transaction_parameters); + + let signature = match get_initial_counterparty_commitment_signature(context, logger) { + Ok(res) => res, + Err(e) => { + log_error!(logger, "Got bad signatures: {:?}!", e); + context.channel_transaction_parameters.funding_outpoint = None; + return Err(e); + } + }; + + if context.signer_pending_funding { + log_trace!(logger, "Counterparty commitment signature ready for funding_created message: clearing signer_pending_funding"); + context.signer_pending_funding = false; + } + + log_info!(logger, "Generated commitment_signed for peer for channel {}", &context.channel_id()); + + Ok(msgs::CommitmentSigned { + channel_id: context.channel_id, + htlc_signatures: vec![], + signature, + batch: None, + #[cfg(taproot)] + partial_signature_with_nonce: None, + }) +} + const SERIALIZATION_VERSION: u8 = 4; const MIN_SERIALIZATION_VERSION: u8 = 3; @@ -9780,6 +10126,7 @@ impl<'a, 'b, 'c, ES: Deref, SP: Deref> ReadableArgs<(&'a ES, &'b SP, u32, &'c Ch }, dual_funding_channel_context: None, interactive_tx_constructor: None, + interactive_tx_signing_session: None, }) } } diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 267bbfb1d09..2fe6ba9029f 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -47,9 +47,12 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::{ChannelId, PaymentHash, PaymentPreimage, PaymentSecret}; -use crate::ln::channel::{self, Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, WithChannelContext}; +use crate::ln::channel::{ + self, Channel, ChannelPhase, ChannelContext, ChannelError, ChannelUpdateStatus, ShutdownResult, + UnfundedChannelContext, UpdateFulfillCommitFetch, OutboundV1Channel, InboundV1Channel, + InboundV2Channel, WithChannelContext, InteractivelyFunded as _, +}; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channel::{InboundV2Channel, InteractivelyFunded as _}; use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures}; #[cfg(any(feature = "_test_utils", test))] use crate::ln::features::Bolt11InvoiceFeatures; @@ -58,7 +61,7 @@ use crate::ln::onion_payment::{check_incoming_htlc_cltv, create_recv_pending_htl use crate::ln::msgs; use crate::ln::onion_utils; use crate::ln::onion_utils::{HTLCFailReason, INVALID_ONION_BLINDING}; -use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError}; +use crate::ln::msgs::{ChannelMessageHandler, CommitmentUpdate, DecodeError, LightningError}; #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; @@ -4957,6 +4960,53 @@ where result } + /// Handles a signed funding transaction generated by interactive transaction construction and + /// provided by the client. + /// + /// Do NOT broadcast the funding transaction yourself. When we have safely received our + /// counterparty's signature(s) the funding transaction will automatically be broadcast via the + /// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed. + pub fn funding_transaction_signed( + &self, channel_id: &ChannelId, counterparty_node_id: &PublicKey, transaction: Transaction + ) -> Result<(), APIError> { + let witnesses: Vec<_> = transaction.input.into_iter().filter_map(|input| { + if input.witness.is_empty() { None } else { Some(input.witness) } + }).collect(); + + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| APIError::ChannelUnavailable { + err: format!("Can't find a peer matching the passed counterparty node_id {}", + counterparty_node_id) })?; + + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + + match peer_state.channel_by_id.get_mut(channel_id) { + Some(ChannelPhase::Funded(chan)) => { + chan.verify_interactive_tx_signatures(&witnesses); + if let Some(ref mut signing_session) = chan.interactive_tx_signing_session { + if let Some(tx_signatures) = signing_session.provide_holder_witnesses(*channel_id, witnesses) { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + } else { + return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not expecting funding signatures", channel_id)}); + } + }, + Some(_) => return Err(APIError::APIMisuseError { + err: format!("Channel with id {} not expecting funding signatures", channel_id)}), + None => return Err(APIError::ChannelUnavailable{ + err: format!("Channel with id {} not found for the passed counterparty node_id {}", channel_id, + counterparty_node_id) }), + } + + Ok(()) + } + /// Atomically applies partial updates to the [`ChannelConfig`] of the given channels. /// /// Once the updates are applied, each eligible channel (advertised with a known short channel @@ -7070,7 +7120,7 @@ where /// Gets the node_id held by this ChannelManager pub fn get_our_node_id(&self) -> PublicKey { - self.our_network_pubkey.clone() + self.our_network_pubkey } fn handle_monitor_update_completion_actions>(&self, actions: I) { @@ -7394,7 +7444,7 @@ where }, OpenChannelMessage::V2(open_channel_msg) => { InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, &self.signer_provider, - *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, + self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, &open_channel_msg, funding_inputs, user_channel_id, &self.default_configuration, best_block_height, &self.logger ) @@ -7713,7 +7763,7 @@ where random_bytes.copy_from_slice(&self.entropy_source.get_secure_random_bytes()[..16]); let user_channel_id = u128::from_be_bytes(random_bytes); let mut channel = match InboundV2Channel::new(&self.fee_estimator, &self.entropy_source, - &self.signer_provider, *counterparty_node_id, &self.channel_type_features(), + &self.signer_provider, self.get_our_node_id(), *counterparty_node_id, &self.channel_type_features(), &peer_state.latest_features, msg, vec![], user_channel_id, &self.default_configuration, best_block_height, &self.logger) { @@ -8043,8 +8093,7 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - let channel_phase = chan_phase_entry.get_mut(); - let (msg_send_event_opt, tx_opt) = match channel_phase { + let (msg_send_event_opt, signing_session_opt) = match chan_phase_entry.get_mut() { ChannelPhase::UnfundedInboundV2(channel) => { channel.tx_complete(msg).into_msg_send_event(counterparty_node_id) }, @@ -8059,11 +8108,56 @@ where }; if let Some(msg_send_event) = msg_send_event_opt { peer_state.pending_msg_events.push(msg_send_event); + }; + if let Some(mut signing_session) = signing_session_opt { + match chan_phase_entry.get_mut() { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.funding_tx_constructed(&mut signing_session, &self.logger) + }, + _ => Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())), + }.and_then(|(commitment_signed, funding_ready_for_sig_event_opt)| { + let (channel_id, channel_phase) = chan_phase_entry.remove_entry(); + match channel_phase { + ChannelPhase::UnfundedOutboundV2(chan) => { + chan.into_channel(signing_session) + }, + ChannelPhase::UnfundedInboundV2(chan) => { + chan.into_channel(signing_session) + }, + _ => { + debug_assert!(false); // It cannot be another variant as we are in the `Ok` branch of the above match. + Err(ChannelError::Warn( + "Got a tx_complete message with no interactive transaction construction expected or in-progress" + .into())) + } + }.map(|channel| (channel_id, channel, funding_ready_for_sig_event_opt, commitment_signed)) + }).map(|(channel_id, channel, funding_ready_for_sig_event_opt, commitment_signed)| { + peer_state.channel_by_id.insert(channel_id, ChannelPhase::Funded(channel)); + if let Some(funding_ready_for_sig_event) = funding_ready_for_sig_event_opt { + let mut pending_events = self.pending_events.lock().unwrap(); + pending_events.push_back((funding_ready_for_sig_event, None)); + } + peer_state.pending_msg_events.push(events::MessageSendEvent::UpdateHTLCs { + node_id: counterparty_node_id, + updates: CommitmentUpdate { + commitment_signed, + update_add_htlcs: vec![], + update_fulfill_htlcs: vec![], + update_fail_htlcs: vec![], + update_fail_malformed_htlcs: vec![], + update_fee: None, + }, + }); + }).map_err(|err| MsgHandleErrInternal::send_err_msg_no_close(format!("{}", err), msg.channel_id)) + } else { + // We're not in a signing session yet so we don't need to do anything else. + Ok(()) } - if let Some(tx) = tx_opt { - // TODO(dual_funding): Handle this unsigned transaction. - } - Ok(()) }, hash_map::Entry::Vacant(_) => { Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) @@ -8071,16 +8165,93 @@ where } } - fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id)), *counterparty_node_id); + fn internal_tx_signatures(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxSignatures) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + match channel_phase { + ChannelPhase::Funded(chan) => { + let (tx_signatures_opt, funding_tx_opt) = try_chan_phase_entry!(self, chan.tx_signatures(msg), chan_phase_entry); + if let Some(tx_signatures) = tx_signatures_opt { + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxSignatures { + node_id: *counterparty_node_id, + msg: tx_signatures, + }); + } + if let Some(ref funding_tx) = funding_tx_opt { + self.tx_broadcaster.broadcast_transactions(&[funding_tx]); + { + let mut pending_events = self.pending_events.lock().unwrap(); + emit_channel_pending_event!(pending_events, chan); + } + } + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Got an unexpected tx_signatures message".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } } - fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id)), *counterparty_node_id); + fn internal_tx_abort(&self, counterparty_node_id: &PublicKey, msg: &msgs::TxAbort) + -> Result<(), MsgHandleErrInternal> { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state_mutex = per_peer_state.get(counterparty_node_id) + .ok_or_else(|| { + debug_assert!(false); + MsgHandleErrInternal::send_err_msg_no_close( + format!("Can't find a peer matching the passed counterparty node_id {}", counterparty_node_id), + msg.channel_id) + })?; + let mut peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &mut *peer_state_lock; + match peer_state.channel_by_id.entry(msg.channel_id) { + hash_map::Entry::Occupied(mut chan_phase_entry) => { + let channel_phase = chan_phase_entry.get_mut(); + let tx_constructor = match channel_phase { + ChannelPhase::UnfundedInboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::UnfundedOutboundV2(chan) => chan.interactive_tx_constructor_mut(), + ChannelPhase::Funded(chan) => &mut chan.interactive_tx_constructor, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Got an unexpected tx_signatures message".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry) + }; + if tx_constructor.take().is_some() { + let msg = msgs::TxAbort { + channel_id: msg.channel_id, + data: "Acknowledged tx_abort".to_string().into_bytes(), + }; + peer_state.pending_msg_events.push(events::MessageSendEvent::SendTxAbort { + node_id: *counterparty_node_id, + msg, + }); + } + Ok(()) + }, + hash_map::Entry::Vacant(_) => { + Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + } + } } fn internal_channel_ready(&self, counterparty_node_id: &PublicKey, msg: &msgs::ChannelReady) -> Result<(), MsgHandleErrInternal> { @@ -8451,6 +8622,7 @@ where } fn internal_commitment_signed(&self, counterparty_node_id: &PublicKey, msg: &msgs::CommitmentSigned) -> Result<(), MsgHandleErrInternal> { + let best_block = *self.best_block.read().unwrap(); let per_peer_state = self.per_peer_state.read().unwrap(); let peer_state_mutex = per_peer_state.get(counterparty_node_id) .ok_or_else(|| { @@ -8461,21 +8633,46 @@ where let peer_state = &mut *peer_state_lock; match peer_state.channel_by_id.entry(msg.channel_id) { hash_map::Entry::Occupied(mut chan_phase_entry) => { - if let ChannelPhase::Funded(chan) = chan_phase_entry.get_mut() { - let logger = WithChannelContext::from(&self.logger, &chan.context, None); - let funding_txo = chan.context.get_funding_txo(); - let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &&logger), chan_phase_entry); - if let Some(monitor_update) = monitor_update_opt { - handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, - peer_state, per_peer_state, chan); - } - Ok(()) - } else { - return try_chan_phase_entry!(self, Err(ChannelError::close( - "Got a commitment_signed message for an unfunded channel!".into())), chan_phase_entry); + match chan_phase_entry.get_mut() { + ChannelPhase::Funded(chan) => { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + let funding_txo = chan.context.get_funding_txo(); + + if chan.interactive_tx_signing_session.is_some() { + let monitor = try_chan_phase_entry!( + self, chan.commitment_signed_initial_v2(msg, best_block, &self.signer_provider, &&logger), + chan_phase_entry); + let monitor_res = self.chain_monitor.watch_channel(monitor.get_funding_txo().0, monitor); + if let Ok(persist_state) = monitor_res { + handle_new_monitor_update!(self, persist_state, peer_state_lock, peer_state, + per_peer_state, chan, INITIAL_MONITOR); + } else { + let logger = WithChannelContext::from(&self.logger, &chan.context, None); + log_error!(logger, "Persisting initial ChannelMonitor failed, implying the funding outpoint was duplicated"); + try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Channel funding outpoint was a duplicate".to_owned(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ) + )), chan_phase_entry) + } + } else { + let monitor_update_opt = try_chan_phase_entry!(self, chan.commitment_signed(&msg, &&logger), chan_phase_entry); + if let Some(monitor_update) = monitor_update_opt { + handle_new_monitor_update!(self, funding_txo.unwrap(), monitor_update, peer_state_lock, + peer_state, per_peer_state, chan); + } + } + Ok(()) + }, + _ => try_chan_phase_entry!(self, Err(ChannelError::Close( + ( + "Got a commitment_signed message for an unfunded channel!".into(), + ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(false) }, + ))), chan_phase_entry), } }, - hash_map::Entry::Vacant(_) => return Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) + hash_map::Entry::Vacant(_) => Err(MsgHandleErrInternal::send_err_msg_no_close(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", counterparty_node_id), msg.channel_id)) } } @@ -11178,9 +11375,8 @@ where } fn handle_tx_signatures(&self, counterparty_node_id: PublicKey, msg: &msgs::TxSignatures) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); + let _ = handle_error!(self, self.internal_tx_signatures(&counterparty_node_id, msg), counterparty_node_id); } fn handle_tx_init_rbf(&self, counterparty_node_id: PublicKey, msg: &msgs::TxInitRbf) { @@ -11196,9 +11392,13 @@ where } fn handle_tx_abort(&self, counterparty_node_id: PublicKey, msg: &msgs::TxAbort) { - let _: Result<(), _> = handle_error!(self, Err(MsgHandleErrInternal::send_err_msg_no_close( - "Dual-funded channels not supported".to_owned(), - msg.channel_id.clone())), counterparty_node_id); + // Note that we never need to persist the updated ChannelManager for an inbound + // tx_add_input message - interactive transaction construction does not need to + // be persisted before any signatures are exchanged. + let _persistence_guard = PersistenceNotifierGuard::optionally_notify(self, || { + let _ = handle_error!(self, self.internal_tx_abort(&counterparty_node_id, msg), counterparty_node_id); + NotifyOption::SkipPersistHandleEvents + }); } fn message_received(&self) { @@ -14647,6 +14847,9 @@ mod tests { expect_pending_htlcs_forwardable!(nodes[0]); } + + // Dual-funding: V2 Channel Establishment Tests + // TODO(dual_funding): Complete these. } #[cfg(ldk_bench)] diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs index 58b467f341b..554be591e08 100644 --- a/lightning/src/ln/interactivetxs.rs +++ b/lightning/src/ln/interactivetxs.rs @@ -17,14 +17,14 @@ use bitcoin::constants::WITNESS_SCALE_FACTOR; use bitcoin::policy::MAX_STANDARD_TX_WEIGHT; use bitcoin::secp256k1::PublicKey; use bitcoin::transaction::Version; -use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight}; +use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Weight, Witness}; use crate::chain::chaininterface::fee_for_weight; use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT}; use crate::events::MessageSendEvent; use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS; use crate::ln::msgs; -use crate::ln::msgs::SerialId; +use crate::ln::msgs::{CommitmentSigned, SerialId, TxSignatures}; use crate::ln::types::ChannelId; use crate::sign::{EntropySource, P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT}; use crate::util::ser::TransactionU16LenLimited; @@ -166,6 +166,7 @@ pub(crate) struct ConstructedTransaction { remote_outputs_value_satoshis: u64, lock_time: AbsoluteLockTime, + holder_sends_tx_signatures_first: bool, } impl ConstructedTransaction { @@ -180,19 +181,39 @@ impl ConstructedTransaction { .iter() .fold(0u64, |value, (_, output)| value.saturating_add(output.local_value())); + let remote_inputs_value_satoshis = context.remote_inputs_value(); + let remote_outputs_value_satoshis = context.remote_outputs_value(); + let mut inputs: Vec = context.inputs.into_values().collect(); + let mut outputs: Vec = context.outputs.into_values().collect(); + // Inputs and outputs must be sorted by serial_id + inputs.sort_unstable_by_key(|input| input.serial_id()); + outputs.sort_unstable_by_key(|output| output.serial_id); + + // There is a strict ordering for `tx_signatures` exchange to prevent deadlocks. + let holder_sends_tx_signatures_first = + if local_inputs_value_satoshis == remote_inputs_value_satoshis { + // If the amounts are the same then the peer with the lowest pubkey lexicographically sends its + // tx_signatures first + context.holder_node_id.serialize() < context.counterparty_node_id.serialize() + } else { + // Otherwise the peer with the lowest contributed input value sends its tx_signatures first. + local_inputs_value_satoshis < remote_inputs_value_satoshis + }; + Self { holder_is_initiator: context.holder_is_initiator, local_inputs_value_satoshis, local_outputs_value_satoshis, - remote_inputs_value_satoshis: context.remote_inputs_value(), - remote_outputs_value_satoshis: context.remote_outputs_value(), + remote_inputs_value_satoshis, + remote_outputs_value_satoshis, - inputs: context.inputs.into_values().collect(), - outputs: context.outputs.into_values().collect(), + inputs, + outputs, lock_time: context.tx_locktime, + holder_sends_tx_signatures_first, } } @@ -210,11 +231,7 @@ impl ConstructedTransaction { } pub fn into_unsigned_tx(self) -> Transaction { - // Inputs and outputs must be sorted by serial_id - let ConstructedTransaction { mut inputs, mut outputs, .. } = self; - - inputs.sort_unstable_by_key(|input| input.serial_id()); - outputs.sort_unstable_by_key(|output| output.serial_id); + let ConstructedTransaction { inputs, outputs, .. } = self; let input: Vec = inputs.into_iter().map(|input| input.txin().clone()).collect(); let output: Vec = @@ -222,10 +239,154 @@ impl ConstructedTransaction { Transaction { version: Version::TWO, lock_time: self.lock_time, input, output } } + + pub fn outputs(&self) -> impl Iterator { + self.outputs.iter() + } + + pub fn inputs(&self) -> impl Iterator { + self.inputs.iter() + } + + pub fn txid(&self) -> Txid { + self.clone().into_unsigned_tx().compute_txid() + } + + pub fn add_local_witnesses(&mut self, witnesses: Vec) { + self.inputs + .iter_mut() + .filter(|input| { + !is_serial_id_valid_for_counterparty(self.holder_is_initiator, input.serial_id()) + }) + .map(|input| input.txin_mut()) + .zip(witnesses) + .for_each(|(input, witness)| input.witness = witness); + } + + pub fn add_remote_witnesses(&mut self, witnesses: Vec) { + self.inputs + .iter_mut() + .filter(|input| { + is_serial_id_valid_for_counterparty(self.holder_is_initiator, input.serial_id()) + }) + .map(|input| input.txin_mut()) + .zip(witnesses) + .for_each(|(input, witness)| input.witness = witness); + } +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct InteractiveTxSigningSession { + pub unsigned_tx: ConstructedTransaction, + holder_sends_tx_signatures_first: bool, + sent_commitment_signed: Option, + received_commitment_signed: Option, + holder_tx_signatures: Option, + counterparty_tx_signatures: Option, +} + +impl InteractiveTxSigningSession { + pub fn received_commitment_signed( + &mut self, commitment_signed: CommitmentSigned, + ) -> Option { + self.received_commitment_signed = Some(commitment_signed); + if self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn get_tx_signatures(&self) -> Option { + self.received_commitment_signed.as_ref().and_then(|_| self.holder_tx_signatures.clone()) + } + + pub fn received_tx_signatures( + &mut self, tx_signatures: TxSignatures, + ) -> (Option, Option) { + if self.counterparty_tx_signatures.is_some() { + return (None, None); + }; + self.unsigned_tx.add_remote_witnesses(tx_signatures.witnesses.clone()); + self.counterparty_tx_signatures = Some(tx_signatures); + + let holder_tx_signatures = if !self.holder_sends_tx_signatures_first { + self.holder_tx_signatures.clone() + } else { + None + }; + + let funding_tx = if self.holder_tx_signatures.is_some() { + Some(self.finalize_funding_tx()) + } else { + None + }; + + (holder_tx_signatures, funding_tx) + } + + pub fn provide_holder_witnesses( + &mut self, channel_id: ChannelId, witnesses: Vec, + ) -> Option { + self.unsigned_tx.add_local_witnesses(witnesses.clone()); + self.holder_tx_signatures = Some(TxSignatures { + channel_id, + tx_hash: self.unsigned_tx.txid(), + witnesses: witnesses.into_iter().collect(), + shared_input_signature: None, + }); + if self.received_commitment_signed.is_some() + && (self.holder_sends_tx_signatures_first || self.counterparty_tx_signatures.is_some()) + { + self.holder_tx_signatures.clone() + } else { + None + } + } + + pub fn remote_inputs_count(&self) -> usize { + self.unsigned_tx + .inputs + .iter() + .filter(|input| { + is_serial_id_valid_for_counterparty( + self.unsigned_tx.holder_is_initiator, + input.serial_id(), + ) + }) + .count() + } + + pub fn local_inputs_count(&self) -> usize { + self.unsigned_tx + .inputs + .iter() + .filter(|input| { + !is_serial_id_valid_for_counterparty( + self.unsigned_tx.holder_is_initiator, + input.serial_id(), + ) + }) + .count() + } + + fn finalize_funding_tx(&mut self) -> Transaction { + let lock_time = self.unsigned_tx.lock_time; + let ConstructedTransaction { inputs, outputs, .. } = &mut self.unsigned_tx; + + Transaction { + version: Version::TWO, + lock_time, + input: inputs.iter().cloned().map(|input| input.into_txin()).collect(), + output: outputs.iter().cloned().map(|output| output.into_tx_out()).collect(), + } + } } #[derive(Debug)] struct NegotiationContext { + holder_node_id: PublicKey, + counterparty_node_id: PublicKey, holder_is_initiator: bool, received_tx_add_input_count: u16, received_tx_add_output_count: u16, @@ -271,17 +432,20 @@ pub(crate) fn get_output_weight(script_pubkey: &ScriptBuf) -> Weight { ) } -fn is_serial_id_valid_for_counterparty(holder_is_initiator: bool, serial_id: &SerialId) -> bool { +fn is_serial_id_valid_for_counterparty(holder_is_initiator: bool, serial_id: SerialId) -> bool { // A received `SerialId`'s parity must match the role of the counterparty. holder_is_initiator == serial_id.is_for_non_initiator() } impl NegotiationContext { fn new( - holder_is_initiator: bool, expected_shared_funding_output: (ScriptBuf, u64), - tx_locktime: AbsoluteLockTime, feerate_sat_per_kw: u32, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, holder_is_initiator: bool, + expected_shared_funding_output: (ScriptBuf, u64), tx_locktime: AbsoluteLockTime, + feerate_sat_per_kw: u32, ) -> Self { NegotiationContext { + holder_node_id, + counterparty_node_id, holder_is_initiator, received_tx_add_input_count: 0, received_tx_add_output_count: 0, @@ -313,7 +477,7 @@ impl NegotiationContext { } fn is_serial_id_valid_for_counterparty(&self, serial_id: &SerialId) -> bool { - is_serial_id_valid_for_counterparty(self.holder_is_initiator, serial_id) + is_serial_id_valid_for_counterparty(self.holder_is_initiator, *serial_id) } fn remote_inputs_value(&self) -> u64 { @@ -346,6 +510,12 @@ impl NegotiationContext { ) } + fn local_inputs_value(&self) -> u64 { + self.inputs + .iter() + .fold(0u64, |acc, (_, input)| acc.saturating_add(input.prev_output().value.to_sat())) + } + fn received_tx_add_input(&mut self, msg: &msgs::TxAddInput) -> Result<(), AbortReason> { // The interactive-txs spec calls for us to fail negotiation if the `prevtx` we receive is // invalid. However, we would not need to account for this explicit negotiation failure @@ -742,7 +912,7 @@ define_state!( ReceivedTxComplete, "We have received a `tx_complete` message and the counterparty is awaiting ours." ); -define_state!(NegotiationComplete, ConstructedTransaction, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); +define_state!(NegotiationComplete, InteractiveTxSigningSession, "We have exchanged consecutive `tx_complete` messages with the counterparty and the transaction negotiation is complete."); define_state!( NegotiationAborted, AbortReason, @@ -785,7 +955,15 @@ macro_rules! define_state_transitions { fn transition(self, _data: &msgs::TxComplete) -> StateTransitionResult { let context = self.into_negotiation_context(); let tx = context.validate_tx()?; - Ok(NegotiationComplete(tx)) + let signing_session = InteractiveTxSigningSession { + holder_sends_tx_signatures_first: tx.holder_sends_tx_signatures_first, + unsigned_tx: tx, + sent_commitment_signed: None, + received_commitment_signed: None, + holder_tx_signatures: None, + counterparty_tx_signatures: None, + }; + Ok(NegotiationComplete(signing_session)) } } @@ -854,10 +1032,13 @@ macro_rules! define_state_machine_transitions { impl StateMachine { fn new( - feerate_sat_per_kw: u32, is_initiator: bool, tx_locktime: AbsoluteLockTime, + holder_node_id: PublicKey, counterparty_node_id: PublicKey, feerate_sat_per_kw: u32, + is_initiator: bool, tx_locktime: AbsoluteLockTime, expected_shared_funding_output: (ScriptBuf, u64), ) -> Self { let context = NegotiationContext::new( + holder_node_id, + counterparty_node_id, is_initiator, expected_shared_funding_output, tx_locktime, @@ -936,7 +1117,7 @@ pub struct LocalOrRemoteInput { } #[derive(Clone, Debug, Eq, PartialEq)] -enum InteractiveTxInput { +pub(crate) enum InteractiveTxInput { Local(LocalOrRemoteInput), Remote(LocalOrRemoteInput), // TODO(splicing) SharedInput should be added @@ -985,6 +1166,13 @@ impl OutputOwned { } } + fn into_tx_out(self) -> TxOut { + match self { + OutputOwned::Single(tx_out) | OutputOwned::SharedControlFullyOwned(tx_out) => tx_out, + OutputOwned::Shared(output) => output.tx_out, + } + } + fn value(&self) -> u64 { self.tx_out().value.to_sat() } @@ -1023,30 +1211,34 @@ impl OutputOwned { } #[derive(Clone, Debug, Eq, PartialEq)] -struct InteractiveTxOutput { +pub(crate) struct InteractiveTxOutput { serial_id: SerialId, added_by: AddingRole, output: OutputOwned, } impl InteractiveTxOutput { - fn tx_out(&self) -> &TxOut { + pub fn tx_out(&self) -> &TxOut { self.output.tx_out() } - fn value(&self) -> u64 { + pub fn into_tx_out(self) -> TxOut { + self.output.into_tx_out() + } + + pub fn value(&self) -> u64 { self.tx_out().value.to_sat() } - fn local_value(&self) -> u64 { + pub fn local_value(&self) -> u64 { self.output.local_value(self.added_by) } - fn remote_value(&self) -> u64 { + pub fn remote_value(&self) -> u64 { self.output.remote_value(self.added_by) } - fn script_pubkey(&self) -> &ScriptBuf { + pub fn script_pubkey(&self) -> &ScriptBuf { &self.output.tx_out().script_pubkey } } @@ -1066,6 +1258,20 @@ impl InteractiveTxInput { } } + pub fn txin_mut(&mut self) -> &mut TxIn { + match self { + InteractiveTxInput::Local(input) => &mut input.input, + InteractiveTxInput::Remote(input) => &mut input.input, + } + } + + pub fn into_txin(self) -> TxIn { + match self { + InteractiveTxInput::Local(input) => input.input, + InteractiveTxInput::Remote(input) => input.input, + } + } + pub fn prev_output(&self) -> &TxOut { match self { InteractiveTxInput::Local(input) => &input.prev_output, @@ -1168,8 +1374,8 @@ where pub(crate) enum HandleTxCompleteValue { SendTxMessage(InteractiveTxMessageSend), - SendTxComplete(InteractiveTxMessageSend, ConstructedTransaction), - NegotiationComplete(ConstructedTransaction), + SendTxComplete(InteractiveTxMessageSend, InteractiveTxSigningSession), + NegotiationComplete(InteractiveTxSigningSession), } pub(super) struct HandleTxCompleteResult(pub Result); @@ -1177,15 +1383,22 @@ pub(super) struct HandleTxCompleteResult(pub Result (Option, Option) { + ) -> (Option, Option) { match self.0 { Ok(tx_complete_res) => { - let (tx_msg_opt, tx_opt) = match tx_complete_res { + let (tx_msg_opt, signing_session_opt) = match tx_complete_res { HandleTxCompleteValue::SendTxMessage(msg) => (Some(msg), None), - HandleTxCompleteValue::SendTxComplete(msg, tx) => (Some(msg), Some(tx)), - HandleTxCompleteValue::NegotiationComplete(tx) => (None, Some(tx)), + HandleTxCompleteValue::SendTxComplete(msg, signing_session) => { + (Some(msg), Some(signing_session)) + }, + HandleTxCompleteValue::NegotiationComplete(signing_session) => { + (None, Some(signing_session)) + }, }; - (tx_msg_opt.map(|tx_msg| tx_msg.into_msg_send_event(counterparty_node_id)), tx_opt) + ( + tx_msg_opt.map(|tx_msg| tx_msg.into_msg_send_event(counterparty_node_id)), + signing_session_opt, + ) }, Err(tx_abort_msg) => ( Some(MessageSendEvent::SendTxAbort { @@ -1203,6 +1416,8 @@ where ES::Target: EntropySource, { pub entropy_source: &'a ES, + pub holder_node_id: PublicKey, + pub counterparty_node_id: PublicKey, pub channel_id: ChannelId, pub feerate_sat_per_kw: u32, pub is_initiator: bool, @@ -1231,6 +1446,8 @@ impl InteractiveTxConstructor { { let InteractiveTxConstructorArgs { entropy_source, + holder_node_id, + counterparty_node_id, channel_id, feerate_sat_per_kw, is_initiator, @@ -1271,6 +1488,8 @@ impl InteractiveTxConstructor { } if let Some(expected_shared_funding_output) = expected_shared_funding_output { let state_machine = StateMachine::new( + holder_node_id, + counterparty_node_id, feerate_sat_per_kw, is_initiator, funding_tx_locktime, @@ -1429,7 +1648,7 @@ mod tests { use bitcoin::key::UntweakedPublicKey; use bitcoin::opcodes; use bitcoin::script::Builder; - use bitcoin::secp256k1::{Keypair, Secp256k1}; + use bitcoin::secp256k1::{Keypair, PublicKey, Secp256k1, SecretKey}; use bitcoin::transaction::Version; use bitcoin::{ OutPoint, PubkeyHash, ScriptBuf, Sequence, Transaction, TxIn, TxOut, WPubkeyHash, @@ -1517,6 +1736,14 @@ mod tests { { let channel_id = ChannelId(entropy_source.get_secure_random_bytes()); let funding_tx_locktime = AbsoluteLockTime::from_height(1337).unwrap(); + let holder_node_id = PublicKey::from_secret_key( + &Secp256k1::signing_only(), + &SecretKey::from_slice(&[42; 32]).unwrap(), + ); + let counterparty_node_id = PublicKey::from_secret_key( + &Secp256k1::signing_only(), + &SecretKey::from_slice(&[43; 32]).unwrap(), + ); // funding output sanity check let shared_outputs_by_a: Vec<_> = @@ -1573,6 +1800,8 @@ mod tests { entropy_source, channel_id, feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW, + holder_node_id, + counterparty_node_id, is_initiator: true, funding_tx_locktime, inputs_to_contribute: session.inputs_a, @@ -1592,6 +1821,8 @@ mod tests { }; let mut constructor_b = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs { entropy_source, + holder_node_id, + counterparty_node_id, channel_id, feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW, is_initiator: false, @@ -1642,9 +1873,10 @@ mod tests { while final_tx_a.is_none() || final_tx_b.is_none() { if let Some(message_send_a) = message_send_a.take() { match handle_message_send(message_send_a, &mut constructor_b) { - Ok((msg_send, final_tx)) => { + Ok((msg_send, interactive_signing_session)) => { message_send_b = msg_send; - final_tx_b = final_tx; + final_tx_b = + interactive_signing_session.map(|session| session.unsigned_tx.txid()); }, Err(abort_reason) => { let error_culprit = match abort_reason { @@ -1666,9 +1898,10 @@ mod tests { } if let Some(message_send_b) = message_send_b.take() { match handle_message_send(message_send_b, &mut constructor_a) { - Ok((msg_send, final_tx)) => { + Ok((msg_send, interactive_signing_session)) => { message_send_a = msg_send; - final_tx_a = final_tx; + final_tx_a = + interactive_signing_session.map(|session| session.unsigned_tx.txid()); }, Err(abort_reason) => { let error_culprit = match abort_reason { @@ -1691,7 +1924,7 @@ mod tests { } assert!(message_send_a.is_none()); assert!(message_send_b.is_none()); - assert_eq!(final_tx_a.unwrap().into_unsigned_tx(), final_tx_b.unwrap().into_unsigned_tx()); + assert_eq!(final_tx_a.unwrap(), final_tx_b.unwrap()); assert!( session.expect_error.is_none(), "Missing expected error {:?}, Test: {}",