From a775008d31bc234a33c94314b995101bebc15ba5 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 13 Jan 2025 23:37:00 +0000 Subject: [PATCH 01/47] Add `ChannelSigner::get_channel_parameters` --- lightning/src/sign/mod.rs | 16 +++++++--------- lightning/src/util/test_channel_signer.rs | 4 ++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 2be0cb39f4f..2370bc95530 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -807,6 +807,9 @@ pub trait ChannelSigner { /// /// channel_parameters.is_populated() MUST be true. fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters); + + /// Returns the parameters of this signer + fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters>; } /// Specifies the recipient of an invoice. @@ -1177,15 +1180,6 @@ impl InMemorySigner { self.get_channel_parameters().map(|params| params.funding_outpoint.as_ref()).flatten() } - /// Returns a [`ChannelTransactionParameters`] for this channel, to be used when verifying or - /// building transactions. - /// - /// Will return `None` if [`ChannelSigner::provide_channel_parameters`] has not been called. - /// In general, this is safe to `unwrap` only in [`ChannelSigner`] implementation. - pub fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters> { - self.channel_parameters.as_ref() - } - /// Returns the channel type features of the channel parameters. Should be helpful for /// determining a channel's category, i. e. legacy/anchors/taproot/etc. /// @@ -1391,6 +1385,10 @@ impl ChannelSigner for InMemorySigner { assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); self.channel_parameters = Some(channel_parameters.clone()); } + + fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters> { + self.channel_parameters.as_ref() + } } const MISSING_PARAMS_ERR: &'static str = diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index f3ef4dc1557..5ad4e301f28 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -212,6 +212,10 @@ impl ChannelSigner for TestChannelSigner { fn provide_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) { self.inner.provide_channel_parameters(channel_parameters) } + + fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters> { + self.inner.get_channel_parameters() + } } impl EcdsaChannelSigner for TestChannelSigner { From 42d65da4a7db7cc5bf4f90868e57127a618b07f4 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 7 Dec 2024 00:37:27 +0000 Subject: [PATCH 02/47] Let `ChannelSigner` set `to_remote` script pubkey This allows the `to_remote` output to easily be changed according to the features of the channel, or the evolution of the LN specification. `to_remote` could even be set to completely arbitrary scripts if compatibility with the formal LN spec is not required. --- lightning/src/chain/channelmonitor.rs | 10 +-- lightning/src/ln/chan_utils.rs | 97 +++++++++++++---------- lightning/src/ln/channel.rs | 29 ++++--- lightning/src/ln/functional_tests.rs | 10 ++- lightning/src/sign/mod.rs | 30 ++++++- lightning/src/util/test_channel_signer.rs | 17 +++- 6 files changed, 126 insertions(+), 67 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 3f6bdc3f256..d152bc6957a 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -1338,7 +1338,7 @@ impl ChannelMonitor { ChannelMonitor { inner: Mutex::new(imp) } } - pub(crate) fn new(secp_ctx: Secp256k1, keys: Signer, shutdown_script: Option, + pub(crate) fn new(secp_ctx: Secp256k1, mut keys: Signer, shutdown_script: Option, on_counterparty_tx_csv: u16, destination_script: &Script, funding_info: (OutPoint, ScriptBuf), channel_parameters: &ChannelTransactionParameters, holder_pays_commitment_tx_fee: bool, funding_redeemscript: ScriptBuf, channel_value_satoshis: u64, @@ -1347,10 +1347,10 @@ impl ChannelMonitor { best_block: BestBlock, counterparty_node_id: PublicKey, channel_id: ChannelId, ) -> ChannelMonitor { + keys.provide_channel_parameters(channel_parameters); + assert!(commitment_transaction_number_obscure_factor <= (1 << 48)); - let counterparty_payment_script = chan_utils::get_counterparty_payment_script( - &channel_parameters.channel_type_features, &keys.pubkeys().payment_point - ); + let counterparty_payment_script = keys.get_counterparty_payment_script(false); let counterparty_channel_parameters = channel_parameters.counterparty_parameters.as_ref().unwrap(); let counterparty_delayed_payment_base_key = counterparty_channel_parameters.pubkeys.delayed_payment_basepoint; @@ -3420,7 +3420,7 @@ impl ChannelMonitorImpl { CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number, to_broadcaster_value, to_countersignatory_value, broadcaster_funding_key, countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs, - channel_parameters) + channel_parameters, &self.onchain_tx_handler.signer, &self.onchain_tx_handler.secp_ctx, false) } fn counterparty_commitment_txs_from_update(&self, update: &ChannelMonitorUpdate) -> Vec { diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index ae76308f0ce..2ad65c6a11e 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -20,14 +20,13 @@ use bitcoin::sighash::EcdsaSighashType; use bitcoin::transaction::Version; use bitcoin::hashes::{Hash, HashEngine}; -use bitcoin::hashes::hash160::Hash as Hash160; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::ripemd160::Hash as Ripemd160; use bitcoin::hash_types::Txid; use crate::chain::chaininterface::fee_for_weight; use crate::chain::package::WEIGHT_REVOKED_OUTPUT; -use crate::sign::EntropySource; +use crate::sign::{ChannelSigner, EntropySource}; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::ln::msgs::DecodeError; use crate::util::ser::{Readable, RequiredWrapper, Writeable, Writer}; @@ -1105,24 +1104,25 @@ impl_writeable_tlv_based!(HolderCommitmentTransaction, { impl HolderCommitmentTransaction { #[cfg(test)] pub fn dummy(htlcs: &mut Vec<(HTLCOutputInCommitment, ())>) -> Self { + use crate::sign::InMemorySigner; let secp_ctx = Secp256k1::new(); - let dummy_key = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); + let dummy_sec = SecretKey::from_slice(&[42; 32]).unwrap(); + let dummy_key = PublicKey::from_secret_key(&secp_ctx, &dummy_sec); let dummy_sig = sign(&secp_ctx, &secp256k1::Message::from_digest([42; 32]), &SecretKey::from_slice(&[42; 32]).unwrap()); - let keys = TxCreationKeys { - per_commitment_point: dummy_key.clone(), - revocation_key: RevocationKey::from_basepoint(&secp_ctx, &RevocationBasepoint::from(dummy_key), &dummy_key), - broadcaster_htlc_key: HtlcKey::from_basepoint(&secp_ctx, &HtlcBasepoint::from(dummy_key), &dummy_key), - countersignatory_htlc_key: HtlcKey::from_basepoint(&secp_ctx, &HtlcBasepoint::from(dummy_key), &dummy_key), - broadcaster_delayed_payment_key: DelayedPaymentKey::from_basepoint(&secp_ctx, &DelayedPaymentBasepoint::from(dummy_key), &dummy_key), - }; - let channel_pubkeys = ChannelPublicKeys { - funding_pubkey: dummy_key.clone(), - revocation_basepoint: RevocationBasepoint::from(dummy_key), - payment_point: dummy_key.clone(), - delayed_payment_basepoint: DelayedPaymentBasepoint::from(dummy_key.clone()), - htlc_basepoint: HtlcBasepoint::from(dummy_key.clone()) - }; + let mut signer = InMemorySigner::new( + &secp_ctx, + dummy_sec, + dummy_sec, + dummy_sec, + dummy_sec, + dummy_sec, + [42; 32], + 0, + [42; 32], + [42; 32], + ); + let channel_pubkeys = signer.pubkeys(); let channel_parameters = ChannelTransactionParameters { holder_pubkeys: channel_pubkeys.clone(), holder_selected_contest_delay: 0, @@ -1131,11 +1131,13 @@ impl HolderCommitmentTransaction { funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), channel_type_features: ChannelTypeFeatures::only_static_remote_key(), }; + signer.provide_channel_parameters(&channel_parameters); + let keys = TxCreationKeys::from_channel_static_keys(&dummy_key.clone(), &signer.pubkeys(), signer.counterparty_pubkeys().unwrap(), &secp_ctx); let mut counterparty_htlc_sigs = Vec::new(); for _ in 0..htlcs.len() { counterparty_htlc_sigs.push(dummy_sig); } - let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable()); + let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable(), &signer, &secp_ctx, false); htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index); HolderCommitmentTransaction { inner, @@ -1445,12 +1447,12 @@ impl CommitmentTransaction { /// Only include HTLCs that are above the dust limit for the channel. /// /// This is not exported to bindings users due to the generic though we likely should expose a version without - pub fn new_with_auxiliary_htlc_data(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> CommitmentTransaction { + pub fn new_with_auxiliary_htlc_data(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> CommitmentTransaction { let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat); let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); // Sort outputs and populate output indices while keeping track of the auxiliary data - let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key).unwrap(); + let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key, signer, secp_ctx, is_holder_tx).unwrap(); let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); @@ -1479,11 +1481,11 @@ impl CommitmentTransaction { self } - fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result { + fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters); let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect(); - let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key)?; + let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key, signer, secp_ctx, is_holder_tx)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); @@ -1507,21 +1509,14 @@ impl CommitmentTransaction { // - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the // caller needs to have sorted together with the HTLCs so it can keep track of the output index // - building of a bitcoin transaction during a verify() call, in which case T is just () - fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec, Vec), ()> { - let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys(); + fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, _secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result<(Vec, Vec), ()> { let contest_delay = channel_parameters.contest_delay(); - let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); if to_countersignatory_value_sat > Amount::ZERO { - let script = if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() { - get_to_countersignatory_with_anchors_redeemscript(&countersignatory_pubkeys.payment_point).to_p2wsh() - } else { - ScriptBuf::new_p2wpkh(&Hash160::hash(&countersignatory_pubkeys.payment_point.serialize()).into()) - }; txouts.push(( TxOut { - script_pubkey: script.clone(), + script_pubkey: signer.get_counterparty_payment_script(is_holder_tx), value: to_countersignatory_value_sat, }, None, @@ -1680,14 +1675,14 @@ impl CommitmentTransaction { /// /// An external validating signer must call this method before signing /// or using the built transaction. - pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1) -> Result { + pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1, signer: &Signer, is_holder_tx: bool) -> Result { // This is the only field of the key cache that we trust let per_commitment_point = self.keys.per_commitment_point; let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, broadcaster_keys, countersignatory_keys, secp_ctx); if keys != self.keys { return Err(()); } - let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey)?; + let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, signer, secp_ctx, is_holder_tx)?; if self.built.transaction != tx.transaction || self.built.txid != tx.txid { return Err(()); } @@ -1895,7 +1890,7 @@ mod tests { use super::{CounterpartyCommitmentSecrets, ChannelPublicKeys}; use crate::chain; use crate::ln::chan_utils::{get_htlc_redeemscript, get_to_countersignatory_with_anchors_redeemscript, CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment}; - use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1}; + use bitcoin::secp256k1::{self, PublicKey, SecretKey, Secp256k1}; use crate::util::test_utils; use crate::sign::{ChannelSigner, SignerProvider}; use bitcoin::{Network, Txid, ScriptBuf, CompressedPublicKey}; @@ -1904,6 +1899,7 @@ mod tests { use crate::types::payment::PaymentHash; use bitcoin::PublicKey as BitcoinPublicKey; use crate::types::features::ChannelTypeFeatures; + use crate::util::test_channel_signer::TestChannelSigner; #[allow(unused_imports)] use crate::prelude::*; @@ -1917,6 +1913,8 @@ mod tests { htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>, channel_parameters: ChannelTransactionParameters, counterparty_pubkeys: ChannelPublicKeys, + signer: TestChannelSigner, + secp_ctx: Secp256k1::, } impl TestCommitmentTxBuilder { @@ -1925,15 +1923,13 @@ mod tests { let seed = [42; 32]; let network = Network::Testnet; let keys_provider = test_utils::TestKeysInterface::new(&seed, network); - let signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0)); + let mut signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(false, 1_000_000, 0)); let counterparty_signer = keys_provider.derive_channel_signer(3000, keys_provider.generate_channel_keys_id(true, 1_000_000, 1)); - let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint; let per_commitment_secret = SecretKey::from_slice(&>::from_hex("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100").unwrap()[..]).unwrap(); let per_commitment_point = PublicKey::from_secret_key(&secp_ctx, &per_commitment_secret); - let htlc_basepoint = &signer.pubkeys().htlc_basepoint; - let holder_pubkeys = signer.pubkeys(); + let holder_pubkeys = signer.pubkeys().clone(); let counterparty_pubkeys = counterparty_signer.pubkeys().clone(); - let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint); + let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, &holder_pubkeys, &counterparty_pubkeys, &secp_ctx); let channel_parameters = ChannelTransactionParameters { holder_pubkeys: holder_pubkeys.clone(), holder_selected_contest_delay: 0, @@ -1942,6 +1938,7 @@ mod tests { funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }), channel_type_features: ChannelTypeFeatures::only_static_remote_key(), }; + signer.provide_channel_parameters(&channel_parameters); let htlcs_with_aux = Vec::new(); Self { @@ -1953,18 +1950,30 @@ mod tests { htlcs_with_aux, channel_parameters, counterparty_pubkeys, + signer, + secp_ctx, } } fn build(&mut self, to_broadcaster_sats: u64, to_countersignatory_sats: u64) -> CommitmentTransaction { CommitmentTransaction::new_with_auxiliary_htlc_data( - self.commitment_number, to_broadcaster_sats, to_countersignatory_sats, + self.commitment_number, + to_broadcaster_sats, + to_countersignatory_sats, self.holder_funding_pubkey.clone(), self.counterparty_funding_pubkey.clone(), self.keys.clone(), self.feerate_per_kw, - &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable() + &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable(), + &self.signer, + &self.secp_ctx, + true, ) } + + fn overwrite_channel_features(&mut self, channel_type_features: ChannelTypeFeatures) { + self.channel_parameters.channel_type_features = channel_type_features; + self.signer.overwrite_channel_parameters(&self.channel_parameters); + } } #[test] @@ -1977,7 +1986,7 @@ mod tests { assert_eq!(tx.built.transaction.output[1].script_pubkey, bitcoin::address::Address::p2wpkh(&CompressedPublicKey(builder.counterparty_pubkeys.payment_point), Network::Testnet).script_pubkey()); // Generate broadcaster and counterparty outputs as well as two anchors - builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + builder.overwrite_channel_features(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()); let tx = builder.build(1000, 2000); assert_eq!(tx.built.transaction.output.len(), 4); assert_eq!(tx.built.transaction.output[3].script_pubkey, get_to_countersignatory_with_anchors_redeemscript(&builder.counterparty_pubkeys.payment_point).to_p2wsh()); @@ -2007,7 +2016,7 @@ mod tests { }; // Generate broadcaster output and received and offered HTLC outputs, w/o anchors - builder.channel_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key(); + builder.overwrite_channel_features(ChannelTypeFeatures::only_static_remote_key()); builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())]; let tx = builder.build(3000, 0); let keys = &builder.keys.clone(); @@ -2020,7 +2029,7 @@ mod tests { "0020215d61bba56b19e9eadb6107f5a85d7f99c40f65992443f69229c290165bc00d"); // Generate broadcaster output and received and offered HTLC outputs, with anchors - builder.channel_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + builder.overwrite_channel_features(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()); builder.htlcs_with_aux = vec![(received_htlc.clone(), ()), (offered_htlc.clone(), ())]; let tx = builder.build(3000, 0); assert_eq!(tx.built.transaction.output.len(), 5); diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index b092a437fe9..291620c21b7 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1858,8 +1858,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide let funding_txo_script = funding_redeemscript.to_p2wsh(); let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound()); let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); - let mut monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); - monitor_signer.provide_channel_parameters(&context.channel_transaction_parameters); + let monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); let channel_monitor = ChannelMonitor::new(context.secp_ctx.clone(), monitor_signer, shutdown_script, context.get_holder_selected_contest_delay(), &context.destination_script, (funding_txo, funding_txo_script), @@ -3379,7 +3378,10 @@ impl ChannelContext where SP::Target: SignerProvider { keys.clone(), feerate_per_kw, &mut included_non_dust_htlcs, - &channel_parameters + &channel_parameters, + self.holder_signer.as_ecdsa().unwrap(), + &self.secp_ctx, + local, ); let mut htlcs_included = included_non_dust_htlcs; // The unwrap is safe, because all non-dust HTLCs have been assigned an output index @@ -10538,6 +10540,14 @@ mod tests { } } + #[cfg(ldk_test_vectors)] + impl crate::ln::channel::ChannelContext<&Keys> { + fn overwrite_channel_features(&mut self, channel_type_features: ChannelTypeFeatures) { + self.channel_transaction_parameters.channel_type_features = channel_type_features; + self.holder_signer.as_mut_ecdsa().unwrap().overwrite_channel_parameters(&self.channel_transaction_parameters); + } + } + #[cfg(ldk_test_vectors)] fn public_from_secret_hex(secp_ctx: &Secp256k1, hex: &str) -> PublicKey { assert!(cfg!(not(feature = "grind_signatures"))); @@ -11143,7 +11153,7 @@ mod tests { let logger : Arc = Arc::new(test_utils::TestLogger::new()); let secp_ctx = Secp256k1::new(); - let mut signer = InMemorySigner::new( + let signer = InMemorySigner::new( &secp_ctx, SecretKey::from_slice(&>::from_hex("30ff4956bbdd3222d44cc5e8a1261dab1e07957bdac5ae88fe3261ef321f3749").unwrap()[..]).unwrap(), SecretKey::from_slice(&>::from_hex("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(), @@ -11160,7 +11170,7 @@ mod tests { assert_eq!(signer.pubkeys().funding_pubkey.serialize()[..], >::from_hex("023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb").unwrap()[..]); - let keys_provider = Keys { signer: signer.clone() }; + let keys_provider = Keys { signer }; let counterparty_node_id = PublicKey::from_secret_key(&secp_ctx, &SecretKey::from_slice(&[42; 32]).unwrap()); let mut config = UserConfig::default(); @@ -11184,7 +11194,6 @@ mod tests { selected_contest_delay: 144 }); chan.context.channel_transaction_parameters.funding_outpoint = Some(funding_info); - signer.provide_channel_parameters(&chan.context.channel_transaction_parameters); assert_eq!(counterparty_pubkeys.payment_point.serialize()[..], >::from_hex("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991").unwrap()[..]); @@ -11206,14 +11215,14 @@ mod tests { macro_rules! test_commitment { ( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => { - chan.context.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::only_static_remote_key(); + chan.context.overwrite_channel_features(ChannelTypeFeatures::only_static_remote_key()); test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::only_static_remote_key(), $($remain)*); }; } macro_rules! test_commitment_with_anchors { ( $counterparty_sig_hex: expr, $sig_hex: expr, $tx_hex: expr, $($remain:tt)* ) => { - chan.context.channel_transaction_parameters.channel_type_features = ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + chan.context.overwrite_channel_features(ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies()); test_commitment_common!($counterparty_sig_hex, $sig_hex, $tx_hex, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(), $($remain)*); }; } @@ -11256,7 +11265,7 @@ mod tests { &chan.context.holder_signer.as_ref().pubkeys().funding_pubkey, chan.context.counterparty_funding_pubkey() ); - let holder_sig = signer.sign_holder_commitment(&holder_commitment_tx, &secp_ctx).unwrap(); + let holder_sig = chan.context.holder_signer.as_ecdsa().unwrap().sign_holder_commitment(&holder_commitment_tx, &secp_ctx).unwrap(); assert_eq!(Signature::from_der(&>::from_hex($sig_hex).unwrap()[..]).unwrap(), holder_sig, "holder_sig"); let funding_redeemscript = chan.context.get_funding_redeemscript(); @@ -11292,7 +11301,7 @@ mod tests { } let htlc_counterparty_sig = htlc_counterparty_sig_iter.next().unwrap(); - let htlc_holder_sig = signer.sign_holder_htlc_transaction(&htlc_tx, 0, &HTLCDescriptor { + let htlc_holder_sig = chan.context.holder_signer.as_ecdsa().unwrap().sign_holder_htlc_transaction(&htlc_tx, 0, &HTLCDescriptor { channel_derivation_parameters: ChannelDerivationParameters { value_satoshis: chan.context.channel_value_satoshis, keys_id: chan.context.channel_keys_id, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 5a7f870078a..5d0881fdde9 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -769,7 +769,10 @@ fn test_update_fee_that_funder_cannot_afford() { commit_tx_keys.clone(), non_buffer_feerate + 4, &mut htlcs, - &local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable() + &local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable(), + local_chan_signer.as_ecdsa().unwrap(), + &secp_ctx, + false, ); local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), Vec::new(), &secp_ctx).unwrap() }; @@ -1517,7 +1520,10 @@ fn test_fee_spike_violation_fails_htlc() { commit_tx_keys.clone(), feerate_per_kw, &mut vec![(accepted_htlc_info, ())], - &local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable() + &local_chan.context.channel_transaction_parameters.as_counterparty_broadcastable(), + local_chan_signer.as_ecdsa().unwrap(), + &secp_ctx, + false, ); local_chan_signer.as_ecdsa().unwrap().sign_counterparty_commitment(&commitment_tx, Vec::new(), Vec::new(), &secp_ctx).unwrap() }; diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 2370bc95530..18414e9b74d 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -42,8 +42,8 @@ use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::ln::chan_utils; use crate::ln::chan_utils::{ - get_revokeable_redeemscript, make_funding_redeemscript, ChannelPublicKeys, - ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, + get_counterparty_payment_script, get_revokeable_redeemscript, make_funding_redeemscript, + ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment, HolderCommitmentTransaction, }; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; @@ -810,6 +810,24 @@ pub trait ChannelSigner { /// Returns the parameters of this signer fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters>; + + /// Returns the script pubkey that should be placed in the `to_remote` output of commitment + /// transactions. + /// + /// Assumes the signer has already been given the channel parameters via + /// `provide_channel_parameters`. + /// + /// If `is_holder_tx` is set, return the `to_remote` script pubkey for the local party's commitment + /// transaction, otherwise, for the remote party's. + fn get_counterparty_payment_script(&self, is_holder_tx: bool) -> ScriptBuf { + let params = if is_holder_tx { + self.get_channel_parameters().unwrap().as_holder_broadcastable() + } else { + self.get_channel_parameters().unwrap().as_counterparty_broadcastable() + }; + let payment_point = ¶ms.countersignatory_pubkeys().payment_point; + get_counterparty_payment_script(params.channel_type_features(), payment_point) + } } /// Specifies the recipient of an invoice. @@ -1332,6 +1350,14 @@ impl InMemorySigner { witness_script.as_bytes(), ])) } + + #[cfg(test)] + pub(crate) fn overwrite_channel_parameters( + &mut self, channel_parameters: &ChannelTransactionParameters, + ) { + assert!(channel_parameters.is_populated(), "Channel parameters must be fully populated"); + self.channel_parameters = Some(channel_parameters.clone()); + } } impl EntropySource for InMemorySigner { diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 5ad4e301f28..499fd58c4e0 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -161,6 +161,11 @@ impl TestChannelSigner { fn is_signer_available(&self, signer_op: SignerOp) -> bool { !self.get_enforcement_state().disabled_signer_ops.contains(&signer_op) } + + #[cfg(test)] + pub(crate) fn overwrite_channel_parameters(&mut self, channel_parameters: &ChannelTransactionParameters) { + self.inner.overwrite_channel_parameters(channel_parameters) + } } impl ChannelSigner for TestChannelSigner { @@ -418,17 +423,21 @@ impl Writeable for TestChannelSigner { } impl TestChannelSigner { - fn verify_counterparty_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { + fn verify_counterparty_commitment_tx<'a>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify( &self.inner.get_channel_parameters().unwrap().as_counterparty_broadcastable(), - self.inner.counterparty_pubkeys().unwrap(), self.inner.pubkeys(), secp_ctx + self.inner.counterparty_pubkeys().unwrap(), self.inner.pubkeys(), secp_ctx, + self, + false, ).expect("derived different per-tx keys or built transaction") } - fn verify_holder_commitment_tx<'a, T: secp256k1::Signing + secp256k1::Verification>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { + fn verify_holder_commitment_tx<'a>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify( &self.inner.get_channel_parameters().unwrap().as_holder_broadcastable(), - self.inner.pubkeys(), self.inner.counterparty_pubkeys().unwrap(), secp_ctx + self.inner.pubkeys(), self.inner.counterparty_pubkeys().unwrap(), secp_ctx, + self, + true, ).expect("derived different per-tx keys or built transaction") } } From 857ab29b05141b23bcd39fa64a2cdf16a7358261 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 13 Dec 2024 02:15:43 +0000 Subject: [PATCH 03/47] Let `ChannelSigner` set `to_local` script pubkey This allows the `to_local` output to easily be changed according to the features of the channel, or the evolution of the LN specification. `to_local` could even be set to completely arbitrary scripts if compatibility with the formal LN spec is not required. --- lightning/src/chain/channelmonitor.rs | 45 +++++++++++---------------- lightning/src/ln/chan_utils.rs | 14 +++------ lightning/src/sign/mod.rs | 33 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index d152bc6957a..33fe37d48ca 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3047,21 +3047,21 @@ impl ChannelMonitorImpl { // holder commitment transactions. if self.broadcasted_holder_revokable_script.is_some() { let holder_commitment_tx = if self.current_holder_commitment_tx.txid == confirmed_spend_txid { - Some(&self.current_holder_commitment_tx) + Some((&self.current_holder_commitment_tx, false)) } else if let Some(prev_holder_commitment_tx) = &self.prev_holder_signed_commitment_tx { if prev_holder_commitment_tx.txid == confirmed_spend_txid { - Some(prev_holder_commitment_tx) + Some((prev_holder_commitment_tx, true)) } else { None } } else { None }; - if let Some(holder_commitment_tx) = holder_commitment_tx { + if let Some((holder_commitment_tx, is_previous_tx)) = holder_commitment_tx { // Assume that the broadcasted commitment transaction confirmed in the current best // block. Even if not, its a reasonable metric for the bump criteria on the HTLC // transactions. - let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height); + let (claim_reqs, _) = self.get_broadcasted_holder_claims(&holder_commitment_tx, self.best_block.height, is_previous_tx); let conf_target = self.closure_conf_target(); self.onchain_tx_handler.update_claims_view_from_requests(claim_reqs, self.best_block.height, self.best_block.height, broadcaster, conf_target, fee_estimator, logger); } @@ -3100,7 +3100,7 @@ impl ChannelMonitorImpl { // assuming it gets confirmed in the next block. Sadly, we have code which considers // "not yet confirmed" things as discardable, so we cannot do that here. let (mut new_outpoints, _) = self.get_broadcasted_holder_claims( - &self.current_holder_commitment_tx, self.best_block.height, + &self.current_holder_commitment_tx, self.best_block.height, false, ); let unsigned_commitment_tx = self.onchain_tx_handler.get_unsigned_holder_commitment_tx(); let new_outputs = self.get_broadcasted_holder_watch_outputs( @@ -3521,15 +3521,11 @@ impl ChannelMonitorImpl { let secret = self.get_secret(commitment_number).unwrap(); let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret)); let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); - let revocation_pubkey = RevocationKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, &self.holder_revocation_basepoint, &per_commitment_point,); - let delayed_key = DelayedPaymentKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, &self.counterparty_commitment_params.counterparty_delayed_payment_base_key, &PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key)); - - let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key); - let revokeable_p2wsh = revokeable_redeemscript.to_p2wsh(); + let revokeable_spk = self.onchain_tx_handler.signer.get_revokeable_spk(false, commitment_number, &per_commitment_point, &self.onchain_tx_handler.secp_ctx); // First, process non-htlc outputs (to_holder & to_counterparty) for (idx, outp) in tx.output.iter().enumerate() { - if outp.script_pubkey == revokeable_p2wsh { + if outp.script_pubkey == revokeable_spk { 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, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx()); let justice_package = PackageTemplate::build_package( commitment_txid, idx as u32, @@ -3639,16 +3635,9 @@ impl ChannelMonitorImpl { } else { return (claimable_outpoints, to_counterparty_output_info); }; if let Some(transaction) = tx { - let revocation_pubkey = RevocationKey::from_basepoint( - &self.onchain_tx_handler.secp_ctx, &self.holder_revocation_basepoint, &per_commitment_point); - - let delayed_key = DelayedPaymentKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, &self.counterparty_commitment_params.counterparty_delayed_payment_base_key, &per_commitment_point); - - let revokeable_p2wsh = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, - self.counterparty_commitment_params.on_counterparty_tx_csv, - &delayed_key).to_p2wsh(); + let revokeable_spk = self.onchain_tx_handler.signer.get_revokeable_spk(false, commitment_number, &per_commitment_point, &self.onchain_tx_handler.secp_ctx); for (idx, outp) in transaction.output.iter().enumerate() { - if outp.script_pubkey == revokeable_p2wsh { + if outp.script_pubkey == revokeable_spk { to_counterparty_output_info = Some((idx.try_into().expect("Can't have > 2^32 outputs"), outp.value)); } @@ -3738,11 +3727,15 @@ impl ChannelMonitorImpl { // Returns (1) `PackageTemplate`s that can be given to the OnchainTxHandler, so that the handler can // broadcast transactions claiming holder HTLC commitment outputs and (2) a holder revokable // script so we can detect whether a holder transaction has been seen on-chain. - fn get_broadcasted_holder_claims(&self, holder_tx: &HolderSignedTx, conf_height: u32) -> (Vec, Option<(ScriptBuf, PublicKey, RevocationKey)>) { + fn get_broadcasted_holder_claims(&self, holder_tx: &HolderSignedTx, conf_height: u32, is_previous_tx: bool) -> (Vec, Option<(ScriptBuf, PublicKey, RevocationKey)>) { let mut claim_requests = Vec::with_capacity(holder_tx.htlc_outputs.len()); - - let redeemscript = chan_utils::get_revokeable_redeemscript(&holder_tx.revocation_key, self.on_holder_tx_csv, &holder_tx.delayed_payment_key); - let broadcasted_holder_revokable_script = Some((redeemscript.to_p2wsh(), holder_tx.per_commitment_point.clone(), holder_tx.revocation_key.clone())); + let commitment_number = if is_previous_tx { + self.current_holder_commitment_number + 1 + } else { + self.current_holder_commitment_number + }; + let revokeable_spk = self.onchain_tx_handler.signer.get_revokeable_spk(true, commitment_number, &holder_tx.per_commitment_point, &self.onchain_tx_handler.secp_ctx); + let broadcasted_holder_revokable_script = Some((revokeable_spk, holder_tx.per_commitment_point.clone(), holder_tx.revocation_key.clone())); for &(ref htlc, _, _) in holder_tx.htlc_outputs.iter() { if let Some(transaction_output_index) = htlc.transaction_output_index { @@ -3809,7 +3802,7 @@ impl ChannelMonitorImpl { if self.current_holder_commitment_tx.txid == commitment_txid { is_holder_tx = true; log_info!(logger, "Got broadcast of latest holder commitment tx {}, searching for available HTLCs to claim", commitment_txid); - let res = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, height); + let res = self.get_broadcasted_holder_claims(&self.current_holder_commitment_tx, height, false); let mut to_watch = self.get_broadcasted_holder_watch_outputs(&self.current_holder_commitment_tx, tx); append_onchain_update!(res, to_watch); fail_unbroadcast_htlcs!(self, "latest holder", commitment_txid, tx, height, @@ -3819,7 +3812,7 @@ impl ChannelMonitorImpl { if holder_tx.txid == commitment_txid { is_holder_tx = true; log_info!(logger, "Got broadcast of previous holder commitment tx {}, searching for available HTLCs to claim", commitment_txid); - let res = self.get_broadcasted_holder_claims(holder_tx, height); + let res = self.get_broadcasted_holder_claims(holder_tx, height, true); let mut to_watch = self.get_broadcasted_holder_watch_outputs(holder_tx, tx); append_onchain_update!(res, to_watch); fail_unbroadcast_htlcs!(self, "previous holder", commitment_txid, tx, height, block_hash, diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 2ad65c6a11e..376897c667f 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1452,7 +1452,7 @@ impl CommitmentTransaction { let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); // Sort outputs and populate output indices while keeping track of the auxiliary data - let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key, signer, secp_ctx, is_holder_tx).unwrap(); + let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); @@ -1485,7 +1485,7 @@ impl CommitmentTransaction { let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters); let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect(); - let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key, signer, secp_ctx, is_holder_tx)?; + let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key, signer, secp_ctx, is_holder_tx, self.commitment_number)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); @@ -1509,8 +1509,7 @@ impl CommitmentTransaction { // - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the // caller needs to have sorted together with the HTLCs so it can keep track of the output index // - building of a bitcoin transaction during a verify() call, in which case T is just () - fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, _secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result<(Vec, Vec), ()> { - let contest_delay = channel_parameters.contest_delay(); + fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); if to_countersignatory_value_sat > Amount::ZERO { @@ -1524,14 +1523,9 @@ impl CommitmentTransaction { } if to_broadcaster_value_sat > Amount::ZERO { - let redeem_script = get_revokeable_redeemscript( - &keys.revocation_key, - contest_delay, - &keys.broadcaster_delayed_payment_key, - ); txouts.push(( TxOut { - script_pubkey: redeem_script.to_p2wsh(), + script_pubkey: signer.get_revokeable_spk(is_holder_tx, commitment_number, &keys.per_commitment_point, secp_ctx), value: to_broadcaster_value_sat, }, None, diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 18414e9b74d..0831bd80c10 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -65,6 +65,7 @@ use crate::crypto::chacha20::ChaCha20; use crate::io::{self, Error}; use crate::ln::msgs::DecodeError; use crate::prelude::*; +use crate::sign::chan_utils::TxCreationKeys; use crate::sign::ecdsa::EcdsaChannelSigner; #[cfg(taproot)] use crate::sign::taproot::TaprootChannelSigner; @@ -828,6 +829,38 @@ pub trait ChannelSigner { let payment_point = ¶ms.countersignatory_pubkeys().payment_point; get_counterparty_payment_script(params.channel_type_features(), payment_point) } + + /// Returns the script pubkey that should be placed in the `to_local` output of commitment + /// transactions, and in the output of second level HTLC transactions. + /// + /// Assumes the signer has already been given the channel parameters via + /// `provide_channel_parameters`. + /// + /// If `is_holder_tx` is set, return the revokeable script pubkey for local party's + /// commitment / htlc transaction, otherwise, for the remote party's. + fn get_revokeable_spk( + &self, is_holder_tx: bool, _commitment_number: u64, per_commitment_point: &PublicKey, + secp_ctx: &Secp256k1, + ) -> ScriptBuf { + let params = if is_holder_tx { + self.get_channel_parameters().unwrap().as_holder_broadcastable() + } else { + self.get_channel_parameters().unwrap().as_counterparty_broadcastable() + }; + let contest_delay = params.contest_delay(); + let keys = TxCreationKeys::from_channel_static_keys( + per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + get_revokeable_redeemscript( + &keys.revocation_key, + contest_delay, + &keys.broadcaster_delayed_payment_key, + ) + .to_p2wsh() + } } /// Specifies the recipient of an invoice. From 828383899ea19f138e752267b036a02eba670752 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 13 Dec 2024 07:56:27 +0000 Subject: [PATCH 04/47] Let `ChannelSigner` set htlc tx script pubkey This allows the htlc tx output to easily be changed according to the features of the channel, or the evolution of the LN specification. The output could even be set to completely arbitrary scripts if compatibility with the formal LN spec is not required. Builders of htlc transactions now ask a `ChannelSigner` for the appropriate revokeable script pubkey to use, and then pass it to the htlc transaction constructors. --- lightning/src/chain/onchaintx.rs | 8 +++-- lightning/src/events/bump_transaction.rs | 16 ++++++---- lightning/src/ln/chan_utils.rs | 26 +++++++--------- lightning/src/ln/channel.rs | 12 ++++---- lightning/src/ln/monitor_tests.rs | 12 ++++++-- lightning/src/sign/mod.rs | 36 +++++++---------------- lightning/src/util/test_channel_signer.rs | 3 +- 7 files changed, 54 insertions(+), 59 deletions(-) diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index cf7f8d7bfe0..c98121ba1e6 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -1210,8 +1210,9 @@ impl OnchainTxHandler { .find(|(_, htlc)| htlc.transaction_output_index.unwrap() == outp.vout) .unwrap(); let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx]; + let revokeable_spk = self.signer.get_revokeable_spk(true, holder_commitment.commitment_number(), &holder_commitment.per_commitment_point(), &self.secp_ctx); let mut htlc_tx = trusted_tx.build_unsigned_htlc_tx( - &self.channel_transaction_parameters.as_holder_broadcastable(), htlc_idx, preimage, + htlc_idx, preimage, revokeable_spk, ); let htlc_descriptor = HTLCDescriptor { @@ -1295,7 +1296,7 @@ mod tests { }; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint, RevocationBasepoint}; use crate::ln::functional_test_utils::create_dummy_block; - use crate::sign::InMemorySigner; + use crate::sign::{ChannelSigner, InMemorySigner}; use crate::types::payment::{PaymentHash, PaymentPreimage}; use crate::util::test_utils::{TestBroadcaster, TestFeeEstimator, TestLogger}; @@ -1307,7 +1308,7 @@ mod tests { #[test] fn test_broadcast_height() { let secp_ctx = Secp256k1::new(); - let signer = InMemorySigner::new( + let mut signer = InMemorySigner::new( &secp_ctx, SecretKey::from_slice(&[41; 32]).unwrap(), SecretKey::from_slice(&[41; 32]).unwrap(), @@ -1356,6 +1357,7 @@ mod tests { funding_outpoint: Some(funding_outpoint), channel_type_features: ChannelTypeFeatures::only_static_remote_key(), }; + signer.provide_channel_parameters(&chan_params); // Create an OnchainTxHandler for a commitment containing HTLCs with CLTV expiries of 0, 1, // and 2 blocks. diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 3acb2145e5b..e4f8574c74f 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -26,7 +26,7 @@ use crate::ln::chan_utils::{ }; use crate::prelude::*; use crate::sign::{ - ChannelDerivationParameters, HTLCDescriptor, SignerProvider, P2WPKH_WITNESS_WEIGHT + ChannelDerivationParameters, ChannelSigner, HTLCDescriptor, SignerProvider, P2WPKH_WITNESS_WEIGHT, }; use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sync::Mutex; @@ -728,6 +728,7 @@ where output: vec![], }; let mut must_spend = Vec::with_capacity(htlc_descriptors.len()); + let mut signers_and_revokeable_spks = BTreeMap::new(); for htlc_descriptor in htlc_descriptors { let htlc_input = htlc_descriptor.unsigned_tx_input(); must_spend.push(Input { @@ -740,7 +741,13 @@ where }, }); htlc_tx.input.push(htlc_input); - let htlc_output = htlc_descriptor.tx_output(&self.secp); + let revokeable_spk = signers_and_revokeable_spks.entry(htlc_descriptor.channel_derivation_parameters.keys_id) + .or_insert_with(|| { + let signer = htlc_descriptor.derive_channel_signer(&self.signer_provider); + let revokeable_spk = signer.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, &self.secp); + (signer, revokeable_spk) + }).1.clone(); + let htlc_output = htlc_descriptor.tx_output(revokeable_spk); htlc_tx.output.push(htlc_output); } @@ -789,10 +796,9 @@ where log_debug!(self.logger, "Signing HTLC transaction {}", htlc_psbt.unsigned_tx.compute_txid()); htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?; - let mut signers = BTreeMap::new(); for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { - let signer = signers.entry(htlc_descriptor.channel_derivation_parameters.keys_id) - .or_insert_with(|| htlc_descriptor.derive_channel_signer(&self.signer_provider)); + // Unwrap because we derived the corresponding signers for all htlc descriptors further above + let signer = &signers_and_revokeable_spks.get(&htlc_descriptor.channel_derivation_parameters.keys_id).unwrap().0; let htlc_sig = signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?; let witness_script = htlc_descriptor.witness_script(&self.secp); htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script); diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 376897c667f..6a97333bd9c 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -704,13 +704,12 @@ pub(crate) fn make_funding_redeemscript_from_slices(broadcaster_funding_key: &[u /// /// Panics if htlc.transaction_output_index.is_none() (as such HTLCs do not appear in the /// commitment transaction). -pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &DelayedPaymentKey, revocation_key: &RevocationKey) -> Transaction { - let txins= vec![build_htlc_input(commitment_txid, htlc, channel_type_features)]; +pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, revokeable_spk: ScriptBuf) -> Transaction { + let txins = vec![build_htlc_input(commitment_txid, htlc, channel_type_features)]; let mut txouts: Vec = Vec::new(); txouts.push(build_htlc_output( - feerate_per_kw, contest_delay, htlc, channel_type_features, - broadcaster_delayed_payment_key, revocation_key + feerate_per_kw, htlc, channel_type_features, revokeable_spk, )); Transaction { @@ -734,7 +733,7 @@ pub(crate) fn build_htlc_input(commitment_txid: &Txid, htlc: &HTLCOutputInCommit } pub(crate) fn build_htlc_output( - feerate_per_kw: u32, contest_delay: u16, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, broadcaster_delayed_payment_key: &DelayedPaymentKey, revocation_key: &RevocationKey + feerate_per_kw: u32, htlc: &HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures, revokeable_spk: ScriptBuf ) -> TxOut { let weight = if htlc.offered { htlc_timeout_tx_weight(channel_type_features) @@ -749,7 +748,7 @@ pub(crate) fn build_htlc_output( }; TxOut { - script_pubkey: get_revokeable_redeemscript(revocation_key, contest_delay, broadcaster_delayed_payment_key).to_p2wsh(), + script_pubkey: revokeable_spk, value: output_value, } } @@ -1728,8 +1727,7 @@ impl<'a> TrustedCommitmentTransaction<'a> { /// /// This function is only valid in the holder commitment context, it always uses EcdsaSighashType::All. pub fn get_htlc_sigs( - &self, htlc_base_key: &SecretKey, channel_parameters: &DirectedChannelTransactionParameters, - entropy_source: &ES, secp_ctx: &Secp256k1, + &self, htlc_base_key: &SecretKey, entropy_source: &ES, secp_ctx: &Secp256k1, revokeable_spk: ScriptBuf, ) -> Result, ()> where ES::Target: EntropySource { let inner = self.inner; let keys = &inner.keys; @@ -1739,7 +1737,7 @@ impl<'a> TrustedCommitmentTransaction<'a> { for this_htlc in inner.htlcs.iter() { assert!(this_htlc.transaction_output_index.is_some()); - let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key); + let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, &this_htlc, &self.channel_type_features, revokeable_spk.clone()); let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key); @@ -1750,11 +1748,7 @@ impl<'a> TrustedCommitmentTransaction<'a> { } /// Builds the second-level holder HTLC transaction for the HTLC with index `htlc_index`. - pub(crate) fn build_unsigned_htlc_tx( - &self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize, - preimage: &Option, - ) -> Transaction { - let keys = &self.inner.keys; + pub(crate) fn build_unsigned_htlc_tx(&self, htlc_index: usize, preimage: &Option, revokeable_spk: ScriptBuf) -> Transaction { let this_htlc = &self.inner.htlcs[htlc_index]; assert!(this_htlc.transaction_output_index.is_some()); // if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction. @@ -1763,8 +1757,8 @@ impl<'a> TrustedCommitmentTransaction<'a> { if this_htlc.offered && preimage.is_some() { unreachable!(); } build_htlc_transaction( - &self.inner.built.txid, self.inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, - &self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key + &self.inner.built.txid, self.inner.feerate_per_kw, &this_htlc, + &self.channel_type_features, revokeable_spk, ) } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 291620c21b7..a3eca8a8416 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -5325,11 +5325,11 @@ impl FundedChannel where let mut nondust_htlc_sources = Vec::with_capacity(htlcs_cloned.len()); let mut htlcs_and_sigs = Vec::with_capacity(htlcs_cloned.len()); + let revokeable_spk = self.context.holder_signer.as_ref().get_revokeable_spk(true, commitment_stats.tx.commitment_number(), &commitment_stats.tx.per_commitment_point(), &self.context.secp_ctx); for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() { if let Some(_) = htlc.transaction_output_index { let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.feerate_per_kw, - self.context.get_counterparty_selected_contest_delay().unwrap(), &htlc, &self.context.channel_type, - &keys.broadcaster_delayed_payment_key, &keys.revocation_key); + &htlc, &self.context.channel_type, revokeable_spk.clone()); let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &keys); let htlc_sighashtype = if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All }; @@ -8287,6 +8287,7 @@ impl FundedChannel where ).map_err(|_| ChannelError::Ignore("Failed to get signatures for new commitment_signed".to_owned()))?; signature = res.0; htlc_signatures = res.1; + let revokeable_spk = ecdsa.get_revokeable_spk(false, commitment_stats.tx.commitment_number(), &commitment_stats.tx.per_commitment_point(), &self.context.secp_ctx); log_trace!(logger, "Signed remote commitment tx {} (txid {}) with redeemscript {} -> {} in channel {}", encode::serialize_hex(&commitment_stats.tx.trust().built_transaction().transaction), @@ -8295,7 +8296,7 @@ impl FundedChannel where for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) { log_trace!(logger, "Signed remote HTLC tx {} with redeemscript {} with pubkey {} -> {} in channel {}", - encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, self.context.get_holder_selected_contest_delay(), htlc, &self.context.channel_type, &counterparty_keys.broadcaster_delayed_payment_key, &counterparty_keys.revocation_key)), + encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, htlc, &self.context.channel_type, revokeable_spk.clone())), encode::serialize_hex(&chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &counterparty_keys)), log_bytes!(counterparty_keys.broadcaster_htlc_key.to_public_key().serialize()), log_bytes!(htlc_sig.serialize_compact()[..]), &self.context.channel_id()); @@ -11280,9 +11281,8 @@ mod tests { let remote_signature = Signature::from_der(&>::from_hex($counterparty_htlc_sig_hex).unwrap()[..]).unwrap(); let ref htlc = htlcs[$htlc_idx]; - let mut htlc_tx = chan_utils::build_htlc_transaction(&unsigned_tx.txid, chan.context.feerate_per_kw, - chan.context.get_counterparty_selected_contest_delay().unwrap(), - &htlc, $opt_anchors, &keys.broadcaster_delayed_payment_key, &keys.revocation_key); + let revokeable_spk = chan.context.holder_signer.as_ref().get_revokeable_spk(true, holder_commitment_tx.commitment_number(), &holder_commitment_tx.per_commitment_point(), &secp_ctx); + let mut htlc_tx = chan_utils::build_htlc_transaction(&unsigned_tx.txid, chan.context.feerate_per_kw, &htlc, $opt_anchors, revokeable_spk); let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, $opt_anchors, &keys); let htlc_sighashtype = if $opt_anchors.supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All }; let htlc_sighash = Message::from_digest(sighash::SighashCache::new(&htlc_tx).p2wsh_signature_hash(0, &htlc_redeemscript, htlc.to_bitcoin_amount(), htlc_sighashtype).unwrap().as_raw_hash().to_byte_array()); diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 9556e988b4e..22ae543277d 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -9,7 +9,9 @@ //! Further functional tests which test blockchain reorganizations. -use crate::sign::{ecdsa::EcdsaChannelSigner, OutputSpender, SpendableOutputDescriptor}; +use alloc::collections::BTreeMap; + +use crate::sign::{ecdsa::EcdsaChannelSigner, ChannelSigner, OutputSpender, SpendableOutputDescriptor}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance, BalanceSource, ChannelMonitorUpdateStep}; use crate::chain::transaction::OutPoint; use crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight}; @@ -2901,6 +2903,7 @@ fn test_anchors_aggregated_revoked_htlc_tx() { }], }; let mut descriptors = Vec::with_capacity(4); + let mut revokeable_spks = BTreeMap::new(); for event in events { // We don't use the `BumpTransactionEventHandler` here because it does not support // creating one transaction from multiple `HTLCResolution` events. @@ -2909,7 +2912,12 @@ fn test_anchors_aggregated_revoked_htlc_tx() { for htlc_descriptor in &htlc_descriptors { assert!(!htlc_descriptor.htlc.offered); htlc_tx.input.push(htlc_descriptor.unsigned_tx_input()); - htlc_tx.output.push(htlc_descriptor.tx_output(&secp)); + let revokeable_spk = revokeable_spks.entry(htlc_descriptor.channel_derivation_parameters.keys_id) + .or_insert_with(|| { + let signer = htlc_descriptor.derive_channel_signer(&nodes[1].keys_manager); + signer.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, &secp) + }).clone(); + htlc_tx.output.push(htlc_descriptor.tx_output(revokeable_spk)); } descriptors.append(&mut htlc_descriptors); htlc_tx.lock_time = tx_lock_time; diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 0831bd80c10..a5c0fe0a19f 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -631,30 +631,12 @@ impl HTLCDescriptor { /// Returns the delayed output created as a result of spending the HTLC output in the commitment /// transaction. - pub fn tx_output( - &self, secp: &Secp256k1, - ) -> TxOut { - let channel_params = - self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable(); - let broadcaster_keys = channel_params.broadcaster_pubkeys(); - let counterparty_keys = channel_params.countersignatory_pubkeys(); - let broadcaster_delayed_key = DelayedPaymentKey::from_basepoint( - secp, - &broadcaster_keys.delayed_payment_basepoint, - &self.per_commitment_point, - ); - let counterparty_revocation_key = &RevocationKey::from_basepoint( - &secp, - &counterparty_keys.revocation_basepoint, - &self.per_commitment_point, - ); + pub fn tx_output(&self, revokeable_spk: ScriptBuf) -> TxOut { chan_utils::build_htlc_output( self.feerate_per_kw, - channel_params.contest_delay(), &self.htlc, - channel_params.channel_type_features(), - &broadcaster_delayed_key, - &counterparty_revocation_key, + &self.channel_derivation_parameters.transaction_parameters.channel_type_features, + revokeable_spk, ) } @@ -1477,19 +1459,21 @@ impl EcdsaChannelSigner for InMemorySigner { let commitment_txid = built_tx.txid; let mut htlc_sigs = Vec::with_capacity(commitment_tx.htlcs().len()); + let revokeable_spk = self.get_revokeable_spk( + false, + trusted_tx.commitment_number(), + &trusted_tx.per_commitment_point(), + secp_ctx, + ); for htlc in commitment_tx.htlcs() { let channel_parameters = self.get_channel_parameters().expect(MISSING_PARAMS_ERR); - let holder_selected_contest_delay = - self.holder_selected_contest_delay().expect(MISSING_PARAMS_ERR); let chan_type = &channel_parameters.channel_type_features; let htlc_tx = chan_utils::build_htlc_transaction( &commitment_txid, commitment_tx.feerate_per_kw(), - holder_selected_contest_delay, htlc, chan_type, - &keys.broadcaster_delayed_payment_key, - &keys.revocation_key, + revokeable_spk.clone(), ); let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, chan_type, &keys); let htlc_sighashtype = if chan_type.supports_anchors_zero_fee_htlc_tx() { diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 499fd58c4e0..b5eaa1078ec 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -303,7 +303,8 @@ impl EcdsaChannelSigner for TestChannelSigner { } } assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input()); - assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(secp_ctx)); + let revokeable_spk = self.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, secp_ctx); + assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(revokeable_spk)); { let witness_script = htlc_descriptor.witness_script(secp_ctx); let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { From 2e8b3f800eedf84548d3023915436dc70f5241e7 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sun, 15 Dec 2024 18:32:39 +0000 Subject: [PATCH 05/47] Let `ChannelSigner` build the punishment witness All LN-Penalty channel signers need to be able to punish the counterparty in case they broadcast an old state. In this commit, we ask implementers of `ChannelSigner` to produce the full transaction with the given input finalized to punish the corresponding previous output. Consumers of the `ChannelSigner` trait can now be agnostic to the specific scripts used in revokeable outputs. We leave passing to the `ChannelSigner` all the previous `TxOut`'s needed to produce valid schnorr signatures under BIP 341 spending rules to a later patch. --- lightning/src/chain/chainmonitor.rs | 2 +- lightning/src/chain/channelmonitor.rs | 35 +++++-------- lightning/src/chain/package.rs | 10 +--- lightning/src/sign/mod.rs | 60 +++++++++++++++++++++++ lightning/src/util/test_channel_signer.rs | 12 +++++ lightning/src/util/test_utils.rs | 2 +- 6 files changed, 87 insertions(+), 34 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index f1b9f729cb3..299370c90d5 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -96,7 +96,7 @@ use bitcoin::secp256k1::PublicKey; /// provided for bulding transactions for a watchtower: /// [`ChannelMonitor::initial_counterparty_commitment_tx`], /// [`ChannelMonitor::counterparty_commitment_txs_from_update`], -/// [`ChannelMonitor::sign_to_local_justice_tx`], [`TrustedCommitmentTransaction::revokeable_output_index`], +/// [`ChannelMonitor::punish_revokeable_output`], [`TrustedCommitmentTransaction::revokeable_output_index`], /// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]. /// /// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 33fe37d48ca..4fe3dcf3bd3 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -29,7 +29,6 @@ use bitcoin::hashes::Hash; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hash_types::{Txid, BlockHash}; -use bitcoin::ecdsa::Signature as BitcoinSignature; use bitcoin::secp256k1::{self, SecretKey, PublicKey, Secp256k1, ecdsa::Signature}; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; @@ -1675,8 +1674,8 @@ impl ChannelMonitor { /// This is provided so that watchtower clients in the persistence pipeline are able to build /// justice transactions for each counterparty commitment upon each update. It's intended to be /// used within an implementation of [`Persist::update_persisted_channel`], which is provided - /// with a monitor and an update. Once revoked, signing a justice transaction can be done using - /// [`Self::sign_to_local_justice_tx`]. + /// with a monitor and an update. Once revoked, punishing a revokeable output can be done using + /// [`Self::punish_revokeable_output`]. /// /// It is expected that a watchtower client may use this method to retrieve the latest counterparty /// commitment transaction(s), and then hold the necessary data until a later update in which @@ -1692,12 +1691,12 @@ impl ChannelMonitor { self.inner.lock().unwrap().counterparty_commitment_txs_from_update(update) } - /// Wrapper around [`EcdsaChannelSigner::sign_justice_revoked_output`] to make - /// signing the justice transaction easier for implementors of + /// Wrapper around [`ChannelSigner::punish_revokeable_output`] to make + /// punishing a revokeable output easier for implementors of /// [`chain::chainmonitor::Persist`]. On success this method returns the provided transaction - /// signing the input at `input_idx`. This method will only produce a valid signature for + /// finalizing the input at `input_idx`. This method will only produce a valid transaction for /// a transaction spending the `to_local` output of a commitment transaction, i.e. this cannot - /// be used for revoked HTLC outputs. + /// be used for revoked HTLC outputs of a commitment transaction. /// /// `Value` is the value of the output being spent by the input at `input_idx`, committed /// in the BIP 143 signature. @@ -1707,10 +1706,10 @@ impl ChannelMonitor { /// to the commitment transaction being revoked, this will return a signed transaction, but /// the signature will not be valid. /// - /// [`EcdsaChannelSigner::sign_justice_revoked_output`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_justice_revoked_output + /// [`ChannelSigner::punish_revokeable_output`]: crate::sign::ChannelSigner::punish_revokeable_output /// [`Persist`]: crate::chain::chainmonitor::Persist - pub fn sign_to_local_justice_tx(&self, justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64) -> Result { - self.inner.lock().unwrap().sign_to_local_justice_tx(justice_tx, input_idx, value, commitment_number) + pub fn punish_revokeable_output(&self, justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64) -> Result { + self.inner.lock().unwrap().punish_revokeable_output(justice_tx, input_idx, value, commitment_number) } pub(crate) fn get_min_seen_secret(&self) -> u64 { @@ -3449,26 +3448,14 @@ impl ChannelMonitorImpl { }).collect() } - fn sign_to_local_justice_tx( + fn punish_revokeable_output( &self, mut justice_tx: Transaction, input_idx: usize, value: u64, commitment_number: u64 ) -> Result { let secret = self.get_secret(commitment_number).ok_or(())?; let per_commitment_key = SecretKey::from_slice(&secret).map_err(|_| ())?; let their_per_commitment_point = PublicKey::from_secret_key( &self.onchain_tx_handler.secp_ctx, &per_commitment_key); - - let revocation_pubkey = RevocationKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, - &self.holder_revocation_basepoint, &their_per_commitment_point); - let delayed_key = DelayedPaymentKey::from_basepoint(&self.onchain_tx_handler.secp_ctx, - &self.counterparty_commitment_params.counterparty_delayed_payment_base_key, &their_per_commitment_point); - let revokeable_redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, - self.counterparty_commitment_params.on_counterparty_tx_csv, &delayed_key); - - let sig = self.onchain_tx_handler.signer.sign_justice_revoked_output( - &justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx)?; - justice_tx.input[input_idx].witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(sig)); - justice_tx.input[input_idx].witness.push(&[1u8]); - justice_tx.input[input_idx].witness.push(revokeable_redeemscript.as_bytes()); + justice_tx.input[input_idx].witness = self.onchain_tx_handler.signer.punish_revokeable_output(&justice_tx, input_idx, value, &per_commitment_key, &self.onchain_tx_handler.secp_ctx, &their_per_commitment_point)?; Ok(justice_tx) } diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 53bba3a754b..67fe34aa152 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -604,15 +604,9 @@ impl PackageSolvingData { fn finalize_input(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler) -> bool { match self { PackageSolvingData::RevokedOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); - let witness_script = chan_utils::get_revokeable_redeemscript(&chan_keys.revocation_key, outp.on_counterparty_tx_csv, &chan_keys.broadcaster_delayed_payment_key); //TODO: should we panic on signer failure ? - if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_output(&bumped_tx, i, outp.amount.to_sat(), &outp.per_commitment_key, &onchain_handler.secp_ctx) { - let mut ser_sig = sig.serialize_der().to_vec(); - ser_sig.push(EcdsaSighashType::All as u8); - bumped_tx.input[i].witness.push(ser_sig); - bumped_tx.input[i].witness.push(vec!(1)); - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + if let Ok(witness) = onchain_handler.signer.punish_revokeable_output(bumped_tx, i, outp.amount.to_sat(), &outp.per_commitment_key, &onchain_handler.secp_ctx, &outp.per_commitment_point) { + bumped_tx.input[i].witness = witness; } else { return false; } }, PackageSolvingData::RevokedHTLCOutput(ref outp) => { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index a5c0fe0a19f..313cf489548 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -843,6 +843,35 @@ pub trait ChannelSigner { ) .to_p2wsh() } + + /// Finalize the given input in a transaction spending an HTLC transaction output + /// or a commitment transaction `to_local` output when our counterparty broadcasts an old state. + /// + /// A justice transaction may claim multiple outputs at the same time if timelocks are + /// similar, but only the input at index `input` should be finalized here. + /// It may be called multiple times for the same output(s) if a fee-bump is needed with regards + /// to an upcoming timelock expiration. + /// + /// Amount is the value of the output spent by this input, committed to in the BIP 143 signature. + /// + /// `per_commitment_key` is revocation secret which was provided by our counterparty when they + /// revoked the state which they eventually broadcast. It's not a _holder_ secret key and does + /// not allow the spending of any funds by itself (you need our holder `revocation_secret` to do + /// so). + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked + /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked + /// + /// TODO(taproot): pass to the `ChannelSigner` all the `TxOut`'s spent by the justice transaction. + fn punish_revokeable_output( + &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1430,6 +1459,37 @@ impl ChannelSigner for InMemorySigner { fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters> { self.channel_parameters.as_ref() } + + fn punish_revokeable_output( + &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + ) -> Result { + let params = self.channel_parameters.as_ref().unwrap().as_counterparty_broadcastable(); + let contest_delay = params.contest_delay(); + let keys = TxCreationKeys::from_channel_static_keys( + per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + let witness_script = get_revokeable_redeemscript( + &keys.revocation_key, + contest_delay, + &keys.broadcaster_delayed_payment_key, + ); + let sig = EcdsaChannelSigner::sign_justice_revoked_output( + self, + justice_tx, + input, + amount, + per_commitment_key, + secp_ctx, + )?; + let ecdsa_sig = EcdsaSignature::sighash_all(sig); + Ok(Witness::from( + &[ecdsa_sig.serialize().as_ref(), &[1][..], witness_script.as_bytes()][..], + )) + } } const MISSING_PARAMS_ERR: &'static str = diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index b5eaa1078ec..8d256e94f51 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -26,6 +26,7 @@ use bitcoin::transaction::Transaction; use bitcoin::hashes::Hash; use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; +use bitcoin::Witness; use bitcoin::secp256k1; #[cfg(taproot)] @@ -221,6 +222,17 @@ impl ChannelSigner for TestChannelSigner { fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters> { self.inner.get_channel_parameters() } + + fn punish_revokeable_output( + &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + ) -> Result { + #[cfg(test)] + if !self.is_signer_available(SignerOp::SignJusticeRevokedOutput) { + return Err(()); + } + self.inner.punish_revokeable_output(justice_tx, input, amount, per_commitment_key, secp_ctx, per_commitment_point) + } } impl EcdsaChannelSigner for TestChannelSigner { diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs index 07b2b19b0d6..8895337db09 100644 --- a/lightning/src/util/test_utils.rs +++ b/lightning/src/util/test_utils.rs @@ -517,7 +517,7 @@ impl chainmonitor::Persist for while let Some(JusticeTxData { justice_tx, value, commitment_number }) = channel_state.front() { let input_idx = 0; let commitment_txid = justice_tx.input[input_idx].previous_output.txid; - match data.sign_to_local_justice_tx(justice_tx.clone(), input_idx, value.to_sat(), *commitment_number) { + match data.punish_revokeable_output(justice_tx.clone(), input_idx, value.to_sat(), *commitment_number) { Ok(signed_justice_tx) => { let dup = self.watchtower_state.lock().unwrap() .get_mut(&funding_txo).unwrap() From 70b9df6e93965512be151092599e87bcd8a8e181 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 03:33:03 +0000 Subject: [PATCH 06/47] Ask `ChannelSigner` the weight of the punishment witness for the revokeable scripts in the `to_local` and the htlc tx outputs. --- lightning/src/chain/channelmonitor.rs | 6 ++++-- lightning/src/chain/package.rs | 6 +++--- lightning/src/sign/mod.rs | 7 +++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 4fe3dcf3bd3..740cb967e20 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3509,11 +3509,12 @@ impl ChannelMonitorImpl { let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret)); let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); let revokeable_spk = self.onchain_tx_handler.signer.get_revokeable_spk(false, commitment_number, &per_commitment_point, &self.onchain_tx_handler.secp_ctx); + let punishment_witness_weight = self.onchain_tx_handler.signer.get_punishment_witness_weight(); // First, process non-htlc outputs (to_holder & to_counterparty) for (idx, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == revokeable_spk { - 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, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx()); + 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, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx(), punishment_witness_weight); let justice_package = PackageTemplate::build_package( commitment_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), @@ -3674,6 +3675,7 @@ impl ChannelMonitorImpl { Err(_) => return (Vec::new(), None) }; let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); + let punishment_witness_weight = self.onchain_tx_handler.signer.get_punishment_witness_weight(); let htlc_txid = tx.compute_txid(); let mut claimable_outpoints = vec![]; @@ -3695,7 +3697,7 @@ impl ChannelMonitorImpl { 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, - false + false, punishment_witness_weight, ); let justice_package = PackageTemplate::build_package( htlc_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 67fe34aa152..3684c59060f 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -135,13 +135,13 @@ pub(crate) struct RevokedOutput { } impl RevokedOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, counterparty_htlc_base_key: HtlcBasepoint, per_commitment_key: SecretKey, amount: Amount, on_counterparty_tx_csv: u16, is_counterparty_balance_on_anchors: bool) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, counterparty_htlc_base_key: HtlcBasepoint, per_commitment_key: SecretKey, amount: Amount, on_counterparty_tx_csv: u16, is_counterparty_balance_on_anchors: bool, weight: u64) -> Self { RevokedOutput { per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, per_commitment_key, - weight: WEIGHT_REVOKED_OUTPUT, + weight, amount, on_counterparty_tx_csv, is_counterparty_balance_on_anchors: if is_counterparty_balance_on_anchors { Some(()) } else { None } @@ -1363,7 +1363,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let dumb_scalar = SecretKey::from_slice(&>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap(); let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar); - PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), dumb_scalar, Amount::ZERO, 0, $is_counterparty_balance_on_anchors)) + PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), dumb_scalar, Amount::ZERO, 0, $is_counterparty_balance_on_anchors, WEIGHT_REVOKED_OUTPUT)) } } } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 313cf489548..b0b4c360821 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -38,6 +38,7 @@ use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness}; use lightning_invoice::RawBolt11Invoice; +use crate::chain::package::WEIGHT_REVOKED_OUTPUT; use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::ln::chan_utils; @@ -872,6 +873,12 @@ pub trait ChannelSigner { &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, ) -> Result; + + /// Return the total weight of the witness required to spend the justice path of the revokeable + /// output. + fn get_punishment_witness_weight(&self) -> u64 { + WEIGHT_REVOKED_OUTPUT + } } /// Specifies the recipient of an invoice. From 907684b2d5d93e8e7f4ec1cf73f83b86c88a81d7 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 03:54:43 +0000 Subject: [PATCH 07/47] Remove unused `RevokedOutput` fields --- lightning/src/chain/channelmonitor.rs | 8 ++++---- lightning/src/chain/package.rs | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 740cb967e20..6d062d54373 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3514,7 +3514,7 @@ impl ChannelMonitorImpl { // First, process non-htlc outputs (to_holder & to_counterparty) for (idx, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == revokeable_spk { - 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, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx(), punishment_witness_weight); + let revk_outp = RevokedOutput::build(per_commitment_point, per_commitment_key, outp.value, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx(), punishment_witness_weight); let justice_package = PackageTemplate::build_package( commitment_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), @@ -3694,9 +3694,9 @@ impl ChannelMonitorImpl { 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, + per_commitment_point, + per_commitment_key, + tx.output[idx].value, false, punishment_witness_weight, ); let justice_package = PackageTemplate::build_package( diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 3684c59060f..c87cc576388 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -125,25 +125,25 @@ const HIGH_FREQUENCY_BUMP_INTERVAL: u32 = 1; #[derive(Clone, PartialEq, Eq)] pub(crate) struct RevokedOutput { per_commitment_point: PublicKey, - counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, - counterparty_htlc_base_key: HtlcBasepoint, + counterparty_delayed_payment_base_key: Option, + counterparty_htlc_base_key: Option, per_commitment_key: SecretKey, weight: u64, amount: Amount, - on_counterparty_tx_csv: u16, + on_counterparty_tx_csv: Option, is_counterparty_balance_on_anchors: Option<()>, } impl RevokedOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, counterparty_htlc_base_key: HtlcBasepoint, per_commitment_key: SecretKey, amount: Amount, on_counterparty_tx_csv: u16, is_counterparty_balance_on_anchors: bool, weight: u64) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, per_commitment_key: SecretKey, amount: Amount, is_counterparty_balance_on_anchors: bool, weight: u64) -> Self { RevokedOutput { per_commitment_point, - counterparty_delayed_payment_base_key, - counterparty_htlc_base_key, + counterparty_delayed_payment_base_key: None, + counterparty_htlc_base_key: None, per_commitment_key, weight, amount, - on_counterparty_tx_csv, + on_counterparty_tx_csv: None, is_counterparty_balance_on_anchors: if is_counterparty_balance_on_anchors { Some(()) } else { None } } } @@ -151,12 +151,12 @@ impl RevokedOutput { impl_writeable_tlv_based!(RevokedOutput, { (0, per_commitment_point, required), - (2, counterparty_delayed_payment_base_key, required), - (4, counterparty_htlc_base_key, required), + (2, counterparty_delayed_payment_base_key, option), + (4, counterparty_htlc_base_key, option), (6, per_commitment_key, required), (8, weight, required), (10, amount, required), - (12, on_counterparty_tx_csv, required), + (12, on_counterparty_tx_csv, option), (14, is_counterparty_balance_on_anchors, option) }); @@ -1363,7 +1363,7 @@ mod tests { let secp_ctx = Secp256k1::new(); let dumb_scalar = SecretKey::from_slice(&>::from_hex("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap(); let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar); - PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), dumb_scalar, Amount::ZERO, 0, $is_counterparty_balance_on_anchors, WEIGHT_REVOKED_OUTPUT)) + PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, dumb_scalar, Amount::ZERO, $is_counterparty_balance_on_anchors, WEIGHT_REVOKED_OUTPUT)) } } } From 42346630b435c366d77c36276cfe6f6c5296e2a7 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 17:13:22 +0000 Subject: [PATCH 08/47] Let `ChannelSigner` build the htlc punishment witness All LN-Penalty channel signers need to be able to punish the counterparty in case they broadcast an old state. In this commit, we ask implementers of `ChannelSigner` to produce the full transaction with the given input finalized to punish the corresponding previous output. Consumers of the `ChannelSigner` trait can now be agnostic to the specific scripts used in HTLC outputs of commitment transactions. We leave passing to the `ChannelSigner` all the previous `TxOut`'s needed to produce valid schnorr signatures under BIP 341 spending rules to a later patch. --- lightning/src/chain/package.rs | 10 +--- lightning/src/sign/mod.rs | 63 +++++++++++++++++++++++ lightning/src/util/test_channel_signer.rs | 11 ++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index c87cc576388..327d379b91d 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -610,15 +610,9 @@ impl PackageSolvingData { } else { return false; } }, PackageSolvingData::RevokedHTLCOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); - let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); //TODO: should we panic on signer failure ? - if let Ok(sig) = onchain_handler.signer.sign_justice_revoked_htlc(&bumped_tx, i, outp.amount, &outp.per_commitment_key, &outp.htlc, &onchain_handler.secp_ctx) { - let mut ser_sig = sig.serialize_der().to_vec(); - ser_sig.push(EcdsaSighashType::All as u8); - bumped_tx.input[i].witness.push(ser_sig); - bumped_tx.input[i].witness.push(chan_keys.revocation_key.to_public_key().serialize().to_vec()); - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + if let Ok(witness) = onchain_handler.signer.punish_htlc_output(&bumped_tx, i, outp.amount, &outp.per_commitment_key, &onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.htlc) { + bumped_tx.input[i].witness = witness; } else { return false; } }, PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index b0b4c360821..da70ea84b1a 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -879,6 +879,36 @@ pub trait ChannelSigner { fn get_punishment_witness_weight(&self) -> u64 { WEIGHT_REVOKED_OUTPUT } + + /// Finalize the given input in a transaction spending a revoked HTLC output in a commitment + /// transaction when our counterparty broadcasts an old state. + /// + /// A justice transaction may claim multiple outputs at the same time if timelocks are + /// similar, but only the input at index `input` should be finalized here. + /// It may be called multiple times for the same output(s) if a fee-bump is needed with regards + /// to an upcoming timelock expiration. + /// + /// Amount is the value of the output spent by this input, committed to in the BIP 143 signature. + /// + /// `per_commitment_key` is revocation secret which was provided by our counterparty when they + /// revoked the state which they eventually broadcast. It's not a _holder_ secret key and does + /// not allow the spending of any funds by itself (you need our holder `revocation_secret` to do + /// so). + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked + /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked + /// + /// TODO(taproot): pass to the `ChannelSigner` all the `TxOut`'s spent by the justice transaction. + fn punish_htlc_output( + &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + htlc: &HTLCOutputInCommitment, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1497,6 +1527,39 @@ impl ChannelSigner for InMemorySigner { &[ecdsa_sig.serialize().as_ref(), &[1][..], witness_script.as_bytes()][..], )) } + + fn punish_htlc_output( + &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + htlc: &HTLCOutputInCommitment, + ) -> Result { + let params = self.channel_parameters.as_ref().unwrap().as_counterparty_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + let witness_script = + chan_utils::get_htlc_redeemscript(htlc, params.channel_type_features(), &keys); + let sig = EcdsaChannelSigner::sign_justice_revoked_htlc( + self, + justice_tx, + input, + amount, + per_commitment_key, + htlc, + secp_ctx, + )?; + let ecdsa_sig = EcdsaSignature::sighash_all(sig); + Ok(Witness::from( + &[ + ecdsa_sig.serialize().as_ref(), + &keys.revocation_key.to_public_key().serialize()[..], + witness_script.as_bytes(), + ][..], + )) + } } const MISSING_PARAMS_ERR: &'static str = diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 8d256e94f51..571d26a0dbe 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -233,6 +233,17 @@ impl ChannelSigner for TestChannelSigner { } self.inner.punish_revokeable_output(justice_tx, input, amount, per_commitment_key, secp_ctx, per_commitment_point) } + + fn punish_htlc_output( + &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, + ) -> Result { + #[cfg(test)] + if !self.is_signer_available(SignerOp::SignJusticeRevokedHtlc) { + return Err(()); + } + self.inner.punish_htlc_output(justice_tx, input, amount, per_commitment_key, secp_ctx, per_commitment_point, htlc) + } } impl EcdsaChannelSigner for TestChannelSigner { From b0a20d83849e579161e21651ef168d7ed5cb1ce5 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 17:33:02 +0000 Subject: [PATCH 09/47] Remove unused `RevokedHTLCOutput` fields --- lightning/src/chain/channelmonitor.rs | 2 +- lightning/src/chain/package.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 6d062d54373..f7c5bfe1df4 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3535,7 +3535,7 @@ impl ChannelMonitorImpl { // per_commitment_data is corrupt or our commitment signing key leaked! return (claimable_outpoints, to_counterparty_output_info); } - let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), &self.onchain_tx_handler.channel_transaction_parameters.channel_type_features); + let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), &self.onchain_tx_handler.channel_transaction_parameters.channel_type_features); let justice_package = PackageTemplate::build_package( commitment_txid, transaction_output_index, diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 327d379b91d..2d9309cd83b 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -171,8 +171,8 @@ impl_writeable_tlv_based!(RevokedOutput, { #[derive(Clone, PartialEq, Eq)] pub(crate) struct RevokedHTLCOutput { per_commitment_point: PublicKey, - counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, - counterparty_htlc_base_key: HtlcBasepoint, + counterparty_delayed_payment_base_key: Option, + counterparty_htlc_base_key: Option, per_commitment_key: SecretKey, weight: u64, amount: u64, @@ -180,12 +180,12 @@ pub(crate) struct RevokedHTLCOutput { } impl RevokedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, counterparty_htlc_base_key: HtlcBasepoint, per_commitment_key: SecretKey, amount: u64, htlc: HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, per_commitment_key: SecretKey, amount: u64, htlc: HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures) -> Self { let weight = if htlc.offered { weight_revoked_offered_htlc(channel_type_features) } else { weight_revoked_received_htlc(channel_type_features) }; RevokedHTLCOutput { per_commitment_point, - counterparty_delayed_payment_base_key, - counterparty_htlc_base_key, + counterparty_delayed_payment_base_key: None, + counterparty_htlc_base_key: None, per_commitment_key, weight, amount, @@ -196,8 +196,8 @@ impl RevokedHTLCOutput { impl_writeable_tlv_based!(RevokedHTLCOutput, { (0, per_commitment_point, required), - (2, counterparty_delayed_payment_base_key, required), - (4, counterparty_htlc_base_key, required), + (2, counterparty_delayed_payment_base_key, option), + (4, counterparty_htlc_base_key, option), (6, per_commitment_key, required), (8, weight, required), (10, amount, required), @@ -1370,7 +1370,7 @@ mod tests { let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar); let hash = PaymentHash([1; 32]); let htlc = HTLCOutputInCommitment { offered: false, amount_msat: 1_000_000, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), dumb_scalar, 1_000_000 / 1_000, htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies())) + PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput::build(dumb_point, dumb_scalar, 1_000_000 / 1_000, htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies())) } } } From 28683792dabc0d22438920bcfb1b844d7af7c0d4 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 18:05:57 +0000 Subject: [PATCH 10/47] Ask `ChannelSigner` the weight of the htlc punishment witness --- lightning/src/chain/channelmonitor.rs | 3 ++- lightning/src/chain/package.rs | 9 +++++---- lightning/src/sign/mod.rs | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index f7c5bfe1df4..011390b8ccb 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3535,7 +3535,8 @@ impl ChannelMonitorImpl { // per_commitment_data is corrupt or our commitment signing key leaked! return (claimable_outpoints, to_counterparty_output_info); } - let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), &self.onchain_tx_handler.channel_transaction_parameters.channel_type_features); + let htlc_punishment_witness_weight = self.onchain_tx_handler.signer.get_htlc_punishment_witness_weight(htlc.offered); + let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), htlc_punishment_witness_weight); let justice_package = PackageTemplate::build_package( commitment_txid, transaction_output_index, diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 2d9309cd83b..22cdb32192b 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -180,8 +180,7 @@ pub(crate) struct RevokedHTLCOutput { } impl RevokedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, per_commitment_key: SecretKey, amount: u64, htlc: HTLCOutputInCommitment, channel_type_features: &ChannelTypeFeatures) -> Self { - let weight = if htlc.offered { weight_revoked_offered_htlc(channel_type_features) } else { weight_revoked_received_htlc(channel_type_features) }; + pub(crate) fn build(per_commitment_point: PublicKey, per_commitment_key: SecretKey, amount: u64, htlc: HTLCOutputInCommitment, weight: u64) -> Self { RevokedHTLCOutput { per_commitment_point, counterparty_delayed_payment_base_key: None, @@ -1319,7 +1318,7 @@ where #[cfg(test)] mod tests { - use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageTemplate, PackageSolvingData, RevokedHTLCOutput, RevokedOutput, WEIGHT_REVOKED_OUTPUT, weight_offered_htlc, weight_received_htlc}; + use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageTemplate, PackageSolvingData, RevokedHTLCOutput, RevokedOutput, WEIGHT_REVOKED_OUTPUT, weight_offered_htlc, weight_received_htlc, weight_revoked_received_htlc}; use crate::chain::Txid; use crate::ln::chan_utils::HTLCOutputInCommitment; use crate::types::payment::{PaymentPreimage, PaymentHash}; @@ -1370,7 +1369,9 @@ mod tests { let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar); let hash = PaymentHash([1; 32]); let htlc = HTLCOutputInCommitment { offered: false, amount_msat: 1_000_000, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput::build(dumb_point, dumb_scalar, 1_000_000 / 1_000, htlc, &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies())) + let features = &ChannelTypeFeatures::anchors_zero_htlc_fee_and_dependencies(); + let witness_weight = weight_revoked_received_htlc(features); + PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput::build(dumb_point, dumb_scalar, 1_000_000 / 1_000, htlc, witness_weight)) } } } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index da70ea84b1a..5ca1371e5d9 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -38,7 +38,9 @@ use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness}; use lightning_invoice::RawBolt11Invoice; -use crate::chain::package::WEIGHT_REVOKED_OUTPUT; +use crate::chain::package::{ + weight_revoked_offered_htlc, weight_revoked_received_htlc, WEIGHT_REVOKED_OUTPUT, +}; use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::ln::chan_utils; @@ -909,6 +911,17 @@ pub trait ChannelSigner { secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, ) -> Result; + + /// Return the total weight of the witness required to spend the justice path of a HTLC output in a + /// commitment transaction. + fn get_htlc_punishment_witness_weight(&self, offered: bool) -> u64 { + let features = &self.get_channel_parameters().unwrap().channel_type_features; + if offered { + weight_revoked_offered_htlc(features) + } else { + weight_revoked_received_htlc(features) + } + } } /// Specifies the recipient of an invoice. From 77d5e245d06ba68a745cd4c92124406b29dc12ea Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 19:43:26 +0000 Subject: [PATCH 11/47] Let `ChannelSigner` build the counterparty htlc sweep witness --- lightning/src/chain/package.rs | 26 +++------------ lightning/src/sign/mod.rs | 39 +++++++++++++++++++++++ lightning/src/util/test_channel_signer.rs | 12 +++++++ 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 22cdb32192b..74540127629 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -21,11 +21,10 @@ use bitcoin::transaction::OutPoint as BitcoinOutPoint; use bitcoin::script::{Script, ScriptBuf}; use bitcoin::hash_types::Txid; use bitcoin::secp256k1::{SecretKey,PublicKey}; -use bitcoin::sighash::EcdsaSighashType; use bitcoin::transaction::Version; use crate::types::payment::PaymentPreimage; -use crate::ln::chan_utils::{self, TxCreationKeys, HTLCOutputInCommitment}; +use crate::ln::chan_utils::{self, HTLCOutputInCommitment}; use crate::types::features::ChannelTypeFeatures; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint}; use crate::ln::channelmanager::MIN_CLTV_EXPIRY_DELTA; @@ -615,28 +614,13 @@ impl PackageSolvingData { } else { return false; } }, PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); - let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); - - if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(&bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) { - let mut ser_sig = sig.serialize_der().to_vec(); - ser_sig.push(EcdsaSighashType::All as u8); - bumped_tx.input[i].witness.push(ser_sig); - bumped_tx.input[i].witness.push(outp.preimage.0.to_vec()); - bumped_tx.input[i].witness.push(witness_script.clone().into_bytes()); + if let Ok(witness) = onchain_handler.signer.sweep_counterparty_htlc_output(&bumped_tx, i, outp.htlc.amount_msat / 1000, &onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.htlc, Some(&outp.preimage)) { + bumped_tx.input[i].witness = witness; } }, PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => { - let chan_keys = TxCreationKeys::derive_new(&onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.counterparty_delayed_payment_base_key, &outp.counterparty_htlc_base_key, &onchain_handler.signer.pubkeys().revocation_basepoint, &onchain_handler.signer.pubkeys().htlc_basepoint); - let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(&outp.htlc, &onchain_handler.channel_type_features(), &chan_keys.broadcaster_htlc_key, &chan_keys.countersignatory_htlc_key, &chan_keys.revocation_key); - - if let Ok(sig) = onchain_handler.signer.sign_counterparty_htlc_transaction(&bumped_tx, i, &outp.htlc.amount_msat / 1000, &outp.per_commitment_point, &outp.htlc, &onchain_handler.secp_ctx) { - let mut ser_sig = sig.serialize_der().to_vec(); - ser_sig.push(EcdsaSighashType::All as u8); - bumped_tx.input[i].witness.push(ser_sig); - // 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()); + if let Ok(witness) = onchain_handler.signer.sweep_counterparty_htlc_output(&bumped_tx, i, outp.htlc.amount_msat / 1000, &onchain_handler.secp_ctx, &outp.per_commitment_point, &outp.htlc, None) { + bumped_tx.input[i].witness = witness; } }, _ => { panic!("API Error!"); } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 5ca1371e5d9..52d14b33f2c 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -922,6 +922,14 @@ pub trait ChannelSigner { weight_revoked_received_htlc(features) } } + + /// Sweep a HTLC output on a counterparty commitment transaction. Sweep an offered htlc output if + /// the preimage is provided, otherwise, sweep a received htlc output. + fn sweep_counterparty_htlc_output( + &self, sweep_tx: &Transaction, input: usize, amount: u64, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + htlc: &HTLCOutputInCommitment, preimage: Option<&PaymentPreimage>, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1573,6 +1581,37 @@ impl ChannelSigner for InMemorySigner { ][..], )) } + + fn sweep_counterparty_htlc_output( + &self, sweep_tx: &Transaction, input: usize, amount: u64, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + htlc: &HTLCOutputInCommitment, preimage: Option<&PaymentPreimage>, + ) -> Result { + let params = self.channel_parameters.as_ref().unwrap().as_counterparty_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + let witness_script = + chan_utils::get_htlc_redeemscript(htlc, params.channel_type_features(), &keys); + let sig = EcdsaChannelSigner::sign_counterparty_htlc_transaction( + self, + sweep_tx, + input, + amount, + per_commitment_point, + htlc, + secp_ctx, + )?; + let ecdsa_sig = EcdsaSignature::sighash_all(sig); + let element = match preimage { + Some(ref p) => &p.0[..], + None => &[][..], + }; + Ok(Witness::from(&[ecdsa_sig.serialize().as_ref(), element, witness_script.as_bytes()][..])) + } } const MISSING_PARAMS_ERR: &'static str = diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 571d26a0dbe..12c1dbadac5 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -244,6 +244,18 @@ impl ChannelSigner for TestChannelSigner { } self.inner.punish_htlc_output(justice_tx, input, amount, per_commitment_key, secp_ctx, per_commitment_point, htlc) } + + fn sweep_counterparty_htlc_output( + &self, sweep_tx: &Transaction, input: usize, amount: u64, + secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, + htlc: &HTLCOutputInCommitment, preimage: Option<&PaymentPreimage>, + ) -> Result { + #[cfg(test)] + if !self.is_signer_available(SignerOp::SignCounterpartyHtlcTransaction) { + return Err(()); + } + self.inner.sweep_counterparty_htlc_output(sweep_tx, input, amount, secp_ctx, per_commitment_point, htlc, preimage) + } } impl EcdsaChannelSigner for TestChannelSigner { From 563efd56ee09531580504b82b43331e0d2bd4a1e Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 20:04:25 +0000 Subject: [PATCH 12/47] Remove unused `CounterpartyOfferedHTLCOutput` fields --- lightning/src/chain/channelmonitor.rs | 2 -- lightning/src/chain/package.rs | 28 +++++++++++++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 011390b8ccb..61fd69e4c35 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3647,8 +3647,6 @@ impl ChannelMonitorImpl { let counterparty_htlc_outp = if htlc.offered { PackageSolvingData::CounterpartyOfferedHTLCOutput( CounterpartyOfferedHTLCOutput::build(*per_commitment_point, - self.counterparty_commitment_params.counterparty_delayed_payment_base_key, - self.counterparty_commitment_params.counterparty_htlc_base_key, preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.channel_type_features().clone())) } else { PackageSolvingData::CounterpartyReceivedHTLCOutput( diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 74540127629..fcc9771dfea 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -213,19 +213,19 @@ impl_writeable_tlv_based!(RevokedHTLCOutput, { #[derive(Clone, PartialEq, Eq)] pub(crate) struct CounterpartyOfferedHTLCOutput { per_commitment_point: PublicKey, - counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, - counterparty_htlc_base_key: HtlcBasepoint, + counterparty_delayed_payment_base_key: Option, + counterparty_htlc_base_key: Option, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures, } impl CounterpartyOfferedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, counterparty_htlc_base_key: HtlcBasepoint, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self { CounterpartyOfferedHTLCOutput { per_commitment_point, - counterparty_delayed_payment_base_key, - counterparty_htlc_base_key, + counterparty_delayed_payment_base_key: None, + counterparty_htlc_base_key: None, preimage, htlc, channel_type_features, @@ -238,8 +238,8 @@ impl Writeable for CounterpartyOfferedHTLCOutput { let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features); write_tlv_fields!(writer, { (0, self.per_commitment_point, required), - (2, self.counterparty_delayed_payment_base_key, required), - (4, self.counterparty_htlc_base_key, required), + (2, self.counterparty_delayed_payment_base_key, option), + (4, self.counterparty_htlc_base_key, option), (6, self.preimage, required), (8, self.htlc, required), (10, legacy_deserialization_prevention_marker, option), @@ -252,8 +252,8 @@ impl Writeable for CounterpartyOfferedHTLCOutput { impl Readable for CounterpartyOfferedHTLCOutput { fn read(reader: &mut R) -> Result { let mut per_commitment_point = RequiredWrapper(None); - let mut counterparty_delayed_payment_base_key = RequiredWrapper(None); - let mut counterparty_htlc_base_key = RequiredWrapper(None); + let mut counterparty_delayed_payment_base_key = None; + let mut counterparty_htlc_base_key = None; let mut preimage = RequiredWrapper(None); let mut htlc = RequiredWrapper(None); let mut _legacy_deserialization_prevention_marker: Option<()> = None; @@ -261,8 +261,8 @@ impl Readable for CounterpartyOfferedHTLCOutput { read_tlv_fields!(reader, { (0, per_commitment_point, required), - (2, counterparty_delayed_payment_base_key, required), - (4, counterparty_htlc_base_key, required), + (2, counterparty_delayed_payment_base_key, option), + (4, counterparty_htlc_base_key, option), (6, preimage, required), (8, htlc, required), (10, _legacy_deserialization_prevention_marker, option), @@ -273,8 +273,8 @@ impl Readable for CounterpartyOfferedHTLCOutput { Ok(Self { per_commitment_point: per_commitment_point.0.unwrap(), - counterparty_delayed_payment_base_key: counterparty_delayed_payment_base_key.0.unwrap(), - counterparty_htlc_base_key: counterparty_htlc_base_key.0.unwrap(), + counterparty_delayed_payment_base_key, + counterparty_htlc_base_key, preimage: preimage.0.unwrap(), htlc: htlc.0.unwrap(), channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()) @@ -1382,7 +1382,7 @@ mod tests { let hash = PaymentHash([1; 32]); let preimage = PaymentPreimage([2;32]); let htlc = HTLCOutputInCommitment { offered: false, amount_msat: $amt, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), preimage, htlc, $features)) + PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, preimage, htlc, $features)) } } } From b7a37ca9985ff731a78efc1566c1ac2f4daf7fe0 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 20 Dec 2024 20:11:52 +0000 Subject: [PATCH 13/47] Remove unused `CounterpartyReceivedHTLCOutput` fields --- lightning/src/chain/channelmonitor.rs | 2 -- lightning/src/chain/package.rs | 29 +++++++++++++-------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 61fd69e4c35..de26e167950 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3651,8 +3651,6 @@ impl ChannelMonitorImpl { } else { PackageSolvingData::CounterpartyReceivedHTLCOutput( CounterpartyReceivedHTLCOutput::build(*per_commitment_point, - self.counterparty_commitment_params.counterparty_delayed_payment_base_key, - self.counterparty_commitment_params.counterparty_htlc_base_key, htlc.clone(), self.onchain_tx_handler.channel_type_features().clone())) }; let counterparty_package = PackageTemplate::build_package(commitment_txid, transaction_output_index, counterparty_htlc_outp, htlc.cltv_expiry); diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index fcc9771dfea..683f8ee9d60 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -291,18 +291,18 @@ impl Readable for CounterpartyOfferedHTLCOutput { #[derive(Clone, PartialEq, Eq)] pub(crate) struct CounterpartyReceivedHTLCOutput { per_commitment_point: PublicKey, - counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, - counterparty_htlc_base_key: HtlcBasepoint, + counterparty_delayed_payment_base_key: Option, + counterparty_htlc_base_key: Option, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures, } impl CounterpartyReceivedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: DelayedPaymentBasepoint, counterparty_htlc_base_key: HtlcBasepoint, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self { CounterpartyReceivedHTLCOutput { per_commitment_point, - counterparty_delayed_payment_base_key, - counterparty_htlc_base_key, + counterparty_delayed_payment_base_key: None, + counterparty_htlc_base_key: None, htlc, channel_type_features } @@ -314,8 +314,8 @@ impl Writeable for CounterpartyReceivedHTLCOutput { let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features); write_tlv_fields!(writer, { (0, self.per_commitment_point, required), - (2, self.counterparty_delayed_payment_base_key, required), - (4, self.counterparty_htlc_base_key, required), + (2, self.counterparty_delayed_payment_base_key, option), + (4, self.counterparty_htlc_base_key, option), (6, self.htlc, required), (8, legacy_deserialization_prevention_marker, option), (9, self.channel_type_features, required), @@ -327,16 +327,16 @@ impl Writeable for CounterpartyReceivedHTLCOutput { impl Readable for CounterpartyReceivedHTLCOutput { fn read(reader: &mut R) -> Result { let mut per_commitment_point = RequiredWrapper(None); - let mut counterparty_delayed_payment_base_key = RequiredWrapper(None); - let mut counterparty_htlc_base_key = RequiredWrapper(None); + let mut counterparty_delayed_payment_base_key = None; + let mut counterparty_htlc_base_key = None; let mut htlc = RequiredWrapper(None); let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; read_tlv_fields!(reader, { (0, per_commitment_point, required), - (2, counterparty_delayed_payment_base_key, required), - (4, counterparty_htlc_base_key, required), + (2, counterparty_delayed_payment_base_key, option), + (4, counterparty_htlc_base_key, option), (6, htlc, required), (8, _legacy_deserialization_prevention_marker, option), (9, channel_type_features, option), @@ -346,8 +346,8 @@ impl Readable for CounterpartyReceivedHTLCOutput { Ok(Self { per_commitment_point: per_commitment_point.0.unwrap(), - counterparty_delayed_payment_base_key: counterparty_delayed_payment_base_key.0.unwrap(), - counterparty_htlc_base_key: counterparty_htlc_base_key.0.unwrap(), + counterparty_delayed_payment_base_key, + counterparty_htlc_base_key, htlc: htlc.0.unwrap(), channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()) }) @@ -1306,7 +1306,6 @@ mod tests { use crate::chain::Txid; use crate::ln::chan_utils::HTLCOutputInCommitment; use crate::types::payment::{PaymentPreimage, PaymentHash}; - use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint}; use bitcoin::absolute::LockTime; use bitcoin::amount::Amount; @@ -1368,7 +1367,7 @@ mod tests { let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar); let hash = PaymentHash([1; 32]); let htlc = HTLCOutputInCommitment { offered: true, amount_msat: $amt, cltv_expiry: $expiry, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, DelayedPaymentBasepoint::from(dumb_point), HtlcBasepoint::from(dumb_point), htlc, $features)) + PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, htlc, $features)) } } } From 35901b56877dc22ba37d305657561e7867d2fc66 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sun, 22 Dec 2024 16:56:59 +0000 Subject: [PATCH 14/47] Ask `ChannelSigner` the weight of counterparty htlc output sweeps --- lightning/src/chain/channelmonitor.rs | 5 ++-- lightning/src/chain/package.rs | 38 ++++++++++++++++++++------- lightning/src/sign/mod.rs | 13 ++++++++- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index de26e167950..119984a58b1 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3644,14 +3644,15 @@ impl ChannelMonitorImpl { } let preimage = if htlc.offered { if let Some((p, _)) = self.payment_preimages.get(&htlc.payment_hash) { Some(*p) } else { None } } else { None }; if preimage.is_some() || !htlc.offered { + let weight = self.onchain_tx_handler.signer.counterparty_htlc_output_witness_weight(htlc.offered); let counterparty_htlc_outp = if htlc.offered { PackageSolvingData::CounterpartyOfferedHTLCOutput( CounterpartyOfferedHTLCOutput::build(*per_commitment_point, - preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.channel_type_features().clone())) + preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.channel_type_features().clone(), weight)) } else { PackageSolvingData::CounterpartyReceivedHTLCOutput( CounterpartyReceivedHTLCOutput::build(*per_commitment_point, - htlc.clone(), self.onchain_tx_handler.channel_type_features().clone())) + htlc.clone(), self.onchain_tx_handler.channel_type_features().clone(), weight)) }; let counterparty_package = PackageTemplate::build_package(commitment_txid, transaction_output_index, counterparty_htlc_outp, htlc.cltv_expiry); claimable_outpoints.push(counterparty_package); diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 683f8ee9d60..c11fd3d3692 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -218,10 +218,11 @@ pub(crate) struct CounterpartyOfferedHTLCOutput { preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures, + weight: u64, } impl CounterpartyOfferedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures, weight: u64) -> Self { CounterpartyOfferedHTLCOutput { per_commitment_point, counterparty_delayed_payment_base_key: None, @@ -229,6 +230,7 @@ impl CounterpartyOfferedHTLCOutput { preimage, htlc, channel_type_features, + weight, } } } @@ -244,6 +246,7 @@ impl Writeable for CounterpartyOfferedHTLCOutput { (8, self.htlc, required), (10, legacy_deserialization_prevention_marker, option), (11, self.channel_type_features, required), + (12, self.weight, required), }); Ok(()) } @@ -258,6 +261,7 @@ impl Readable for CounterpartyOfferedHTLCOutput { let mut htlc = RequiredWrapper(None); let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; + let mut weight = None; read_tlv_fields!(reader, { (0, per_commitment_point, required), @@ -267,17 +271,22 @@ impl Readable for CounterpartyOfferedHTLCOutput { (8, htlc, required), (10, _legacy_deserialization_prevention_marker, option), (11, channel_type_features, option), + (12, weight, option), }); verify_channel_type_features(&channel_type_features, None)?; + let channel_type_features = channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()); + let weight = weight.unwrap_or(weight_offered_htlc(&channel_type_features)); + Ok(Self { per_commitment_point: per_commitment_point.0.unwrap(), counterparty_delayed_payment_base_key, counterparty_htlc_base_key, preimage: preimage.0.unwrap(), htlc: htlc.0.unwrap(), - channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()) + channel_type_features, + weight, }) } } @@ -295,16 +304,18 @@ pub(crate) struct CounterpartyReceivedHTLCOutput { counterparty_htlc_base_key: Option, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures, + weight: u64, } impl CounterpartyReceivedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, htlc: HTLCOutputInCommitment, channel_type_features: ChannelTypeFeatures, weight: u64) -> Self { CounterpartyReceivedHTLCOutput { per_commitment_point, counterparty_delayed_payment_base_key: None, counterparty_htlc_base_key: None, htlc, - channel_type_features + channel_type_features, + weight, } } } @@ -319,6 +330,7 @@ impl Writeable for CounterpartyReceivedHTLCOutput { (6, self.htlc, required), (8, legacy_deserialization_prevention_marker, option), (9, self.channel_type_features, required), + (10, self.weight, required), }); Ok(()) } @@ -332,6 +344,7 @@ impl Readable for CounterpartyReceivedHTLCOutput { let mut htlc = RequiredWrapper(None); let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; + let mut weight = None; read_tlv_fields!(reader, { (0, per_commitment_point, required), @@ -340,16 +353,21 @@ impl Readable for CounterpartyReceivedHTLCOutput { (6, htlc, required), (8, _legacy_deserialization_prevention_marker, option), (9, channel_type_features, option), + (10, weight, option), }); verify_channel_type_features(&channel_type_features, None)?; + let channel_type_features = channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()); + let weight = weight.unwrap_or(weight_received_htlc(&channel_type_features)); + Ok(Self { per_commitment_point: per_commitment_point.0.unwrap(), counterparty_delayed_payment_base_key, counterparty_htlc_base_key, htlc: htlc.0.unwrap(), - channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()) + channel_type_features, + weight, }) } } @@ -526,8 +544,8 @@ impl PackageSolvingData { 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.channel_type_features) as usize, - PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => weight_received_htlc(&outp.channel_type_features) as usize, + PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => outp.weight as usize, + PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => outp.weight as usize, PackageSolvingData::HolderHTLCOutput(ref outp) => { debug_assert!(outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()); if outp.preimage.is_none() { @@ -1367,7 +1385,8 @@ mod tests { let dumb_point = PublicKey::from_secret_key(&secp_ctx, &dumb_scalar); let hash = PaymentHash([1; 32]); let htlc = HTLCOutputInCommitment { offered: true, amount_msat: $amt, cltv_expiry: $expiry, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, htlc, $features)) + let weight = weight_received_htlc(&$features); + PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, htlc, $features, weight)) } } } @@ -1381,7 +1400,8 @@ mod tests { let hash = PaymentHash([1; 32]); let preimage = PaymentPreimage([2;32]); let htlc = HTLCOutputInCommitment { offered: false, amount_msat: $amt, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, preimage, htlc, $features)) + let weight = weight_offered_htlc(&$features); + PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, preimage, htlc, $features, weight)) } } } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 52d14b33f2c..1e00e85720d 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -39,7 +39,8 @@ use bitcoin::{secp256k1, Psbt, Sequence, Txid, WPubkeyHash, Witness}; use lightning_invoice::RawBolt11Invoice; use crate::chain::package::{ - weight_revoked_offered_htlc, weight_revoked_received_htlc, WEIGHT_REVOKED_OUTPUT, + weight_offered_htlc, weight_received_htlc, weight_revoked_offered_htlc, + weight_revoked_received_htlc, WEIGHT_REVOKED_OUTPUT, }; use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; @@ -930,6 +931,16 @@ pub trait ChannelSigner { secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, preimage: Option<&PaymentPreimage>, ) -> Result; + + /// Weight of the witness that sweeps htlc outputs in counterparty commitment transactions + fn counterparty_htlc_output_witness_weight(&self, offered: bool) -> u64 { + let features = &self.get_channel_parameters().unwrap().channel_type_features; + if offered { + weight_offered_htlc(features) + } else { + weight_received_htlc(features) + } + } } /// Specifies the recipient of an invoice. From b88a2acc4a4ac5f9411aac9534b6d225d6d2c832 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sun, 22 Dec 2024 17:45:57 +0000 Subject: [PATCH 15/47] Let `sign_holder_commitment` build the commitment tx witness --- lightning/src/chain/onchaintx.rs | 8 ++-- lightning/src/chain/package.rs | 4 +- lightning/src/ln/chan_utils.rs | 24 ++++++++++ lightning/src/ln/channel.rs | 7 +-- lightning/src/sign/ecdsa.rs | 26 ++--------- lightning/src/sign/mod.rs | 56 ++++++++++++++++------- lightning/src/util/test_channel_signer.rs | 34 +++++++------- 7 files changed, 92 insertions(+), 67 deletions(-) diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index c98121ba1e6..c2c4466afe1 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -16,7 +16,9 @@ use bitcoin::amount::Amount; use bitcoin::locktime::absolute::LockTime; use bitcoin::transaction::Transaction; use bitcoin::transaction::OutPoint as BitcoinOutPoint; -use bitcoin::script::{Script, ScriptBuf}; +#[cfg(test)] +use bitcoin::script::Script; +use bitcoin::script::ScriptBuf; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hash_types::{Txid, BlockHash}; @@ -1187,9 +1189,9 @@ impl OnchainTxHandler { &self.holder_commitment.trust().built_transaction().transaction } - pub(crate) fn get_maybe_signed_holder_tx(&mut self, funding_redeemscript: &Script) -> MaybeSignedTransaction { + pub(crate) fn get_maybe_signed_holder_tx(&mut self) -> MaybeSignedTransaction { let tx = self.signer.sign_holder_commitment(&self.holder_commitment, &self.secp_ctx) - .map(|sig| self.holder_commitment.add_holder_sig(funding_redeemscript, sig)) + .map(|witness| self.holder_commitment.extract_tx(witness)) .unwrap_or_else(|_| self.get_unsigned_holder_commitment_tx().clone()); MaybeSignedTransaction(tx) } diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index c11fd3d3692..19d92a42955 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -651,8 +651,8 @@ impl PackageSolvingData { debug_assert!(!outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()); onchain_handler.get_maybe_signed_htlc_tx(outpoint, &outp.preimage) } - PackageSolvingData::HolderFundingOutput(ref outp) => { - Some(onchain_handler.get_maybe_signed_holder_tx(&outp.funding_redeemscript)) + PackageSolvingData::HolderFundingOutput(ref _outp) => { + Some(onchain_handler.get_maybe_signed_holder_tx()) } _ => { panic!("API Error!"); } } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 6a97333bd9c..a7372b515f5 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1157,6 +1157,7 @@ impl HolderCommitmentTransaction { } } + #[cfg(test)] pub(crate) fn add_holder_sig(&self, funding_redeemscript: &Script, holder_sig: Signature) -> Transaction { // First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element. let mut tx = self.inner.built.transaction.clone(); @@ -1173,6 +1174,29 @@ impl HolderCommitmentTransaction { tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec()); tx } + + pub(crate) fn finalize_witness(&self, funding_redeemscript: &Script, holder_sig: Signature) -> Witness { + // First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element. + let mut witness = Witness::new(); + witness.push(Vec::new()); + + if self.holder_sig_first { + witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(holder_sig)); + witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(self.counterparty_sig)); + } else { + witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(self.counterparty_sig)); + witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(holder_sig)); + } + + witness.push(funding_redeemscript.to_bytes()); + witness + } + + pub(crate) fn extract_tx(&self, witness: Witness) -> Transaction { + let mut tx = self.inner.built.transaction.clone(); + tx.input[0].witness = witness; + tx + } } /// A pre-built Bitcoin commitment transaction and its txid. diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index a3eca8a8416..d52dac71b83 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11266,11 +11266,8 @@ mod tests { &chan.context.holder_signer.as_ref().pubkeys().funding_pubkey, chan.context.counterparty_funding_pubkey() ); - let holder_sig = chan.context.holder_signer.as_ecdsa().unwrap().sign_holder_commitment(&holder_commitment_tx, &secp_ctx).unwrap(); - assert_eq!(Signature::from_der(&>::from_hex($sig_hex).unwrap()[..]).unwrap(), holder_sig, "holder_sig"); - - let funding_redeemscript = chan.context.get_funding_redeemscript(); - let tx = holder_commitment_tx.add_holder_sig(&funding_redeemscript, holder_sig); + let witness = chan.context.holder_signer.as_ref().sign_holder_commitment(&holder_commitment_tx, &secp_ctx).unwrap(); + let tx = holder_commitment_tx.extract_tx(witness); assert_eq!(serialize(&tx)[..], >::from_hex($tx_hex).unwrap()[..], "tx"); // ((htlc, counterparty_sig), (index, holder_sig)) diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index 2f42d332962..ecdbca9e371 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -6,9 +6,9 @@ use bitcoin::secp256k1; use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; -use crate::ln::chan_utils::{ - ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment, HolderCommitmentTransaction, -}; +#[cfg(test)] +use crate::ln::chan_utils::HolderCommitmentTransaction; +use crate::ln::chan_utils::{ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment}; use crate::ln::msgs::UnsignedChannelAnnouncement; use crate::types::payment::PaymentPreimage; @@ -56,26 +56,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { &self, commitment_tx: &CommitmentTransaction, inbound_htlc_preimages: Vec, outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1, ) -> Result<(Signature, Vec), ()>; - /// Creates a signature for a holder's commitment transaction. - /// - /// This will be called - /// - with a non-revoked `commitment_tx`. - /// - with the latest `commitment_tx` when we initiate a force-close. - /// - /// This may be called multiple times for the same transaction. - /// - /// An external signer implementation should check that the commitment has not been revoked. - /// - /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid - /// signature and should be retried later. Once the signer is ready to provide a signature after - /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its - /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. - /// - /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked - /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked - fn sign_holder_commitment( - &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, - ) -> Result; /// 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 state. Thus, needs its own method as [`sign_holder_commitment`] may enforce that we diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 1e00e85720d..cea614300b9 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -941,6 +941,27 @@ pub trait ChannelSigner { weight_received_htlc(features) } } + + /// Creates a signature for a holder's commitment transaction. + /// + /// This will be called + /// - with a non-revoked `commitment_tx`. + /// - with the latest `commitment_tx` when we initiate a force-close. + /// + /// This may be called multiple times for the same transaction. + /// + /// An external signer implementation should check that the commitment has not been revoked. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked + /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked + fn sign_holder_commitment( + &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1623,6 +1644,24 @@ impl ChannelSigner for InMemorySigner { }; Ok(Witness::from(&[ecdsa_sig.serialize().as_ref(), element, witness_script.as_bytes()][..])) } + + fn sign_holder_commitment( + &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, + ) -> Result { + let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let funding_redeemscript = + make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); + let trusted_tx = commitment_tx.trust(); + let sig = trusted_tx.built_transaction().sign_holder_commitment( + &self.funding_key, + &funding_redeemscript, + self.channel_value_satoshis, + &self, + secp_ctx, + ); + Ok(commitment_tx.finalize_witness(&funding_redeemscript, sig)) + } } const MISSING_PARAMS_ERR: &'static str = @@ -1695,23 +1734,6 @@ impl EcdsaChannelSigner for InMemorySigner { Ok((commitment_sig, htlc_sigs)) } - fn sign_holder_commitment( - &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, - ) -> Result { - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); - let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); - let funding_redeemscript = - make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); - let trusted_tx = commitment_tx.trust(); - Ok(trusted_tx.built_transaction().sign_holder_commitment( - &self.funding_key, - &funding_redeemscript, - self.channel_value_satoshis, - &self, - secp_ctx, - )) - } - #[cfg(any(test, feature = "unsafe_revoked_tx_signing"))] fn unsafe_sign_holder_commitment( &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 12c1dbadac5..9e51e866103 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -256,6 +256,23 @@ impl ChannelSigner for TestChannelSigner { } self.inner.sweep_counterparty_htlc_output(sweep_tx, input, amount, secp_ctx, per_commitment_point, htlc, preimage) } + + fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result { + #[cfg(test)] + if !self.is_signer_available(SignerOp::SignHolderCommitment) { + return Err(()); + } + let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx); + let state = self.state.lock().unwrap(); + let commitment_number = trusted_tx.commitment_number(); + if state.last_holder_revoked_commitment - 1 != commitment_number && state.last_holder_revoked_commitment - 2 != commitment_number { + if !self.disable_revocation_policy_check { + panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}", + state.last_holder_revoked_commitment, commitment_number, self.inner.commitment_seed[0]) + } + } + Ok(self.inner.sign_holder_commitment(commitment_tx, secp_ctx).unwrap()) + } } impl EcdsaChannelSigner for TestChannelSigner { @@ -282,23 +299,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(self.inner.sign_counterparty_commitment(commitment_tx, inbound_htlc_preimages, outbound_htlc_preimages, secp_ctx).unwrap()) } - fn sign_holder_commitment(&self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1) -> Result { - #[cfg(test)] - if !self.is_signer_available(SignerOp::SignHolderCommitment) { - return Err(()); - } - let trusted_tx = self.verify_holder_commitment_tx(commitment_tx, secp_ctx); - let state = self.state.lock().unwrap(); - let commitment_number = trusted_tx.commitment_number(); - if state.last_holder_revoked_commitment - 1 != commitment_number && state.last_holder_revoked_commitment - 2 != commitment_number { - if !self.disable_revocation_policy_check { - panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}", - state.last_holder_revoked_commitment, commitment_number, self.inner.commitment_seed[0]) - } - } - 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()) From 4de22fa7bcfe78a85fe7a97363ef95609a7ebff9 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sun, 22 Dec 2024 18:02:23 +0000 Subject: [PATCH 16/47] Let `unsafe_sign_holder_commitment` build the commitment tx witness --- lightning/src/chain/channelmonitor.rs | 2 +- lightning/src/chain/onchaintx.rs | 8 ++-- lightning/src/ln/chan_utils.rs | 18 --------- lightning/src/sign/ecdsa.rs | 12 ------ lightning/src/sign/mod.rs | 47 ++++++++++++++--------- lightning/src/util/test_channel_signer.rs | 10 ++--- 6 files changed, 38 insertions(+), 59 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 119984a58b1..18f91788bed 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3875,7 +3875,7 @@ impl ChannelMonitorImpl { &mut self, logger: &WithChannelMonitor ) -> Vec where L::Target: Logger { log_debug!(logger, "Getting signed copy of latest holder commitment transaction!"); - let commitment_tx = self.onchain_tx_handler.get_fully_signed_copy_holder_tx(&self.funding_redeemscript); + let commitment_tx = self.onchain_tx_handler.get_fully_signed_copy_holder_tx(); let txid = commitment_tx.compute_txid(); let mut holder_transactions = vec![commitment_tx]; // When anchor outputs are present, the HTLC transactions are only final once the commitment diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index c2c4466afe1..2ae6a8dde54 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -16,8 +16,6 @@ use bitcoin::amount::Amount; use bitcoin::locktime::absolute::LockTime; use bitcoin::transaction::Transaction; use bitcoin::transaction::OutPoint as BitcoinOutPoint; -#[cfg(test)] -use bitcoin::script::Script; use bitcoin::script::ScriptBuf; use bitcoin::hashes::{Hash, HashEngine}; use bitcoin::hashes::sha256::Hash as Sha256; @@ -1197,9 +1195,9 @@ impl OnchainTxHandler { } #[cfg(any(test, feature="unsafe_revoked_tx_signing"))] - pub(crate) fn get_fully_signed_copy_holder_tx(&mut self, funding_redeemscript: &Script) -> Transaction { - let sig = self.signer.unsafe_sign_holder_commitment(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment"); - self.holder_commitment.add_holder_sig(funding_redeemscript, sig) + pub(crate) fn get_fully_signed_copy_holder_tx(&mut self) -> Transaction { + let witness = self.signer.unsafe_sign_holder_commitment(&self.holder_commitment, &self.secp_ctx).expect("sign holder commitment"); + self.holder_commitment.extract_tx(witness) } pub(crate) fn get_maybe_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option) -> Option { diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index a7372b515f5..2953921ca6a 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1157,24 +1157,6 @@ impl HolderCommitmentTransaction { } } - #[cfg(test)] - pub(crate) fn add_holder_sig(&self, funding_redeemscript: &Script, holder_sig: Signature) -> Transaction { - // First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element. - let mut tx = self.inner.built.transaction.clone(); - tx.input[0].witness.push(Vec::new()); - - if self.holder_sig_first { - tx.input[0].witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(holder_sig)); - tx.input[0].witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(self.counterparty_sig)); - } else { - tx.input[0].witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(self.counterparty_sig)); - tx.input[0].witness.push_ecdsa_signature(&BitcoinSignature::sighash_all(holder_sig)); - } - - tx.input[0].witness.push(funding_redeemscript.as_bytes().to_vec()); - tx - } - pub(crate) fn finalize_witness(&self, funding_redeemscript: &Script, holder_sig: Signature) -> Witness { // First push the multisig dummy, note that due to BIP147 (NULLDUMMY) it must be a zero-length element. let mut witness = Witness::new(); diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index ecdbca9e371..9793af77236 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -6,8 +6,6 @@ use bitcoin::secp256k1; use bitcoin::secp256k1::ecdsa::Signature; use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; -#[cfg(test)] -use crate::ln::chan_utils::HolderCommitmentTransaction; use crate::ln::chan_utils::{ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment}; use crate::ln::msgs::UnsignedChannelAnnouncement; use crate::types::payment::PaymentPreimage; @@ -56,16 +54,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { &self, commitment_tx: &CommitmentTransaction, inbound_htlc_preimages: Vec, outbound_htlc_preimages: Vec, 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 state. Thus, needs its own method as [`sign_holder_commitment`] may enforce that we - /// only ever get called once. - /// - /// This method is *not* async as it is intended only for testing purposes. - #[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 the given input in a transaction spending an HTLC transaction output /// or a commitment transaction `to_local` output when our counterparty broadcasts an old state. /// diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index cea614300b9..4b102f05738 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -962,6 +962,16 @@ pub trait ChannelSigner { fn sign_holder_commitment( &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, ) -> Result; + /// 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 state. Thus, needs its own method as [`sign_holder_commitment`] may enforce that we + /// only ever get called once. + /// + /// This method is *not* async as it is intended only for testing purposes. + #[cfg(any(test, feature = "unsafe_revoked_tx_signing"))] + fn unsafe_sign_holder_commitment( + &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1662,6 +1672,25 @@ impl ChannelSigner for InMemorySigner { ); Ok(commitment_tx.finalize_witness(&funding_redeemscript, sig)) } + + #[cfg(any(test, feature = "unsafe_revoked_tx_signing"))] + fn unsafe_sign_holder_commitment( + &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, + ) -> Result { + let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); + let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); + let funding_redeemscript = + make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); + let trusted_tx = commitment_tx.trust(); + let sig = trusted_tx.built_transaction().sign_holder_commitment( + &self.funding_key, + &funding_redeemscript, + self.channel_value_satoshis, + &self, + secp_ctx, + ); + Ok(commitment_tx.finalize_witness(&funding_redeemscript, sig)) + } } const MISSING_PARAMS_ERR: &'static str = @@ -1734,24 +1763,6 @@ impl EcdsaChannelSigner for InMemorySigner { Ok((commitment_sig, htlc_sigs)) } - #[cfg(any(test, feature = "unsafe_revoked_tx_signing"))] - fn unsafe_sign_holder_commitment( - &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, - ) -> Result { - let funding_pubkey = PublicKey::from_secret_key(secp_ctx, &self.funding_key); - let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); - let funding_redeemscript = - make_funding_redeemscript(&funding_pubkey, &counterparty_keys.funding_pubkey); - let trusted_tx = commitment_tx.trust(); - Ok(trusted_tx.built_transaction().sign_holder_commitment( - &self.funding_key, - &funding_redeemscript, - self.channel_value_satoshis, - &self, - secp_ctx, - )) - } - fn sign_justice_revoked_output( &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1, diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 9e51e866103..63c49ec26c2 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -273,6 +273,11 @@ impl ChannelSigner for TestChannelSigner { } 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()) + } } impl EcdsaChannelSigner for TestChannelSigner { @@ -299,11 +304,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(self.inner.sign_counterparty_commitment(commitment_tx, inbound_htlc_preimages, outbound_htlc_preimages, 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_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1) -> Result { #[cfg(test)] if !self.is_signer_available(SignerOp::SignJusticeRevokedOutput) { From 4c96432fe908a87e90b3aea86a2bca0c074d64c1 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sun, 22 Dec 2024 20:00:37 +0000 Subject: [PATCH 17/47] Let `sign_holder_htlc_transaction` build the htlc tx witness --- lightning/src/chain/onchaintx.rs | 6 +- lightning/src/events/bump_transaction.rs | 9 ++- lightning/src/ln/chan_utils.rs | 17 ----- lightning/src/ln/channel.rs | 14 ++-- lightning/src/ln/monitor_tests.rs | 7 +- lightning/src/sign/ecdsa.rs | 23 +----- lightning/src/sign/mod.rs | 81 +++++++++++++-------- lightning/src/sign/taproot.rs | 16 ----- lightning/src/util/test_channel_signer.rs | 86 +++++++++++------------ 9 files changed, 109 insertions(+), 150 deletions(-) diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 2ae6a8dde54..35cd884cd8e 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -1229,10 +1229,8 @@ impl OnchainTxHandler { preimage: preimage.clone(), counterparty_sig: counterparty_htlc_sig.clone(), }; - if let Ok(htlc_sig) = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx) { - htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness( - htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage, - ); + if let Ok(witness) = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx) { + htlc_tx.input[0].witness = witness; } Some(MaybeSignedTransaction(htlc_tx)) }; diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index e4f8574c74f..5e8dbdf007b 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -189,7 +189,7 @@ pub enum BumpTransactionEvent { /// 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 [`EcdsaChannelSigner`] should be re-derived /// through [`HTLCDescriptor::derive_channel_signer`]. Each HTLC input's signature can be - /// computed with [`EcdsaChannelSigner::sign_holder_htlc_transaction`], which can then be + /// computed with [`ChannelSigner::sign_holder_htlc_transaction`], which can then be /// provided to [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required /// to spend. /// @@ -204,7 +204,7 @@ pub enum BumpTransactionEvent { /// to the HTLC transaction is greater in value than the HTLCs being claimed. /// /// [`EcdsaChannelSigner`]: crate::sign::ecdsa::EcdsaChannelSigner - /// [`EcdsaChannelSigner::sign_holder_htlc_transaction`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_holder_htlc_transaction + /// [`ChannelSigner::sign_holder_htlc_transaction`]: crate::sign::ChannelSigner::sign_holder_htlc_transaction HTLCResolution { /// The `channel_id` of the channel which has been closed. channel_id: ChannelId, @@ -799,9 +799,8 @@ where for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() { // Unwrap because we derived the corresponding signers for all htlc descriptors further above let signer = &signers_and_revokeable_spks.get(&htlc_descriptor.channel_derivation_parameters.keys_id).unwrap().0; - let htlc_sig = signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?; - let witness_script = htlc_descriptor.witness_script(&self.secp); - htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script); + let witness = signer.sign_holder_htlc_transaction(&htlc_tx, idx, htlc_descriptor, &self.secp)?; + htlc_tx.input[idx].witness = witness; } #[cfg(debug_assertions)] { diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 2953921ca6a..5a02d2cd105 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1768,23 +1768,6 @@ impl<'a> TrustedCommitmentTransaction<'a> { ) } - - /// Builds the witness required to spend the input for the HTLC with index `htlc_index` in a - /// second-level holder HTLC transaction. - pub(crate) fn build_htlc_input_witness( - &self, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, - preimage: &Option - ) -> Witness { - let keys = &self.inner.keys; - let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys( - &self.inner.htlcs[htlc_index], &self.channel_type_features, &keys.broadcaster_htlc_key, - &keys.countersignatory_htlc_key, &keys.revocation_key - ); - build_htlc_input_witness( - signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features, - ) - } - /// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to /// the broadcaster, in the built transaction, if any exists. /// diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index d52dac71b83..ee86526311b 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11140,7 +11140,7 @@ mod tests { use bitcoin::hash_types::Txid; use bitcoin::hex::DisplayHex; use bitcoin::secp256k1::Message; - use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, ecdsa::EcdsaChannelSigner}; + use crate::sign::{ChannelDerivationParameters, HTLCDescriptor}; use crate::types::payment::PaymentPreimage; use crate::ln::channel::{HTLCOutputInCommitment ,TxCreationKeys}; use crate::ln::channel_keys::{DelayedPaymentBasepoint, HtlcBasepoint}; @@ -11297,8 +11297,12 @@ mod tests { assert!(preimage.is_some()); } + let num_anchors = if $opt_anchors.supports_anchors_zero_fee_htlc_tx() { 2 } else { 0 }; + assert_eq!(htlc.transaction_output_index, Some($htlc_idx + num_anchors), "output index"); + let htlc_counterparty_sig = htlc_counterparty_sig_iter.next().unwrap(); - let htlc_holder_sig = chan.context.holder_signer.as_ecdsa().unwrap().sign_holder_htlc_transaction(&htlc_tx, 0, &HTLCDescriptor { + + htlc_tx.input[0].witness = chan.context.holder_signer.as_ref().sign_holder_htlc_transaction(&htlc_tx, 0, &HTLCDescriptor { channel_derivation_parameters: ChannelDerivationParameters { value_satoshis: chan.context.channel_value_satoshis, keys_id: chan.context.channel_keys_id, @@ -11312,13 +11316,7 @@ mod tests { preimage: preimage.clone(), counterparty_sig: *htlc_counterparty_sig, }, &secp_ctx).unwrap(); - let num_anchors = if $opt_anchors.supports_anchors_zero_fee_htlc_tx() { 2 } else { 0 }; - assert_eq!(htlc.transaction_output_index, Some($htlc_idx + num_anchors), "output index"); - let signature = Signature::from_der(&>::from_hex($htlc_sig_hex).unwrap()[..]).unwrap(); - assert_eq!(signature, htlc_holder_sig, "htlc sig"); - let trusted_tx = holder_commitment_tx.trust(); - htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness($htlc_idx, htlc_counterparty_sig, &htlc_holder_sig, &preimage); log_trace!(logger, "htlc_tx = {}", serialize(&htlc_tx).as_hex()); assert_eq!(serialize(&htlc_tx)[..], >::from_hex($htlc_tx_hex).unwrap()[..], "htlc tx"); })* diff --git a/lightning/src/ln/monitor_tests.rs b/lightning/src/ln/monitor_tests.rs index 22ae543277d..324256b02b1 100644 --- a/lightning/src/ln/monitor_tests.rs +++ b/lightning/src/ln/monitor_tests.rs @@ -11,7 +11,7 @@ use alloc::collections::BTreeMap; -use crate::sign::{ecdsa::EcdsaChannelSigner, ChannelSigner, OutputSpender, SpendableOutputDescriptor}; +use crate::sign::{ChannelSigner, OutputSpender, SpendableOutputDescriptor}; use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance, BalanceSource, ChannelMonitorUpdateStep}; use crate::chain::transaction::OutPoint; use crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight}; @@ -2928,9 +2928,8 @@ fn test_anchors_aggregated_revoked_htlc_tx() { for (idx, htlc_descriptor) in descriptors.into_iter().enumerate() { let htlc_input_idx = idx + 1; let signer = htlc_descriptor.derive_channel_signer(&nodes[1].keys_manager); - let our_sig = signer.sign_holder_htlc_transaction(&htlc_tx, htlc_input_idx, &htlc_descriptor, &secp).unwrap(); - let witness_script = htlc_descriptor.witness_script(&secp); - htlc_tx.input[htlc_input_idx].witness = htlc_descriptor.tx_input_witness(&our_sig, &witness_script); + let witness = signer.sign_holder_htlc_transaction(&htlc_tx, htlc_input_idx, &htlc_descriptor, &secp).unwrap(); + htlc_tx.input[htlc_input_idx].witness = witness; } let fee_utxo_sig = { let witness_script = ScriptBuf::new_p2pkh(&public_key.pubkey_hash()); diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index 9793af77236..ecfb73c234c 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -13,7 +13,7 @@ use crate::types::payment::PaymentPreimage; #[allow(unused_imports)] use crate::prelude::*; -use crate::sign::{ChannelSigner, HTLCDescriptor}; +use crate::sign::ChannelSigner; /// A trait to sign Lightning channel transactions as described in /// [BOLT 3](https://github.com/lightning/bolts/blob/master/03-transactions.md). @@ -110,27 +110,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, ) -> Result; - /// 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 may be called for HTLCs in the penultimate commitment transaction if a - /// [`ChannelMonitor`] [replica](https://github.com/lightningdevkit/rust-lightning/blob/main/GLOSSARY.md#monitor-replicas) - /// broadcasts it before receiving the update for the latest commitment transaction. - /// - /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid - /// signature and should be retried later. Once the signer is ready to provide a signature after - /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its - /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. - /// - /// [`EcdsaSighashType::All`]: bitcoin::sighash::EcdsaSighashType::All - /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor - /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked - /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked - 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. /// diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 4b102f05738..e886673fe3d 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -972,6 +972,27 @@ pub trait ChannelSigner { fn unsafe_sign_holder_commitment( &self, commitment_tx: &HolderCommitmentTransaction, secp_ctx: &Secp256k1, ) -> Result; + /// 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 may be called for HTLCs in the penultimate commitment transaction if a + /// [`ChannelMonitor`] [replica](https://github.com/lightningdevkit/rust-lightning/blob/main/GLOSSARY.md#monitor-replicas) + /// broadcasts it before receiving the update for the latest commitment transaction. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. + /// + /// [`EcdsaSighashType::All`]: bitcoin::sighash::EcdsaSighashType::All + /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked + /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked + fn sign_holder_htlc_transaction( + &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, + secp_ctx: &Secp256k1, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1691,6 +1712,37 @@ impl ChannelSigner for InMemorySigner { ); Ok(commitment_tx.finalize_witness(&funding_redeemscript, sig)) } + + fn sign_holder_htlc_transaction( + &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, + secp_ctx: &Secp256k1, + ) -> Result { + let witness_script = htlc_descriptor.witness_script(secp_ctx); + let sighash = &sighash::SighashCache::new(&*htlc_tx) + .p2wsh_signature_hash( + input, + &witness_script, + htlc_descriptor.htlc.to_bitcoin_amount(), + EcdsaSighashType::All, + ) + .map_err(|_| ())?; + let our_htlc_private_key = chan_utils::derive_private_key( + &secp_ctx, + &htlc_descriptor.per_commitment_point, + &self.htlc_base_key, + ); + let sighash = hash_to_message!(sighash.as_byte_array()); + let sig = sign_with_aux_rand(&secp_ctx, &sighash, &our_htlc_private_key, &self); + + let features = &self.channel_parameters.as_ref().unwrap().channel_type_features; + Ok(chan_utils::build_htlc_input_witness( + &sig, + &htlc_descriptor.counterparty_sig, + &htlc_descriptor.preimage, + &witness_script, + features, + )) + } } const MISSING_PARAMS_ERR: &'static str = @@ -1857,28 +1909,6 @@ impl EcdsaChannelSigner for InMemorySigner { return Ok(sign_with_aux_rand(secp_ctx, &sighash, &revocation_key, &self)); } - fn sign_holder_htlc_transaction( - &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, - secp_ctx: &Secp256k1, - ) -> Result { - let witness_script = htlc_descriptor.witness_script(secp_ctx); - let sighash = &sighash::SighashCache::new(&*htlc_tx) - .p2wsh_signature_hash( - input, - &witness_script, - htlc_descriptor.htlc.to_bitcoin_amount(), - EcdsaSighashType::All, - ) - .map_err(|_| ())?; - let our_htlc_private_key = chan_utils::derive_private_key( - &secp_ctx, - &htlc_descriptor.per_commitment_point, - &self.htlc_base_key, - ); - let sighash = hash_to_message!(sighash.as_byte_array()); - Ok(sign_with_aux_rand(&secp_ctx, &sighash, &our_htlc_private_key, &self)) - } - fn sign_counterparty_htlc_transaction( &self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, @@ -2019,13 +2049,6 @@ impl TaprootChannelSigner for InMemorySigner { todo!() } - fn sign_holder_htlc_transaction( - &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, - secp_ctx: &Secp256k1, - ) -> Result { - todo!() - } - fn sign_counterparty_htlc_transaction( &self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, diff --git a/lightning/src/sign/taproot.rs b/lightning/src/sign/taproot.rs index ebfaad26c85..a3b7e211261 100644 --- a/lightning/src/sign/taproot.rs +++ b/lightning/src/sign/taproot.rs @@ -105,22 +105,6 @@ pub trait TaprootChannelSigner: ChannelSigner { htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, ) -> Result; - /// 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 [`TapSighashType::Default`]. - /// - /// Note that this may be called for HTLCs in the penultimate commitment transaction if a - /// [`ChannelMonitor`] [replica](https://github.com/lightningdevkit/rust-lightning/blob/main/GLOSSARY.md#monitor-replicas) - /// broadcasts it before receiving the update for the latest commitment transaction. - /// - /// - /// [`TapSighashType::Default`]: bitcoin::sighash::TapSighashType::Default - /// [`ChannelMonitor`]: crate::chain::channelmonitor::ChannelMonitor - 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. /// diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 63c49ec26c2..db42cf14e78 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -278,6 +278,47 @@ impl ChannelSigner for TestChannelSigner { 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_htlc_transaction( + &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, + secp_ctx: &Secp256k1 + ) -> Result { + #[cfg(test)] + if !self.is_signer_available(SignerOp::SignHolderHtlcTransaction) { + return Err(()); + } + let state = self.state.lock().unwrap(); + if state.last_holder_revoked_commitment - 1 != htlc_descriptor.per_commitment_number && + state.last_holder_revoked_commitment - 2 != htlc_descriptor.per_commitment_number + { + if !self.disable_revocation_policy_check { + panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}", + state.last_holder_revoked_commitment, htlc_descriptor.per_commitment_number, self.inner.commitment_seed[0]) + } + } + assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input()); + let revokeable_spk = self.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, secp_ctx); + assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(revokeable_spk)); + { + let witness_script = htlc_descriptor.witness_script(secp_ctx); + let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { + EcdsaSighashType::SinglePlusAnyoneCanPay + } else { + EcdsaSighashType::All + }; + let sighash = &sighash::SighashCache::new(&*htlc_tx).p2wsh_signature_hash( + input, &witness_script, htlc_descriptor.htlc.to_bitcoin_amount(), sighash_type + ).unwrap(); + let countersignatory_htlc_key = HtlcKey::from_basepoint( + &secp_ctx, &self.inner.counterparty_pubkeys().unwrap().htlc_basepoint, &htlc_descriptor.per_commitment_point, + ); + + secp_ctx.verify_ecdsa( + &hash_to_message!(sighash.as_byte_array()), &htlc_descriptor.counterparty_sig, &countersignatory_htlc_key.to_public_key() + ).unwrap(); + } + Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap()) + } } impl EcdsaChannelSigner for TestChannelSigner { @@ -320,47 +361,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(EcdsaChannelSigner::sign_justice_revoked_htlc(&self.inner, justice_tx, input, amount, per_commitment_key, htlc, secp_ctx).unwrap()) } - fn sign_holder_htlc_transaction( - &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, - secp_ctx: &Secp256k1 - ) -> Result { - #[cfg(test)] - if !self.is_signer_available(SignerOp::SignHolderHtlcTransaction) { - return Err(()); - } - let state = self.state.lock().unwrap(); - if state.last_holder_revoked_commitment - 1 != htlc_descriptor.per_commitment_number && - state.last_holder_revoked_commitment - 2 != htlc_descriptor.per_commitment_number - { - if !self.disable_revocation_policy_check { - panic!("can only sign the next two unrevoked commitment numbers, revoked={} vs requested={} for {}", - state.last_holder_revoked_commitment, htlc_descriptor.per_commitment_number, self.inner.commitment_seed[0]) - } - } - assert_eq!(htlc_tx.input[input], htlc_descriptor.unsigned_tx_input()); - let revokeable_spk = self.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, secp_ctx); - assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(revokeable_spk)); - { - let witness_script = htlc_descriptor.witness_script(secp_ctx); - let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { - EcdsaSighashType::SinglePlusAnyoneCanPay - } else { - EcdsaSighashType::All - }; - let sighash = &sighash::SighashCache::new(&*htlc_tx).p2wsh_signature_hash( - input, &witness_script, htlc_descriptor.htlc.to_bitcoin_amount(), sighash_type - ).unwrap(); - let countersignatory_htlc_key = HtlcKey::from_basepoint( - &secp_ctx, &self.inner.counterparty_pubkeys().unwrap().htlc_basepoint, &htlc_descriptor.per_commitment_point, - ); - - secp_ctx.verify_ecdsa( - &hash_to_message!(sighash.as_byte_array()), &htlc_descriptor.counterparty_sig, &countersignatory_htlc_key.to_public_key() - ).unwrap(); - } - Ok(EcdsaChannelSigner::sign_holder_htlc_transaction(&self.inner, 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 { #[cfg(test)] if !self.is_signer_available(SignerOp::SignCounterpartyHtlcTransaction) { @@ -430,10 +430,6 @@ impl TaprootChannelSigner for TestChannelSigner { todo!() } - fn sign_holder_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1) -> Result { - todo!() - } - fn sign_counterparty_htlc_transaction(&self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result { todo!() } From 9687da3bec6928f084699087e8bedc95f803305e Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sun, 22 Dec 2024 20:45:31 +0000 Subject: [PATCH 18/47] Relax `EcdsaChannelSigner` to `ChannelSigner` in `mod chain` --- lightning/src/chain/chainmonitor.rs | 68 +++++++++++++-------------- lightning/src/chain/channelmonitor.rs | 30 ++++++------ lightning/src/chain/mod.rs | 6 +-- lightning/src/chain/onchaintx.rs | 14 +++--- lightning/src/chain/package.rs | 12 ++--- lightning/src/util/persist.rs | 27 ++++------- 6 files changed, 75 insertions(+), 82 deletions(-) diff --git a/lightning/src/chain/chainmonitor.rs b/lightning/src/chain/chainmonitor.rs index 299370c90d5..f5ede9a6441 100644 --- a/lightning/src/chain/chainmonitor.rs +++ b/lightning/src/chain/chainmonitor.rs @@ -32,7 +32,7 @@ use crate::chain::chaininterface::{BroadcasterInterface, FeeEstimator}; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Balance, MonitorEvent, TransactionOutputs, WithChannelMonitor}; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::ln::types::ChannelId; -use crate::sign::ecdsa::EcdsaChannelSigner; +use crate::sign::ChannelSigner; use crate::events::{self, Event, EventHandler, ReplayEvent}; use crate::util::logger::{Logger, WithContext}; use crate::util::errors::APIError; @@ -101,7 +101,7 @@ use bitcoin::secp256k1::PublicKey; /// /// [`TrustedCommitmentTransaction::revokeable_output_index`]: crate::ln::chan_utils::TrustedCommitmentTransaction::revokeable_output_index /// [`TrustedCommitmentTransaction::build_to_local_justice_tx`]: crate::ln::chan_utils::TrustedCommitmentTransaction::build_to_local_justice_tx -pub trait Persist { +pub trait Persist { /// Persist a new channel's data in response to a [`chain::Watch::watch_channel`] call. This is /// called by [`ChannelManager`] for new channels, or may be called directly, e.g. on startup. /// @@ -118,7 +118,7 @@ pub trait Persist { /// /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [`Writeable::write`]: crate::util::ser::Writeable::write - fn persist_new_channel(&self, channel_funding_outpoint: OutPoint, monitor: &ChannelMonitor) -> ChannelMonitorUpdateStatus; + fn persist_new_channel(&self, channel_funding_outpoint: OutPoint, monitor: &ChannelMonitor) -> ChannelMonitorUpdateStatus; /// Update one channel's data. The provided [`ChannelMonitor`] has already applied the given /// update. @@ -157,7 +157,7 @@ pub trait Persist { /// [`ChannelMonitorUpdateStatus`] for requirements when returning errors. /// /// [`Writeable::write`]: crate::util::ser::Writeable::write - fn update_persisted_channel(&self, channel_funding_outpoint: OutPoint, monitor_update: Option<&ChannelMonitorUpdate>, monitor: &ChannelMonitor) -> ChannelMonitorUpdateStatus; + fn update_persisted_channel(&self, channel_funding_outpoint: OutPoint, monitor_update: Option<&ChannelMonitorUpdate>, monitor: &ChannelMonitor) -> ChannelMonitorUpdateStatus; /// Prevents the channel monitor from being loaded on startup. /// /// Archiving the data in a backup location (rather than deleting it fully) is useful for @@ -172,8 +172,8 @@ pub trait Persist { fn archive_persisted_channel(&self, channel_funding_outpoint: OutPoint); } -struct MonitorHolder { - monitor: ChannelMonitor, +struct MonitorHolder { + monitor: ChannelMonitor, /// The full set of pending monitor updates for this Channel. /// /// Note that this lock must be held from [`ChannelMonitor::update_monitor`] through to @@ -191,7 +191,7 @@ struct MonitorHolder { pending_monitor_updates: Mutex>, } -impl MonitorHolder { +impl MonitorHolder { fn has_pending_updates(&self, pending_monitor_updates_lock: &MutexGuard>) -> bool { !pending_monitor_updates_lock.is_empty() } @@ -201,14 +201,14 @@ impl MonitorHolder { /// /// Note that this holds a mutex in [`ChainMonitor`] and may block other events until it is /// released. -pub struct LockedChannelMonitor<'a, ChannelSigner: EcdsaChannelSigner> { - lock: RwLockReadGuard<'a, HashMap>>, +pub struct LockedChannelMonitor<'a, Signer: ChannelSigner> { + lock: RwLockReadGuard<'a, HashMap>>, funding_txo: OutPoint, } -impl Deref for LockedChannelMonitor<'_, ChannelSigner> { - type Target = ChannelMonitor; - fn deref(&self) -> &ChannelMonitor { +impl Deref for LockedChannelMonitor<'_, Signer> { + type Target = ChannelMonitor; + fn deref(&self) -> &ChannelMonitor { &self.lock.get(&self.funding_txo).expect("Checked at construction").monitor } } @@ -229,14 +229,14 @@ impl Deref for LockedChannelMonitor<'_, Chann /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager /// [module-level documentation]: crate::chain::chainmonitor /// [`rebroadcast_pending_claims`]: Self::rebroadcast_pending_claims -pub struct ChainMonitor +pub struct ChainMonitor where C::Target: chain::Filter, T::Target: BroadcasterInterface, F::Target: FeeEstimator, L::Target: Logger, - P::Target: Persist, + P::Target: Persist, { - monitors: RwLock>>, + monitors: RwLock>>, chain_source: Option, broadcaster: T, logger: L, @@ -253,12 +253,12 @@ pub struct ChainMonitor ChainMonitor +impl ChainMonitor where C::Target: chain::Filter, T::Target: BroadcasterInterface, F::Target: FeeEstimator, L::Target: Logger, - P::Target: Persist, + P::Target: Persist, { /// Dispatches to per-channel monitors, which are responsible for updating their on-chain view /// of a channel and reacting accordingly based on transactions in the given chain data. See @@ -273,7 +273,7 @@ where C::Target: chain::Filter, /// Calls which represent a new blockchain tip height should set `best_height`. fn process_chain_data(&self, header: &Header, best_height: Option, txdata: &TransactionData, process: FN) where - FN: Fn(&ChannelMonitor, &TransactionData) -> Vec + FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { let err_str = "ChannelMonitor[Update] persistence failed unrecoverably. This indicates we cannot continue normal operation and must shut down."; let funding_outpoints = hash_set_from_iter(self.monitors.read().unwrap().keys().cloned()); @@ -316,8 +316,8 @@ where C::Target: chain::Filter, fn update_monitor_with_chain_data( &self, header: &Header, best_height: Option, txdata: &TransactionData, process: FN, funding_outpoint: &OutPoint, - monitor_state: &MonitorHolder, channel_count: usize, - ) -> Result<(), ()> where FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { + monitor_state: &MonitorHolder, channel_count: usize, + ) -> Result<(), ()> where FN: Fn(&ChannelMonitor, &TransactionData) -> Vec { let monitor = &monitor_state.monitor; let logger = WithChannelMonitor::from(&self.logger, &monitor, None); @@ -428,7 +428,7 @@ where C::Target: chain::Filter, /// /// Note that the result holds a mutex over our monitor set, and should not be held /// indefinitely. - pub fn get_monitor(&self, funding_txo: OutPoint) -> Result, ()> { + pub fn get_monitor(&self, funding_txo: OutPoint) -> Result, ()> { let lock = self.monitors.read().unwrap(); if lock.get(&funding_txo).is_some() { Ok(LockedChannelMonitor { lock, funding_txo }) @@ -472,7 +472,7 @@ where C::Target: chain::Filter, #[cfg(test)] - pub fn remove_monitor(&self, funding_txo: &OutPoint) -> ChannelMonitor { + pub fn remove_monitor(&self, funding_txo: &OutPoint) -> ChannelMonitor { self.monitors.write().unwrap().remove(funding_txo).unwrap().monitor } @@ -670,14 +670,14 @@ where C::Target: chain::Filter, } } -impl -chain::Listen for ChainMonitor +impl +chain::Listen for ChainMonitor where C::Target: chain::Filter, T::Target: BroadcasterInterface, F::Target: FeeEstimator, L::Target: Logger, - P::Target: Persist, + P::Target: Persist, { fn filtered_block_connected(&self, header: &Header, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "New best block {} at height {} provided via block_connected", header.block_hash(), height); @@ -699,14 +699,14 @@ where } } -impl -chain::Confirm for ChainMonitor +impl +chain::Confirm for ChainMonitor where C::Target: chain::Filter, T::Target: BroadcasterInterface, F::Target: FeeEstimator, L::Target: Logger, - P::Target: Persist, + P::Target: Persist, { fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData, height: u32) { log_debug!(self.logger, "{} provided transactions confirmed at height {} in block {}", txdata.len(), height, header.block_hash()); @@ -753,15 +753,15 @@ where } } -impl -chain::Watch for ChainMonitor +impl +chain::Watch for ChainMonitor where C::Target: chain::Filter, T::Target: BroadcasterInterface, F::Target: FeeEstimator, L::Target: Logger, - P::Target: Persist, + P::Target: Persist, { - fn watch_channel(&self, funding_outpoint: OutPoint, monitor: ChannelMonitor) -> Result { + fn watch_channel(&self, funding_outpoint: OutPoint, monitor: ChannelMonitor) -> Result { let logger = WithChannelMonitor::from(&self.logger, &monitor, None); let mut monitors = self.monitors.write().unwrap(); let entry = match monitors.entry(funding_outpoint) { @@ -892,12 +892,12 @@ where C::Target: chain::Filter, } } -impl events::EventsProvider for ChainMonitor +impl events::EventsProvider for ChainMonitor where C::Target: chain::Filter, T::Target: BroadcasterInterface, F::Target: FeeEstimator, L::Target: Logger, - P::Target: Persist, + P::Target: Persist, { /// Processes [`SpendableOutputs`] events produced from each [`ChannelMonitor`] upon maturity. /// diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 18f91788bed..b5dca0426c9 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -42,7 +42,7 @@ use crate::chain; use crate::chain::{BestBlock, WatchedOutput}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; use crate::chain::transaction::{OutPoint, TransactionData}; -use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, ecdsa::EcdsaChannelSigner, SignerProvider, EntropySource}; +use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, SpendableOutputDescriptor, StaticPaymentOutputDescriptor, DelayedPaymentOutputDescriptor, ChannelSigner, SignerProvider, EntropySource}; use crate::chain::onchaintx::{ClaimEvent, FeerateStrategy, OnchainTxHandler}; use crate::chain::package::{CounterpartyOfferedHTLCOutput, CounterpartyReceivedHTLCOutput, HolderFundingOutput, HolderHTLCOutput, PackageSolvingData, PackageTemplate, RevokedOutput, RevokedHTLCOutput}; use crate::chain::Filter; @@ -846,14 +846,14 @@ impl Readable for IrrevocablyResolvedHTLC { /// the "reorg path" (ie disconnecting blocks until you find a common ancestor from both the /// returned block hash and the the current chain and then reconnecting blocks to get to the /// best chain) upon deserializing the object! -pub struct ChannelMonitor { +pub struct ChannelMonitor { #[cfg(test)] pub(crate) inner: Mutex>, #[cfg(not(test))] pub(super) inner: Mutex>, } -impl Clone for ChannelMonitor where Signer: Clone { +impl Clone for ChannelMonitor where Signer: Clone { fn clone(&self) -> Self { let inner = self.inner.lock().unwrap().clone(); ChannelMonitor::from_impl(inner) @@ -861,7 +861,7 @@ impl Clone for ChannelMonitor where Signer: } #[derive(Clone, PartialEq)] -pub(crate) struct ChannelMonitorImpl { +pub(crate) struct ChannelMonitorImpl { latest_update_id: u64, commitment_transaction_number_obscure_factor: u64, @@ -1027,7 +1027,7 @@ pub(crate) struct ChannelMonitorImpl { /// Transaction outputs to watch for on-chain spends. pub type TransactionOutputs = (Txid, Vec<(u32, TxOut)>); -impl PartialEq for ChannelMonitor where Signer: PartialEq { +impl PartialEq for ChannelMonitor where Signer: PartialEq { fn eq(&self, other: &Self) -> bool { // We need some kind of total lockorder. Absent a better idea, we sort by position in // memory and take locks in that order (assuming that we can't move within memory while a @@ -1039,7 +1039,7 @@ impl PartialEq for ChannelMonitor where Sign } } -impl Writeable for ChannelMonitor { +impl Writeable for ChannelMonitor { fn write(&self, writer: &mut W) -> Result<(), Error> { self.inner.lock().unwrap().write(writer) } @@ -1049,7 +1049,7 @@ impl Writeable for ChannelMonitor { const SERIALIZATION_VERSION: u8 = 1; const MIN_SERIALIZATION_VERSION: u8 = 1; -impl Writeable for ChannelMonitorImpl { +impl Writeable for ChannelMonitorImpl { fn write(&self, writer: &mut W) -> Result<(), Error> { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); @@ -1316,11 +1316,11 @@ impl<'a, L: Deref> Logger for WithChannelMonitor<'a, L> where L::Target: Logger } impl<'a, L: Deref> WithChannelMonitor<'a, L> where L::Target: Logger { - pub(crate) fn from(logger: &'a L, monitor: &ChannelMonitor, payment_hash: Option) -> Self { + pub(crate) fn from(logger: &'a L, monitor: &ChannelMonitor, payment_hash: Option) -> Self { Self::from_impl(logger, &*monitor.inner.lock().unwrap(), payment_hash) } - pub(crate) fn from_impl(logger: &'a L, monitor_impl: &ChannelMonitorImpl, payment_hash: Option) -> Self { + pub(crate) fn from_impl(logger: &'a L, monitor_impl: &ChannelMonitorImpl, payment_hash: Option) -> Self { let peer_id = monitor_impl.counterparty_node_id; let channel_id = Some(monitor_impl.channel_id()); WithChannelMonitor { @@ -1329,7 +1329,7 @@ impl<'a, L: Deref> WithChannelMonitor<'a, L> where L::Target: Logger { } } -impl ChannelMonitor { +impl ChannelMonitor { /// For lockorder enforcement purposes, we need to have a single site which constructs the /// `inner` mutex, otherwise cases where we lock two monitors at the same time (eg in our /// PartialEq implementation) we may decide a lockorder violation has occurred. @@ -2085,7 +2085,7 @@ impl ChannelMonitor { } } -impl ChannelMonitorImpl { +impl ChannelMonitorImpl { /// Helper for get_claimable_balances which does the work for an individual HTLC, generating up /// to one `Balance` for the HTLC. fn get_htlc_balance(&self, htlc: &HTLCOutputInCommitment, source: Option<&HTLCSource>, @@ -2276,7 +2276,7 @@ impl ChannelMonitorImpl { } } -impl ChannelMonitor { +impl ChannelMonitor { /// Gets the balances in this channel which are either claimable by us if we were to /// force-close the channel now or which are claimable on-chain (possibly awaiting /// confirmation). @@ -2736,7 +2736,7 @@ pub fn deliberately_bogus_accepted_htlc_witness() -> Vec> { vec![Vec::new(), Vec::new(), Vec::new(), Vec::new(), deliberately_bogus_accepted_htlc_witness_program().into()].into() } -impl ChannelMonitorImpl { +impl ChannelMonitorImpl { /// Gets the [`ConfirmationTarget`] we should use when selecting feerates for channel closure /// transactions for this channel right now. fn closure_conf_target(&self) -> ConfirmationTarget { @@ -4674,7 +4674,7 @@ impl ChannelMonitorImpl { } } -impl chain::Listen for (ChannelMonitor, T, F, L) +impl chain::Listen for (ChannelMonitor, T, F, L) where T::Target: BroadcasterInterface, F::Target: FeeEstimator, @@ -4689,7 +4689,7 @@ where } } -impl chain::Confirm for (M, T, F, L) +impl chain::Confirm for (M, T, F, L) where M: Deref>, T::Target: BroadcasterInterface, diff --git a/lightning/src/chain/mod.rs b/lightning/src/chain/mod.rs index 36b1ce57309..a94be9b8d94 100644 --- a/lightning/src/chain/mod.rs +++ b/lightning/src/chain/mod.rs @@ -18,7 +18,7 @@ use bitcoin::secp256k1::PublicKey; use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, MonitorEvent}; use crate::ln::types::ChannelId; -use crate::sign::ecdsa::EcdsaChannelSigner; +use crate::sign::ChannelSigner; use crate::chain::transaction::{OutPoint, TransactionData}; use crate::impl_writeable_tlv_based; @@ -260,7 +260,7 @@ pub enum ChannelMonitorUpdateStatus { /// application crashes. /// /// See method documentation and [`ChannelMonitorUpdateStatus`] for specific requirements. -pub trait Watch { +pub trait Watch { /// Watches a channel identified by `funding_txo` using `monitor`. /// /// Implementations are responsible for watching the chain for the funding transaction along @@ -276,7 +276,7 @@ pub trait Watch { /// [`get_outputs_to_watch`]: channelmonitor::ChannelMonitor::get_outputs_to_watch /// [`block_connected`]: channelmonitor::ChannelMonitor::block_connected /// [`block_disconnected`]: channelmonitor::ChannelMonitor::block_disconnected - fn watch_channel(&self, funding_txo: OutPoint, monitor: ChannelMonitor) -> Result; + fn watch_channel(&self, funding_txo: OutPoint, monitor: ChannelMonitor) -> Result; /// Updates a channel identified by `funding_txo` by applying `update` to its monitor. /// diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 35cd884cd8e..4daac9daf42 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -25,7 +25,7 @@ use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature}; use bitcoin::secp256k1; use crate::chain::chaininterface::{ConfirmationTarget, compute_feerate_sat_per_1000_weight}; -use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, ChannelSigner, EntropySource, SignerProvider, ecdsa::EcdsaChannelSigner}; +use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, ChannelSigner, EntropySource, SignerProvider}; use crate::ln::msgs::DecodeError; use crate::types::payment::PaymentPreimage; use crate::ln::chan_utils::{self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction}; @@ -228,14 +228,14 @@ pub(crate) enum FeerateStrategy { /// OnchainTxHandler receives claiming requests, aggregates them if it's sound, broadcast and /// do RBF bumping if possible. #[derive(Clone)] -pub struct OnchainTxHandler { +pub struct OnchainTxHandler { channel_value_satoshis: u64, channel_keys_id: [u8; 32], destination_script: ScriptBuf, holder_commitment: HolderCommitmentTransaction, prev_holder_commitment: Option, - pub(super) signer: ChannelSigner, + pub(super) signer: Signer, pub(crate) channel_transaction_parameters: ChannelTransactionParameters, // Used to track claiming requests. If claim tx doesn't confirm before height timer expiration we need to bump @@ -284,7 +284,7 @@ pub struct OnchainTxHandler { pub(super) secp_ctx: Secp256k1, } -impl PartialEq for OnchainTxHandler { +impl PartialEq for OnchainTxHandler { fn eq(&self, other: &Self) -> bool { // `signer`, `secp_ctx`, and `pending_claim_events` are excluded on purpose. self.channel_value_satoshis == other.channel_value_satoshis && @@ -303,7 +303,7 @@ impl PartialEq for OnchainTxHandler OnchainTxHandler { +impl OnchainTxHandler { pub(crate) fn write(&self, writer: &mut W) -> Result<(), io::Error> { write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION); @@ -443,10 +443,10 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP } } -impl OnchainTxHandler { +impl OnchainTxHandler { pub(crate) fn new( channel_value_satoshis: u64, channel_keys_id: [u8; 32], destination_script: ScriptBuf, - signer: ChannelSigner, channel_parameters: ChannelTransactionParameters, + signer: Signer, channel_parameters: ChannelTransactionParameters, holder_commitment: HolderCommitmentTransaction, secp_ctx: Secp256k1 ) -> Self { OnchainTxHandler { diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 19d92a42955..56471a3b162 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -32,7 +32,7 @@ use crate::ln::msgs::DecodeError; use crate::chain::channelmonitor::COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE; use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT, compute_feerate_sat_per_1000_weight, FEERATE_FLOOR_SATS_PER_KW}; use crate::chain::transaction::MaybeSignedTransaction; -use crate::sign::ecdsa::EcdsaChannelSigner; +use crate::sign::ChannelSigner; use crate::chain::onchaintx::{FeerateStrategy, ExternalHTLCClaim, OnchainTxHandler}; use crate::util::logger::Logger; use crate::util::ser::{Readable, Writer, Writeable, RequiredWrapper}; @@ -617,7 +617,7 @@ impl PackageSolvingData { witness: Witness::new(), } } - fn finalize_input(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler) -> bool { + fn finalize_input(&self, bumped_tx: &mut Transaction, i: usize, onchain_handler: &mut OnchainTxHandler) -> bool { match self { PackageSolvingData::RevokedOutput(ref outp) => { //TODO: should we panic on signer failure ? @@ -645,7 +645,7 @@ impl PackageSolvingData { } true } - fn get_maybe_finalized_tx(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler) -> Option { + fn get_maybe_finalized_tx(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler) -> Option { match self { PackageSolvingData::HolderHTLCOutput(ref outp) => { debug_assert!(!outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()); @@ -958,7 +958,7 @@ impl PackageTemplate { let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR; (inputs_weight + witnesses_weight + transaction_weight + output_weight) as u64 } - pub(crate) fn construct_malleable_package_with_external_funding( + pub(crate) fn construct_malleable_package_with_external_funding( &self, onchain_handler: &mut OnchainTxHandler, ) -> Option> { debug_assert!(self.requires_external_funding()); @@ -976,7 +976,7 @@ impl PackageTemplate { } htlcs } - pub(crate) fn maybe_finalize_malleable_package( + pub(crate) fn maybe_finalize_malleable_package( &self, current_height: u32, onchain_handler: &mut OnchainTxHandler, value: Amount, destination_script: ScriptBuf, logger: &L ) -> Option { @@ -999,7 +999,7 @@ impl PackageTemplate { } Some(MaybeSignedTransaction(bumped_tx)) } - pub(crate) fn maybe_finalize_untractable_package( + pub(crate) fn maybe_finalize_untractable_package( &self, onchain_handler: &mut OnchainTxHandler, logger: &L, ) -> Option { debug_assert!(!self.is_malleable()); diff --git a/lightning/src/util/persist.rs b/lightning/src/util/persist.rs index d0e9e01a482..1b4042f1413 100644 --- a/lightning/src/util/persist.rs +++ b/lightning/src/util/persist.rs @@ -26,7 +26,7 @@ use crate::chain::transaction::OutPoint; use crate::ln::channelmanager::AChannelManager; use crate::routing::gossip::NetworkGraph; use crate::routing::scoring::WriteableScore; -use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, SignerProvider}; +use crate::sign::{ChannelSigner, EntropySource, SignerProvider}; use crate::util::logger::Logger; use crate::util::ser::{Readable, ReadableArgs, Writeable}; @@ -252,14 +252,14 @@ where } } -impl Persist for K { +impl Persist for K { // TODO: We really need a way for the persister to inform the user that its time to crash/shut // down once these start returning failure. // Then we should return InProgress rather than UnrecoverableError, implying we should probably // just shut down the node since we're not retrying persistence! fn persist_new_channel( - &self, funding_txo: OutPoint, monitor: &ChannelMonitor, + &self, funding_txo: OutPoint, monitor: &ChannelMonitor, ) -> chain::ChannelMonitorUpdateStatus { let key = format!("{}_{}", funding_txo.txid.to_string(), funding_txo.index); match self.write( @@ -275,7 +275,7 @@ impl Persist, - monitor: &ChannelMonitor, + monitor: &ChannelMonitor, ) -> chain::ChannelMonitorUpdateStatus { let key = format!("{}_{}", funding_txo.txid.to_string(), funding_txo.index); match self.write( @@ -702,15 +702,8 @@ where } } -impl< - ChannelSigner: EcdsaChannelSigner, - K: Deref, - L: Deref, - ES: Deref, - SP: Deref, - BI: Deref, - FE: Deref, - > Persist for MonitorUpdatingPersister +impl + Persist for MonitorUpdatingPersister where K::Target: KVStore, L::Target: Logger, @@ -722,7 +715,7 @@ where /// Persists a new channel. This means writing the entire monitor to the /// parametrized [`KVStore`]. fn persist_new_channel( - &self, funding_txo: OutPoint, monitor: &ChannelMonitor, + &self, funding_txo: OutPoint, monitor: &ChannelMonitor, ) -> chain::ChannelMonitorUpdateStatus { // Determine the proper key for this monitor let monitor_name = MonitorName::from(funding_txo); @@ -764,7 +757,7 @@ where /// - The update is at [`u64::MAX`], indicating an update generated by pre-0.1 LDK. fn update_persisted_channel( &self, funding_txo: OutPoint, update: Option<&ChannelMonitorUpdate>, - monitor: &ChannelMonitor, + monitor: &ChannelMonitor, ) -> chain::ChannelMonitorUpdateStatus { const LEGACY_CLOSED_CHANNEL_UPDATE_ID: u64 = u64::MAX; if let Some(update) = update { @@ -1458,9 +1451,9 @@ mod tests { .is_err()); } - fn persist_fn(_persist: P) -> bool + fn persist_fn(_persist: P) -> bool where - P::Target: Persist, + P::Target: Persist, { true } From 0e3227e26a06726c0c619ab4b85e0965b3ddee5e Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 23 Dec 2024 23:42:49 +0000 Subject: [PATCH 19/47] Ask `ChannelSigner` the weight of the htlc tx input --- lightning/src/events/bump_transaction.rs | 29 +++++++++++------------- lightning/src/sign/mod.rs | 10 ++++++++ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 5e8dbdf007b..be4d6247dae 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -20,10 +20,7 @@ use crate::io_extras::sink; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::types::ChannelId; use crate::ln::chan_utils; -use crate::ln::chan_utils::{ - ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT, - HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT, HTLCOutputInCommitment -}; +use crate::ln::chan_utils::{ANCHOR_INPUT_WITNESS_WEIGHT, HTLCOutputInCommitment}; use crate::prelude::*; use crate::sign::{ ChannelDerivationParameters, ChannelSigner, HTLCDescriptor, SignerProvider, P2WPKH_WITNESS_WEIGHT, @@ -730,24 +727,24 @@ where let mut must_spend = Vec::with_capacity(htlc_descriptors.len()); let mut signers_and_revokeable_spks = BTreeMap::new(); for htlc_descriptor in htlc_descriptors { + // TODO: avoid having mutable references flying around + let (_, revokeable_spk, witness_weight) = signers_and_revokeable_spks.entry(htlc_descriptor.channel_derivation_parameters.keys_id) + .or_insert_with(|| { + let signer = htlc_descriptor.derive_channel_signer(&self.signer_provider); + let revokeable_spk = signer.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, &self.secp); + // TODO: cache this, it does not change throughout the lifetime of the channel + let witness_weight = signer.get_holder_htlc_transaction_witness_weight(htlc_descriptor.htlc.offered); + (signer, revokeable_spk, witness_weight) + }); + let htlc_input = htlc_descriptor.unsigned_tx_input(); must_spend.push(Input { outpoint: htlc_input.previous_output.clone(), previous_utxo: htlc_descriptor.previous_utxo(&self.secp), - satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + if htlc_descriptor.preimage.is_some() { - HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT - } else { - HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT - }, + satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + *witness_weight, }); htlc_tx.input.push(htlc_input); - let revokeable_spk = signers_and_revokeable_spks.entry(htlc_descriptor.channel_derivation_parameters.keys_id) - .or_insert_with(|| { - let signer = htlc_descriptor.derive_channel_signer(&self.signer_provider); - let revokeable_spk = signer.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, &self.secp); - (signer, revokeable_spk) - }).1.clone(); - let htlc_output = htlc_descriptor.tx_output(revokeable_spk); + let htlc_output = htlc_descriptor.tx_output(revokeable_spk.clone()); htlc_tx.output.push(htlc_output); } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index e886673fe3d..9412eb5c57b 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -993,6 +993,16 @@ pub trait ChannelSigner { &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1, ) -> Result; + + /// Gets the weight of the witness of the input that spends the htlc output of a + /// holder commitment transaction + fn get_holder_htlc_transaction_witness_weight(&self, offered: bool) -> u64 { + if offered { + chan_utils::HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT + } else { + chan_utils::HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT + } + } } /// Specifies the recipient of an invoice. From a9c3844cb210401e09e05f1d727b9e030e9794e0 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 24 Dec 2024 17:46:26 +0000 Subject: [PATCH 20/47] Let `ChannelSigner` set commit tx htlc output script pubkey --- lightning/src/events/bump_transaction.rs | 11 ++-- lightning/src/ln/chan_utils.rs | 3 +- lightning/src/sign/mod.rs | 73 ++++++++++------------- lightning/src/util/test_channel_signer.rs | 11 +++- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index be4d6247dae..2ded41f3e39 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -728,19 +728,20 @@ where let mut signers_and_revokeable_spks = BTreeMap::new(); for htlc_descriptor in htlc_descriptors { // TODO: avoid having mutable references flying around - let (_, revokeable_spk, witness_weight) = signers_and_revokeable_spks.entry(htlc_descriptor.channel_derivation_parameters.keys_id) + let (_, revokeable_spk, witness_weight, htlc_spk) = signers_and_revokeable_spks.entry(htlc_descriptor.channel_derivation_parameters.keys_id) .or_insert_with(|| { let signer = htlc_descriptor.derive_channel_signer(&self.signer_provider); let revokeable_spk = signer.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, &self.secp); // TODO: cache this, it does not change throughout the lifetime of the channel let witness_weight = signer.get_holder_htlc_transaction_witness_weight(htlc_descriptor.htlc.offered); - (signer, revokeable_spk, witness_weight) + let htlc_spk = signer.get_htlc_spk(&htlc_descriptor.htlc, true, &htlc_descriptor.per_commitment_point, &self.secp); + (signer, revokeable_spk, witness_weight, htlc_spk) }); let htlc_input = htlc_descriptor.unsigned_tx_input(); must_spend.push(Input { outpoint: htlc_input.previous_output.clone(), - previous_utxo: htlc_descriptor.previous_utxo(&self.secp), + previous_utxo: htlc_descriptor.previous_utxo(htlc_spk.clone()), satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + *witness_weight, }); htlc_tx.input.push(htlc_input); @@ -775,7 +776,9 @@ where // add witness_utxo to htlc inputs for (i, htlc_descriptor) in htlc_descriptors.iter().enumerate() { debug_assert_eq!(htlc_psbt.unsigned_tx.input[i].previous_output, htlc_descriptor.outpoint()); - htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(&self.secp)); + // Unwrap because we derived the corresponding script pubkeys for all the htlc descriptors further above + let htlc_spk = signers_and_revokeable_spks.get(&htlc_descriptor.channel_derivation_parameters.keys_id).unwrap().3.clone(); + htlc_psbt.inputs[i].witness_utxo = Some(htlc_descriptor.previous_utxo(htlc_spk)); } // add witness_utxo to remaining inputs for (idx, utxo) in coin_selection.confirmed_utxos.into_iter().enumerate() { diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 5a02d2cd105..970cc2f77f3 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1563,9 +1563,8 @@ impl CommitmentTransaction { let mut htlcs = Vec::with_capacity(htlcs_with_aux.len()); for (htlc, _) in htlcs_with_aux { - let script = get_htlc_redeemscript(&htlc, &channel_parameters.channel_type_features(), &keys); let txout = TxOut { - script_pubkey: script.to_p2wsh(), + script_pubkey: signer.get_htlc_spk(htlc, is_holder_tx, &keys.per_commitment_point, secp_ctx), value: htlc.to_bitcoin_amount(), }; txouts.push((txout, Some(htlc))); diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 9412eb5c57b..98382bce477 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -614,13 +614,8 @@ impl HTLCDescriptor { /// Returns the UTXO to be spent by the HTLC input, which can be obtained via /// [`Self::unsigned_tx_input`]. - pub fn previous_utxo( - &self, secp: &Secp256k1, - ) -> TxOut { - TxOut { - script_pubkey: self.witness_script(secp).to_p2wsh(), - value: self.htlc.to_bitcoin_amount(), - } + pub fn previous_utxo(&self, htlc_spk: ScriptBuf) -> TxOut { + TxOut { script_pubkey: htlc_spk, value: self.htlc.to_bitcoin_amount() } } /// Returns the unsigned transaction input spending the HTLC output in the commitment @@ -644,38 +639,6 @@ impl HTLCDescriptor { ) } - /// Returns the witness script of the HTLC output in the commitment transaction. - pub fn witness_script( - &self, secp: &Secp256k1, - ) -> ScriptBuf { - let channel_params = - self.channel_derivation_parameters.transaction_parameters.as_holder_broadcastable(); - let broadcaster_keys = channel_params.broadcaster_pubkeys(); - let counterparty_keys = channel_params.countersignatory_pubkeys(); - let broadcaster_htlc_key = HtlcKey::from_basepoint( - secp, - &broadcaster_keys.htlc_basepoint, - &self.per_commitment_point, - ); - let counterparty_htlc_key = HtlcKey::from_basepoint( - secp, - &counterparty_keys.htlc_basepoint, - &self.per_commitment_point, - ); - let counterparty_revocation_key = &RevocationKey::from_basepoint( - &secp, - &counterparty_keys.revocation_basepoint, - &self.per_commitment_point, - ); - chan_utils::get_htlc_redeemscript_with_explicit_keys( - &self.htlc, - channel_params.channel_type_features(), - &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 { @@ -1003,6 +966,25 @@ pub trait ChannelSigner { chan_utils::HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT } } + + /// Gets the script pubkey of a htlc output in a commitment transaction + fn get_htlc_spk( + &self, htlc: &HTLCOutputInCommitment, is_holder_tx: bool, per_commitment_point: &PublicKey, + secp_ctx: &Secp256k1, + ) -> ScriptBuf { + let params = if is_holder_tx { + self.get_channel_parameters().unwrap().as_holder_broadcastable() + } else { + self.get_channel_parameters().unwrap().as_counterparty_broadcastable() + }; + let keys = TxCreationKeys::from_channel_static_keys( + per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + chan_utils::get_htlc_redeemscript(htlc, params.channel_type_features(), &keys).to_p2wsh() + } } /// Specifies the recipient of an invoice. @@ -1727,7 +1709,18 @@ impl ChannelSigner for InMemorySigner { &self, htlc_tx: &Transaction, input: usize, htlc_descriptor: &HTLCDescriptor, secp_ctx: &Secp256k1, ) -> Result { - let witness_script = htlc_descriptor.witness_script(secp_ctx); + let params = self.get_channel_parameters().unwrap().as_holder_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + &htlc_descriptor.per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + let witness_script = chan_utils::get_htlc_redeemscript( + &htlc_descriptor.htlc, + params.channel_type_features(), + &keys, + ); let sighash = &sighash::SighashCache::new(&*htlc_tx) .p2wsh_signature_hash( input, diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index db42cf14e78..bf673d665b2 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -8,7 +8,7 @@ // licenses. use crate::ln::channel::{ANCHOR_OUTPUT_VALUE_SATOSHI, MIN_CHAN_DUST_LIMIT_SATOSHIS}; -use crate::ln::chan_utils::{HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction, ClosingTransaction}; +use crate::ln::chan_utils::{self, HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction, ClosingTransaction, TxCreationKeys}; use crate::ln::channel_keys::{HtlcKey}; use crate::ln::msgs; use crate::types::payment::PaymentPreimage; @@ -300,7 +300,14 @@ impl ChannelSigner for TestChannelSigner { let revokeable_spk = self.get_revokeable_spk(true, htlc_descriptor.per_commitment_number, &htlc_descriptor.per_commitment_point, secp_ctx); assert_eq!(htlc_tx.output[input], htlc_descriptor.tx_output(revokeable_spk)); { - let witness_script = htlc_descriptor.witness_script(secp_ctx); + let params = self.get_channel_parameters().unwrap().as_holder_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + &htlc_descriptor.per_commitment_point, + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + let witness_script = chan_utils::get_htlc_redeemscript(&htlc_descriptor.htlc, params.channel_type_features(), &keys); let sighash_type = if self.channel_type_features().supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { From ec407589561c18982468bcc4c1c31cad95ae37d4 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 24 Dec 2024 18:04:34 +0000 Subject: [PATCH 21/47] Remove `TxCreationKeys` from `CommitmentTransaction::internal_build_outputs` --- lightning/src/ln/chan_utils.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 970cc2f77f3..ffe1cc54c76 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1457,7 +1457,7 @@ impl CommitmentTransaction { let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); // Sort outputs and populate output indices while keeping track of the auxiliary data - let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); + let (outputs, htlcs) = Self::internal_build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); @@ -1486,11 +1486,11 @@ impl CommitmentTransaction { self } - fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { + fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters); let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect(); - let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key, signer, secp_ctx, is_holder_tx, self.commitment_number)?; + let (outputs, _) = Self::internal_build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key, signer, secp_ctx, is_holder_tx, self.commitment_number)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); @@ -1514,7 +1514,7 @@ impl CommitmentTransaction { // - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the // caller needs to have sorted together with the HTLCs so it can keep track of the output index // - building of a bitcoin transaction during a verify() call, in which case T is just () - fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { + fn internal_build_outputs(per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); if to_countersignatory_value_sat > Amount::ZERO { @@ -1530,7 +1530,7 @@ impl CommitmentTransaction { if to_broadcaster_value_sat > Amount::ZERO { txouts.push(( TxOut { - script_pubkey: signer.get_revokeable_spk(is_holder_tx, commitment_number, &keys.per_commitment_point, secp_ctx), + script_pubkey: signer.get_revokeable_spk(is_holder_tx, commitment_number, per_commitment_point, secp_ctx), value: to_broadcaster_value_sat, }, None, @@ -1564,7 +1564,7 @@ impl CommitmentTransaction { let mut htlcs = Vec::with_capacity(htlcs_with_aux.len()); for (htlc, _) in htlcs_with_aux { let txout = TxOut { - script_pubkey: signer.get_htlc_spk(htlc, is_holder_tx, &keys.per_commitment_point, secp_ctx), + script_pubkey: signer.get_htlc_spk(htlc, is_holder_tx, per_commitment_point, secp_ctx), value: htlc.to_bitcoin_amount(), }; txouts.push((txout, Some(htlc))); @@ -1680,7 +1680,7 @@ impl CommitmentTransaction { if keys != self.keys { return Err(()); } - let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, signer, secp_ctx, is_holder_tx)?; + let tx = self.internal_rebuild_transaction(&keys.per_commitment_point, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, signer, secp_ctx, is_holder_tx)?; if self.built.transaction != tx.transaction || self.built.txid != tx.txid { return Err(()); } From 207b70fa268efa37225584a713f25351dafd1021 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 14 Jan 2025 00:53:51 +0000 Subject: [PATCH 22/47] Let `ChannelSigner` set commit tx anchor output script pubkey --- lightning/src/chain/channelmonitor.rs | 6 +-- lightning/src/ln/chan_utils.rs | 54 +++++++---------------- lightning/src/ln/channel.rs | 7 --- lightning/src/ln/functional_tests.rs | 22 ++++----- lightning/src/sign/mod.rs | 29 ++++++++++-- lightning/src/util/test_channel_signer.rs | 4 +- 6 files changed, 54 insertions(+), 68 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index b5dca0426c9..22b6378b251 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3409,16 +3409,14 @@ impl ChannelMonitorImpl { let countersignatory_keys = &self.onchain_tx_handler.channel_transaction_parameters.holder_pubkeys; - let broadcaster_funding_key = broadcaster_keys.funding_pubkey; - let countersignatory_funding_key = countersignatory_keys.funding_pubkey; let keys = TxCreationKeys::from_channel_static_keys(&their_per_commitment_point, &broadcaster_keys, &countersignatory_keys, &self.onchain_tx_handler.secp_ctx); let channel_parameters = &self.onchain_tx_handler.channel_transaction_parameters.as_counterparty_broadcastable(); CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number, - to_broadcaster_value, to_countersignatory_value, broadcaster_funding_key, - countersignatory_funding_key, keys, feerate_per_kw, &mut nondust_htlcs, + to_broadcaster_value, to_countersignatory_value, + keys, feerate_per_kw, &mut nondust_htlcs, channel_parameters, &self.onchain_tx_handler.signer, &self.onchain_tx_handler.secp_ctx, false) } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index ffe1cc54c76..2bb64f3d766 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -41,7 +41,7 @@ use bitcoin::{secp256k1, Sequence, Witness}; use crate::io; use core::cmp; use crate::util::transaction_utils::sort_outputs; -use crate::ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI}; +use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use core::ops::Deref; use crate::chain; use crate::types::features::ChannelTypeFeatures; @@ -1136,7 +1136,7 @@ impl HolderCommitmentTransaction { for _ in 0..htlcs.len() { counterparty_htlc_sigs.push(dummy_sig); } - let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, dummy_key.clone(), dummy_key.clone(), keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable(), &signer, &secp_ctx, false); + let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, keys, 0, htlcs, &channel_parameters.as_counterparty_broadcastable(), &signer, &secp_ctx, false); htlcs.sort_by_key(|htlc| htlc.0.transaction_output_index); HolderCommitmentTransaction { inner, @@ -1452,12 +1452,12 @@ impl CommitmentTransaction { /// Only include HTLCs that are above the dust limit for the channel. /// /// This is not exported to bindings users due to the generic though we likely should expose a version without - pub fn new_with_auxiliary_htlc_data(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, broadcaster_funding_key: PublicKey, countersignatory_funding_key: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> CommitmentTransaction { + pub fn new_with_auxiliary_htlc_data(commitment_number: u64, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> CommitmentTransaction { let to_broadcaster_value_sat = Amount::from_sat(to_broadcaster_value_sat); let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); // Sort outputs and populate output indices while keeping track of the auxiliary data - let (outputs, htlcs) = Self::internal_build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, &broadcaster_funding_key, &countersignatory_funding_key, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); + let (outputs, htlcs) = Self::internal_build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); @@ -1486,11 +1486,11 @@ impl CommitmentTransaction { self } - fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { + fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, channel_parameters: &DirectedChannelTransactionParameters, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters); let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect(); - let (outputs, _) = Self::internal_build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, broadcaster_funding_key, countersignatory_funding_key, signer, secp_ctx, is_holder_tx, self.commitment_number)?; + let (outputs, _) = Self::internal_build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, signer, secp_ctx, is_holder_tx, self.commitment_number)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); @@ -1514,7 +1514,7 @@ impl CommitmentTransaction { // - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the // caller needs to have sorted together with the HTLCs so it can keep track of the output index // - building of a bitcoin transaction during a verify() call, in which case T is just () - fn internal_build_outputs(per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { + fn internal_build_outputs(per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); if to_countersignatory_value_sat > Amount::ZERO { @@ -1537,27 +1537,15 @@ impl CommitmentTransaction { )); } - if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() { - if to_broadcaster_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() { - let anchor_script = get_anchor_redeemscript(broadcaster_funding_key); - txouts.push(( - TxOut { - script_pubkey: anchor_script.to_p2wsh(), - value: Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI), - }, - None, - )); + if to_broadcaster_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() { + if let Some(txout) = signer.get_anchor_txout(is_holder_tx, true) { + txouts.push((txout, None)); } + } - if to_countersignatory_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() { - let anchor_script = get_anchor_redeemscript(countersignatory_funding_key); - txouts.push(( - TxOut { - script_pubkey: anchor_script.to_p2wsh(), - value: Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI), - }, - None, - )); + if to_countersignatory_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() { + if let Some(txout) = signer.get_anchor_txout(is_holder_tx, false) { + txouts.push((txout, None)); } } @@ -1673,14 +1661,10 @@ impl CommitmentTransaction { /// /// An external validating signer must call this method before signing /// or using the built transaction. - pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_keys: &ChannelPublicKeys, countersignatory_keys: &ChannelPublicKeys, secp_ctx: &Secp256k1, signer: &Signer, is_holder_tx: bool) -> Result { + pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1, signer: &Signer, is_holder_tx: bool) -> Result { // This is the only field of the key cache that we trust let per_commitment_point = self.keys.per_commitment_point; - let keys = TxCreationKeys::from_channel_static_keys(&per_commitment_point, broadcaster_keys, countersignatory_keys, secp_ctx); - if keys != self.keys { - return Err(()); - } - let tx = self.internal_rebuild_transaction(&keys.per_commitment_point, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey, signer, secp_ctx, is_holder_tx)?; + let tx = self.internal_rebuild_transaction(&per_commitment_point, channel_parameters, signer, secp_ctx, is_holder_tx)?; if self.built.transaction != tx.transaction || self.built.txid != tx.txid { return Err(()); } @@ -1882,8 +1866,6 @@ mod tests { struct TestCommitmentTxBuilder { commitment_number: u64, - holder_funding_pubkey: PublicKey, - counterparty_funding_pubkey: PublicKey, keys: TxCreationKeys, feerate_per_kw: u32, htlcs_with_aux: Vec<(HTLCOutputInCommitment, ())>, @@ -1919,8 +1901,6 @@ mod tests { Self { commitment_number: 0, - holder_funding_pubkey: holder_pubkeys.funding_pubkey, - counterparty_funding_pubkey: counterparty_pubkeys.funding_pubkey, keys, feerate_per_kw: 1, htlcs_with_aux, @@ -1936,8 +1916,6 @@ mod tests { self.commitment_number, to_broadcaster_sats, to_countersignatory_sats, - self.holder_funding_pubkey.clone(), - self.counterparty_funding_pubkey.clone(), self.keys.clone(), self.feerate_per_kw, &mut self.htlcs_with_aux, &self.channel_parameters.as_holder_broadcastable(), &self.signer, diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index ee86526311b..9cbc0ceff06 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3347,11 +3347,6 @@ impl ChannelContext where SP::Target: SignerProvider { let mut value_to_a = if local { value_to_self } else { value_to_remote }; let mut value_to_b = if local { value_to_remote } else { value_to_self }; - let (funding_pubkey_a, funding_pubkey_b) = if local { - (self.get_holder_pubkeys().funding_pubkey, self.get_counterparty_pubkeys().funding_pubkey) - } else { - (self.get_counterparty_pubkeys().funding_pubkey, self.get_holder_pubkeys().funding_pubkey) - }; if value_to_a >= (broadcaster_dust_limit_satoshis as i64) { log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, value_to_a); @@ -3373,8 +3368,6 @@ impl ChannelContext where SP::Target: SignerProvider { let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number, value_to_a as u64, value_to_b as u64, - funding_pubkey_a, - funding_pubkey_b, keys.clone(), feerate_per_kw, &mut included_non_dust_htlcs, diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 5d0881fdde9..8e0dfdf1122 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -731,24 +731,22 @@ fn test_update_fee_that_funder_cannot_afford() { // Get the TestChannelSigner for each channel, which will be used to (1) get the keys // needed to sign the new commitment tx and (2) sign the new commitment tx. - let (local_revocation_basepoint, local_htlc_basepoint, local_funding) = { + let (local_revocation_basepoint, local_htlc_basepoint) = { let per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap(); let local_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); let chan_signer = local_chan.get_signer(); let pubkeys = chan_signer.as_ref().pubkeys(); - (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, - pubkeys.funding_pubkey) + (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint) }; - let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point, remote_funding) = { + let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point) = { let per_peer_state = nodes[1].node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap(); let remote_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); let chan_signer = remote_chan.get_signer(); let pubkeys = chan_signer.as_ref().pubkeys(); (pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, - chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap(), - pubkeys.funding_pubkey) + chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap()) }; // Assemble the set of keys we can use for signatures for our commitment_signed message. @@ -765,7 +763,6 @@ fn test_update_fee_that_funder_cannot_afford() { INITIAL_COMMITMENT_NUMBER - 1, push_sats, channel_value - push_sats - commit_tx_fee_msat(non_buffer_feerate + 4, 0, &channel_type_features) / 1000, - local_funding, remote_funding, commit_tx_keys.clone(), non_buffer_feerate + 4, &mut htlcs, @@ -1464,7 +1461,7 @@ fn test_fee_spike_violation_fails_htlc() { // Get the TestChannelSigner for each channel, which will be used to (1) get the keys // needed to sign the new commitment tx and (2) sign the new commitment tx. - let (local_revocation_basepoint, local_htlc_basepoint, local_secret, next_local_point, local_funding) = { + let (local_revocation_basepoint, local_htlc_basepoint, local_secret, next_local_point) = { let per_peer_state = nodes[0].node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(&nodes[1].node.get_our_node_id()).unwrap().lock().unwrap(); let local_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); @@ -1475,18 +1472,16 @@ fn test_fee_spike_violation_fails_htlc() { let pubkeys = chan_signer.as_ref().pubkeys(); (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, chan_signer.as_ref().release_commitment_secret(INITIAL_COMMITMENT_NUMBER).unwrap(), - chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx).unwrap(), - chan_signer.as_ref().pubkeys().funding_pubkey) + chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx).unwrap()) }; - let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point, remote_funding) = { + let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point) = { let per_peer_state = nodes[1].node.per_peer_state.read().unwrap(); let chan_lock = per_peer_state.get(&nodes[0].node.get_our_node_id()).unwrap().lock().unwrap(); let remote_chan = chan_lock.channel_by_id.get(&chan.2).and_then(Channel::as_funded).unwrap(); let chan_signer = remote_chan.get_signer(); let pubkeys = chan_signer.as_ref().pubkeys(); (pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, - chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap(), - chan_signer.as_ref().pubkeys().funding_pubkey) + chan_signer.as_ref().get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx).unwrap()) }; // Assemble the set of keys we can use for signatures for our commitment_signed message. @@ -1516,7 +1511,6 @@ fn test_fee_spike_violation_fails_htlc() { commitment_number, 95000, local_chan_balance, - local_funding, remote_funding, commit_tx_keys.clone(), feerate_per_kw, &mut vec![(accepted_htlc_info, ())], diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 98382bce477..253c5c18f15 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -46,9 +46,9 @@ use crate::chain::transaction::OutPoint; use crate::crypto::utils::{hkdf_extract_expand_twice, sign, sign_with_aux_rand}; use crate::ln::chan_utils; use crate::ln::chan_utils::{ - get_counterparty_payment_script, get_revokeable_redeemscript, make_funding_redeemscript, - ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, - HTLCOutputInCommitment, HolderCommitmentTransaction, + get_anchor_redeemscript, get_counterparty_payment_script, get_revokeable_redeemscript, + make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, + CommitmentTransaction, HTLCOutputInCommitment, HolderCommitmentTransaction, }; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::channel_keys::{ @@ -985,6 +985,29 @@ pub trait ChannelSigner { ); chan_utils::get_htlc_redeemscript(htlc, params.channel_type_features(), &keys).to_p2wsh() } + + /// Get the anchor output of a commit tx + fn get_anchor_txout(&self, is_holder_tx: bool, is_broadcaster_anchor: bool) -> Option { + let channel_parameters = self.get_channel_parameters().unwrap(); + if channel_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() { + let params = if is_holder_tx { + channel_parameters.as_holder_broadcastable() + } else { + channel_parameters.as_counterparty_broadcastable() + }; + let funding_key = if is_broadcaster_anchor { + params.broadcaster_pubkeys().funding_pubkey + } else { + params.countersignatory_pubkeys().funding_pubkey + }; + Some(TxOut { + script_pubkey: get_anchor_redeemscript(&funding_key).to_p2wsh(), + value: Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI), + }) + } else { + None + } + } } /// Specifies the recipient of an invoice. diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index bf673d665b2..108d984fadc 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -465,7 +465,7 @@ impl TestChannelSigner { fn verify_counterparty_commitment_tx<'a>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify( &self.inner.get_channel_parameters().unwrap().as_counterparty_broadcastable(), - self.inner.counterparty_pubkeys().unwrap(), self.inner.pubkeys(), secp_ctx, + secp_ctx, self, false, ).expect("derived different per-tx keys or built transaction") @@ -474,7 +474,7 @@ impl TestChannelSigner { fn verify_holder_commitment_tx<'a>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify( &self.inner.get_channel_parameters().unwrap().as_holder_broadcastable(), - self.inner.pubkeys(), self.inner.counterparty_pubkeys().unwrap(), secp_ctx, + secp_ctx, self, true, ).expect("derived different per-tx keys or built transaction") From 557a754713d792e6a50e2a2e8a59b258f7be5962 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Wed, 25 Dec 2024 17:06:08 +0000 Subject: [PATCH 23/47] Let `ChannelSigner` build the anchor sweep witness --- lightning/src/events/bump_transaction.rs | 4 ++-- lightning/src/sign/mod.rs | 14 ++++++++++++++ lightning/src/util/test_channel_signer.rs | 6 ++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 2ded41f3e39..5867676350d 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -687,8 +687,8 @@ where anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?; let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider); - let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?; - anchor_tx.input[0].witness = anchor_descriptor.tx_input_witness(&anchor_sig); + let anchor_witness = signer.spend_holder_anchor_output(&anchor_tx, 0, &self.secp)?; + anchor_tx.input[0].witness = anchor_witness; #[cfg(debug_assertions)] { let signed_tx_weight = anchor_tx.weight().to_wu(); diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 253c5c18f15..378b6d9af2a 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1008,6 +1008,11 @@ pub trait ChannelSigner { None } } + + /// Spend the holder anchor output + fn spend_holder_anchor_output( + &self, anchor_tx: &Transaction, input_idx: usize, secp_ctx: &Secp256k1, + ) -> Result; } /// Specifies the recipient of an invoice. @@ -1769,6 +1774,15 @@ impl ChannelSigner for InMemorySigner { features, )) } + + fn spend_holder_anchor_output( + &self, anchor_tx: &Transaction, input_idx: usize, secp_ctx: &Secp256k1, + ) -> Result { + let anchor_sig = + EcdsaChannelSigner::sign_holder_anchor_input(self, anchor_tx, input_idx, secp_ctx)?; + let funding_pubkey = self.pubkeys().funding_pubkey; + Ok(chan_utils::build_anchor_input_witness(&funding_pubkey, &anchor_sig)) + } } const MISSING_PARAMS_ERR: &'static str = diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 108d984fadc..7b3d225686d 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -326,6 +326,12 @@ impl ChannelSigner for TestChannelSigner { } Ok(self.inner.sign_holder_htlc_transaction(htlc_tx, input, htlc_descriptor, secp_ctx).unwrap()) } + + fn spend_holder_anchor_output( + &self, anchor_tx: &Transaction, input_idx: usize, secp_ctx: &Secp256k1, + ) -> Result { + self.inner.spend_holder_anchor_output(anchor_tx, input_idx, secp_ctx) + } } impl EcdsaChannelSigner for TestChannelSigner { From 7ec1efc55e1796d4720932bf2c795d9a40f17de2 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 6 Jan 2025 17:48:49 +0000 Subject: [PATCH 24/47] Ask `ChannelSigner` the weight of the anchor tx input --- lightning/src/events/bump_transaction.rs | 12 +++++++----- lightning/src/sign/mod.rs | 6 ++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 5867676350d..7e71e2501e8 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -20,7 +20,7 @@ use crate::io_extras::sink; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::types::ChannelId; use crate::ln::chan_utils; -use crate::ln::chan_utils::{ANCHOR_INPUT_WITNESS_WEIGHT, HTLCOutputInCommitment}; +use crate::ln::chan_utils::HTLCOutputInCommitment; use crate::prelude::*; use crate::sign::{ ChannelDerivationParameters, ChannelSigner, HTLCDescriptor, SignerProvider, P2WPKH_WITNESS_WEIGHT, @@ -608,6 +608,9 @@ where &self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32, commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor, ) -> Result<(), ()> { + let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider); + let anchor_input_witness_weight = signer.get_holder_anchor_input_witness_weight(); + // Our commitment transaction already has fees allocated to it, so we should take them into // account. We do so by pretending the commitment transaction's fee and weight are part of // the anchor input. @@ -615,7 +618,7 @@ where let commitment_tx_fee_sat = Amount::from_sat(commitment_tx_fee_sat); anchor_utxo.value += commitment_tx_fee_sat; let starting_package_and_fixed_input_satisfaction_weight = - commitment_tx.weight().to_wu() + ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT; + commitment_tx.weight().to_wu() + anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT; let mut package_and_fixed_input_satisfaction_weight = starting_package_and_fixed_input_satisfaction_weight; @@ -640,7 +643,7 @@ where output: vec![], }; - let total_satisfaction_weight = ANCHOR_INPUT_WITNESS_WEIGHT + EMPTY_SCRIPT_SIG_WEIGHT + + let total_satisfaction_weight = anchor_input_witness_weight + EMPTY_SCRIPT_SIG_WEIGHT + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.satisfaction_weight).sum::(); let total_input_amount = must_spend_amount + coin_selection.confirmed_utxos.iter().map(|utxo| utxo.output.value).sum(); @@ -686,7 +689,6 @@ where log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid); anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?; - let signer = anchor_descriptor.derive_channel_signer(&self.signer_provider); let anchor_witness = signer.spend_holder_anchor_output(&anchor_tx, 0, &self.secp)?; anchor_tx.input[0].witness = anchor_witness; @@ -862,7 +864,7 @@ mod tests { use super::*; use crate::io::Cursor; - use crate::ln::chan_utils::ChannelTransactionParameters; + use crate::ln::chan_utils::{ANCHOR_INPUT_WITNESS_WEIGHT, ChannelTransactionParameters}; use crate::util::ser::Readable; use crate::util::test_utils::{TestBroadcaster, TestLogger}; use crate::sign::KeysManager; diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 378b6d9af2a..1bc7e635231 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -49,6 +49,7 @@ use crate::ln::chan_utils::{ get_anchor_redeemscript, get_counterparty_payment_script, get_revokeable_redeemscript, make_funding_redeemscript, ChannelPublicKeys, ChannelTransactionParameters, ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment, HolderCommitmentTransaction, + ANCHOR_INPUT_WITNESS_WEIGHT, }; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::channel_keys::{ @@ -1013,6 +1014,11 @@ pub trait ChannelSigner { fn spend_holder_anchor_output( &self, anchor_tx: &Transaction, input_idx: usize, secp_ctx: &Secp256k1, ) -> Result; + + /// Get the weight of the witness to spend the holder anchor input + fn get_holder_anchor_input_witness_weight(&self) -> u64 { + ANCHOR_INPUT_WITNESS_WEIGHT + } } /// Specifies the recipient of an invoice. From f95a7c0f5341f9fcaf72d0111ce01745c82d991a Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 7 Jan 2025 19:43:28 +0000 Subject: [PATCH 25/47] Mark `PackageSolvingData::HolderHTLCOutput` weight unreachable --- lightning/src/chain/package.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 56471a3b162..346d14d31f4 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -546,14 +546,9 @@ impl PackageSolvingData { PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.weight as usize, PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => outp.weight as usize, PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => outp.weight as usize, - PackageSolvingData::HolderHTLCOutput(ref outp) => { - debug_assert!(outp.channel_type_features.supports_anchors_zero_fee_htlc_tx()); - if outp.preimage.is_none() { - weight_offered_htlc(&outp.channel_type_features) as usize - } else { - weight_received_htlc(&outp.channel_type_features) as usize - } - }, + // Since HolderHLTCOutput requires external funding, we never inquire the witness + // weight of the HTLC transaction here. See OnchainTxHandler::generate_claim. + PackageSolvingData::HolderHTLCOutput(ref _outp) => unreachable!(), // Since HolderFundingOutput maps to an untractable package that is already signed, its // weight can be determined from the transaction itself. PackageSolvingData::HolderFundingOutput(..) => unreachable!(), From a606d035ec052009dea0c181158807f4746088d9 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Fri, 10 Jan 2025 02:27:33 +0000 Subject: [PATCH 26/47] Remove unused `HolderFundingOutput` fields --- lightning/src/chain/channelmonitor.rs | 10 +++++----- lightning/src/chain/package.rs | 13 +++++-------- lightning/src/ln/channel.rs | 5 ++--- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 22b6378b251..ee651271d9e 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -878,6 +878,7 @@ pub(crate) struct ChannelMonitorImpl { prev_counterparty_commitment_txid: Option, counterparty_commitment_params: CounterpartyCommitmentParameters, + // this field is not used, but kept for backwards compatibility funding_redeemscript: ScriptBuf, channel_value_satoshis: u64, // first is the idx of the first of the two per-commitment points @@ -1340,7 +1341,7 @@ impl ChannelMonitor { pub(crate) fn new(secp_ctx: Secp256k1, mut keys: Signer, shutdown_script: Option, on_counterparty_tx_csv: u16, destination_script: &Script, funding_info: (OutPoint, ScriptBuf), channel_parameters: &ChannelTransactionParameters, holder_pays_commitment_tx_fee: bool, - funding_redeemscript: ScriptBuf, channel_value_satoshis: u64, + channel_value_satoshis: u64, commitment_transaction_number_obscure_factor: u64, initial_holder_commitment_tx: HolderCommitmentTransaction, best_block: BestBlock, counterparty_node_id: PublicKey, channel_id: ChannelId, @@ -1404,7 +1405,7 @@ impl ChannelMonitor { prev_counterparty_commitment_txid: None, counterparty_commitment_params, - funding_redeemscript, + funding_redeemscript: ScriptBuf::new(), channel_value_satoshis, their_cur_per_commitment_points: None, @@ -3069,7 +3070,6 @@ impl ChannelMonitorImpl { fn generate_claimable_outpoints_and_watch_outputs(&mut self, reason: ClosureReason) -> (Vec, Vec) { let funding_outp = HolderFundingOutput::build( - self.funding_redeemscript.clone(), self.channel_value_satoshis, self.onchain_tx_handler.channel_type_features().clone() ); @@ -5278,7 +5278,7 @@ mod tests { let monitor = ChannelMonitor::new(Secp256k1::new(), keys, Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(), (OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()), - &channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), + &channel_parameters, true, 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), best_block, dummy_key, channel_id); let mut htlcs = preimages_slice_to_htlcs!(preimages[0..10]); @@ -5528,7 +5528,7 @@ mod tests { let monitor = ChannelMonitor::new(Secp256k1::new(), keys, Some(ShutdownScript::new_p2wpkh_from_pubkey(shutdown_pubkey).into_inner()), 0, &ScriptBuf::new(), (OutPoint { txid: Txid::from_slice(&[43; 32]).unwrap(), index: 0 }, ScriptBuf::new()), - &channel_parameters, true, ScriptBuf::new(), 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), + &channel_parameters, true, 46, 0, HolderCommitmentTransaction::dummy(&mut Vec::new()), best_block, dummy_key, channel_id); let chan_id = monitor.inner.lock().unwrap().channel_id(); diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index 346d14d31f4..bab3b28f98b 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -455,16 +455,14 @@ impl Readable for HolderHTLCOutput { /// Note that on upgrades, some features of existing outputs may be missed. #[derive(Clone, PartialEq, Eq)] pub(crate) struct HolderFundingOutput { - funding_redeemscript: ScriptBuf, pub(crate) funding_amount: Option, channel_type_features: ChannelTypeFeatures, } impl HolderFundingOutput { - pub(crate) fn build(funding_redeemscript: ScriptBuf, funding_amount: u64, channel_type_features: ChannelTypeFeatures) -> Self { + pub(crate) fn build(funding_amount: u64, channel_type_features: ChannelTypeFeatures) -> Self { HolderFundingOutput { - funding_redeemscript, funding_amount: Some(funding_amount), channel_type_features, } @@ -475,7 +473,7 @@ impl Writeable for HolderFundingOutput { fn write(&self, writer: &mut W) -> Result<(), io::Error> { let legacy_deserialization_prevention_marker = chan_utils::legacy_deserialization_prevention_marker_for_channel_type_features(&self.channel_type_features); write_tlv_fields!(writer, { - (0, self.funding_redeemscript, required), + (0, None::, option), (1, self.channel_type_features, required), (2, legacy_deserialization_prevention_marker, option), (3, self.funding_amount, option), @@ -486,13 +484,13 @@ impl Writeable for HolderFundingOutput { impl Readable for HolderFundingOutput { fn read(reader: &mut R) -> Result { - let mut funding_redeemscript = RequiredWrapper(None); + let mut _funding_redeemscript: Option = None; let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; let mut funding_amount = None; read_tlv_fields!(reader, { - (0, funding_redeemscript, required), + (0, _funding_redeemscript, option), (1, channel_type_features, option), (2, _legacy_deserialization_prevention_marker, option), (3, funding_amount, option), @@ -501,7 +499,6 @@ impl Readable for HolderFundingOutput { verify_channel_type_features(&channel_type_features, None)?; Ok(Self { - funding_redeemscript: funding_redeemscript.0.unwrap(), channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()), funding_amount }) @@ -1420,7 +1417,7 @@ mod tests { macro_rules! dumb_funding_output { () => { - PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build(ScriptBuf::new(), 0, ChannelTypeFeatures::only_static_remote_key())) + PackageSolvingData::HolderFundingOutput(HolderFundingOutput::build(0, ChannelTypeFeatures::only_static_remote_key())) } } diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 9cbc0ceff06..82d6d2dbd3d 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1853,9 +1853,8 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide return Err(ChannelError::close("Failed to advance holder commitment point".to_owned())); } - let funding_redeemscript = context.get_funding_redeemscript(); + let funding_txo_script = context.get_funding_redeemscript().to_p2wsh(); let funding_txo = context.get_funding_txo().unwrap(); - let funding_txo_script = funding_redeemscript.to_p2wsh(); let obscure_factor = get_commitment_transaction_number_obscure_factor(&context.get_holder_pubkeys().payment_point, &context.get_counterparty_pubkeys().payment_point, context.is_outbound()); let shutdown_script = context.shutdown_scriptpubkey.clone().map(|script| script.into_inner()); let monitor_signer = signer_provider.derive_channel_signer(context.channel_value_satoshis, context.channel_keys_id); @@ -1863,7 +1862,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide shutdown_script, context.get_holder_selected_contest_delay(), &context.destination_script, (funding_txo, funding_txo_script), &context.channel_transaction_parameters, context.is_outbound(), - funding_redeemscript.clone(), context.channel_value_satoshis, + context.channel_value_satoshis, obscure_factor, holder_commitment_tx, best_block, context.counterparty_node_id, context.channel_id()); channel_monitor.provide_initial_counterparty_commitment_tx( From 03d6a40330ff756f0035e60c839178c99727c31c Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 13 Jan 2025 18:27:24 +0000 Subject: [PATCH 27/47] Add `ChannelTransactionParameters::channel_type_features` --- lightning/src/chain/onchaintx.rs | 2 +- lightning/src/ln/chan_utils.rs | 4 ++++ lightning/src/ln/channel.rs | 6 +++--- lightning/src/sign/mod.rs | 18 +++++++++--------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 4daac9daf42..1ed7ffbc13d 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -1272,7 +1272,7 @@ impl OnchainTxHandler { } pub(crate) fn channel_type_features(&self) -> &ChannelTypeFeatures { - &self.channel_transaction_parameters.channel_type_features + self.channel_transaction_parameters.channel_type_features() } } diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 2bb64f3d766..6a0f6dffe95 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -949,6 +949,10 @@ impl ChannelTransactionParameters { channel_type_features: ChannelTypeFeatures::empty(), } } + + pub(crate) fn channel_type_features(&self) -> &ChannelTypeFeatures { + &self.channel_type_features + } } impl_writeable_tlv_based!(CounterpartyChannelTransactionParameters, { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 82d6d2dbd3d..ea4ad2ba0d7 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -3336,8 +3336,8 @@ impl ChannelContext where SP::Target: SignerProvider { broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, value_to_remote_msat as u64); } - let total_fee_sat = commit_tx_fee_sat(feerate_per_kw, included_non_dust_htlcs.len(), &self.channel_transaction_parameters.channel_type_features); - let anchors_val = if self.channel_transaction_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 } as i64; + let total_fee_sat = commit_tx_fee_sat(feerate_per_kw, included_non_dust_htlcs.len(), self.channel_transaction_parameters.channel_type_features()); + let anchors_val = if self.channel_transaction_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 } as i64; let (value_to_self, value_to_remote) = if self.is_outbound() { (value_to_self_msat / 1000 - anchors_val - total_fee_sat as i64, value_to_remote_msat / 1000) } else { @@ -4191,7 +4191,7 @@ impl ChannelContext where SP::Target: SignerProvider { if self.channel_type.supports_anchors_zero_fee_htlc_tx() { self.channel_type.clear_anchors_zero_fee_htlc_tx(); self.feerate_per_kw = fee_estimator.bounded_sat_per_1000_weight(ConfirmationTarget::NonAnchorChannelFee); - assert!(!self.channel_transaction_parameters.channel_type_features.supports_anchors_nonzero_fee_htlc_tx()); + assert!(!self.channel_transaction_parameters.channel_type_features().supports_anchors_nonzero_fee_htlc_tx()); } else if self.channel_type.supports_scid_privacy() { self.channel_type.clear_scid_privacy(); } else { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 1bc7e635231..9a91774b7db 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -625,7 +625,7 @@ impl HTLCDescriptor { chan_utils::build_htlc_input( &self.commitment_txid, &self.htlc, - &self.channel_derivation_parameters.transaction_parameters.channel_type_features, + self.channel_derivation_parameters.transaction_parameters.channel_type_features(), ) } @@ -635,7 +635,7 @@ impl HTLCDescriptor { chan_utils::build_htlc_output( self.feerate_per_kw, &self.htlc, - &self.channel_derivation_parameters.transaction_parameters.channel_type_features, + self.channel_derivation_parameters.transaction_parameters.channel_type_features(), revokeable_spk, ) } @@ -648,7 +648,7 @@ impl HTLCDescriptor { &self.counterparty_sig, &self.preimage, witness_script, - &self.channel_derivation_parameters.transaction_parameters.channel_type_features, + self.channel_derivation_parameters.transaction_parameters.channel_type_features(), ) } @@ -880,7 +880,7 @@ pub trait ChannelSigner { /// Return the total weight of the witness required to spend the justice path of a HTLC output in a /// commitment transaction. fn get_htlc_punishment_witness_weight(&self, offered: bool) -> u64 { - let features = &self.get_channel_parameters().unwrap().channel_type_features; + let features = self.get_channel_parameters().unwrap().channel_type_features(); if offered { weight_revoked_offered_htlc(features) } else { @@ -898,7 +898,7 @@ pub trait ChannelSigner { /// Weight of the witness that sweeps htlc outputs in counterparty commitment transactions fn counterparty_htlc_output_witness_weight(&self, offered: bool) -> u64 { - let features = &self.get_channel_parameters().unwrap().channel_type_features; + let features = self.get_channel_parameters().unwrap().channel_type_features(); if offered { weight_offered_htlc(features) } else { @@ -990,7 +990,7 @@ pub trait ChannelSigner { /// Get the anchor output of a commit tx fn get_anchor_txout(&self, is_holder_tx: bool, is_broadcaster_anchor: bool) -> Option { let channel_parameters = self.get_channel_parameters().unwrap(); - if channel_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() { + if channel_parameters.channel_type_features().supports_anchors_zero_fee_htlc_tx() { let params = if is_holder_tx { channel_parameters.as_holder_broadcastable() } else { @@ -1395,7 +1395,7 @@ impl InMemorySigner { /// Will return `None` if [`ChannelSigner::provide_channel_parameters`] has not been called. /// In general, this is safe to `unwrap` only in [`ChannelSigner`] implementation. pub fn channel_type_features(&self) -> Option<&ChannelTypeFeatures> { - self.get_channel_parameters().map(|params| ¶ms.channel_type_features) + self.get_channel_parameters().map(|params| params.channel_type_features()) } /// Sign the single input of `spend_tx` at index `input_idx`, which spends the output described @@ -1771,7 +1771,7 @@ impl ChannelSigner for InMemorySigner { let sighash = hash_to_message!(sighash.as_byte_array()); let sig = sign_with_aux_rand(&secp_ctx, &sighash, &our_htlc_private_key, &self); - let features = &self.channel_parameters.as_ref().unwrap().channel_type_features; + let features = self.channel_parameters.as_ref().unwrap().channel_type_features(); Ok(chan_utils::build_htlc_input_witness( &sig, &htlc_descriptor.counterparty_sig, @@ -1826,7 +1826,7 @@ impl EcdsaChannelSigner for InMemorySigner { ); for htlc in commitment_tx.htlcs() { let channel_parameters = self.get_channel_parameters().expect(MISSING_PARAMS_ERR); - let chan_type = &channel_parameters.channel_type_features; + let chan_type = channel_parameters.channel_type_features(); let htlc_tx = chan_utils::build_htlc_transaction( &commitment_txid, commitment_tx.feerate_per_kw(), From fbc1b5cde546466b47ab9981e5a7fc65a3e4728c Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 14 Jan 2025 02:49:36 +0000 Subject: [PATCH 28/47] Delete `EcdsaChannelSigner::sign_justice_revoked_output` --- lightning/src/sign/ecdsa.rs | 26 --------- lightning/src/sign/mod.rs | 69 ++++++----------------- lightning/src/util/test_channel_signer.rs | 8 --- 3 files changed, 17 insertions(+), 86 deletions(-) diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index ecfb73c234c..e73070a8caa 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -54,32 +54,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { &self, commitment_tx: &CommitmentTransaction, inbound_htlc_preimages: Vec, outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1, ) -> Result<(Signature, Vec), ()>; - /// Create a signature for the given input in a transaction spending an HTLC transaction output - /// or a commitment transaction `to_local` output when our counterparty broadcasts an old state. - /// - /// A justice transaction may claim multiple outputs at the same time if timelocks are - /// similar, but only a signature for the input at index `input` should be signed for here. - /// It may be called multiple times for same output(s) if a fee-bump is needed with regards - /// to an upcoming timelock expiration. - /// - /// Amount is value of the output spent by this input, committed to in the BIP 143 signature. - /// - /// `per_commitment_key` is revocation secret which was provided by our counterparty when they - /// revoked the state which they eventually broadcast. It's not a _holder_ secret key and does - /// not allow the spending of any funds by itself (you need our holder `revocation_secret` to do - /// so). - /// - /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid - /// signature and should be retried later. Once the signer is ready to provide a signature after - /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its - /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. - /// - /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked - /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked - fn sign_justice_revoked_output( - &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, - secp_ctx: &Secp256k1, - ) -> Result; /// Create a signature for the given input in a transaction spending a commitment transaction /// HTLC output when our counterparty broadcasts an old state. /// diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 9a91774b7db..d82939e0198 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1624,14 +1624,23 @@ impl ChannelSigner for InMemorySigner { contest_delay, &keys.broadcaster_delayed_payment_key, ); - let sig = EcdsaChannelSigner::sign_justice_revoked_output( - self, - justice_tx, - input, - amount, - per_commitment_key, - secp_ctx, - )?; + let revocation_key = chan_utils::derive_private_revocation_key( + &secp_ctx, + &per_commitment_key, + &self.revocation_base_key, + ); + let mut sighash_parts = sighash::SighashCache::new(justice_tx); + let sighash = hash_to_message!( + &sighash_parts + .p2wsh_signature_hash( + input, + &witness_script, + Amount::from_sat(amount), + EcdsaSighashType::All + ) + .unwrap()[..] + ); + let sig = sign_with_aux_rand(secp_ctx, &sighash, &revocation_key, &self); let ecdsa_sig = EcdsaSignature::sighash_all(sig); Ok(Witness::from( &[ecdsa_sig.serialize().as_ref(), &[1][..], witness_script.as_bytes()][..], @@ -1861,50 +1870,6 @@ impl EcdsaChannelSigner for InMemorySigner { Ok((commitment_sig, htlc_sigs)) } - fn sign_justice_revoked_output( - &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, - secp_ctx: &Secp256k1, - ) -> Result { - let revocation_key = chan_utils::derive_private_revocation_key( - &secp_ctx, - &per_commitment_key, - &self.revocation_base_key, - ); - let per_commitment_point = PublicKey::from_secret_key(secp_ctx, &per_commitment_key); - let revocation_pubkey = RevocationKey::from_basepoint( - &secp_ctx, - &self.pubkeys().revocation_basepoint, - &per_commitment_point, - ); - let witness_script = { - let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); - let holder_selected_contest_delay = - self.holder_selected_contest_delay().expect(MISSING_PARAMS_ERR); - let counterparty_delayedpubkey = DelayedPaymentKey::from_basepoint( - &secp_ctx, - &counterparty_keys.delayed_payment_basepoint, - &per_commitment_point, - ); - chan_utils::get_revokeable_redeemscript( - &revocation_pubkey, - holder_selected_contest_delay, - &counterparty_delayedpubkey, - ) - }; - let mut sighash_parts = sighash::SighashCache::new(justice_tx); - let sighash = hash_to_message!( - &sighash_parts - .p2wsh_signature_hash( - input, - &witness_script, - Amount::from_sat(amount), - EcdsaSighashType::All - ) - .unwrap()[..] - ); - return Ok(sign_with_aux_rand(secp_ctx, &sighash, &revocation_key, &self)); - } - fn sign_justice_revoked_htlc( &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 7b3d225686d..b458ed6f73e 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -358,14 +358,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(self.inner.sign_counterparty_commitment(commitment_tx, inbound_htlc_preimages, outbound_htlc_preimages, secp_ctx).unwrap()) } - fn sign_justice_revoked_output(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, secp_ctx: &Secp256k1) -> Result { - #[cfg(test)] - if !self.is_signer_available(SignerOp::SignJusticeRevokedOutput) { - return Err(()); - } - Ok(EcdsaChannelSigner::sign_justice_revoked_output(&self.inner, justice_tx, input, amount, per_commitment_key, secp_ctx).unwrap()) - } - fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result { #[cfg(test)] if !self.is_signer_available(SignerOp::SignJusticeRevokedHtlc) { From 5f27ad709962e6d0e0c2d2ce476826caf11d16ff Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 14 Jan 2025 02:58:06 +0000 Subject: [PATCH 29/47] Delete `EcdsaChannelSigner::sign_justice_revoked_htlc` --- lightning/src/sign/ecdsa.rs | 32 +--------- lightning/src/sign/mod.rs | 76 +++++------------------ lightning/src/util/test_channel_signer.rs | 8 --- 3 files changed, 18 insertions(+), 98 deletions(-) diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index e73070a8caa..ecb8464f55d 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -4,7 +4,7 @@ use bitcoin::transaction::Transaction; use bitcoin::secp256k1; use bitcoin::secp256k1::ecdsa::Signature; -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use bitcoin::secp256k1::{PublicKey, Secp256k1}; use crate::ln::chan_utils::{ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment}; use crate::ln::msgs::UnsignedChannelAnnouncement; @@ -54,36 +54,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { &self, commitment_tx: &CommitmentTransaction, inbound_htlc_preimages: Vec, outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1, ) -> Result<(Signature, Vec), ()>; - /// Create a signature for the given input in a transaction spending a commitment transaction - /// HTLC output when our counterparty broadcasts an old state. - /// - /// A justice transaction may claim multiple outputs at the same time if timelocks are - /// similar, but only a signature for the input at index `input` should be signed for here. - /// It may be called multiple times for same output(s) if a fee-bump is needed with regards - /// to an upcoming timelock expiration. - /// - /// `amount` is the value of the output spent by this input, committed to in the BIP 143 - /// signature. - /// - /// `per_commitment_key` is revocation secret which was provided by our counterparty when they - /// revoked the state which they eventually broadcast. It's not a _holder_ secret key and does - /// not allow the spending of any funds by itself (you need our holder revocation_secret to do - /// so). - /// - /// `htlc` holds HTLC elements (hash, timelock), thus changing the format of the witness script - /// (which is committed to in the BIP 143 signatures). - /// - /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid - /// signature and should be retried later. Once the signer is ready to provide a signature after - /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its - /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. - /// - /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked - /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked - fn sign_justice_revoked_htlc( - &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, - htlc: &HTLCOutputInCommitment, 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. /// diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index d82939e0198..a2b82bd0309 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1661,15 +1661,23 @@ impl ChannelSigner for InMemorySigner { ); let witness_script = chan_utils::get_htlc_redeemscript(htlc, params.channel_type_features(), &keys); - let sig = EcdsaChannelSigner::sign_justice_revoked_htlc( - self, - justice_tx, - input, - amount, - per_commitment_key, - htlc, - secp_ctx, - )?; + let revocation_key = chan_utils::derive_private_revocation_key( + &secp_ctx, + &per_commitment_key, + &self.revocation_base_key, + ); + let mut sighash_parts = sighash::SighashCache::new(justice_tx); + let sighash = hash_to_message!( + &sighash_parts + .p2wsh_signature_hash( + input, + &witness_script, + Amount::from_sat(amount), + EcdsaSighashType::All + ) + .unwrap()[..] + ); + let sig = sign_with_aux_rand(secp_ctx, &sighash, &revocation_key, &self); let ecdsa_sig = EcdsaSignature::sighash_all(sig); Ok(Witness::from( &[ @@ -1870,56 +1878,6 @@ impl EcdsaChannelSigner for InMemorySigner { Ok((commitment_sig, htlc_sigs)) } - fn sign_justice_revoked_htlc( - &self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, - htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, - ) -> Result { - let revocation_key = chan_utils::derive_private_revocation_key( - &secp_ctx, - &per_commitment_key, - &self.revocation_base_key, - ); - let per_commitment_point = PublicKey::from_secret_key(secp_ctx, &per_commitment_key); - let revocation_pubkey = RevocationKey::from_basepoint( - &secp_ctx, - &self.pubkeys().revocation_basepoint, - &per_commitment_point, - ); - let witness_script = { - let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); - let counterparty_htlcpubkey = HtlcKey::from_basepoint( - &secp_ctx, - &counterparty_keys.htlc_basepoint, - &per_commitment_point, - ); - let holder_htlcpubkey = HtlcKey::from_basepoint( - &secp_ctx, - &self.pubkeys().htlc_basepoint, - &per_commitment_point, - ); - let chan_type = self.channel_type_features().expect(MISSING_PARAMS_ERR); - chan_utils::get_htlc_redeemscript_with_explicit_keys( - &htlc, - chan_type, - &counterparty_htlcpubkey, - &holder_htlcpubkey, - &revocation_pubkey, - ) - }; - let mut sighash_parts = sighash::SighashCache::new(justice_tx); - let sighash = hash_to_message!( - &sighash_parts - .p2wsh_signature_hash( - input, - &witness_script, - Amount::from_sat(amount), - EcdsaSighashType::All - ) - .unwrap()[..] - ); - return Ok(sign_with_aux_rand(secp_ctx, &sighash, &revocation_key, &self)); - } - fn sign_counterparty_htlc_transaction( &self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index b458ed6f73e..88252c057a9 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -358,14 +358,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(self.inner.sign_counterparty_commitment(commitment_tx, inbound_htlc_preimages, outbound_htlc_preimages, secp_ctx).unwrap()) } - fn sign_justice_revoked_htlc(&self, justice_tx: &Transaction, input: usize, amount: u64, per_commitment_key: &SecretKey, htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1) -> Result { - #[cfg(test)] - if !self.is_signer_available(SignerOp::SignJusticeRevokedHtlc) { - return Err(()); - } - Ok(EcdsaChannelSigner::sign_justice_revoked_htlc(&self.inner, justice_tx, input, amount, per_commitment_key, htlc, 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 { #[cfg(test)] if !self.is_signer_available(SignerOp::SignCounterpartyHtlcTransaction) { From fc2a45ebfa4d924a0ad74b44921f9a6601adfd46 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 14 Jan 2025 03:08:48 +0000 Subject: [PATCH 30/47] Delete `EcdsaChannelSigner::sign_counterparty_htlc_transaction` --- lightning/src/sign/ecdsa.rs | 33 +------- lightning/src/sign/mod.rs | 93 ++++++++++------------- lightning/src/util/test_channel_signer.rs | 8 -- 3 files changed, 42 insertions(+), 92 deletions(-) diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index ecb8464f55d..a0c2dbc7721 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -4,9 +4,9 @@ use bitcoin::transaction::Transaction; use bitcoin::secp256k1; use bitcoin::secp256k1::ecdsa::Signature; -use bitcoin::secp256k1::{PublicKey, Secp256k1}; +use bitcoin::secp256k1::Secp256k1; -use crate::ln::chan_utils::{ClosingTransaction, CommitmentTransaction, HTLCOutputInCommitment}; +use crate::ln::chan_utils::{ClosingTransaction, CommitmentTransaction}; use crate::ln::msgs::UnsignedChannelAnnouncement; use crate::types::payment::PaymentPreimage; @@ -54,35 +54,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { &self, commitment_tx: &CommitmentTransaction, inbound_htlc_preimages: Vec, outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1, ) -> Result<(Signature, Vec), ()>; - /// Create a signature for a claiming transaction for a HTLC output on a counterparty's commitment - /// transaction, either offered or received. - /// - /// Such a transaction may claim multiples offered outputs at same time if we know the - /// preimage for each when we create it, but only the input at index `input` should be - /// signed for here. It may be called multiple times for same output(s) if a fee-bump is - /// needed with regards to an upcoming timelock expiration. - /// - /// `witness_script` is either an offered or received script as defined in BOLT3 for HTLC - /// outputs. - /// - /// `amount` is value of the output spent by this input, committed to in the BIP 143 signature. - /// - /// `per_commitment_point` is the dynamic point corresponding to the channel state - /// detected onchain. It has been generated by our counterparty and is used to derive - /// channel state keys, which are then included in the witness script and committed to in the - /// BIP 143 signature. - /// - /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid - /// signature and should be retried later. Once the signer is ready to provide a signature after - /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its - /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. - /// - /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked - /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked - fn sign_counterparty_htlc_transaction( - &self, htlc_tx: &Transaction, input: usize, amount: u64, per_commitment_point: &PublicKey, - htlc: &HTLCOutputInCommitment, secp_ctx: &Secp256k1, - ) -> Result; /// Create a signature for a (proposed) closing transaction. /// /// Note that, due to rounding, there may be one "missing" satoshi, and either party may have diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index a2b82bd0309..bad92e4cb3f 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -53,7 +53,7 @@ use crate::ln::chan_utils::{ }; use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI; use crate::ln::channel_keys::{ - add_public_key_tweak, DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, HtlcKey, + add_public_key_tweak, DelayedPaymentBasepoint, DelayedPaymentKey, HtlcBasepoint, RevocationBasepoint, RevocationKey, }; use crate::ln::inbound_payment::ExpandedKey; @@ -888,8 +888,31 @@ pub trait ChannelSigner { } } - /// Sweep a HTLC output on a counterparty commitment transaction. Sweep an offered htlc output if - /// the preimage is provided, otherwise, sweep a received htlc output. + /// Create a signature for a claiming transaction for a HTLC output on a counterparty's commitment + /// transaction, either offered or received. + /// + /// Such a transaction may claim multiples offered outputs at same time if we know the + /// preimage for each when we create it, but only the input at index `input` should be + /// signed for here. It may be called multiple times for same output(s) if a fee-bump is + /// needed with regards to an upcoming timelock expiration. + /// + /// `witness_script` is either an offered or received script as defined in BOLT3 for HTLC + /// outputs. + /// + /// `amount` is value of the output spent by this input, committed to in the BIP 143 signature. + /// + /// `per_commitment_point` is the dynamic point corresponding to the channel state + /// detected onchain. It has been generated by our counterparty and is used to derive + /// channel state keys, which are then included in the witness script and committed to in the + /// BIP 143 signature. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked + /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked fn sweep_counterparty_htlc_output( &self, sweep_tx: &Transaction, input: usize, amount: u64, secp_ctx: &Secp256k1, per_commitment_point: &PublicKey, @@ -1702,15 +1725,20 @@ impl ChannelSigner for InMemorySigner { ); let witness_script = chan_utils::get_htlc_redeemscript(htlc, params.channel_type_features(), &keys); - let sig = EcdsaChannelSigner::sign_counterparty_htlc_transaction( - self, - sweep_tx, - input, - amount, - per_commitment_point, - htlc, - secp_ctx, - )?; + let htlc_key = + chan_utils::derive_private_key(&secp_ctx, &per_commitment_point, &self.htlc_base_key); + let mut sighash_parts = sighash::SighashCache::new(sweep_tx); + let sighash = hash_to_message!( + &sighash_parts + .p2wsh_signature_hash( + input, + &witness_script, + Amount::from_sat(amount), + EcdsaSighashType::All + ) + .unwrap()[..] + ); + let sig = sign_with_aux_rand(secp_ctx, &sighash, &htlc_key, &self); let ecdsa_sig = EcdsaSignature::sighash_all(sig); let element = match preimage { Some(ref p) => &p.0[..], @@ -1878,47 +1906,6 @@ impl EcdsaChannelSigner for InMemorySigner { Ok((commitment_sig, htlc_sigs)) } - 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 = RevocationKey::from_basepoint( - &secp_ctx, - &self.pubkeys().revocation_basepoint, - &per_commitment_point, - ); - let counterparty_keys = self.counterparty_pubkeys().expect(MISSING_PARAMS_ERR); - let counterparty_htlcpubkey = HtlcKey::from_basepoint( - &secp_ctx, - &counterparty_keys.htlc_basepoint, - &per_commitment_point, - ); - let htlc_basepoint = self.pubkeys().htlc_basepoint; - let htlcpubkey = HtlcKey::from_basepoint(&secp_ctx, &htlc_basepoint, &per_commitment_point); - let chan_type = self.channel_type_features().expect(MISSING_PARAMS_ERR); - let witness_script = chan_utils::get_htlc_redeemscript_with_explicit_keys( - &htlc, - chan_type, - &counterparty_htlcpubkey, - &htlcpubkey, - &revocation_pubkey, - ); - let mut sighash_parts = sighash::SighashCache::new(htlc_tx); - let sighash = hash_to_message!( - &sighash_parts - .p2wsh_signature_hash( - input, - &witness_script, - Amount::from_sat(amount), - EcdsaSighashType::All - ) - .unwrap()[..] - ); - Ok(sign_with_aux_rand(secp_ctx, &sighash, &htlc_key, &self)) - } - fn sign_closing_transaction( &self, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1, ) -> Result { diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 88252c057a9..acaa08430e7 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -358,14 +358,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(self.inner.sign_counterparty_commitment(commitment_tx, inbound_htlc_preimages, outbound_htlc_preimages, 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 { - #[cfg(test)] - if !self.is_signer_available(SignerOp::SignCounterpartyHtlcTransaction) { - return Err(()); - } - Ok(EcdsaChannelSigner::sign_counterparty_htlc_transaction(&self.inner, htlc_tx, input, amount, per_commitment_point, htlc, secp_ctx).unwrap()) - } - fn sign_closing_transaction(&self, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1) -> Result { #[cfg(test)] if !self.is_signer_available(SignerOp::SignClosingTransaction) { From c9050a57ddbcc8afa30ced53a56f6e9b9d9e6233 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Tue, 14 Jan 2025 03:24:40 +0000 Subject: [PATCH 31/47] Delete `EcdsaChannelSigner::sign_holder_anchor_input` --- lightning/src/events/bump_transaction.rs | 13 +++---- lightning/src/sign/ecdsa.rs | 13 ------- lightning/src/sign/mod.rs | 44 ++++++++++++----------- lightning/src/util/test_channel_signer.rs | 15 -------- 4 files changed, 28 insertions(+), 57 deletions(-) diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 7e71e2501e8..6daa25dfd7c 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -119,11 +119,9 @@ pub enum BumpTransactionEvent { /// 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 [`EcdsaChannelSigner`] should be - /// re-derived through [`AnchorDescriptor::derive_channel_signer`]. The anchor input signature - /// can be computed with [`EcdsaChannelSigner::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. + /// child anchor transaction. To sign its anchor input, a [`ChannelSigner`] should be + /// re-derived through [`AnchorDescriptor::derive_channel_signer`]. The anchor input witness + /// can be computed with [`ChannelSigner::spend_holder_anchor_output`]. /// /// 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 @@ -142,9 +140,8 @@ pub enum BumpTransactionEvent { /// an empty `pending_htlcs`), confirmation of the commitment transaction can be considered to /// be not urgent. /// - /// [`EcdsaChannelSigner`]: crate::sign::ecdsa::EcdsaChannelSigner - /// [`EcdsaChannelSigner::sign_holder_anchor_input`]: crate::sign::ecdsa::EcdsaChannelSigner::sign_holder_anchor_input - /// [`build_anchor_input_witness`]: crate::ln::chan_utils::build_anchor_input_witness + /// [`ChannelSigner`]: crate::sign::ChannelSigner + /// [`ChannelSigner::spend_holder_anchor_output`]: crate::sign::ChannelSigner::spend_holder_anchor_output ChannelClose { /// The `channel_id` of the channel which has been closed. channel_id: ChannelId, diff --git a/lightning/src/sign/ecdsa.rs b/lightning/src/sign/ecdsa.rs index a0c2dbc7721..a950319d629 100644 --- a/lightning/src/sign/ecdsa.rs +++ b/lightning/src/sign/ecdsa.rs @@ -67,19 +67,6 @@ pub trait EcdsaChannelSigner: ChannelSigner { fn sign_closing_transaction( &self, closing_tx: &ClosingTransaction, secp_ctx: &Secp256k1, ) -> Result; - /// Computes the signature for a commitment transaction's anchor output used as an - /// input within `anchor_tx`, which spends the commitment transaction, at index `input`. - /// - /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid - /// signature and should be retried later. Once the signer is ready to provide a signature after - /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its - /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. - /// - /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked - /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked - fn sign_holder_anchor_input( - &self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1, - ) -> Result; /// Signs a channel announcement message with our funding key proving it comes from one of the /// channel participants. /// diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index bad92e4cb3f..cdc82c12dfd 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1033,7 +1033,16 @@ pub trait ChannelSigner { } } - /// Spend the holder anchor output + /// Computes the signature for a commitment transaction's anchor output used as an + /// input within `anchor_tx`, which spends the commitment transaction, at index `input`. + /// + /// An `Err` can be returned to signal that the signer is unavailable/cannot produce a valid + /// signature and should be retried later. Once the signer is ready to provide a signature after + /// previously returning an `Err`, [`ChannelMonitor::signer_unblocked`] must be called on its + /// monitor or [`ChainMonitor::signer_unblocked`] called to attempt unblocking all monitors. + /// + /// [`ChannelMonitor::signer_unblocked`]: crate::chain::channelmonitor::ChannelMonitor::signer_unblocked + /// [`ChainMonitor::signer_unblocked`]: crate::chain::chainmonitor::ChainMonitor::signer_unblocked fn spend_holder_anchor_output( &self, anchor_tx: &Transaction, input_idx: usize, secp_ctx: &Secp256k1, ) -> Result; @@ -1829,10 +1838,19 @@ impl ChannelSigner for InMemorySigner { fn spend_holder_anchor_output( &self, anchor_tx: &Transaction, input_idx: usize, secp_ctx: &Secp256k1, ) -> Result { - let anchor_sig = - EcdsaChannelSigner::sign_holder_anchor_input(self, anchor_tx, input_idx, secp_ctx)?; - let funding_pubkey = self.pubkeys().funding_pubkey; - Ok(chan_utils::build_anchor_input_witness(&funding_pubkey, &anchor_sig)) + let funding_pubkey = &self.pubkeys().funding_pubkey; + let witness_script = chan_utils::get_anchor_redeemscript(funding_pubkey); + let sighash = sighash::SighashCache::new(anchor_tx) + .p2wsh_signature_hash( + input_idx, + &witness_script, + Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI), + EcdsaSighashType::All, + ) + .unwrap(); + let sig = + sign_with_aux_rand(secp_ctx, &hash_to_message!(&sighash[..]), &self.funding_key, &self); + Ok(chan_utils::build_anchor_input_witness(funding_pubkey, &sig)) } } @@ -1922,22 +1940,6 @@ impl EcdsaChannelSigner for InMemorySigner { )) } - fn sign_holder_anchor_input( - &self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1, - ) -> Result { - let witness_script = - chan_utils::get_anchor_redeemscript(&self.holder_channel_pubkeys.funding_pubkey); - let sighash = sighash::SighashCache::new(&*anchor_tx) - .p2wsh_signature_hash( - input, - &witness_script, - Amount::from_sat(ANCHOR_OUTPUT_VALUE_SATOSHI), - EcdsaSighashType::All, - ) - .unwrap(); - Ok(sign_with_aux_rand(secp_ctx, &hash_to_message!(&sighash[..]), &self.funding_key, &self)) - } - fn sign_channel_announcement_with_funding_key( &self, msg: &UnsignedChannelAnnouncement, secp_ctx: &Secp256k1, ) -> Result { diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index acaa08430e7..852aa01f1d1 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -7,7 +7,6 @@ // You may not use this file except in accordance with one or both of these // licenses. -use crate::ln::channel::{ANCHOR_OUTPUT_VALUE_SATOSHI, MIN_CHAN_DUST_LIMIT_SATOSHIS}; use crate::ln::chan_utils::{self, HTLCOutputInCommitment, ChannelPublicKeys, HolderCommitmentTransaction, CommitmentTransaction, ChannelTransactionParameters, TrustedCommitmentTransaction, ClosingTransaction, TxCreationKeys}; use crate::ln::channel_keys::{HtlcKey}; use crate::ln::msgs; @@ -368,20 +367,6 @@ impl EcdsaChannelSigner for TestChannelSigner { Ok(self.inner.sign_closing_transaction(closing_tx, secp_ctx).unwrap()) } - fn sign_holder_anchor_input( - &self, anchor_tx: &Transaction, input: usize, secp_ctx: &Secp256k1, - ) -> Result { - debug_assert!(MIN_CHAN_DUST_LIMIT_SATOSHIS > ANCHOR_OUTPUT_VALUE_SATOSHI); - // As long as our minimum dust limit is enforced and is greater than our anchor output - // value, an anchor output can only have an index within [0, 1]. - assert!(anchor_tx.input[input].previous_output.vout == 0 || anchor_tx.input[input].previous_output.vout == 1); - #[cfg(test)] - if !self.is_signer_available(SignerOp::SignHolderAnchorInput) { - return Err(()); - } - EcdsaChannelSigner::sign_holder_anchor_input(&self.inner, anchor_tx, input, secp_ctx) - } - fn sign_channel_announcement_with_funding_key( &self, msg: &msgs::UnsignedChannelAnnouncement, secp_ctx: &Secp256k1 ) -> Result { From c20b8fc9bf16d5c38aa4e8fb7142062bdae5e539 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 16 Jan 2025 00:25:30 +0000 Subject: [PATCH 32/47] Add `ChannelSigner::get_to_remote_witness_weight` --- lightning/src/chain/channelmonitor.rs | 2 ++ lightning/src/sign/mod.rs | 42 +++++++++++++++++---------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index ee651271d9e..c77f54d0034 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4611,6 +4611,7 @@ impl ChannelMonitorImpl { fn get_spendable_outputs(&self, tx: &Transaction) -> Vec { let mut spendable_outputs = Vec::new(); + let signer = &self.onchain_tx_handler.signer; for (i, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == self.destination_script { spendable_outputs.push(SpendableOutputDescriptor::StaticOutput { @@ -4640,6 +4641,7 @@ impl ChannelMonitorImpl { channel_keys_id: self.channel_keys_id, channel_value_satoshis: self.channel_value_satoshis, channel_transaction_parameters: Some(self.onchain_tx_handler.channel_transaction_parameters.clone()), + witness_weight: signer.get_to_remote_witness_weight(), })); } if self.shutdown_script.as_ref() == Some(&outp.script_pubkey) { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index cdc82c12dfd..19b8896f666 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -167,6 +167,8 @@ pub struct StaticPaymentOutputDescriptor { /// /// Added as optional, but always `Some` if the descriptor was produced in v0.0.117 or later. pub channel_transaction_parameters: Option, + /// Witness weight + pub witness_weight: u64, } impl StaticPaymentOutputDescriptor { @@ -184,20 +186,6 @@ impl StaticPaymentOutputDescriptor { } }) } - - /// The maximum length a well-formed witness spending one of these should have. - /// Note: If you have the grind_signatures feature enabled, this will be at least 1 byte - /// shorter. - pub fn max_witness_length(&self) -> u64 { - if self.channel_transaction_parameters.as_ref().map_or(false, |p| p.supports_anchors()) { - let witness_script_weight = 1 /* pubkey push */ + 33 /* pubkey */ + - 1 /* OP_CHECKSIGVERIFY */ + 1 /* OP_1 */ + 1 /* OP_CHECKSEQUENCEVERIFY */; - 1 /* num witness items */ + 1 /* sig push */ + 73 /* sig including sighash flag */ + - 1 /* witness script push */ + witness_script_weight - } else { - P2WPKH_WITNESS_WEIGHT - } - } } impl_writeable_tlv_based!(StaticPaymentOutputDescriptor, { (0, outpoint, required), @@ -205,6 +193,15 @@ impl_writeable_tlv_based!(StaticPaymentOutputDescriptor, { (4, channel_keys_id, required), (6, channel_value_satoshis, required), (7, channel_transaction_parameters, option), + // Don't break downgrades ? + (9, witness_weight, (default_value, + if channel_transaction_parameters.as_ref().map_or(false, |p: &ChannelTransactionParameters| p.supports_anchors()) { + let witness_script_weight = 1 /* pubkey push */ + 33 /* pubkey */ + 1 /* OP_CHECKSIGVERIFY */ + 1 /* OP_1 */ + 1 /* OP_CHECKSEQUENCEVERIFY */; + 1 /* num witness items */ + 1 /* sig push */ + 73 /* sig including sighash flag */ + 1 /* witness script push */ + witness_script_weight + } else { + P2WPKH_WITNESS_WEIGHT + }) + ), }); /// Describes the necessary information to spend a spendable output. @@ -468,7 +465,7 @@ impl SpendableOutputDescriptor { sequence, witness: Witness::new(), }); - witness_weight += descriptor.max_witness_length(); + witness_weight += descriptor.witness_weight; #[cfg(feature = "grind_signatures")] { // Guarantees a low R signature @@ -1051,6 +1048,21 @@ pub trait ChannelSigner { fn get_holder_anchor_input_witness_weight(&self) -> u64 { ANCHOR_INPUT_WITNESS_WEIGHT } + + /// Gets the weight of the witness that spends a `to_remote` output + fn get_to_remote_witness_weight(&self) -> u64 { + // The maximum length a well-formed witness spending one of these should have. + // Note: If you have the grind_signatures feature enabled, this will be at least 1 byte + // shorter. + if self.get_channel_parameters().as_ref().map_or(false, |p| p.supports_anchors()) { + let witness_script_weight = 1 /* pubkey push */ + 33 /* pubkey */ + + 1 /* OP_CHECKSIGVERIFY */ + 1 /* OP_1 */ + 1 /* OP_CHECKSEQUENCEVERIFY */; + 1 /* num witness items */ + 1 /* sig push */ + 73 /* sig including sighash flag */ + + 1 /* witness script push */ + witness_script_weight + } else { + P2WPKH_WITNESS_WEIGHT + } + } } /// Specifies the recipient of an invoice. From 3ab029384a98ecb03db4d19b2471670dee81b967 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 16 Jan 2025 01:05:55 +0000 Subject: [PATCH 33/47] Add `ChannelSigner::get_to_local_witness_weight` --- lightning/src/chain/channelmonitor.rs | 1 + lightning/src/sign/mod.rs | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index c77f54d0034..8f8414122b6 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -4631,6 +4631,7 @@ impl ChannelMonitorImpl { channel_keys_id: self.channel_keys_id, channel_value_satoshis: self.channel_value_satoshis, channel_transaction_parameters: Some(self.onchain_tx_handler.channel_transaction_parameters.clone()), + witness_weight: signer.get_to_local_witness_weight(), })); } } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 19b8896f666..7de12287965 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -115,16 +115,8 @@ pub struct DelayedPaymentOutputDescriptor { /// /// Added as optional, but always `Some` if the descriptor was produced in v0.0.123 or later. pub channel_transaction_parameters: Option, -} - -impl DelayedPaymentOutputDescriptor { - /// The maximum length a well-formed witness spending one of these should have. - /// Note: If you have the grind_signatures feature enabled, this will be at least 1 byte - /// shorter. - // Calculated as 1 byte length + 73 byte signature, 1 byte empty vec push, 1 byte length plus - // redeemscript push length. - pub const MAX_WITNESS_LENGTH: u64 = - 1 + 73 + 1 + chan_utils::REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH as u64 + 1; + /// Witness weight + pub witness_weight: u64, } impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, { @@ -136,6 +128,8 @@ impl_writeable_tlv_based!(DelayedPaymentOutputDescriptor, { (10, channel_keys_id, required), (12, channel_value_satoshis, required), (13, channel_transaction_parameters, option), + // Don't break downgrades ? + (15, witness_weight, (default_value, 1 + 73 + 1 + chan_utils::REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH as u64 + 1)), }); pub(crate) const P2WPKH_WITNESS_WEIGHT: u64 = 1 /* num stack items */ + @@ -483,7 +477,7 @@ impl SpendableOutputDescriptor { sequence: Sequence(descriptor.to_self_delay as u32), witness: Witness::new(), }); - witness_weight += DelayedPaymentOutputDescriptor::MAX_WITNESS_LENGTH; + witness_weight += descriptor.witness_weight; #[cfg(feature = "grind_signatures")] { // Guarantees a low R signature @@ -1063,6 +1057,16 @@ pub trait ChannelSigner { P2WPKH_WITNESS_WEIGHT } } + + /// Gets the weight of the witness that spends a `to_local` output + fn get_to_local_witness_weight(&self) -> u64 { + // The maximum length a well-formed witness spending one of these should have. + // Note: If you have the grind_signatures feature enabled, this will be at least 1 byte + // shorter. + // Calculated as 1 byte length + 73 byte signature, 1 byte empty vec push, 1 byte length plus + // redeemscript push length. + 1 + 73 + 1 + chan_utils::REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH as u64 + 1 + } } /// Specifies the recipient of an invoice. From 61fc584112a7daed2b320f555fd622eddfa84698 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 16 Jan 2025 02:12:54 +0000 Subject: [PATCH 34/47] Delete `StaticPaymentOutputDescriptor::witness_script` --- lightning/src/sign/mod.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 7de12287965..39b2edf84e0 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -165,22 +165,6 @@ pub struct StaticPaymentOutputDescriptor { pub witness_weight: u64, } -impl StaticPaymentOutputDescriptor { - /// Returns the `witness_script` of the spendable output. - /// - /// Note that this will only return `Some` for [`StaticPaymentOutputDescriptor`]s that - /// originated from an anchor outputs channel, as they take the form of a P2WSH script. - pub fn witness_script(&self) -> Option { - self.channel_transaction_parameters.as_ref().and_then(|channel_params| { - if channel_params.supports_anchors() { - let payment_point = channel_params.holder_pubkeys.payment_point; - Some(chan_utils::get_to_countersignatory_with_anchors_redeemscript(&payment_point)) - } else { - None - } - }) - } -} impl_writeable_tlv_based!(StaticPaymentOutputDescriptor, { (0, outpoint, required), (2, output, required), @@ -405,10 +389,21 @@ impl SpendableOutputDescriptor { ..Default::default() } }, - SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => bitcoin::psbt::Input { - witness_utxo: Some(descriptor.output.clone()), - witness_script: descriptor.witness_script(), - ..Default::default() + SpendableOutputDescriptor::StaticPaymentOutput(descriptor) => { + let witness_script = + descriptor.channel_transaction_parameters.as_ref().and_then(|channel_params| { + channel_params.supports_anchors().then(|| { + let payment_point = channel_params.holder_pubkeys.payment_point; + chan_utils::get_to_countersignatory_with_anchors_redeemscript( + &payment_point, + ) + }) + }); + bitcoin::psbt::Input { + witness_utxo: Some(descriptor.output.clone()), + witness_script, + ..Default::default() + } }, } } From 2ed352c17bdf3d94edd4d48e768aac69a37635d5 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Thu, 16 Jan 2025 22:35:57 +0000 Subject: [PATCH 35/47] Delete `HTLCDescriptor::tx_input_witness` --- lightning/src/events/bump_transaction.rs | 6 ++---- lightning/src/sign/mod.rs | 14 +------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/lightning/src/events/bump_transaction.rs b/lightning/src/events/bump_transaction.rs index 6daa25dfd7c..6af3ec2b527 100644 --- a/lightning/src/events/bump_transaction.rs +++ b/lightning/src/events/bump_transaction.rs @@ -182,10 +182,8 @@ pub enum BumpTransactionEvent { /// /// 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 [`EcdsaChannelSigner`] should be re-derived - /// through [`HTLCDescriptor::derive_channel_signer`]. Each HTLC input's signature can be - /// computed with [`ChannelSigner::sign_holder_htlc_transaction`], which can then be - /// provided to [`HTLCDescriptor::tx_input_witness`] to obtain the fully signed witness required - /// to spend. + /// through [`HTLCDescriptor::derive_channel_signer`]. Each HTLC input's witness can be + /// computed with [`ChannelSigner::sign_holder_htlc_transaction`]. /// /// 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 diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 39b2edf84e0..2cf63559b50 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -18,7 +18,7 @@ use bitcoin::ecdsa::Signature as EcdsaSignature; use bitcoin::locktime::absolute::LockTime; use bitcoin::network::Network; use bitcoin::opcodes; -use bitcoin::script::{Builder, Script, ScriptBuf}; +use bitcoin::script::{Builder, ScriptBuf}; use bitcoin::sighash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::transaction::Version; @@ -626,18 +626,6 @@ impl HTLCDescriptor { ) } - /// 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, - self.channel_derivation_parameters.transaction_parameters.channel_type_features(), - ) - } - /// Derives the channel signer required to sign the HTLC input. pub fn derive_channel_signer(&self, signer_provider: &SP) -> S where From fd1a47ab797d415d90e4282606a673f7d47034a2 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 18 Jan 2025 03:32:38 +0000 Subject: [PATCH 36/47] Delete `TrustedCommitmentTransaction::get_htlc_sigs` --- lightning/src/ln/chan_utils.rs | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 6a0f6dffe95..a4ae6fd11af 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -698,9 +698,8 @@ pub(crate) fn make_funding_redeemscript_from_slices(broadcaster_funding_key: &[u } /// Builds an unsigned HTLC-Success or HTLC-Timeout transaction from the given channel and HTLC -/// parameters. This is used by [`TrustedCommitmentTransaction::get_htlc_sigs`] to fetch the -/// transaction which needs signing, and can be used to construct an HTLC transaction which is -/// broadcastable given a counterparty HTLC signature. +/// parameters. This is can be used to construct an HTLC transaction which is broadcastable given a +/// counterparty HTLC signature. /// /// Panics if htlc.transaction_output_index.is_none() (as such HTLCs do not appear in the /// commitment transaction). @@ -1713,33 +1712,6 @@ impl<'a> TrustedCommitmentTransaction<'a> { &self.inner.channel_type_features } - /// Get a signature for each HTLC which was included in the commitment transaction (ie for - /// which HTLCOutputInCommitment::transaction_output_index.is_some()). - /// - /// The returned Vec has one entry for each HTLC, and in the same order. - /// - /// This function is only valid in the holder commitment context, it always uses EcdsaSighashType::All. - pub fn get_htlc_sigs( - &self, htlc_base_key: &SecretKey, entropy_source: &ES, secp_ctx: &Secp256k1, revokeable_spk: ScriptBuf, - ) -> Result, ()> where ES::Target: EntropySource { - let inner = self.inner; - let keys = &inner.keys; - let txid = inner.built.txid; - let mut ret = Vec::with_capacity(inner.htlcs.len()); - let holder_htlc_key = derive_private_key(secp_ctx, &inner.keys.per_commitment_point, htlc_base_key); - - for this_htlc in inner.htlcs.iter() { - assert!(this_htlc.transaction_output_index.is_some()); - let htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, &this_htlc, &self.channel_type_features, revokeable_spk.clone()); - - let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key); - - let sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).p2wsh_signature_hash(0, &htlc_redeemscript, this_htlc.to_bitcoin_amount(), EcdsaSighashType::All).unwrap()[..]); - ret.push(sign_with_aux_rand(secp_ctx, &sighash, &holder_htlc_key, entropy_source)); - } - Ok(ret) - } - /// Builds the second-level holder HTLC transaction for the HTLC with index `htlc_index`. pub(crate) fn build_unsigned_htlc_tx(&self, htlc_index: usize, preimage: &Option, revokeable_spk: ScriptBuf) -> Transaction { let this_htlc = &self.inner.htlcs[htlc_index]; From 6ea558922b3ad34c2a246f811d8ea3e33607d377 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 18 Jan 2025 18:04:55 +0000 Subject: [PATCH 37/47] Move commitment tx sig validation from `FundedChannel` to `ChannelSigner` --- lightning/src/ln/channel.rs | 44 +++++++++-------------- lightning/src/sign/mod.rs | 34 +++++++++++++----- lightning/src/util/test_channel_signer.rs | 8 +++-- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index ea4ad2ba0d7..604fa6313df 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1830,7 +1830,7 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide context.counterparty_funding_pubkey() ); - if context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new()).is_err() { + if context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new(), &context.secp_ctx).is_err() { return Err(ChannelError::close("Failed to validate our commitment".to_owned())); } @@ -5245,23 +5245,28 @@ impl FundedChannel where return Err(ChannelError::close("Peer sent commitment_signed after we'd started exchanging closing_signeds".to_owned())); } - let funding_script = self.context.get_funding_redeemscript(); - let keys = self.context.build_holder_transaction_keys(self.holder_commitment_point.current_point()); let commitment_stats = self.context.build_commitment_transaction(self.holder_commitment_point.transaction_number(), &keys, true, false, logger); + + if msg.htlc_signatures.len() != commitment_stats.num_nondust_htlcs { + return Err(ChannelError::close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_stats.num_nondust_htlcs))); + } + + let holder_commitment_tx = HolderCommitmentTransaction::new( + commitment_stats.tx.clone(), + msg.signature, + msg.htlc_signatures.clone(), + &self.context.get_holder_pubkeys().funding_pubkey, + self.context.counterparty_funding_pubkey() + ); + + self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_stats.outbound_htlc_preimages, &self.context.secp_ctx) + .map_err(|_| ChannelError::close("Failed to validate our commitment".to_owned()))?; + let commitment_txid = { let trusted_tx = commitment_stats.tx.trust(); let bitcoin_tx = trusted_tx.built_transaction(); - let sighash = bitcoin_tx.get_sighash_all(&funding_script, self.context.channel_value_satoshis); - - log_trace!(logger, "Checking commitment tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}", - log_bytes!(msg.signature.serialize_compact()[..]), - log_bytes!(self.context.counterparty_funding_pubkey().serialize()), encode::serialize_hex(&bitcoin_tx.transaction), - log_bytes!(sighash[..]), encode::serialize_hex(&funding_script), &self.context.channel_id()); - if let Err(_) = self.context.secp_ctx.verify_ecdsa(&sighash, &msg.signature, &self.context.counterparty_funding_pubkey()) { - return Err(ChannelError::close("Invalid commitment tx signature from peer".to_owned())); - } bitcoin_tx.txid }; let mut htlcs_cloned: Vec<_> = commitment_stats.htlcs_included.iter().map(|htlc| (htlc.0.clone(), htlc.1.map(|h| h.clone()))).collect(); @@ -5296,10 +5301,6 @@ impl FundedChannel where } } - if msg.htlc_signatures.len() != commitment_stats.num_nondust_htlcs { - return Err(ChannelError::close(format!("Got wrong number of HTLC signatures ({}) from remote. It must be {}", msg.htlc_signatures.len(), commitment_stats.num_nondust_htlcs))); - } - // Up to LDK 0.0.115, HTLC information was required to be duplicated in the // `htlcs_and_sigs` vec and in the `holder_commitment_tx` itself, both of which were passed // in the `ChannelMonitorUpdate`. In 0.0.115, support for having a separate set of @@ -5346,17 +5347,6 @@ impl FundedChannel where debug_assert!(source_opt.is_none(), "HTLCSource should have been put somewhere"); } - let holder_commitment_tx = HolderCommitmentTransaction::new( - commitment_stats.tx, - msg.signature, - msg.htlc_signatures.clone(), - &self.context.get_holder_pubkeys().funding_pubkey, - self.context.counterparty_funding_pubkey() - ); - - self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_stats.outbound_htlc_preimages) - .map_err(|_| ChannelError::close("Failed to validate our commitment".to_owned()))?; - // Update state now that we've passed all the can-fail calls... let mut need_commitment = false; if let &mut Some((_, ref mut update_state)) = &mut self.context.pending_update_fee { diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 2cf63559b50..8505943e4ad 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -697,8 +697,24 @@ pub trait ChannelSigner { /// and pause future signing operations until this validation completes. fn validate_holder_commitment( &self, holder_tx: &HolderCommitmentTransaction, - outbound_htlc_preimages: Vec, - ) -> Result<(), ()>; + _outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1, + ) -> Result<(), ()> { + let channel_value_satoshis = self.get_channel_value_satoshis(); + let params = self.get_channel_parameters().unwrap(); + let holder_pubkey = params.holder_pubkeys.funding_pubkey; + let counterparty_pubkey = + params.counterparty_parameters.as_ref().unwrap().pubkeys.funding_pubkey; + let funding_redeemscript = make_funding_redeemscript(&holder_pubkey, &counterparty_pubkey); + let trusted_tx = holder_tx.trust(); + let bitcoin_tx = trusted_tx.built_transaction(); + let sighash = bitcoin_tx.get_sighash_all(&funding_redeemscript, channel_value_satoshis); + if let Err(_) = + secp_ctx.verify_ecdsa(&sighash, &holder_tx.counterparty_sig, &counterparty_pubkey) + { + return Err(()); + } + Ok(()) + } /// Validate the counterparty's revocation. /// @@ -736,6 +752,9 @@ pub trait ChannelSigner { /// Returns the parameters of this signer fn get_channel_parameters(&self) -> Option<&ChannelTransactionParameters>; + /// Return the channel value + fn get_channel_value_satoshis(&self) -> u64; + /// Returns the script pubkey that should be placed in the `to_remote` output of commitment /// transactions. /// @@ -1602,13 +1621,6 @@ impl ChannelSigner for InMemorySigner { Ok(chan_utils::build_commitment_secret(&self.commitment_seed, idx)) } - fn validate_holder_commitment( - &self, _holder_tx: &HolderCommitmentTransaction, - _outbound_htlc_preimages: Vec, - ) -> Result<(), ()> { - Ok(()) - } - fn validate_counterparty_revocation(&self, _idx: u64, _secret: &SecretKey) -> Result<(), ()> { Ok(()) } @@ -1851,6 +1863,10 @@ impl ChannelSigner for InMemorySigner { sign_with_aux_rand(secp_ctx, &hash_to_message!(&sighash[..]), &self.funding_key, &self); Ok(chan_utils::build_anchor_input_witness(funding_pubkey, &sig)) } + + fn get_channel_value_satoshis(&self) -> u64 { + self.channel_value_satoshis + } } const MISSING_PARAMS_ERR: &'static str = diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 852aa01f1d1..2ac4e8314f1 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -191,12 +191,12 @@ impl ChannelSigner for TestChannelSigner { self.inner.release_commitment_secret(idx) } - fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction, _outbound_htlc_preimages: Vec) -> Result<(), ()> { + fn validate_holder_commitment(&self, holder_tx: &HolderCommitmentTransaction, outbound_htlc_preimages: Vec, secp_ctx: &Secp256k1) -> Result<(), ()> { let mut state = self.state.lock().unwrap(); let idx = holder_tx.commitment_number(); assert!(idx == state.last_holder_commitment || idx == state.last_holder_commitment - 1, "expecting to validate the current or next holder commitment - trying {}, current {}", idx, state.last_holder_commitment); state.last_holder_commitment = idx; - Ok(()) + self.inner.validate_holder_commitment(holder_tx, outbound_htlc_preimages, secp_ctx) } fn validate_counterparty_revocation(&self, idx: u64, _secret: &SecretKey) -> Result<(), ()> { @@ -331,6 +331,10 @@ impl ChannelSigner for TestChannelSigner { ) -> Result { self.inner.spend_holder_anchor_output(anchor_tx, input_idx, secp_ctx) } + + fn get_channel_value_satoshis(&self) -> u64 { + self.inner.get_channel_value_satoshis() + } } impl EcdsaChannelSigner for TestChannelSigner { From e2f73a5e43be1f6ff383c5aedb62ceec1fef8194 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 18 Jan 2025 18:06:23 +0000 Subject: [PATCH 38/47] Move htlc tx sig validation from `FundedChannel` to `ChannelSigner` --- lightning/src/ln/channel.rs | 21 +------------- lightning/src/sign/mod.rs | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 604fa6313df..2a68c7aa7b2 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -11,7 +11,6 @@ use bitcoin::amount::Amount; use bitcoin::constants::ChainHash; 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; @@ -5254,7 +5253,7 @@ impl FundedChannel where } let holder_commitment_tx = HolderCommitmentTransaction::new( - commitment_stats.tx.clone(), + commitment_stats.tx, msg.signature, msg.htlc_signatures.clone(), &self.context.get_holder_pubkeys().funding_pubkey, @@ -5264,11 +5263,6 @@ impl FundedChannel where self.context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, commitment_stats.outbound_htlc_preimages, &self.context.secp_ctx) .map_err(|_| ChannelError::close("Failed to validate our commitment".to_owned()))?; - let commitment_txid = { - let trusted_tx = commitment_stats.tx.trust(); - let bitcoin_tx = trusted_tx.built_transaction(); - bitcoin_tx.txid - }; let mut htlcs_cloned: Vec<_> = commitment_stats.htlcs_included.iter().map(|htlc| (htlc.0.clone(), htlc.1.map(|h| h.clone()))).collect(); // If our counterparty updated the channel fee in this commitment transaction, check that @@ -5318,21 +5312,8 @@ impl FundedChannel where let mut nondust_htlc_sources = Vec::with_capacity(htlcs_cloned.len()); let mut htlcs_and_sigs = Vec::with_capacity(htlcs_cloned.len()); - let revokeable_spk = self.context.holder_signer.as_ref().get_revokeable_spk(true, commitment_stats.tx.commitment_number(), &commitment_stats.tx.per_commitment_point(), &self.context.secp_ctx); for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() { if let Some(_) = htlc.transaction_output_index { - let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.feerate_per_kw, - &htlc, &self.context.channel_type, revokeable_spk.clone()); - - let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &keys); - let htlc_sighashtype = if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All }; - let htlc_sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).p2wsh_signature_hash(0, &htlc_redeemscript, htlc.to_bitcoin_amount(), htlc_sighashtype).unwrap()[..]); - log_trace!(logger, "Checking HTLC tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}.", - log_bytes!(msg.htlc_signatures[idx].serialize_compact()[..]), log_bytes!(keys.countersignatory_htlc_key.to_public_key().serialize()), - encode::serialize_hex(&htlc_tx), log_bytes!(htlc_sighash[..]), encode::serialize_hex(&htlc_redeemscript), &self.context.channel_id()); - if let Err(_) = self.context.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &keys.countersignatory_htlc_key.to_public_key()) { - return Err(ChannelError::close("Invalid HTLC tx signature from peer".to_owned())); - } if !separate_nondust_htlc_sources { htlcs_and_sigs.push((htlc, Some(msg.htlc_signatures[idx]), source_opt.take())); } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 8505943e4ad..a21e0250946 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -713,6 +713,62 @@ pub trait ChannelSigner { { return Err(()); } + + if holder_tx.htlcs().len() != holder_tx.counterparty_htlc_sigs.len() { + return Err(()); + } + + let commitment_txid = bitcoin_tx.txid; + let revokeable_spk = self.get_revokeable_spk( + true, + holder_tx.commitment_number(), + &holder_tx.per_commitment_point(), + secp_ctx, + ); + let params = self.get_channel_parameters().unwrap().as_holder_broadcastable(); + let keys = TxCreationKeys::from_channel_static_keys( + &holder_tx.per_commitment_point(), + params.broadcaster_pubkeys(), + params.countersignatory_pubkeys(), + secp_ctx, + ); + for (idx, htlc) in holder_tx.htlcs().iter().enumerate() { + if let Some(_) = htlc.transaction_output_index { + let htlc_tx = chan_utils::build_htlc_transaction( + &commitment_txid, + holder_tx.feerate_per_kw(), + &htlc, + params.channel_type_features(), + revokeable_spk.clone(), + ); + let htlc_redeemscript = + chan_utils::get_htlc_redeemscript(&htlc, params.channel_type_features(), &keys); + let htlc_sighashtype = + if params.channel_type_features().supports_anchors_zero_fee_htlc_tx() { + EcdsaSighashType::SinglePlusAnyoneCanPay + } else { + EcdsaSighashType::All + }; + let htlc_sighash = hash_to_message!( + &sighash::SighashCache::new(&htlc_tx) + .p2wsh_signature_hash( + 0, + &htlc_redeemscript, + htlc.to_bitcoin_amount(), + htlc_sighashtype + ) + .unwrap()[..] + ); + secp_ctx + .verify_ecdsa( + &htlc_sighash, + &holder_tx.counterparty_htlc_sigs[idx], + &keys.countersignatory_htlc_key.to_public_key(), + ) + .map_err(|_| ())?; + } + } + Ok(()) } From ccf9824f6dc335c7ee9cd90afab6e675db3504e1 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 18 Jan 2025 20:41:16 +0000 Subject: [PATCH 39/47] Delete `InitialRemoteCommitmentReceiver::check_counterparty_commitment_signature` --- lightning/src/ln/channel.rs | 60 ++++++++-------------------- lightning/src/ln/functional_tests.rs | 2 +- 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 2a68c7aa7b2..81ee7b37ee3 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -1770,26 +1770,6 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide fn received_msg(&self) -> &'static str; - fn check_counterparty_commitment_signature( - &self, sig: &Signature, holder_commitment_point: &mut HolderCommitmentPoint, logger: &L - ) -> Result where L::Target: Logger { - let funding_script = self.context().get_funding_redeemscript(); - - let keys = self.context().build_holder_transaction_keys(holder_commitment_point.current_point()); - let initial_commitment_tx = self.context().build_commitment_transaction(holder_commitment_point.transaction_number(), &keys, 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 the holder commitment transaction... - log_trace!(logger, "Checking {} tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} for channel {}.", - self.received_msg(), log_bytes!(sig.serialize_compact()[..]), log_bytes!(self.context().counterparty_funding_pubkey().serialize()), - encode::serialize_hex(&initial_commitment_bitcoin_tx.transaction), log_bytes!(sighash[..]), - encode::serialize_hex(&funding_script), &self.context().channel_id()); - secp_check!(self.context().secp_ctx.verify_ecdsa(&sighash, sig, self.context().counterparty_funding_pubkey()), format!("Invalid {} signature from peer", self.received_msg())); - - Ok(initial_commitment_tx) - } - fn initial_commitment_signed( &mut self, channel_id: ChannelId, counterparty_signature: Signature, holder_commitment_point: &mut HolderCommitmentPoint, counterparty_commitment_number: u64, best_block: BestBlock, signer_provider: &SP, logger: &L, @@ -1797,30 +1777,9 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide where L::Target: Logger { - let initial_commitment_tx = match self.check_counterparty_commitment_signature(&counterparty_signature, holder_commitment_point, logger) { - Ok(res) => res, - Err(ChannelError::Close(e)) => { - // TODO(dual_funding): Update for V2 established channels. - if !self.context().is_outbound() { - self.context_mut().channel_transaction_parameters.funding_outpoint = None; - } - return Err(ChannelError::Close(e)); - }, - Err(e) => { - // The only error we know how to handle is ChannelError::Close, so we fall over here - // to make sure we don't continue with an inconsistent state. - panic!("unexpected error type from check_counterparty_commitment_signature {:?}", e); - } - }; - let context = self.context_mut(); - 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; - 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 {}", - &context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); - + let context = self.context(); + let keys = context.build_holder_transaction_keys(holder_commitment_point.current_point()); + let initial_commitment_tx = context.build_commitment_transaction(holder_commitment_point.transaction_number(), &keys, true, false, logger).tx; let holder_commitment_tx = HolderCommitmentTransaction::new( initial_commitment_tx, counterparty_signature, @@ -1830,10 +1789,23 @@ trait InitialRemoteCommitmentReceiver where SP::Target: SignerProvide ); if context.holder_signer.as_ref().validate_holder_commitment(&holder_commitment_tx, Vec::new(), &context.secp_ctx).is_err() { + // TODO(dual_funding): Update for V2 established channels. + if !self.context().is_outbound() { + self.context_mut().channel_transaction_parameters.funding_outpoint = None; + } return Err(ChannelError::close("Failed to validate our commitment".to_owned())); } + 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; + 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 {}", + &context.channel_id(), counterparty_initial_bitcoin_tx.txid, encode::serialize_hex(&counterparty_initial_bitcoin_tx.transaction)); + // Now that we're past error-generating stuff, update our local state: + let context = self.context_mut(); context.channel_id = channel_id; diff --git a/lightning/src/ln/functional_tests.rs b/lightning/src/ln/functional_tests.rs index 8e0dfdf1122..7a2a91c6637 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -9113,7 +9113,7 @@ fn test_duplicate_funding_err_in_funding() { funding_created_msg.funding_output_index += 10; nodes[1].node.handle_funding_created(nodes[2].node.get_our_node_id(), &funding_created_msg); get_err_msg(&nodes[1], &nodes[2].node.get_our_node_id()); - let err = "Invalid funding_created signature from peer".to_owned(); + let err = "Failed to validate our commitment".to_owned(); let reason = ClosureReason::ProcessingError { err }; let expected_closing = ExpectedCloseEvent::from_id_reason(real_channel_id, false, reason); check_closed_events(&nodes[1], &[expected_closing]); From 5fc079b60b5983526b0959b1cbcd227d6dc31c0a Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Sat, 18 Jan 2025 21:26:48 +0000 Subject: [PATCH 40/47] Call `validate_counterparty_revocation` on `ChannelSigner` directly --- lightning/src/ln/channel.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 81ee7b37ee3..b7c2f32582c 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -5598,17 +5598,8 @@ impl FundedChannel where *self.context.next_remote_commitment_tx_fee_info_cached.lock().unwrap() = None; } - match &self.context.holder_signer { - ChannelSignerType::Ecdsa(ecdsa) => { - ecdsa.validate_counterparty_revocation( - self.context.cur_counterparty_commitment_transaction_number + 1, - &secret - ).map_err(|_| ChannelError::close("Failed to validate revocation from peer".to_owned()))?; - }, - // TODO (taproot|arik) - #[cfg(taproot)] - _ => todo!() - }; + self.context.holder_signer.as_ref().validate_counterparty_revocation(self.context.cur_counterparty_commitment_transaction_number + 1, &secret) + .map_err(|_| ChannelError::close("Failed to validate revocation from peer".to_owned()))?; self.context.commitment_secrets.provide_secret(self.context.cur_counterparty_commitment_transaction_number + 1, msg.per_commitment_secret) .map_err(|_| ChannelError::close("Previous secrets did not match new one".to_owned()))?; From 79f1ca845685ba72de16bae486f5134dec7eafb7 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 27 Jan 2025 18:56:14 +0000 Subject: [PATCH 41/47] f: Make id of revoked outputs in HTLC transactions generic over HTLC script For a given commitment number, all revokeable outputs of HTLC transactions have the same script pubkey. If we know the secret for that commitment number, any outputs with that script pubkey are `RevokedOutput`s. This patch removes any checks on the input at the index of the revoked output, as these are not necessary. --- lightning/src/chain/channelmonitor.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lightning/src/chain/channelmonitor.rs b/lightning/src/chain/channelmonitor.rs index 8f8414122b6..7ea4208e36f 100644 --- a/lightning/src/chain/channelmonitor.rs +++ b/lightning/src/chain/channelmonitor.rs @@ -3663,7 +3663,7 @@ 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, commitment_txid: &Txid, height: u32, logger: &L + &mut self, tx: &Transaction, commitment_number: u64, 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 = match SecretKey::from_slice(&secret) { @@ -3671,6 +3671,7 @@ impl ChannelMonitorImpl { Err(_) => return (Vec::new(), None) }; let per_commitment_point = PublicKey::from_secret_key(&self.onchain_tx_handler.secp_ctx, &per_commitment_key); + let revokeable_spk = self.onchain_tx_handler.signer.get_revokeable_spk(false, commitment_number, &per_commitment_point, &self.onchain_tx_handler.secp_ctx); let punishment_witness_weight = self.onchain_tx_handler.signer.get_punishment_witness_weight(); let htlc_txid = tx.compute_txid(); @@ -3683,11 +3684,10 @@ impl ChannelMonitorImpl { // 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() { + // We make sure we're not vulnerable to this case by checking all outputs of the transaction, + // and claim those which have the expected revokeable script pubkey for the given commitment number. + for (idx, outp) in tx.output.iter().enumerate() { + if revokeable_spk == outp.script_pubkey { log_error!(logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}", htlc_txid, idx); let revk_outp = RevokedOutput::build( per_commitment_point, @@ -4063,7 +4063,7 @@ impl ChannelMonitorImpl { 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 + &tx, commitment_number, height, &logger ); claimable_outpoints.append(&mut new_outpoints); if let Some(new_outputs) = new_outputs_option { From 0c2b44b9ff5e7524693255b65d542c18907052dc Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 20 Jan 2025 22:35:09 +0000 Subject: [PATCH 42/47] f: Make Counterparty{*}HTLCOutput ser backwards compatible --- lightning/src/chain/package.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index bab3b28f98b..9491dbbc46f 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -246,7 +246,7 @@ impl Writeable for CounterpartyOfferedHTLCOutput { (8, self.htlc, required), (10, legacy_deserialization_prevention_marker, option), (11, self.channel_type_features, required), - (12, self.weight, required), + (13, self.weight, required), }); Ok(()) } @@ -261,7 +261,7 @@ impl Readable for CounterpartyOfferedHTLCOutput { let mut htlc = RequiredWrapper(None); let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; - let mut weight = None; + let mut weight = RequiredWrapper(None); read_tlv_fields!(reader, { (0, per_commitment_point, required), @@ -271,13 +271,12 @@ impl Readable for CounterpartyOfferedHTLCOutput { (8, htlc, required), (10, _legacy_deserialization_prevention_marker, option), (11, channel_type_features, option), - (12, weight, option), + (13, weight, (default_value, weight_offered_htlc(channel_type_features.as_ref().unwrap_or(&ChannelTypeFeatures::only_static_remote_key())))), }); verify_channel_type_features(&channel_type_features, None)?; let channel_type_features = channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()); - let weight = weight.unwrap_or(weight_offered_htlc(&channel_type_features)); Ok(Self { per_commitment_point: per_commitment_point.0.unwrap(), @@ -286,7 +285,7 @@ impl Readable for CounterpartyOfferedHTLCOutput { preimage: preimage.0.unwrap(), htlc: htlc.0.unwrap(), channel_type_features, - weight, + weight: weight.0.unwrap(), }) } } @@ -330,7 +329,7 @@ impl Writeable for CounterpartyReceivedHTLCOutput { (6, self.htlc, required), (8, legacy_deserialization_prevention_marker, option), (9, self.channel_type_features, required), - (10, self.weight, required), + (11, self.weight, required), }); Ok(()) } @@ -344,7 +343,7 @@ impl Readable for CounterpartyReceivedHTLCOutput { let mut htlc = RequiredWrapper(None); let mut _legacy_deserialization_prevention_marker: Option<()> = None; let mut channel_type_features = None; - let mut weight = None; + let mut weight = RequiredWrapper(None); read_tlv_fields!(reader, { (0, per_commitment_point, required), @@ -353,13 +352,12 @@ impl Readable for CounterpartyReceivedHTLCOutput { (6, htlc, required), (8, _legacy_deserialization_prevention_marker, option), (9, channel_type_features, option), - (10, weight, option), + (11, weight, (default_value, weight_received_htlc(channel_type_features.as_ref().unwrap_or(&ChannelTypeFeatures::only_static_remote_key())))), }); verify_channel_type_features(&channel_type_features, None)?; let channel_type_features = channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()); - let weight = weight.unwrap_or(weight_received_htlc(&channel_type_features)); Ok(Self { per_commitment_point: per_commitment_point.0.unwrap(), @@ -367,7 +365,7 @@ impl Readable for CounterpartyReceivedHTLCOutput { counterparty_htlc_base_key, htlc: htlc.0.unwrap(), channel_type_features, - weight, + weight: weight.0.unwrap(), }) } } From 7484c3be291819eb75b08bad107243b6cb778a56 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 27 Jan 2025 03:18:18 +0000 Subject: [PATCH 43/47] f: Use `ChannelSigner::get_anchor_txout` to id the anchor output --- lightning/src/chain/onchaintx.rs | 6 +++--- lightning/src/ln/chan_utils.rs | 8 -------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lightning/src/chain/onchaintx.rs b/lightning/src/chain/onchaintx.rs index 1ed7ffbc13d..df48e9da22c 100644 --- a/lightning/src/chain/onchaintx.rs +++ b/lightning/src/chain/onchaintx.rs @@ -28,7 +28,7 @@ use crate::chain::chaininterface::{ConfirmationTarget, compute_feerate_sat_per_1 use crate::sign::{ChannelDerivationParameters, HTLCDescriptor, ChannelSigner, EntropySource, SignerProvider}; use crate::ln::msgs::DecodeError; use crate::types::payment::PaymentPreimage; -use crate::ln::chan_utils::{self, ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction}; +use crate::ln::chan_utils::{ChannelTransactionParameters, HTLCOutputInCommitment, HolderCommitmentTransaction}; use crate::chain::ClaimId; use crate::chain::chaininterface::{FeeEstimator, BroadcasterInterface, LowerBoundedFeeEstimator}; use crate::chain::channelmonitor::ANTI_REORG_DELAY; @@ -665,8 +665,8 @@ impl OnchainTxHandler { } // We'll locate an anchor output we can spend within the commitment transaction. - let funding_pubkey = &self.channel_transaction_parameters.holder_pubkeys.funding_pubkey; - match chan_utils::get_anchor_output(&tx.0, funding_pubkey) { + let anchor_spk = self.signer.get_anchor_txout(true, true).expect("signer does not support anchors").script_pubkey; + match tx.0.output.iter().enumerate().find(|(_, txout)| anchor_spk == txout.script_pubkey).map(|(idx, txout)| (idx as u32, txout)) { // An anchor output was found, so we should yield a funding event externally. Some((idx, _)) => { // TODO: Use a lower confirmation target when both our and the diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index a4ae6fd11af..8c505c179bf 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -840,14 +840,6 @@ pub fn get_anchor_redeemscript(funding_pubkey: &PublicKey) -> ScriptBuf { .into_script() } -/// Locates the output with an anchor script paying to `funding_pubkey` within `commitment_tx`. -pub(crate) fn get_anchor_output<'a>(commitment_tx: &'a Transaction, funding_pubkey: &PublicKey) -> Option<(u32, &'a TxOut)> { - let anchor_script = get_anchor_redeemscript(funding_pubkey).to_p2wsh(); - commitment_tx.output.iter().enumerate() - .find(|(_, txout)| txout.script_pubkey == anchor_script) - .map(|(idx, txout)| (idx as u32, txout)) -} - /// Returns the witness required to satisfy and spend an anchor input. pub fn build_anchor_input_witness(funding_key: &PublicKey, funding_sig: &Signature) -> Witness { let anchor_redeem_script = get_anchor_redeemscript(funding_key); From 9ab694829a5a1c9dfd23e268d57e8fa75242ce44 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 27 Jan 2025 00:45:46 +0000 Subject: [PATCH 44/47] Remove generic from `CommitmentTransaction::internal_build_outputs` --- lightning/src/ln/chan_utils.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 8c505c179bf..4bc02f82072 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1452,7 +1452,8 @@ impl CommitmentTransaction { let to_countersignatory_value_sat = Amount::from_sat(to_countersignatory_value_sat); // Sort outputs and populate output indices while keeping track of the auxiliary data - let (outputs, htlcs) = Self::internal_build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); + let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs_with_aux.iter_mut().map(|(htlc, _)| htlc).collect(); + let (outputs, sorted_htlcs) = Self::internal_build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); @@ -1463,7 +1464,7 @@ impl CommitmentTransaction { to_countersignatory_value_sat, to_broadcaster_delay: Some(channel_parameters.contest_delay()), feerate_per_kw, - htlcs, + htlcs: sorted_htlcs, channel_type_features: channel_parameters.channel_type_features().clone(), keys, built: BuiltCommitmentTransaction { @@ -1484,8 +1485,9 @@ impl CommitmentTransaction { fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, channel_parameters: &DirectedChannelTransactionParameters, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters); - let mut htlcs_with_aux = self.htlcs.iter().map(|h| (h.clone(), ())).collect(); - let (outputs, _) = Self::internal_build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, signer, secp_ctx, is_holder_tx, self.commitment_number)?; + let mut htlcs: Vec<_> = self.htlcs.iter().map(|h| h.clone()).collect(); + let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs.iter_mut().collect(); + let (outputs, _) = Self::internal_build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, htlcs, signer, secp_ctx, is_holder_tx, self.commitment_number)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); @@ -1506,10 +1508,9 @@ impl CommitmentTransaction { } // This is used in two cases: - // - initial sorting of outputs / HTLCs in the constructor, in which case T is auxiliary data the - // caller needs to have sorted together with the HTLCs so it can keep track of the output index - // - building of a bitcoin transaction during a verify() call, in which case T is just () - fn internal_build_outputs(per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { + // - initial sorting of outputs / HTLCs in the constructor + // - building of a bitcoin transaction during a verify() call + fn internal_build_outputs(per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs: Vec<&mut HTLCOutputInCommitment>, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); if to_countersignatory_value_sat > Amount::ZERO { @@ -1532,20 +1533,20 @@ impl CommitmentTransaction { )); } - if to_broadcaster_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() { + if to_broadcaster_value_sat > Amount::ZERO || !htlcs.is_empty() { if let Some(txout) = signer.get_anchor_txout(is_holder_tx, true) { txouts.push((txout, None)); } } - if to_countersignatory_value_sat > Amount::ZERO || !htlcs_with_aux.is_empty() { + if to_countersignatory_value_sat > Amount::ZERO || !htlcs.is_empty() { if let Some(txout) = signer.get_anchor_txout(is_holder_tx, false) { txouts.push((txout, None)); } } - let mut htlcs = Vec::with_capacity(htlcs_with_aux.len()); - for (htlc, _) in htlcs_with_aux { + let mut sorted_htlcs = Vec::with_capacity(htlcs.len()); + for htlc in htlcs { let txout = TxOut { script_pubkey: signer.get_htlc_spk(htlc, is_holder_tx, per_commitment_point, secp_ctx), value: htlc.to_bitcoin_amount(), @@ -1573,11 +1574,11 @@ impl CommitmentTransaction { for (idx, out) in txouts.drain(..).enumerate() { if let Some(htlc) = out.1 { htlc.transaction_output_index = Some(idx as u32); - htlcs.push(htlc.clone()); + sorted_htlcs.push(htlc.clone()); } outputs.push(out.0); } - Ok((outputs, htlcs)) + Ok((outputs, sorted_htlcs)) } fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec) { From bb16170ab3a76f16b9f525287477fc67aac60528 Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 27 Jan 2025 00:56:23 +0000 Subject: [PATCH 45/47] Let `ChannelSigner` build the outputs of the commit tx --- lightning/src/ln/chan_utils.rs | 79 +-------------------------- lightning/src/sign/mod.rs | 98 ++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 77 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 4bc02f82072..8e811406173 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -40,7 +40,6 @@ use bitcoin::{secp256k1, Sequence, Witness}; use crate::io; use core::cmp; -use crate::util::transaction_utils::sort_outputs; use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use core::ops::Deref; use crate::chain; @@ -1453,7 +1452,7 @@ impl CommitmentTransaction { // Sort outputs and populate output indices while keeping track of the auxiliary data let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs_with_aux.iter_mut().map(|(htlc, _)| htlc).collect(); - let (outputs, sorted_htlcs) = Self::internal_build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs, signer, secp_ctx, is_holder_tx, commitment_number).unwrap(); + let (outputs, sorted_htlcs) = signer.build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, commitment_number).unwrap(); let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); @@ -1487,7 +1486,7 @@ impl CommitmentTransaction { let mut htlcs: Vec<_> = self.htlcs.iter().map(|h| h.clone()).collect(); let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs.iter_mut().collect(); - let (outputs, _) = Self::internal_build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, htlcs, signer, secp_ctx, is_holder_tx, self.commitment_number)?; + let (outputs, _) = signer.build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, self.commitment_number)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); @@ -1507,80 +1506,6 @@ impl CommitmentTransaction { } } - // This is used in two cases: - // - initial sorting of outputs / HTLCs in the constructor - // - building of a bitcoin transaction during a verify() call - fn internal_build_outputs(per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, to_countersignatory_value_sat: Amount, htlcs: Vec<&mut HTLCOutputInCommitment>, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64) -> Result<(Vec, Vec), ()> { - let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); - - if to_countersignatory_value_sat > Amount::ZERO { - txouts.push(( - TxOut { - script_pubkey: signer.get_counterparty_payment_script(is_holder_tx), - value: to_countersignatory_value_sat, - }, - None, - )) - } - - if to_broadcaster_value_sat > Amount::ZERO { - txouts.push(( - TxOut { - script_pubkey: signer.get_revokeable_spk(is_holder_tx, commitment_number, per_commitment_point, secp_ctx), - value: to_broadcaster_value_sat, - }, - None, - )); - } - - if to_broadcaster_value_sat > Amount::ZERO || !htlcs.is_empty() { - if let Some(txout) = signer.get_anchor_txout(is_holder_tx, true) { - txouts.push((txout, None)); - } - } - - if to_countersignatory_value_sat > Amount::ZERO || !htlcs.is_empty() { - if let Some(txout) = signer.get_anchor_txout(is_holder_tx, false) { - txouts.push((txout, None)); - } - } - - let mut sorted_htlcs = Vec::with_capacity(htlcs.len()); - for htlc in htlcs { - let txout = TxOut { - script_pubkey: signer.get_htlc_spk(htlc, is_holder_tx, per_commitment_point, secp_ctx), - value: htlc.to_bitcoin_amount(), - }; - txouts.push((txout, Some(htlc))); - } - - // Sort output in BIP-69 order (amount, scriptPubkey). Tie-breaks based on HTLC - // CLTV expiration height. - sort_outputs(&mut txouts, |a, b| { - if let &Some(ref a_htlcout) = a { - if let &Some(ref b_htlcout) = b { - a_htlcout.cltv_expiry.cmp(&b_htlcout.cltv_expiry) - // Note that due to hash collisions, we have to have a fallback comparison - // here for fuzzing mode (otherwise at least chanmon_fail_consistency - // may fail)! - .then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0)) - // For non-HTLC outputs, if they're copying our SPK we don't really care if we - // close the channel due to mismatches - they're doing something dumb: - } else { cmp::Ordering::Equal } - } else { cmp::Ordering::Equal } - }); - - let mut outputs = Vec::with_capacity(txouts.len()); - for (idx, out) in txouts.drain(..).enumerate() { - if let Some(htlc) = out.1 { - htlc.transaction_output_index = Some(idx as u32); - sorted_htlcs.push(htlc.clone()); - } - outputs.push(out.0); - } - Ok((outputs, sorted_htlcs)) - } - fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec) { let broadcaster_pubkeys = channel_parameters.broadcaster_pubkeys(); let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys(); diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index a21e0250946..d64a35b6733 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -62,6 +62,7 @@ use crate::ln::msgs::PartialSignatureWithNonce; use crate::ln::msgs::{UnsignedChannelAnnouncement, UnsignedGossipMessage}; use crate::ln::script::ShutdownScript; use crate::offers::invoice::UnsignedBolt12Invoice; +use crate::sign::transaction_utils::sort_outputs; use crate::types::payment::PaymentPreimage; use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer}; use crate::util::transaction_utils; @@ -76,6 +77,7 @@ use crate::sign::ecdsa::EcdsaChannelSigner; use crate::sign::taproot::TaprootChannelSigner; use crate::types::features::ChannelTypeFeatures; use crate::util::atomic_counter::AtomicCounter; +use core::cmp; use core::convert::TryInto; use core::ops::Deref; use core::sync::atomic::{AtomicUsize, Ordering}; @@ -1125,6 +1127,102 @@ pub trait ChannelSigner { // redeemscript push length. 1 + 73 + 1 + chan_utils::REVOKEABLE_REDEEMSCRIPT_MAX_LENGTH as u64 + 1 } + + /// Builds the outputs of a commitment transaction + /// + /// This is used in two cases: + /// - initial sorting of outputs / HTLCs in the constructor + /// - building of a bitcoin transaction during a `CommitmentTransaction::verify` call + fn build_outputs( + &self, per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, + to_countersignatory_value_sat: Amount, htlcs: Vec<&mut HTLCOutputInCommitment>, + secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64, + ) -> Result<(Vec, Vec), ()> { + let mut txouts: Vec<(TxOut, Option<&mut HTLCOutputInCommitment>)> = Vec::new(); + + if to_countersignatory_value_sat > Amount::ZERO { + txouts.push(( + TxOut { + script_pubkey: self.get_counterparty_payment_script(is_holder_tx), + value: to_countersignatory_value_sat, + }, + None, + )) + } + + if to_broadcaster_value_sat > Amount::ZERO { + txouts.push(( + TxOut { + script_pubkey: self.get_revokeable_spk( + is_holder_tx, + commitment_number, + per_commitment_point, + secp_ctx, + ), + value: to_broadcaster_value_sat, + }, + None, + )); + } + + if to_broadcaster_value_sat > Amount::ZERO || !htlcs.is_empty() { + if let Some(txout) = self.get_anchor_txout(is_holder_tx, true) { + txouts.push((txout, None)); + } + } + + if to_countersignatory_value_sat > Amount::ZERO || !htlcs.is_empty() { + if let Some(txout) = self.get_anchor_txout(is_holder_tx, false) { + txouts.push((txout, None)); + } + } + + let mut sorted_htlcs = Vec::with_capacity(htlcs.len()); + for htlc in htlcs { + let txout = TxOut { + script_pubkey: self.get_htlc_spk( + htlc, + is_holder_tx, + per_commitment_point, + secp_ctx, + ), + value: htlc.to_bitcoin_amount(), + }; + txouts.push((txout, Some(htlc))); + } + + // Sort output in BIP-69 order (amount, scriptPubkey). Tie-breaks based on HTLC + // CLTV expiration height. + sort_outputs(&mut txouts, |a, b| { + if let &Some(ref a_htlcout) = a { + if let &Some(ref b_htlcout) = b { + a_htlcout + .cltv_expiry + .cmp(&b_htlcout.cltv_expiry) + // Note that due to hash collisions, we have to have a fallback comparison + // here for fuzzing mode (otherwise at least chanmon_fail_consistency + // may fail)! + .then(a_htlcout.payment_hash.0.cmp(&b_htlcout.payment_hash.0)) + // For non-HTLC outputs, if they're copying our SPK we don't really care if we + // close the channel due to mismatches - they're doing something dumb: + } else { + cmp::Ordering::Equal + } + } else { + cmp::Ordering::Equal + } + }); + + let mut outputs = Vec::with_capacity(txouts.len()); + for (idx, out) in txouts.into_iter().enumerate() { + if let Some(htlc) = out.1 { + htlc.transaction_output_index = Some(idx as u32); + sorted_htlcs.push(htlc.clone()); + } + outputs.push(out.0); + } + Ok((outputs, sorted_htlcs)) + } } /// Specifies the recipient of an invoice. From 09cbb8b52289188506682395d4f8d836ef17ed1d Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 27 Jan 2025 17:33:11 +0000 Subject: [PATCH 46/47] Let `ChannelSigner` build the inputs of the commit tx --- lightning/src/ln/chan_utils.rs | 36 +++------------------- lightning/src/sign/mod.rs | 37 ++++++++++++++++++++++- lightning/src/util/test_channel_signer.rs | 2 -- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 8e811406173..756e7884717 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -40,7 +40,6 @@ use bitcoin::{secp256k1, Sequence, Witness}; use crate::io; use core::cmp; -use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use core::ops::Deref; use crate::chain; use crate::types::features::ChannelTypeFeatures; @@ -1454,7 +1453,7 @@ impl CommitmentTransaction { let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs_with_aux.iter_mut().map(|(htlc, _)| htlc).collect(); let (outputs, sorted_htlcs) = signer.build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, commitment_number).unwrap(); - let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(commitment_number, channel_parameters); + let (obscured_commitment_transaction_number, txins) = signer.build_inputs(commitment_number, is_holder_tx); let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); CommitmentTransaction { @@ -1481,8 +1480,8 @@ impl CommitmentTransaction { self } - fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, channel_parameters: &DirectedChannelTransactionParameters, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { - let (obscured_commitment_transaction_number, txins) = Self::internal_build_inputs(self.commitment_number, channel_parameters); + fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { + let (obscured_commitment_transaction_number, txins) = signer.build_inputs(self.commitment_number, is_holder_tx); let mut htlcs: Vec<_> = self.htlcs.iter().map(|h| h.clone()).collect(); let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs.iter_mut().collect(); @@ -1506,31 +1505,6 @@ impl CommitmentTransaction { } } - fn internal_build_inputs(commitment_number: u64, channel_parameters: &DirectedChannelTransactionParameters) -> (u64, Vec) { - let broadcaster_pubkeys = channel_parameters.broadcaster_pubkeys(); - let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys(); - let commitment_transaction_number_obscure_factor = get_commitment_transaction_number_obscure_factor( - &broadcaster_pubkeys.payment_point, - &countersignatory_pubkeys.payment_point, - channel_parameters.is_outbound(), - ); - - let obscured_commitment_transaction_number = - commitment_transaction_number_obscure_factor ^ (INITIAL_COMMITMENT_NUMBER - commitment_number); - - let txins = { - let ins: Vec = vec![TxIn { - previous_output: channel_parameters.funding_outpoint(), - script_sig: ScriptBuf::new(), - sequence: Sequence(((0x80 as u32) << 8 * 3) - | ((obscured_commitment_transaction_number >> 3 * 8) as u32)), - witness: Witness::new(), - }]; - ins - }; - (obscured_commitment_transaction_number, txins) - } - /// The backwards-counting commitment number pub fn commitment_number(&self) -> u64 { self.commitment_number @@ -1582,10 +1556,10 @@ impl CommitmentTransaction { /// /// An external validating signer must call this method before signing /// or using the built transaction. - pub fn verify(&self, channel_parameters: &DirectedChannelTransactionParameters, secp_ctx: &Secp256k1, signer: &Signer, is_holder_tx: bool) -> Result { + pub fn verify(&self, secp_ctx: &Secp256k1, signer: &Signer, is_holder_tx: bool) -> Result { // This is the only field of the key cache that we trust let per_commitment_point = self.keys.per_commitment_point; - let tx = self.internal_rebuild_transaction(&per_commitment_point, channel_parameters, signer, secp_ctx, is_holder_tx)?; + let tx = self.internal_rebuild_transaction(&per_commitment_point, signer, secp_ctx, is_holder_tx)?; if self.built.transaction != tx.transaction || self.built.txid != tx.txid { return Err(()); } diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index d64a35b6733..81f21a020a0 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -69,9 +69,10 @@ use crate::util::transaction_utils; use crate::crypto::chacha20::ChaCha20; use crate::io::{self, Error}; +use crate::ln::channel::INITIAL_COMMITMENT_NUMBER; use crate::ln::msgs::DecodeError; use crate::prelude::*; -use crate::sign::chan_utils::TxCreationKeys; +use crate::sign::chan_utils::{get_commitment_transaction_number_obscure_factor, TxCreationKeys}; use crate::sign::ecdsa::EcdsaChannelSigner; #[cfg(taproot)] use crate::sign::taproot::TaprootChannelSigner; @@ -1223,6 +1224,40 @@ pub trait ChannelSigner { } Ok((outputs, sorted_htlcs)) } + + /// Builds the input of a commitment transaction + fn build_inputs(&self, commitment_number: u64, is_holder_tx: bool) -> (u64, Vec) { + let params = if is_holder_tx { + self.get_channel_parameters().unwrap().as_holder_broadcastable() + } else { + self.get_channel_parameters().unwrap().as_counterparty_broadcastable() + }; + let broadcaster_pubkeys = params.broadcaster_pubkeys(); + let countersignatory_pubkeys = params.countersignatory_pubkeys(); + let commitment_transaction_number_obscure_factor = + get_commitment_transaction_number_obscure_factor( + &broadcaster_pubkeys.payment_point, + &countersignatory_pubkeys.payment_point, + params.is_outbound(), + ); + + let obscured_commitment_transaction_number = commitment_transaction_number_obscure_factor + ^ (INITIAL_COMMITMENT_NUMBER - commitment_number); + + let txins = { + let ins: Vec = vec![TxIn { + previous_output: params.funding_outpoint(), + script_sig: ScriptBuf::new(), + sequence: Sequence( + ((0x80 as u32) << 8 * 3) + | ((obscured_commitment_transaction_number >> 3 * 8) as u32), + ), + witness: Witness::new(), + }]; + ins + }; + (obscured_commitment_transaction_number, txins) + } } /// Specifies the recipient of an invoice. diff --git a/lightning/src/util/test_channel_signer.rs b/lightning/src/util/test_channel_signer.rs index 2ac4e8314f1..d4e689e785c 100644 --- a/lightning/src/util/test_channel_signer.rs +++ b/lightning/src/util/test_channel_signer.rs @@ -435,7 +435,6 @@ impl Writeable for TestChannelSigner { impl TestChannelSigner { fn verify_counterparty_commitment_tx<'a>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify( - &self.inner.get_channel_parameters().unwrap().as_counterparty_broadcastable(), secp_ctx, self, false, @@ -444,7 +443,6 @@ impl TestChannelSigner { fn verify_holder_commitment_tx<'a>(&self, commitment_tx: &'a CommitmentTransaction, secp_ctx: &Secp256k1) -> TrustedCommitmentTransaction<'a> { commitment_tx.verify( - &self.inner.get_channel_parameters().unwrap().as_holder_broadcastable(), secp_ctx, self, true, From 22c9a5cb37aefb3b546c89d632c3480b4f4f0c5d Mon Sep 17 00:00:00 2001 From: Leo Nash Date: Mon, 27 Jan 2025 18:00:31 +0000 Subject: [PATCH 47/47] Let `ChannelSigner` build the commit tx --- lightning/src/ln/chan_utils.rs | 18 ++---------------- lightning/src/sign/mod.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 756e7884717..6e02e6c459c 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -1451,10 +1451,8 @@ impl CommitmentTransaction { // Sort outputs and populate output indices while keeping track of the auxiliary data let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs_with_aux.iter_mut().map(|(htlc, _)| htlc).collect(); - let (outputs, sorted_htlcs) = signer.build_outputs(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, commitment_number).unwrap(); + let (transaction, sorted_htlcs) = signer.build_transaction(&keys.per_commitment_point, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, commitment_number); - let (obscured_commitment_transaction_number, txins) = signer.build_inputs(commitment_number, is_holder_tx); - let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); CommitmentTransaction { commitment_number, @@ -1481,13 +1479,10 @@ impl CommitmentTransaction { } fn internal_rebuild_transaction(&self, per_commitment_point: &PublicKey, signer: &Signer, secp_ctx: &Secp256k1, is_holder_tx: bool) -> Result { - let (obscured_commitment_transaction_number, txins) = signer.build_inputs(self.commitment_number, is_holder_tx); - let mut htlcs: Vec<_> = self.htlcs.iter().map(|h| h.clone()).collect(); let htlcs: Vec<&mut HTLCOutputInCommitment> = htlcs.iter_mut().collect(); - let (outputs, _) = signer.build_outputs(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, self.commitment_number)?; + let (transaction, _) = signer.build_transaction(per_commitment_point, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, htlcs, secp_ctx, is_holder_tx, self.commitment_number); - let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.compute_txid(); let built_transaction = BuiltCommitmentTransaction { transaction, @@ -1496,15 +1491,6 @@ impl CommitmentTransaction { Ok(built_transaction) } - fn make_transaction(obscured_commitment_transaction_number: u64, txins: Vec, outputs: Vec) -> Transaction { - Transaction { - version: Version::TWO, - lock_time: LockTime::from_consensus(((0x20 as u32) << 8 * 3) | ((obscured_commitment_transaction_number & 0xffffffu64) as u32)), - input: txins, - output: outputs, - } - } - /// The backwards-counting commitment number pub fn commitment_number(&self) -> u64 { self.commitment_number diff --git a/lightning/src/sign/mod.rs b/lightning/src/sign/mod.rs index 81f21a020a0..4a1b1d7fd58 100644 --- a/lightning/src/sign/mod.rs +++ b/lightning/src/sign/mod.rs @@ -1258,6 +1258,39 @@ pub trait ChannelSigner { }; (obscured_commitment_transaction_number, txins) } + + /// Builds a commitment transaction + fn build_transaction( + &self, per_commitment_point: &PublicKey, to_broadcaster_value_sat: Amount, + to_countersignatory_value_sat: Amount, htlcs: Vec<&mut HTLCOutputInCommitment>, + secp_ctx: &Secp256k1, is_holder_tx: bool, commitment_number: u64, + ) -> (Transaction, Vec) { + let (obscured_commitment_transaction_number, txins) = + self.build_inputs(commitment_number, is_holder_tx); + let (txouts, sorted_htlcs) = self + .build_outputs( + per_commitment_point, + to_broadcaster_value_sat, + to_countersignatory_value_sat, + htlcs, + secp_ctx, + is_holder_tx, + commitment_number, + ) + .unwrap(); + ( + Transaction { + version: Version::TWO, + lock_time: LockTime::from_consensus( + ((0x20 as u32) << 8 * 3) + | ((obscured_commitment_transaction_number & 0xffffffu64) as u32), + ), + input: txins, + output: txouts, + }, + sorted_htlcs, + ) + } } /// Specifies the recipient of an invoice.