diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 2be7f4cf1f5..5c1e102aefc 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -53,7 +53,7 @@ use crate::util::ser::{Readable, ReadableArgs, MaybeReadable, Writer, Writeable, use crate::util::byte_utils; use crate::util::events::Event; #[cfg(anchors)] -use crate::util::events::{AnchorDescriptor, BumpTransactionEvent}; +use crate::util::events::{AnchorDescriptor, HTLCDescriptor, BumpTransactionEvent}; use crate::prelude::*; use core::{cmp, mem}; @@ -647,6 +647,7 @@ struct IrrevocablyResolvedHTLC { /// was not present in the confirmed commitment transaction), HTLC-Success, or HTLC-Timeout /// transaction. resolving_txid: Option, // Added as optional, but always filled in, in 0.0.110 + resolving_tx: Option, /// Only set if the HTLC claim was ours using a payment preimage payment_preimage: Option, } @@ -662,6 +663,7 @@ impl Writeable for IrrevocablyResolvedHTLC { (0, mapped_commitment_tx_output_idx, required), (1, self.resolving_txid, option), (2, self.payment_preimage, option), + (3, self.resolving_tx, option), }); Ok(()) } @@ -672,15 +674,18 @@ impl Readable for IrrevocablyResolvedHTLC { let mut mapped_commitment_tx_output_idx = 0; let mut resolving_txid = None; let mut payment_preimage = None; + let mut resolving_tx = None; read_tlv_fields!(reader, { (0, mapped_commitment_tx_output_idx, required), (1, resolving_txid, option), (2, payment_preimage, option), + (3, resolving_tx, option), }); Ok(Self { commitment_tx_output_idx: if mapped_commitment_tx_output_idx == u32::max_value() { None } else { Some(mapped_commitment_tx_output_idx) }, resolving_txid, payment_preimage, + resolving_tx, }) } } @@ -1526,6 +1531,7 @@ impl ChannelMonitorImpl { if let Some(v) = htlc.transaction_output_index { v } else { return None; }; let mut htlc_spend_txid_opt = None; + let mut htlc_spend_tx_opt = None; let mut holder_timeout_spend_pending = None; let mut htlc_spend_pending = None; let mut holder_delayed_output_pending = None; @@ -1534,7 +1540,9 @@ impl ChannelMonitorImpl { OnchainEvent::HTLCUpdate { commitment_tx_output_idx, htlc_value_satoshis, .. } if commitment_tx_output_idx == Some(htlc_commitment_tx_output_idx) => { debug_assert!(htlc_spend_txid_opt.is_none()); - htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid()); + htlc_spend_txid_opt = Some(&event.txid); + debug_assert!(htlc_spend_tx_opt.is_none()); + htlc_spend_tx_opt = event.transaction.as_ref(); debug_assert!(holder_timeout_spend_pending.is_none()); debug_assert_eq!(htlc_value_satoshis.unwrap(), htlc.amount_msat / 1000); holder_timeout_spend_pending = Some(event.confirmation_threshold()); @@ -1542,7 +1550,9 @@ impl ChannelMonitorImpl { OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. } if commitment_tx_output_idx == htlc_commitment_tx_output_idx => { debug_assert!(htlc_spend_txid_opt.is_none()); - htlc_spend_txid_opt = event.transaction.as_ref().map(|tx| tx.txid()); + htlc_spend_txid_opt = Some(&event.txid); + debug_assert!(htlc_spend_tx_opt.is_none()); + htlc_spend_tx_opt = event.transaction.as_ref(); debug_assert!(htlc_spend_pending.is_none()); htlc_spend_pending = Some((event.confirmation_threshold(), preimage.is_some())); }, @@ -1558,19 +1568,32 @@ impl ChannelMonitorImpl { let htlc_resolved = self.htlcs_resolved_on_chain.iter() .find(|v| if v.commitment_tx_output_idx == Some(htlc_commitment_tx_output_idx) { debug_assert!(htlc_spend_txid_opt.is_none()); - htlc_spend_txid_opt = v.resolving_txid; + htlc_spend_txid_opt = v.resolving_txid.as_ref(); + debug_assert!(htlc_spend_tx_opt.is_none()); + htlc_spend_tx_opt = v.resolving_tx.as_ref(); true } else { false }); debug_assert!(holder_timeout_spend_pending.is_some() as u8 + htlc_spend_pending.is_some() as u8 + htlc_resolved.is_some() as u8 <= 1); + let htlc_commitment_outpoint = BitcoinOutPoint::new(confirmed_txid.unwrap(), htlc_commitment_tx_output_idx); let htlc_output_to_spend = if let Some(txid) = htlc_spend_txid_opt { - debug_assert!( - self.onchain_tx_handler.channel_transaction_parameters.opt_anchors.is_none(), - "This code needs updating for anchors"); - BitcoinOutPoint::new(txid, 0) + // Because HTLC transactions either only have 1 input and 1 output (pre-anchors) or + // are signed with SIGHASH_SINGLE|ANYONECANPAY under BIP-0143 (post-anchors), we can + // locate the correct output by ensuring its adjacent input spends the HTLC output + // in the commitment. + if let Some(ref tx) = htlc_spend_tx_opt { + let htlc_input_idx_opt = tx.input.iter().enumerate() + .find(|(_, input)| input.previous_output == htlc_commitment_outpoint) + .map(|(idx, _)| idx as u32); + debug_assert!(htlc_input_idx_opt.is_some()); + BitcoinOutPoint::new(*txid, htlc_input_idx_opt.unwrap_or(0)) + } else { + debug_assert!(!self.onchain_tx_handler.opt_anchors()); + BitcoinOutPoint::new(*txid, 0) + } } else { - BitcoinOutPoint::new(confirmed_txid.unwrap(), htlc_commitment_tx_output_idx) + htlc_commitment_outpoint }; let htlc_output_spend_pending = self.onchain_tx_handler.is_output_spend_pending(&htlc_output_to_spend); @@ -1594,8 +1617,7 @@ impl ChannelMonitorImpl { } = &event.event { if event.transaction.as_ref().map(|tx| tx.input.iter().any(|inp| { if let Some(htlc_spend_txid) = htlc_spend_txid_opt { - Some(tx.txid()) == htlc_spend_txid_opt || - inp.previous_output.txid == htlc_spend_txid + tx.txid() == *htlc_spend_txid || inp.previous_output.txid == *htlc_spend_txid } else { Some(inp.previous_output.txid) == confirmed_txid && inp.previous_output.vout == htlc_commitment_tx_output_idx @@ -2403,6 +2425,27 @@ impl ChannelMonitorImpl { pending_htlcs, })); }, + ClaimEvent::BumpHTLC { + target_feerate_sat_per_1000_weight, htlcs, + } => { + let mut htlc_descriptors = Vec::with_capacity(htlcs.len()); + for htlc in htlcs { + htlc_descriptors.push(HTLCDescriptor { + channel_keys_id: self.channel_keys_id, + channel_value_satoshis: self.channel_value_satoshis, + channel_parameters: self.onchain_tx_handler.channel_transaction_parameters.clone(), + commitment_txid: htlc.commitment_txid, + per_commitment_number: htlc.per_commitment_number, + htlc: htlc.htlc, + preimage: htlc.preimage, + counterparty_sig: htlc.counterparty_sig, + }); + } + ret.push(Event::BumpTransaction(BumpTransactionEvent::HTLCResolution { + target_feerate_sat_per_1000_weight, + htlc_descriptors, + })); + } } } ret @@ -2623,31 +2666,49 @@ impl ChannelMonitorImpl { } /// Attempts to claim a counterparty HTLC-Success/HTLC-Timeout's outputs using the revocation key - fn check_spend_counterparty_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32, logger: &L) -> (Vec, Option) where L::Target: Logger { - let htlc_txid = tx.txid(); - if tx.input.len() != 1 || tx.output.len() != 1 || tx.input[0].witness.len() != 5 { - return (Vec::new(), None) - } - - macro_rules! ignore_error { - ( $thing : expr ) => { - match $thing { - Ok(a) => a, - Err(_) => return (Vec::new(), None) - } - }; - } - + fn check_spend_counterparty_htlc( + &mut self, tx: &Transaction, commitment_number: u64, commitment_txid: &Txid, height: u32, logger: &L + ) -> (Vec, Option) where L::Target: Logger { let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (Vec::new(), None); }; - let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret)); + let per_commitment_key = match SecretKey::from_slice(&secret) { + Ok(key) => key, + Err(_) => return (Vec::new(), None) + }; let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key); - log_error!(logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}", htlc_txid, 0); - let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, tx.output[0].value, self.counterparty_commitment_params.on_counterparty_tx_csv); - let justice_package = PackageTemplate::build_package(htlc_txid, 0, PackageSolvingData::RevokedOutput(revk_outp), height + self.counterparty_commitment_params.on_counterparty_tx_csv as u32, true, height); - let claimable_outpoints = vec!(justice_package); - let outputs = vec![(0, tx.output[0].clone())]; - (claimable_outpoints, Some((htlc_txid, outputs))) + let htlc_txid = tx.txid(); + let mut claimable_outpoints = vec![]; + let mut outputs_to_watch = None; + // Previously, we would only claim HTLCs from revoked HTLC transactions if they had 1 input + // with a witness of 5 elements and 1 output. This wasn't enough for anchor outputs, as the + // counterparty can now aggregate multiple HTLCs into a single transaction thanks to + // `SIGHASH_SINGLE` remote signatures, leading us to not claim any HTLCs upon seeing a + // confirmed revoked HTLC transaction (for more details, see + // https://lists.linuxfoundation.org/pipermail/lightning-dev/2022-April/003561.html). + // + // We make sure we're not vulnerable to this case by checking all inputs of the transaction, + // and claim those which spend the commitment transaction, have a witness of 5 elements, and + // have a corresponding output at the same index within the transaction. + for (idx, input) in tx.input.iter().enumerate() { + if input.previous_output.txid == *commitment_txid && input.witness.len() == 5 && tx.output.get(idx).is_some() { + log_error!(logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}", htlc_txid, idx); + let revk_outp = RevokedOutput::build( + per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, + self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, + tx.output[idx].value, self.counterparty_commitment_params.on_counterparty_tx_csv + ); + let justice_package = PackageTemplate::build_package( + htlc_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), + height + self.counterparty_commitment_params.on_counterparty_tx_csv as u32, true, height + ); + claimable_outpoints.push(justice_package); + if outputs_to_watch.is_none() { + outputs_to_watch = Some((htlc_txid, vec![])); + } + outputs_to_watch.as_mut().unwrap().1.push((idx as u32, tx.output[idx].clone())); + } + } + (claimable_outpoints, outputs_to_watch) } // Returns (1) `PackageTemplate`s that can be given to the OnchainTxHandler, so that the handler can @@ -2661,18 +2722,28 @@ impl ChannelMonitorImpl { for &(ref htlc, _, _) in holder_tx.htlc_outputs.iter() { if let Some(transaction_output_index) = htlc.transaction_output_index { - let htlc_output = if htlc.offered { - HolderHTLCOutput::build_offered(htlc.amount_msat, htlc.cltv_expiry) + let (htlc_output, aggregable) = if htlc.offered { + let htlc_output = HolderHTLCOutput::build_offered( + htlc.amount_msat, htlc.cltv_expiry, self.onchain_tx_handler.opt_anchors() + ); + (htlc_output, false) + } else { + let payment_preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.payment_hash) { + preimage.clone() } else { - let payment_preimage = if let Some(preimage) = self.payment_preimages.get(&htlc.payment_hash) { - preimage.clone() - } else { - // We can't build an HTLC-Success transaction without the preimage - continue; - }; - HolderHTLCOutput::build_accepted(payment_preimage, htlc.amount_msat) + // We can't build an HTLC-Success transaction without the preimage + continue; }; - let htlc_package = PackageTemplate::build_package(holder_tx.txid, transaction_output_index, PackageSolvingData::HolderHTLCOutput(htlc_output), htlc.cltv_expiry, false, conf_height); + let htlc_output = HolderHTLCOutput::build_accepted( + payment_preimage, htlc.amount_msat, self.onchain_tx_handler.opt_anchors() + ); + (htlc_output, self.onchain_tx_handler.opt_anchors()) + }; + let htlc_package = PackageTemplate::build_package( + holder_tx.txid, transaction_output_index, + PackageSolvingData::HolderHTLCOutput(htlc_output), + htlc.cltv_expiry, aggregable, conf_height + ); claim_requests.push(htlc_package); } } @@ -2905,9 +2976,9 @@ impl ChannelMonitorImpl { if tx.input.len() == 1 { // Assuming our keys were not leaked (in which case we're screwed no matter what), - // commitment transactions and HTLC transactions will all only ever have one input, - // which is an easy way to filter out any potential non-matching txn for lazy - // filters. + // commitment transactions and HTLC transactions will all only ever have one input + // (except for HTLC transactions for channels with anchor outputs), which is an easy + // way to filter out any potential non-matching txn for lazy filters. let prevout = &tx.input[0].previous_output; if prevout.txid == self.funding_info.0.txid && prevout.vout == self.funding_info.0.index as u32 { let mut balance_spendable_csv = None; @@ -2945,22 +3016,33 @@ impl ChannelMonitorImpl { commitment_tx_to_counterparty_output, }, }); - } else { - if let Some(&commitment_number) = self.counterparty_commitment_txn_on_chain.get(&prevout.txid) { - let (mut new_outpoints, new_outputs_option) = self.check_spend_counterparty_htlc(&tx, commitment_number, height, &logger); + } + } + if tx.input.len() >= 1 { + // While all commitment transactions have one input, HTLC transactions may have more + // if the HTLC was present in an anchor channel. HTLCs can also be resolved in a few + // other ways which can have more than one output. + for tx_input in &tx.input { + let commitment_txid = tx_input.previous_output.txid; + if let Some(&commitment_number) = self.counterparty_commitment_txn_on_chain.get(&commitment_txid) { + let (mut new_outpoints, new_outputs_option) = self.check_spend_counterparty_htlc( + &tx, commitment_number, &commitment_txid, height, &logger + ); claimable_outpoints.append(&mut new_outpoints); if let Some(new_outputs) = new_outputs_option { watch_outputs.push(new_outputs); } + // Since there may be multiple HTLCs (all from the same commitment) being + // claimed by the counterparty within the same transaction, and + // `check_spend_counterparty_htlc` already checks for all of them, we can + // safely break from our loop. + break; } } - } - // While all commitment/HTLC-Success/HTLC-Timeout transactions have one input, HTLCs - // can also be resolved in a few other ways which can have more than one output. Thus, - // we call is_resolving_htlc_output here outside of the tx.input.len() == 1 check. - self.is_resolving_htlc_output(&tx, height, &block_hash, &logger); + self.is_resolving_htlc_output(&tx, height, &block_hash, &logger); - self.is_paying_spendable_output(&tx, height, &block_hash, &logger); + self.is_paying_spendable_output(&tx, height, &block_hash, &logger); + } } if height > self.best_block.height() { @@ -3074,7 +3156,9 @@ impl ChannelMonitorImpl { htlc_value_satoshis, })); self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC { - commitment_tx_output_idx, resolving_txid: Some(entry.txid), + commitment_tx_output_idx, + resolving_txid: Some(entry.txid), + resolving_tx: entry.transaction, payment_preimage: None, }); }, @@ -3087,7 +3171,9 @@ impl ChannelMonitorImpl { }, OnchainEvent::HTLCSpendConfirmation { commitment_tx_output_idx, preimage, .. } => { self.htlcs_resolved_on_chain.push(IrrevocablyResolvedHTLC { - commitment_tx_output_idx: Some(commitment_tx_output_idx), resolving_txid: Some(entry.txid), + commitment_tx_output_idx: Some(commitment_tx_output_idx), + resolving_txid: Some(entry.txid), + resolving_tx: entry.transaction, payment_preimage: preimage, }); }, diff --git a/lightning/src/chain/keysinterface.rs b/lightning/src/chain/keysinterface.rs index 3ca66fcbc3a..f2d198bbfa1 100644 --- a/lightning/src/chain/keysinterface.rs +++ b/lightning/src/chain/keysinterface.rs @@ -34,7 +34,8 @@ use bitcoin::{PackedLockTime, secp256k1, Sequence, Witness}; use crate::util::transaction_utils; use crate::util::crypto::{hkdf_extract_expand_twice, sign}; use crate::util::ser::{Writeable, Writer, Readable, ReadableArgs}; - +#[cfg(anchors)] +use crate::util::events::HTLCDescriptor; use crate::chain::transaction::OutPoint; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::{chan_utils, PaymentPreimage}; @@ -325,6 +326,19 @@ pub trait BaseSign { /// (which is committed to in the BIP 143 signatures). fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result; + #[cfg(anchors)] + /// Computes the signature for a commitment transaction's HTLC output used as an input within + /// `htlc_tx`, which spends the commitment transaction, at index `input`. The signature returned + /// must be be computed using [`EcdsaSighashType::All`]. Note that this should only be used to + /// sign HTLC transactions from channels supporting anchor outputs after all additional + /// inputs/outputs have been added to the transaction. + /// + /// [`EcdsaSighashType::All`]: bitcoin::blockdata::transaction::EcdsaSighashType::All + fn sign_holder_htlc_transaction( + &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, + secp_ctx: &Secp256k1 + ) -> Result; + /// Create a signature for a claiming transaction for a HTLC output on a counterparty's commitment /// transaction, either offered or received. /// @@ -682,7 +696,6 @@ impl InMemorySigner { witness.push(witness_script.clone().into_bytes()); Ok(witness) } - } impl BaseSign for InMemorySigner { @@ -779,6 +792,24 @@ impl BaseSign for InMemorySigner { return Ok(sign(secp_ctx, &sighash, &revocation_key)) } + #[cfg(anchors)] + fn sign_holder_htlc_transaction( + &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, + secp_ctx: &Secp256k1 + ) -> Result { + let per_commitment_point = self.get_per_commitment_point( + htlc_descriptor.per_commitment_number, &secp_ctx + ); + let witness_script = htlc_descriptor.witness_script(&per_commitment_point, secp_ctx); + let sighash = &sighash::SighashCache::new(&*htlc_tx).segwit_signature_hash( + input, &witness_script, htlc_descriptor.htlc.amount_msat / 1000, EcdsaSighashType::All + ).map_err(|_| ())?; + let our_htlc_private_key = chan_utils::derive_private_key( + &secp_ctx, &per_commitment_point, &self.htlc_base_key + ); + Ok(sign(&secp_ctx, &hash_to_message!(sighash), &our_htlc_private_key)) + } + fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result { let htlc_key = chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.htlc_base_key); let revocation_pubkey = chan_utils::derive_public_revocation_key(&secp_ctx, &per_commitment_point, &self.pubkeys().revocation_basepoint); diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index a0850dfb2ef..5bf3a8fd482 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -25,7 +25,7 @@ use crate::chain::keysinterface::BaseSign; use crate::ln::msgs::DecodeError; use crate::ln::PaymentPreimage; #[cfg(anchors)] -use crate::ln::chan_utils; +use crate::ln::chan_utils::{self, HTLCOutputInCommitment}; use crate::ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction}; #[cfg(anchors)] use crate::chain::chaininterface::ConfirmationTarget; @@ -174,6 +174,16 @@ impl Writeable for Option>> { } } +#[cfg(anchors)] +/// The claim commonly referred to as the pre-signed second-stage HTLC transaction. +pub(crate) struct ExternalHTLCClaim { + pub(crate) commitment_txid: Txid, + pub(crate) per_commitment_number: u64, + pub(crate) htlc: HTLCOutputInCommitment, + pub(crate) preimage: Option, + pub(crate) counterparty_sig: Signature, +} + // Represents the different types of claims for which events are yielded externally to satisfy said // claims. #[cfg(anchors)] @@ -185,6 +195,12 @@ pub(crate) enum ClaimEvent { commitment_tx: Transaction, anchor_output_idx: u32, }, + /// Event yielded to signal that the commitment transaction has confirmed and its HTLCs must be + /// resolved by broadcasting a transaction with sufficient fee to claim them. + BumpHTLC { + target_feerate_sat_per_1000_weight: u32, + htlcs: Vec, + }, } /// Represents the different ways an output can be claimed (i.e., spent to an address under our @@ -198,6 +214,9 @@ pub(crate) enum OnchainClaim { Event(ClaimEvent), } +/// An internal identifier to track pending package claims within the `OnchainTxHandler`. +type PackageID = [u8; 32]; + /// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and /// do RBF bumping if possible. pub struct OnchainTxHandler { @@ -225,11 +244,11 @@ pub struct OnchainTxHandler { // us and is immutable until all outpoint of the claimable set are post-anti-reorg-delay solved. // Entry is cache of elements need to generate a bumped claiming transaction (see ClaimTxBumpMaterial) #[cfg(test)] // Used in functional_test to verify sanitization - pub(crate) pending_claim_requests: HashMap, + pub(crate) pending_claim_requests: HashMap, #[cfg(not(test))] - pending_claim_requests: HashMap, + pending_claim_requests: HashMap, #[cfg(anchors)] - pending_claim_events: HashMap, + pending_claim_events: HashMap, // Used to link outpoints claimed in a connected block to a pending claim request. // Key is outpoint than monitor parsing has detected we have keys/scripts to claim @@ -238,9 +257,9 @@ pub struct OnchainTxHandler { // post-anti-reorg-delay solved, confirmaiton_block is used to erase entry if // block with output gets disconnected. #[cfg(test)] // Used in functional_test to verify sanitization - pub claimable_outpoints: HashMap, + pub claimable_outpoints: HashMap, #[cfg(not(test))] - claimable_outpoints: HashMap, + claimable_outpoints: HashMap, locktimed_packages: BTreeMap>, @@ -462,7 +481,7 @@ impl OnchainTxHandler { // since requests can have outpoints split off. if !self.onchain_events_awaiting_threshold_conf.iter() .any(|event_entry| if let OnchainEvent::Claim { claim_request } = event_entry.event { - first_claim_txid_height.0 == claim_request + first_claim_txid_height.0 == claim_request.into_inner() } else { // The onchain event is not a claim, keep seeking until we find one. false @@ -485,15 +504,36 @@ impl OnchainTxHandler { // didn't receive confirmation of it before, or not enough reorg-safe depth on top of it). let new_timer = Some(cached_request.get_height_timer(cur_height)); if cached_request.is_malleable() { + #[cfg(anchors)] + { // Attributes are not allowed on if expressions on our current MSRV of 1.41. + if cached_request.requires_external_funding() { + let target_feerate_sat_per_1000_weight = cached_request + .compute_package_feerate(fee_estimator, ConfirmationTarget::HighPriority); + if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) { + return Some(( + new_timer, + target_feerate_sat_per_1000_weight as u64, + OnchainClaim::Event(ClaimEvent::BumpHTLC { + target_feerate_sat_per_1000_weight, + htlcs, + }), + )); + } else { + return None; + } + } + } + let predicted_weight = cached_request.package_weight(&self.destination_script); - if let Some((output_value, new_feerate)) = - cached_request.compute_package_output(predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger) { + if let Some((output_value, new_feerate)) = cached_request.compute_package_output( + predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger, + ) { assert!(new_feerate != 0); let transaction = cached_request.finalize_malleable_package(self, output_value, self.destination_script.clone(), logger).unwrap(); log_trace!(logger, "...with timer {} and feerate {}", new_timer.unwrap(), new_feerate); assert!(predicted_weight >= transaction.weight()); - return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction))) + return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction))); } } else { // Untractable packages cannot have their fees bumped through Replace-By-Fee. Some @@ -549,7 +589,7 @@ impl OnchainTxHandler { debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding"); None }, - }); + }) } None } @@ -628,27 +668,38 @@ impl OnchainTxHandler { if let Some((new_timer, new_feerate, claim)) = self.generate_claim(cur_height, &req, &*fee_estimator, &*logger) { req.set_timer(new_timer); req.set_feerate(new_feerate); - let txid = match claim { + let package_id = match claim { OnchainClaim::Tx(tx) => { log_info!(logger, "Broadcasting onchain {}", log_tx!(tx)); broadcaster.broadcast_transaction(&tx); - tx.txid() + tx.txid().into_inner() }, #[cfg(anchors)] OnchainClaim::Event(claim_event) => { log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints()); - let txid = match claim_event { - ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(), + let package_id = match claim_event { + ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid().into_inner(), + ClaimEvent::BumpHTLC { ref htlcs, .. } => { + // Use the same construction as a lightning channel id to generate + // the package id for this request based on the first HTLC. It + // doesn't matter what we use as long as it's unique per request. + let mut package_id = [0; 32]; + package_id[..].copy_from_slice(&htlcs[0].commitment_txid[..]); + let htlc_output_index = htlcs[0].htlc.transaction_output_index.unwrap(); + package_id[30] ^= ((htlc_output_index >> 8) & 0xff) as u8; + package_id[31] ^= ((htlc_output_index >> 0) & 0xff) as u8; + package_id + }, }; - self.pending_claim_events.insert(txid, claim_event); - txid + self.pending_claim_events.insert(package_id, claim_event); + package_id }, }; for k in req.outpoints() { log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout); - self.claimable_outpoints.insert(k.clone(), (txid, conf_height)); + self.claimable_outpoints.insert(k.clone(), (package_id, conf_height)); } - self.pending_claim_requests.insert(txid, req); + self.pending_claim_requests.insert(package_id, req); } } } @@ -681,15 +732,32 @@ impl OnchainTxHandler { //... we need to verify equality between transaction outpoints and claim request // outpoints to know if transaction is the original claim or a bumped one issued // by us. - let mut set_equality = true; - if request.outpoints().len() != tx.input.len() { - set_equality = false; + let mut are_sets_equal = true; + if !request.requires_external_funding() || !request.is_malleable() { + // If the claim does not require external funds to be allocated through + // additional inputs we can simply check the inputs in order as they + // cannot change under us. + if request.outpoints().len() != tx.input.len() { + are_sets_equal = false; + } else { + for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) { + if **claim_inp != tx_inp.previous_output { + are_sets_equal = false; + } + } + } } else { - for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) { - if **claim_inp != tx_inp.previous_output { - set_equality = false; + // Otherwise, we'll do a linear search for each input (we don't expect + // large input sets to exist) to ensure the request's input set is fully + // spent to be resilient against the external claim reordering inputs. + let mut spends_all_inputs = true; + for request_input in request.outpoints() { + if tx.input.iter().find(|input| input.previous_output == *request_input).is_none() { + spends_all_inputs = false; + break; } } + are_sets_equal = spends_all_inputs; } macro_rules! clean_claim_request_after_safety_delay { @@ -698,7 +766,7 @@ impl OnchainTxHandler { txid: tx.txid(), height: conf_height, block_hash: Some(conf_hash), - event: OnchainEvent::Claim { claim_request: first_claim_txid_height.0.clone() } + event: OnchainEvent::Claim { claim_request: Txid::from_inner(first_claim_txid_height.0) } }; if !self.onchain_events_awaiting_threshold_conf.contains(&entry) { self.onchain_events_awaiting_threshold_conf.push(entry); @@ -709,7 +777,7 @@ impl OnchainTxHandler { // If this is our transaction (or our counterparty spent all the outputs // before we could anyway with same inputs order than us), wait for // ANTI_REORG_DELAY and clean the RBF tracking map. - if set_equality { + if are_sets_equal { clean_claim_request_after_safety_delay!(); } else { // If false, generate new claim request with update outpoint set let mut at_least_one_drop = false; @@ -754,14 +822,15 @@ impl OnchainTxHandler { if entry.has_reached_confirmation_threshold(cur_height) { match entry.event { OnchainEvent::Claim { claim_request } => { + let package_id = claim_request.into_inner(); // We may remove a whole set of claim outpoints here, as these one may have // been aggregated in a single tx and claimed so atomically - if let Some(request) = self.pending_claim_requests.remove(&claim_request) { + if let Some(request) = self.pending_claim_requests.remove(&package_id) { for outpoint in request.outpoints() { log_debug!(logger, "Removing claim tracking for {} due to maturation of claim tx {}.", outpoint, claim_request); self.claimable_outpoints.remove(&outpoint); #[cfg(anchors)] - self.pending_claim_events.remove(&claim_request); + self.pending_claim_events.remove(&package_id); } } }, @@ -994,6 +1063,37 @@ impl OnchainTxHandler { htlc_tx } + #[cfg(anchors)] + pub(crate) fn generate_external_htlc_claim( + &mut self, outp: &::bitcoin::OutPoint, preimage: &Option + ) -> Option { + let find_htlc = |holder_commitment: &HolderCommitmentTransaction| -> Option { + let trusted_tx = holder_commitment.trust(); + if outp.txid != trusted_tx.txid() { + return None; + } + trusted_tx.htlcs().iter().enumerate() + .find(|(_, htlc)| if let Some(output_index) = htlc.transaction_output_index { + output_index == outp.vout + } else { + false + }) + .map(|(htlc_idx, htlc)| { + let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx]; + ExternalHTLCClaim { + commitment_txid: trusted_tx.txid(), + per_commitment_number: trusted_tx.commitment_number(), + htlc: htlc.clone(), + preimage: *preimage, + counterparty_sig: counterparty_htlc_sig, + } + }) + }; + // Check if the HTLC spends from the current holder commitment or the previous one otherwise. + find_htlc(&self.holder_commitment) + .or_else(|| self.prev_holder_commitment.as_ref().map(|c| find_htlc(c)).flatten()) + } + pub(crate) fn opt_anchors(&self) -> bool { self.channel_transaction_parameters.opt_anchors.is_some() } diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 32f38323d81..dbf46298888 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -26,6 +26,8 @@ use crate::ln::chan_utils; use crate::ln::msgs::DecodeError; use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT}; use crate::chain::keysinterface::Sign; +#[cfg(anchors)] +use crate::chain::onchaintx::ExternalHTLCClaim; use crate::chain::onchaintx::OnchainTxHandler; use crate::util::logger::Logger; use crate::util::ser::{Readable, Writer, Writeable}; @@ -252,33 +254,41 @@ impl_writeable_tlv_based!(CounterpartyReceivedHTLCOutput, { #[derive(Clone, PartialEq, Eq)] pub(crate) struct HolderHTLCOutput { preimage: Option, - amount: u64, + amount_msat: u64, /// Defaults to 0 for HTLC-Success transactions, which have no expiry cltv_expiry: u32, + opt_anchors: Option<()>, } impl HolderHTLCOutput { - pub(crate) fn build_offered(amount: u64, cltv_expiry: u32) -> Self { + pub(crate) fn build_offered(amount_msat: u64, cltv_expiry: u32, opt_anchors: bool) -> Self { HolderHTLCOutput { preimage: None, - amount, + amount_msat, cltv_expiry, + opt_anchors: if opt_anchors { Some(()) } else { None } , } } - pub(crate) fn build_accepted(preimage: PaymentPreimage, amount: u64) -> Self { + pub(crate) fn build_accepted(preimage: PaymentPreimage, amount_msat: u64, opt_anchors: bool) -> Self { HolderHTLCOutput { preimage: Some(preimage), - amount, + amount_msat, cltv_expiry: 0, + opt_anchors: if opt_anchors { Some(()) } else { None } , } } + + fn opt_anchors(&self) -> bool { + self.opt_anchors.is_some() + } } impl_writeable_tlv_based!(HolderHTLCOutput, { - (0, amount, required), + (0, amount_msat, required), (2, cltv_expiry, required), - (4, preimage, option) + (4, preimage, option), + (6, opt_anchors, option) }); /// A struct to describe the channel output on the funding transaction. @@ -333,10 +343,10 @@ impl PackageSolvingData { PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.amount, PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000, PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000, - // Note: Currently, amounts of holder outputs spending witnesses aren't used - // as we can't malleate spending package to increase their feerate. This - // should change with the remaining anchor output patchset. - PackageSolvingData::HolderHTLCOutput(..) => unreachable!(), + PackageSolvingData::HolderHTLCOutput(ref outp) => { + debug_assert!(outp.opt_anchors()); + outp.amount_msat / 1000 + }, PackageSolvingData::HolderFundingOutput(ref outp) => { debug_assert!(outp.opt_anchors()); outp.funding_amount.unwrap() @@ -345,18 +355,23 @@ impl PackageSolvingData { amt } fn weight(&self) -> usize { - let weight = match self { - PackageSolvingData::RevokedOutput(ref outp) => { outp.weight as usize }, - PackageSolvingData::RevokedHTLCOutput(ref outp) => { outp.weight as usize }, - PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => { weight_offered_htlc(outp.opt_anchors()) as usize }, - PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => { weight_received_htlc(outp.opt_anchors()) as usize }, - // Note: Currently, weights of holder outputs spending witnesses aren't used - // as we can't malleate spending package to increase their feerate. This - // should change with the remaining anchor output patchset. - PackageSolvingData::HolderHTLCOutput(..) => { unreachable!() }, - PackageSolvingData::HolderFundingOutput(..) => { unreachable!() }, - }; - weight + match self { + PackageSolvingData::RevokedOutput(ref outp) => outp.weight as usize, + PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.weight as usize, + PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => weight_offered_htlc(outp.opt_anchors()) as usize, + PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => weight_received_htlc(outp.opt_anchors()) as usize, + PackageSolvingData::HolderHTLCOutput(ref outp) => { + debug_assert!(outp.opt_anchors()); + if outp.preimage.is_none() { + weight_offered_htlc(true) as usize + } else { + weight_received_htlc(true) as usize + } + }, + // Since HolderFundingOutput maps to an untractable package that is already signed, its + // weight can be determined from the transaction itself. + PackageSolvingData::HolderFundingOutput(..) => unreachable!(), + } } fn is_compatible(&self, input: &PackageSolvingData) -> bool { match self { @@ -435,8 +450,13 @@ impl PackageSolvingData { } fn get_finalized_tx(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler) -> Option { match self { - PackageSolvingData::HolderHTLCOutput(ref outp) => { return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage); } - PackageSolvingData::HolderFundingOutput(ref outp) => { return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript)); } + PackageSolvingData::HolderHTLCOutput(ref outp) => { + debug_assert!(!outp.opt_anchors()); + return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage); + } + PackageSolvingData::HolderFundingOutput(ref outp) => { + return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript)); + } _ => { panic!("API Error!"); } } } @@ -636,6 +656,25 @@ impl PackageTemplate { let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR; inputs_weight + witnesses_weight + transaction_weight + output_weight } + #[cfg(anchors)] + pub(crate) fn construct_malleable_package_with_external_funding( + &self, onchain_handler: &mut OnchainTxHandler, + ) -> Option> { + debug_assert!(self.requires_external_funding()); + let mut htlcs: Option> = None; + for (previous_output, input) in &self.inputs { + match input { + PackageSolvingData::HolderHTLCOutput(ref outp) => { + debug_assert!(outp.opt_anchors()); + onchain_handler.generate_external_htlc_claim(&previous_output, &outp.preimage).map(|htlc| { + htlcs.get_or_insert_with(|| Vec::with_capacity(self.inputs.len())).push(htlc); + }); + } + _ => debug_assert!(false, "Expected HolderHTLCOutputs to not be aggregated with other input types"), + } + } + htlcs + } pub(crate) fn finalize_malleable_package( &self, onchain_handler: &mut OnchainTxHandler, value: u64, destination_script: Script, logger: &L ) -> Option where L::Target: Logger { @@ -740,6 +779,7 @@ impl PackageTemplate { pub(crate) fn requires_external_funding(&self) -> bool { self.inputs.iter().find(|input| match input.1 { PackageSolvingData::HolderFundingOutput(ref outp) => outp.opt_anchors(), + PackageSolvingData::HolderHTLCOutput(ref outp) => outp.opt_anchors(), _ => false, }).is_some() } @@ -750,7 +790,11 @@ impl PackageTemplate { PackageSolvingData::RevokedHTLCOutput(..) => PackageMalleability::Malleable, PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => PackageMalleability::Malleable, PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => PackageMalleability::Malleable, - PackageSolvingData::HolderHTLCOutput(..) => PackageMalleability::Untractable, + PackageSolvingData::HolderHTLCOutput(ref outp) => if outp.opt_anchors() { + PackageMalleability::Malleable + } else { + PackageMalleability::Untractable + }, PackageSolvingData::HolderFundingOutput(..) => PackageMalleability::Untractable, }; let mut inputs = Vec::with_capacity(1); @@ -799,7 +843,11 @@ impl Readable for PackageTemplate { PackageSolvingData::RevokedHTLCOutput(..) => { (PackageMalleability::Malleable, true) }, PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => { (PackageMalleability::Malleable, true) }, PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => { (PackageMalleability::Malleable, false) }, - PackageSolvingData::HolderHTLCOutput(..) => { (PackageMalleability::Untractable, false) }, + PackageSolvingData::HolderHTLCOutput(ref outp) => if outp.opt_anchors() { + (PackageMalleability::Malleable, outp.preimage.is_some()) + } else { + (PackageMalleability::Untractable, false) + }, PackageSolvingData::HolderFundingOutput(..) => { (PackageMalleability::Untractable, false) }, } } else { return Err(DecodeError::InvalidValue); }; @@ -959,7 +1007,7 @@ mod tests { () => { { let preimage = PaymentPreimage([2;32]); - PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_accepted(preimage, 0)) + PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build_accepted(preimage, 0, false)) } } } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 5871d1706bd..729425d70d5 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -462,7 +462,7 @@ impl_writeable_tlv_based!(TxCreationKeys, { }); /// One counterparty's public keys which do not change over the life of a channel. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ChannelPublicKeys { /// The public key which is used to sign all commitment transactions, as it appears in the /// on-chain channel lock-in 2-of-2 multisig output. @@ -679,7 +679,24 @@ pub fn make_funding_redeemscript(broadcaster: &PublicKey, countersignatory: &Pub /// commitment transaction). pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool, use_non_zero_fee_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey) -> Transaction { let mut txins: Vec = Vec::new(); - txins.push(TxIn { + txins.push(build_htlc_input(commitment_txid, htlc, opt_anchors)); + + let mut txouts: Vec = Vec::new(); + txouts.push(build_htlc_output( + feerate_per_kw, contest_delay, htlc, opt_anchors, use_non_zero_fee_anchors, + broadcaster_delayed_payment_key, revocation_key + )); + + Transaction { + version: 2, + lock_time: PackedLockTime(if htlc.offered { htlc.cltv_expiry } else { 0 }), + input: txins, + output: txouts, + } +} + +pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommitment, opt_anchors: bool) -> TxIn { + TxIn { previous_output: OutPoint { txid: commitment_txid.clone(), vout: htlc.transaction_output_index.expect("Can't build an HTLC transaction for a dust output"), @@ -687,8 +704,13 @@ pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, conte script_sig: Script::new(), sequence: Sequence(if opt_anchors { 1 } else { 0 }), witness: Witness::new(), - }); + } +} +pub(crate) fn build_htlc_output( + feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, opt_anchors: bool, + use_non_zero_fee_anchors: bool, broadcaster_delayed_payment_key: &PublicKey, revocation_key: &PublicKey +) -> TxOut { let weight = if htlc.offered { htlc_timeout_tx_weight(opt_anchors) } else { @@ -701,18 +723,41 @@ pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, conte htlc.amount_msat / 1000 - total_fee }; - let mut txouts: Vec = Vec::new(); - txouts.push(TxOut { + TxOut { script_pubkey: get_revokeable_redeemscript(revocation_key, contest_delay, broadcaster_delayed_payment_key).to_v0_p2wsh(), value: output_value, - }); + } +} - Transaction { - version: 2, - lock_time: PackedLockTime(if htlc.offered { htlc.cltv_expiry } else { 0 }), - input: txins, - output: txouts, +/// Returns the witness required to satisfy and spend a HTLC input. +pub fn build_htlc_input_witness( + local_sig: &Signature, remote_sig: &Signature, preimage: &Option, + redeem_script: &Script, opt_anchors: bool, +) -> Witness { + let remote_sighash_type = if opt_anchors { + EcdsaSighashType::SinglePlusAnyoneCanPay + } else { + EcdsaSighashType::All + }; + let mut remote_sig = remote_sig.serialize_der().to_vec(); + remote_sig.push(remote_sighash_type as u8); + + let mut local_sig = local_sig.serialize_der().to_vec(); + local_sig.push(EcdsaSighashType::All as u8); + + let mut witness = Witness::new(); + // First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element. + witness.push(vec![]); + witness.push(remote_sig); + witness.push(local_sig); + if let Some(preimage) = preimage { + witness.push(preimage.0.to_vec()); + } else { + // Due to BIP146 (MINIMALIF) this must be a zero-length element to relay. + witness.push(vec![]); } + witness.push(redeem_script.to_bytes()); + witness } /// Gets the witnessScript for the to_remote output when anchors are enabled. @@ -766,7 +811,7 @@ pub fn build_anchor_input_witness(funding_key: &PublicKey, funding_sig: &Signatu /// /// Normally, this is converted to the broadcaster/countersignatory-organized DirectedChannelTransactionParameters /// before use, via the as_holder_broadcastable and as_counterparty_broadcastable functions. -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct ChannelTransactionParameters { /// Holder public keys pub holder_pubkeys: ChannelPublicKeys, @@ -790,7 +835,7 @@ pub struct ChannelTransactionParameters { } /// Late-bound per-channel counterparty data used to build transactions. -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct CounterpartyChannelTransactionParameters { /// Counter-party public keys pub pubkeys: ChannelPublicKeys, @@ -1553,26 +1598,9 @@ impl<'a> TrustedCommitmentTransaction<'a> { let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key); - let sighashtype = if self.opt_anchors() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All }; - - // First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element. - htlc_tx.input[0].witness.push(Vec::new()); - - let mut cp_sig_ser = counterparty_signature.serialize_der().to_vec(); - cp_sig_ser.push(sighashtype as u8); - htlc_tx.input[0].witness.push(cp_sig_ser); - let mut holder_sig_ser = signature.serialize_der().to_vec(); - holder_sig_ser.push(EcdsaSighashType::All as u8); - htlc_tx.input[0].witness.push(holder_sig_ser); - - if this_htlc.offered { - // Due to BIP146 (MINIMALIF) this must be a zero-length element to relay. - htlc_tx.input[0].witness.push(Vec::new()); - } else { - htlc_tx.input[0].witness.push(preimage.unwrap().0.to_vec()); - } - - htlc_tx.input[0].witness.push(htlc_redeemscript.as_bytes().to_vec()); + htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness( + signature, counterparty_signature, preimage, &htlc_redeemscript, self.opt_anchors(), + ); htlc_tx } } diff --git a/lightning/src/util/enforcing_trait_impls.rs b/lightning/src/util/enforcing_trait_impls.rs index 21ef5d4b178..1a9038a2f5d 100644 --- a/lightning/src/util/enforcing_trait_impls.rs +++ b/lightning/src/util/enforcing_trait_impls.rs @@ -23,6 +23,8 @@ use bitcoin::util::sighash; use bitcoin::secp256k1; use bitcoin::secp256k1::{SecretKey, PublicKey}; use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; +#[cfg(anchors)] +use crate::util::events::HTLCDescriptor; use crate::util::ser::{Writeable, Writer}; use crate::io::Error; @@ -190,6 +192,17 @@ impl BaseSign for EnforcingSigner { Ok(self.inner.sign_justice_revoked_htlc(justice_tx, input, amount, per_commitment_key, htlc, secp_ctx).unwrap()) } + #[cfg(anchors)] + fn sign_holder_htlc_transaction( + &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, + secp_ctx: &Secp256k1 + ) -> Result { + let per_commitment_point = self.get_per_commitment_point(htlc_descriptor.per_commitment_number, secp_ctx); + assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input()); + assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(&per_commitment_point, secp_ctx)); + Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap()) + } + fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result { Ok(self.inner.sign_counterparty_htlc_transaction(htlc_tx, input, amount, per_commitment_point, htlc, secp_ctx).unwrap()) } diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index 28bd6978f6a..2c7e5413daf 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -16,7 +16,7 @@ use crate::chain::keysinterface::SpendableOutputDescriptor; #[cfg(anchors)] -use crate::ln::chan_utils::HTLCOutputInCommitment; +use crate::ln::chan_utils::{self, ChannelTransactionParameters, HTLCOutputInCommitment}; use crate::ln::channelmanager::{InterceptId, PaymentId}; use crate::ln::channel::FUNDING_CONF_DEADLINE_BLOCKS; use crate::ln::features::ChannelTypeFeatures; @@ -29,11 +29,15 @@ use crate::routing::router::{RouteHop, RouteParameters}; use bitcoin::{PackedLockTime, Transaction}; #[cfg(anchors)] -use bitcoin::OutPoint; +use bitcoin::{OutPoint, Txid, TxIn, TxOut, Witness}; use bitcoin::blockdata::script::Script; use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::secp256k1::PublicKey; +#[cfg(anchors)] +use bitcoin::secp256k1::{self, Secp256k1}; +#[cfg(anchors)] +use bitcoin::secp256k1::ecdsa::Signature; use crate::io; use crate::prelude::*; use core::time::Duration; @@ -237,6 +241,99 @@ pub struct AnchorDescriptor { pub outpoint: OutPoint, } +#[cfg(anchors)] +/// A descriptor used to sign for a commitment transaction's HTLC output. +#[derive(Clone, Debug)] +pub struct HTLCDescriptor { + /// A unique identifier used along with `channel_value_satoshis` to re-derive the + /// [`InMemorySigner`] required to sign `input`. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + pub channel_keys_id: [u8; 32], + /// The value in satoshis of the channel we're attempting to spend the anchor output of. This is + /// used along with `channel_keys_id` to re-derive the [`InMemorySigner`] required to sign + /// `input`. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + pub channel_value_satoshis: u64, + /// The necessary channel parameters that need to be provided to the re-derived + /// [`InMemorySigner`] through [`BaseSign::ready_channel`]. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + /// [`BaseSign::ready_channel`]: crate::chain::keysinterface::BaseSign::ready_channel + pub channel_parameters: ChannelTransactionParameters, + /// The txid of the commitment transaction in which the HTLC output lives. + pub commitment_txid: Txid, + /// The number of the commitment transaction in which the HTLC output lives. + pub per_commitment_number: u64, + /// The details of the HTLC as it appears in the commitment transaction. + pub htlc: HTLCOutputInCommitment, + /// The preimage, if `Some`, to claim the HTLC output with. If `None`, the timeout path must be + /// taken. + pub preimage: Option, + /// The counterparty's signature required to spend the HTLC output. + pub counterparty_sig: Signature +} + +#[cfg(anchors)] +impl HTLCDescriptor { + /// Returns the unsigned transaction input spending the HTLC output in the commitment + /// transaction. + pub fn unsigned_tx_input(&self) -> TxIn { + chan_utils::build_htlc_input(&self.commitment_txid, &self.htlc, true /* opt_anchors */) + } + + /// Returns the delayed output created as a result of spending the HTLC output in the commitment + /// transaction. + pub fn tx_output( + &self, per_commitment_point: &PublicKey, secp: &Secp256k1 + ) -> TxOut { + let channel_params = self.channel_parameters.as_holder_broadcastable(); + let broadcaster_keys = channel_params.broadcaster_pubkeys(); + let counterparty_keys = channel_params.countersignatory_pubkeys(); + let broadcaster_delayed_key = chan_utils::derive_public_key( + secp, per_commitment_point, &broadcaster_keys.delayed_payment_basepoint + ); + let counterparty_revocation_key = chan_utils::derive_public_revocation_key( + secp, per_commitment_point, &counterparty_keys.revocation_basepoint + ); + chan_utils::build_htlc_output( + 0 /* feerate_per_kw */, channel_params.contest_delay(), &self.htlc, true /* opt_anchors */, + false /* use_non_zero_fee_anchors */, &broadcaster_delayed_key, &counterparty_revocation_key + ) + } + + /// Returns the witness script of the HTLC output in the commitment transaction. + pub fn witness_script( + &self, per_commitment_point: &PublicKey, secp: &Secp256k1 + ) -> Script { + let channel_params = self.channel_parameters.as_holder_broadcastable(); + let broadcaster_keys = channel_params.broadcaster_pubkeys(); + let counterparty_keys = channel_params.countersignatory_pubkeys(); + let broadcaster_htlc_key = chan_utils::derive_public_key( + secp, per_commitment_point, &broadcaster_keys.htlc_basepoint + ); + let counterparty_htlc_key = chan_utils::derive_public_key( + secp, per_commitment_point, &counterparty_keys.htlc_basepoint + ); + let counterparty_revocation_key = chan_utils::derive_public_revocation_key( + secp, per_commitment_point, &counterparty_keys.revocation_basepoint + ); + chan_utils::get_htlc_redeemscript_with_explicit_keys( + &self.htlc, true /* opt_anchors */, &broadcaster_htlc_key, &counterparty_htlc_key, + &counterparty_revocation_key, + ) + } + + /// Returns the fully signed witness required to spend the HTLC output in the commitment + /// transaction. + pub fn tx_input_witness(&self, signature: &Signature, witness_script: &Script) -> Witness { + chan_utils::build_htlc_input_witness( + signature, &self.counterparty_sig, &self.preimage, witness_script, true /* opt_anchors */ + ) + } +} + #[cfg(anchors)] /// Represents the different types of transactions, originating from LDK, to be bumped. #[derive(Clone, Debug)] @@ -249,14 +346,17 @@ pub enum BumpTransactionEvent { /// with additional inputs to meet the target feerate. Failure to meet the target feerate /// decreases the confirmation odds of the transaction package (which includes the commitment /// and child anchor transactions), possibly resulting in a loss of funds. Once the transaction - /// is constructed, it must be fully signed for and broadcasted by the consumer of the event + /// is constructed, it must be fully signed for and broadcast by the consumer of the event /// along with the `commitment_tx` enclosed. Note that the `commitment_tx` must always be /// broadcast first, as the child anchor transaction depends on it. /// /// The consumer should be able to sign for any of the additional inputs included within the /// child anchor transaction. To sign its anchor input, an [`InMemorySigner`] should be /// re-derived through [`KeysManager::derive_channel_keys`] with the help of - /// [`AnchorDescriptor::channel_keys_id`] and [`AnchorDescriptor::channel_value_satoshis`]. + /// [`AnchorDescriptor::channel_keys_id`] and [`AnchorDescriptor::channel_value_satoshis`]. The + /// anchor input signature can be computed with [`BaseSign::sign_holder_anchor_input`], + /// which can then be provided to [`build_anchor_input_witness`] along with the `funding_pubkey` + /// to obtain the full witness required to spend. /// /// It is possible to receive more than one instance of this event if a valid child anchor /// transaction is never broadcast or is but not with a sufficient fee to be mined. Care should @@ -277,6 +377,8 @@ pub enum BumpTransactionEvent { /// /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner /// [`KeysManager::derive_channel_keys`]: crate::chain::keysinterface::KeysManager::derive_channel_keys + /// [`BaseSign::sign_holder_anchor_input`]: crate::chain::keysinterface::BaseSign::sign_holder_anchor_input + /// [`build_anchor_input_witness`]: crate::ln::chan_utils::build_anchor_input_witness ChannelClose { /// The target feerate that the transaction package, which consists of the commitment /// transaction and the to-be-crafted child anchor transaction, must meet. @@ -295,6 +397,41 @@ pub enum BumpTransactionEvent { /// commitment transaction confirms. pending_htlcs: Vec, }, + /// Indicates that a channel featuring anchor outputs has unilaterally closed on-chain by a + /// holder commitment transaction and its HTLC(s) need to be resolved on-chain. With the + /// zero-HTLC-transaction-fee variant of anchor outputs, the pre-signed HTLC + /// transactions have a zero fee, thus requiring additional inputs and/or outputs to be attached + /// for a timely confirmation within the chain. These additional inputs and/or outputs must be + /// appended to the resulting HTLC transaction to meet the target feerate. Failure to meet the + /// target feerate decreases the confirmation odds of the transaction, possibly resulting in a + /// loss of funds. Once the transaction meets the target feerate, it must be signed for and + /// broadcast by the consumer of the event. + /// + /// The consumer should be able to sign for any of the non-HTLC inputs added to the resulting + /// HTLC transaction. To sign HTLC inputs, an [`InMemorySigner`] should be re-derived through + /// [`KeysManager::derive_channel_keys`] with the help of `channel_keys_id` and + /// `channel_value_satoshis`. Each HTLC input's signature can be computed with + /// [`BaseSign::sign_holder_htlc_transaction`], which can then be provided to + /// [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required to spend. + /// + /// It is possible to receive more than one instance of this event if a valid HTLC transaction + /// is never broadcast or is but not with a sufficient fee to be mined. Care should be taken by + /// the consumer of the event to ensure any future iterations of the HTLC transaction adhere to + /// the [Replace-By-Fee + /// rules](https://github.com/bitcoin/bitcoin/blob/master/doc/policy/mempool-replacements.md) + /// for fee bumps to be accepted into the mempool, and eventually the chain. As the frequency of + /// these events is not user-controlled, users may ignore/drop the event if either they are no + /// longer able to commit external confirmed funds to the HTLC transaction or the fee committed + /// to the HTLC transaction is greater in value than the HTLCs being claimed. + /// + /// [`InMemorySigner`]: crate::chain::keysinterface::InMemorySigner + /// [`KeysManager::derive_channel_keys`]: crate::chain::keysinterface::KeysManager::derive_channel_keys + /// [`BaseSign::sign_holder_htlc_transaction`]: crate::chain::keysinterface::BaseSign::sign_holder_htlc_transaction + /// [`HTLCDescriptor::tx_input_witness`]: HTLCDescriptor::tx_input_witness + HTLCResolution { + target_feerate_sat_per_1000_weight: u32, + htlc_descriptors: Vec, + }, } /// Will be used in [`Event::HTLCIntercepted`] to identify the next hop in the HTLC's path. @@ -983,9 +1120,10 @@ impl Writeable for Event { &Event::BumpTransaction(ref event)=> { 27u8.write(writer)?; match event { - // We never write the ChannelClose events as they'll be replayed upon restarting - // anyway if the commitment transaction remains unconfirmed. + // We never write the ChannelClose|HTLCResolution events as they'll be replayed + // upon restarting anyway if they remain unresolved. BumpTransactionEvent::ChannelClose { .. } => {} + BumpTransactionEvent::HTLCResolution { .. } => {} } } &Event::ChannelReady { ref channel_id, ref user_channel_id, ref counterparty_node_id, ref channel_type } => {