Skip to content

Commit 83ab627

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 5958604 commit 83ab627

File tree

3 files changed

+177
-39
lines changed

3 files changed

+177
-39
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 37 additions & 28 deletions
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,43 @@ 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 commitment_txid = self.holder_commitment.trust().txid();
1162-
// Check if the HTLC spends from the current holder commitment
1163-
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-
}
1172-
}
1173-
// 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() {
1175-
let commitment_txid = self.prev_holder_commitment.as_ref().unwrap().trust().txid();
1176-
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-
}
1161+
let get_signed_htlc_tx = |holder_commitment: &HolderCommitmentTransaction| {
1162+
let trusted_tx = holder_commitment.trust();
1163+
if trusted_tx.txid() != outp.txid {
1164+
return None;
11861165
}
1187-
}
1188-
htlc_tx
1166+
let (htlc_idx, htlc) = trusted_tx.htlcs().iter().enumerate()
1167+
.find(|(_, htlc)| htlc.transaction_output_index.unwrap() == outp.vout)
1168+
.unwrap();
1169+
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
1170+
let mut htlc_tx = trusted_tx.build_unsigned_htlc_tx(
1171+
&self.channel_transaction_parameters.as_holder_broadcastable(), htlc_idx, preimage,
1172+
);
1173+
1174+
let htlc_descriptor = HTLCDescriptor {
1175+
channel_derivation_parameters: ChannelDerivationParameters {
1176+
value_satoshis: self.channel_value_satoshis,
1177+
keys_id: self.channel_keys_id,
1178+
transaction_parameters: self.channel_transaction_parameters.clone(),
1179+
},
1180+
commitment_txid: trusted_tx.txid(),
1181+
per_commitment_number: trusted_tx.commitment_number(),
1182+
per_commitment_point: trusted_tx.per_commitment_point(),
1183+
feerate_per_kw: trusted_tx.feerate_per_kw(),
1184+
htlc: htlc.clone(),
1185+
preimage: preimage.clone(),
1186+
counterparty_sig: counterparty_htlc_sig.clone(),
1187+
};
1188+
let htlc_sig = self.signer.sign_holder_htlc_transaction(&htlc_tx, 0, &htlc_descriptor, &self.secp_ctx).unwrap();
1189+
htlc_tx.input[0].witness = trusted_tx.build_htlc_input_witness(
1190+
htlc_idx, &counterparty_htlc_sig, &htlc_sig, preimage,
1191+
);
1192+
Some(htlc_tx)
1193+
};
1194+
1195+
// Check if the HTLC spends from the current holder commitment first, or the previous.
1196+
get_signed_htlc_tx(&self.holder_commitment)
1197+
.or_else(|| self.prev_holder_commitment.as_ref().map(|prev_holder_commitment| get_signed_htlc_tx(prev_holder_commitment)).flatten())
11891198
}
11901199

11911200
pub(crate) fn generate_external_htlc_claim(

lightning/src/ln/chan_utils.rs

Lines changed: 30 additions & 11 deletions
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
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
@@ -1720,26 +1725,40 @@ impl<'a> TrustedCommitmentTransaction<'a> {
17201725
Ok(ret)
17211726
}
17221727

1723-
/// 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 {
1725-
let inner = self.inner;
1726-
let keys = &inner.keys;
1727-
let txid = inner.built.txid;
1728-
let this_htlc = &inner.htlcs[htlc_index];
1728+
/// Builds the second-level holder HTLC transaction for the HTLC with index `htlc_index`.
1729+
pub(crate) fn build_unsigned_htlc_tx(
1730+
&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize,
1731+
preimage: &Option<PaymentPreimage>,
1732+
) -> Transaction {
1733+
let keys = &self.inner.keys;
1734+
let this_htlc = &self.inner.htlcs[htlc_index];
17291735
assert!(this_htlc.transaction_output_index.is_some());
17301736
// if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction.
17311737
if !this_htlc.offered && preimage.is_none() { unreachable!(); }
17321738
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
17331739
if this_htlc.offered && preimage.is_some() { unreachable!(); }
17341740

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);
1741+
build_htlc_transaction(
1742+
&self.inner.built.txid, self.inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc,
1743+
&self.channel_type_features, &keys.broadcaster_delayed_payment_key, &keys.revocation_key
1744+
)
1745+
}
17361746

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);
17381747

1739-
htlc_tx.input[0].witness = chan_utils::build_htlc_input_witness(
1740-
signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
1748+
/// Builds the witness required to spend the input for the HTLC with index `htlc_index` in a
1749+
/// second-level holder HTLC transaction.
1750+
pub(crate) fn build_htlc_input_witness(
1751+
&self, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature,
1752+
preimage: &Option<PaymentPreimage>
1753+
) -> Witness {
1754+
let keys = &self.inner.keys;
1755+
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(
1756+
&self.inner.htlcs[htlc_index], &self.channel_type_features, &keys.broadcaster_htlc_key,
1757+
&keys.countersignatory_htlc_key, &keys.revocation_key
17411758
);
1742-
htlc_tx
1759+
chan_utils::build_htlc_input_witness(
1760+
signature, counterparty_signature, preimage, &htlc_redeemscript, &self.channel_type_features,
1761+
)
17431762
}
17441763

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

lightning/src/ln/monitor_tests.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,3 +2715,113 @@ 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, confirm_counterparty_commitment: 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 (_, _, chan_id, 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+
2752+
let (closing_node, other_node) = if confirm_counterparty_commitment {
2753+
(&nodes[1], &nodes[0])
2754+
} else {
2755+
(&nodes[0], &nodes[1])
2756+
};
2757+
2758+
closing_node.node.force_close_broadcasting_latest_txn(&chan_id, &other_node.node.get_our_node_id()).unwrap();
2759+
2760+
// The commitment transaction comes first.
2761+
let commitment_tx = {
2762+
let mut txn = closing_node.tx_broadcaster.unique_txn_broadcast();
2763+
assert_eq!(txn.len(), 1);
2764+
check_spends!(txn[0], funding_tx);
2765+
txn.pop().unwrap()
2766+
};
2767+
2768+
mine_transaction(closing_node, &commitment_tx);
2769+
check_added_monitors!(closing_node, 1);
2770+
check_closed_broadcast!(closing_node, true);
2771+
check_closed_event!(closing_node, 1, ClosureReason::HolderForceClosed, [other_node.node.get_our_node_id()], 1_000_000);
2772+
2773+
mine_transaction(other_node, &commitment_tx);
2774+
check_added_monitors!(other_node, 1);
2775+
check_closed_broadcast!(other_node, true);
2776+
check_closed_event!(other_node, 1, ClosureReason::CommitmentTxConfirmed, [closing_node.node.get_our_node_id()], 1_000_000);
2777+
2778+
// If we update the best block to the new height before providing the confirmed transactions,
2779+
// we'll see another broadcast of the commitment transaction.
2780+
if anchors && !confirm_counterparty_commitment && nodes[0].connect_style.borrow().updates_best_block_first() {
2781+
let _ = nodes[0].tx_broadcaster.txn_broadcast();
2782+
}
2783+
2784+
// Then comes the HTLC timeout transaction.
2785+
if confirm_counterparty_commitment {
2786+
connect_blocks(&nodes[0], 5);
2787+
test_spendable_output(&nodes[0], &commitment_tx, false);
2788+
connect_blocks(&nodes[0], TEST_FINAL_CLTV - 5);
2789+
} else {
2790+
connect_blocks(&nodes[0], TEST_FINAL_CLTV);
2791+
}
2792+
if anchors && !confirm_counterparty_commitment {
2793+
handle_bump_htlc_event(&nodes[0], 1);
2794+
}
2795+
let htlc_timeout_tx = {
2796+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
2797+
assert_eq!(txn.len(), 1);
2798+
let tx = if txn[0].input[0].previous_output.txid == commitment_tx.txid() {
2799+
txn[0].clone()
2800+
} else {
2801+
txn[1].clone()
2802+
};
2803+
check_spends!(tx, commitment_tx, coinbase_tx);
2804+
tx
2805+
};
2806+
2807+
// Check we rebroadcast it with a different wtxid.
2808+
nodes[0].chain_monitor.chain_monitor.rebroadcast_pending_claims();
2809+
if anchors && !confirm_counterparty_commitment {
2810+
handle_bump_htlc_event(&nodes[0], 1);
2811+
}
2812+
{
2813+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
2814+
assert_eq!(txn.len(), 1);
2815+
assert_eq!(txn[0].txid(), htlc_timeout_tx.txid());
2816+
assert_ne!(txn[0].wtxid(), htlc_timeout_tx.wtxid());
2817+
}
2818+
}
2819+
2820+
#[cfg(not(feature = "_test_vectors"))]
2821+
#[test]
2822+
fn test_monitor_claims_with_random_signatures() {
2823+
do_test_monitor_claims_with_random_signatures(false, false);
2824+
do_test_monitor_claims_with_random_signatures(false, true);
2825+
do_test_monitor_claims_with_random_signatures(true, false);
2826+
do_test_monitor_claims_with_random_signatures(true, true);
2827+
}

0 commit comments

Comments
 (0)