java-topology/whitepaper/outreach/sparrow-0002.md
russell@unturf.com 652608142a feat: close outreach doc gap — 276 docs (batches 11-16)
All projects with patches now have outreach docs. 276 new docs covering
CWE-407, CWE-312, CWE-362 across C, C++, Java, Python, Go, Rust, C#,
PHP, Ruby, JavaScript, Dart, Erlang, R, and more.

Outreach gap: 276 -> 0.
2026-04-15 13:57:42 -04:00

2.4 KiB
Raw Permalink Blame History

Sparrow Wallet — CWE-407 Disclosure Brief (sparrow-0002)

2026-04-13 · Patch available — awaiting upstream merge

Finding

An O(M×T) stream scan in UtxoEntry.recountMixesDone() at src/main/java/com/sparrowwallet/sparrow/wallet/UtxoEntry.java:149. For each mix iteration, the code streams the entire walletTxos set to find matching transaction outputs using anyMatch(), performing O(T) per input per mix step where T = wallet transaction outputs.

The Defect

sparrow-0002 (PATCHED — MEDIUM): UtxoEntry.java:149

// Rebuilt on every mix iteration:
Set<BlockTransactionHashIndex> walletTxos = postmixWallet.getWalletTxos().entrySet().stream()
    .filter(entry -> entry.getValue().getKeyPurpose() == KeyPurpose.RECEIVE)
    .map(Map.Entry::getKey).collect(Collectors.toSet());
// Then for each input:
walletTxos.stream().anyMatch(txo -> txo.getHash().equals(...) && txo.getIndex() == ...)
// O(T) per input per mix step

Complexity Proof

At T=5,000 wallet TXOs, M=10 mix iterations, I=2 inputs per mix:

  • Defective: 10 × 2 × 5,000 = 100,000 stream scans (plus set rebuild per iteration)
  • Fixed: 5,000 index build once + 10 × 2 × O(1) = 5,020 operations
  • 20× op reduction

Impact

Sparrow Wallet tracks CoinJoin mix counts for privacy analysis. recountMixesDone() fires when displaying UTXO details. Whirlpool users with many mixed UTXOs and deep mix chains trigger the worst case.

The Fix

Build a lookup index Map<Sha256Hash, Set<Long>> mapping transaction hash to output indices once before the mix-tracing loop:

Map<Sha256Hash, Set<Long>> txoIndex = new HashMap<>();
for(Map.Entry<BlockTransactionHashIndex, WalletNode> entry : postmixWallet.getWalletTxos().entrySet()) {
    if(entry.getValue().getKeyPurpose() == KeyPurpose.RECEIVE) {
        txoIndex.computeIfAbsent(txo.getHash(), k -> new HashSet<>()).add(txo.getIndex());
    }
}

Each mix step becomes O(1) via txoIndex.get(hash).contains(index).

Patch

Fix available: defects/sparrow-0002/patch/sparrow-0002.patch

20× op reduction at T=5,000, M=10.

What We Ask

  1. Confirm receipt and assign a GitHub issue reference (sparrowwallet/sparrow).
  2. Assess severity — fires on UTXO detail display for CoinJoin users.
  3. Coordinate a disclosure date — targeting 90 days from first contact.
  4. We will credit the Sparrow team in the public disclosure.

Contact: see cover email. This brief is confidential until coordinated disclosure.