benches: Add benchmark to test duplicate-inputs worst case scenario
What changed, and why it matters
This commit only adds a new performance benchmark. It does not change any library code, fix any bug, or alter behavior. The benchmark measures how fast different methods can detect duplicate transaction inputs in a worst-case scenario. There is no security issue in the commit itself.
No action required. This is a benign benchmark addition. If the project later replaces a duplicate-input check with one of these algorithms, that subsequent change should be reviewed for correctness and DoS resistance.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces a Criterion benchmark in benches/bitcoin/duplicate_inputs.rs comparing five duplicate-detection strategies (pairwise, BTreeSet, sorted, sorted_unstable, HashSet) against a worst-case input vector where the duplicate OutPoint is placed at the end. It registers the benchmark in benches/Cargo.toml. No production code is modified.
Changed components
benches/bitcoin/duplicate_inputs.rsbenches/Cargo.tomlInspect captured patch +111 / −0
diff --git a/benches/Cargo.toml b/benches/Cargo.toml
index 4836e2b8..3889c4bf 100644
--- a/benches/Cargo.toml
+++ b/benches/Cargo.toml
@@ -31,6 +31,11 @@ name = "witness"
path = "bitcoin/witness.rs"
harness = false
+[[bench]]
+name = "duplicate_inputs"
+path = "bitcoin/duplicate_inputs.rs"
+harness = false
+
[[bench]]
name = "chacha20poly1305"
diff --git a/benches/bitcoin/duplicate_inputs.rs b/benches/bitcoin/duplicate_inputs.rs
new file mode 100644
index 00000000..6b253264
--- /dev/null
+++ b/benches/bitcoin/duplicate_inputs.rs
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: CC0-1.0
+
+use std::collections::{BTreeSet, HashSet};
+use std::hint::black_box;
+
+use bitcoin::transaction::OutPoint;
+use bitcoin::Txid;
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
+
+/// we put duplicate at the very end
+fn generate_worst_case(n: usize) -> Vec<OutPoint> {
+ let mut inputs: Vec<OutPoint> = (0..n)
+ .map(|i| {
+ let mut bytes = [0u8; 32];
+ bytes[31] = i as u8;
+ OutPoint { txid: Txid::from_byte_array(bytes), vout: i as u32 }
+ })
+ .collect();
+ if n > 1 {
+ inputs[n - 1] = inputs[n - 2];
+ }
+ inputs
+}
+
+
+
+fn check_hashset(inputs: &[OutPoint]) -> bool {
+ let mut seen: HashSet<&OutPoint> = HashSet::with_capacity(inputs.len());
+ for input in inputs {
+ if !seen.insert(input) {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_pairwise(inputs: &[OutPoint]) -> bool {
+ for i in 0..inputs.len() {
+ for j in (i + 1)..inputs.len() {
+ if inputs[i] == inputs[j] {
+ return true;
+ }
+ }
+ }
+ false
+}
+
+// current implementation
+fn check_btreeset(inputs: &[OutPoint]) -> bool {
+ let mut seen = BTreeSet::new();
+ for input in inputs {
+ if !seen.insert(input) {
+ return true;
+ }
+ }
+ false
+}
+
+fn check_sorted(inputs: &[OutPoint]) -> bool {
+ let mut sorted: Vec<_> = inputs.iter().collect();
+ sorted.sort();
+ sorted.windows(2).any(|w| w[0] == w[1])
+}
+
+fn check_sorted_unstable(inputs: &[OutPoint]) -> bool {
+ let mut sorted: Vec<_> = inputs.iter().collect();
+ sorted.sort_unstable();
+ sorted.windows(2).any(|w| w[0] == w[1])
+}
+
+fn bench_duplicate_inputs(c: &mut Criterion) {
+ let mut group = c.benchmark_group("duplicate_inputs");
+
+ for size in [2, 5, 10, 50, 100, 500, 1000] {
+ let inputs = generate_worst_case(size);
+ group.throughput(Throughput::Elements(size as u64));
+
+ group.bench_with_input(BenchmarkId::new("pairwise", size), &inputs, |b, inputs| {
+ b.iter(|| black_box(check_pairwise(black_box(inputs))))
+ });
+
+ group.bench_with_input(BenchmarkId::new("btreeset", size), &inputs, |b, inputs| {
+ b.iter(|| black_box(check_btreeset(black_box(inputs))))
+ });
+
+ group.bench_with_input(BenchmarkId::new("sorted", size), &inputs, |b, inputs| {
+ b.iter(|| black_box(check_sorted(black_box(inputs))))
+ });
+
+ group.bench_with_input(
+ BenchmarkId::new("sorted_unstable", size),
+ &inputs,
+ |b, inputs| b.iter(|| black_box(check_sorted_unstable(black_box(inputs)))),
+ );
+
+ group.bench_with_input(BenchmarkId::new("hashset", size), &inputs, |b, inputs| {
+ b.iter(|| black_box(check_hashset(black_box(inputs))))
+ });
+
+ }
+
+ group.finish();
+}
+
+criterion_group!(benches, bench_duplicate_inputs);
+criterion_main!(benches);
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.