Skip to content

Commit 496be8e

Browse files
committed
Use sign_holder_htlc_transaction to sign non-anchors holder HTLCs
We want to ensure we use fresh random signatures to prevent certain classes of transaction replacement attacks at the bitcoin P2P layer. This was already covered for commitment transactions and zero fee holder HTLC transactions, but was missing for holder HTLC transactions on non-anchors channels. We can easily do this by reusing the existing `EcdsaChannelSigner::sign_holder_htlc_transaction` method and circumventing the existing `holder_htlc_sigs/prev_holder_htlc_sigs` caches, which will be removed in a later commit anyway.
1 parent f5a1a3d commit 496be8e

File tree

3 files changed

+162
-26
lines changed

3 files changed

+162
-26
lines changed

lightning/src/chain/onchaintx.rs

+37-20
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
2323
use bitcoin::secp256k1;
2424

2525
use crate::chain::chaininterface::compute_feerate_sat_per_1000_weight;
26+
use crate::events::bump_transaction::{ChannelDerivationParameters, HTLCDescriptor};
2627
use crate::sign::{ChannelSigner, EntropySource, SignerProvider};
2728
use crate::ln::msgs::DecodeError;
2829
use crate::ln::PaymentPreimage;
@@ -1157,35 +1158,51 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
11571158
}
11581159

11591160
pub(crate) fn get_fully_signed_htlc_tx(&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>) -> Option<Transaction> {
1160-
let mut htlc_tx = None;
1161+
let get_signed_htlc_tx = |holder_commitment: &HolderCommitmentTransaction| {
1162+
let trusted_tx = holder_commitment.trust();
1163+
let (htlc_idx, htlc) = trusted_tx.htlcs().iter().enumerate()
1164+
.find(|(_, htlc)| htlc.transaction_output_index.unwrap() == outp.vout)
1165+
.unwrap();
1166+
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
1167+
let mut htlc_tx = trusted_tx.get_unsigned_htlc_tx(
1168+
&self.channel_transaction_parameters.as_holder_broadcastable(), htlc_idx, preimage,
1169+
);
1170+
1171+
let htlc_descriptor = HTLCDescriptor {
1172+
channel_derivation_parameters: ChannelDerivationParameters {
1173+
value_satoshis: self.channel_value_satoshis,
1174+
keys_id: self.channel_keys_id,
1175+
transaction_parameters: self.channel_transaction_parameters.clone(),
1176+
},
1177+
commitment_txid: trusted_tx.txid(),
1178+
per_commitment_number: trusted_tx.commitment_number(),
1179+
per_commitment_point: trusted_tx.per_commitment_point(),
1180+
feerate_per_kw: trusted_tx.feerate_per_kw(),
1181+
htlc: htlc.clone(),
1182+
preimage: preimage.clone(),
1183+
counterparty_sig: counterparty_htlc_sig.clone(),
1184+
};
1185+
let htlc_sig = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx).unwrap();
1186+
htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness(
1187+
htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage,
1188+
);
1189+
htlc_tx
1190+
};
1191+
11611192
let commitment_txid = self.holder_commitment.trust().txid();
11621193
// Check if the HTLC spends from the current holder commitment
11631194
if commitment_txid == outp.txid {
1164-
self.sign_latest_holder_htlcs();
1165-
if let &Some(ref htlc_sigs) = &self.holder_htlc_sigs {
1166-
let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
1167-
let trusted_tx = self.holder_commitment.trust();
1168-
let counterparty_htlc_sig = self.holder_commitment.counterparty_htlc_sigs[*htlc_idx];
1169-
htlc_tx = Some(trusted_tx
1170-
.get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage));
1171-
}
1195+
return Some(get_signed_htlc_tx(&self.holder_commitment));
11721196
}
11731197
// If the HTLC doesn't spend the current holder commitment, check if it spends the previous one
1174-
if htlc_tx.is_none() && self.prev_holder_commitment.is_some() {
1198+
if self.prev_holder_commitment.is_some() {
11751199
let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().trust().txid();
11761200
if commitment_txid == outp.txid {
1177-
self.sign_prev_holder_htlcs();
1178-
if let &Some(ref htlc_sigs) = &self.prev_holder_htlc_sigs {
1179-
let &(ref htlc_idx, ref htlc_sig) = htlc_sigs[outp.vout as usize].as_ref().unwrap();
1180-
let holder_commitment = self.prev_holder_commitment.as_ref().unwrap();
1181-
let trusted_tx = holder_commitment.trust();
1182-
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[*htlc_idx];
1183-
htlc_tx = Some(trusted_tx
1184-
.get_signed_htlc_tx(&self.channel_transaction_parameters.as_holder_broadcastable(), *htlc_idx, &counterparty_htlc_sig, htlc_sig, preimage));
1185-
}
1201+
let holder_commitment = self.prev_holder_commitment.as_ref().unwrap();
1202+
return Some(get_signed_htlc_tx(holder_commitment));
11861203
}
11871204
}
1188-
htlc_tx
1205+
None
11891206
}
11901207

11911208
pub(crate) fn generate_external_htlc_claim(

lightning/src/ln/chan_utils.rs

+26-6
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,11 @@ impl CommitmentTransaction {
15991599
self.commitment_number
16001600
}
16011601

1602+
/// The per commitment point used by the broadcaster.
1603+
pub fn per_commitment_point(&self) -> PublicKey {
1604+
self.keys.per_commitment_point.clone()
1605+
}
1606+
16021607
/// The value to be sent to the broadcaster
16031608
pub fn to_broadcaster_value_sat(&self) -> u64 {
16041609
self.to_broadcaster_value_sat
@@ -1721,7 +1726,10 @@ impl<'a> TrustedCommitmentTransaction<'a> {
17211726
}
17221727

17231728
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC transaction signature.
1724-
pub(crate) fn get_signed_htlc_tx(&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>) -> Transaction {
1729+
pub(crate) fn get_unsigned_htlc_tx(
1730+
&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize,
1731+
preimage: &Option<PaymentPreimage>,
1732+
) -> Transaction {
17251733
let inner = self.inner;
17261734
let keys = &inner.keys;
17271735
let txid = inner.built.txid;
@@ -1732,14 +1740,26 @@ impl<'a> TrustedCommitmentTransaction<'a> {
17321740
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
17331741
if this_htlc.offered && preimage.is_some() { unreachable!(); }
17341742

1735-
let mut 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);
1743+
build_htlc_transaction(
1744+
&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc,
1745+
&self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key
1746+
)
1747+
}
17361748

1737-
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);
17381749

1739-
htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness(
1740-
signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
1750+
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC transaction signature.
1751+
pub(crate) fn build_htlc_input_witness(&self, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>) -> Witness {
1752+
let inner = self.inner;
1753+
let keys = &inner.keys;
1754+
let this_htlc = &inner.htlcs[htlc_index];
1755+
1756+
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(
1757+
&this_htlc, &self.channel_type_features, &keys.broadcaster_htlc_key,
1758+
&keys.countersignatory_htlc_key, &keys.revocation_key
17411759
);
1742-
htlc_tx
1760+
chan_utils::build_htlc_input_witness(
1761+
signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
1762+
)
17431763
}
17441764

17451765
/// Returns the index of the revokeable output, i.e. the `to_local` output sending funds to

lightning/src/ln/monitor_tests.rs

+99
Original file line numberDiff line numberDiff line change
@@ -2715,3 +2715,102 @@ fn test_anchors_monitor_fixes_counterparty_payment_script_on_reload() {
27152715
do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(false);
27162716
do_test_anchors_monitor_fixes_counterparty_payment_script_on_reload(true);
27172717
}
2718+
2719+
#[cfg(not(feature = "_test_vectors"))]
2720+
fn do_test_monitor_claims_with_random_signatures(anchors: bool) {
2721+
// Tests that our monitor claims will always use fresh random signatures (ensuring a unique
2722+
// wtxid) to prevent certain classes of transaction replacement at the bitcoin P2P layer.
2723+
let chanmon_cfgs = create_chanmon_cfgs(2);
2724+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
2725+
let mut user_config = test_default_channel_config();
2726+
if anchors {
2727+
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
2728+
user_config.manually_accept_inbound_channels = true;
2729+
}
2730+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(user_config), Some(user_config)]);
2731+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
2732+
2733+
let coinbase_tx = Transaction {
2734+
version: 2,
2735+
lock_time: PackedLockTime::ZERO,
2736+
input: vec![TxIn { ..Default::default() }],
2737+
output: vec![
2738+
TxOut {
2739+
value: Amount::ONE_BTC.to_sat(),
2740+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
2741+
},
2742+
],
2743+
};
2744+
if anchors {
2745+
nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
2746+
}
2747+
2748+
// Open a channel and route a payment. We'll let it timeout to claim it.
2749+
let (_, _, _, funding_tx) = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2750+
route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
2751+
connect_blocks(&nodes[0], TEST_FINAL_CLTV + LATENCY_GRACE_PERIOD_BLOCKS + 1);
2752+
2753+
// The commitment transaction comes first.
2754+
let commitment_tx = {
2755+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
2756+
assert_eq!(txn.len(), if anchors { 1 } else { 2 });
2757+
check_spends!(txn[0], funding_tx);
2758+
txn.remove(0)
2759+
};
2760+
2761+
// Check we rebroadcast it with a different wtxid.
2762+
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
2763+
{
2764+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
2765+
assert_eq!(txn.len(), if anchors { 1 } else { 2 });
2766+
let new_commitment_tx = if txn[0].input[0].previous_output.txid == funding_tx.txid() {
2767+
&txn[0]
2768+
} else {
2769+
&txn[1]
2770+
};
2771+
assert_eq!(new_commitment_tx.txid(), commitment_tx.txid());
2772+
assert_ne!(new_commitment_tx.wtxid(), commitment_tx.wtxid());
2773+
};
2774+
2775+
mine_transaction(&nodes[0], &commitment_tx);
2776+
check_added_monitors!(nodes[0], 1);
2777+
check_closed_broadcast!(nodes[0], true);
2778+
check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1_000_000);
2779+
2780+
// Then comes the HTLC timeout transaction.
2781+
if anchors {
2782+
handle_bump_htlc_event(&nodes[0], 1);
2783+
}
2784+
let htlc_timeout_tx = {
2785+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
2786+
// If we update the best block to the new height before providing the confirmed
2787+
// transactions, we'll see another broadcast of the commitment transaction.
2788+
assert_eq!(txn.len(), if nodes[0].connect_style.borrow().updates_best_block_first() { 2 } else { 1 });
2789+
let tx = if txn[0].input[0].previous_output.txid == commitment_tx.txid() {
2790+
txn[0].clone()
2791+
} else {
2792+
txn[1].clone()
2793+
};
2794+
check_spends!(tx, commitment_tx, coinbase_tx);
2795+
tx
2796+
};
2797+
2798+
// Check we rebroadcast it with a different wtxid.
2799+
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
2800+
if anchors {
2801+
handle_bump_htlc_event(&nodes[0], 1);
2802+
}
2803+
{
2804+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
2805+
assert_eq!(txn.len(), 1);
2806+
assert_eq!(txn[0].txid(), htlc_timeout_tx.txid());
2807+
assert_ne!(txn[0].wtxid(), htlc_timeout_tx.wtxid());
2808+
}
2809+
}
2810+
2811+
#[cfg(not(feature = "_test_vectors"))]
2812+
#[test]
2813+
fn test_monitor_claims_with_random_signatures() {
2814+
do_test_monitor_claims_with_random_signatures(false);
2815+
do_test_monitor_claims_with_random_signatures(true);
2816+
}

0 commit comments

Comments
 (0)