Concept · A1131

COVERAGE EVALUATION +
GREEDY CELF SELECTION

Set-Cover Optimization Lazy Greedy (CELF) Submodular Marginal Gain

How the rulest pipeline turns a huge pool of candidate rules into a small ruleset that actually maximizes distinct password recovery — instead of just summing individual rule scores — and why this consistently beats plain frequency ranking at every budget size.

2 phases
coverage evaluation · CELF selection
1 objective
| union of recovered target words |
O(log n) amortized cost per pick
vs O(rules) for naive greedy
1.7M candidate rules
this stays tractable at, via streaming
The Core Idea

Sorting candidate rules by raw GPU frequency and taking the top-N is the obvious first approach, but it optimizes the wrong quantity. Two high-frequency rules can crack almost the same subset of the target list — keeping both wastes budget that a lower-frequency, but orthogonal, rule would have spent far better. What actually matters for a ruleset of size k is not the sum of individual scores, but the size of the union of everything those k rules recover.

That's a classic set-cover problem: each rule r defines a set C(r) of the distinct target words it actually cracks, and the goal is to choose a budget-sized collection of rules whose combined C(r) sets cover as much of the target universe as possible. Set cover is NP-hard to solve exactly, but the coverage function is submodular — each additional rule can only add as much or less than it would have earlier, never more — which is exactly the property that makes a simple greedy strategy provably close to optimal (within a factor of 1 − 1/e, roughly 63%, of the true best selection).

Phase 1 — Coverage Evaluation
01

GPU bloom-filter prefilter

Every candidate rule is dispatched to the GPU against the full base wordlist using a fast bloom-filter kernel. This cheaply flags which (rule, base_word) pairs are probably a hit, without paying the cost of exact string verification on the entire candidate pool up front.

02

Exact verification of every bloom-positive pair

Bloom filters admit false positives, and a set-cover objective is only meaningful if its universe is the thing actually being maximized. So every bloom-positive pair is re-run through an exact verification kernel, and only outputs that literally match a real target-wordlist entry are kept. This removes bloom false positives before they can distort the optimization.

03

Build C(r) against distinct target IDs

Each verified hit is mapped to an integer ID in the deduplicated target-word universe, so a rule's coverage is "which distinct target words does this rule actually recover" — not "which base words happened to trigger a bloom hit." This is the same distinction that separates a meaningful cover objective from a proxy that can be gamed.

04

Stream results, never materialize the full matrix

Each GPU batch's coverage rows are written straight to a coverage store and dropped from memory immediately after — the full {rule: hits} matrix is never assembled at once. Below a size threshold this store is a plain in-memory dict (zero overhead); above it, it's disk-backed, so candidate pools that don't fit in RAM at all remain usable instead of forcing a smaller run.

Phase 2 — Greedy CELF Selection
05

Naive greedy: correct, but too slow at scale

The textbook greedy algorithm repeatedly scans every remaining candidate, recomputes its marginal gain against the current covered set, and picks the best one — then repeats from scratch. That's O(rules × budget) evaluations, which is fine at a few thousand candidates but becomes the bottleneck well before 100k, let alone the 1.7M-candidate pools this pipeline routinely evaluates.

06

CELF: lazy re-validation via a max-heap

The Cost-Effective Lazy Forward selection algorithm exploits submodularity directly: since a rule's marginal gain can only shrink (never grow) as more of the target universe gets covered, a rule whose gain was already computed doesn't need to be rescanned — it only needs to be re-checked if it reaches the top of a priority queue. Each step pops the current best candidate off a heap, recomputes its gain against the up-to-date covered set, and either accepts it immediately (if it's still the best) or re-inserts it at its corrected priority and pops again.

07

Boolean mask coverage, not Python set unions

The covered set is tracked as a single numpy boolean array over the target-word universe, updated in place with fancy indexing, rather than as Python set() unions. At 150k–1.7M candidates this avoids repeated hashing and boxed-integer churn, keeping each marginal-gain check a cheap array operation instead of an object-heavy one.

08

Stop at budget, or at saturation

Selection continues until either the requested rule budget is reached, or no remaining candidate adds any new coverage at all (saturation). An optional cost term (gain / depth^α) can bias the search toward shorter, cheaper rule chains among candidates with similar marginal gain — pure marginal-coverage greedy is the α = 0 default.

09

Optional 1-swap local search

After greedy selection, an optional local-search pass tries swapping each selected rule for a promising unselected one, accepting the swap only if it strictly increases total recovery. This nudges the result slightly past greedy's own local optimum at very low extra cost, since only a residual candidate pool — not the full candidate set — needs to be re-scored.

Why This Beats Frequency Ranking

Frequency ranking asks "how many hits does this rule get on its own?" Greedy CELF asks "how many new passwords does this rule recover that nothing already selected recovers?" Those are different questions, and at every budget size the second one is the one that actually determines how many unique passwords a ruleset of size k will crack. The practical result is a ruleset that reaches a given coverage percentage with dramatically fewer rules — or reaches a much higher coverage percentage at the same rule count — than a frequency-sorted list ever can, because frequency ranking has no mechanism to notice or penalize redundancy between rules.

The submodularity guarantee: because marginal gains only ever shrink as more gets covered, CELF's lazy re-validation is not an approximation shortcut — it produces the exact same selection as naive greedy, just without the wasted rescans. The speed comes for free; nothing is traded away for it.
Benchmark Comparison (Same Corpus)

Single-run results on base = hashmob.mini, target = hashmob.medium. Each pair below holds ruleset size fixed and compares greedy CELF selection against plain frequency ranking.

Ruleset Cover Size Efficiency
rulest.greedy.150000.rule36.12%150,0000.2408
rulest.freq.150000.rule28.02%150,0000.1868
rulest.greedy.50000.rule29.78%50,0000.5956
rulest.freq.50000.rule20.25%50,0000.4050
rulest.greedy.25000.rule24.91%25,0000.9964
rulest.freq.25000.rule15.99%25,0000.6396
rulest.greedy.10000.rule19.71%10,0001.971
rulest.freq.10000.rule11.44%10,0001.144
rulest.greedy.1500.rule10.45%1,5006.9667
rulest.freq.1500.rule4.61%1,5003.0733
rulest.greedy.250.rule5.06%25020.24
rulest.freq.250.rule1.73%2506.92
rulest.greedy.64.rule2.10%6432.8125
rulest.freq.64.rule0.88%6413.75
greedy (CELF marginal coverage) freq (raw GPU-frequency ranking)
Reading the table: at every fixed size, greedy roughly matches or exceeds 1.4–2.4× the coverage of frequency ranking with the same rule budget — and the gap actually widens at smaller sizes (64 rules: 2.10% vs 0.88%, nearly 2.4×), which is exactly what redundancy elimination predicts: the fewer rules you can afford, the more it costs to waste any of them on overlap.
Practical Notes
Disk-backed vs in-memory store

Below the candidate-count threshold, coverage is kept in a plain dict — zero overhead. Above it, a SQLite-backed store takes over so pools far larger than available RAM stay usable; point lookups cost roughly one order of magnitude more, negligible next to the GPU-bound evaluation phase it follows.

Budget vs saturation

A fixed budget stops selection at exactly k rules. Leaving it unset runs to saturation — every rule that can still add coverage gets added, useful for finding the practical ceiling of a candidate pool before picking a production budget.

Cost-adjusted gain (α)

Raising cost_alpha above 0 divides marginal gain by chain depth raised to that power, nudging ties toward shorter, cheaper rules — useful when hashcat runtime per candidate matters as much as raw coverage.

Tie-breaking

Raw GPU frequency is accepted as a tie-breaker input but has no effect on final ordering — the heap's own tuple comparison already decides ties deterministically by rule string, independent of any pre-sort.

Takeaway: greedy CELF selection isn't a heuristic trick layered on top of frequency ranking — it optimizes a fundamentally different, and more correct, objective (union coverage instead of summed score), and CELF's lazy heap is what makes running that correct objective affordable at candidate pools from a few thousand up to millions of rules.