Skip to content

Commit aef76eb

Browse files
Antoine RiardAntoine Riard
Antoine Riard
authored and
Antoine Riard
committed
Anchor: do not aggregate claim of revoked output
See lightning/bolts#803 This protect the justice claim of counterparty revoked output. As otherwise if the all the revoked outputs claims are batched in a single transaction, low-feerate HTLCs transactions can delay our honest justice claim transaction until BREAKDOWN_TIMEOUT expires.
1 parent 505102d commit aef76eb

File tree

2 files changed

+30
-22
lines changed

2 files changed

+30
-22
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,8 +2436,10 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
24362436
// First, process non-htlc outputs (to_holder & to_counterparty)
24372437
for (idx, outp) in tx.output.iter().enumerate() {
24382438
if outp.script_pubkey == revokeable_p2wsh {
2439-
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);
2440-
let justice_package = PackageTemplate::build_package(commitment_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), height + self.counterparty_commitment_params.on_counterparty_tx_csv as u32, true, height);
2439+
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.opt_anchors(), true);
2440+
// Post-anchor, aggregation of outputs of different types is unsafe. See https://github.com/lightning/bolts/pull/803.
2441+
let aggregation = if self.onchain_tx_handler.opt_anchors() { false } else { true };
2442+
let justice_package = PackageTemplate::build_package(commitment_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp), height + self.counterparty_commitment_params.on_counterparty_tx_csv as u32, aggregation, height);
24412443
claimable_outpoints.push(justice_package);
24422444
to_counterparty_output_info =
24432445
Some((idx.try_into().expect("Txn can't have more than 2^32 outputs"), outp.value));
@@ -2624,7 +2626,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
26242626
let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
26252627

26262628
log_error!(logger, "Got broadcast of revoked counterparty HTLC transaction, spending {}:{}", htlc_txid, 0);
2627-
let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, tx.output[0].value, self.counterparty_commitment_params.on_counterparty_tx_csv);
2629+
let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, tx.output[0].value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.opt_anchors(), false);
26282630
let justice_package = PackageTemplate::build_package(htlc_txid, 0, PackageSolvingData::RevokedOutput(revk_outp), height + self.counterparty_commitment_params.on_counterparty_tx_csv as u32, true, height);
26292631
let claimable_outpoints = vec!(justice_package);
26302632
let outputs = vec![(0, tx.output[0].clone())];

lightning/src/chain/package.rs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -97,30 +97,36 @@ pub(crate) struct RevokedOutput {
9797
weight: u64,
9898
amount: u64,
9999
on_counterparty_tx_csv: u16,
100+
opt_anchors: Option<()>,
101+
is_counterparty_balance_or_non_anchors: Option<()>,
100102
}
101103

102104
impl RevokedOutput {
103-
pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, per_commitment_key: SecretKey, amount: u64, on_counterparty_tx_csv: u16) -> Self {
105+
pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, per_commitment_key: SecretKey, amount: u64, on_counterparty_tx_csv: u16, opt_anchors: bool, is_counterparty_balance: bool) -> Self {
104106
RevokedOutput {
105107
per_commitment_point,
106108
counterparty_delayed_payment_base_key,
107109
counterparty_htlc_base_key,
108110
per_commitment_key,
109111
weight: WEIGHT_REVOKED_OUTPUT,
110112
amount,
111-
on_counterparty_tx_csv
113+
on_counterparty_tx_csv,
114+
opt_anchors: if opt_anchors { Some(()) } else { None },
115+
is_counterparty_balance_or_non_anchors: if is_counterparty_balance { Some(()) } else { None },
112116
}
113117
}
114118
}
115119

116120
impl_writeable_tlv_based!(RevokedOutput, {
117121
(0, per_commitment_point, required),
122+
(1, is_counterparty_balance_or_non_anchors, option),
118123
(2, counterparty_delayed_payment_base_key, required),
119124
(4, counterparty_htlc_base_key, required),
120125
(6, per_commitment_key, required),
121126
(8, weight, required),
122127
(10, amount, required),
123128
(12, on_counterparty_tx_csv, required),
129+
(14, opt_anchors, option),
124130
});
125131

126132
/// A struct to describe a revoked offered output and corresponding information to generate a
@@ -750,14 +756,7 @@ impl PackageTemplate {
750756
}
751757

752758
pub (crate) fn build_package(txid: Txid, vout: u32, input_solving_data: PackageSolvingData, soonest_conf_deadline: u32, aggregable: bool, height_original: u32) -> Self {
753-
let malleability = match input_solving_data {
754-
PackageSolvingData::RevokedOutput(..) => PackageMalleability::Malleable,
755-
PackageSolvingData::RevokedHTLCOutput(..) => PackageMalleability::Malleable,
756-
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => PackageMalleability::Malleable,
757-
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => PackageMalleability::Malleable,
758-
PackageSolvingData::HolderHTLCOutput(..) => PackageMalleability::Untractable,
759-
PackageSolvingData::HolderFundingOutput(..) => PackageMalleability::Untractable,
760-
};
759+
let (malleability, aggregable) = Self::map_output_type_flags(&input_solving_data);
761760
let mut inputs = Vec::with_capacity(1);
762761
inputs.push((BitcoinOutPoint { txid, vout }, input_solving_data));
763762
PackageTemplate {
@@ -770,6 +769,20 @@ impl PackageTemplate {
770769
height_original,
771770
}
772771
}
772+
773+
fn map_output_type_flags(input_solving_data: &PackageSolvingData) -> (PackageMalleability, bool) {
774+
let (malleability, aggregable) = match input_solving_data {
775+
PackageSolvingData::RevokedOutput(RevokedOutput { is_counterparty_balance_or_non_anchors: None, .. }) => { (PackageMalleability::Malleable, true) },
776+
PackageSolvingData::RevokedOutput(RevokedOutput { opt_anchors: Some(..), .. }) => { (PackageMalleability::Malleable, false) },
777+
PackageSolvingData::RevokedOutput(RevokedOutput { opt_anchors: None, .. }) => { (PackageMalleability::Malleable, true) },
778+
PackageSolvingData::RevokedHTLCOutput(..) => { (PackageMalleability::Malleable, true) },
779+
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => { (PackageMalleability::Malleable, true) },
780+
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => { (PackageMalleability::Malleable, false) },
781+
PackageSolvingData::HolderHTLCOutput(..) => { (PackageMalleability::Untractable, false) },
782+
PackageSolvingData::HolderFundingOutput(..) => { (PackageMalleability::Untractable, false) },
783+
};
784+
(malleability, aggregable)
785+
}
773786
}
774787

775788
impl Writeable for PackageTemplate {
@@ -799,14 +812,7 @@ impl Readable for PackageTemplate {
799812
inputs.push((outpoint, rev_outp));
800813
}
801814
let (malleability, aggregable) = if let Some((_, lead_input)) = inputs.first() {
802-
match lead_input {
803-
PackageSolvingData::RevokedOutput(..) => { (PackageMalleability::Malleable, true) },
804-
PackageSolvingData::RevokedHTLCOutput(..) => { (PackageMalleability::Malleable, true) },
805-
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => { (PackageMalleability::Malleable, true) },
806-
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => { (PackageMalleability::Malleable, false) },
807-
PackageSolvingData::HolderHTLCOutput(..) => { (PackageMalleability::Untractable, false) },
808-
PackageSolvingData::HolderFundingOutput(..) => { (PackageMalleability::Untractable, false) },
809-
}
815+
Self::map_output_type_flags(&lead_input)
810816
} else { return Err(DecodeError::InvalidValue); };
811817
let mut soonest_conf_deadline = 0;
812818
let mut feerate_previous = 0;
@@ -930,7 +936,7 @@ mod tests {
930936
{
931937
let dumb_scalar = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap();
932938
let dumb_point = PublicKey::from_secret_key(&$secp_ctx, &dumb_scalar);
933-
PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, dumb_point, dumb_point, dumb_scalar, 0, 0))
939+
PackageSolvingData::RevokedOutput(RevokedOutput::build(dumb_point, dumb_point, dumb_point, dumb_scalar, 0, 0, false, false))
934940
}
935941
}
936942
}

0 commit comments

Comments
 (0)