diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index b87e73d98bd..b46a2df13ee 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -969,7 +969,6 @@ impl ChannelMonitor { let key_derivation_params = keys.key_derivation_params(); let holder_revocation_basepoint = keys.pubkeys().revocation_basepoint; - let mut onchain_tx_handler = OnchainTxHandler::new(destination_script.clone(), keys, channel_parameters.clone()); let secp_ctx = Secp256k1::new(); @@ -991,7 +990,9 @@ impl ChannelMonitor { }; (holder_commitment_tx, trusted_tx.commitment_number()) }; - onchain_tx_handler.provide_latest_holder_tx(initial_holder_commitment_tx); + + let onchain_tx_handler = + OnchainTxHandler::new(destination_script.clone(), keys, channel_parameters.clone(), initial_holder_commitment_tx); let mut outputs_to_watch = HashMap::new(); outputs_to_watch.insert(funding_info.0.txid, vec![(funding_info.0.index as u32, funding_info.1.clone())]); @@ -1725,28 +1726,26 @@ impl ChannelMonitor { pub fn get_latest_holder_commitment_txn(&mut self, logger: &L) -> Vec where L::Target: Logger { log_trace!(logger, "Getting signed latest holder commitment transaction!"); self.holder_tx_signed = true; - if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript) { - let txid = commitment_tx.txid(); - let mut res = vec![commitment_tx]; - for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() { - if let Some(vout) = htlc.0.transaction_output_index { - let preimage = if !htlc.0.offered { - if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else { - // We can't build an HTLC-Success transaction without the preimage - continue; - } - } else { None }; - if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx( - &::bitcoin::OutPoint { txid, vout }, &preimage) { - res.push(htlc_tx); + let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript); + let txid = commitment_tx.txid(); + let mut res = vec![commitment_tx]; + for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() { + if let Some(vout) = htlc.0.transaction_output_index { + let preimage = if !htlc.0.offered { + if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else { + // We can't build an HTLC-Success transaction without the preimage + continue; } + } else { None }; + if let Some(htlc_tx) = self.onchain_tx_handler.get_fully_signed_htlc_tx( + &::bitcoin::OutPoint { txid, vout }, &preimage) { + res.push(htlc_tx); } } - // We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do. - // The data will be re-generated and tracked in check_spend_holder_transaction if we get a confirmation. - return res } - Vec::new() + // We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do. + // The data will be re-generated and tracked in check_spend_holder_transaction if we get a confirmation. + return res; } /// Unsafe test-only version of get_latest_holder_commitment_txn used by our test framework @@ -1755,26 +1754,24 @@ impl ChannelMonitor { #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] pub fn unsafe_get_latest_holder_commitment_txn(&mut self, logger: &L) -> Vec where L::Target: Logger { log_trace!(logger, "Getting signed copy of latest holder commitment transaction!"); - if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_copy_holder_tx(&self.funding_redeemscript) { - let txid = commitment_tx.txid(); - let mut res = vec![commitment_tx]; - for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() { - if let Some(vout) = htlc.0.transaction_output_index { - let preimage = if !htlc.0.offered { - if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else { - // We can't build an HTLC-Success transaction without the preimage - continue; - } - } else { None }; - if let Some(htlc_tx) = self.onchain_tx_handler.unsafe_get_fully_signed_htlc_tx( - &::bitcoin::OutPoint { txid, vout }, &preimage) { - res.push(htlc_tx); + let commitment_tx = self.onchain_tx_handler.get_fully_signed_copy_holder_tx(&self.funding_redeemscript); + let txid = commitment_tx.txid(); + let mut res = vec![commitment_tx]; + for htlc in self.current_holder_commitment_tx.htlc_outputs.iter() { + if let Some(vout) = htlc.0.transaction_output_index { + let preimage = if !htlc.0.offered { + if let Some(preimage) = self.payment_preimages.get(&htlc.0.payment_hash) { Some(preimage.clone()) } else { + // We can't build an HTLC-Success transaction without the preimage + continue; } + } else { None }; + if let Some(htlc_tx) = self.onchain_tx_handler.unsafe_get_fully_signed_htlc_tx( + &::bitcoin::OutPoint { txid, vout }, &preimage) { + res.push(htlc_tx); } } - return res } - Vec::new() + return res } /// Processes transactions in a newly connected block, which may result in any of the following: @@ -1853,15 +1850,14 @@ impl ChannelMonitor { } if should_broadcast { self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0)); - if let Some(commitment_tx) = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript) { - self.holder_tx_signed = true; - let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx); - let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx); - if !new_outputs.is_empty() { - watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs)); - } - claimable_outpoints.append(&mut new_outpoints); - } + let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript); + self.holder_tx_signed = true; + let (mut new_outpoints, _) = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx); + let new_outputs = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, &commitment_tx); + if !new_outputs.is_empty() { + watch_outputs.push((self.current_holder_commitment_tx.txid.clone(), new_outputs)); + } + claimable_outpoints.append(&mut new_outpoints); } if let Some(events) = self.onchain_events_waiting_threshold_conf.remove(&height) { for ev in events { diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 4a3a937a5c4..80f734edbde 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -233,13 +233,21 @@ pub trait ChannelKeys : Send+Clone + Writeable { // TODO: Document the things someone using this interface should enforce before signing. fn sign_counterparty_commitment(&self, commitment_tx: &CommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()>; - /// Create a signature for a holder's commitment transaction. This will only ever be called with - /// the same commitment_tx (or a copy thereof), though there are currently no guarantees - /// that it will not be called multiple times. + /// Create a signatures for a holder's commitment transaction and its claiming HTLC transactions. + /// This will only ever be called with a non-revoked commitment_tx. This will be called with the + /// latest commitment_tx when we initiate a force-close. + /// This will be called with the previous latest, just to get claiming HTLC signatures, if we are + /// reacting to a ChannelMonitor replica that decided to broadcast before it had been updated to + /// the latest. + /// This may be called multiple times for the same transaction. + /// /// An external signer implementation should check that the commitment has not been revoked. + /// + /// May return Err if key derivation fails. Callers, such as ChannelMonitor, will panic in such a case. // // TODO: Document the things someone using this interface should enforce before signing. - fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result; + // TODO: Key derivation failure should panic rather than Err + fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()>; /// Same as sign_holder_commitment, but exists only for tests to get access to holder commitment /// transactions which will be broadcasted later, after the channel has moved on to a newer @@ -248,18 +256,6 @@ pub trait ChannelKeys : Send+Clone + Writeable { #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] fn unsafe_sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result; - /// Create a signature for each HTLC transaction spending a holder's commitment transaction. - /// - /// Unlike sign_holder_commitment, this may be called multiple times with *different* - /// commitment_tx values. While this will never be called with a revoked - /// commitment_tx, it is possible that it is called with the second-latest - /// commitment_tx (only if we haven't yet revoked it) if some watchtower/secondary - /// ChannelMonitor decided to broadcast before it had been updated to the latest. - /// - /// Either an Err should be returned, or a Vec with one entry for each HTLC which exists in - /// commitment_tx. - fn sign_holder_commitment_htlc_transactions(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result, ()>; - /// Create a signature for the given input in a transaction spending an HTLC or commitment /// transaction output when our counterparty broadcasts an old state. /// @@ -500,11 +496,14 @@ impl ChannelKeys for InMemoryChannelKeys { Ok((commitment_sig, htlc_sigs)) } - fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result { + fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()> { let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); let funding_redeemscript = make_funding_redeemscript(&funding_pubkey, &self.counterparty_pubkeys().funding_pubkey); let sig = commitment_tx.trust().built_transaction().sign(&self.funding_key, &funding_redeemscript, self.channel_value_satoshis, secp_ctx); - Ok(sig) + let channel_parameters = self.get_channel_parameters(); + let trusted_tx = commitment_tx.trust(); + let htlc_sigs = trusted_tx.get_htlc_sigs(&self.htlc_base_key, &channel_parameters.as_holder_broadcastable(), secp_ctx)?; + Ok((sig, htlc_sigs)) } #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] @@ -514,12 +513,6 @@ impl ChannelKeys for InMemoryChannelKeys { Ok(commitment_tx.trust().built_transaction().sign(&self.funding_key, &channel_funding_redeemscript, self.channel_value_satoshis, secp_ctx)) } - fn sign_holder_commitment_htlc_transactions(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result, ()> { - let channel_parameters = self.get_channel_parameters(); - let trusted_tx = commitment_tx.trust(); - trusted_tx.get_htlc_sigs(&self.htlc_base_key, &channel_parameters.as_holder_broadcastable(), secp_ctx) - } - fn sign_justice_transaction(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &Option, secp_ctx: &Secp256k1) -> Result { let revocation_key = match chan_utils::derive_private_revocation_key(&secp_ctx, &per_commitment_key, &self.revocation_base_key) { Ok(revocation_key) => revocation_key, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 4ba8b28bfd5..08faea160bb 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -4742,15 +4742,13 @@ mod tests { &chan.holder_keys.pubkeys().funding_pubkey, chan.counterparty_funding_pubkey() ); - let holder_sig = chan_keys.sign_holder_commitment(&holder_commitment_tx, &secp_ctx).unwrap(); + let (holder_sig, htlc_sigs) = chan_keys.sign_holder_commitment_and_htlcs(&holder_commitment_tx, &secp_ctx).unwrap(); assert_eq!(Signature::from_der(&hex::decode($sig_hex).unwrap()[..]).unwrap(), holder_sig, "holder_sig"); let funding_redeemscript = chan.get_funding_redeemscript(); let tx = holder_commitment_tx.add_holder_sig(&funding_redeemscript, holder_sig); assert_eq!(serialize(&tx)[..], hex::decode($tx_hex).unwrap()[..], "tx"); - let htlc_sigs = chan_keys.sign_holder_commitment_htlc_transactions(&holder_commitment_tx, &secp_ctx).unwrap(); - // ((htlc, counterparty_sig), (index, holder_sig)) let mut htlc_sig_iter = holder_commitment_tx.htlcs().iter().zip(&holder_commitment_tx.counterparty_htlc_sigs).zip(htlc_sigs.iter().enumerate()); diff --git a/lightning/src/ln/onchaintx.rs b/lightning/src/ln/onchaintx.rs index 1b0060dabc7..02776fdefe5 100644 --- a/lightning/src/ln/onchaintx.rs +++ b/lightning/src/ln/onchaintx.rs @@ -35,6 +35,7 @@ use util::byte_utils; use std::collections::{HashMap, hash_map}; use std::cmp; use std::ops::Deref; +use std::mem::replace; const MAX_ALLOC_SIZE: usize = 64*1024; @@ -241,7 +242,7 @@ impl Writeable for Option>> { /// do RBF bumping if possible. pub struct OnchainTxHandler { destination_script: Script, - holder_commitment: Option, + holder_commitment: HolderCommitmentTransaction, // holder_htlc_sigs and prev_holder_htlc_sigs are in the order as they appear in the commitment // transaction outputs (hence the Option<>s inside the Vec). The first usize is the index in // the set of HTLCs in the HolderCommitmentTransaction. @@ -423,13 +424,13 @@ impl<'a, K: KeysInterface> ReadableArgs<&'a K> for OnchainTxHandler OnchainTxHandler { - pub(crate) fn new(destination_script: Script, keys: ChanSigner, channel_parameters: ChannelTransactionParameters) -> Self { + pub(crate) fn new(destination_script: Script, keys: ChanSigner, channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction) -> Self { let key_storage = keys; OnchainTxHandler { destination_script, - holder_commitment: None, + holder_commitment, holder_htlc_sigs: None, prev_holder_commitment: None, prev_holder_htlc_sigs: None, @@ -490,6 +491,8 @@ impl OnchainTxHandler { /// Lightning security model (i.e being able to redeem/timeout HTLC or penalize coutnerparty onchain) lays on the assumption of claim transactions getting confirmed before timelock expiration /// (CSV or CLTV following cases). In case of high-fee spikes, claim tx may stuck in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or Child-Pay-For-Parent. + /// Panics if there are signing errors, because signing operations in reaction to on-chain events + /// are not expected to fail, and if they do, we may lose funds. fn generate_claim_tx(&mut self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: &F, logger: &L) -> Option<(Option, u32, Transaction)> where F::Target: FeeEstimator, L::Target: Logger, @@ -610,17 +613,15 @@ impl OnchainTxHandler { chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, *on_counterparty_tx_csv, &chan_keys.broadcaster_delayed_payment_key) }; - if let Ok(sig) = self.key_storage.sign_justice_transaction(&bumped_tx, i, *amount, &per_commitment_key, htlc, &self.secp_ctx) { - bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); - bumped_tx.input[i].witness[0].push(SigHashType::All as u8); - if htlc.is_some() { - bumped_tx.input[i].witness.push(chan_keys.revocation_key.clone().serialize().to_vec()); - } else { - bumped_tx.input[i].witness.push(vec!(1)); - } - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); - } else { return None; } - //TODO: panic ? + let sig = self.key_storage.sign_justice_transaction(&bumped_tx, i, *amount, &per_commitment_key, htlc, &self.secp_ctx).expect("sign justice tx"); + bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[i].witness[0].push(SigHashType::All as u8); + if htlc.is_some() { + bumped_tx.input[i].witness.push(chan_keys.revocation_key.clone().serialize().to_vec()); + } else { + bumped_tx.input[i].witness.push(vec!(1)); + } + bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); log_trace!(logger, "Going to broadcast Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}...", bumped_tx.txid(), if *input_descriptor == InputDescriptors::RevokedOutput { "to_holder" } else if *input_descriptor == InputDescriptors::RevokedOfferedHTLC { "offered" } else if *input_descriptor == InputDescriptors::RevokedReceivedHTLC { "received" } else { "" }, outp.vout, outp.txid, new_feerate); } @@ -630,17 +631,16 @@ impl OnchainTxHandler { let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&htlc, &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); if !preimage.is_some() { bumped_tx.lock_time = htlc.cltv_expiry }; // Right now we don't aggregate time-locked transaction, if we do we should set lock_time before to avoid breaking hash computation - if let Ok(sig) = self.key_storage.sign_counterparty_htlc_transaction(&bumped_tx, i, &htlc.amount_msat / 1000, &per_commitment_point, htlc, &self.secp_ctx) { - bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); - bumped_tx.input[i].witness[0].push(SigHashType::All as u8); - if let &Some(preimage) = preimage { - bumped_tx.input[i].witness.push(preimage.0.to_vec()); - } else { - // Due to BIP146 (MINIMALIF) this must be a zero-length element to relay. - bumped_tx.input[i].witness.push(vec![]); - } - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + let sig = self.key_storage.sign_counterparty_htlc_transaction(&bumped_tx, i, &htlc.amount_msat / 1000, &per_commitment_point, htlc, &self.secp_ctx).expect("sign counterparty HTLC tx"); + bumped_tx.input[i].witness.push(sig.serialize_der().to_vec()); + bumped_tx.input[i].witness[0].push(SigHashType::All as u8); + if let &Some(preimage) = preimage { + bumped_tx.input[i].witness.push(preimage.0.to_vec()); + } else { + // Due to BIP146 (MINIMALIF) this must be a zero-length element to relay. + bumped_tx.input[i].witness.push(vec![]); } + bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); log_trace!(logger, "Going to broadcast Claim Transaction {} claiming counterparty {} htlc output {} from {} with new feerate {}...", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate); } }, @@ -664,10 +664,10 @@ impl OnchainTxHandler { return None; }, &InputMaterial::Funding { ref funding_redeemscript } => { - let signed_tx = self.get_fully_signed_holder_tx(funding_redeemscript).unwrap(); + let signed_tx = self.get_fully_signed_holder_tx(funding_redeemscript); // Timer set to $NEVER given we can't bump tx without anchor outputs log_trace!(logger, "Going to broadcast Holder Transaction {} claiming funding output {} from {}...", signed_tx.txid(), outp.vout, outp.txid); - return Some((None, self.holder_commitment.as_ref().unwrap().feerate_per_kw(), signed_tx)); + return Some((None, self.holder_commitment.feerate_per_kw(), signed_tx)); } _ => unreachable!() } @@ -905,21 +905,27 @@ impl OnchainTxHandler { } pub(crate) fn provide_latest_holder_tx(&mut self, tx: HolderCommitmentTransaction) { - self.prev_holder_commitment = self.holder_commitment.take(); - self.holder_commitment = Some(tx); + self.prev_holder_commitment = Some(replace(&mut self.holder_commitment, tx)); + self.holder_htlc_sigs = None; } + // Normally holder HTLCs are signed at the same time as the holder commitment tx. However, + // in some configurations, the holder commitment tx has been signed and broadcast by a + // ChannelMonitor replica, so we handle that case here. fn sign_latest_holder_htlcs(&mut self) { - if let Some(ref holder_commitment) = self.holder_commitment { - if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) { - self.holder_htlc_sigs = Some(Self::extract_holder_sigs(holder_commitment, sigs)); - } + if self.holder_htlc_sigs.is_none() { + let (_sig, sigs) = self.key_storage.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment"); + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, sigs)); } } + // Normally only the latest commitment tx and HTLCs need to be signed. However, in some + // configurations we may have updated our holder commitment but a replica of the ChannelMonitor + // broadcast the previous one before we sync with it. We handle that case here. fn sign_prev_holder_htlcs(&mut self) { - if let Some(ref holder_commitment) = self.prev_holder_commitment { - if let Ok(sigs) = self.key_storage.sign_holder_commitment_htlc_transactions(holder_commitment, &self.secp_ctx) { + if self.prev_holder_htlc_sigs.is_none() { + if let Some(ref holder_commitment) = self.prev_holder_commitment { + let (_sig, sigs) = self.key_storage.sign_holder_commitment_and_htlcs(holder_commitment, &self.secp_ctx).expect("sign previous holder commitment"); self.prev_holder_htlc_sigs = Some(Self::extract_holder_sigs(holder_commitment, sigs)); } } @@ -939,50 +945,35 @@ impl OnchainTxHandler { // have empty holder commitment transaction if a ChannelMonitor is asked to force-close just after Channel::get_outbound_funding_created, // before providing a initial commitment transaction. For outbound channel, init ChannelMonitor at Channel::funding_signed, there is nothing // to monitor before. - pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Option { - if let Some(ref mut holder_commitment) = self.holder_commitment { - match self.key_storage.sign_holder_commitment(&holder_commitment, &self.secp_ctx) { - Ok(sig) => { - Some(holder_commitment.add_holder_sig(funding_redeemscript, sig)) - }, - Err(_) => return None, - } - } else { - None - } + pub(crate) fn get_fully_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction { + let (sig, htlc_sigs) = self.key_storage.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("signing holder commitment"); + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, htlc_sigs)); + self.holder_commitment.add_holder_sig(funding_redeemscript, sig) } #[cfg(any(test, feature="unsafe_revoked_tx_signing"))] - pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Option { - if let Some(ref mut holder_commitment) = self.holder_commitment { - match self.key_storage.sign_holder_commitment(holder_commitment, &self.secp_ctx) { - Ok(sig) => { - Some(holder_commitment.add_holder_sig(funding_redeemscript, sig)) - }, - Err(_) => return None, - } - } else { - None - } + pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction { + let (sig, htlc_sigs) = self.key_storage.sign_holder_commitment_and_htlcs(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment"); + self.holder_htlc_sigs = Some(Self::extract_holder_sigs(&self.holder_commitment, htlc_sigs)); + self.holder_commitment.add_holder_sig(funding_redeemscript, sig) } pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { let mut htlc_tx = None; - if self.holder_commitment.is_some() { - let commitment_txid = self.holder_commitment.as_ref().unwrap().trust().txid(); - if commitment_txid == outp.txid { - self.sign_latest_holder_htlcs(); - if let &Some(ref htlc_sigs) = &self.holder_htlc_sigs { - let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap(); - let holder_commitment = self.holder_commitment.as_ref().unwrap(); - let trusted_tx = holder_commitment.trust(); - let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[*htlc_idx]; - htlc_tx = Some(trusted_tx - .get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage)); - } + let commitment_txid = self.holder_commitment.trust().txid(); + // Check if the HTLC spends from the current holder commitment + if commitment_txid == outp.txid { + self.sign_latest_holder_htlcs(); + if let &Some(ref htlc_sigs) = &self.holder_htlc_sigs { + let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap(); + let trusted_tx = self.holder_commitment.trust(); + let counterparty_htlc_sig = self.holder_commitment.counterparty_htlc_sigs[*htlc_idx]; + htlc_tx = Some(trusted_tx + .get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage)); } } - if self.prev_holder_commitment.is_some() { + // If the HTLC doesn't spend the current holder commitment, check if it spends the previous one + if htlc_tx.is_none() && self.prev_holder_commitment.is_some() { let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().trust().txid(); if commitment_txid == outp.txid { self.sign_prev_holder_htlcs(); diff --git a/lightning/src/util/enforcing_trait_impls.rs b/lightning/src/util/enforcing_trait_impls.rs index 92cf178c8c5..8229f604860 100644 --- a/lightning/src/util/enforcing_trait_impls.rs +++ b/lightning/src/util/enforcing_trait_impls.rs @@ -72,20 +72,7 @@ impl ChannelKeys for EnforcingChannelKeys { Ok(self.inner.sign_counterparty_commitment(commitment_tx, secp_ctx).unwrap()) } - fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result { - self.verify_holder_commitment_tx(commitment_tx, secp_ctx); - - // TODO: enforce the ChannelKeys contract - error if this commitment was already revoked - // TODO: need the commitment number - Ok(self.inner.sign_holder_commitment(commitment_tx, secp_ctx).unwrap()) - } - - #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] - fn unsafe_sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result { - Ok(self.inner.unsafe_sign_holder_commitment(commitment_tx, secp_ctx).unwrap()) - } - - fn sign_holder_commitment_htlc_transactions(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result, ()> { + fn sign_holder_commitment_and_htlcs(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result<(Signature, Vec), ()> { let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx); let commitment_txid = trusted_tx.txid(); let holder_csv = self.inner.counterparty_selected_contest_delay(); @@ -101,7 +88,14 @@ impl ChannelKeys for EnforcingChannelKeys { secp_ctx.verify(&sighash, sig, &keys.countersignatory_htlc_key).unwrap(); } - Ok(self.inner.sign_holder_commitment_htlc_transactions(commitment_tx, secp_ctx).unwrap()) + // TODO: enforce the ChannelKeys contract - error if this commitment was already revoked + // TODO: need the commitment number + Ok(self.inner.sign_holder_commitment_and_htlcs(commitment_tx, secp_ctx).unwrap()) + } + + #[cfg(any(test,feature = "unsafe_revoked_tx_signing"))] + fn unsafe_sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result { + Ok(self.inner.unsafe_sign_holder_commitment(commitment_tx, secp_ctx).unwrap()) } fn sign_justice_transaction(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &Option, secp_ctx: &Secp256k1) -> Result {