From 8275698f3a23256cf0d9760728e9fcd8c2bed399 Mon Sep 17 00:00:00 2001 From: Devrandom Date: Sun, 22 Aug 2021 11:08:28 +0200 Subject: [PATCH] Add anchor outputs pair in CommitmentTransaction The anchor ouputs pair is added if there are pending HTLCs. Or a a per-party anchor is added if the party has a pending balance. --- lightning/src/ln/chan_utils.rs | 145 +++++++++++++++++++++++++-- lightning/src/ln/channel.rs | 12 ++- lightning/src/ln/functional_tests.rs | 11 +- lightning/src/util/ser.rs | 6 ++ 4 files changed, 160 insertions(+), 14 deletions(-) diff --git a/lightning/src/ln/chan_utils.rs b/lightning/src/ln/chan_utils.rs index 982257b63..6bdef4307 100644 --- a/lightning/src/ln/chan_utils.rs +++ b/lightning/src/ln/chan_utils.rs @@ -36,7 +36,7 @@ use prelude::*; use core::cmp; use ln::chan_utils; use util::transaction_utils::sort_outputs; -use ln::channel::INITIAL_COMMITMENT_NUMBER; +use ln::channel::{INITIAL_COMMITMENT_NUMBER, ANCHOR_OUTPUT_VALUE_SATOSHI}; use core::ops::Deref; use chain; @@ -569,6 +569,7 @@ pub fn build_htlc_transaction(commitment_txid: &Txid, feerate_per_kw: u32, conte /// /// After 16 blocks of confirmation, an alternative satisfying witness could be: /// <> +/// (empty vector required to satisfy compliance with MINIMALIF-standard rule) #[inline] pub(crate) fn get_anchor_redeemscript(funding_pubkey: &PublicKey) -> Script { Builder::new().push_slice(&funding_pubkey.serialize()[..]) @@ -771,7 +772,7 @@ impl HolderCommitmentTransaction { funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 }) }; let mut htlcs_with_aux: Vec<(_, ())> = Vec::new(); - let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable()); + let inner = CommitmentTransaction::new_with_auxiliary_htlc_data(0, 0, 0, false, dummy_key.clone(), dummy_key.clone(), keys, 0, &mut htlcs_with_aux, &channel_parameters.as_counterparty_broadcastable()); HolderCommitmentTransaction { inner, counterparty_sig: dummy_sig, @@ -858,6 +859,8 @@ pub struct CommitmentTransaction { to_countersignatory_value_sat: u64, feerate_per_kw: u32, htlcs: Vec, + // A boolean that is serialization backwards-compatible + opt_anchors: Option<()>, // A cache of the parties' pubkeys required to construct the transaction, see doc for trust() keys: TxCreationKeys, // For access to the pre-built transaction, see doc for trust() @@ -871,6 +874,7 @@ impl PartialEq for CommitmentTransaction { self.to_countersignatory_value_sat == o.to_countersignatory_value_sat && self.feerate_per_kw == o.feerate_per_kw && self.htlcs == o.htlcs && + self.opt_anchors == o.opt_anchors && self.keys == o.keys; if eq { debug_assert_eq!(self.built.transaction, o.built.transaction); @@ -888,6 +892,7 @@ impl_writeable_tlv_based!(CommitmentTransaction, { (8, keys, required), (10, built, required), (12, htlcs, vec_type), + (14, opt_anchors, option), }); impl CommitmentTransaction { @@ -901,9 +906,9 @@ impl CommitmentTransaction { /// Only include HTLCs that are above the dust limit for the channel. /// /// (C-not exported) 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, 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, opt_anchors: bool, 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 { // 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).unwrap(); + let (outputs, htlcs) = Self::internal_build_outputs(&keys, to_broadcaster_value_sat, to_countersignatory_value_sat, htlcs_with_aux, channel_parameters, opt_anchors, &broadcaster_funding_key, &countersignatory_funding_key).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); @@ -914,6 +919,7 @@ impl CommitmentTransaction { to_countersignatory_value_sat, feerate_per_kw, htlcs, + opt_anchors: if opt_anchors { Some(()) } else { None }, keys, built: BuiltCommitmentTransaction { transaction, @@ -922,11 +928,11 @@ impl CommitmentTransaction { } } - fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters) -> Result { + fn internal_rebuild_transaction(&self, keys: &TxCreationKeys, channel_parameters: &DirectedChannelTransactionParameters, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> 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)?; + let (outputs, _) = Self::internal_build_outputs(keys, self.to_broadcaster_value_sat, self.to_countersignatory_value_sat, &mut htlcs_with_aux, channel_parameters, self.opt_anchors.is_some(), broadcaster_funding_key, countersignatory_funding_key)?; let transaction = Self::make_transaction(obscured_commitment_transaction_number, txins, outputs); let txid = transaction.txid(); @@ -950,7 +956,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: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters) -> Result<(Vec, Vec), ()> { + fn internal_build_outputs(keys: &TxCreationKeys, to_broadcaster_value_sat: u64, to_countersignatory_value_sat: u64, htlcs_with_aux: &mut Vec<(HTLCOutputInCommitment, T)>, channel_parameters: &DirectedChannelTransactionParameters, opt_anchors: bool, broadcaster_funding_key: &PublicKey, countersignatory_funding_key: &PublicKey) -> Result<(Vec, Vec), ()> { let countersignatory_pubkeys = channel_parameters.countersignatory_pubkeys(); let contest_delay = channel_parameters.contest_delay(); @@ -982,6 +988,30 @@ impl CommitmentTransaction { )); } + if opt_anchors { + if to_broadcaster_value_sat > 0 || !htlcs_with_aux.is_empty() { + let anchor_script = get_anchor_redeemscript(broadcaster_funding_key); + txouts.push(( + TxOut { + script_pubkey: anchor_script.to_v0_p2wsh(), + value: ANCHOR_OUTPUT_VALUE_SATOSHI, + }, + None, + )); + } + + if to_countersignatory_value_sat > 0 || !htlcs_with_aux.is_empty() { + let anchor_script = get_anchor_redeemscript(countersignatory_funding_key); + txouts.push(( + TxOut { + script_pubkey: anchor_script.to_v0_p2wsh(), + value: ANCHOR_OUTPUT_VALUE_SATOSHI, + }, + None, + )); + } + } + let mut htlcs = Vec::with_capacity(htlcs_with_aux.len()); for (htlc, _) in htlcs_with_aux { let script = chan_utils::get_htlc_redeemscript(&htlc, &keys); @@ -1098,7 +1128,7 @@ impl CommitmentTransaction { if keys != self.keys { return Err(()); } - let tx = self.internal_rebuild_transaction(&keys, channel_parameters)?; + let tx = self.internal_rebuild_transaction(&keys, channel_parameters, &broadcaster_keys.funding_pubkey, &countersignatory_keys.funding_pubkey)?; if self.built.transaction != tx.transaction || self.built.txid != tx.txid { return Err(()); } @@ -1236,8 +1266,105 @@ fn script_for_p2wpkh(key: &PublicKey) -> Script { #[cfg(test)] mod tests { use super::CounterpartyCommitmentSecrets; - use hex; + use ::{hex, chain}; use prelude::*; + use ln::chan_utils::{CommitmentTransaction, TxCreationKeys, ChannelTransactionParameters, CounterpartyChannelTransactionParameters, HTLCOutputInCommitment}; + use bitcoin::secp256k1::{PublicKey, SecretKey, Secp256k1}; + use util::test_utils; + use chain::keysinterface::{KeysInterface, BaseSign}; + use bitcoin::Network; + use ln::PaymentHash; + + #[test] + fn test_anchors() { + let secp_ctx = Secp256k1::new(); + + let seed = [42; 32]; + let network = Network::Testnet; + let keys_provider = test_utils::TestKeysInterface::new(&seed, network); + let signer = keys_provider.get_channel_signer(false, 3000); + let counterparty_signer = keys_provider.get_channel_signer(false, 3000); + let delayed_payment_base = &signer.pubkeys().delayed_payment_basepoint; + let per_commitment_secret = SecretKey::from_slice(&hex::decode("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 counterparty_pubkeys = counterparty_signer.pubkeys(); + let keys = TxCreationKeys::derive_new(&secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint).unwrap(); + let channel_parameters = ChannelTransactionParameters { + holder_pubkeys: holder_pubkeys.clone(), + holder_selected_contest_delay: 0, + is_outbound_from_holder: false, + counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }), + funding_outpoint: Some(chain::transaction::OutPoint { txid: Default::default(), index: 0 }) + }; + + let mut htlcs_with_aux: Vec<(_, ())> = Vec::new(); + + // Generate broadcaster and counterparty outputs + let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( + 0, 1000, 2000, + false, + holder_pubkeys.funding_pubkey, + counterparty_pubkeys.funding_pubkey, + keys.clone(), 1, + &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() + ); + assert_eq!(tx.built.transaction.output.len(), 2); + + // Generate broadcaster and counterparty outputs as well as two anchors + let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( + 0, 1000, 2000, + true, + holder_pubkeys.funding_pubkey, + counterparty_pubkeys.funding_pubkey, + keys.clone(), 1, + &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() + ); + assert_eq!(tx.built.transaction.output.len(), 4); + + // Generate broadcaster output and anchor + let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( + 0, 3000, 0, + true, + holder_pubkeys.funding_pubkey, + counterparty_pubkeys.funding_pubkey, + keys.clone(), 1, + &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() + ); + assert_eq!(tx.built.transaction.output.len(), 2); + + // Generate counterparty output and anchor + let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( + 0, 0, 3000, + true, + holder_pubkeys.funding_pubkey, + counterparty_pubkeys.funding_pubkey, + keys.clone(), 1, + &mut htlcs_with_aux, &channel_parameters.as_holder_broadcastable() + ); + assert_eq!(tx.built.transaction.output.len(), 2); + + // Generate broadcaster output, an HTLC output and two anchors + let payment_hash = PaymentHash([42; 32]); + let htlc_info = HTLCOutputInCommitment { + offered: false, + amount_msat: 1000000, + cltv_expiry: 100, + payment_hash, + transaction_output_index: None, + }; + + let tx = CommitmentTransaction::new_with_auxiliary_htlc_data( + 0, 3000, 0, + true, + holder_pubkeys.funding_pubkey, + counterparty_pubkeys.funding_pubkey, + keys.clone(), 1, + &mut vec![(htlc_info, ())], &channel_parameters.as_holder_broadcastable() + ); + assert_eq!(tx.built.transaction.output.len(), 4); + } #[test] fn test_per_commitment_storage() { diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs index 03968118a..b44a642b7 100644 --- a/lightning/src/ln/channel.rs +++ b/lightning/src/ln/channel.rs @@ -567,7 +567,9 @@ const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172; #[cfg(test)] pub const COMMITMENT_TX_WEIGHT_PER_HTLC: u64 = 172; -/// Maximmum `funding_satoshis` value, according to the BOLT #2 specification +pub const ANCHOR_OUTPUT_VALUE_SATOSHI: u64 = 330; + +/// Maximum `funding_satoshis` value, according to the BOLT #2 specification /// it's 2^24. pub const MAX_FUNDING_SATOSHIS: u64 = 1 << 24; @@ -1206,6 +1208,11 @@ impl Channel { 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); @@ -1227,6 +1234,9 @@ impl Channel { let tx = CommitmentTransaction::new_with_auxiliary_htlc_data(commitment_number, value_to_a as u64, value_to_b as u64, + false, + 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 132893957..b7eb6c006 100644 --- a/lightning/src/ln/functional_tests.rs +++ b/lightning/src/ln/functional_tests.rs @@ -1297,22 +1297,24 @@ fn test_fee_spike_violation_fails_htlc() { // Get the EnforcingSigner 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) = { + let (local_revocation_basepoint, local_htlc_basepoint, local_secret, next_local_point, local_funding) = { let chan_lock = nodes[0].node.channel_state.lock().unwrap(); let local_chan = chan_lock.by_id.get(&chan.2).unwrap(); let chan_signer = local_chan.get_signer(); let pubkeys = chan_signer.pubkeys(); (pubkeys.revocation_basepoint, pubkeys.htlc_basepoint, chan_signer.release_commitment_secret(INITIAL_COMMITMENT_NUMBER), - chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx)) + chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 2, &secp_ctx), + chan_signer.pubkeys().funding_pubkey) }; - let (remote_delayed_payment_basepoint, remote_htlc_basepoint,remote_point) = { + let (remote_delayed_payment_basepoint, remote_htlc_basepoint, remote_point, remote_funding) = { let chan_lock = nodes[1].node.channel_state.lock().unwrap(); let remote_chan = chan_lock.by_id.get(&chan.2).unwrap(); let chan_signer = remote_chan.get_signer(); let pubkeys = chan_signer.pubkeys(); (pubkeys.delayed_payment_basepoint, pubkeys.htlc_basepoint, - chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx)) + chan_signer.get_per_commitment_point(INITIAL_COMMITMENT_NUMBER - 1, &secp_ctx), + chan_signer.pubkeys().funding_pubkey) }; // Assemble the set of keys we can use for signatures for our commitment_signed message. @@ -1341,6 +1343,7 @@ fn test_fee_spike_violation_fails_htlc() { commitment_number, 95000, local_chan_balance, + false, local_funding, remote_funding, commit_tx_keys.clone(), feerate_per_kw, &mut vec![(accepted_htlc_info, ())], diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs index a7f69fcfc..0b5036bd7 100644 --- a/lightning/src/util/ser.rs +++ b/lightning/src/util/ser.rs @@ -861,3 +861,9 @@ impl Writeable for (A, B, C) { self.2.write(w) } } + +impl Readable for () { + fn read(_r: &mut R) -> Result { + Ok(()) + } +} -- 2.39.5