sparrow-0001: WalletUtxosEntry.updateUtxos() ArrayList.removeAll O(N^2) UTXO diff uses List.removeAll which is O(current * previous). Fix: Set-based diff via Sets.difference (same pattern already used in WalletTransactionsEntry). MEDIUM, 250x at N=1000 UTXOs. sparrow-0002: UtxoEntry.recountMixesDone stream().anyMatch O(M*I*T) Whirlpool mix chain walk streams all wallet TXOs per input per mix. Fix: HashMap<Sha256Hash, Set<Long>> index for O(1) lookup. MEDIUM-HIGH, 50x at M=200 mixes, T=1000 TXOs. MOAD-0002 (Intertangle): EventManager is a thin Guava EventBus singleton, not a god object. Wallet/network/UI coupling is event-driven, acceptable. MOAD-0003 (Leaked Context): CLEAN, no ThreadLocal usage found. MOAD-0004 (Logged Secret): CLEAN, no private keys/mnemonics/passphrases logged. SecureChannelSession has commented-out secret logging. MOAD-0005 (Thundering Herd): CLEAN, no unsynchronized cache patterns. 4/4 unit tests PASS. UNDF-2026-000000931 through UNDF-2026-000000932.
111 lines
3.9 KiB
Java
111 lines
3.9 KiB
Java
import java.util.*;
|
|
|
|
/**
|
|
* sparrow-0002: UtxoEntry.recountMixesDone stream().anyMatch() O(M*I*T)
|
|
*
|
|
* In Whirlpool postmix, recountMixesDone walks the mix chain backwards.
|
|
* For each mix iteration, for each input, it streams all wallet TXOs to
|
|
* find a matching (hash, index) pair. With a HashMap index, the inner
|
|
* lookup is O(1) instead of O(T).
|
|
*
|
|
* Severity: MEDIUM-HIGH. Whirlpool users with 100+ mix rounds and 500+
|
|
* receive TXOs hit O(M * I * T) = O(100 * 5 * 500) = 250,000 comparisons
|
|
* vs O(M * I) = O(500) with the fix.
|
|
*/
|
|
public class sparrow_0002_test {
|
|
|
|
// Simulates a TXO with hash and index
|
|
static class FakeTxo {
|
|
final int hash;
|
|
final long index;
|
|
|
|
FakeTxo(int hash, long index) {
|
|
this.hash = hash;
|
|
this.index = index;
|
|
}
|
|
}
|
|
|
|
// Defective: stream().anyMatch() over all TXOs per input per mix
|
|
static long defectiveRecountMixesDone(List<FakeTxo> walletTxos, int mixDepth, int inputsPerMix) {
|
|
long ops = 0;
|
|
for (int mix = 0; mix < mixDepth; mix++) {
|
|
int targetHash = mix; // Each mix looks for a specific tx
|
|
long targetIndex = 0;
|
|
for (int inp = 0; inp < inputsPerMix; inp++) {
|
|
// stream().anyMatch: scans all wallet TXOs
|
|
for (FakeTxo txo : walletTxos) {
|
|
ops++;
|
|
if (txo.hash == targetHash && txo.index == targetIndex) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
// Fixed: HashMap index for O(1) lookup
|
|
static long fixedRecountMixesDone(List<FakeTxo> walletTxos, int mixDepth, int inputsPerMix) {
|
|
// Build index once: O(T)
|
|
Map<Integer, Set<Long>> txoIndex = new HashMap<>();
|
|
long ops = 0;
|
|
for (FakeTxo txo : walletTxos) {
|
|
ops++;
|
|
txoIndex.computeIfAbsent(txo.hash, k -> new HashSet<>()).add(txo.index);
|
|
}
|
|
|
|
for (int mix = 0; mix < mixDepth; mix++) {
|
|
int targetHash = mix;
|
|
long targetIndex = 0;
|
|
for (int inp = 0; inp < inputsPerMix; inp++) {
|
|
ops++;
|
|
Set<Long> indices = txoIndex.get(targetHash);
|
|
if (indices != null && indices.contains(targetIndex)) {
|
|
// found
|
|
}
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("sparrow-0002: UtxoEntry.recountMixesDone stream().anyMatch O(M*I*T)");
|
|
System.out.println("=".repeat(72));
|
|
System.out.printf("%-8s %-8s %-8s %-12s %-12s %-10s %-6s%n",
|
|
"Mixes", "Inputs", "TXOs", "Defect ops", "Fixed ops", "Ratio", "Pass");
|
|
System.out.println("-".repeat(72));
|
|
|
|
boolean allPassed = true;
|
|
|
|
int[][] configs = {
|
|
{10, 5, 100},
|
|
{50, 5, 300},
|
|
{100, 5, 500},
|
|
{200, 5, 1000}
|
|
};
|
|
|
|
for (int[] cfg : configs) {
|
|
int mixDepth = cfg[0];
|
|
int inputsPerMix = cfg[1];
|
|
int numTxos = cfg[2];
|
|
|
|
List<FakeTxo> walletTxos = new ArrayList<>();
|
|
for (int i = 0; i < numTxos; i++) {
|
|
walletTxos.add(new FakeTxo(i, 0));
|
|
}
|
|
|
|
long defectOps = defectiveRecountMixesDone(walletTxos, mixDepth, inputsPerMix);
|
|
long fixedOps = fixedRecountMixesDone(walletTxos, mixDepth, inputsPerMix);
|
|
double ratio = (double) defectOps / fixedOps;
|
|
boolean pass = ratio > 1.5;
|
|
if (!pass) allPassed = false;
|
|
|
|
System.out.printf("%-8d %-8d %-8d %-12d %-12d %-10.1fx %-6s%n",
|
|
mixDepth, inputsPerMix, numTxos, defectOps, fixedOps, ratio, pass ? "PASS" : "FAIL");
|
|
}
|
|
|
|
System.out.println("-".repeat(72));
|
|
System.out.println("Result: " + (allPassed ? "ALL PASS" : "FAIL"));
|
|
System.exit(allPassed ? 0 : 1);
|
|
}
|
|
}
|