Skip to content

Commit ba7d77a

Browse files
committed
Fix HTLC-output-in-commitment sorting for duplicate-HTLCs
This resolves both an issue that hits fuzzing due to hash collisions as well as implements an update to the BOLT spec.
1 parent 35853a6 commit ba7d77a

File tree

2 files changed

+22
-8
lines changed

2 files changed

+22
-8
lines changed

src/ln/channel.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,19 @@ impl Channel {
934934
}, None));
935935
}
936936

937-
transaction_utils::sort_outputs(&mut txouts);
937+
transaction_utils::sort_outputs(&mut txouts, |a, b| {
938+
if let &Some(ref a_htlc) = a {
939+
if let &Some(ref b_htlc) = b {
940+
a_htlc.0.cltv_expiry.cmp(&b_htlc.0.cltv_expiry)
941+
// Note that due to hash collisions, we have to have a fallback comparison
942+
// here for fuzztarget mode (otherwise at least chanmon_fail_consistency
943+
// may fail)!
944+
.then(a_htlc.0.payment_hash.0.cmp(&b_htlc.0.payment_hash.0))
945+
// For non-HTLC outputs, if they're copying our SPK we don't really care if we
946+
// close the channel due to mismatches - they're doing something dumb:
947+
} else { cmp::Ordering::Equal }
948+
} else { cmp::Ordering::Equal }
949+
});
938950

939951
let mut outputs: Vec<TxOut> = Vec::with_capacity(txouts.len());
940952
let mut htlcs_included: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(txouts.len() + included_dust_htlcs.len());
@@ -1010,7 +1022,7 @@ impl Channel {
10101022
}, ()));
10111023
}
10121024

1013-
transaction_utils::sort_outputs(&mut txouts);
1025+
transaction_utils::sort_outputs(&mut txouts, |_, _| { cmp::Ordering::Equal }); // Ordering doesnt matter if they used our pubkey...
10141026

10151027
let mut outputs: Vec<TxOut> = Vec::new();
10161028
for out in txouts.drain(..) {

src/util/transaction_utils.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ use bitcoin_hashes::sha256d::Hash as Sha256dHash;
33

44
use std::cmp::Ordering;
55

6-
pub fn sort_outputs<T>(outputs: &mut Vec<(TxOut, T)>) {
6+
pub fn sort_outputs<T, C : Fn(&T, &T) -> Ordering>(outputs: &mut Vec<(TxOut, T)>, tie_breaker: C) {
77
outputs.sort_unstable_by(|a, b| {
88
a.0.value.cmp(&b.0.value).then(
9-
a.0.script_pubkey[..].cmp(&b.0.script_pubkey[..])
9+
a.0.script_pubkey[..].cmp(&b.0.script_pubkey[..]).then_with(
10+
|| { tie_breaker(&a.1, &b.1) }
11+
)
1012
)
1113
});
1214
}
@@ -58,7 +60,7 @@ mod tests {
5860
let txout2_ = txout2.clone();
5961

6062
let mut outputs = vec![(txout1, "ignore"), (txout2, "ignore")];
61-
sort_outputs(&mut outputs);
63+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
6264

6365
assert_eq!(
6466
&outputs,
@@ -81,7 +83,7 @@ mod tests {
8183
let txout2_ = txout2.clone();
8284

8385
let mut outputs = vec![(txout1, "ignore"), (txout2, "ignore")];
84-
sort_outputs(&mut outputs);
86+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
8587

8688
assert_eq!(
8789
&outputs,
@@ -105,7 +107,7 @@ mod tests {
105107
let txout2_ = txout2.clone();
106108

107109
let mut outputs = vec![(txout1, "ignore"), (txout2, "ignore")];
108-
sort_outputs(&mut outputs);
110+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
109111

110112
assert_eq!(&outputs, &vec![(txout1_, "ignore"), (txout2_, "ignore")]);
111113
}
@@ -131,7 +133,7 @@ mod tests {
131133
outputs.reverse(); // prep it
132134

133135
// actually do the work!
134-
sort_outputs(&mut outputs);
136+
sort_outputs(&mut outputs, |_, _| { unreachable!(); });
135137

136138
assert_eq!(outputs, expected);
137139
}

0 commit comments

Comments
 (0)